From 60fef977bbfc384646ae3d341b9a7f779812c17e Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Tue, 16 Jul 2024 21:15:24 -0700 Subject: [PATCH 1/4] Rewrite LegacyAdapterMiddleware Target is now pulled directly from BaseRoute Doesnt replace all instances, only the start, fixes a small very unlikely bug --- .../Middlewares/LegacyAdapterMiddleware.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Refresh.GameServer/Middlewares/LegacyAdapterMiddleware.cs b/Refresh.GameServer/Middlewares/LegacyAdapterMiddleware.cs index 2a46ad5a..ce31f90a 100644 --- a/Refresh.GameServer/Middlewares/LegacyAdapterMiddleware.cs +++ b/Refresh.GameServer/Middlewares/LegacyAdapterMiddleware.cs @@ -1,23 +1,24 @@ using Bunkum.Listener.Request; using Bunkum.Core.Database; using Bunkum.Core.Endpoints.Middlewares; +using Refresh.GameServer.Endpoints; namespace Refresh.GameServer.Middlewares; public class LegacyAdapterMiddleware : IMiddleware { - private const string OldUrl = "/LITTLEBIGPLANETPS3_XML"; - private const string NewUrl = "/lbp"; + public const string OldBaseRoute = "/LITTLEBIGPLANETPS3_XML/"; + private const string NewBaseRoute = GameEndpointAttribute.BaseRoute; public void HandleRequest(ListenerContext context, Lazy database, Action next) { - if (!context.Uri.AbsolutePath.StartsWith(OldUrl)) + if (!context.Uri.AbsolutePath.StartsWith(OldBaseRoute)) { next(); return; } - context.Uri = new Uri(context.Uri, context.Uri.AbsolutePath.Replace(OldUrl, NewUrl)); + context.Uri = new Uri(context.Uri, string.Concat(NewBaseRoute, context.Uri.AbsolutePath.AsSpan()[OldBaseRoute.Length..])); next(); } From 361ab97614ed832dc914f8b49433803d1ea54930 Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Tue, 16 Jul 2024 21:19:58 -0700 Subject: [PATCH 2/4] GameDatabaseProvider: Remove accidental Console.WriteLine --- Refresh.GameServer/Database/GameDatabaseProvider.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Refresh.GameServer/Database/GameDatabaseProvider.cs b/Refresh.GameServer/Database/GameDatabaseProvider.cs index 254810bb..0d96d74a 100644 --- a/Refresh.GameServer/Database/GameDatabaseProvider.cs +++ b/Refresh.GameServer/Database/GameDatabaseProvider.cs @@ -358,8 +358,6 @@ protected override void Migrate(Migration migration, ulong oldVersion) oldSubjects.Add(new GamePhotoSubject(user, subject.DisplayName, bounds)); } - Console.WriteLine(JsonConvert.SerializeObject(oldSubjects)); - newPhoto.Subjects = oldSubjects; } } From daddff653254730d46b954747225557dd437da0d Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Tue, 16 Jul 2024 21:26:31 -0700 Subject: [PATCH 3/4] DigestMiddleware: Add support for multiple digest keys + HMAC digests --- Refresh.GameServer/Authentication/Token.cs | 7 + .../Configuration/GameServerConfig.cs | 5 +- .../Database/GameDatabaseContext.Tokens.cs | 9 + .../Database/GameDatabaseProvider.cs | 2 +- .../Middlewares/DigestMiddleware.cs | 176 +++++++++++------- Refresh.GameServer/RefreshGameServer.cs | 5 +- .../Middlewares/DigestMiddlewareTests.cs | 47 +++-- 7 files changed, 156 insertions(+), 95 deletions(-) diff --git a/Refresh.GameServer/Authentication/Token.cs b/Refresh.GameServer/Authentication/Token.cs index 98163b21..fdceab24 100644 --- a/Refresh.GameServer/Authentication/Token.cs +++ b/Refresh.GameServer/Authentication/Token.cs @@ -1,5 +1,6 @@ using System.Xml.Serialization; using Bunkum.Core.Authentication; +using JetBrains.Annotations; using MongoDB.Bson; using Realms; using Refresh.GameServer.Types.UserData; @@ -47,4 +48,10 @@ public TokenGame TokenGame public string IpAddress { get; set; } public GameUser User { get; set; } + + /// + /// The digest key to use with this token, determined from the first game request created by this token + /// + [CanBeNull] public string Digest { get; set; } + public bool IsHmacDigest { get; set; } } \ No newline at end of file diff --git a/Refresh.GameServer/Configuration/GameServerConfig.cs b/Refresh.GameServer/Configuration/GameServerConfig.cs index 4ee86fab..d06cba8b 100644 --- a/Refresh.GameServer/Configuration/GameServerConfig.cs +++ b/Refresh.GameServer/Configuration/GameServerConfig.cs @@ -9,7 +9,7 @@ namespace Refresh.GameServer.Configuration; [SuppressMessage("ReSharper", "RedundantDefaultMemberInitializer")] public class GameServerConfig : Config { - public override int CurrentConfigVersion => 16; + public override int CurrentConfigVersion => 17; public override int Version { get; set; } = 0; protected override void Migrate(int oldVer, dynamic oldConfig) {} @@ -49,4 +49,7 @@ protected override void Migrate(int oldVer, dynamic oldConfig) {} /// Whether to print the room state whenever a `FindBestRoom` match returns no results /// public bool PrintRoomStateWhenNoFoundRooms { get; set; } = true; + + public string[] Sha1DigestKeys = ["CustomServerDigest"]; + public string[] HmacDigestKeys = ["CustomServerDigest"]; } \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs b/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs index 74d6b3f3..ca2acbd8 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Tokens.cs @@ -171,6 +171,15 @@ public void DenyIpVerificationRequest(GameUser user, string ipAddress) }); } + public void SetTokenDigestInfo(Token token, string digest, bool isHmacDigest) + { + this.Write(() => + { + token.Digest = digest; + token.IsHmacDigest = isHmacDigest; + }); + } + public DatabaseList GetIpVerificationRequestsForUser(GameUser user, int count, int skip) => new(this.GameIpVerificationRequests.Where(r => r.User == user), skip, count); } \ No newline at end of file diff --git a/Refresh.GameServer/Database/GameDatabaseProvider.cs b/Refresh.GameServer/Database/GameDatabaseProvider.cs index 0d96d74a..d02e9082 100644 --- a/Refresh.GameServer/Database/GameDatabaseProvider.cs +++ b/Refresh.GameServer/Database/GameDatabaseProvider.cs @@ -32,7 +32,7 @@ protected GameDatabaseProvider(IDateTimeProvider time) this._time = time; } - protected override ulong SchemaVersion => 133; + protected override ulong SchemaVersion => 134; protected override string Filename => "refreshGameServer.realm"; diff --git a/Refresh.GameServer/Middlewares/DigestMiddleware.cs b/Refresh.GameServer/Middlewares/DigestMiddleware.cs index ee88b15c..6ff3fdeb 100644 --- a/Refresh.GameServer/Middlewares/DigestMiddleware.cs +++ b/Refresh.GameServer/Middlewares/DigestMiddleware.cs @@ -1,114 +1,158 @@ -using System.Diagnostics; using System.Security.Cryptography; +using System.Text; using Bunkum.Listener.Request; using Bunkum.Core.Database; using Bunkum.Core.Endpoints.Middlewares; using Refresh.Common.Extensions; +using Refresh.GameServer.Authentication; +using Refresh.GameServer.Configuration; +using Refresh.GameServer.Database; using Refresh.GameServer.Endpoints; namespace Refresh.GameServer.Middlewares; -public class DigestMiddleware : IMiddleware +public class DigestMiddleware(GameServerConfig config) : IMiddleware { - // Should be 19 characters (or less maybe?) - // Length was taken from PS3 and PS4 digest keys - private const string DigestKey = "CustomServerDigest"; - - public static string CalculateDigest(string url, Stream body, string auth, short? exeVersion, short? dataVersion) + public record PspVersionInfo(short ExeVersion, short DataVersion) {} + + public static string CalculateDigest( + string digest, + string route, + Stream body, + string auth, + PspVersionInfo? pspVersionInfo, + bool isUpload, + bool hmacDigest) { - using MemoryStream ms = new(); - - if (!url.StartsWith($"{GameEndpointAttribute.BaseRoute}upload/")) + // Init a MemoryStream with the known final capacity capacity + using MemoryStream ms = new((int)(auth.Length + route.Length + digest.Length + (isUpload ? 0 : body.Length)) + (pspVersionInfo == null ? 0 : 4)); + + // If this is not an upload endpoint, then we need to copy the body of the request into the digest calculation + if (!isUpload) { - // get request body body.CopyTo(ms); body.Seek(0, SeekOrigin.Begin); } ms.WriteString(auth); - ms.WriteString(url); - if (exeVersion.HasValue) + ms.WriteString(route); + if (pspVersionInfo != null) { - byte[] bytes = BitConverter.GetBytes(exeVersion.Value); + Span bytes = stackalloc byte[2]; + + BitConverter.TryWriteBytes(bytes, pspVersionInfo.ExeVersion); + // If we are on a big endian system, we need to flip the bytes if(!BitConverter.IsLittleEndian) - Array.Reverse(bytes); + bytes.Reverse(); ms.Write(bytes); - } - if (dataVersion.HasValue) - { - byte[] bytes = BitConverter.GetBytes(dataVersion.Value); + + BitConverter.TryWriteBytes(bytes, pspVersionInfo.DataVersion); + // If we are on a big endian system, we need to flip the bytes if(!BitConverter.IsLittleEndian) - Array.Reverse(bytes); + bytes.Reverse(); ms.Write(bytes); - } - ms.WriteString(DigestKey); + } + if(!hmacDigest) + ms.WriteString(digest); ms.Position = 0; - using SHA1 sha = SHA1.Create(); - string digestResponse = Convert.ToHexString(sha.ComputeHash(ms)).ToLower(); - return digestResponse; + if (hmacDigest) + { + using HMACSHA1 hmac = new(Encoding.UTF8.GetBytes(digest)); + return Convert.ToHexString(hmac.ComputeHash(ms)).ToLower(); + } + + using SHA1 sha = SHA1.Create(); + return Convert.ToHexString(sha.ComputeHash(ms)).ToLower(); } - // Referenced from Project Lighthouse - // https://github.com/LBPUnion/ProjectLighthouse/blob/d16132f67f82555ef636c0dabab5aabf36f57556/ProjectLighthouse.Servers.GameServer/Middlewares/DigestMiddleware.cs - // https://github.com/LBPUnion/ProjectLighthouse/blob/19ea44e0e2ff5f2ebae8d9dfbaf0f979720bd7d9/ProjectLighthouse/Helpers/CryptoHelper.cs#L35 - private bool VerifyDigestRequest(ListenerContext context, short? exeVersion, short? dataVersion) + public void HandleRequest(ListenerContext context, Lazy database, Action next) { - string url = context.Uri.AbsolutePath; - string auth = context.Cookies["MM_AUTH"] ?? string.Empty; + string route = context.Uri.AbsolutePath; + + //If this isn't an LBP endpoint, dont do digest + if (!route.StartsWith(GameEndpointAttribute.BaseRoute) && !route.StartsWith(LegacyAdapterMiddleware.OldBaseRoute)) + { + next(); + return; + } + + PspVersionInfo? pspVersionInfo = null; + // Try to acquire the exe and data version, this is only accounted for in the client digests, not the server digests + if (short.TryParse(context.RequestHeaders["X-Exe-V"], out short exeVer) && + short.TryParse(context.RequestHeaders["X-Data-V"], out short dataVer)) + pspVersionInfo = new PspVersionInfo(exeVer, dataVer); - bool isUpload = url.StartsWith($"{GameEndpointAttribute.BaseRoute}upload/"); + string auth = context.Cookies["MM_AUTH"] ?? string.Empty; + bool isUpload = route.StartsWith($"{LegacyAdapterMiddleware.OldBaseRoute}upload/") || route.StartsWith($"{GameEndpointAttribute.BaseRoute}upload/"); - MemoryStream body = isUpload ? new MemoryStream(0) : context.InputStream; + // For upload requests, the X-Digest-B header is in use instead by the client string digestHeader = isUpload ? "X-Digest-B" : "X-Digest-A"; string clientDigest = context.RequestHeaders[digestHeader] ?? string.Empty; - - string expectedDigest = CalculateDigest(url, body, auth, isUpload ? null : exeVersion, isUpload ? null : dataVersion); - context.ResponseHeaders["X-Digest-B"] = expectedDigest; - if (clientDigest == expectedDigest) return true; + // Pass through the client's digest right back to the digest B response + context.ResponseHeaders["X-Digest-B"] = clientDigest; - return false; - } - - private void SetDigestResponse(ListenerContext context) - { - string url = context.Uri.AbsolutePath; - string auth = context.Cookies["MM_AUTH"] ?? string.Empty; - - string digestResponse = CalculateDigest(url, context.ResponseStream, auth, null, null); + next(); + + GameDatabaseContext gameDatabase = (GameDatabaseContext)database.Value; + + Token? token = gameDatabase.GetTokenFromTokenData(auth, TokenType.Game); - context.ResponseHeaders["X-Digest-A"] = digestResponse; - } + // Make sure the digest calculation reads the whole response stream + context.ResponseStream.Seek(0, SeekOrigin.Begin); - public void HandleRequest(ListenerContext context, Lazy database, Action next) - { - //If this isn't an LBP endpoint, dont do digest - if (!context.Uri.AbsolutePath.StartsWith(GameEndpointAttribute.BaseRoute)) + // If the digest is already saved on the token, use the token's digest + if (token is { Digest: not null }) { - next(); + SetDigestResponse(context, CalculateDigest(token.Digest, route, context.ResponseStream, auth, null, isUpload, token.IsHmacDigest)); return; } - short? exeVersion = null; - short? dataVersion = null; - if (short.TryParse(context.RequestHeaders["X-Exe-V"], out short exeVer)) + foreach (string digest in config.Sha1DigestKeys) { - exeVersion = exeVer; + string calculatedClientDigest = CalculateDigest(digest, route, context.InputStream, auth, pspVersionInfo, isUpload, false); + + // If the calculated client digest is invalid, then this isn't the digest the game is using, so check the next one + if (calculatedClientDigest != clientDigest) + continue; + + SetDigestResponse(context, CalculateDigest(digest, route, context.ResponseStream, auth, null, isUpload, false)); + + if(token != null) + gameDatabase.SetTokenDigestInfo(token, digest, false); + + return; } - if (short.TryParse(context.RequestHeaders["X-Data-V"], out short dataVer)) + + foreach (string digest in config.HmacDigestKeys) { - dataVersion = dataVer; + string calculatedClientDigest = CalculateDigest(digest, route, context.InputStream, auth, pspVersionInfo, isUpload, true); + + // If the calculated client digest is invalid, then this isn't the digest the game is using, so check the next one + if (calculatedClientDigest != clientDigest) + continue; + + SetDigestResponse(context, CalculateDigest(digest, route, context.ResponseStream, auth, null, isUpload, true)); + + if(token != null) + gameDatabase.SetTokenDigestInfo(token, digest, true); + + return; } - this.VerifyDigestRequest(context, exeVersion, dataVersion); - Debug.Assert(context.InputStream.Position == 0); // should be at position 0 before we pass down the pipeline + // If we were unable to find any digests, just use the first one specified as a backup + // TODO: once we have PS4 support, check if the token is a PS4 token + bool isPs4 = context.RequestHeaders["User-Agent"] == "MM CHTTPClient LBP3 01.26"; + string firstDigest = isPs4 ? config.HmacDigestKeys[0] : config.Sha1DigestKeys[0]; - next(); - - // should be at position 0 before we try to set digest - context.ResponseStream.Seek(0, SeekOrigin.Begin); - this.SetDigestResponse(context); + SetDigestResponse(context, CalculateDigest(firstDigest, route, context.ResponseStream, auth, null, isUpload, isPs4)); + + if(token != null) + gameDatabase.SetTokenDigestInfo(token, firstDigest, isPs4); } + + private static void SetDigestResponse(ListenerContext context, string calculatedDigest) + => context.ResponseHeaders["X-Digest-A"] = calculatedDigest; } \ No newline at end of file diff --git a/Refresh.GameServer/RefreshGameServer.cs b/Refresh.GameServer/RefreshGameServer.cs index 768bc8d7..4fe9dc25 100644 --- a/Refresh.GameServer/RefreshGameServer.cs +++ b/Refresh.GameServer/RefreshGameServer.cs @@ -86,12 +86,13 @@ protected override void Initialize() protected override void SetupMiddlewares() { - this.Server.AddMiddleware(); this.Server.AddMiddleware(); this.Server.AddMiddleware(new DeflateMiddleware(this._config!)); - this.Server.AddMiddleware(); + // Digest middleware must be run before LegacyAdapterMiddleware, because digest is based on the raw route, not the fixed route + this.Server.AddMiddleware(new DigestMiddleware(this._config!)); this.Server.AddMiddleware(); this.Server.AddMiddleware(); + this.Server.AddMiddleware(); } protected override void SetupConfiguration() diff --git a/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs b/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs index 27d38260..d1edaa35 100644 --- a/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs +++ b/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs @@ -9,7 +9,7 @@ public class DigestMiddlewareTests : GameServerTest public void DoesntIncludeDigestWhenOutsideOfGame() { using TestContext context = this.GetServer(); - context.Server.Value.Server.AddMiddleware(); + context.Server.Value.Server.AddMiddleware(new DigestMiddleware(context.Server.Value.GameServerConfig)); HttpResponseMessage response = context.Http.GetAsync("/api/v3/instance").Result; @@ -25,7 +25,7 @@ public void IncludesDigestInGame() { using TestContext context = this.GetServer(); context.Server.Value.Server.AddEndpointGroup(); - context.Server.Value.Server.AddMiddleware(); + context.Server.Value.Server.AddMiddleware(new DigestMiddleware(context.Server.Value.GameServerConfig)); HttpResponseMessage response = context.Http.GetAsync("/lbp/eula").Result; @@ -36,21 +36,26 @@ public void IncludesDigestInGame() }); } - [Test] - public void DigestIsCorrect() + [TestCase(false)] + [TestCase(true)] + public void DigestIsCorrect(bool isHmac) { using TestContext context = this.GetServer(); context.Server.Value.Server.AddEndpointGroup(); - context.Server.Value.Server.AddMiddleware(); + context.Server.Value.Server.AddMiddleware(new DigestMiddleware(context.Server.Value.GameServerConfig)); const string endpoint = "/lbp/test"; const string expectedResultStr = "test"; using MemoryStream blankMs = new(); using MemoryStream expectedResultMs = new(Encoding.ASCII.GetBytes(expectedResultStr)); + + string digest = isHmac + ? context.Server.Value.GameServerConfig.HmacDigestKeys[0] + : context.Server.Value.GameServerConfig.Sha1DigestKeys[0]; - string serverDigest = DigestMiddleware.CalculateDigest(endpoint, expectedResultMs, "", null, null); - string clientDigest = DigestMiddleware.CalculateDigest(endpoint, blankMs, "", null, null); + string serverDigest = DigestMiddleware.CalculateDigest(digest, endpoint, expectedResultMs, "", null, false, isHmac); + string clientDigest = DigestMiddleware.CalculateDigest(digest, endpoint, blankMs, "", null, false, isHmac); context.Http.DefaultRequestHeaders.Add("X-Digest-A", clientDigest); HttpResponseMessage response = context.Http.GetAsync(endpoint).Result; @@ -67,12 +72,13 @@ public void DigestIsCorrect() }); } - [Test] - public void PspDigestIsCorrect() + [TestCase(false)] + [TestCase(true)] + public void PspDigestIsCorrect(bool isHmac) { using TestContext context = this.GetServer(); context.Server.Value.Server.AddEndpointGroup(); - context.Server.Value.Server.AddMiddleware(); + context.Server.Value.Server.AddMiddleware(new DigestMiddleware(context.Server.Value.GameServerConfig)); const string endpoint = "/lbp/test"; const string expectedResultStr = "test"; @@ -80,8 +86,12 @@ public void PspDigestIsCorrect() using MemoryStream blankMs = new(); using MemoryStream expectedResultMs = new(Encoding.ASCII.GetBytes(expectedResultStr)); - string serverDigest = DigestMiddleware.CalculateDigest(endpoint, expectedResultMs, "", null, null); - string clientDigest = DigestMiddleware.CalculateDigest(endpoint, blankMs, "", 205, 5); + string digest = isHmac + ? context.Server.Value.GameServerConfig.HmacDigestKeys[0] + : context.Server.Value.GameServerConfig.Sha1DigestKeys[0]; + + string serverDigest = DigestMiddleware.CalculateDigest(digest, endpoint, expectedResultMs, "", null, false, isHmac); + string clientDigest = DigestMiddleware.CalculateDigest(digest, endpoint, blankMs, "", new DigestMiddleware.PspVersionInfo(205, 5), false, isHmac); context.Http.DefaultRequestHeaders.Add("X-Digest-A", clientDigest); context.Http.DefaultRequestHeaders.Add("X-data-v", "5"); @@ -99,17 +109,4 @@ public void PspDigestIsCorrect() Assert.That(response.Headers.GetValues("X-Digest-B").First(), Is.EqualTo(clientDigest)); }); } - - [Test] - public void FailsWhenDigestIsBad() - { - using TestContext context = this.GetServer(); - context.Server.Value.Server.AddEndpointGroup(); - context.Server.Value.Server.AddMiddleware(); - - context.Http.DefaultRequestHeaders.Add("X-Digest-A", "asdf"); - HttpResponseMessage response = context.Http.GetAsync("/lbp/eula").Result; - - Assert.Pass(); // TODO: we have no way of detecting a failed digest check - } } \ No newline at end of file From 65f608cc3b1de82cb423d20ddc098c378e75a09b Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Wed, 17 Jul 2024 18:36:58 -0700 Subject: [PATCH 4/4] Apply suggestions from code review --- .../Middlewares/DigestMiddleware.cs | 70 +++++++++++-------- .../Middlewares/DigestMiddleware.cs | 13 +++- 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/Refresh.GameServer/Middlewares/DigestMiddleware.cs b/Refresh.GameServer/Middlewares/DigestMiddleware.cs index 6ff3fdeb..a661e4c7 100644 --- a/Refresh.GameServer/Middlewares/DigestMiddleware.cs +++ b/Refresh.GameServer/Middlewares/DigestMiddleware.cs @@ -11,8 +11,15 @@ namespace Refresh.GameServer.Middlewares; -public class DigestMiddleware(GameServerConfig config) : IMiddleware +public class DigestMiddleware : IMiddleware { + private readonly GameServerConfig _config; + + public DigestMiddleware(GameServerConfig config) + { + this._config = config; + } + public record PspVersionInfo(short ExeVersion, short DataVersion) {} public static string CalculateDigest( @@ -110,47 +117,50 @@ public void HandleRequest(ListenerContext context, Lazy databa return; } - foreach (string digest in config.Sha1DigestKeys) - { - string calculatedClientDigest = CalculateDigest(digest, route, context.InputStream, auth, pspVersionInfo, isUpload, false); - - // If the calculated client digest is invalid, then this isn't the digest the game is using, so check the next one - if (calculatedClientDigest != clientDigest) - continue; + (string digest, bool hmac)? foundDigest = this.FindBestKey(clientDigest, route, context.InputStream, auth, pspVersionInfo, isUpload, false) ?? + this.FindBestKey(clientDigest, route, context.InputStream, auth, pspVersionInfo, isUpload, true); - SetDigestResponse(context, CalculateDigest(digest, route, context.ResponseStream, auth, null, isUpload, false)); + if (foundDigest != null) + { + string digest = foundDigest.Value.digest; + bool hmac = foundDigest.Value.hmac; + SetDigestResponse(context, CalculateDigest(digest, route, context.ResponseStream, auth, null, isUpload, hmac)); + if(token != null) - gameDatabase.SetTokenDigestInfo(token, digest, false); - - return; + gameDatabase.SetTokenDigestInfo(token, digest, hmac); + } + else + { + // If we were unable to find any digests, just use the first one specified as a backup + // TODO: once we have PS4 support, check if the token is a PS4 token + bool isPs4 = context.RequestHeaders["User-Agent"] == "MM CHTTPClient LBP3 01.26"; + string firstDigest = isPs4 ? this._config.HmacDigestKeys[0] : this._config.Sha1DigestKeys[0]; + + SetDigestResponse(context, CalculateDigest(firstDigest, route, context.ResponseStream, auth, null, isUpload, isPs4)); + + if(token != null) + gameDatabase.SetTokenDigestInfo(token, firstDigest, isPs4); } + } - foreach (string digest in config.HmacDigestKeys) + private (string digest, bool hmac)? FindBestKey(string clientDigest, string route, MemoryStream inputStream, string auth, PspVersionInfo? pspVersionInfo, bool isUpload, bool hmac) + { + string[] keys = hmac ? this._config.HmacDigestKeys : this._config.Sha1DigestKeys; + + foreach (string digest in keys) { - string calculatedClientDigest = CalculateDigest(digest, route, context.InputStream, auth, pspVersionInfo, isUpload, true); + string calculatedClientDigest = CalculateDigest(digest, route, inputStream, auth, pspVersionInfo, isUpload, hmac); // If the calculated client digest is invalid, then this isn't the digest the game is using, so check the next one if (calculatedClientDigest != clientDigest) continue; - - SetDigestResponse(context, CalculateDigest(digest, route, context.ResponseStream, auth, null, isUpload, true)); - - if(token != null) - gameDatabase.SetTokenDigestInfo(token, digest, true); - - return; + + // If they match, we found the client's digest + return (digest, hmac); } - // If we were unable to find any digests, just use the first one specified as a backup - // TODO: once we have PS4 support, check if the token is a PS4 token - bool isPs4 = context.RequestHeaders["User-Agent"] == "MM CHTTPClient LBP3 01.26"; - string firstDigest = isPs4 ? config.HmacDigestKeys[0] : config.Sha1DigestKeys[0]; - - SetDigestResponse(context, CalculateDigest(firstDigest, route, context.ResponseStream, auth, null, isUpload, isPs4)); - - if(token != null) - gameDatabase.SetTokenDigestInfo(token, firstDigest, isPs4); + return null; } private static void SetDigestResponse(ListenerContext context, string calculatedDigest) diff --git a/Refresh.HttpsProxy/Middlewares/DigestMiddleware.cs b/Refresh.HttpsProxy/Middlewares/DigestMiddleware.cs index 4a41da93..15cefc80 100644 --- a/Refresh.HttpsProxy/Middlewares/DigestMiddleware.cs +++ b/Refresh.HttpsProxy/Middlewares/DigestMiddleware.cs @@ -8,8 +8,15 @@ namespace Refresh.HttpsProxy.Middlewares; -public class DigestMiddleware(ProxyConfig config) : IMiddleware +public class DigestMiddleware : IMiddleware { + private readonly ProxyConfig _config; + + public DigestMiddleware(ProxyConfig config) + { + this._config = config; + } + public string CalculatePs3Digest(string route, Stream body, string auth, bool isUpload) { using MemoryStream ms = new(); @@ -23,7 +30,7 @@ public string CalculatePs3Digest(string route, Stream body, string auth, bool is ms.WriteString(auth); ms.WriteString(route); - ms.WriteString(config.Ps3Digest); + ms.WriteString(this._config.Ps3Digest); ms.Position = 0; using SHA1 sha = SHA1.Create(); @@ -48,7 +55,7 @@ public string CalculatePs4Digest(string route, Stream body, string auth, bool is ms.Position = 0; - using HMACSHA1 hmac = new(Encoding.UTF8.GetBytes(config.Ps4Digest)); + using HMACSHA1 hmac = new(Encoding.UTF8.GetBytes(this._config.Ps4Digest)); string digestResponse = Convert.ToHexString(hmac.ComputeHash(ms)).ToLower(); return digestResponse;