diff --git a/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net5/Services/BackgroundServices/WxpayCertificateRefreshingBackgroundService.cs b/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net5/Services/BackgroundServices/WxpayCertificateRefreshingBackgroundService.cs index 5d02dc85..bd792a34 100644 --- a/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net5/Services/BackgroundServices/WxpayCertificateRefreshingBackgroundService.cs +++ b/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net5/Services/BackgroundServices/WxpayCertificateRefreshingBackgroundService.cs @@ -49,7 +49,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net5.Services.BackgroundSe var response = await client.ExecuteQueryCertificatesAsync(request, cancellationToken: stoppingToken); if (response.IsSuccessful()) { - client.DecryptResponseEncryptedData(ref response); + response = client.DecryptResponseSensitiveProperty(response); foreach (var certificateModel in response.CertificateList) { _certificateManager.AddEntry(new CertificateEntry(certificateModel)); diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/AssemblyInfo.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/AssemblyInfo.cs new file mode 100644 index 00000000..ae4629e6 --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests")] \ No newline at end of file diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Attributes/WechatTenpaySensitivePropertyAttribute.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Attributes/WechatTenpaySensitivePropertyAttribute.cs new file mode 100644 index 00000000..6f304890 --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Attributes/WechatTenpaySensitivePropertyAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 +{ + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public sealed class WechatTenpaySensitivePropertyAttribute : Attribute + { + public string Algorithm { get; } + + public WechatTenpaySensitivePropertyAttribute(string algorithm) + { + Algorithm = algorithm; + } + } +} diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Constants/EncryptionAlgorithms.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Constants/EncryptionAlgorithms.cs index 0c93afd2..85835ad2 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Constants/EncryptionAlgorithms.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Constants/EncryptionAlgorithms.cs @@ -1,9 +1,9 @@ -using System; - -namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants { public static class EncryptionAlgorithms { public const string AEAD_AES_256_GCM = "AEAD_AES_256_GCM"; + + public const string RSA_2048_PKCS8_ECB = "RSA_2048_PKCS18_ECB"; } } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Constants/Internal/FormDataFields.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Constants/Internal/FormDataFields.cs index ae5948f0..ddb51bd2 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Constants/Internal/FormDataFields.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Constants/Internal/FormDataFields.cs @@ -1,6 +1,4 @@ -using System; - -namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants { internal static class FormDataFields { diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Constants/SignAlgorithms.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Constants/SignAlgorithms.cs index 56ffe932..f9c31239 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Constants/SignAlgorithms.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Constants/SignAlgorithms.cs @@ -1,6 +1,4 @@ -using System; - -namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants { public static class SignAlgorithms { diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Constants/SignTypes.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Constants/SignTypes.cs index ebc1cb84..04fb9bc1 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Constants/SignTypes.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Constants/SignTypes.cs @@ -1,6 +1,4 @@ -using System; - -namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants { public static class SignTypes { diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventDecryptionExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventDecryptionExtensions.cs index 081eae28..880c6abe 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventDecryptionExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventDecryptionExtensions.cs @@ -70,7 +70,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 } else { - throw new Exceptions.WechatTenpayEventDecryptionException("Unknown encrypt algorithm of the resource."); + throw new Exceptions.WechatTenpayEventDecryptionException("Unsupported encrypt algorithm of the resource."); } return client.JsonSerializer.Deserialize(plainJson); diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseDecryptionExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseDecryptionExtensions.cs index 2afa07d5..34cf3967 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseDecryptionExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseDecryptionExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 { @@ -15,7 +16,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 /// /// /// - public static Models.GetEcommerceApplymentByOutRequestNumberResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.GetEcommerceApplymentByOutRequestNumberResponse response) + public static TResponse DecryptResponseSensitiveProperty(this WechatTenpayClient client, TResponse response) + where TResponse : WechatTenpayResponse { if (client == null) throw new ArgumentNullException(nameof(client)); if (response == null) throw new ArgumentNullException(nameof(response)); @@ -26,159 +28,17 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 if (!response.IsSuccessful()) throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful."); - if (response.AccountValidation != null) + try { - IList exceptions = new List(); - - var accountValidationModel = response.AccountValidation; - - if (!string.IsNullOrEmpty(accountValidationModel.AccountName)) + // [GET] /certificates 接口的响应模型需特殊处理 + if (response is Models.QueryCertificatesResponse queryCertificatesResponse) { - try + if (queryCertificatesResponse.CertificateList == null) + return response; + + foreach (var certificateModel in queryCertificatesResponse.CertificateList) { - accountValidationModel.AccountName = Utilities.RSAUtility.DecryptWithECB( - client.Credentials.MerchantCertPrivateKey, - accountValidationModel.AccountName - ); - } - catch (Exception ex) - { - exceptions.Add(ex); - } - } - - if (!string.IsNullOrEmpty(accountValidationModel.AccountNumber)) - { - try - { - accountValidationModel.AccountNumber = Utilities.RSAUtility.DecryptWithECB( - client.Credentials.MerchantCertPrivateKey, - accountValidationModel.AccountNumber! - ); - } - catch (Exception ex) - { - exceptions.Add(ex); - } - } - - if (exceptions.Any()) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", new AggregateException(exceptions)); - } - - return response; - } - - /// - /// 解密响应中返回的敏感数据。该方法会改变传入的响应信息。 - /// - /// - /// - /// - public static Models.GetEcommerceBillFundflowBillResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.GetEcommerceBillFundflowBillResponse response) - { - if (client == null) throw new ArgumentNullException(nameof(client)); - if (response == null) throw new ArgumentNullException(nameof(response)); - - if (string.IsNullOrEmpty(client.Credentials.MerchantCertPrivateKey)) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because there is no merchant private key."); - - if (!response.IsSuccessful()) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful."); - - if (response.DownloadBillList != null) - { - IList exceptions = new List(); - - foreach (var downloadBillModel in response.DownloadBillList) - { - if (!string.IsNullOrEmpty(downloadBillModel.EncryptKey)) - { - try - { - downloadBillModel.EncryptKey = Utilities.RSAUtility.DecryptWithECB( - client.Credentials.MerchantCertPrivateKey, - downloadBillModel.EncryptKey - ); - } - catch (Exception ex) - { - exceptions.Add(ex); - } - } - } - - if (exceptions.Any()) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", new AggregateException(exceptions)); - } - - return response; - } - - /// - /// 解密响应中返回的敏感数据。该方法会改变传入的响应信息。 - /// - /// - /// - /// - public static Models.GetMerchantServiceComplaintByComplaintIdResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.GetMerchantServiceComplaintByComplaintIdResponse response) - { - if (client == null) throw new ArgumentNullException(nameof(client)); - if (response == null) throw new ArgumentNullException(nameof(response)); - - if (string.IsNullOrEmpty(client.Credentials.MerchantCertPrivateKey)) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because there is no merchant private key."); - - if (!response.IsSuccessful()) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful."); - - if (response.ComplaintDetail != null) - { - if (!string.IsNullOrEmpty(response.PayerPhone)) - { - try - { - response.PayerPhone = Utilities.RSAUtility.DecryptWithECB( - client.Credentials.MerchantCertPrivateKey, - response.PayerPhone! - ); - } - catch (Exception ex) - { - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", ex); - } - } - } - - return response; - } - - /// - /// 解密响应中返回的敏感数据。该方法会改变传入的响应信息。 - /// - /// - /// - /// - public static Models.QueryCertificatesResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.QueryCertificatesResponse response) - { - if (client == null) throw new ArgumentNullException(nameof(client)); - if (response == null) throw new ArgumentNullException(nameof(response)); - - if (string.IsNullOrEmpty(client.Credentials.MerchantV3Secret)) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because there is no merchant secret."); - - if (!response.IsSuccessful()) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful."); - - if (response.CertificateList != null) - { - IList exceptions = new List(); - - foreach (var certificateModel in response.CertificateList) - { - if (Constants.EncryptionAlgorithms.AEAD_AES_256_GCM.Equals(certificateModel.EncryptCertificate?.Algorithm)) - { - try + if (Constants.EncryptionAlgorithms.AEAD_AES_256_GCM.Equals(certificateModel.EncryptCertificate?.Algorithm)) { certificateModel.EncryptCertificate.CipherText = Utilities.AESUtility.DecryptWithGCM( key: client.Credentials.MerchantV3Secret, @@ -187,266 +47,38 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 cipherText: certificateModel.EncryptCertificate.CipherText ); } - catch (Exception ex) + else { - exceptions.Add(ex); + throw new Exceptions.WechatTenpayResponseDecryptionException("Unsupported decryption algorithm."); } } + + return response; + } + + // 遍历并解密被标记为敏感数据的字段 + Utilities.ReflectionUtility.ReplacePropertyStringValue(ref response, (obj, prop, value) => + { + var attr = prop.GetCustomAttribute(); + if (attr == null) + return value; + + if (Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB.Equals(attr.Algorithm)) + { + return Utilities.RSAUtility.DecryptWithECB( + privateKey: client.Credentials.MerchantCertPrivateKey, + cipherText: value + ); + } else { - exceptions.Add(new Exception("Unknown encrypt algorithm of the certificate.")); + throw new Exceptions.WechatTenpayResponseDecryptionException("Unsupported decryption algorithm."); } - } - - if (exceptions.Any()) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", new AggregateException(exceptions)); + }); } - - return response; - } - - /// - /// 解密响应中返回的敏感数据。该方法会改变传入的响应信息。 - /// - /// - /// - /// - public static Models.QueryMerchantServiceComplaintsResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.QueryMerchantServiceComplaintsResponse response) - { - if (client == null) throw new ArgumentNullException(nameof(client)); - if (response == null) throw new ArgumentNullException(nameof(response)); - - if (string.IsNullOrEmpty(client.Credentials.MerchantCertPrivateKey)) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because there is no merchant private key."); - - if (!response.IsSuccessful()) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful."); - - if (response.ComplaintList != null) + catch (Exception ex) when (!(ex is Exceptions.WechatTenpayResponseDecryptionException)) { - IList exceptions = new List(); - - foreach (var complaintModel in response.ComplaintList) - { - if (!string.IsNullOrEmpty(complaintModel.PayerPhone)) - { - try - { - complaintModel.PayerPhone = Utilities.RSAUtility.DecryptWithECB( - client.Credentials.MerchantCertPrivateKey, - complaintModel.PayerPhone! - ); - } - catch (Exception ex) - { - exceptions.Add(ex); - } - } - } - - if (exceptions.Any()) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", new AggregateException(exceptions)); - } - - return response; - } - - /// - /// 解密响应中返回的敏感数据。该方法会改变传入的响应信息。 - /// - /// - /// - /// - public static Models.QuerySmartGuidesResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.QuerySmartGuidesResponse response) - { - if (client == null) throw new ArgumentNullException(nameof(client)); - if (response == null) throw new ArgumentNullException(nameof(response)); - - if (string.IsNullOrEmpty(client.Credentials.MerchantCertPrivateKey)) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because there is no merchant private key."); - - if (!response.IsSuccessful()) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful."); - - if (response.GuideList != null) - { - IList exceptions = new List(); - - foreach (var guideModel in response.GuideList) - { - if (!string.IsNullOrEmpty(guideModel.UserName)) - { - try - { - guideModel.UserName = Utilities.RSAUtility.DecryptWithECB( - client.Credentials.MerchantCertPrivateKey, - guideModel.UserName - ); - } - catch (Exception ex) - { - exceptions.Add(ex); - } - } - - if (!string.IsNullOrEmpty(guideModel.UserMobile)) - { - try - { - guideModel.UserMobile = Utilities.RSAUtility.DecryptWithECB( - client.Credentials.MerchantCertPrivateKey, - guideModel.UserMobile - ); - } - catch (Exception ex) - { - exceptions.Add(ex); - } - } - } - - if (exceptions.Any()) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", new AggregateException(exceptions)); - } - - return response; - } - - /// - /// 解密响应中返回的敏感数据。该方法会改变传入的响应信息。 - /// - /// - /// - /// - public static Models.GetTransferBatchDetailByOutDetailNumberResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.GetTransferBatchDetailByOutDetailNumberResponse response) - { - if (client == null) throw new ArgumentNullException(nameof(client)); - if (response == null) throw new ArgumentNullException(nameof(response)); - - if (string.IsNullOrEmpty(client.Credentials.MerchantCertPrivateKey)) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because there is no merchant private key."); - - if (!response.IsSuccessful()) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful."); - - if (!string.IsNullOrEmpty(response.UserName)) - { - try - { - response.UserName = Utilities.RSAUtility.DecryptWithECB( - client.Credentials.MerchantCertPrivateKey, - response.UserName - ); - } - catch (Exception ex) - { - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", ex); - } - } - - return response; - } - - /// - /// 解密响应中返回的敏感数据。该方法会改变传入的响应信息。 - /// - /// - /// - /// - public static Models.GetTransferBatchDetailByDetailIdResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.GetTransferBatchDetailByDetailIdResponse response) - { - if (client == null) throw new ArgumentNullException(nameof(client)); - if (response == null) throw new ArgumentNullException(nameof(response)); - - if (string.IsNullOrEmpty(client.Credentials.MerchantCertPrivateKey)) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because there is no merchant private key."); - - if (!response.IsSuccessful()) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful."); - - if (!string.IsNullOrEmpty(response.UserName)) - { - try - { - response.UserName = Utilities.RSAUtility.DecryptWithECB( - client.Credentials.MerchantCertPrivateKey, - response.UserName - ); - } - catch (Exception ex) - { - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", ex); - } - } - - return response; - } - - /// - /// 解密响应中返回的敏感数据。该方法会改变传入的响应信息。 - /// - /// - /// - /// - public static Models.GetPartnerTransferBatchDetailByOutDetailNumberResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.GetPartnerTransferBatchDetailByOutDetailNumberResponse response) - { - if (client == null) throw new ArgumentNullException(nameof(client)); - if (response == null) throw new ArgumentNullException(nameof(response)); - - if (string.IsNullOrEmpty(client.Credentials.MerchantCertPrivateKey)) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because there is no merchant private key."); - - if (!response.IsSuccessful()) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful."); - - if (!string.IsNullOrEmpty(response.UserName)) - { - try - { - response.UserName = Utilities.RSAUtility.DecryptWithECB( - client.Credentials.MerchantCertPrivateKey, - response.UserName - ); - } - catch (Exception ex) - { - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", ex); - } - } - - return response; - } - - /// - /// 解密响应中返回的敏感数据。该方法会改变传入的响应信息。 - /// - /// - /// - /// - public static Models.GetPartnerTransferBatchDetailByDetailIdResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.GetPartnerTransferBatchDetailByDetailIdResponse response) - { - if (client == null) throw new ArgumentNullException(nameof(client)); - if (response == null) throw new ArgumentNullException(nameof(response)); - - if (string.IsNullOrEmpty(client.Credentials.MerchantCertPrivateKey)) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because there is no merchant private key."); - - if (!response.IsSuccessful()) - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful."); - - if (!string.IsNullOrEmpty(response.UserName)) - { - try - { - response.UserName = Utilities.RSAUtility.DecryptWithECB( - client.Credentials.MerchantCertPrivateKey, - response.UserName - ); - } - catch (Exception ex) - { - throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", ex); - } + throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed. Please see the `InnerException` for more details.", ex); } return response; diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Bill/GetBillSubMerchantFundflowBillResponse.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Bill/GetBillSubMerchantFundflowBillResponse.cs index e863a96c..27039579 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Bill/GetBillSubMerchantFundflowBillResponse.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Bill/GetBillSubMerchantFundflowBillResponse.cs @@ -45,6 +45,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models /// [Newtonsoft.Json.JsonProperty("encrypt_key")] [System.Text.Json.Serialization.JsonPropertyName("encrypt_key")] + [WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)] public string EncryptKey { get; set; } = default!; /// diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Certificates/QueryCertificatesResponse.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Certificates/QueryCertificatesResponse.cs index 5537bb36..1b366e78 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Certificates/QueryCertificatesResponse.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Certificates/QueryCertificatesResponse.cs @@ -42,6 +42,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models /// [Newtonsoft.Json.JsonProperty("ciphertext")] [System.Text.Json.Serialization.JsonPropertyName("ciphertext")] + [WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.AEAD_AES_256_GCM)] public string CipherText { get; set; } = default!; } } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/EcommerceApplyments/GetEcommerceApplymentByOutRequestNumberResponse.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/EcommerceApplyments/GetEcommerceApplymentByOutRequestNumberResponse.cs index 83bc4b6b..c60daa1d 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/EcommerceApplyments/GetEcommerceApplymentByOutRequestNumberResponse.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/EcommerceApplyments/GetEcommerceApplymentByOutRequestNumberResponse.cs @@ -17,6 +17,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models /// [Newtonsoft.Json.JsonProperty("account_name")] [System.Text.Json.Serialization.JsonPropertyName("account_name")] + [WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)] public string AccountName { get; set; } = default!; /// @@ -24,6 +25,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models /// [Newtonsoft.Json.JsonProperty("account_no")] [System.Text.Json.Serialization.JsonPropertyName("account_no")] + [WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)] public string? AccountNumber { get; set; } /// diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/EcommerceBill/GetEcommerceBillFundflowBillResponse.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/EcommerceBill/GetEcommerceBillFundflowBillResponse.cs index 793f23e5..833fa9ed 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/EcommerceBill/GetEcommerceBillFundflowBillResponse.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/EcommerceBill/GetEcommerceBillFundflowBillResponse.cs @@ -45,6 +45,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models /// [Newtonsoft.Json.JsonProperty("encrypt_key")] [System.Text.Json.Serialization.JsonPropertyName("encrypt_key")] + [WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)] public string EncryptKey { get; set; } = default!; /// diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/MerchantService/ComplaintsV2/GetMerchantServiceComplaintByComplaintIdResponse.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/MerchantService/ComplaintsV2/GetMerchantServiceComplaintByComplaintIdResponse.cs index 9a64404e..32576789 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/MerchantService/ComplaintsV2/GetMerchantServiceComplaintByComplaintIdResponse.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/MerchantService/ComplaintsV2/GetMerchantServiceComplaintByComplaintIdResponse.cs @@ -94,6 +94,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models /// [Newtonsoft.Json.JsonProperty("payer_phone")] [System.Text.Json.Serialization.JsonPropertyName("payer_phone")] + [WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)] public string? PayerPhone { get; set; } /// diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/MerchantService/ComplaintsV2/QueryMerchantServiceComplaintsResponse.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/MerchantService/ComplaintsV2/QueryMerchantServiceComplaintsResponse.cs index 8282dce3..f3acb06e 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/MerchantService/ComplaintsV2/QueryMerchantServiceComplaintsResponse.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/MerchantService/ComplaintsV2/QueryMerchantServiceComplaintsResponse.cs @@ -58,6 +58,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models /// [Newtonsoft.Json.JsonProperty("payer_phone")] [System.Text.Json.Serialization.JsonPropertyName("payer_phone")] + [WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)] public string? PayerPhone { get; set; } /// diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/PartnerTransfer/Batches/GetPartnerTransferBatchDetailByOutDetailNumberResponse.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/PartnerTransfer/Batches/GetPartnerTransferBatchDetailByOutDetailNumberResponse.cs index f0e993b8..23b5e457 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/PartnerTransfer/Batches/GetPartnerTransferBatchDetailByOutDetailNumberResponse.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/PartnerTransfer/Batches/GetPartnerTransferBatchDetailByOutDetailNumberResponse.cs @@ -20,6 +20,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models /// [Newtonsoft.Json.JsonProperty("username")] [System.Text.Json.Serialization.JsonPropertyName("username")] + [WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)] public override string UserName { get; set; } = default!; } } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/PayScoreBill/GetPayScoreMerchantBillResponse.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/PayScoreBill/GetPayScoreMerchantBillResponse.cs index 36d3a61a..3037dd69 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/PayScoreBill/GetPayScoreMerchantBillResponse.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/PayScoreBill/GetPayScoreMerchantBillResponse.cs @@ -45,6 +45,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models /// [Newtonsoft.Json.JsonProperty("encrypt_key")] [System.Text.Json.Serialization.JsonPropertyName("encrypt_key")] + [WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)] public string EncryptKey { get; set; } = default!; /// diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/SmartGuide/QuerySmartGuidesResponse.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/SmartGuide/QuerySmartGuidesResponse.cs index 0223f275..4f3e4236 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/SmartGuide/QuerySmartGuidesResponse.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/SmartGuide/QuerySmartGuidesResponse.cs @@ -38,6 +38,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models /// [Newtonsoft.Json.JsonProperty("name")] [System.Text.Json.Serialization.JsonPropertyName("name")] + [WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)] public string UserName { get; set; } = default!; /// @@ -45,6 +46,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models /// [Newtonsoft.Json.JsonProperty("mobile")] [System.Text.Json.Serialization.JsonPropertyName("mobile")] + [WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)] public string UserMobile { get; set; } = default!; /// diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Transfer/Batches/GetTransferBatchDetailByOutDetailNumberResponse.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Transfer/Batches/GetTransferBatchDetailByOutDetailNumberResponse.cs index 12cae7b9..6d6485be 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Transfer/Batches/GetTransferBatchDetailByOutDetailNumberResponse.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Models/Transfer/Batches/GetTransferBatchDetailByOutDetailNumberResponse.cs @@ -83,6 +83,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models /// [Newtonsoft.Json.JsonProperty("user_name")] [System.Text.Json.Serialization.JsonPropertyName("user_name")] + [WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)] public virtual string UserName { get; set; } = default!; /// diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Settings/CertificateManager.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Settings/CertificateManager.cs index eae7520b..c1440010 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Settings/CertificateManager.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Settings/CertificateManager.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings { @@ -49,7 +50,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings public override IEnumerable AllEntries() { - return _dict.Values; + return _dict.Values.Where(e => e.IsAvailable()).ToArray(); } public override void AddEntry(CertificateEntry entry) diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/Internal/ReflectionUtility.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/Internal/ReflectionUtility.cs new file mode 100644 index 00000000..e0a0a481 --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/Internal/ReflectionUtility.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections; +using System.Reflection; + +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities +{ + internal static class ReflectionUtility + { + public delegate string ReplacePropertyStringValueReplacement(object obj, PropertyInfo prop, string value); + + public static void ReplacePropertyStringValue(ref T obj, ReplacePropertyStringValueReplacement replacement) + { + InnerReplacePropertyStringValue(ref obj, replacement, null); + } + + private static void InnerReplacePropertyStringValue(ref T obj, ReplacePropertyStringValueReplacement replacement, PropertyInfo? currentProp) + { + if (obj == null) throw new ArgumentNullException(nameof(obj)); + if (replacement == null) throw new ArgumentNullException(nameof(replacement)); + + Type objType = obj.GetType(); + if (!objType.IsClass) + throw new NotSupportedException(); + + if (objType.IsArray) + { + var array = (obj as Array)!; + for (int i = 0, len = array.Length; i < len; i++) + { + object? element = array.GetValue(i); + if (element is null) + continue; + + Type elementType = element.GetType(); + if (elementType == typeof(string)) + { + if (currentProp == null) + continue; + + string oldValue = (string)element!; + string newValue = replacement(obj, currentProp, oldValue); + array.SetValue(newValue, i); + } + else if (elementType.IsClass) + { + InnerReplacePropertyStringValue(ref element, replacement, currentProp); + array.SetValue(element, i); + } + else + { + continue; + } + } + } + else if (obj is IList list) + { + for (int i = 0, len = list.Count; i < len; i++) + { + object? element = list[i]; + if (element is null) + continue; + + Type elementType = element.GetType(); + if (elementType == typeof(string)) + { + if (currentProp == null) + continue; + + string oldValue = (string)element!; + string newValue = replacement(obj, currentProp, oldValue); + list[i] = newValue; + } + else if (elementType.IsClass) + { + InnerReplacePropertyStringValue(ref element, replacement, currentProp); + list[i] = element; + } + else + { + continue; + } + } + } + else if (obj is IDictionary dict) + { + foreach (DictionaryEntry entry in dict) + { + object? entryValue = entry.Value; + if (entryValue is null) + continue; + + Type entryValueType = entryValue.GetType(); + if (entryValueType == typeof(string)) + { + if (currentProp == null) + continue; + + string oldValue = (string)entryValue!; + string newValue = replacement(obj, currentProp, oldValue); + dict[entry.Key] = newValue; + } + else if (entryValueType.IsClass) + { + InnerReplacePropertyStringValue(ref entryValue, replacement, currentProp); + dict[entry.Key] = entryValue; + } + else + { + continue; + } + } + } + else + { + foreach (var childProp in objType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (!childProp.CanWrite) + continue; + + Type propType = childProp.PropertyType; + if (propType == typeof(string)) + { + string oldValue = (string)childProp.GetValue(obj, null)!; + string newValue = replacement(obj, childProp, oldValue); + childProp.SetValue(obj, newValue); + } + else if (propType.IsClass) + { + object? value = childProp.GetValue(obj, null); + if (value is null) + continue; + + InnerReplacePropertyStringValue(ref value, replacement, childProp); + childProp.SetValue(obj, value); + } + } + } + } + } +} diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClient.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClient.cs index 9dfe3224..68399eba 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClient.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClient.cs @@ -23,7 +23,17 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 /// /// 获取当前客户端使用的微信商户平台证书管理器。 /// - internal Settings.CertificateManager CertificateManager { get; } + public Settings.CertificateManager CertificateManager { get; } + + /// + /// 获取是否自动加密请求中的敏感字段数据。 + /// + protected bool AutoEncryptRequestSensitiveProperty { get; } + + /// + /// 获取是否自动解密请求中的敏感字段数据。 + /// + protected bool AutoDecryptResponseSensitiveProperty { get; } /// /// 用指定的配置项初始化 类的新实例。 @@ -35,6 +45,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 Credentials = new Settings.Credentials(options); CertificateManager = options.CertificateManager; + AutoEncryptRequestSensitiveProperty = options.AutoEncryptRequestSensitiveProperty; + AutoDecryptResponseSensitiveProperty = options.AutoDecryptResponseSensitiveProperty; FlurlClient.BaseUrl = options.Endpoints ?? WechatTenpayEndpoints.DEFAULT; FlurlClient.Headers.Remove("Accept"); @@ -75,6 +87,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 flurlRequest.WithHeader("Wechatpay-Serial", request.WechatpayCertSerialNumber); } + if (AutoDecryptResponseSensitiveProperty) + { + // this.EncryptRequestSensitiveProperty(request); + throw new NotImplementedException(); + } + return flurlRequest; } @@ -152,6 +170,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 result.WechatpayTimestamp = flurlResponse.Headers.GetAll("Wechatpay-Timestamp").FirstOrDefault() ?? string.Empty; result.WechatpaySignature = flurlResponse.Headers.GetAll("Wechatpay-Signature").FirstOrDefault() ?? string.Empty; result.WechatpayCertSerialNumber = flurlResponse.Headers.GetAll("Wechatpay-Serial").FirstOrDefault() ?? string.Empty; + + if (AutoDecryptResponseSensitiveProperty) + { + this.DecryptResponseSensitiveProperty(result); + } + return result; } } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClientOptions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClientOptions.cs index b325320f..97c92423 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClientOptions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClientOptions.cs @@ -58,6 +58,16 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 /// public string MerchantCertPrivateKey { get; set; } = default!; + /// + /// 获取或设置是否自动加密请求中的敏感字段数据。 + /// + public bool AutoEncryptRequestSensitiveProperty { get; set; } + + /// + /// 获取或设置是否自动解密请求中的敏感字段数据。 + /// + public bool AutoDecryptResponseSensitiveProperty { get; set; } + /// /// 获取或设置微信商户平台证书管理器。 /// 默认值: diff --git a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/WechatTenpayResponseDecryptionTests.cs b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/WechatTenpayResponseDecryptionTests.cs new file mode 100644 index 00000000..f1d8d66c --- /dev/null +++ b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/WechatTenpayResponseDecryptionTests.cs @@ -0,0 +1,189 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests +{ + public class WechatTenpayResponseDecryptionTests + { + private const string RSA_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCd094X2jr6wrL1pIBTSbKX5hVqxyCJhKLqaf31KQsw2LdzfXEOL45KiYr6IshwZaSPFok7Sq+1wLrO6/DZ7/GSmzVwdFIV/zjDbI3jaSHt69ZAJGBS212AzC4cwHvpGrHm3kqzVifJyUNxsoTvWklRGSE50pPWaXoxmdPgD5da8IPDHNzkPM0QYTOlHmIRsDQZpWiZRPaFdrN3i/qG9toCRCPVptCVNYrWLDEFXh3ioOqOdTk1fHxFczaNf4YvQhsqXlG/lGiqGbuPuFYfF/RvS9KfptAM1yycknTslkSeiOpA22sFbDdb+m7DHv+nc8DQPPmD7SxDwVIDTcIS7pEJAgMBAAECggEAM6E+czEZwDR3FLKGpDhkqxpCgVa4xyPTo7mesVeG6KjMgoRTup9F/g42n5NHXLVzereVlwbcqiltMwmuFa1PqnUJq0ryG24NZKibVfxrdAiFYyBDPneyg9LHdvJk1qG79tlbOIWDqJglbwlGQYKYn2YIH4FKiYahyZ4X2KFhEwc9mWydHSOyN8zOOJcRCy1FzUcUvITRKob+Q9epz9/4/VX0g1AnB6FwIR5pBqwMYLSv+g+JxfVKPRnSaIxq/2HOvhiyJ7fUqX1yGI+konJJHrn66JIux8xt4SxEnomZBveHlOMUTgTqovxpXisbvXQGBDV7dwW/qhGZet6h57qogQKBgQDNFLP9S6aPnwseeavxK0ygQpgJRbXbHCyqH/mVA/Pg9DaIhxl7+JCC1lS/JuZslow4t3JvjwixAzQEz9SkwUuiRO5vUWb5R+DZJ8HeqtMfoll6wxepu1TQT1pTPnSHliJQP15k5AQ1bkTZjcGA79iUmkksa4EP/GWtOooE8JhMEQKBgQDFA6oyBtbMyWlnGmdsieQuRsjCklZhPL93INX5VUfcfRqQdhqrmoaJg+OZTwVrARp6VHGEaURBTSj6bSoRbBckFNxjVsL6Utpgof+ZWmr5u8ZGHGHIPJMLt7GxI9SItpNNNY33OiUkkfSH4zHK5KZeG9sKKraQwITJCwLZUnnNeQKBgChHkLKHUUeULVLnAuZzYrF3YvUvQ/CtL/iaHyMti5D7Zlqabl7zCy8nea2xrkBVsWTSYx+WMFbUEjt/tnxFmt1cPJiQnHEJtRfxvxpE4wKrmHeMKfGkYZwoec0vzyNyUXsBd0DJqCn2Zn90YDU65ocJZqXa15aUNEQ54zHlL4SBAoGAHbve3OwBUSj4unHWuB/bi0xtkkgJt2U2tGEFSjsfvFw5PSJGBi4tLeX03Ld7ZtnkyB+kfkpw3bYqgBknpzd8CpsHZAq9JJCKmtj4PYnS6Vv4oa4458KUoskXjVeOBRAhDR8PDQf+gRVyJWwZoLh/j2Z+2Xr20MPthnYd+PSko2kCgYBra4rMhYx2Hg0rRe2O7ju+MPm+JK01VpbvwDTnEPnYgMImDmLAXF6GljCt3iy/8X1WcjMPxGjTJ/xfTMne/aqKwvPhZCBL4DdNLNRzppCovsaaMHzrQzy4cvg0IEhIprFeR7ED4eMs8zLUhl3vgNhHOkeQ7cyuEnTl5wB9xOkbSw==-----END PRIVATE KEY-----"; + private const string RSA_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAndPeF9o6+sKy9aSAU0myl+YVascgiYSi6mn99SkLMNi3c31xDi+OSomK+iLIcGWkjxaJO0qvtcC6zuvw2e/xkps1cHRSFf84w2yN42kh7evWQCRgUttdgMwuHMB76Rqx5t5Ks1YnyclDcbKE71pJURkhOdKT1ml6MZnT4A+XWvCDwxzc5DzNEGEzpR5iEbA0GaVomUT2hXazd4v6hvbaAkQj1abQlTWK1iwxBV4d4qDqjnU5NXx8RXM2jX+GL0IbKl5Rv5Roqhm7j7hWHxf0b0vSn6bQDNcsnJJ07JZEnojqQNtrBWw3W/puwx7/p3PA0Dz5g+0sQ8FSA03CEu6RCQIDAQAB-----END PUBLIC KEY-----"; + private const string MockText = "mock_text"; + private readonly Lazy MockClientInstance = new Lazy(() => + { + return new WechatTenpayClient(new WechatTenpayClientOptions() + { + MerchantCertPrivateKey = RSA_PRIVATE_KEY + }); + }, isThreadSafe: false); + + [Fact(DisplayName = "解密响应中的敏感数据([GET] /ecommerce/applyments/out-request-no/{out_request_no})")] + public void DecryptResponseSensitiveProperty_GetEcommerceApplymentByOutRequestNumberResponseTest() + { + var mock = new Models.GetEcommerceApplymentByOutRequestNumberResponse() + { + RawStatus = 200, + ApplymentState = MockText, + AccountValidation = new Models.GetEcommerceApplymentByOutRequestNumberResponse.Types.AccountValidation() + { + AccountName = Utilities.RSAUtility.EncryptWithECB(RSA_PUBLIC_KEY, MockText), + AccountNumber = Utilities.RSAUtility.EncryptWithECB(RSA_PUBLIC_KEY, MockText), + DestinationAccountName = MockText, + DestinationAccountNumber = MockText + } + }; + var data = MockClientInstance.Value.DecryptResponseSensitiveProperty(mock); + + Assert.Equal(MockText, data.ApplymentState); + Assert.Equal(MockText, data.AccountValidation.AccountNumber); + Assert.Equal(MockText, data.AccountValidation.DestinationAccountName); + Assert.Equal(MockText, data.AccountValidation.DestinationAccountNumber); + Assert.Equal(MockText, data.AccountValidation.AccountName); + } + + [Fact(DisplayName = "解密响应中的敏感数据([GET] /ecommerce/bill/fundflowbill)")] + public void DecryptResponseSensitiveProperty_GetEcommerceBillFundflowBillResponse() + { + var mock = new Models.GetEcommerceBillFundflowBillResponse() + { + RawStatus = 200, + DownloadBillCount = 1, + DownloadBillList = new Models.GetEcommerceBillFundflowBillResponse.Types.DownloadBill[] + { + new Models.GetEcommerceBillFundflowBillResponse.Types.DownloadBill() + { + DownloadUrl = MockText, + EncryptKey = Utilities.RSAUtility.EncryptWithECB(RSA_PUBLIC_KEY, MockText) + }, + new Models.GetEcommerceBillFundflowBillResponse.Types.DownloadBill() + { + DownloadUrl = MockText, + EncryptKey = Utilities.RSAUtility.EncryptWithECB(RSA_PUBLIC_KEY, MockText) + } + } + }; + var data = MockClientInstance.Value.DecryptResponseSensitiveProperty(mock); + + Assert.Equal(1, data.DownloadBillCount); + Assert.Equal(MockText, data.DownloadBillList[0].DownloadUrl); + Assert.Equal(MockText, data.DownloadBillList[0].EncryptKey); + Assert.Equal(MockText, data.DownloadBillList[1].DownloadUrl); + Assert.Equal(MockText, data.DownloadBillList[1].EncryptKey); + } + + [Fact(DisplayName = "解密响应中的敏感数据([GET] /merchant-service/complaints-v2/{complaint_id})")] + public void DecryptResponseSensitiveProperty_GetMerchantServiceComplaintByComplaintIdResponse() + { + var mock = new Models.GetMerchantServiceComplaintByComplaintIdResponse() + { + RawStatus = 200, + ComplaintOrderList = new Models.GetMerchantServiceComplaintByComplaintIdResponse.Types.ComplaintOrder[] + { + new Models.GetMerchantServiceComplaintByComplaintIdResponse.Types.ComplaintOrder() + { + TransactionId = MockText + } + }, + ComplaintDetail = MockText, + PayerPhone = Utilities.RSAUtility.EncryptWithECB(RSA_PUBLIC_KEY, MockText) + }; + var data = MockClientInstance.Value.DecryptResponseSensitiveProperty(mock); + + Assert.Equal(MockText, data.ComplaintDetail); + Assert.Equal(MockText, data.PayerPhone); + Assert.Equal(MockText, data.ComplaintOrderList[0].TransactionId); + } + + [Fact(DisplayName = "解密响应中的敏感数据([GET] /merchant-service/complaints-v2)")] + public void DecryptResponseSensitiveProperty_QueryMerchantServiceComplaintsResponse() + { + var mock = new Models.QueryMerchantServiceComplaintsResponse() + { + RawStatus = 200, + ComplaintList = new Models.QueryMerchantServiceComplaintsResponse.Types.Complaint[] + { + new Models.QueryMerchantServiceComplaintsResponse.Types.Complaint() + { + PayerPhone = Utilities.RSAUtility.EncryptWithECB(RSA_PUBLIC_KEY, MockText), + PayerOpenId = MockText + } + } + }; + var data = MockClientInstance.Value.DecryptResponseSensitiveProperty(mock); + + Assert.Equal(MockText, data.ComplaintList[0].PayerPhone); + Assert.Equal(MockText, data.ComplaintList[0].PayerOpenId); + } + + [Fact(DisplayName = "解密响应中的敏感数据([GET] /smartguide/guides)")] + public void DecryptResponseSensitiveProperty_QuerySmartGuidesResponse() + { + var mock = new Models.QuerySmartGuidesResponse() + { + RawStatus = 200, + GuideList = new Models.QuerySmartGuidesResponse.Types.Guide[] + { + new Models.QuerySmartGuidesResponse.Types.Guide() + { + GuideId = MockText, + UserName = Utilities.RSAUtility.EncryptWithECB(RSA_PUBLIC_KEY, MockText), + UserMobile = Utilities.RSAUtility.EncryptWithECB(RSA_PUBLIC_KEY, MockText) + } + } + }; + var data = MockClientInstance.Value.DecryptResponseSensitiveProperty(mock); + + Assert.Equal(MockText, data.GuideList[0].GuideId); + Assert.Equal(MockText, data.GuideList[0].UserName); + Assert.Equal(MockText, data.GuideList[0].UserMobile); + } + + [Fact(DisplayName = "解密响应中的敏感数据([GET] /transfer/batches/out-batch-no/{out_batch_no}/details/out-detail-no/{out_detail_no})")] + public void DecryptResponseSensitiveProperty_GetTransferBatchDetailByOutDetailNumberResponse() + { + var mock = new Models.GetTransferBatchDetailByOutDetailNumberResponse() + { + RawStatus = 200, + UserName = Utilities.RSAUtility.EncryptWithECB(RSA_PUBLIC_KEY, MockText), + OpenId = MockText + }; + var data = MockClientInstance.Value.DecryptResponseSensitiveProperty(mock); + + Assert.Equal(MockText, data.UserName); + Assert.Equal(MockText, data.OpenId); + } + + [Fact(DisplayName = "解密响应中的敏感数据([GET] /transfer/batches/batch-id/{batch_id}/details/detail-id/{detail_id})")] + public void DecryptResponseSensitiveProperty_GetTransferBatchDetailByDetailIdResponse() + { + var mock = new Models.GetTransferBatchDetailByDetailIdResponse() + { + RawStatus = 200, + UserName = Utilities.RSAUtility.EncryptWithECB(RSA_PUBLIC_KEY, MockText), + OpenId = MockText + }; + var data = MockClientInstance.Value.DecryptResponseSensitiveProperty(mock); + + Assert.Equal(MockText, data.UserName); + Assert.Equal(MockText, data.OpenId); + } + + [Fact(DisplayName = "解密响应中的敏感数据([GET] /partner-transfer/batches/batch-id/{batch_id}/details/detail-id/{detail_id})")] + public void DecryptResponseSensitiveProperty_GetPartnerTransferBatchDetailByDetailIdResponse() + { + var mock = new Models.GetPartnerTransferBatchDetailByDetailIdResponse() + { + RawStatus = 200, + UserName = Utilities.RSAUtility.EncryptWithECB(RSA_PUBLIC_KEY, MockText), + OpenId = MockText + }; + var data = MockClientInstance.Value.DecryptResponseSensitiveProperty(mock); + + Assert.Equal(MockText, data.UserName); + Assert.Equal(MockText, data.OpenId); + } + } +} diff --git a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/WechatTenpayResponseVerificationTests.cs b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/WechatTenpayResponseVerificationTests.cs index c3fe8658..912c2fc7 100644 --- a/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/WechatTenpayResponseVerificationTests.cs +++ b/test/SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests/WechatTenpayResponseVerificationTests.cs @@ -21,7 +21,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests Assert.NotNull(response.WechatpayCertSerialNumber); Assert.NotNull(response.WechatpaySignature); - TestClients.Instance.DecryptResponseEncryptedData(ref response); + response = TestClients.Instance.DecryptResponseSensitiveProperty(response); foreach (var certificateModel in response.CertificateList) { TestClients.GlobalCertificateManager.AddEntry(new Settings.CertificateEntry(certificateModel));