-
Notifications
You must be signed in to change notification settings - Fork 550
Add support for Alipay certificate signing #1131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from 1 commit
acc2fe0
eabb122
e54d133
db212e0
872164d
ab6e026
59de161
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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"); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Get or set a value indicating whether to use certificate mode for signature implementation. | ||
AigioL marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
AigioL marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /// <para>https://opendocs.alipay.com/common/057k53?pathHash=e18d6f77#%E8%AF%81%E4%B9%A6%E6%A8%A1%E5%BC%8F</para> | ||
| /// </summary> | ||
| public bool EnableCertSignature { get; set; } | ||
AigioL marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /// <summary> | ||
| /// Gets or sets the optional ID for your Sign in with app_cert_sn. | ||
AigioL marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /// </summary> | ||
| public string? AppCertSNKeyId { get; set; } | ||
AigioL marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /// <summary> | ||
| /// Gets or sets the optional ID for your Sign in with alipay_root_cert_sn. | ||
AigioL marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /// </summary> | ||
| public string? RootCertSNKeyId { get; set; } | ||
AigioL marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /// <summary> | ||
| /// Gets or sets an optional delegate to get the client's private key which is passed | ||
| /// the value of the <see cref="AppCertSNKeyId"/> or <see cref="RootCertSNKeyId"/> property and the <see cref="CancellationToken"/> | ||
| /// associated with the current HTTP request. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// The private key should be in PKCS #8 (<c>.p8</c>) format. | ||
| /// </remarks> | ||
| public Func<string, CancellationToken, Task<ReadOnlyMemory<char>>>? PrivateKey { get; set; } | ||
|
|
||
| /// <inheritdoc /> | ||
| public override void Validate() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this also validate that |
||
| { | ||
| 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)); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
|
|
||
| /// <summary> | ||
| /// Extension methods to configure Sign in with Alipay authentication capabilities for an HTTP application pipeline. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it actually called "Sign in with Alipay", or is this just copy-paste where "Apple" has been changed to "Alipay"?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is correct, see https://auth.alipay.com/login/global.htm
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So it's called "Sign In Alipay", not "Sign In with Alipay"? |
||
| /// </summary> | ||
| public static class AlipayAuthenticationOptionsExtensions | ||
| { | ||
| /// <summary> | ||
| /// Configures the application to use a specified private to generate a client secret for the provider. | ||
AigioL marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /// </summary> | ||
| /// <param name="options">The Apple authentication options to configure.</param> | ||
AigioL marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /// <param name="privateKeyFile"> | ||
| /// A delegate to a method to return the <see cref="IFileInfo"/> for the private | ||
| /// key which is passed the value of <see cref="AlipayAuthenticationOptions.AppCertSNKeyId"/> or <see cref="AlipayAuthenticationOptions.RootCertSNKeyId"/>. | ||
| /// </param> | ||
| /// <returns> | ||
| /// The value of the <paramref name="options"/> argument. | ||
| /// </returns> | ||
| public static AlipayAuthenticationOptions UsePrivateKey( | ||
| [NotNull] this AlipayAuthenticationOptions options, | ||
| [NotNull] Func<string, IFileInfo> 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; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
|
|
||
| /// <summary> | ||
| /// https://github.com/alipay/alipay-sdk-net-all/blob/master/v2/AlipaySDKNet.Standard/Util/AntCertificationUtil.cs | ||
AigioL marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /// </summary> | ||
| internal static class AntCertificationUtil | ||
AigioL marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| public static string GetCertSN(ReadOnlySpan<char> 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<char> chars = len <= StackallocByteThreshold ? | ||
| stackalloc char[StackallocByteThreshold] : | ||
| (array = ArrayPool<char>.Shared.Rent(len)); | ||
| try | ||
| { | ||
| if (issuerDN.StartsWith("CN", StringComparison.InvariantCulture)) | ||
| { | ||
| issuerDN.CopyTo(chars); | ||
| serialNumber.AsSpan().CopyTo(chars[issuerDN.Length..]); | ||
| return CalculateMd5(chars[..len]); | ||
| } | ||
|
|
||
| List<Range> attributes = []; | ||
| var issuerDNSplit = issuerDN.Split(','); | ||
| while (issuerDNSplit.MoveNext()) | ||
| { | ||
| attributes.Add(issuerDNSplit.Current); | ||
| } | ||
|
|
||
| Span<char> 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<char>.Shared.Return(array); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public static string GetRootCertSN(ReadOnlySpan<char> rootCertContent, string signType = "RSA2") | ||
| { | ||
| var rootCertSN = string.Join('_', GetRootCertSNCore(rootCertContent, signType)); | ||
| return rootCertSN; | ||
| } | ||
|
|
||
| private static IEnumerable<string> 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<string> GetRootCertSNCore(ReadOnlySpan<char> rootCertContent, string signType) | ||
| { | ||
| X509Certificate2Collection x509Certificates = []; | ||
| x509Certificates.ImportFromPem(rootCertContent); | ||
| return GetRootCertSNCore(x509Certificates, signType); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// https://github.com/dotnet/runtime/blob/v9.0.8/src/libraries/System.Text.Json/Common/JsonConstants.cs#L12 | ||
| /// </summary> | ||
| private const int StackallocByteThreshold = 256; | ||
|
|
||
| private static string CalculateMd5(ReadOnlySpan<char> chars) | ||
| { | ||
| var lenU8 = Encoding.UTF8.GetMaxByteCount(chars.Length); | ||
| byte[]? array = null; | ||
| Span<byte> bytes = lenU8 <= StackallocByteThreshold ? | ||
| stackalloc byte[StackallocByteThreshold] : | ||
| (array = ArrayPool<byte>.Shared.Rent(lenU8)); | ||
| try | ||
| { | ||
| Encoding.UTF8.TryGetBytes(chars, bytes, out var bytesWritten); | ||
| bytes = bytes[..bytesWritten]; | ||
|
|
||
| Span<byte> 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<byte>.Shared.Return(array); | ||
| } | ||
| } | ||
| } | ||
| } | ||

Uh oh!
There was an error while loading. Please reload this page.