diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/ExtendedSDK/Global/WechatTenpayGlobalClient.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/ExtendedSDK/Global/WechatTenpayGlobalClient.cs index a26c639b..5ffbe0d1 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/ExtendedSDK/Global/WechatTenpayGlobalClient.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/ExtendedSDK/Global/WechatTenpayGlobalClient.cs @@ -69,7 +69,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.ExtendedSDK.Global PlatformAuthScheme = _ROOT_.Settings.PlatformAuthScheme.Certificate, PlatformCertificateManager = options.PlatformCertificateManager, AutoEncryptRequestSensitiveProperty = options.AutoEncryptRequestSensitiveProperty, - AutoDecryptResponseSensitiveProperty = options.AutoDecryptResponseSensitiveProperty + AutoDecryptResponseSensitiveProperty = options.AutoDecryptResponseSensitiveProperty, }, httpClient, disposeClient); Credentials = new Settings.Credentials(options); @@ -100,7 +100,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.ExtendedSDK.Global /// public IFlurlRequest CreateFlurlRequest(WechatTenpayGlobalRequest request, HttpMethod httpMethod, params object[] urlSegments) { - return base.CreateFlurlRequest(request, httpMethod, urlSegments); + return ProxyClient.CreateFlurlRequest(request, httpMethod, urlSegments); } /// diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventVerificationExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventVerificationExtensions.cs index b609abec..49a792e5 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventVerificationExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventVerificationExtensions.cs @@ -61,7 +61,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 { if (client is null) throw new ArgumentNullException(nameof(client)); - return WechatTenpayClientSigningExtensions.VerifySignature( + return WechatTenpayClientSigningExtensions._VerifySignature( client, strTimestamp: webhookTimestamp, strNonce: webhookNonce, @@ -127,7 +127,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 { if (client is null) throw new ArgumentNullException(nameof(client)); - return WechatTenpayClientSigningExtensions.VerifySignatureAsync( + return WechatTenpayClientSigningExtensions._VerifySignatureAsync( client, strTimestamp: webhookTimestamp, strNonce: webhookNonce, diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientRequestEncryptionExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientRequestEncryptionExtensions.cs index 7745e351..255c45e8 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientRequestEncryptionExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientRequestEncryptionExtensions.cs @@ -25,16 +25,37 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 if (client is null) throw new ArgumentNullException(nameof(client)); if (request is null) throw new ArgumentNullException(nameof(request)); - switch (client.PlatformAuthScheme) + bool required = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute)); + if (!required) + return request; + + try { - case PlatformAuthScheme.Certificate: - return EncryptRequestSensitivePropertyByCertificate(client, request); + switch (client.PlatformAuthScheme) + { + case PlatformAuthScheme.Certificate: + { + WechatTenpayClientRequestSerialNumberExtensions._EnsureRequestWechatpaySerialNumberIsSet(client, request); + return EncryptRequestSensitivePropertyUseCertificateManager(client.PlatformCertificateManager, client.Credentials.SignScheme, request); + } - case PlatformAuthScheme.PublicKey: - return EncryptRequestSensitivePropertyByPublicKey(client, request); + case PlatformAuthScheme.PublicKey: + { + WechatTenpayClientRequestSerialNumberExtensions._EnsureRequestWechatpaySerialNumberIsSet(client, request); + return EncryptRequestSensitivePropertyUsePublicKeyManager(client.PlatformPublicKeyManager, client.Credentials.SignScheme, request); + } - default: - throw new WechatTenpayException($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\"."); + default: + throw new NotSupportedException($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\"."); + } + } + catch (WechatTenpayException) + { + throw; + } + catch (Exception ex) + { + throw new WechatTenpayException("Failed to encrypt request. Please see the inner exception for more details.", ex); } } @@ -45,84 +66,47 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 /// /// /// - public static Task EncryptRequestSensitivePropertyAsync(this WechatTenpayClient client, TRequest request, CancellationToken cancellationToken = default) + public static async Task EncryptRequestSensitivePropertyAsync(this WechatTenpayClient client, TRequest request, CancellationToken cancellationToken = default) where TRequest : WechatTenpayRequest { if (client is null) throw new ArgumentNullException(nameof(client)); if (request is null) throw new ArgumentNullException(nameof(request)); - switch (client.PlatformAuthScheme) - { - case PlatformAuthScheme.Certificate: - return EncryptRequestSensitivePropertyByCertificateAsync(client, request); + bool required = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute)); + if (!required) + return request; - case PlatformAuthScheme.PublicKey: - return EncryptRequestSensitivePropertyByPublicKeyAsync(client, request); - - default: - throw new WechatTenpayException($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\"."); - } - } - - private static TRequest EncryptRequestSensitivePropertyByCertificate(this WechatTenpayClient client, TRequest request) - where TRequest : WechatTenpayRequest - { try { - bool requireEncrypt = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute)); - if (!requireEncrypt) - return request; - - string signScheme = client.Credentials.SignScheme; - string algorithmType = // 签名方式与加密算法保持一致:RSA_SHA256 签名需 RSA 加密,SM3 签名需 SM2 加密 - SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_RSA : - SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_SM2 : - throw new WechatTenpayException($"Failed to encrypt request. Unsupported signing scheme: \"{signScheme}\"."); - - string certificate; - if (string.IsNullOrEmpty(request.WechatpaySerialNumber)) + switch (client.PlatformAuthScheme) { - // 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的 - IEnumerable entries = client.PlatformCertificateManager.AllEntries() - .Where(e => e.AlgorithmType == algorithmType) - .OrderByDescending(e => e.ExpireTime); - if (!entries.Any()) - { - throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is empty. Please make sure you have downloaded platform (NOT merchant) certificates first."); - } + case PlatformAuthScheme.Certificate: + { + if (client.PlatformCertificateManager is not ICertificateManagerAsync) + { + // 降级为同步调用 + return EncryptRequestSensitivePropertyUseCertificateManager(client.PlatformCertificateManager, client.Credentials.SignScheme, request); + } - CertificateEntry entry = entries.First(); - certificate = entry.Certificate; - request.WechatpaySerialNumber = entry.SerialNumber; + await WechatTenpayClientRequestSerialNumberExtensions._EnsureRequestWechatpaySerialNumberIsSetAsync(client, request, cancellationToken).ConfigureAwait(false); + return await EncryptRequestSensitivePropertyUseCertificateManagerAsync((ICertificateManagerAsync)client.PlatformCertificateManager, client.Credentials.SignScheme, request, cancellationToken).ConfigureAwait(false); + } + + case PlatformAuthScheme.PublicKey: + { + if (client.PlatformPublicKeyManager is not IPublicKeyManagerAsync) + { + // 降级为同步调用 + return EncryptRequestSensitivePropertyUsePublicKeyManager(client.PlatformPublicKeyManager, client.Credentials.SignScheme, request); + } + + await WechatTenpayClientRequestSerialNumberExtensions._EnsureRequestWechatpaySerialNumberIsSetAsync(client, request, cancellationToken).ConfigureAwait(false); + return await EncryptRequestSensitivePropertyUsePublicKeyManagerAsync((IPublicKeyManagerAsync)client.PlatformPublicKeyManager, client.Credentials.SignScheme, request, cancellationToken).ConfigureAwait(false); + } + + default: + throw new NotSupportedException($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\"."); } - else - { - // 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值 - CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(request.WechatpaySerialNumber!); - if (!entry.HasValue) - { - throw new WechatTenpayException($"Failed to encrypt request, because the platform certificate manager does not contain a certificate matched the serial number \"{request.WechatpaySerialNumber}\". Please make sure you have downloaded platform (NOT merchant) certificates first."); - } - - certificate = entry.Value.Certificate; - } - - ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) => - { - if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute))) - return (false, oldValue); - - WechatTenpaySensitivePropertyAttribute? attribute = currentProp - .GetCustomAttributes() - .FirstOrDefault(attr => attr.Scheme == signScheme); - if (attribute is null) - return (false, oldValue); - - string newValue = GenerateEncryptedValueByCertificate(attribute.Algorithm, certificate, oldValue); - return (true, newValue); - }); - - return request; } catch (WechatTenpayException) { @@ -134,281 +118,128 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 } } - private static async Task EncryptRequestSensitivePropertyByCertificateAsync(this WechatTenpayClient client, TRequest request, CancellationToken cancellationToken = default) + private static TRequest EncryptRequestSensitivePropertyUseCertificateManager(ICertificateManager manager, string signScheme, TRequest request) where TRequest : WechatTenpayRequest { - if (client.PlatformCertificateManager is not ICertificateManagerAsync) - { - // 降级为同步调用 - return EncryptRequestSensitivePropertyByCertificate(client, request); - } + if (manager is null) + throw new NullReferenceException("The platform certificate manager is not configured."); - try - { - bool requireEncrypt = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute)); - if (!requireEncrypt) - return request; + CertificateEntry? entry = manager.GetEntry(request.WechatpaySerialNumber!); + if (!entry.HasValue) + throw new WechatTenpayException($"Failed to encrypt request, because the platform certificate manager does not contain a certificate matched the serial number \"{request.WechatpaySerialNumber}\". Please make sure you have downloaded platform (NOT merchant) certificates first."); - string signScheme = client.Credentials.SignScheme; - string algorithmType = // 签名方式与加密算法保持一致:RSA_SHA256 签名需 RSA 加密,SM3 签名需 SM2 加密 - SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_RSA : - SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_SM2 : - throw new WechatTenpayException($"Failed to encrypt request. Unsupported signing scheme: \"{signScheme}\"."); - - string certificate; - if (string.IsNullOrEmpty(request.WechatpaySerialNumber)) - { - if (client.PlatformCertificateManager is null) - throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is not initialized."); - - // 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的 - IEnumerable entries = await ((ICertificateManagerAsync)client.PlatformCertificateManager).AllEntriesAsync(cancellationToken).ConfigureAwait(false); - entries = entries.Where(e => e.AlgorithmType == algorithmType).OrderByDescending(e => e.ExpireTime); - if (!entries.Any()) - { - throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is empty. Please make sure you have downloaded platform (NOT merchant) certificates first."); - } - - CertificateEntry entry = entries.First(); - certificate = entry.Certificate; - request.WechatpaySerialNumber = entry.SerialNumber; - } - else - { - // 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值 - CertificateEntry? entry = await ((ICertificateManagerAsync)client.PlatformCertificateManager).GetEntryAsync(request.WechatpaySerialNumber!, cancellationToken).ConfigureAwait(false); - if (!entry.HasValue) - { - throw new WechatTenpayException($"Failed to encrypt request, because the platform certificate manager does not contain a certificate matched the serial number \"{request.WechatpaySerialNumber}\". Please make sure you have downloaded platform (NOT merchant) certificates first."); - } - - certificate = entry.Value.Certificate; - } - - ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) => - { - if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute))) - return (false, oldValue); - - WechatTenpaySensitivePropertyAttribute? attribute = currentProp - .GetCustomAttributes() - .FirstOrDefault(attr => attr.Scheme == signScheme); - if (attribute is null) - return (false, oldValue); - - string newValue = GenerateEncryptedValueByCertificate(attribute.Algorithm, certificate, oldValue); - return (true, newValue); - }); - - return request; - } - catch (WechatTenpayException) - { - throw; - } - catch (Exception ex) - { - throw new WechatTenpayException("Failed to encrypt request. Please see the inner exception for more details.", ex); - } + return PopulateRequestEncryptedFieldsByCertificate(signScheme, entry.Value.Certificate, ref request); } - private static TRequest EncryptRequestSensitivePropertyByPublicKey(this WechatTenpayClient client, TRequest request) + private static async Task EncryptRequestSensitivePropertyUseCertificateManagerAsync(ICertificateManagerAsync manager, string signScheme, TRequest request, CancellationToken cancellationToken = default) where TRequest : WechatTenpayRequest { - try - { - bool requireEncrypt = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute)); - if (!requireEncrypt) - return request; + if (manager is null) + throw new NullReferenceException("The platform certificate manager is not configured."); - string signScheme = client.Credentials.SignScheme; - string algorithmType = // 签名方式与加密算法保持一致:RSA_SHA256 签名需 RSA 加密,SM3 签名需 SM2 加密 - SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(signScheme) ? PublicKeyEntry.ALGORITHM_TYPE_RSA : - SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? PublicKeyEntry.ALGORITHM_TYPE_SM2 : - throw new WechatTenpayException($"Failed to encrypt request. Unsupported signing scheme: \"{signScheme}\"."); + CertificateEntry? entry = await manager.GetEntryAsync(request.WechatpaySerialNumber!, cancellationToken).ConfigureAwait(false); + if (!entry.HasValue) + throw new WechatTenpayException($"Failed to encrypt request, because the platform certificate manager does not contain a certificate matched the serial number \"{request.WechatpaySerialNumber}\". Please make sure you have downloaded platform (NOT merchant) certificates first."); - string publicKey; - if (string.IsNullOrEmpty(request.WechatpaySerialNumber)) - { - // 如果未在请求中指定特定的平台公钥 ID,从管理器中取第一个 - IEnumerable entries = client.PlatformPublicKeyManager.AllEntries() - .Where(e => e.AlgorithmType == algorithmType); - if (!entries.Any()) - { - throw new WechatTenpayException("Failed to encrypt request, because the platform public key manager is empty."); - } - - PublicKeyEntry entry = entries.First(); - publicKey = entry.PublicKey; - request.WechatpaySerialNumber = entry.SerialNumber; - } - else - { - // 如果已在请求中指定特定的平台公钥 ID,直接从管理器中取值 - PublicKeyEntry? entry = client.PlatformPublicKeyManager.GetEntry(request.WechatpaySerialNumber!); - if (!entry.HasValue) - { - throw new WechatTenpayException($"Failed to encrypt request, because the platform public key manager does not contain a key matched the serial number \"{request.WechatpaySerialNumber}\"."); - } - - publicKey = entry.Value.PublicKey; - } - - ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) => - { - if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute))) - return (false, oldValue); - - WechatTenpaySensitivePropertyAttribute? attribute = currentProp - .GetCustomAttributes() - .FirstOrDefault(attr => attr.Scheme == signScheme); - if (attribute is null) - return (false, oldValue); - - string newValue = GenerateEncryptedValueByPublicKey(attribute.Algorithm, publicKey, oldValue); - return (true, newValue); - }); - - return request; - } - catch (WechatTenpayException) - { - throw; - } - catch (Exception ex) - { - throw new WechatTenpayException("Failed to encrypt request. Please see the inner exception for more details.", ex); - } + return PopulateRequestEncryptedFieldsByCertificate(signScheme, entry.Value.Certificate, ref request); } - private static async Task EncryptRequestSensitivePropertyByPublicKeyAsync(this WechatTenpayClient client, TRequest request, CancellationToken cancellationToken = default) + private static TRequest EncryptRequestSensitivePropertyUsePublicKeyManager(IPublicKeyManager manager, string signScheme, TRequest request) where TRequest : WechatTenpayRequest { - if (client.PlatformPublicKeyManager is not IPublicKeyManagerAsync) - { - // 降级为同步调用 - return EncryptRequestSensitivePropertyByPublicKey(client, request); - } + if (manager is null) + throw new NullReferenceException("The platform public key manager is not configured."); - try - { - bool requireEncrypt = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute)); - if (!requireEncrypt) - return request; + PublicKeyEntry? entry = manager.GetEntry(request.WechatpaySerialNumber!); + if (!entry.HasValue) + throw new WechatTenpayException($"Failed to encrypt request, because the platform public key manager does not contain a key matched the serial number \"{request.WechatpaySerialNumber}\"."); - string signScheme = client.Credentials.SignScheme; - string algorithmType = // 签名方式与加密算法保持一致:RSA_SHA256 签名需 RSA 加密,SM3 签名需 SM2 加密 - SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(signScheme) ? PublicKeyEntry.ALGORITHM_TYPE_RSA : - SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? PublicKeyEntry.ALGORITHM_TYPE_SM2 : - throw new WechatTenpayException($"Failed to encrypt request. Unsupported signing scheme: \"{signScheme}\"."); - - string publicKey; - if (string.IsNullOrEmpty(request.WechatpaySerialNumber)) - { - if (client.PlatformPublicKeyManager is null) - throw new WechatTenpayException("Failed to encrypt request, because the platform public key manager is not initialized."); - - // 如果未在请求中指定特定的平台公钥 ID,从管理器中第一个 - IEnumerable entries = await ((IPublicKeyManagerAsync)client.PlatformPublicKeyManager).AllEntriesAsync(cancellationToken).ConfigureAwait(false); - entries = entries.Where(e => e.AlgorithmType == algorithmType); - if (!entries.Any()) - { - throw new WechatTenpayException("Failed to encrypt request, because the platform public key manager is empty."); - } - - PublicKeyEntry entry = entries.First(); - publicKey = entry.PublicKey; - request.WechatpaySerialNumber = entry.SerialNumber; - } - else - { - // 如果已在请求中指定特定的平台公钥 ID,直接从管理器中取值 - PublicKeyEntry? entry = await ((IPublicKeyManagerAsync)client.PlatformPublicKeyManager).GetEntryAsync(request.WechatpaySerialNumber!, cancellationToken).ConfigureAwait(false); - if (!entry.HasValue) - { - throw new WechatTenpayException($"Failed to encrypt request, because the platform public key manager does not contain a key matched the serial number \"{request.WechatpaySerialNumber}\"."); - } - - publicKey = entry.Value.PublicKey; - } - - ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) => - { - if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute))) - return (false, oldValue); - - WechatTenpaySensitivePropertyAttribute? attribute = currentProp - .GetCustomAttributes() - .FirstOrDefault(attr => attr.Scheme == signScheme); - if (attribute is null) - return (false, oldValue); - - string newValue = GenerateEncryptedValueByPublicKey(attribute.Algorithm, publicKey, oldValue); - return (true, newValue); - }); - - return request; - } - catch (WechatTenpayException) - { - throw; - } - catch (Exception ex) - { - throw new WechatTenpayException("Failed to encrypt request. Please see the inner exception for more details.", ex); - } + return PopulateRequestEncryptedFieldsByPublicKey(signScheme, entry.Value.PublicKey, ref request); } - private static string GenerateEncryptedValueByCertificate(string algorithm, string certificate, string value) + private static async Task EncryptRequestSensitivePropertyUsePublicKeyManagerAsync(IPublicKeyManagerAsync manager, string signScheme, TRequest request, CancellationToken cancellationToken = default) + where TRequest : WechatTenpayRequest { - switch (algorithm) + if (manager is null) + throw new NullReferenceException("The platform public key manager is not configured."); + + PublicKeyEntry? entry = await manager.GetEntryAsync(request.WechatpaySerialNumber!, cancellationToken).ConfigureAwait(false); + if (!entry.HasValue) + throw new WechatTenpayException($"Failed to encrypt request, because the platform public key manager does not contain a key matched the serial number \"{request.WechatpaySerialNumber}\"."); + + return PopulateRequestEncryptedFieldsByPublicKey(signScheme, entry.Value.PublicKey, ref request); + } + + private static TRequest PopulateRequestEncryptedFieldsByCertificate(string scheme, string certificate, ref TRequest request) + where TRequest : WechatTenpayRequest + { + switch (scheme) { - case EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1: - case EncryptionAlgorithms.RSA_2048_ECB_PKCS1: + case SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256: { string publicKey = RSAUtility.ExportPublicKeyFromCertificate(certificate); - return GenerateEncryptedValueByPublicKey(algorithm, publicKey, value); + return PopulateRequestEncryptedFieldsByPublicKey(scheme, publicKey, ref request); } - case EncryptionAlgorithms.SM2_C1C3C2_ASN1: + case SignSchemes.WECHATPAY2_SM2_WITH_SM3: { string publicKey = SM2Utility.ExportPublicKeyFromCertificate(certificate); - return GenerateEncryptedValueByPublicKey(algorithm, publicKey, value); + return PopulateRequestEncryptedFieldsByPublicKey(scheme, publicKey, ref request); } default: - throw new WechatTenpayException($"Failed to encrypt request. Unsupported encryption algorithm: \"{algorithm}\"."); + throw new NotSupportedException($"Unsupported signing scheme: \"{scheme}\"."); } } - private static string GenerateEncryptedValueByPublicKey(string algorithm, string publicKey, string value) + private static TRequest PopulateRequestEncryptedFieldsByPublicKey(string scheme, string publicKey, ref TRequest request) + where TRequest : WechatTenpayRequest { - switch (algorithm) + ReflectionHelper.ReplaceObjectStringProperties(ref request, (_, currentProp, oldValue) => { - case EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1: - return RSAUtility.EncryptWithECB( - publicKeyPem: publicKey, - plainData: value, - paddingMode: RSAUtility.PADDING_MODE_OAEPWITHSHA1ANDMGF1 - )!; + if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute))) + return (false, oldValue); - case EncryptionAlgorithms.RSA_2048_ECB_PKCS1: - return RSAUtility.EncryptWithECB( - publicKeyPem: publicKey, - plainData: value, - paddingMode: RSAUtility.PADDING_MODE_PKCS1 - )!; + WechatTenpaySensitivePropertyAttribute? attribute = currentProp + .GetCustomAttributes() + .FirstOrDefault(attr => attr.Scheme == scheme); + if (attribute is null) + return (false, oldValue); - case EncryptionAlgorithms.SM2_C1C3C2_ASN1: - return SM2Utility.Encrypt( - publicKeyPem: publicKey, - plainData: value, - asn1Encoding: true - )!; + string newValue; + switch (attribute.Algorithm) + { + case EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1: + newValue = RSAUtility.EncryptWithECB( + publicKeyPem: publicKey, + plainData: oldValue, + paddingMode: RSAUtility.PADDING_MODE_OAEPWITHSHA1ANDMGF1 + )!; + break; - default: - throw new WechatTenpayException($"Failed to encrypt request. Unsupported encryption algorithm: \"{algorithm}\"."); - } + case EncryptionAlgorithms.RSA_2048_ECB_PKCS1: + newValue = RSAUtility.EncryptWithECB( + publicKeyPem: publicKey, + plainData: oldValue, + paddingMode: RSAUtility.PADDING_MODE_PKCS1 + )!; + break; + + case EncryptionAlgorithms.SM2_C1C3C2_ASN1: + newValue = SM2Utility.Encrypt( + publicKeyPem: publicKey, + plainData: oldValue, + asn1Encoding: true + )!; + break; + + default: + throw new NotSupportedException($"Unsupported encryption algorithm: \"{attribute.Algorithm}\"."); + } + + return (true, newValue); + }); + return request; } } } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseDecryptionExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseDecryptionExtensions.cs index 9ca3d798..102bf49e 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseDecryptionExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseDecryptionExtensions.cs @@ -107,7 +107,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 string signScheme = client.Credentials.SignScheme; - ReflectionHelper.ReplaceObjectStringProperties(response, (_, currentProp, oldValue) => + ReflectionHelper.ReplaceObjectStringProperties(ref response, (_, currentProp, oldValue) => { if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute))) return (false, oldValue); diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseVerificationExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseVerificationExtensions.cs index 8e70d12d..fd9dc780 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseVerificationExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseVerificationExtensions.cs @@ -93,7 +93,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 { if (client is null) throw new ArgumentNullException(nameof(client)); - return WechatTenpayClientSigningExtensions.VerifySignature( + return WechatTenpayClientSigningExtensions._VerifySignature( client, strTimestamp: responseTimestamp, strNonce: responseNonce, @@ -192,7 +192,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 { if (client is null) throw new ArgumentNullException(nameof(client)); - return WechatTenpayClientSigningExtensions.VerifySignatureAsync( + return WechatTenpayClientSigningExtensions._VerifySignatureAsync( client, strTimestamp: responseTimestamp, strNonce: responseNonce, diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/[Internal]/WechatTenpayClientRequestSerialNumberExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/[Internal]/WechatTenpayClientRequestSerialNumberExtensions.cs new file mode 100644 index 00000000..0a2c6c4b --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/[Internal]/WechatTenpayClientRequestSerialNumberExtensions.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 +{ + using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants; + using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings; + + internal static class WechatTenpayClientRequestSerialNumberExtensions + { + public static TRequest _EnsureRequestWechatpaySerialNumberIsSet(this WechatTenpayClient client, TRequest request) + where TRequest : WechatTenpayRequest + { + if (client is null) throw new ArgumentNullException(nameof(client)); + if (request is null) throw new ArgumentNullException(nameof(request)); + + try + { + switch (client.PlatformAuthScheme) + { + case PlatformAuthScheme.Certificate: + return EnsureRequestWechatpaySerialNumberIsSetUseCertificateManager(client.PlatformCertificateManager, client.Credentials.SignScheme, request); + + case PlatformAuthScheme.PublicKey: + return EnsureRequestWechatpaySerialNumberIsSetUsePublicKeyManager(client.PlatformPublicKeyManager, client.Credentials.SignScheme, request); + + default: + throw new NotSupportedException($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\"."); + } + } + catch (WechatTenpayException) + { + throw; + } + catch (Exception ex) + { + throw new WechatTenpayException("Failed to set the wechatpay serial number of request. Please see the inner exception for more details.", ex); + } + } + + public static async Task _EnsureRequestWechatpaySerialNumberIsSetAsync(this WechatTenpayClient client, TRequest request, CancellationToken cancellationToken = default) + where TRequest : WechatTenpayRequest + { + if (client is null) throw new ArgumentNullException(nameof(client)); + if (request is null) throw new ArgumentNullException(nameof(request)); + + try + { + switch (client.PlatformAuthScheme) + { + case PlatformAuthScheme.Certificate: + { + if (client.PlatformCertificateManager is not ICertificateManagerAsync) + { + // 降级为同步调用 + return EnsureRequestWechatpaySerialNumberIsSetUseCertificateManager(client.PlatformCertificateManager, client.Credentials.SignScheme, request); + } + + return await EnsureRequestWechatpaySerialNumberIsSetUseCertificateManagerAsync((ICertificateManagerAsync)client.PlatformCertificateManager, client.Credentials.SignScheme, request).ConfigureAwait(false); + } + + case PlatformAuthScheme.PublicKey: + { + if (client.PlatformPublicKeyManager is not IPublicKeyManagerAsync) + { + // 降级为同步调用 + return EnsureRequestWechatpaySerialNumberIsSetUsePublicKeyManager(client.PlatformPublicKeyManager, client.Credentials.SignScheme, request); + } + + return await EnsureRequestWechatpaySerialNumberIsSetUsePublicKeyManagerAsync((IPublicKeyManagerAsync)client.PlatformPublicKeyManager, client.Credentials.SignScheme, request).ConfigureAwait(false); + } + + default: + throw new NotSupportedException($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\"."); + } + } + catch (WechatTenpayException) + { + throw; + } + catch (Exception ex) + { + throw new WechatTenpayException("Failed to set the wechatpay serial number of request. Please see the inner exception for more details.", ex); + } + } + + private static bool TryGetAlgorithmType(string signScheme, out string algorithmType) + { + algorithmType = // 签名方式与加密算法保持一致:RSA_SHA256 签名需 RSA 加密,SM3 签名需 SM2 加密 + SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_RSA : + SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_SM2 : + default!; + return !string.IsNullOrEmpty(algorithmType); + } + + private static TRequest EnsureRequestWechatpaySerialNumberIsSetUseCertificateManager(ICertificateManager manager, string signScheme, TRequest request) + where TRequest : WechatTenpayRequest + { + if (string.IsNullOrEmpty(request.WechatpaySerialNumber)) + { + if (!TryGetAlgorithmType(signScheme, out string algorithmType)) + throw new NotSupportedException($"Unsupported signing scheme: \"{signScheme}\"."); + + IEnumerable entries = manager.AllEntries(); + entries = entries.Where(e => e.AlgorithmType == algorithmType).OrderByDescending(e => e.ExpireTime); + if (!entries.Any()) + throw new Exception("The platform certificate manager is empty. Please make sure you have downloaded platform (NOT merchant) certificates first."); + + CertificateEntry entry = entries.First(); + request.WechatpaySerialNumber = entry.SerialNumber; + } + + return request; + } + + private static async Task EnsureRequestWechatpaySerialNumberIsSetUseCertificateManagerAsync(ICertificateManagerAsync manager, string signScheme, TRequest request, CancellationToken cancellationToken = default) + where TRequest : WechatTenpayRequest + { + if (string.IsNullOrEmpty(request.WechatpaySerialNumber)) + { + if (!TryGetAlgorithmType(signScheme, out string algorithmType)) + throw new NotSupportedException($"Unsupported signing scheme: \"{signScheme}\"."); + + IEnumerable entries = await manager.AllEntriesAsync(cancellationToken).ConfigureAwait(false); + entries = entries.Where(e => e.AlgorithmType == algorithmType).OrderByDescending(e => e.ExpireTime); + if (!entries.Any()) + throw new Exception("The platform certificate manager is empty. Please make sure you have downloaded platform (NOT merchant) certificates first."); + + CertificateEntry entry = entries.First(); + request.WechatpaySerialNumber = entry.SerialNumber; + } + + return request; + } + + private static TRequest EnsureRequestWechatpaySerialNumberIsSetUsePublicKeyManager(IPublicKeyManager manager, string signScheme, TRequest request) + where TRequest : WechatTenpayRequest + { + if (string.IsNullOrEmpty(request.WechatpaySerialNumber)) + { + if (!TryGetAlgorithmType(signScheme, out string algorithmType)) + throw new NotSupportedException($"Unsupported signing scheme: \"{signScheme}\"."); + + IEnumerable entries = manager.AllEntries(); + entries = entries.Where(e => e.AlgorithmType == algorithmType); + if (!entries.Any()) + throw new Exception("The platform public key manager is empty. Perhaps you forget to add one?"); + + PublicKeyEntry entry = entries.First(); + request.WechatpaySerialNumber = entry.SerialNumber; + } + return request; + } + + private static async Task EnsureRequestWechatpaySerialNumberIsSetUsePublicKeyManagerAsync(IPublicKeyManagerAsync manager, string signScheme, TRequest request, CancellationToken cancellationToken = default) + where TRequest : WechatTenpayRequest + { + if (string.IsNullOrEmpty(request.WechatpaySerialNumber)) + { + if (!TryGetAlgorithmType(signScheme, out string algorithmType)) + throw new NotSupportedException($"Unsupported signing scheme: \"{signScheme}\"."); + + IEnumerable entries = await manager.AllEntriesAsync(cancellationToken).ConfigureAwait(false); + entries = entries.Where(e => e.AlgorithmType == algorithmType); + if (!entries.Any()) + throw new Exception("The platform public key manager is empty. Perhaps you forget to add one?"); + + PublicKeyEntry entry = entries.First(); + request.WechatpaySerialNumber = entry.SerialNumber; + } + return request; + } + } +} diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/[Internal]/WechatTenpayClientSigningExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/[Internal]/WechatTenpayClientSigningExtensions.cs index 3f741c0f..0d381ca1 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/[Internal]/WechatTenpayClientSigningExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/[Internal]/WechatTenpayClientSigningExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -10,75 +11,54 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 internal static class WechatTenpayClientSigningExtensions { - public static ErroredResult VerifySignature(this WechatTenpayClient client, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignScheme, string strSerialNumber) + public static ErroredResult _VerifySignature(this WechatTenpayClient client, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignScheme, string strSerialNumber) { if (client is null) throw new ArgumentNullException(nameof(client)); - switch (client.PlatformAuthScheme) + PlatformAuthScheme authSchema; + if (!TryGetAuthScheme(client, strSerialNumber, out authSchema)) + { + return ErroredResult.Fail(new Exception("Could not detect the platform auth schema because the serial number is missing or invalid.")); + } + + switch (authSchema) { case PlatformAuthScheme.Certificate: { - CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(strSerialNumber); - if (!entry.HasValue) - { - return ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate matched the serial number \"{strSerialNumber}\".")); - } - - return GenerateSignatureResultByCertificate( - scheme: strSignScheme, - certificate: entry.Value.Certificate, - message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent), - signature: strSignature - ); + return VerifySignatureUseCertificateManager(client.PlatformCertificateManager, strTimestamp: strTimestamp, strNonce: strNonce, strContent: strContent, strSignature: strSignature, strSignScheme: strSignScheme, strSerialNumber: strSerialNumber); } case PlatformAuthScheme.PublicKey: { - PublicKeyEntry? entry = client.PlatformPublicKeyManager.GetEntry(strSerialNumber); - if (!entry.HasValue) - { - return ErroredResult.Fail(new Exception($"The platform public key manager does not contain a key matched the serial number \"{strSerialNumber}\".")); - } - - return GenerateSignatureResultByPublicKey( - scheme: strSignScheme, - publicKey: entry.Value.PublicKey, - message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent), - signature: strSignature - ); + return VerifySignatureUsePublicKeyManager(client.PlatformPublicKeyManager, strTimestamp: strTimestamp, strNonce: strNonce, strContent: strContent, strSignature: strSignature, strSignScheme: strSignScheme, strSerialNumber: strSerialNumber); } default: - return ErroredResult.Fail(new Exception($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\".")); + return ErroredResult.Fail(new NotSupportedException($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\".")); } } - public static async Task VerifySignatureAsync(this WechatTenpayClient client, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignScheme, string strSerialNumber, CancellationToken cancellationToken = default) + public static async Task _VerifySignatureAsync(this WechatTenpayClient client, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignScheme, string strSerialNumber, CancellationToken cancellationToken = default) { if (client is null) throw new ArgumentNullException(nameof(client)); - switch (client.PlatformAuthScheme) + PlatformAuthScheme authSchema; + if (!TryGetAuthScheme(client, strSerialNumber, out authSchema)) + { + return ErroredResult.Fail(new Exception("Could not detect the platform auth schema because the serial number is missing or invalid.")); + } + + switch (authSchema) { case PlatformAuthScheme.Certificate: { if (client.PlatformCertificateManager is not ICertificateManagerAsync) { // 降级为同步调用 - return VerifySignature(client, strTimestamp, strNonce, strContent, strSignature, strSignScheme, strSerialNumber); + return _VerifySignature(client, strTimestamp, strNonce, strContent, strSignature, strSignScheme, strSerialNumber); } - CertificateEntry? entry = await ((ICertificateManagerAsync)client.PlatformCertificateManager).GetEntryAsync(strSerialNumber, cancellationToken).ConfigureAwait(false); - if (!entry.HasValue) - { - return ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate matched the serial number \"{strSerialNumber}\".")); - } - - return GenerateSignatureResultByCertificate( - scheme: strSignScheme, - certificate: entry.Value.Certificate, - message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent), - signature: strSignature - ); + return await VerifySignatureUseCertificateManagerAsync((ICertificateManagerAsync)client.PlatformCertificateManager, strTimestamp: strTimestamp, strNonce: strNonce, strContent: strContent, strSignature: strSignature, strSignScheme: strSignScheme, strSerialNumber: strSerialNumber, cancellationToken).ConfigureAwait(false); } case PlatformAuthScheme.PublicKey: @@ -86,34 +66,130 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 if (client.PlatformCertificateManager is not IPublicKeyManagerAsync) { // 降级为同步调用 - return VerifySignature(client, strTimestamp, strNonce, strContent, strSignature, strSignScheme, strSerialNumber); + return _VerifySignature(client, strTimestamp, strNonce, strContent, strSignature, strSignScheme, strSerialNumber); } - PublicKeyEntry? entry = await ((IPublicKeyManagerAsync)client.PlatformPublicKeyManager).GetEntryAsync(strSerialNumber, cancellationToken).ConfigureAwait(false); - if (!entry.HasValue) - { - return ErroredResult.Fail(new Exception($"The platform public key manager does not contain a key matched the serial number \"{strSerialNumber}\".")); - } - - return GenerateSignatureResultByPublicKey( - scheme: strSignScheme, - publicKey: entry.Value.PublicKey, - message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent), - signature: strSignature - ); + return await VerifySignatureUseCertificateManagerAsync((IPublicKeyManagerAsync)client.PlatformPublicKeyManager, strTimestamp: strTimestamp, strNonce: strNonce, strContent: strContent, strSignature: strSignature, strSignScheme: strSignScheme, strSerialNumber: strSerialNumber, cancellationToken).ConfigureAwait(false); } default: - return ErroredResult.Fail(new Exception($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\".")); + return ErroredResult.Fail(new NotSupportedException($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\".")); } } + private static ErroredResult VerifySignatureUseCertificateManager(ICertificateManager manager, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignScheme, string strSerialNumber) + { + if (manager is null) + { + return ErroredResult.Fail(new NullReferenceException("The platform certificate manager is not configured.")); + } + + CertificateEntry? entry = manager.GetEntry(strSerialNumber); + if (!entry.HasValue) + { + return ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate matched the serial number \"{strSerialNumber}\".")); + } + + return GenerateVerifyResultByCertificate( + scheme: strSignScheme, + certificate: entry.Value.Certificate, + message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent), + signature: strSignature + ); + } + + private static async Task VerifySignatureUseCertificateManagerAsync(ICertificateManagerAsync manager, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignScheme, string strSerialNumber, CancellationToken cancellationToken = default) + { + if (manager is null) + { + return ErroredResult.Fail(new NullReferenceException("The platform certificate manager is not configured.")); + } + + CertificateEntry? entry = await manager.GetEntryAsync(strSerialNumber, cancellationToken).ConfigureAwait(false); + if (!entry.HasValue) + { + return ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate matched the serial number \"{strSerialNumber}\".")); + } + + return GenerateVerifyResultByCertificate( + scheme: strSignScheme, + certificate: entry.Value.Certificate, + message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent), + signature: strSignature + ); + } + + private static ErroredResult VerifySignatureUsePublicKeyManager(IPublicKeyManager manager, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignScheme, string strSerialNumber) + { + if (manager is null) + { + return ErroredResult.Fail(new NullReferenceException("The platform public key manager is not configured.")); + } + + PublicKeyEntry? entry = manager.GetEntry(strSerialNumber); + if (!entry.HasValue) + { + return ErroredResult.Fail(new Exception($"The platform public key manager does not contain a key matched the serial number \"{strSerialNumber}\".")); + } + + return GenerateVerifyResultByPublicKey( + scheme: strSignScheme, + publicKey: entry.Value.PublicKey, + message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent), + signature: strSignature + ); + } + + private static async Task VerifySignatureUseCertificateManagerAsync(IPublicKeyManagerAsync manager, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignScheme, string strSerialNumber, CancellationToken cancellationToken = default) + { + if (manager is null) + { + return ErroredResult.Fail(new NullReferenceException("The platform public key manager is not configured.")); + } + + PublicKeyEntry? entry = await manager.GetEntryAsync(strSerialNumber, cancellationToken).ConfigureAwait(false); + if (!entry.HasValue) + { + return ErroredResult.Fail(new Exception($"The platform public key manager does not contain a key matched the serial number \"{strSerialNumber}\".")); + } + + return GenerateVerifyResultByPublicKey( + scheme: strSignScheme, + publicKey: entry.Value.PublicKey, + message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent), + signature: strSignature + ); + } + + private static bool TryGetAuthScheme(WechatTenpayClient client, string strSerialNumber, out PlatformAuthScheme authScheme) + { + authScheme = client.PlatformAuthScheme; + if (client.PlatformAuthFallbackSwitch) + { + if (string.IsNullOrEmpty(strSerialNumber)) + { + return false; + } + + if (Regex.IsMatch(strSerialNumber, "^PUB_KEY_ID_", RegexOptions.IgnoreCase)) + { + authScheme = PlatformAuthScheme.PublicKey; + } + else if (Regex.IsMatch(strSerialNumber, "^[A-Za-z0-9]+$", RegexOptions.IgnoreCase)) + { + authScheme = PlatformAuthScheme.Certificate; + } + } + + return true; + } + private static string GenerateMessageForSignature(string timestamp, string nonce, string body) { return $"{timestamp}\n{nonce}\n{body}\n"; } - private static ErroredResult GenerateSignatureResultByCertificate(string scheme, string certificate, string message, string signature) + private static ErroredResult GenerateVerifyResultByCertificate(string scheme, string certificate, string message, string signature) { string publicKey = string.Empty; @@ -146,10 +222,10 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 break; } - return GenerateSignatureResultByPublicKey(scheme, publicKey, message, signature); + return GenerateVerifyResultByPublicKey(scheme, publicKey, message, signature); } - private static ErroredResult GenerateSignatureResultByPublicKey(string scheme, string publicKey, string message, string signature) + private static ErroredResult GenerateVerifyResultByPublicKey(string scheme, string publicKey, string message, string signature) { ErroredResult result; @@ -199,7 +275,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 default: { - result = ErroredResult.Fail(new Exception($"Unsupported signing scheme: \"{scheme}\".")); + result = ErroredResult.Fail(new NotSupportedException($"Unsupported signing scheme: \"{scheme}\".")); } break; } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/[Internal]/ReflectionHelper.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/[Internal]/ReflectionHelper.cs index ad62769b..9d7ecd15 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/[Internal]/ReflectionHelper.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/[Internal]/ReflectionHelper.cs @@ -10,7 +10,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities { internal static partial class ReflectionHelper { - public static void ReplaceObjectStringProperties(object targetObj, ReplaceObjectStringPropertiesReplacementDelegate replacement) + public static void ReplaceObjectStringProperties(ref T targetObj, ReplaceObjectStringPropertiesReplacementDelegate replacement) { if (targetObj is null) throw new ArgumentNullException(nameof(targetObj)); if (replacement is null) throw new ArgumentNullException(nameof(replacement)); @@ -24,7 +24,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities { public delegate (bool IsModified, string NewValue) ReplaceObjectStringPropertiesReplacementDelegate(object currentObj, PropertyInfo? currentProp, string oldValue); - private static void InnerReplaceObjectStringProperties(ref object currentObj, PropertyInfo? currentProp, ReplaceObjectStringPropertiesReplacementDelegate replacement, ISet visited) + private static void InnerReplaceObjectStringProperties(ref T currentObj, PropertyInfo? currentProp, ReplaceObjectStringPropertiesReplacementDelegate replacement, ISet visited) { if (currentObj is null) throw new ArgumentNullException(nameof(currentObj)); if (replacement is null) throw new ArgumentNullException(nameof(replacement)); @@ -53,7 +53,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities // 处理数组类型 if (type.IsArray) { - Array currentObjAsArray = (Array)currentObj; + Array currentObjAsArray = (currentObj as Array)!; for (int i = 0; i < currentObjAsArray.Length; i++) { object? element = currentObjAsArray.GetValue(i); diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClient.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClient.cs index 012c5300..2cce3b44 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClient.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClient.cs @@ -20,7 +20,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 public Settings.Credentials Credentials { get; } /// - /// 获取当前客户端使用的微信支付平台认证方案。 + /// 获取当前客户端是否开启微信支付平台 API 认证方案回退开关。 + /// + public bool PlatformAuthFallbackSwitch { get; } + + /// + /// 获取当前客户端使用的微信支付平台 API 认证方案。 /// public Settings.PlatformAuthScheme PlatformAuthScheme { get; } @@ -65,6 +70,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 if (options is null) throw new ArgumentNullException(nameof(options)); Credentials = new Settings.Credentials(options); + PlatformAuthFallbackSwitch = options.PlatformAuthFallbackSwitch; PlatformAuthScheme = options.PlatformAuthScheme; PlatformCertificateManager = options.PlatformCertificateManager; PlatformPublicKeyManager = options.PlatformPublicKeyManager; @@ -96,6 +102,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 { IFlurlRequest flurlRequest = base.CreateFlurlRequest(request, httpMethod, urlSegments); + if (PlatformAuthFallbackSwitch) + { + this._EnsureRequestWechatpaySerialNumberIsSet(request); + } + if (AutoEncryptRequestSensitiveProperty) { this.EncryptRequestSensitiveProperty(request); diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClientOptions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClientOptions.cs index 4ea93076..149a1bf6 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClientOptions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClientOptions.cs @@ -74,7 +74,17 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 public bool AutoDecryptResponseSensitiveProperty { get; set; } /// - /// 获取或设置微信支付平台认证方案。 + /// 获取或设置微信支付平台 API 认证方案回退开关。
+ /// 开启后会在验证签名时根据响应或回调中接收到的序列号自动推测使用哪种认证方案。
+ /// 开启后会有一定的性能损耗,建议仅在灰度切换期间时开启,待切换进度为百分之百后请关闭。 + /// + /// 默认值:false + /// + ///
+ public bool PlatformAuthFallbackSwitch { get; set; } + + /// + /// 获取或设置微信支付平台 API 认证方案。 /// 默认值: /// public Settings.PlatformAuthScheme PlatformAuthScheme { get; set; } = Settings.PlatformAuthScheme.Certificate; @@ -82,7 +92,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 /// /// 获取或设置微信支付平台证书管理器。 /// - /// 仅当 的值为 时有效。 + /// 仅当 的值为 ,或开启 开关时有效。 /// /// /// 默认值: @@ -93,7 +103,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 /// /// 获取或设置微信支付平台公钥管理器。 /// - /// 仅当 的值为 时有效。 + /// 仅当 的值为 ,或开启 开关时有效。 /// /// /// 默认值: diff --git a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_RequestEncryptionTests.cs b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_RequestEncryptionTests.cs index 79fd7069..4901cff1 100644 --- a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_RequestEncryptionTests.cs +++ b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/TestCase_RequestEncryptionTests.cs @@ -2561,7 +2561,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests var manager = new Settings.InMemoryCertificateManager(); manager.AddEntry(new Settings.CertificateEntry( algorithmType: Settings.CertificateEntry.ALGORITHM_TYPE_RSA, - serialNumber: "OBSOLETED1", + serialNumber: "MOCK1", certificate: "-----BEGIN CERTIFICATE----------END CERTIFICATE-----", effectiveTime: DateTimeOffset.Now.AddSeconds(-1), expireTime: DateTimeOffset.Now.AddSeconds(-1) @@ -2575,7 +2575,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests )); manager.AddEntry(new Settings.CertificateEntry( algorithmType: Settings.CertificateEntry.ALGORITHM_TYPE_RSA, - serialNumber: "OBSOLETED2", + serialNumber: "MOCK2", certificate: "-----BEGIN CERTIFICATE----------END CERTIFICATE-----", effectiveTime: DateTimeOffset.Now.AddSeconds(-1), expireTime: DateTimeOffset.Now.AddSeconds(-1) @@ -2598,7 +2598,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests var manager = new Settings.InMemoryCertificateManager(); manager.AddEntry(new Settings.CertificateEntry( algorithmType: Settings.CertificateEntry.ALGORITHM_TYPE_SM2, - serialNumber: "OBSOLETED1", + serialNumber: "MOCK1", certificate: "-----BEGIN CERTIFICATE----------END CERTIFICATE-----", effectiveTime: DateTimeOffset.Now.AddSeconds(-1), expireTime: DateTimeOffset.Now.AddSeconds(-1) @@ -2612,7 +2612,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests )); manager.AddEntry(new Settings.CertificateEntry( algorithmType: Settings.CertificateEntry.ALGORITHM_TYPE_SM2, - serialNumber: "OBSOLETED2", + serialNumber: "MOCK2", certificate: "-----BEGIN CERTIFICATE----------END CERTIFICATE-----", effectiveTime: DateTimeOffset.Now.AddSeconds(-1), expireTime: DateTimeOffset.Now.AddSeconds(-1)