From acc2fe006b5828c49eaeef7841c84efef21eb5e6 Mon Sep 17 00:00:00 2001 From: Aigio Liu Date: Tue, 18 Nov 2025 16:12:07 +0800 Subject: [PATCH 1/4] Add support for Alipay certificate signing --- .../AlipayAuthenticationConstants.cs | 11 ++ .../AlipayAuthenticationHandler.cs | 32 +++- .../AlipayAuthenticationOptions.cs | 47 ++++++ .../AlipayAuthenticationOptionsExtensions.cs | 45 ++++++ .../AntCertificationUtil.cs | 140 ++++++++++++++++++ 5 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptionsExtensions.cs create mode 100644 src/AspNet.Security.OAuth.Alipay/AntCertificationUtil.cs diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationConstants.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationConstants.cs index d6188fc4f..1a2eeca51 100644 --- a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationConstants.cs +++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationConstants.cs @@ -25,5 +25,16 @@ public static class Claims /// The user's gender. F: Female; M: Male. /// public const string Gender = "urn:alipay:gender"; + + /// + /// OpenID is the unique identifier of Alipay users in the application dimension. + /// See https://opendocs.alipay.com/mini/0ai2i6 + /// + public const string OpenId = "urn:alipay:open_id"; + + /// + /// Alipay user system internal identifier, will no longer be independently open in the future, and will be replaced by OpenID. + /// + public const string UserId = "urn:alipay:user_id"; } } diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs index b33da5630..f8327553b 100644 --- a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs +++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs @@ -11,6 +11,7 @@ using System.Text; using System.Text.Encodings.Web; using System.Text.Json; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; @@ -44,6 +45,21 @@ protected override Task HandleRemoteAuthenticateAsync() return base.HandleRemoteAuthenticateAsync(); } + private const string SignType = "RSA2"; + + private async Task AddCertSignatureParametersAsync(SortedDictionary parameters) + { + ArgumentNullException.ThrowIfNull(Options.PrivateKey); + ArgumentNullException.ThrowIfNull(Options.AppCertSNKeyId); + ArgumentNullException.ThrowIfNull(Options.RootCertSNKeyId); + + var app_cert_sn = await Options.PrivateKey(Options.AppCertSNKeyId, Context.RequestAborted); + var alipay_root_cert_sn = await Options.PrivateKey(Options.RootCertSNKeyId, Context.RequestAborted); + + parameters["app_cert_sn"] = AntCertificationUtil.GetCertSN(app_cert_sn.Span); + parameters["alipay_root_cert_sn"] = AntCertificationUtil.GetRootCertSN(alipay_root_cert_sn.Span, SignType); + } + protected override async Task ExchangeCodeAsync([NotNull] OAuthCodeExchangeContext context) { // See https://opendocs.alipay.com/apis/api_9/alipay.system.oauth.token for details. @@ -55,10 +71,16 @@ protected override async Task ExchangeCodeAsync([NotNull] OA ["format"] = "JSON", ["grant_type"] = "authorization_code", ["method"] = "alipay.system.oauth.token", - ["sign_type"] = "RSA2", + ["sign_type"] = SignType, ["timestamp"] = TimeProvider.GetUtcNow().ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), ["version"] = "1.0", }; + + if (Options.EnableCertSignature) + { + await AddCertSignatureParametersAsync(tokenRequestParameters); + } + tokenRequestParameters.Add("sign", GetRSA2Signature(tokenRequestParameters)); // PKCE https://tools.ietf.org/html/rfc7636#section-4.5, see BuildChallengeUrl @@ -103,10 +125,16 @@ protected override async Task CreateTicketAsync( ["charset"] = "utf-8", ["format"] = "JSON", ["method"] = "alipay.user.info.share", - ["sign_type"] = "RSA2", + ["sign_type"] = SignType, ["timestamp"] = TimeProvider.GetUtcNow().ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), ["version"] = "1.0", }; + + if (Options.EnableCertSignature) + { + await AddCertSignatureParametersAsync(parameters); + } + parameters.Add("sign", GetRSA2Signature(parameters)); var address = QueryHelpers.AddQueryString(Options.UserInformationEndpoint, parameters); diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs index 08677ddd5..0c0fe33df 100644 --- a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs +++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs @@ -29,5 +29,52 @@ public AlipayAuthenticationOptions() ClaimActions.MapJsonKey(Claims.Gender, "gender"); ClaimActions.MapJsonKey(Claims.Nickname, "nick_name"); ClaimActions.MapJsonKey(Claims.Province, "province"); + ClaimActions.MapJsonKey(Claims.OpenId, "open_id"); + ClaimActions.MapJsonKey(Claims.UserId, "user_id"); + } + + /// + /// Get or set a value indicating whether to use certificate mode for signature implementation. + /// https://opendocs.alipay.com/common/057k53?pathHash=e18d6f77#%E8%AF%81%E4%B9%A6%E6%A8%A1%E5%BC%8F + /// + public bool EnableCertSignature { get; set; } + + /// + /// Gets or sets the optional ID for your Sign in with app_cert_sn. + /// + public string? AppCertSNKeyId { get; set; } + + /// + /// Gets or sets the optional ID for your Sign in with alipay_root_cert_sn. + /// + public string? RootCertSNKeyId { get; set; } + + /// + /// Gets or sets an optional delegate to get the client's private key which is passed + /// the value of the or property and the + /// associated with the current HTTP request. + /// + /// + /// The private key should be in PKCS #8 (.p8) format. + /// + public Func>>? PrivateKey { get; set; } + + /// + public override void Validate() + { + base.Validate(); + + if (EnableCertSignature) + { + if (string.IsNullOrEmpty(AppCertSNKeyId)) + { + throw new ArgumentException($"The '{nameof(AppCertSNKeyId)}' option must be provided if the '{nameof(EnableCertSignature)}' option is set to true.", nameof(AppCertSNKeyId)); + } + + if (string.IsNullOrEmpty(RootCertSNKeyId)) + { + throw new ArgumentException($"The '{nameof(RootCertSNKeyId)}' option must be provided if the '{nameof(EnableCertSignature)}' option is set to true.", nameof(RootCertSNKeyId)); + } + } } } diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptionsExtensions.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptionsExtensions.cs new file mode 100644 index 000000000..2a3acc736 --- /dev/null +++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptionsExtensions.cs @@ -0,0 +1,45 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers + * for more information concerning the license and the contributors participating to this project. + */ + +using AspNet.Security.OAuth.Alipay; +using Microsoft.Extensions.FileProviders; + +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Extension methods to configure Sign in with Alipay authentication capabilities for an HTTP application pipeline. +/// +public static class AlipayAuthenticationOptionsExtensions +{ + /// + /// Configures the application to use a specified private to generate a client secret for the provider. + /// + /// The Apple authentication options to configure. + /// + /// A delegate to a method to return the for the private + /// key which is passed the value of or . + /// + /// + /// The value of the argument. + /// + public static AlipayAuthenticationOptions UsePrivateKey( + [NotNull] this AlipayAuthenticationOptions options, + [NotNull] Func privateKeyFile) + { + options.EnableCertSignature = true; + options.PrivateKey = async (keyId, cancellationToken) => + { + var fileInfo = privateKeyFile(keyId); + + using var stream = fileInfo.CreateReadStream(); + using var reader = new StreamReader(stream); + + return (await reader.ReadToEndAsync(cancellationToken)).AsMemory(); + }; + + return options; + } +} diff --git a/src/AspNet.Security.OAuth.Alipay/AntCertificationUtil.cs b/src/AspNet.Security.OAuth.Alipay/AntCertificationUtil.cs new file mode 100644 index 000000000..08c9e6de3 --- /dev/null +++ b/src/AspNet.Security.OAuth.Alipay/AntCertificationUtil.cs @@ -0,0 +1,140 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers + * for more information concerning the license and the contributors participating to this project. + */ + +using System.Buffers; +using System.Globalization; +using System.Numerics; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace AspNet.Security.OAuth.Alipay; + +/// +/// https://github.com/alipay/alipay-sdk-net-all/blob/master/v2/AlipaySDKNet.Standard/Util/AntCertificationUtil.cs +/// +internal static class AntCertificationUtil +{ + public static string GetCertSN(ReadOnlySpan certContent) + { + using var cert = X509Certificate2.CreateFromPem(certContent); + return GetCertSN(cert); + } + + public static string GetCertSN(X509Certificate2 cert) + { + var issuerDN = cert.Issuer.Replace(", ", ",", StringComparison.InvariantCulture).AsSpan(); + var serialNumber = new BigInteger(cert.GetSerialNumber()).ToString(CultureInfo.InvariantCulture); + var len = issuerDN.Length + serialNumber.Length; + char[]? array = null; + Span chars = len <= StackallocByteThreshold ? + stackalloc char[StackallocByteThreshold] : + (array = ArrayPool.Shared.Rent(len)); + try + { + if (issuerDN.StartsWith("CN", StringComparison.InvariantCulture)) + { + issuerDN.CopyTo(chars); + serialNumber.AsSpan().CopyTo(chars[issuerDN.Length..]); + return CalculateMd5(chars[..len]); + } + + List attributes = []; + var issuerDNSplit = issuerDN.Split(','); + while (issuerDNSplit.MoveNext()) + { + attributes.Add(issuerDNSplit.Current); + } + + Span charsTemp = chars; + for (var i = attributes.Count - 1; i >= 0; i--) // attributes.Reverse() + { + var it = issuerDN[attributes[i]]; + it.CopyTo(charsTemp); + charsTemp = charsTemp[it.Length..]; + if (i != 0) + { + charsTemp[0] = ','; + charsTemp = charsTemp[1..]; + } + } + + serialNumber.AsSpan().CopyTo(charsTemp); + return CalculateMd5(chars[..len]); + } + finally + { + if (array != null) + { + ArrayPool.Shared.Return(array); + } + } + } + + public static string GetRootCertSN(ReadOnlySpan rootCertContent, string signType = "RSA2") + { + var rootCertSN = string.Join('_', GetRootCertSNCore(rootCertContent, signType)); + return rootCertSN; + } + + private static IEnumerable GetRootCertSNCore(X509Certificate2Collection x509Certificates, string signType) + { + foreach (X509Certificate2 cert in x509Certificates) + { + var signatureAlgorithm = cert.SignatureAlgorithm.Value; + if (signatureAlgorithm != null) + { + if ((signType.StartsWith("RSA", StringComparison.InvariantCultureIgnoreCase) && + signatureAlgorithm.StartsWith("1.2.840.113549.1.1", StringComparison.InvariantCultureIgnoreCase)) || + (signType.StartsWith("SM2", StringComparison.InvariantCultureIgnoreCase) && + signatureAlgorithm.StartsWith("1.2.156.10197.1.501", StringComparison.InvariantCultureIgnoreCase))) + { + yield return GetCertSN(cert); + } + } + } + } + + private static IEnumerable GetRootCertSNCore(ReadOnlySpan rootCertContent, string signType) + { + X509Certificate2Collection x509Certificates = []; + x509Certificates.ImportFromPem(rootCertContent); + return GetRootCertSNCore(x509Certificates, signType); + } + + /// + /// https://github.com/dotnet/runtime/blob/v9.0.8/src/libraries/System.Text.Json/Common/JsonConstants.cs#L12 + /// + private const int StackallocByteThreshold = 256; + + private static string CalculateMd5(ReadOnlySpan chars) + { + var lenU8 = Encoding.UTF8.GetMaxByteCount(chars.Length); + byte[]? array = null; + Span bytes = lenU8 <= StackallocByteThreshold ? + stackalloc byte[StackallocByteThreshold] : + (array = ArrayPool.Shared.Rent(lenU8)); + try + { + Encoding.UTF8.TryGetBytes(chars, bytes, out var bytesWritten); + bytes = bytes[..bytesWritten]; + + Span hash = stackalloc byte[MD5.HashSizeInBytes]; +#pragma warning disable CA5351 + MD5.HashData(bytes, hash); +#pragma warning restore CA5351 + + return Convert.ToHexStringLower(hash); + } + finally + { + if (array != null) + { + ArrayPool.Shared.Return(array); + } + } + } +} From e54d1336545a0bbff0777650575ce4011973513c Mon Sep 17 00:00:00 2001 From: LY Date: Wed, 10 Dec 2025 18:55:34 +0800 Subject: [PATCH 2/4] Improve naming conventions and documentation comments --- .../AlipayAuthenticationConstants.cs | 4 +-- .../AlipayAuthenticationHandler.cs | 19 +++++++------- .../AlipayAuthenticationOptions.cs | 26 ++++++++++--------- .../AlipayAuthenticationOptionsExtensions.cs | 6 ++--- .../AntCertificationUtil.cs | 5 ++-- 5 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationConstants.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationConstants.cs index 1a2eeca51..67122afe5 100644 --- a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationConstants.cs +++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationConstants.cs @@ -27,13 +27,13 @@ public static class Claims public const string Gender = "urn:alipay:gender"; /// - /// OpenID is the unique identifier of Alipay users in the application dimension. + /// OpenID is the unique identifier for Alipay users at the application level. /// See https://opendocs.alipay.com/mini/0ai2i6 /// public const string OpenId = "urn:alipay:open_id"; /// - /// Alipay user system internal identifier, will no longer be independently open in the future, and will be replaced by OpenID. + /// The internal identifier for Alipay users will no longer be independently available going forward and will be replaced by OpenID. /// public const string UserId = "urn:alipay:user_id"; } diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs index f8327553b..a1676664b 100644 --- a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs +++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs @@ -11,7 +11,6 @@ using System.Text; using System.Text.Encodings.Web; using System.Text.Json; -using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; @@ -47,14 +46,14 @@ protected override Task HandleRemoteAuthenticateAsync() private const string SignType = "RSA2"; - private async Task AddCertSignatureParametersAsync(SortedDictionary parameters) + private async Task AddCertificateSignatureParametersAsync(SortedDictionary parameters) { ArgumentNullException.ThrowIfNull(Options.PrivateKey); - ArgumentNullException.ThrowIfNull(Options.AppCertSNKeyId); - ArgumentNullException.ThrowIfNull(Options.RootCertSNKeyId); + ArgumentNullException.ThrowIfNull(Options.ApplicationCertificateSnKeyId); + ArgumentNullException.ThrowIfNull(Options.RootCertificateSnKeyId); - var app_cert_sn = await Options.PrivateKey(Options.AppCertSNKeyId, Context.RequestAborted); - var alipay_root_cert_sn = await Options.PrivateKey(Options.RootCertSNKeyId, Context.RequestAborted); + var app_cert_sn = await Options.PrivateKey(Options.ApplicationCertificateSnKeyId, Context.RequestAborted); + var alipay_root_cert_sn = await Options.PrivateKey(Options.RootCertificateSnKeyId, Context.RequestAborted); parameters["app_cert_sn"] = AntCertificationUtil.GetCertSN(app_cert_sn.Span); parameters["alipay_root_cert_sn"] = AntCertificationUtil.GetRootCertSN(alipay_root_cert_sn.Span, SignType); @@ -76,9 +75,9 @@ protected override async Task ExchangeCodeAsync([NotNull] OA ["version"] = "1.0", }; - if (Options.EnableCertSignature) + if (Options.UseCertificateSignatures) { - await AddCertSignatureParametersAsync(tokenRequestParameters); + await AddCertificateSignatureParametersAsync(tokenRequestParameters); } tokenRequestParameters.Add("sign", GetRSA2Signature(tokenRequestParameters)); @@ -130,9 +129,9 @@ protected override async Task CreateTicketAsync( ["version"] = "1.0", }; - if (Options.EnableCertSignature) + if (Options.UseCertificateSignatures) { - await AddCertSignatureParametersAsync(parameters); + await AddCertificateSignatureParametersAsync(parameters); } parameters.Add("sign", GetRSA2Signature(parameters)); diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs index 0c0fe33df..0b7261288 100644 --- a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs +++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs @@ -34,24 +34,26 @@ public AlipayAuthenticationOptions() } /// - /// Get or set a value indicating whether to use certificate mode for signature implementation. + /// Gets or sets a value indicating whether to use certificate mode for signature implementation. /// https://opendocs.alipay.com/common/057k53?pathHash=e18d6f77#%E8%AF%81%E4%B9%A6%E6%A8%A1%E5%BC%8F /// - public bool EnableCertSignature { get; set; } + public bool UseCertificateSignatures { get; set; } /// - /// Gets or sets the optional ID for your Sign in with app_cert_sn. + /// Gets or sets the optional ID for your Sign in with Application Public Key Certificate SN(app_cert_sn). + /// https://opendocs.alipay.com/support/01raux /// - public string? AppCertSNKeyId { get; set; } + public string? ApplicationCertificateSnKeyId { get; set; } /// - /// Gets or sets the optional ID for your Sign in with alipay_root_cert_sn. + /// Gets or sets the optional ID for your Sign in with Alipay Root Certificate SN. + /// https://opendocs.alipay.com/support/01rauy /// - public string? RootCertSNKeyId { get; set; } + public string? RootCertificateSnKeyId { get; set; } /// /// Gets or sets an optional delegate to get the client's private key which is passed - /// the value of the or property and the + /// the value of the or property and the /// associated with the current HTTP request. /// /// @@ -64,16 +66,16 @@ public override void Validate() { base.Validate(); - if (EnableCertSignature) + if (UseCertificateSignatures) { - if (string.IsNullOrEmpty(AppCertSNKeyId)) + if (string.IsNullOrEmpty(ApplicationCertificateSnKeyId)) { - throw new ArgumentException($"The '{nameof(AppCertSNKeyId)}' option must be provided if the '{nameof(EnableCertSignature)}' option is set to true.", nameof(AppCertSNKeyId)); + throw new ArgumentException($"The '{nameof(ApplicationCertificateSnKeyId)}' option must be provided if the '{nameof(UseCertificateSignatures)}' option is set to true.", nameof(ApplicationCertificateSnKeyId)); } - if (string.IsNullOrEmpty(RootCertSNKeyId)) + if (string.IsNullOrEmpty(RootCertificateSnKeyId)) { - throw new ArgumentException($"The '{nameof(RootCertSNKeyId)}' option must be provided if the '{nameof(EnableCertSignature)}' option is set to true.", nameof(RootCertSNKeyId)); + throw new ArgumentException($"The '{nameof(RootCertificateSnKeyId)}' option must be provided if the '{nameof(UseCertificateSignatures)}' option is set to true.", nameof(RootCertificateSnKeyId)); } } } diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptionsExtensions.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptionsExtensions.cs index 2a3acc736..2b754f3bc 100644 --- a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptionsExtensions.cs +++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptionsExtensions.cs @@ -15,12 +15,12 @@ namespace Microsoft.Extensions.DependencyInjection; public static class AlipayAuthenticationOptionsExtensions { /// - /// Configures the application to use a specified private to generate a client secret for the provider. + /// Configures the application to use a specified private key to generate a client secret for the provider. /// /// The Apple authentication options to configure. /// /// A delegate to a method to return the for the private - /// key which is passed the value of or . + /// key which is passed the value of or . /// /// /// The value of the argument. @@ -29,7 +29,7 @@ public static AlipayAuthenticationOptions UsePrivateKey( [NotNull] this AlipayAuthenticationOptions options, [NotNull] Func privateKeyFile) { - options.EnableCertSignature = true; + options.UseCertificateSignatures = true; options.PrivateKey = async (keyId, cancellationToken) => { var fileInfo = privateKeyFile(keyId); diff --git a/src/AspNet.Security.OAuth.Alipay/AntCertificationUtil.cs b/src/AspNet.Security.OAuth.Alipay/AntCertificationUtil.cs index 08c9e6de3..c8398917c 100644 --- a/src/AspNet.Security.OAuth.Alipay/AntCertificationUtil.cs +++ b/src/AspNet.Security.OAuth.Alipay/AntCertificationUtil.cs @@ -14,7 +14,7 @@ namespace AspNet.Security.OAuth.Alipay; /// -/// https://github.com/alipay/alipay-sdk-net-all/blob/master/v2/AlipaySDKNet.Standard/Util/AntCertificationUtil.cs +/// https://github.com/alipay/alipay-sdk-net-all/blob/b482d75d322e740760f9230d2a3859090af642a7/v2/AlipaySDKNet.Standard/Util/AntCertificationUtil.cs /// internal static class AntCertificationUtil { @@ -49,8 +49,9 @@ public static string GetCertSN(X509Certificate2 cert) attributes.Add(issuerDNSplit.Current); } + // attributes.Reverse() Span charsTemp = chars; - for (var i = attributes.Count - 1; i >= 0; i--) // attributes.Reverse() + for (var i = attributes.Count - 1; i >= 0; i--) { var it = issuerDN[attributes[i]]; it.CopyTo(charsTemp); From db212e0deabd5a4568c8beb6d82ef97a4f58ee8a Mon Sep 17 00:00:00 2001 From: LY Date: Thu, 11 Dec 2025 10:42:53 +0800 Subject: [PATCH 3/4] Streamline code --- .../AlipayAuthenticationHandler.cs | 4 +- ...tionUtil.cs => AlipayCertificationUtil.cs} | 52 +++---------------- 2 files changed, 10 insertions(+), 46 deletions(-) rename src/AspNet.Security.OAuth.Alipay/{AntCertificationUtil.cs => AlipayCertificationUtil.cs} (69%) diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs index a1676664b..70893e103 100644 --- a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs +++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs @@ -55,8 +55,8 @@ private async Task AddCertificateSignatureParametersAsync(SortedDictionary ExchangeCodeAsync([NotNull] OAuthCodeExchangeContext context) diff --git a/src/AspNet.Security.OAuth.Alipay/AntCertificationUtil.cs b/src/AspNet.Security.OAuth.Alipay/AlipayCertificationUtil.cs similarity index 69% rename from src/AspNet.Security.OAuth.Alipay/AntCertificationUtil.cs rename to src/AspNet.Security.OAuth.Alipay/AlipayCertificationUtil.cs index c8398917c..c627280e8 100644 --- a/src/AspNet.Security.OAuth.Alipay/AntCertificationUtil.cs +++ b/src/AspNet.Security.OAuth.Alipay/AlipayCertificationUtil.cs @@ -16,7 +16,7 @@ namespace AspNet.Security.OAuth.Alipay; /// /// https://github.com/alipay/alipay-sdk-net-all/blob/b482d75d322e740760f9230d2a3859090af642a7/v2/AlipaySDKNet.Standard/Util/AntCertificationUtil.cs /// -internal static class AntCertificationUtil +internal static class AlipayCertificationUtil { public static string GetCertSN(ReadOnlySpan certContent) { @@ -26,53 +26,17 @@ public static string GetCertSN(ReadOnlySpan certContent) public static string GetCertSN(X509Certificate2 cert) { - var issuerDN = cert.Issuer.Replace(", ", ",", StringComparison.InvariantCulture).AsSpan(); + var issuerDN = cert.Issuer.Replace(", ", ",", StringComparison.InvariantCulture); var serialNumber = new BigInteger(cert.GetSerialNumber()).ToString(CultureInfo.InvariantCulture); - var len = issuerDN.Length + serialNumber.Length; - char[]? array = null; - Span chars = len <= StackallocByteThreshold ? - stackalloc char[StackallocByteThreshold] : - (array = ArrayPool.Shared.Rent(len)); - try - { - if (issuerDN.StartsWith("CN", StringComparison.InvariantCulture)) - { - issuerDN.CopyTo(chars); - serialNumber.AsSpan().CopyTo(chars[issuerDN.Length..]); - return CalculateMd5(chars[..len]); - } - - List attributes = []; - var issuerDNSplit = issuerDN.Split(','); - while (issuerDNSplit.MoveNext()) - { - attributes.Add(issuerDNSplit.Current); - } - - // attributes.Reverse() - Span charsTemp = chars; - for (var i = attributes.Count - 1; i >= 0; i--) - { - var it = issuerDN[attributes[i]]; - it.CopyTo(charsTemp); - charsTemp = charsTemp[it.Length..]; - if (i != 0) - { - charsTemp[0] = ','; - charsTemp = charsTemp[1..]; - } - } - serialNumber.AsSpan().CopyTo(charsTemp); - return CalculateMd5(chars[..len]); - } - finally + if (issuerDN.StartsWith("CN", StringComparison.InvariantCulture)) { - if (array != null) - { - ArrayPool.Shared.Return(array); - } + return CalculateMd5(issuerDN + serialNumber); } + + var attributes = issuerDN.Split(','); + Array.Reverse(attributes); + return CalculateMd5(string.Join(',', attributes) + serialNumber); } public static string GetRootCertSN(ReadOnlySpan rootCertContent, string signType = "RSA2") From 872164d9d90d0d9c938ff2dd76ed3f0bf0d832be Mon Sep 17 00:00:00 2001 From: LY Date: Thu, 11 Dec 2025 10:47:13 +0800 Subject: [PATCH 4/4] Corrected Annotations --- src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs index 0b7261288..e61e84851 100644 --- a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs +++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs @@ -34,7 +34,7 @@ public AlipayAuthenticationOptions() } /// - /// Gets or sets a value indicating whether to use certificate mode for signature implementation. + /// Gets or sets a value indicating whether to use certificate mode for signing calls. /// https://opendocs.alipay.com/common/057k53?pathHash=e18d6f77#%E8%AF%81%E4%B9%A6%E6%A8%A1%E5%BC%8F /// public bool UseCertificateSignatures { get; set; }