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;
|
2022-11-13 23:17:18 +08:00
|
|
|
|
using System.Text;
|
2021-05-10 15:30:00 +08:00
|
|
|
|
|
|
|
|
|
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|
|
|
|
|
{
|
|
|
|
|
|
public static class WechatTenpayClientResponseDecryptionExtensions
|
|
|
|
|
|
{
|
2022-11-13 23:17:18 +08:00
|
|
|
|
private static TResponse InnerDecryptResponseSensitiveProperty<TResponse>(WechatTenpayClient client, TResponse response)
|
|
|
|
|
|
where TResponse : Models.QueryCertificatesResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
if (response.CertificateList == null)
|
|
|
|
|
|
return response;
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var certificate in response.CertificateList)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (certificate.EncryptCertificate == null)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
switch (certificate.EncryptCertificate.Algorithm)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Constants.EncryptionAlgorithms.AEAD_AES_256_GCM:
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(client.Credentials.MerchantCertificatePrivateKey))
|
2023-03-09 18:12:30 +08:00
|
|
|
|
throw new Exceptions.WechatTenpayResponseDecryptionException("Failed to decrypt response, because there is no merchant private key.");
|
2022-11-13 23:17:18 +08:00
|
|
|
|
|
|
|
|
|
|
certificate.EncryptCertificate.CipherText = Utilities.AESUtility.DecryptWithGCM(
|
|
|
|
|
|
key: client.Credentials.MerchantV3Secret,
|
|
|
|
|
|
nonce: certificate.EncryptCertificate.Nonce,
|
|
|
|
|
|
aad: certificate.EncryptCertificate.AssociatedData,
|
|
|
|
|
|
cipherText: certificate.EncryptCertificate.CipherText
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case Constants.EncryptionAlgorithms.AEAD_SM4_128_GCM:
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(client.Credentials.MerchantV3Secret))
|
2023-03-09 18:12:30 +08:00
|
|
|
|
throw new Exceptions.WechatTenpayResponseDecryptionException("Failed to decrypt response, because there is no merchant APIv3 secret.");
|
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。
|
|
|
|
|
|
byte[] secretBytes = Utilities.SM3Utility.Hash(Encoding.UTF8.GetBytes(client.Credentials.MerchantV3Secret));
|
|
|
|
|
|
byte[] keyBytes = new byte[16];
|
|
|
|
|
|
Array.Copy(secretBytes, keyBytes, keyBytes.Length);
|
|
|
|
|
|
|
|
|
|
|
|
byte[] plainBytes = Utilities.SM4Utility.DecryptWithGCM(
|
|
|
|
|
|
keyBytes: keyBytes,
|
|
|
|
|
|
nonceBytes: Encoding.UTF8.GetBytes(certificate.EncryptCertificate.Nonce),
|
|
|
|
|
|
aadBytes: Encoding.UTF8.GetBytes(certificate.EncryptCertificate.AssociatedData ?? string.Empty),
|
|
|
|
|
|
cipherBytes: Convert.FromBase64String(certificate.EncryptCertificate.CipherText)
|
|
|
|
|
|
);
|
|
|
|
|
|
certificate.EncryptCertificate.CipherText = Encoding.UTF8.GetString(plainBytes);
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new Exceptions.WechatTenpayResponseDecryptionException($"Unsupported encryption algorithm: \"{certificate.EncryptCertificate.Algorithm}\".");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return response;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static TResponse InnerDecryptResponseSensitivePropertyByRSA<TResponse>(WechatTenpayClient client, TResponse response)
|
|
|
|
|
|
where TResponse : WechatTenpayResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
Utilities.ReflectionUtility.ReplacePropertyStringValue(ref response, (target, currentProp, oldValue) =>
|
|
|
|
|
|
{
|
2023-03-30 21:40:48 +08:00
|
|
|
|
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
|
2022-11-13 23:17:18 +08:00
|
|
|
|
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
|
|
|
|
|
|
.FirstOrDefault(attr => Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(attr.Scheme));
|
|
|
|
|
|
if (attribute == null)
|
|
|
|
|
|
return (false, oldValue);
|
|
|
|
|
|
|
|
|
|
|
|
string newValue;
|
|
|
|
|
|
switch (attribute.Algorithm)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
|
|
|
|
|
|
{
|
|
|
|
|
|
newValue = Utilities.RSAUtility.DecryptWithECB(
|
|
|
|
|
|
privateKey: client.Credentials.MerchantCertificatePrivateKey,
|
|
|
|
|
|
cipherText: oldValue
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
|
|
|
|
|
|
{
|
|
|
|
|
|
newValue = Utilities.RSAUtility.DecryptWithECB(
|
|
|
|
|
|
privateKey: client.Credentials.MerchantCertificatePrivateKey,
|
|
|
|
|
|
cipherText: oldValue,
|
|
|
|
|
|
paddingMode: "PKCS1PADDING"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new Exceptions.WechatTenpayResponseDecryptionException($"Unsupported encryption algorithm: \"{attribute.Algorithm}\".");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (true, newValue);
|
|
|
|
|
|
});
|
|
|
|
|
|
return response;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static TResponse InnerDecryptResponseSensitivePropertyBySM<TResponse>(WechatTenpayClient client, TResponse response)
|
|
|
|
|
|
where TResponse : WechatTenpayResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
Utilities.ReflectionUtility.ReplacePropertyStringValue(ref response, (target, currentProp, oldValue) =>
|
|
|
|
|
|
{
|
2023-03-30 21:40:48 +08:00
|
|
|
|
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
|
2022-11-13 23:17:18 +08:00
|
|
|
|
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
|
|
|
|
|
|
.FirstOrDefault(attr => Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(attr.Scheme));
|
|
|
|
|
|
if (attribute == null)
|
|
|
|
|
|
return (false, oldValue);
|
|
|
|
|
|
|
|
|
|
|
|
string newValue;
|
|
|
|
|
|
switch (attribute.Algorithm)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Constants.EncryptionAlgorithms.SM2_C1C3C2_ASN1:
|
|
|
|
|
|
{
|
|
|
|
|
|
newValue = Utilities.SM2Utility.Decrypt(
|
|
|
|
|
|
privateKey: client.Credentials.MerchantCertificatePrivateKey,
|
|
|
|
|
|
cipherText: oldValue,
|
|
|
|
|
|
asn1Encoding: true
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new Exceptions.WechatTenpayResponseDecryptionException($"Unsupported encryption algorithm: \"{attribute.Algorithm}\".");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (true, newValue);
|
|
|
|
|
|
});
|
|
|
|
|
|
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
|
|
|
|
{
|
|
|
|
|
|
if (client == null) throw new ArgumentNullException(nameof(client));
|
|
|
|
|
|
if (response == null) throw new ArgumentNullException(nameof(response));
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.IsSuccessful())
|
2023-03-09 18:12:30 +08:00
|
|
|
|
throw new Exceptions.WechatTenpayResponseDecryptionException("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 接口的响应模型需特殊处理
|
|
|
|
|
|
if (response is Models.QueryCertificatesResponse queryCertificatesResponse)
|
2021-05-10 15:30:00 +08:00
|
|
|
|
{
|
2022-11-13 23:17:18 +08:00
|
|
|
|
return (InnerDecryptResponseSensitiveProperty(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));
|
2021-12-03 16:27:13 +08:00
|
|
|
|
if (requireDecrypt)
|
2021-05-10 15:30:00 +08:00
|
|
|
|
{
|
2022-11-13 23:17:18 +08:00
|
|
|
|
switch (client.Credentials.SignScheme)
|
2021-05-10 15:30:00 +08:00
|
|
|
|
{
|
2022-11-13 23:17:18 +08:00
|
|
|
|
case Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256:
|
|
|
|
|
|
return InnerDecryptResponseSensitivePropertyByRSA(client, response);
|
2021-12-03 16:27:13 +08:00
|
|
|
|
|
2022-11-13 23:17:18 +08:00
|
|
|
|
case Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3:
|
|
|
|
|
|
return InnerDecryptResponseSensitivePropertyBySM(client, response);
|
|
|
|
|
|
}
|
2021-12-03 16:27:13 +08:00
|
|
|
|
}
|
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
|
|
|
|
{
|
2023-03-09 18:12:30 +08:00
|
|
|
|
throw new Exceptions.WechatTenpayResponseDecryptionException("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
|
|
|
|
}
|
|
|
|
|
|
}
|