mirror of
https://gitee.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat.git
synced 2026-02-11 18:26:20 +08:00
feat(work): 异步的消息加解密密钥管理器feat(tenpayv3): 异步的平台证书管理器
This commit is contained in:
@@ -3,6 +3,7 @@ using System;
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Primitives;
|
||||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants;
|
||||
|
||||
public static class WechatTenpayClientEventDecryptionExtensions
|
||||
{
|
||||
@@ -52,7 +53,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
string plainJson;
|
||||
switch (webhookEventResource.Algorithm)
|
||||
{
|
||||
case Constants.EncryptionAlgorithms.AEAD_AES_256_GCM:
|
||||
case EncryptionAlgorithms.AEAD_AES_256_GCM:
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -70,7 +71,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
}
|
||||
break;
|
||||
|
||||
case Constants.EncryptionAlgorithms.AEAD_SM4_128_GCM:
|
||||
case EncryptionAlgorithms.AEAD_SM4_128_GCM:
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Primitives;
|
||||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants;
|
||||
|
||||
public static class WechatTenpayClientEventVerificationExtensions
|
||||
{
|
||||
@@ -29,7 +32,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
webhookNonce: webhookNonce,
|
||||
webhookBody: webhookBody,
|
||||
webhookSignature: webhookSignature,
|
||||
webhookSignatureType: Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256,
|
||||
webhookSignatureType: SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256,
|
||||
webhookSerialNumber: webhookSerialNumber
|
||||
);
|
||||
}
|
||||
@@ -64,5 +67,66 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
strSerialNumber: webhookSerialNumber
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>异步验证回调通知事件签名。</para>
|
||||
/// <para>
|
||||
/// REF: <br/>
|
||||
/// <![CDATA[ https://pay.weixin.qq.com/docs/merchant/development/interface-rules/signature-verification.html ]]> <br/>
|
||||
/// <![CDATA[ https://pay.weixin.qq.com/docs/partner/development/interface-rules/signature-verification.html ]]>
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="webhookTimestamp">微信回调通知中的 "Wechatpay-Timestamp" 请求标头。</param>
|
||||
/// <param name="webhookNonce">微信回调通知中的 "Wechatpay-Nonce" 请求标头。</param>
|
||||
/// <param name="webhookBody">微信回调通知中请求正文。</param>
|
||||
/// <param name="webhookSignature">微信回调通知中的 "Wechatpay-Signature" 请求标头。</param>
|
||||
/// <param name="webhookSerialNumber">微信回调通知中的 "Wechatpay-Serial" 请求标头。</param>
|
||||
/// <returns></returns>
|
||||
public static Task<ErroredResult> VerifyEventSignatureAsync(this WechatTenpayClient client, string webhookTimestamp, string webhookNonce, string webhookBody, string webhookSignature, string webhookSerialNumber, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return VerifyEventSignatureAsync(
|
||||
client,
|
||||
webhookTimestamp: webhookTimestamp,
|
||||
webhookNonce: webhookNonce,
|
||||
webhookBody: webhookBody,
|
||||
webhookSignature: webhookSignature,
|
||||
webhookSignatureType: SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256,
|
||||
webhookSerialNumber: webhookSerialNumber,
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>异步验证回调通知事件签名。</para>
|
||||
/// <para>
|
||||
/// REF: <br/>
|
||||
/// <![CDATA[ https://pay.weixin.qq.com/docs/merchant/development/interface-rules/signature-verification.html ]]> <br/>
|
||||
/// <![CDATA[ https://pay.weixin.qq.com/docs/partner/development/interface-rules/signature-verification.html ]]>
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="webhookTimestamp">微信回调通知中的 "Wechatpay-Timestamp" 请求标头。</param>
|
||||
/// <param name="webhookNonce">微信回调通知中的 "Wechatpay-Nonce" 请求标头。</param>
|
||||
/// <param name="webhookBody">微信回调通知中请求正文。</param>
|
||||
/// <param name="webhookSignature">微信回调通知中的 "Wechatpay-Signature" 请求标头。</param>
|
||||
/// <param name="webhookSignatureType">微信回调通知中的 "Wechatpay-Signature-Type" 请求标头。</param>
|
||||
/// <param name="webhookSerialNumber">微信回调通知中的 "Wechatpay-Serial" 请求标头。</param>
|
||||
/// <returns></returns>
|
||||
public static Task<ErroredResult> VerifyEventSignatureAsync(this WechatTenpayClient client, string webhookTimestamp, string webhookNonce, string webhookBody, string webhookSignature, string webhookSignatureType, string webhookSerialNumber, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||
|
||||
return WechatTenpayClientSigningExtensions.VerifySignatureAsync(
|
||||
client,
|
||||
strTimestamp: webhookTimestamp,
|
||||
strNonce: webhookNonce,
|
||||
strContent: webhookBody,
|
||||
strSignature: webhookSignature,
|
||||
strSignScheme: webhookSignatureType,
|
||||
strSerialNumber: webhookSerialNumber,
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ using System.Collections.ObjectModel;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants;
|
||||
|
||||
public static class WechatTenpayClientParameterExtensions
|
||||
{
|
||||
/// <summary>
|
||||
@@ -47,7 +49,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
{ "timeStamp", timestamp },
|
||||
{ "nonceStr", nonce },
|
||||
{ "package", package },
|
||||
{ "signType", Constants.SignTypes.RSA },
|
||||
{ "signType", SignTypes.RSA },
|
||||
{ "paySign", sign }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants;
|
||||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
|
||||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities;
|
||||
|
||||
@@ -30,8 +33,94 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
|
||||
string signScheme = client.Credentials.SignScheme;
|
||||
string algorithmType = // 签名方式与加密算法保持一致:RSA_SHA256 签名需 RSA 加密,SM3 签名需 SM2 加密
|
||||
Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_RSA :
|
||||
Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_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}\".");
|
||||
|
||||
ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) =>
|
||||
{
|
||||
if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute)))
|
||||
return (false, oldValue);
|
||||
|
||||
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
|
||||
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
|
||||
.FirstOrDefault(attr => attr.Scheme == signScheme);
|
||||
if (attribute is null)
|
||||
return (false, oldValue);
|
||||
|
||||
string certificate;
|
||||
if (string.IsNullOrEmpty(request.WechatpayCertificateSerialNumber))
|
||||
{
|
||||
// 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的
|
||||
IEnumerable<CertificateEntry> 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.");
|
||||
}
|
||||
|
||||
CertificateEntry entry = entries.First();
|
||||
certificate = entry.Certificate;
|
||||
request.WechatpayCertificateSerialNumber = entry.SerialNumber;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值
|
||||
CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(request.WechatpayCertificateSerialNumber!);
|
||||
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.WechatpayCertificateSerialNumber}\". Please make sure you have downloaded platform (NOT merchant) certificates first.");
|
||||
}
|
||||
|
||||
certificate = entry.Value.Certificate;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>异步加密请求中传入的敏感数据。该方法会改变传入的请求模型对象。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<TRequest> EncryptRequestSensitivePropertyAsync<TRequest>(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));
|
||||
|
||||
if (client.PlatformCertificateManager is not ICertificateManagerAsync)
|
||||
{
|
||||
// 降级为同步调用
|
||||
return Task.FromResult(request);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
bool requireEncrypt = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute));
|
||||
if (!requireEncrypt)
|
||||
return Task.FromResult(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}\".");
|
||||
|
||||
ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) =>
|
||||
@@ -52,7 +141,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is not initialized.");
|
||||
|
||||
// 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的
|
||||
IEnumerable<CertificateEntry> entries = client.PlatformCertificateManager.AllEntries()
|
||||
IEnumerable<CertificateEntry> entries = ((ICertificateManagerAsync)client.PlatformCertificateManager)
|
||||
.AllEntriesAsync(cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
.GetAwaiter()
|
||||
.GetResult()
|
||||
.Where(e => e.AlgorithmType == algorithmType)
|
||||
.OrderByDescending(e => e.ExpireTime);
|
||||
if (!entries.Any())
|
||||
@@ -67,7 +160,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
else
|
||||
{
|
||||
// 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值
|
||||
CertificateEntry? entry = client.PlatformCertificateManager?.GetEntry(request.WechatpayCertificateSerialNumber!);
|
||||
CertificateEntry? entry = ((ICertificateManagerAsync)client.PlatformCertificateManager)
|
||||
.GetEntryAsync(request.WechatpayCertificateSerialNumber!, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
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.WechatpayCertificateSerialNumber}\". Please make sure you have downloaded platform (NOT merchant) certificates first.");
|
||||
@@ -76,49 +173,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
certificate = entry.Value.Certificate;
|
||||
}
|
||||
|
||||
string newValue;
|
||||
switch (attribute.Algorithm)
|
||||
{
|
||||
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
|
||||
{
|
||||
newValue = RSAUtility.EncryptWithECBByCertificate(
|
||||
certificatePem: certificate,
|
||||
plainData: oldValue,
|
||||
paddingMode: RSAUtility.PADDING_MODE_OAEPWITHSHA1ANDMGF1
|
||||
)!;
|
||||
}
|
||||
break;
|
||||
|
||||
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
|
||||
{
|
||||
newValue = RSAUtility.EncryptWithECBByCertificate(
|
||||
certificatePem: certificate,
|
||||
plainData: oldValue,
|
||||
paddingMode: RSAUtility.PADDING_MODE_PKCS1
|
||||
)!;
|
||||
}
|
||||
break;
|
||||
|
||||
case Constants.EncryptionAlgorithms.SM2_C1C3C2_ASN1:
|
||||
{
|
||||
newValue = SM2Utility.EncryptByCertificate(
|
||||
certificatePem: certificate,
|
||||
plainData: oldValue,
|
||||
asn1Encoding: true
|
||||
)!;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
throw new WechatTenpayException($"Failed to encrypt request. Unsupported encryption algorithm: \"{attribute.Algorithm}\".");
|
||||
}
|
||||
}
|
||||
|
||||
string newValue = GenerateEncryptedValueByCertificate(attribute.Algorithm, certificate, oldValue);
|
||||
return (true, newValue);
|
||||
});
|
||||
|
||||
return request;
|
||||
return Task.FromResult(request);
|
||||
}
|
||||
catch (WechatTenpayException)
|
||||
{
|
||||
@@ -129,5 +188,35 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
throw new WechatTenpayException("Failed to encrypt request. Please see the inner exception for more details.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GenerateEncryptedValueByCertificate(string algorithm, string certificate, string value)
|
||||
{
|
||||
switch (algorithm)
|
||||
{
|
||||
case EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
|
||||
return RSAUtility.EncryptWithECBByCertificate(
|
||||
certificatePem: certificate,
|
||||
plainData: value,
|
||||
paddingMode: RSAUtility.PADDING_MODE_OAEPWITHSHA1ANDMGF1
|
||||
)!;
|
||||
|
||||
case EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
|
||||
return RSAUtility.EncryptWithECBByCertificate(
|
||||
certificatePem: certificate,
|
||||
plainData: value,
|
||||
paddingMode: RSAUtility.PADDING_MODE_PKCS1
|
||||
)!;
|
||||
|
||||
case EncryptionAlgorithms.SM2_C1C3C2_ASN1:
|
||||
return SM2Utility.EncryptByCertificate(
|
||||
certificatePem: certificate,
|
||||
plainData: value,
|
||||
asn1Encoding: true
|
||||
)!;
|
||||
|
||||
default:
|
||||
throw new WechatTenpayException($"Failed to encrypt request. Unsupported encryption algorithm: \"{algorithm}\".");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Reflection;
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Primitives;
|
||||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants;
|
||||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Models;
|
||||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities;
|
||||
|
||||
@@ -31,7 +32,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
|
||||
switch (certificate.EncryptCertificate.Algorithm)
|
||||
{
|
||||
case Constants.EncryptionAlgorithms.AEAD_AES_256_GCM:
|
||||
case EncryptionAlgorithms.AEAD_AES_256_GCM:
|
||||
{
|
||||
if (string.IsNullOrEmpty(client.Credentials.MerchantCertificatePrivateKey))
|
||||
throw new WechatTenpayException("Failed to decrypt response, because the merchant private key is not set.");
|
||||
@@ -45,7 +46,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
}
|
||||
break;
|
||||
|
||||
case Constants.EncryptionAlgorithms.AEAD_SM4_128_GCM:
|
||||
case EncryptionAlgorithms.AEAD_SM4_128_GCM:
|
||||
{
|
||||
if (string.IsNullOrEmpty(client.Credentials.MerchantV3Secret))
|
||||
throw new WechatTenpayException("Failed to decrypt response, because the merchant APIv3 secret is not set.");
|
||||
@@ -120,7 +121,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
string newValue;
|
||||
switch (attribute.Algorithm)
|
||||
{
|
||||
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
|
||||
case EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
|
||||
{
|
||||
newValue = RSAUtility.DecryptWithECB(
|
||||
privateKeyPem: client.Credentials.MerchantCertificatePrivateKey,
|
||||
@@ -130,7 +131,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
}
|
||||
break;
|
||||
|
||||
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
|
||||
case EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
|
||||
{
|
||||
newValue = RSAUtility.DecryptWithECB(
|
||||
privateKeyPem: client.Credentials.MerchantCertificatePrivateKey,
|
||||
@@ -140,7 +141,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
}
|
||||
break;
|
||||
|
||||
case Constants.EncryptionAlgorithms.SM2_C1C3C2_ASN1:
|
||||
case EncryptionAlgorithms.SM2_C1C3C2_ASN1:
|
||||
{
|
||||
newValue = SM2Utility.Decrypt(
|
||||
privateKeyPem: client.Credentials.MerchantCertificatePrivateKey,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Primitives;
|
||||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants;
|
||||
|
||||
public static class WechatTenpayClientResponseVerificationExtensions
|
||||
{
|
||||
@@ -59,7 +62,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
responseNonce: responseNonce,
|
||||
responseBody: responseBody,
|
||||
responseSignature: responseSignature,
|
||||
responseSignatureType: Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256,
|
||||
responseSignatureType: SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256,
|
||||
responseSerialNumber
|
||||
);
|
||||
}
|
||||
@@ -94,5 +97,96 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
strSerialNumber: responseSerialNumber
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>异步验证响应签名。</para>
|
||||
/// <para>
|
||||
/// REF: <br/>
|
||||
/// <![CDATA[ https://pay.weixin.qq.com/docs/merchant/development/interface-rules/signature-verification.html ]]> <br/>
|
||||
/// <![CDATA[ https://pay.weixin.qq.com/docs/partner/development/interface-rules/signature-verification.html ]]>
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse"></typeparam>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="response"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<ErroredResult> VerifyResponseSignatureAsync<TResponse>(this WechatTenpayClient client, TResponse response, CancellationToken cancellationToken = default)
|
||||
where TResponse : WechatTenpayResponse
|
||||
{
|
||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||
if (response is null) throw new ArgumentNullException(nameof(response));
|
||||
|
||||
return VerifyResponseSignatureAsync(
|
||||
client,
|
||||
responseTimestamp: response.WechatpayTimestamp,
|
||||
responseNonce: response.WechatpayNonce,
|
||||
responseBody: Encoding.UTF8.GetString(response.GetRawBytes()),
|
||||
responseSignature: response.WechatpaySignature,
|
||||
responseSignatureType: response.WechatpaySignatureType,
|
||||
responseSerialNumber: response.WechatpayCertificateSerialNumber,
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>异步验证响应签名。</para>
|
||||
/// <para>
|
||||
/// REF: <br/>
|
||||
/// <![CDATA[ https://pay.weixin.qq.com/docs/merchant/development/interface-rules/signature-verification.html ]]> <br/>
|
||||
/// <![CDATA[ https://pay.weixin.qq.com/docs/partner/development/interface-rules/signature-verification.html ]]>
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="responseTimestamp"></param>
|
||||
/// <param name="responseNonce">。</param>
|
||||
/// <param name="responseBody"></param>
|
||||
/// <param name="responseSignature"></param>
|
||||
/// <param name="responseSerialNumber"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<ErroredResult> VerifyResponseSignatureAsync(this WechatTenpayClient client, string responseTimestamp, string responseNonce, string responseBody, string responseSignature, string responseSerialNumber, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return VerifyResponseSignatureAsync(
|
||||
client,
|
||||
responseTimestamp: responseTimestamp,
|
||||
responseNonce: responseNonce,
|
||||
responseBody: responseBody,
|
||||
responseSignature: responseSignature,
|
||||
responseSignatureType: SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256,
|
||||
responseSerialNumber: responseSerialNumber,
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>异步验证响应签名。</para>
|
||||
/// <para>
|
||||
/// REF: <br/>
|
||||
/// <![CDATA[ https://pay.weixin.qq.com/docs/merchant/development/interface-rules/signature-verification.html ]]> <br/>
|
||||
/// <![CDATA[ https://pay.weixin.qq.com/docs/partner/development/interface-rules/signature-verification.html ]]>
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="responseTimestamp"></param>
|
||||
/// <param name="responseNonce">。</param>
|
||||
/// <param name="responseBody"></param>
|
||||
/// <param name="responseSignature"></param>
|
||||
/// <param name="responseSignatureType"></param>
|
||||
/// <param name="responseSerialNumber"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<ErroredResult> VerifyResponseSignatureAsync(this WechatTenpayClient client, string responseTimestamp, string responseNonce, string responseBody, string responseSignature, string responseSignatureType, string responseSerialNumber, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||
|
||||
return WechatTenpayClientSigningExtensions.VerifySignatureAsync(
|
||||
client,
|
||||
strTimestamp: responseTimestamp,
|
||||
strNonce: responseNonce,
|
||||
strContent: responseBody,
|
||||
strSignature: responseSignature,
|
||||
strSignScheme: responseSignatureType,
|
||||
strSerialNumber: responseSerialNumber,
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
{
|
||||
using SKIT.FlurlHttpClient.Primitives;
|
||||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants;
|
||||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
|
||||
|
||||
internal static class WechatTenpayClientSigningExtensions
|
||||
@@ -11,36 +14,68 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
{
|
||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||
|
||||
CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(strSerialNumber);
|
||||
if (!entry.HasValue)
|
||||
{
|
||||
return ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate with serial number \"{strSerialNumber}\"."));
|
||||
}
|
||||
|
||||
return GenerateSignatureResultByCertificate(
|
||||
scheme: strSignScheme,
|
||||
certificate: entry.Value.Certificate,
|
||||
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
|
||||
signature: strSignature
|
||||
);
|
||||
}
|
||||
|
||||
public static async Task<ErroredResult> 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));
|
||||
|
||||
if (client.PlatformCertificateManager is not ICertificateManagerAsync)
|
||||
{
|
||||
// 降级为同步调用
|
||||
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 with serial number \"{strSerialNumber}\"."));
|
||||
}
|
||||
|
||||
return GenerateSignatureResultByCertificate(
|
||||
scheme: strSignScheme,
|
||||
certificate: entry.Value.Certificate,
|
||||
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
|
||||
signature: strSignature
|
||||
);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
ErroredResult result;
|
||||
|
||||
switch (strSignScheme)
|
||||
switch (scheme)
|
||||
{
|
||||
case Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256:
|
||||
case SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256:
|
||||
{
|
||||
try
|
||||
{
|
||||
if (client.PlatformCertificateManager is null)
|
||||
{
|
||||
result = ErroredResult.Fail(new Exception("The platform certificate manager is not initialized."));
|
||||
break;
|
||||
}
|
||||
|
||||
CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(strSerialNumber);
|
||||
if (!entry.HasValue)
|
||||
{
|
||||
result = ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate with serial number \"{strSerialNumber}\"."));
|
||||
break;
|
||||
}
|
||||
|
||||
bool valid = Utilities.RSAUtility.VerifyByCertificate(
|
||||
certificatePem: entry.Value.Certificate,
|
||||
messageData: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
|
||||
encodingSignature: new EncodedString(strSignature, EncodingKinds.Base64)
|
||||
certificatePem: certificate,
|
||||
messageData: message,
|
||||
encodingSignature: new EncodedString(signature, EncodingKinds.Base64)
|
||||
);
|
||||
if (valid)
|
||||
result = ErroredResult.Ok();
|
||||
else
|
||||
result = ErroredResult.Fail(new Exception($"Signature does not match. Maybe \"{strSignature}\" is an illegal signature."));
|
||||
result = ErroredResult.Fail(new Exception($"Signature does not match. Maybe \"{signature}\" is an illegal signature."));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -49,32 +84,19 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
}
|
||||
break;
|
||||
|
||||
case Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3:
|
||||
case SignSchemes.WECHATPAY2_SM2_WITH_SM3:
|
||||
{
|
||||
try
|
||||
{
|
||||
if (client.PlatformCertificateManager is null)
|
||||
{
|
||||
result = ErroredResult.Fail(new Exception("The platform certificate manager is not initialized."));
|
||||
break;
|
||||
}
|
||||
|
||||
CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(strSerialNumber);
|
||||
if (!entry.HasValue)
|
||||
{
|
||||
result = ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate with serial number \"{strSerialNumber}\"."));
|
||||
break;
|
||||
}
|
||||
|
||||
bool valid = Utilities.SM2Utility.VerifyWithSM3ByCertificate(
|
||||
certificatePem: entry.Value.Certificate,
|
||||
messageData: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
|
||||
encodingSignature: new EncodedString(strSignature, EncodingKinds.Base64)
|
||||
certificatePem: certificate,
|
||||
messageData: message,
|
||||
encodingSignature: new EncodedString(signature, EncodingKinds.Base64)
|
||||
);
|
||||
if (valid)
|
||||
result = ErroredResult.Ok();
|
||||
else
|
||||
result = ErroredResult.Fail(new Exception($"Signature does not match. Maybe \"{strSignature}\" is an illegal signature."));
|
||||
result = ErroredResult.Fail(new Exception($"Signature does not match. Maybe \"{signature}\" is an illegal signature."));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -85,17 +107,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
|
||||
default:
|
||||
{
|
||||
result = ErroredResult.Fail(new Exception($"Unsupported signing scheme: \"{strSignScheme}\"."));
|
||||
result = ErroredResult.Fail(new Exception($"Unsupported signing scheme: \"{scheme}\"."));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string GenerateMessageForSignature(string timestamp, string nonce, string body)
|
||||
{
|
||||
return $"{timestamp}\n{nonce}\n{body}\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user