From 87f3be5af581bf2c142cc42d65914fa9b144044d Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Tue, 23 Jul 2024 19:40:22 -0700 Subject: [PATCH 1/3] DigestMiddleware: Move code to use IncrementalHash This reduces allocations, and increases performance --- .../Extensions/IncrementalHashExtensions.cs | 9 ++++ .../Extensions/MemoryStreamExtensions.cs | 8 --- .../Middlewares/DigestMiddleware.cs | 51 +++++++++---------- .../Middlewares/DigestMiddlewareTests.cs | 14 ++--- 4 files changed, 36 insertions(+), 46 deletions(-) create mode 100644 Refresh.Common/Extensions/IncrementalHashExtensions.cs delete mode 100644 Refresh.Common/Extensions/MemoryStreamExtensions.cs diff --git a/Refresh.Common/Extensions/IncrementalHashExtensions.cs b/Refresh.Common/Extensions/IncrementalHashExtensions.cs new file mode 100644 index 00000000..56bc102b --- /dev/null +++ b/Refresh.Common/Extensions/IncrementalHashExtensions.cs @@ -0,0 +1,9 @@ +using System.Security.Cryptography; +using System.Text; + +namespace Refresh.Common.Extensions; + +public static class IncrementalHashExtensions +{ + public static void WriteString(this IncrementalHash hash, string str) => hash.AppendData(Encoding.UTF8.GetBytes(str)); +} \ No newline at end of file diff --git a/Refresh.Common/Extensions/MemoryStreamExtensions.cs b/Refresh.Common/Extensions/MemoryStreamExtensions.cs deleted file mode 100644 index 66c0700a..00000000 --- a/Refresh.Common/Extensions/MemoryStreamExtensions.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Text; - -namespace Refresh.Common.Extensions; - -public static class MemoryStreamExtensions -{ - public static void WriteString(this MemoryStream ms, string str) => ms.Write(Encoding.UTF8.GetBytes(str)); -} \ No newline at end of file diff --git a/Refresh.GameServer/Middlewares/DigestMiddleware.cs b/Refresh.GameServer/Middlewares/DigestMiddleware.cs index a661e4c7..6899dbe4 100644 --- a/Refresh.GameServer/Middlewares/DigestMiddleware.cs +++ b/Refresh.GameServer/Middlewares/DigestMiddleware.cs @@ -25,24 +25,24 @@ public record PspVersionInfo(short ExeVersion, short DataVersion) {} public static string CalculateDigest( string digest, string route, - Stream body, + ReadOnlySpan body, string auth, PspVersionInfo? pspVersionInfo, bool isUpload, bool hmacDigest) { - // 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)); + IncrementalHash hash = hmacDigest + ? IncrementalHash.CreateHMAC(HashAlgorithmName.SHA1, Encoding.UTF8.GetBytes(digest)) + : IncrementalHash.CreateHash(HashAlgorithmName.SHA1); // If this is not an upload endpoint, then we need to copy the body of the request into the digest calculation if (!isUpload) { - body.CopyTo(ms); - body.Seek(0, SeekOrigin.Begin); + hash.AppendData(body); } - - ms.WriteString(auth); - ms.WriteString(route); + + hash.WriteString(auth); + hash.WriteString(route); if (pspVersionInfo != null) { Span bytes = stackalloc byte[2]; @@ -51,27 +51,19 @@ public static string CalculateDigest( // If we are on a big endian system, we need to flip the bytes if(!BitConverter.IsLittleEndian) bytes.Reverse(); - ms.Write(bytes); + hash.AppendData(bytes); BitConverter.TryWriteBytes(bytes, pspVersionInfo.DataVersion); // If we are on a big endian system, we need to flip the bytes if(!BitConverter.IsLittleEndian) bytes.Reverse(); - ms.Write(bytes); + hash.AppendData(bytes); } + if(!hmacDigest) - ms.WriteString(digest); - - ms.Position = 0; + hash.WriteString(digest); - 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(); + return Convert.ToHexString(hash.GetCurrentHash()).ToLower(); } public void HandleRequest(ListenerContext context, Lazy database, Action next) @@ -107,25 +99,28 @@ public void HandleRequest(ListenerContext context, Lazy databa Token? token = gameDatabase.GetTokenFromTokenData(auth, TokenType.Game); + byte[] responseBody = context.ResponseStream.ToArray(); + byte[] requestBody = context.InputStream.ToArray(); + // Make sure the digest calculation reads the whole response stream context.ResponseStream.Seek(0, SeekOrigin.Begin); // If the digest is already saved on the token, use the token's digest if (token is { Digest: not null }) { - SetDigestResponse(context, CalculateDigest(token.Digest, route, context.ResponseStream, auth, null, isUpload, token.IsHmacDigest)); + SetDigestResponse(context, CalculateDigest(token.Digest, route, responseBody, auth, null, isUpload, token.IsHmacDigest)); return; } - (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); + (string digest, bool hmac)? foundDigest = this.FindBestKey(clientDigest, route, requestBody, auth, pspVersionInfo, isUpload, false) ?? + this.FindBestKey(clientDigest, route, requestBody, auth, pspVersionInfo, isUpload, true); if (foundDigest != null) { string digest = foundDigest.Value.digest; bool hmac = foundDigest.Value.hmac; - SetDigestResponse(context, CalculateDigest(digest, route, context.ResponseStream, auth, null, isUpload, hmac)); + SetDigestResponse(context, CalculateDigest(digest, route, responseBody, auth, null, isUpload, hmac)); if(token != null) gameDatabase.SetTokenDigestInfo(token, digest, hmac); @@ -137,20 +132,20 @@ public void HandleRequest(ListenerContext context, Lazy databa 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)); + SetDigestResponse(context, CalculateDigest(firstDigest, route, responseBody, auth, null, isUpload, isPs4)); if(token != null) gameDatabase.SetTokenDigestInfo(token, firstDigest, isPs4); } } - private (string digest, bool hmac)? FindBestKey(string clientDigest, string route, MemoryStream inputStream, string auth, PspVersionInfo? pspVersionInfo, bool isUpload, bool hmac) + private (string digest, bool hmac)? FindBestKey(string clientDigest, string route, ReadOnlySpan requestData, 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, inputStream, auth, pspVersionInfo, isUpload, hmac); + string calculatedClientDigest = CalculateDigest(digest, route, requestData, 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) diff --git a/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs b/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs index d1edaa35..adadae28 100644 --- a/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs +++ b/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs @@ -46,16 +46,13 @@ public void DigestIsCorrect(bool isHmac) 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(digest, endpoint, expectedResultMs, "", null, false, isHmac); - string clientDigest = DigestMiddleware.CalculateDigest(digest, endpoint, blankMs, "", null, false, isHmac); + string serverDigest = DigestMiddleware.CalculateDigest(digest, endpoint, Encoding.ASCII.GetBytes(expectedResultStr), "", null, false, isHmac); + string clientDigest = DigestMiddleware.CalculateDigest(digest, endpoint, [], "", null, false, isHmac); context.Http.DefaultRequestHeaders.Add("X-Digest-A", clientDigest); HttpResponseMessage response = context.Http.GetAsync(endpoint).Result; @@ -83,15 +80,12 @@ public void PspDigestIsCorrect(bool isHmac) 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(digest, endpoint, expectedResultMs, "", null, false, isHmac); - string clientDigest = DigestMiddleware.CalculateDigest(digest, endpoint, blankMs, "", new DigestMiddleware.PspVersionInfo(205, 5), false, isHmac); + string serverDigest = DigestMiddleware.CalculateDigest(digest, endpoint, Encoding.ASCII.GetBytes(expectedResultStr), "", null, false, isHmac); + string clientDigest = DigestMiddleware.CalculateDigest(digest, endpoint, [], "", new DigestMiddleware.PspVersionInfo(205, 5), false, isHmac); context.Http.DefaultRequestHeaders.Add("X-Digest-A", clientDigest); context.Http.DefaultRequestHeaders.Add("X-data-v", "5"); From 03ee63cae92f77771771bfdc9b8bf7a28bd08727 Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Tue, 23 Jul 2024 19:40:44 -0700 Subject: [PATCH 2/3] DigestMiddlewareTests: Add new tests to make sure digest fallback and multi-digest works --- .../Middlewares/DigestMiddlewareTests.cs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs b/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs index adadae28..44a9b64d 100644 --- a/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs +++ b/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs @@ -69,6 +69,75 @@ public void DigestIsCorrect(bool isHmac) }); } + [TestCase(false)] + [TestCase(true)] + public void CanFindSecondaryDigest(bool isHmac) + { + using TestContext context = this.GetServer(); + context.Server.Value.Server.AddEndpointGroup(); + context.Server.Value.Server.AddMiddleware(new DigestMiddleware(context.Server.Value.GameServerConfig)); + context.Server.Value.GameServerConfig.Sha1DigestKeys = ["sha1_digest1", "sha1_digest2"]; + context.Server.Value.GameServerConfig.HmacDigestKeys = ["hmac_digest1", "hmac_digest2"]; + + const string endpoint = "/lbp/test"; + const string expectedResultStr = "test"; + + string digest = isHmac + ? context.Server.Value.GameServerConfig.HmacDigestKeys[1] + : context.Server.Value.GameServerConfig.Sha1DigestKeys[1]; + + string serverDigest = DigestMiddleware.CalculateDigest(digest, endpoint, Encoding.ASCII.GetBytes(expectedResultStr), "", null, false, isHmac); + string clientDigest = DigestMiddleware.CalculateDigest(digest, endpoint, [], "", null, false, isHmac); + + context.Http.DefaultRequestHeaders.Add("X-Digest-A", clientDigest); + HttpResponseMessage response = context.Http.GetAsync(endpoint).Result; + + Assert.Multiple(() => + { + Assert.That(response.StatusCode, Is.EqualTo(OK)); + + Assert.That(response.Headers.Contains("X-Digest-A"), Is.True); + Assert.That(response.Headers.Contains("X-Digest-B"), Is.True); + + Assert.That(response.Headers.GetValues("X-Digest-A").First(), Is.EqualTo(serverDigest)); + Assert.That(response.Headers.GetValues("X-Digest-B").First(), Is.EqualTo(clientDigest)); + }); + } + + [TestCase(false)] + // TODO: once we model PS4 clients in our tokens, re-enable the HMAC version of this test, and make the request come from an authenticated PS4 client. + // [TestCase(true)] + public void FallsBackToFirstDigest(bool isHmac) + { + using TestContext context = this.GetServer(); + context.Server.Value.Server.AddEndpointGroup(); + context.Server.Value.Server.AddMiddleware(new DigestMiddleware(context.Server.Value.GameServerConfig)); + + const string endpoint = "/lbp/test"; + const string expectedResultStr = "test"; + + string digest = isHmac + ? context.Server.Value.GameServerConfig.HmacDigestKeys[0] + : context.Server.Value.GameServerConfig.Sha1DigestKeys[0]; + + // Calculate the digest response as if the digest used was the first digest + string serverDigest = DigestMiddleware.CalculateDigest(digest, endpoint, + Encoding.ASCII.GetBytes(expectedResultStr), "", null, false, isHmac); + + context.Http.DefaultRequestHeaders.Add("X-Digest-A", "nonsense digest"); + HttpResponseMessage response = context.Http.GetAsync(endpoint).Result; + + Assert.Multiple(() => + { + Assert.That(response.StatusCode, Is.EqualTo(OK)); + + Assert.That(response.Headers.Contains("X-Digest-A"), Is.True); + Assert.That(response.Headers.Contains("X-Digest-B"), Is.True); + + Assert.That(response.Headers.GetValues("X-Digest-A").First(), Is.EqualTo(serverDigest)); + }); + } + [TestCase(false)] [TestCase(true)] public void PspDigestIsCorrect(bool isHmac) From a1cb2481c10f8daca224f3cd6685b2510b34b10a Mon Sep 17 00:00:00 2001 From: Beyley Thomas Date: Wed, 24 Jul 2024 02:43:29 -0700 Subject: [PATCH 3/3] Make HttpsProxy tell the server which digest to use. LBP3 gets mad with `/eula` if the server digest is incorrect, and since it doesn't send a client digest for that endpoint, we are unable to guess the correct digest for LBP3 clients. Also fixes the server locking in the wrong digest on endpoints which expect a server digest, but dont send a client digest. Now it will only lock in the digest for requests which contain a client digest. --- .../Middlewares/DigestMiddleware.cs | 26 ++++- Refresh.HttpsProxy/Config/ProxyConfig.cs | 6 +- .../Middlewares/DigestMiddleware.cs | 98 ------------------- .../Middlewares/ProxyMiddleware.cs | 3 + Refresh.HttpsProxy/Program.cs | 6 +- .../Middlewares/DigestMiddlewareTests.cs | 49 +++++++++- 6 files changed, 77 insertions(+), 111 deletions(-) delete mode 100644 Refresh.HttpsProxy/Middlewares/DigestMiddleware.cs diff --git a/Refresh.GameServer/Middlewares/DigestMiddleware.cs b/Refresh.GameServer/Middlewares/DigestMiddleware.cs index 6899dbe4..66616be3 100644 --- a/Refresh.GameServer/Middlewares/DigestMiddleware.cs +++ b/Refresh.GameServer/Middlewares/DigestMiddleware.cs @@ -77,6 +77,9 @@ public void HandleRequest(ListenerContext context, Lazy databa return; } + // TODO: once we have PS4 support, check if the token is a PS4 token + bool isPs4 = context.RequestHeaders["User-Agent"]?.Contains("MM CHTTPClient LBP3 01.26") ?? false; + 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) && @@ -112,6 +115,22 @@ public void HandleRequest(ListenerContext context, Lazy databa return; } + // If the client asks for a particular digest index, use that digest + if (int.TryParse(context.RequestHeaders["Refresh-Ps3-Digest-Index"], out int ps3DigestIndex) && + int.TryParse(context.RequestHeaders["Refresh-Ps4-Digest-Index"], out int ps4DigestIndex)) + { + string digest = isPs4 + ? this._config.HmacDigestKeys[ps4DigestIndex] + : this._config.Sha1DigestKeys[ps3DigestIndex]; + + SetDigestResponse(context, CalculateDigest(digest, route, responseBody, auth, null, isUpload, isPs4)); + + if(token != null) + gameDatabase.SetTokenDigestInfo(token, digest, isPs4); + + return; + } + (string digest, bool hmac)? foundDigest = this.FindBestKey(clientDigest, route, requestBody, auth, pspVersionInfo, isUpload, false) ?? this.FindBestKey(clientDigest, route, requestBody, auth, pspVersionInfo, isUpload, true); @@ -128,13 +147,14 @@ public void HandleRequest(ListenerContext context, Lazy databa 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, responseBody, auth, null, isUpload, isPs4)); - if(token != null) + // If theres no token, or the client didnt provide any client digest, lock the token into the found digest + // The second condition is to make sure that we dont lock in a digest for endpoints which send no client digest, + // but expect a server digest. + if(token != null && !string.IsNullOrEmpty(clientDigest)) gameDatabase.SetTokenDigestInfo(token, firstDigest, isPs4); } } diff --git a/Refresh.HttpsProxy/Config/ProxyConfig.cs b/Refresh.HttpsProxy/Config/ProxyConfig.cs index 596d6705..be9328c9 100644 --- a/Refresh.HttpsProxy/Config/ProxyConfig.cs +++ b/Refresh.HttpsProxy/Config/ProxyConfig.cs @@ -2,12 +2,12 @@ namespace Refresh.HttpsProxy.Config; public class ProxyConfig : Bunkum.Core.Configuration.Config { - public override int CurrentConfigVersion => 1; + public override int CurrentConfigVersion => 2; public override int Version { get; set; } protected override void Migrate(int oldVer, dynamic oldConfig) { } public string TargetServerUrl { get; set; } = "https://lbp.littlebigrefresh.com"; - public string Ps3Digest { get; set; } = "CustomServerDigest"; - public string Ps4Digest { get; set; } = "CustomServerDigest"; + public int Ps3DigestIndex { get; set; } = 0; + public int Ps4DigestIndex { get; set; } = 0; } \ No newline at end of file diff --git a/Refresh.HttpsProxy/Middlewares/DigestMiddleware.cs b/Refresh.HttpsProxy/Middlewares/DigestMiddleware.cs deleted file mode 100644 index 15cefc80..00000000 --- a/Refresh.HttpsProxy/Middlewares/DigestMiddleware.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System.Security.Cryptography; -using System.Text; -using Bunkum.Core.Database; -using Bunkum.Core.Endpoints.Middlewares; -using Bunkum.Listener.Request; -using Refresh.Common.Extensions; -using Refresh.HttpsProxy.Config; - -namespace Refresh.HttpsProxy.Middlewares; - -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(); - - // If this request is not to an upload endpoint, then we copy the body of the request into the digest data - if (!isUpload) - { - body.CopyTo(ms); - body.Seek(0, SeekOrigin.Begin); - } - - ms.WriteString(auth); - ms.WriteString(route); - ms.WriteString(this._config.Ps3Digest); - - ms.Position = 0; - using SHA1 sha = SHA1.Create(); - string digestResponse = Convert.ToHexString(sha.ComputeHash(ms)).ToLower(); - - return digestResponse; - } - - public string CalculatePs4Digest(string route, Stream body, string auth, bool isUpload) - { - using MemoryStream ms = new(); - - // If this request is not to an upload endpoint, then we copy the body of the request into the digest data - if (!isUpload) - { - body.CopyTo(ms); - body.Seek(0, SeekOrigin.Begin); - } - - ms.WriteString(auth); - ms.WriteString(route); - - ms.Position = 0; - - using HMACSHA1 hmac = new(Encoding.UTF8.GetBytes(this._config.Ps4Digest)); - string digestResponse = Convert.ToHexString(hmac.ComputeHash(ms)).ToLower(); - - return digestResponse; - } - - public void HandleRequest(ListenerContext context, Lazy database, Action next) - { - string route = context.Uri.AbsolutePath; - - //If this isn't an LBP endpoint, dont do digest - if (!route.StartsWith("/LITTLEBIGPLANETPS3_XML/")) - { - next(); - return; - } - - bool isUpload = route.StartsWith("/LITTLEBIGPLANETPS3_XML/upload/"); - string auth = context.Cookies["MM_AUTH"] ?? string.Empty; - - // 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; - - // Pass through the client's digest right back to the digest B response - context.ResponseHeaders["X-Digest-B"] = clientDigest; - - // Run the rest of the middlewares - next(); - - // Make sure the calculation reads the whole response stream - context.ResponseStream.Seek(0, SeekOrigin.Begin); - - // If we are a PS4 client, use the PS4 digest code - if (context.RequestHeaders["User-Agent"] == "MM CHTTPClient LBP3 01.26") - context.ResponseHeaders["X-Digest-A"] = this.CalculatePs4Digest(route, context.ResponseStream, auth, isUpload); - // Else, use PS3 digest code - else - context.ResponseHeaders["X-Digest-A"] = this.CalculatePs3Digest(route, context.ResponseStream, auth, isUpload); - } -} \ No newline at end of file diff --git a/Refresh.HttpsProxy/Middlewares/ProxyMiddleware.cs b/Refresh.HttpsProxy/Middlewares/ProxyMiddleware.cs index 483d32cf..1fd6b158 100644 --- a/Refresh.HttpsProxy/Middlewares/ProxyMiddleware.cs +++ b/Refresh.HttpsProxy/Middlewares/ProxyMiddleware.cs @@ -63,6 +63,9 @@ public void HandleRequest(ListenerContext context, Lazy databa requestMessage.Headers.TryAddWithoutValidation(key, value); } + requestMessage.Headers.Add("Refresh-Ps3-Digest-Index", config.Ps3DigestIndex.ToString()); + requestMessage.Headers.Add("Refresh-Ps4-Digest-Index", config.Ps4DigestIndex.ToString()); + // Send our HTTP request HttpResponseMessage response = client.Send(requestMessage); diff --git a/Refresh.HttpsProxy/Program.cs b/Refresh.HttpsProxy/Program.cs index 04eafe2c..9d581a04 100644 --- a/Refresh.HttpsProxy/Program.cs +++ b/Refresh.HttpsProxy/Program.cs @@ -1,6 +1,4 @@ -// See https://aka.ms/new-console-template for more information - -using Bunkum.Core; +using Bunkum.Core; using Bunkum.Core.Configuration; using Bunkum.Protocols.Http; using Bunkum.Protocols.Https; @@ -30,13 +28,11 @@ httpsServer.Initialize = s => { s.AddMiddleware(new ProxyMiddleware(config)); - s.AddMiddleware(new DigestMiddleware(config)); }; httpServer.Initialize = s => { s.AddMiddleware(new ProxyMiddleware(config)); - s.AddMiddleware(new DigestMiddleware(config)); }; // Start the server in multi-threaded mode, and let Bunkum manage the rest. diff --git a/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs b/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs index 44a9b64d..1c4101a9 100644 --- a/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs +++ b/RefreshTests.GameServer/Tests/Middlewares/DigestMiddlewareTests.cs @@ -103,10 +103,51 @@ public void CanFindSecondaryDigest(bool isHmac) Assert.That(response.Headers.GetValues("X-Digest-B").First(), Is.EqualTo(clientDigest)); }); } + + [TestCase(false)] + [TestCase(true)] + public void DigestSelectionHeaderWorks(bool isHmac) + { + using TestContext context = this.GetServer(); + context.Server.Value.Server.AddEndpointGroup(); + context.Server.Value.Server.AddMiddleware(new DigestMiddleware(context.Server.Value.GameServerConfig)); + context.Server.Value.GameServerConfig.Sha1DigestKeys = ["sha1_digest1", "sha1_digest2"]; + context.Server.Value.GameServerConfig.HmacDigestKeys = ["hmac_digest1", "hmac_digest2"]; + + const string endpoint = "/lbp/test"; + const string expectedResultStr = "test"; + + string digest = isHmac + ? context.Server.Value.GameServerConfig.HmacDigestKeys[1] + : context.Server.Value.GameServerConfig.Sha1DigestKeys[1]; + + string serverDigest = DigestMiddleware.CalculateDigest(digest, endpoint, Encoding.ASCII.GetBytes(expectedResultStr), "", null, false, isHmac); + + context.Http.DefaultRequestHeaders.Add("Refresh-Ps3-Digest-Index", "1"); + context.Http.DefaultRequestHeaders.Add("Refresh-Ps4-Digest-Index", "1"); + + // TODO: once we model PS4 clients in our tokens, make the request come from an authenticated PS4 client. + if(isHmac) + context.Http.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "MM CHTTPClient LBP3 01.26"); + + // send a blank digest to make it have to guess + context.Http.DefaultRequestHeaders.Add("X-Digest-A", ""); + HttpResponseMessage response = context.Http.GetAsync(endpoint).Result; + + Assert.Multiple(() => + { + Assert.That(response.StatusCode, Is.EqualTo(OK)); + + Assert.That(response.Headers.Contains("X-Digest-A"), Is.True); + Assert.That(response.Headers.Contains("X-Digest-B"), Is.True); + + // Ensure that the server digest used the second digest key, even though without the headers it would default to the first + Assert.That(response.Headers.GetValues("X-Digest-A").First(), Is.EqualTo(serverDigest)); + }); + } [TestCase(false)] - // TODO: once we model PS4 clients in our tokens, re-enable the HMAC version of this test, and make the request come from an authenticated PS4 client. - // [TestCase(true)] + [TestCase(true)] public void FallsBackToFirstDigest(bool isHmac) { using TestContext context = this.GetServer(); @@ -124,6 +165,10 @@ public void FallsBackToFirstDigest(bool isHmac) string serverDigest = DigestMiddleware.CalculateDigest(digest, endpoint, Encoding.ASCII.GetBytes(expectedResultStr), "", null, false, isHmac); + // TODO: once we model PS4 clients in our tokens, make the request come from an authenticated PS4 client. + if(isHmac) + context.Http.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "MM CHTTPClient LBP3 01.26"); + context.Http.DefaultRequestHeaders.Add("X-Digest-A", "nonsense digest"); HttpResponseMessage response = context.Http.GetAsync(endpoint).Result;