diff --git a/src/AspNet.Security.OAuth.Bilibili/BilibiliAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Bilibili/BilibiliAuthenticationHandler.cs index e5e3b5456..1cfb8e74e 100644 --- a/src/AspNet.Security.OAuth.Bilibili/BilibiliAuthenticationHandler.cs +++ b/src/AspNet.Security.OAuth.Bilibili/BilibiliAuthenticationHandler.cs @@ -90,17 +90,19 @@ private static string ComputeHmacSHA256(string key, string data) return Convert.ToHexStringLower(hash); } - private static string BuildSignatureString(HttpRequestMessage request, string appSecret) + private static string BuildSignatureString(SortedList xbiliHeaders, string appSecret) { - var headers = request.Headers - .Where(h => h.Key.StartsWith("x-bili-", StringComparison.OrdinalIgnoreCase)) - .OrderBy(h => h.Key) - .Select(h => $"{h.Key}:{string.Join(",", h.Value)}") - .ToList(); - - var signature = string.Join('\n', headers); + var sb = new StringBuilder(256); // 256 is an estimated size for the plain text + foreach ((var name, var value) in xbiliHeaders) + { + sb.Append(name) + .Append(':') + .Append(value) + .Append('\n'); + } - return ComputeHmacSHA256(appSecret, signature); + var signSrcText = sb.ToString(0, sb.Length - 1); // Ignore the last '\n' + return ComputeHmacSHA256(appSecret, signSrcText); } protected override async Task CreateTicketAsync( @@ -110,14 +112,14 @@ protected override async Task CreateTicketAsync( { using var request = new HttpRequestMessage(HttpMethod.Get, Options.UserInformationEndpoint); request.Headers.Add("access-token", tokens.AccessToken); - request.Headers.Add("x-bili-accesskeyid", Options.ClientId); - request.Headers.Add("x-bili-content-md5", "d41d8cd98f00b204e9800998ecf8427e"); // It's a GET request so there's no content, so we send the MD5 hash of an empty string - request.Headers.Add("x-bili-signature-method", "HMAC-SHA256"); - request.Headers.Add("x-bili-signature-nonce", Base64Url.EncodeToString(RandomNumberGenerator.GetBytes(256 / 8))); - request.Headers.Add("x-bili-signature-version", "2.0"); - request.Headers.Add("x-bili-timestamp", TimeProvider.GetUtcNow().ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture)); - - var signature = BuildSignatureString(request, Options.ClientSecret); + + var xbiliHeaders = BuildXBiliHeaders(); + foreach ((var name, var value) in xbiliHeaders) + { + request.Headers.Add(name, value); + } + + var signature = BuildSignatureString(xbiliHeaders, Options.ClientSecret); request.Headers.Add("Authorization", signature); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); @@ -145,6 +147,23 @@ protected override async Task CreateTicketAsync( return new AuthenticationTicket(context.Principal!, context.Properties, Scheme.Name); } + private SortedList BuildXBiliHeaders() => new(6, StringComparer.OrdinalIgnoreCase) + { + { "x-bili-accesskeyid", Options.ClientId }, + { "x-bili-content-md5", "d41d8cd98f00b204e9800998ecf8427e" }, // It's a GET request so there's no content, so we send the MD5 hash of an empty string + { "x-bili-signature-method", "HMAC-SHA256" }, + { "x-bili-signature-nonce", GenerateNonce() }, + { "x-bili-signature-version", "2.0" }, + { "x-bili-timestamp", TimeProvider.GetUtcNow().ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture) } + }; + + private static string GenerateNonce() + { + Span bytes = stackalloc byte[256 / 8]; + RandomNumberGenerator.Fill(bytes); + return Base64Url.EncodeToString(bytes); + } + /// /// Check the code sent back by server for potential server errors. /// @@ -154,14 +173,13 @@ protected override async Task CreateTicketAsync( /// True if succeed, otherwise false. private static bool ValidateReturnCode(JsonElement element, out int code) { - code = 0; if (!element.TryGetProperty("code", out JsonElement errorCodeElement)) { + code = 0; return true; } code = errorCodeElement.GetInt32(); - return code == 0; } diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Bilibili/BilibiliTests.cs b/test/AspNet.Security.OAuth.Providers.Tests/Bilibili/BilibiliTests.cs index 333ef2c0b..2fbbca5db 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/Bilibili/BilibiliTests.cs +++ b/test/AspNet.Security.OAuth.Providers.Tests/Bilibili/BilibiliTests.cs @@ -4,7 +4,6 @@ * for more information concerning the license and the contributors participating to this project. */ -using System.Web; using Microsoft.AspNetCore.WebUtilities; namespace AspNet.Security.OAuth.Bilibili;