mirror of
https://gitee.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat.git
synced 2025-12-29 09:54:44 +08:00
feat(tenpayv3): 兼容平台证书与公钥灰度切换
This commit is contained in:
@@ -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
|
||||
/// <returns></returns>
|
||||
public IFlurlRequest CreateFlurlRequest(WechatTenpayGlobalRequest request, HttpMethod httpMethod, params object[] urlSegments)
|
||||
{
|
||||
return base.CreateFlurlRequest(request, httpMethod, urlSegments);
|
||||
return ProxyClient.CreateFlurlRequest(request, httpMethod, urlSegments);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<TRequest>(client, request);
|
||||
switch (client.PlatformAuthScheme)
|
||||
{
|
||||
case PlatformAuthScheme.Certificate:
|
||||
{
|
||||
WechatTenpayClientRequestSerialNumberExtensions._EnsureRequestWechatpaySerialNumberIsSet(client, request);
|
||||
return EncryptRequestSensitivePropertyUseCertificateManager<TRequest>(client.PlatformCertificateManager, client.Credentials.SignScheme, request);
|
||||
}
|
||||
|
||||
case PlatformAuthScheme.PublicKey:
|
||||
return EncryptRequestSensitivePropertyByPublicKey<TRequest>(client, request);
|
||||
case PlatformAuthScheme.PublicKey:
|
||||
{
|
||||
WechatTenpayClientRequestSerialNumberExtensions._EnsureRequestWechatpaySerialNumberIsSet(client, request);
|
||||
return EncryptRequestSensitivePropertyUsePublicKeyManager<TRequest>(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
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<TRequest> EncryptRequestSensitivePropertyAsync<TRequest>(this WechatTenpayClient client, TRequest request, CancellationToken cancellationToken = default)
|
||||
public static async 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));
|
||||
|
||||
switch (client.PlatformAuthScheme)
|
||||
{
|
||||
case PlatformAuthScheme.Certificate:
|
||||
return EncryptRequestSensitivePropertyByCertificateAsync<TRequest>(client, request);
|
||||
bool required = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute));
|
||||
if (!required)
|
||||
return request;
|
||||
|
||||
case PlatformAuthScheme.PublicKey:
|
||||
return EncryptRequestSensitivePropertyByPublicKeyAsync<TRequest>(client, request);
|
||||
|
||||
default:
|
||||
throw new WechatTenpayException($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\".");
|
||||
}
|
||||
}
|
||||
|
||||
private static TRequest EncryptRequestSensitivePropertyByCertificate<TRequest>(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<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.");
|
||||
}
|
||||
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<TRequest>((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<TRequest>((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<WechatTenpaySensitivePropertyAttribute>()
|
||||
.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<TRequest> EncryptRequestSensitivePropertyByCertificateAsync<TRequest>(this WechatTenpayClient client, TRequest request, CancellationToken cancellationToken = default)
|
||||
private static TRequest EncryptRequestSensitivePropertyUseCertificateManager<TRequest>(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<CertificateEntry> 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<WechatTenpaySensitivePropertyAttribute>()
|
||||
.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<TRequest>(this WechatTenpayClient client, TRequest request)
|
||||
private static async Task<TRequest> EncryptRequestSensitivePropertyUseCertificateManagerAsync<TRequest>(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<PublicKeyEntry> 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<WechatTenpaySensitivePropertyAttribute>()
|
||||
.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<TRequest> EncryptRequestSensitivePropertyByPublicKeyAsync<TRequest>(this WechatTenpayClient client, TRequest request, CancellationToken cancellationToken = default)
|
||||
private static TRequest EncryptRequestSensitivePropertyUsePublicKeyManager<TRequest>(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<PublicKeyEntry> 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<WechatTenpaySensitivePropertyAttribute>()
|
||||
.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<TRequest> EncryptRequestSensitivePropertyUsePublicKeyManagerAsync<TRequest>(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<TRequest>(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<TRequest>(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<WechatTenpaySensitivePropertyAttribute>()
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<TRequest>(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<TRequest>(client.PlatformCertificateManager, client.Credentials.SignScheme, request);
|
||||
|
||||
case PlatformAuthScheme.PublicKey:
|
||||
return EnsureRequestWechatpaySerialNumberIsSetUsePublicKeyManager<TRequest>(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<TRequest> _EnsureRequestWechatpaySerialNumberIsSetAsync<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));
|
||||
|
||||
try
|
||||
{
|
||||
switch (client.PlatformAuthScheme)
|
||||
{
|
||||
case PlatformAuthScheme.Certificate:
|
||||
{
|
||||
if (client.PlatformCertificateManager is not ICertificateManagerAsync)
|
||||
{
|
||||
// 降级为同步调用
|
||||
return EnsureRequestWechatpaySerialNumberIsSetUseCertificateManager(client.PlatformCertificateManager, client.Credentials.SignScheme, request);
|
||||
}
|
||||
|
||||
return await EnsureRequestWechatpaySerialNumberIsSetUseCertificateManagerAsync<TRequest>((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<TRequest>((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<TRequest>(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<CertificateEntry> 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<TRequest> EnsureRequestWechatpaySerialNumberIsSetUseCertificateManagerAsync<TRequest>(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<CertificateEntry> 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<TRequest>(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<PublicKeyEntry> 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<TRequest> EnsureRequestWechatpaySerialNumberIsSetUsePublicKeyManagerAsync<TRequest>(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<PublicKeyEntry> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<ErroredResult> VerifySignatureAsync(this WechatTenpayClient client, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignScheme, string strSerialNumber, CancellationToken cancellationToken = default)
|
||||
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));
|
||||
|
||||
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<ErroredResult> 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<ErroredResult> 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;
|
||||
}
|
||||
|
||||
@@ -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<T>(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<object> visited)
|
||||
private static void InnerReplaceObjectStringProperties<T>(ref T currentObj, PropertyInfo? currentProp, ReplaceObjectStringPropertiesReplacementDelegate replacement, ISet<object> 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);
|
||||
|
||||
@@ -20,7 +20,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
public Settings.Credentials Credentials { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前客户端使用的微信支付平台认证方案。
|
||||
/// 获取当前客户端是否开启微信支付平台 API 认证方案回退开关。
|
||||
/// </summary>
|
||||
public bool PlatformAuthFallbackSwitch { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前客户端使用的微信支付平台 API 认证方案。
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
@@ -74,7 +74,17 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
public bool AutoDecryptResponseSensitiveProperty { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置微信支付平台认证方案。
|
||||
/// 获取或设置微信支付平台 API 认证方案回退开关。<br/>
|
||||
/// 开启后会在验证签名时根据响应或回调中接收到的序列号自动推测使用哪种认证方案。<br/>
|
||||
/// 开启后会有一定的性能损耗,建议仅在灰度切换期间时开启,待切换进度为百分之百后请关闭。
|
||||
/// <para>
|
||||
/// 默认值:false
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public bool PlatformAuthFallbackSwitch { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置微信支付平台 API 认证方案。
|
||||
/// <para>默认值:<see cref="Settings.PlatformAuthScheme.Certificate"/></para>
|
||||
/// </summary>
|
||||
public Settings.PlatformAuthScheme PlatformAuthScheme { get; set; } = Settings.PlatformAuthScheme.Certificate;
|
||||
@@ -82,7 +92,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
/// <summary>
|
||||
/// 获取或设置微信支付平台证书管理器。
|
||||
/// <para>
|
||||
/// 仅当 <see cref="PlatformAuthScheme"/> 的值为 <see cref="Settings.PlatformAuthScheme.Certificate"/> 时有效。
|
||||
/// 仅当 <see cref="PlatformAuthScheme"/> 的值为 <see cref="Settings.PlatformAuthScheme.Certificate"/>,或开启 <see cref="PlatformAuthSchemaFallback"/> 开关时有效。
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 默认值:<see cref="Settings.InMemoryCertificateManager"/>
|
||||
@@ -93,7 +103,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
/// <summary>
|
||||
/// 获取或设置微信支付平台公钥管理器。
|
||||
/// <para>
|
||||
/// 仅当 <see cref="PlatformAuthScheme"/> 的值为 <see cref="Settings.PlatformAuthScheme.PublicKey"/> 时有效。
|
||||
/// 仅当 <see cref="PlatformAuthScheme"/> 的值为 <see cref="Settings.PlatformAuthScheme.PublicKey"/>,或开启 <see cref="PlatformAuthSchemaFallback"/> 开关时有效。
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 默认值:<see cref="Settings.InMemoryPublicKeyManager"/>
|
||||
|
||||
Reference in New Issue
Block a user