2022-11-13 23:17:18 +08:00
|
|
|
|
using System;
|
2021-12-03 16:27:13 +08:00
|
|
|
|
using System.Linq;
|
2021-11-25 18:05:22 +08:00
|
|
|
|
using System.Reflection;
|
2021-05-10 15:30:00 +08:00
|
|
|
|
|
|
|
|
|
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|
|
|
|
|
{
|
2024-02-05 10:53:59 +08:00
|
|
|
|
using SKIT.FlurlHttpClient.Primitives;
|
2024-02-06 11:23:04 +08:00
|
|
|
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants;
|
2024-02-04 11:39:32 +08:00
|
|
|
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Models;
|
|
|
|
|
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities;
|
|
|
|
|
|
|
2021-05-10 15:30:00 +08:00
|
|
|
|
public static class WechatTenpayClientResponseDecryptionExtensions
|
|
|
|
|
|
{
|
2024-02-04 11:39:32 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// <para>解密响应中返回的敏感数据。该方法会改变传入的响应模型对象。</para>
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="client"></param>
|
|
|
|
|
|
/// <param name="response"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public static QueryCertificatesResponse DecryptResponseSensitiveProperty(this WechatTenpayClient client, QueryCertificatesResponse response)
|
2022-11-13 23:17:18 +08:00
|
|
|
|
{
|
2024-02-04 11:39:32 +08:00
|
|
|
|
if (client is null) throw new ArgumentNullException(nameof(client));
|
|
|
|
|
|
if (response is null) throw new ArgumentNullException(nameof(response));
|
|
|
|
|
|
|
2024-01-29 23:12:37 +08:00
|
|
|
|
if (response.CertificateList is null)
|
2022-11-13 23:17:18 +08:00
|
|
|
|
return response;
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var certificate in response.CertificateList)
|
|
|
|
|
|
{
|
2024-01-29 23:12:37 +08:00
|
|
|
|
if (certificate.EncryptCertificate is null)
|
2022-11-13 23:17:18 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
switch (certificate.EncryptCertificate.Algorithm)
|
|
|
|
|
|
{
|
2024-02-06 11:23:04 +08:00
|
|
|
|
case EncryptionAlgorithms.AEAD_AES_256_GCM:
|
2022-11-13 23:17:18 +08:00
|
|
|
|
{
|
2024-02-19 19:41:31 +08:00
|
|
|
|
if (string.IsNullOrEmpty(client.Credentials.MerchantV3Secret))
|
|
|
|
|
|
throw new WechatTenpayException("Failed to decrypt response, because the merchant APIv3 secret is not set.");
|
2022-11-13 23:17:18 +08:00
|
|
|
|
|
2024-02-04 11:39:32 +08:00
|
|
|
|
certificate.EncryptCertificate.CipherText = AESUtility.DecryptWithGCM(
|
2024-02-05 10:53:59 +08:00
|
|
|
|
encodingKey: new EncodedString(client.Credentials.MerchantV3Secret, EncodingKinds.Literal),
|
|
|
|
|
|
encodingNonce: new EncodedString(certificate.EncryptCertificate.Nonce, EncodingKinds.Literal),
|
|
|
|
|
|
encodingAssociatedData: new EncodedString(certificate.EncryptCertificate.AssociatedData, EncodingKinds.Literal),
|
|
|
|
|
|
encodingCipher: new EncodedString(certificate.EncryptCertificate.CipherText, EncodingKinds.Base64)
|
|
|
|
|
|
)!;
|
2022-11-13 23:17:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2024-02-06 11:23:04 +08:00
|
|
|
|
case EncryptionAlgorithms.AEAD_SM4_128_GCM:
|
2022-11-13 23:17:18 +08:00
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(client.Credentials.MerchantV3Secret))
|
2024-01-29 23:12:37 +08:00
|
|
|
|
throw new WechatTenpayException("Failed to decrypt response, because the merchant APIv3 secret is not set.");
|
2022-11-13 23:17:18 +08:00
|
|
|
|
|
|
|
|
|
|
// REF: https://pay.weixin.qq.com/docs/merchant/development/shangmi/guide.html
|
|
|
|
|
|
// 由于 SM4 密钥长度的限制,密钥由 APIv3 密钥通过国密 SM3 Hash 计算生成。SM4 密钥取其摘要(256bit)的前 128bit。
|
2024-02-05 10:53:59 +08:00
|
|
|
|
byte[] secretBytes = SM3Utility.Hash(EncodedString.FromLiteralString(client.Credentials.MerchantV3Secret));
|
2022-11-13 23:17:18 +08:00
|
|
|
|
byte[] keyBytes = new byte[16];
|
|
|
|
|
|
Array.Copy(secretBytes, keyBytes, keyBytes.Length);
|
|
|
|
|
|
|
2024-02-04 11:39:32 +08:00
|
|
|
|
byte[] plainBytes = SM4Utility.DecryptWithGCM(
|
2022-11-13 23:17:18 +08:00
|
|
|
|
keyBytes: keyBytes,
|
2024-02-05 10:53:59 +08:00
|
|
|
|
nonceBytes: EncodedString.FromLiteralString(certificate.EncryptCertificate.Nonce),
|
|
|
|
|
|
associatedDataBytes: EncodedString.FromLiteralString(certificate.EncryptCertificate.AssociatedData),
|
|
|
|
|
|
cipherBytes: EncodedString.FromBase64String(certificate.EncryptCertificate.CipherText)
|
2022-11-13 23:17:18 +08:00
|
|
|
|
);
|
2024-02-05 10:53:59 +08:00
|
|
|
|
certificate.EncryptCertificate.CipherText = EncodedString.ToLiteralString(plainBytes).Value!;
|
2022-11-13 23:17:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
{
|
2024-01-29 23:12:37 +08:00
|
|
|
|
throw new WechatTenpayException($"Failed to decrypt response. Unsupported encryption algorithm: \"{certificate.EncryptCertificate.Algorithm}\".");
|
2022-11-13 23:17:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return response;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-10 15:30:00 +08:00
|
|
|
|
/// <summary>
|
2021-11-25 18:42:54 +08:00
|
|
|
|
/// <para>解密响应中返回的敏感数据。该方法会改变传入的响应模型对象。</para>
|
2021-05-10 15:30:00 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="client"></param>
|
|
|
|
|
|
/// <param name="response"></param>
|
|
|
|
|
|
/// <returns></returns>
|
2021-11-25 18:05:22 +08:00
|
|
|
|
public static TResponse DecryptResponseSensitiveProperty<TResponse>(this WechatTenpayClient client, TResponse response)
|
|
|
|
|
|
where TResponse : WechatTenpayResponse
|
2021-05-10 15:30:00 +08:00
|
|
|
|
{
|
2024-01-29 23:12:37 +08:00
|
|
|
|
if (client is null) throw new ArgumentNullException(nameof(client));
|
|
|
|
|
|
if (response is null) throw new ArgumentNullException(nameof(response));
|
2021-05-10 15:30:00 +08:00
|
|
|
|
|
|
|
|
|
|
if (!response.IsSuccessful())
|
2024-01-29 23:12:37 +08:00
|
|
|
|
throw new WechatTenpayException("Failed to decrypt response, because the response is not successful.");
|
2021-05-10 15:30:00 +08:00
|
|
|
|
|
2021-11-25 18:05:22 +08:00
|
|
|
|
try
|
2021-05-10 15:30:00 +08:00
|
|
|
|
{
|
2021-11-25 18:05:22 +08:00
|
|
|
|
// [GET] /certificates 接口的响应模型需特殊处理
|
2024-02-04 11:39:32 +08:00
|
|
|
|
if (response is QueryCertificatesResponse queryCertificatesResponse)
|
2021-05-10 15:30:00 +08:00
|
|
|
|
{
|
2024-02-04 11:39:32 +08:00
|
|
|
|
return (DecryptResponseSensitiveProperty(client, queryCertificatesResponse) as TResponse)!;
|
2021-05-10 15:30:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-11-13 23:17:18 +08:00
|
|
|
|
// 遍历并解密被标记为敏感数据的字段
|
2022-06-14 10:23:28 +08:00
|
|
|
|
bool requireDecrypt = Attribute.IsDefined(response.GetType(), typeof(WechatTenpaySensitiveAttribute));
|
2024-02-04 11:39:32 +08:00
|
|
|
|
if (!requireDecrypt)
|
|
|
|
|
|
return response;
|
|
|
|
|
|
|
|
|
|
|
|
string signScheme = client.Credentials.SignScheme;
|
|
|
|
|
|
|
|
|
|
|
|
ReflectionHelper.ReplaceObjectStringProperties(response, (_, currentProp, oldValue) =>
|
2021-05-10 15:30:00 +08:00
|
|
|
|
{
|
2024-02-04 11:39:32 +08:00
|
|
|
|
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);
|
2021-12-03 16:27:13 +08:00
|
|
|
|
|
2024-02-04 11:39:32 +08:00
|
|
|
|
string newValue;
|
|
|
|
|
|
switch (attribute.Algorithm)
|
|
|
|
|
|
{
|
2024-02-06 11:23:04 +08:00
|
|
|
|
case EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
|
2024-02-04 11:39:32 +08:00
|
|
|
|
{
|
|
|
|
|
|
newValue = RSAUtility.DecryptWithECB(
|
2024-02-05 10:53:59 +08:00
|
|
|
|
privateKeyPem: client.Credentials.MerchantCertificatePrivateKey,
|
|
|
|
|
|
encodingCipher: new EncodedString(oldValue, EncodingKinds.Base64),
|
|
|
|
|
|
paddingMode: RSAUtility.PADDING_MODE_OAEPWITHSHA1ANDMGF1
|
|
|
|
|
|
)!;
|
2024-02-04 11:39:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2024-02-06 11:23:04 +08:00
|
|
|
|
case EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
|
2024-02-04 11:39:32 +08:00
|
|
|
|
{
|
|
|
|
|
|
newValue = RSAUtility.DecryptWithECB(
|
2024-02-05 10:53:59 +08:00
|
|
|
|
privateKeyPem: client.Credentials.MerchantCertificatePrivateKey,
|
|
|
|
|
|
encodingCipher: new EncodedString(oldValue, EncodingKinds.Base64),
|
|
|
|
|
|
paddingMode: RSAUtility.PADDING_MODE_PKCS1
|
|
|
|
|
|
)!;
|
2024-02-04 11:39:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
2024-02-06 11:23:04 +08:00
|
|
|
|
case EncryptionAlgorithms.SM2_C1C3C2_ASN1:
|
2024-02-04 11:39:32 +08:00
|
|
|
|
{
|
|
|
|
|
|
newValue = SM2Utility.Decrypt(
|
2024-02-05 10:53:59 +08:00
|
|
|
|
privateKeyPem: client.Credentials.MerchantCertificatePrivateKey,
|
|
|
|
|
|
encodingCipher: new EncodedString(oldValue, EncodingKinds.Base64),
|
2024-02-04 11:39:32 +08:00
|
|
|
|
asn1Encoding: true
|
2024-02-05 10:53:59 +08:00
|
|
|
|
)!;
|
2024-02-04 11:39:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new WechatTenpayException($"Failed to decrypt response. Unsupported encryption algorithm: \"{attribute.Algorithm}\".");
|
|
|
|
|
|
}
|
2022-11-13 23:17:18 +08:00
|
|
|
|
}
|
2024-02-04 11:39:32 +08:00
|
|
|
|
|
|
|
|
|
|
return (true, newValue);
|
|
|
|
|
|
});
|
2022-11-13 23:17:18 +08:00
|
|
|
|
|
|
|
|
|
|
return response;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (WechatTenpayException)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw;
|
2021-07-19 22:05:59 +08:00
|
|
|
|
}
|
2022-11-13 23:17:18 +08:00
|
|
|
|
catch (Exception ex)
|
2021-07-19 21:02:25 +08:00
|
|
|
|
{
|
2024-01-29 23:12:37 +08:00
|
|
|
|
throw new WechatTenpayException("Failed to decrypt response. Please see the inner exception for more details.", ex);
|
2021-07-19 21:02:25 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2021-05-10 15:30:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|