diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs index f01dd8b08..91cd48054 100644 --- a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs +++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs @@ -59,6 +59,12 @@ protected override async Task ExchangeCodeAsync([NotNull] OA ["timestamp"] = TimeProvider.GetUtcNow().ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), ["version"] = "1.0", }; + if (AlipayCertificationManager.Enable) + { + tokenRequestParameters["app_cert_sn"] = AlipayCertificationManager.AppCertSN; + tokenRequestParameters["alipay_root_cert_sn"] = AlipayCertificationManager.RootCertSN; + } + tokenRequestParameters.Add("sign", GetRSA2Signature(tokenRequestParameters)); // PKCE https://tools.ietf.org/html/rfc7636#section-4.5, see BuildChallengeUrl @@ -107,6 +113,12 @@ protected override async Task CreateTicketAsync( ["timestamp"] = TimeProvider.GetUtcNow().ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture), ["version"] = "1.0", }; + if (AlipayCertificationManager.Enable) + { + parameters["app_cert_sn"] = AlipayCertificationManager.AppCertSN; + parameters["alipay_root_cert_sn"] = AlipayCertificationManager.RootCertSN; + } + 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 5beceb715..b7f1f1d26 100644 --- a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs +++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationOptions.cs @@ -34,9 +34,31 @@ public AlipayAuthenticationOptions() ClaimActions.MapCustomJson(System.Security.Claims.ClaimTypes.NameIdentifier, user => user.GetString(NameIdentifierKey)); } + public override void Validate() + { + base.Validate(); + + if (!string.IsNullOrEmpty(AppCertPath) && !string.IsNullOrEmpty(RootCertPath)) + { + try + { + AlipayCertificationManager.AppCertSN = AlipayCertificationManager.GetCertSN(AppCertPath); + AlipayCertificationManager.RootCertSN = AlipayCertificationManager.GetRootCertSN(RootCertPath); + AlipayCertificationManager.Enable = true; + } + catch (Exception ex) + { + throw new ArgumentException($"The '{nameof(AppCertPath)}' and '{nameof(RootCertPath)}' options must be set to the correct certificate files.", ex); + } + } + } + /// - /// Alipay user system internal identifier, which will no longer be open independently in the future and will be replaced by open_id. Currently the default is user_id /// See https://opendocs.alipay.com/mini/0ai2i6?pathHash=13dd5946 /// public string NameIdentifierKey { get; set; } = "user_id"; + + public string? AppCertPath { get; set; } + + public string? RootCertPath { get; set; } } diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayCertificationManager.cs b/src/AspNet.Security.OAuth.Alipay/AlipayCertificationManager.cs new file mode 100644 index 000000000..ab53f9bdf --- /dev/null +++ b/src/AspNet.Security.OAuth.Alipay/AlipayCertificationManager.cs @@ -0,0 +1,61 @@ +/* + * 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.Numerics; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace AspNet.Security.OAuth.Alipay; + +/// +/// This class of code refers to the Alipay official SDK code. +/// See https://github.com/alipay/alipay-sdk-net-all/blob/1b7b73909954b107bddb6476dec68aafcc3f16e9/v2/AlipaySDKNet.Standard/Util/AntCertificationUtil.cs +/// See https://opendocs.alipay.com/common/056zub?pathHash=91c49771 +/// +internal static class AlipayCertificationManager +{ + public static bool Enable { get; set; } + + public static string AppCertSN { get; set; } = string.Empty; + + public static string RootCertSN { get; set; } = string.Empty; + + internal static string GetCertSN([NotNull] string certFilePath) + { + using var cert = X509Certificate.CreateFromCertFile(certFilePath); + return GetCertSN(cert); + } + + internal static string GetCertSN([NotNull] X509Certificate cert) + { + var issuerDN = cert.Issuer.Replace(", ", ",", StringComparison.InvariantCulture); + var input = issuerDN + new BigInteger(cert.GetSerialNumber()); +#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms + var certSN = Convert.ToHexString(MD5.HashData(Encoding.UTF8.GetBytes(input))).ToLowerInvariant(); +#pragma warning restore CA5351 // Do Not Use Broken Cryptographic Algorithms + return certSN; + } + + internal static string GetRootCertSN([NotNull] string rootCertPath, [NotNull] string signType = "RSA2") + { + var certSNs = new List(); + var certCollection = new X509Certificate2Collection(); + certCollection.ImportFromPemFile(rootCertPath); + + foreach (var cert in certCollection) + { + if ((signType.StartsWith("RSA", StringComparison.Ordinal) && cert.SignatureAlgorithm.Value?.StartsWith("1.2.840.113549.1.1", StringComparison.Ordinal) == true) || + (signType.Equals("SM2", StringComparison.Ordinal) && cert.SignatureAlgorithm.Value?.StartsWith("1.2.156.10197.1.501", StringComparison.Ordinal) == true)) + { + certSNs.Add(GetCertSN(cert)); + } + } + + var rootCertSN = string.Join('_', certSNs); + return rootCertSN; + } +}