From 00ab1075c91527762e86192a703e42c4a3da3ade Mon Sep 17 00:00:00 2001 From: jeanwu <513564656@qq.com> Date: Thu, 16 May 2024 10:31:55 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=9B=9E=E8=B0=83=E5=85=AC?= =?UTF-8?q?=E9=92=A5=E9=AA=8C=E7=AD=BE=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/core/cipher/AbstractVerifier.java | 30 +++++- .../pay/java/core/cipher/RSAVerifier.java | 4 + .../AbstractNotificationConfig.java | 40 ++++++++ .../RSACombinedNotificationConfig.java | 94 +++++++++++++++++++ .../notification/RSANotificationConfig.java | 6 +- .../RSAPublicKeyNotificationConfig.java | 60 ++++++++++++ 6 files changed, 230 insertions(+), 4 deletions(-) create mode 100644 core/src/main/java/com/wechat/pay/java/core/notification/RSACombinedNotificationConfig.java create mode 100644 core/src/main/java/com/wechat/pay/java/core/notification/RSAPublicKeyNotificationConfig.java diff --git a/core/src/main/java/com/wechat/pay/java/core/cipher/AbstractVerifier.java b/core/src/main/java/com/wechat/pay/java/core/cipher/AbstractVerifier.java index 5ce17fce..4c99a7e0 100644 --- a/core/src/main/java/com/wechat/pay/java/core/cipher/AbstractVerifier.java +++ b/core/src/main/java/com/wechat/pay/java/core/cipher/AbstractVerifier.java @@ -40,6 +40,7 @@ protected AbstractVerifier(String algorithmName, CertificateProvider certificate * * @param algorithmName 获取Signature对象时指定的算法,例如SHA256withRSA * @param publicKey 验签使用的微信支付平台公钥,非空 + * @param publicKeyId 验签使用的微信支付平台公钥id */ protected AbstractVerifier(String algorithmName, PublicKey publicKey, String publicKeyId) { this.publicKey = requireNonNull(publicKey); @@ -48,6 +49,25 @@ protected AbstractVerifier(String algorithmName, PublicKey publicKey, String pub this.certificateProvider = null; } + /** + * AbstractVerifier 构造函数,仅在平台证书和平台公钥灰度切换阶段使用 + * + * @param algorithmName 获取Signature对象时指定的算法,例如SHA256withRSA + * @param publicKey 验签使用的微信支付平台公钥,非空 + * @param publicKeyId 验签使用的微信支付平台公钥id + * @param certificateProvider 验签使用的微信支付平台证书管理器,非空 + */ + protected AbstractVerifier( + String algorithmName, + PublicKey publicKey, + String publicKeyId, + CertificateProvider certificateProvider) { + this.publicKey = requireNonNull(publicKey); + this.publicKeyId = publicKeyId; + this.algorithmName = requireNonNull(algorithmName); + this.certificateProvider = requireNonNull(certificateProvider); + } + protected boolean verify(X509Certificate certificate, String message, String signature) { try { Signature sign = Signature.getInstance(algorithmName); @@ -87,15 +107,19 @@ public boolean verify(String serialNumber, String message, String signature) { if (serialNumber.equals(publicKeyId)) { return verify(message, signature); } - logger.error("publicKeyId[{}] and serialNumber[{}] are not equal", publicKeyId, serialNumber); - return false; + // 如果证书为空,则说明是传入的publicKeyId错误,如果不为空,则继续使用证书验签 + if (certificateProvider == null) { + logger.error( + "publicKeyId[{}] and serialNumber[{}] are not equal", publicKeyId, serialNumber); + return false; + } } // 使用证书验签 requireNonNull(certificateProvider); X509Certificate certificate = certificateProvider.getCertificate(serialNumber); if (certificate == null) { logger.error( - "Verify the signature and get the WechatPay certificate corresponding to " + "Verify the signature and get the WechatPay certificate or publicKey corresponding to " + "serialNumber[{}] is empty.", serialNumber); return false; diff --git a/core/src/main/java/com/wechat/pay/java/core/cipher/RSAVerifier.java b/core/src/main/java/com/wechat/pay/java/core/cipher/RSAVerifier.java index 1e28ab66..561e6691 100644 --- a/core/src/main/java/com/wechat/pay/java/core/cipher/RSAVerifier.java +++ b/core/src/main/java/com/wechat/pay/java/core/cipher/RSAVerifier.java @@ -15,4 +15,8 @@ public RSAVerifier(CertificateProvider provider) { public RSAVerifier(PublicKey publicKey, String publicKeyId) { super(SHA256WITHRSA, publicKey, publicKeyId); } + + public RSAVerifier(PublicKey publicKey, String publicKeyId, CertificateProvider provider) { + super(SHA256WITHRSA, publicKey, publicKeyId, provider); + } } diff --git a/core/src/main/java/com/wechat/pay/java/core/notification/AbstractNotificationConfig.java b/core/src/main/java/com/wechat/pay/java/core/notification/AbstractNotificationConfig.java index edc764f8..cd75994e 100644 --- a/core/src/main/java/com/wechat/pay/java/core/notification/AbstractNotificationConfig.java +++ b/core/src/main/java/com/wechat/pay/java/core/notification/AbstractNotificationConfig.java @@ -4,6 +4,7 @@ import com.wechat.pay.java.core.cipher.AeadCipher; import com.wechat.pay.java.core.cipher.RSAVerifier; import com.wechat.pay.java.core.cipher.Verifier; +import java.security.PublicKey; public abstract class AbstractNotificationConfig implements NotificationConfig { @@ -11,6 +12,8 @@ public abstract class AbstractNotificationConfig implements NotificationConfig { private final String cipherAlgorithm; private final CertificateProvider certificateProvider; private final AeadCipher aeadCipher; + private final PublicKey publicKey; + private final String publicKeyId; protected AbstractNotificationConfig( String signType, @@ -21,6 +24,37 @@ protected AbstractNotificationConfig( this.cipherAlgorithm = cipherAlgorithm; this.certificateProvider = certificateProvider; this.aeadCipher = aeadCipher; + this.publicKey = null; + this.publicKeyId = null; + } + + protected AbstractNotificationConfig( + String signType, + String cipherAlgorithm, + PublicKey publicKey, + String publicKeyId, + AeadCipher aeadCipher) { + this.signType = signType; + this.cipherAlgorithm = cipherAlgorithm; + this.publicKey = publicKey; + this.publicKeyId = publicKeyId; + this.aeadCipher = aeadCipher; + this.certificateProvider = null; + } + + protected AbstractNotificationConfig( + String signType, + String cipherAlgorithm, + CertificateProvider certificateProvider, + PublicKey publicKey, + String publicKeyId, + AeadCipher aeadCipher) { + this.signType = signType; + this.cipherAlgorithm = cipherAlgorithm; + this.publicKey = publicKey; + this.publicKeyId = publicKeyId; + this.aeadCipher = aeadCipher; + this.certificateProvider = certificateProvider; } @Override @@ -35,6 +69,12 @@ public String getCipherType() { @Override public Verifier createVerifier() { + if (publicKey != null && certificateProvider != null) { + return new RSAVerifier(publicKey, publicKeyId, certificateProvider); + } + if (publicKey != null) { + return new RSAVerifier(publicKey, publicKeyId); + } return new RSAVerifier(certificateProvider); } diff --git a/core/src/main/java/com/wechat/pay/java/core/notification/RSACombinedNotificationConfig.java b/core/src/main/java/com/wechat/pay/java/core/notification/RSACombinedNotificationConfig.java new file mode 100644 index 00000000..f5d74eff --- /dev/null +++ b/core/src/main/java/com/wechat/pay/java/core/notification/RSACombinedNotificationConfig.java @@ -0,0 +1,94 @@ +package com.wechat.pay.java.core.notification; + +import static com.wechat.pay.java.core.notification.Constant.AES_CIPHER_ALGORITHM; +import static com.wechat.pay.java.core.notification.Constant.RSA_SIGN_TYPE; +import static java.util.Objects.requireNonNull; + +import com.wechat.pay.java.core.AbstractRSAConfigBuilder; +import com.wechat.pay.java.core.certificate.CertificateProvider; +import com.wechat.pay.java.core.certificate.RSAAutoCertificateProvider; +import com.wechat.pay.java.core.cipher.AeadAesCipher; +import com.wechat.pay.java.core.cipher.AeadCipher; +import com.wechat.pay.java.core.http.HttpClient; +import com.wechat.pay.java.core.util.PemUtil; +import java.nio.charset.StandardCharsets; +import java.security.PublicKey; + +/** 通知回调配置类 该类仅在商户由平台证书切换为平台公钥的灰度阶段使用,灰度完成后请切换为RSAPublicKeyNotificationConfig */ +public final class RSACombinedNotificationConfig extends AbstractNotificationConfig { + + private RSACombinedNotificationConfig( + CertificateProvider certificateProvider, + PublicKey publicKey, + String publicKeyId, + AeadCipher aeadAesCipher) { + super( + RSA_SIGN_TYPE, + AES_CIPHER_ALGORITHM, + certificateProvider, + publicKey, + publicKeyId, + aeadAesCipher); + } + + public static class Builder extends AbstractRSAConfigBuilder { + protected HttpClient httpClient; + protected byte[] apiV3Key; + + private PublicKey publicKey; + private String publicKeyId; + + public Builder apiV3Key(String apiV3Key) { + this.apiV3Key = apiV3Key.getBytes(StandardCharsets.UTF_8); + return this; + } + + public Builder httpClient(HttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + public Builder publicKey(String publicKey) { + this.publicKey = PemUtil.loadPublicKeyFromString(publicKey); + return this; + } + + public Builder publicKey(PublicKey publicKey) { + this.publicKey = publicKey; + return this; + } + + public Builder publicFromPath(String publicKeyPath) { + this.publicKey = PemUtil.loadPublicKeyFromPath(publicKeyPath); + return this; + } + + public Builder publicKeyId(String publicKeyId) { + this.publicKeyId = publicKeyId; + return this; + } + + @Override + protected Builder self() { + return this; + } + + public RSACombinedNotificationConfig build() { + + RSAAutoCertificateProvider.Builder builder = + new RSAAutoCertificateProvider.Builder() + .apiV3Key(requireNonNull(apiV3Key)) + .privateKey(requireNonNull(privateKey)) + .merchantId(requireNonNull(merchantId)) + .merchantSerialNumber(requireNonNull(merchantSerialNumber)); + if (httpClient != null) { + builder.httpClient(httpClient); + } + return new RSACombinedNotificationConfig( + builder.build(), + requireNonNull(publicKey), + requireNonNull(publicKeyId), + new AeadAesCipher(requireNonNull(apiV3Key))); + } + } +} diff --git a/core/src/main/java/com/wechat/pay/java/core/notification/RSANotificationConfig.java b/core/src/main/java/com/wechat/pay/java/core/notification/RSANotificationConfig.java index 1627b4d0..7edf9fcb 100644 --- a/core/src/main/java/com/wechat/pay/java/core/notification/RSANotificationConfig.java +++ b/core/src/main/java/com/wechat/pay/java/core/notification/RSANotificationConfig.java @@ -15,7 +15,11 @@ import java.util.Arrays; import java.util.List; -/** 签名类型为RSA的通知配置参数 */ +/** + * 通知回调配置类 + * + * @deprecated 请使用 RSAAutoCertificateConfig,开发者应尽快迁移,我们将在未来某个时间移除这段废弃的代码。 + */ public final class RSANotificationConfig extends AbstractNotificationConfig { private RSANotificationConfig(CertificateProvider certificateProvider, AeadCipher aeadCipher) { diff --git a/core/src/main/java/com/wechat/pay/java/core/notification/RSAPublicKeyNotificationConfig.java b/core/src/main/java/com/wechat/pay/java/core/notification/RSAPublicKeyNotificationConfig.java new file mode 100644 index 00000000..c667ea81 --- /dev/null +++ b/core/src/main/java/com/wechat/pay/java/core/notification/RSAPublicKeyNotificationConfig.java @@ -0,0 +1,60 @@ +package com.wechat.pay.java.core.notification; + +import static com.wechat.pay.java.core.notification.Constant.AES_CIPHER_ALGORITHM; +import static com.wechat.pay.java.core.notification.Constant.RSA_SIGN_TYPE; +import static java.util.Objects.requireNonNull; + +import com.wechat.pay.java.core.cipher.AeadAesCipher; +import com.wechat.pay.java.core.cipher.AeadCipher; +import com.wechat.pay.java.core.util.PemUtil; +import java.nio.charset.StandardCharsets; +import java.security.PublicKey; + +/** 签名类型为RSA的通知配置参数 */ +public final class RSAPublicKeyNotificationConfig extends AbstractNotificationConfig { + + private RSAPublicKeyNotificationConfig( + PublicKey publicKey, String publicKeyId, AeadCipher aeadCipher) { + super(RSA_SIGN_TYPE, AES_CIPHER_ALGORITHM, publicKey, publicKeyId, aeadCipher); + } + + public static class Builder { + private byte[] apiV3Key; + + private PublicKey publicKey; + private String publicKeyId; + + public Builder publicKey(String publicKey) { + this.publicKey = PemUtil.loadPublicKeyFromString(publicKey); + return this; + } + + public Builder publicKey(PublicKey publicKey) { + this.publicKey = publicKey; + return this; + } + + public Builder publicFromPath(String publicKeyPath) { + this.publicKey = PemUtil.loadPublicKeyFromPath(publicKeyPath); + return this; + } + + public Builder apiV3Key(String apiV3Key) { + this.apiV3Key = apiV3Key.getBytes(StandardCharsets.UTF_8); + return this; + } + + public Builder publicKeyId(String publicKeyId) { + this.publicKeyId = publicKeyId; + return this; + } + + public RSAPublicKeyNotificationConfig build() { + requireNonNull(publicKey); + requireNonNull(publicKeyId); + requireNonNull(apiV3Key); + return new RSAPublicKeyNotificationConfig( + publicKey, requireNonNull(publicKeyId), new AeadAesCipher(requireNonNull(apiV3Key))); + } + } +}