diff --git a/README.md b/README.md
index fa3e6c22..7b0d31f3 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
[](https://www.javadoc.io/doc/com.github.wechatpay-apiv3/wechatpay-java/latest/index.html)
-
+
[](https://sonarcloud.io/summary/overall?id=wechatpay-apiv3_wechatpay-java)
[](https://sonarcloud.io/summary/overall?id=wechatpay-apiv3_wechatpay-java)
[](https://sonarcloud.io/summary/overall?id=wechatpay-apiv3_wechatpay-java)
@@ -36,7 +36,7 @@
在你的 build.gradle 文件中加入如下的依赖
```groovy
-implementation 'com.github.wechatpay-apiv3:wechatpay-java:0.2.12'
+implementation 'com.github.wechatpay-apiv3:wechatpay-java:0.2.14'
```
#### Maven
@@ -47,7 +47,7 @@ implementation 'com.github.wechatpay-apiv3:wechatpay-java:0.2.12'
com.github.wechatpay-apiv3
wechatpay-java
- 0.2.12
+ 0.2.14
```
@@ -233,6 +233,23 @@ Config config =
.build();
```
+## 使用本地平台公钥
+
+如果你的商户可使用微信支付的公钥验证应答和回调的签名,可使用微信支付公钥和公钥ID初始化。
+
+```java
+// 可以根据实际情况使用publicKeyFromPath或publicKey加载公钥
+Config config =
+ new RSAPublicKeyConfig.Builder()
+ .merchantId(merchantId)
+ .privateKeyFromPath(privateKeyPath)
+ .publicKeyFromPath(publicKeyPath)
+ .publicKeyId(publicKeyId)
+ .merchantSerialNumber(merchantSerialNumber)
+ .apiV3Key(apiV3Key)
+ .build();
+```
+
## 回调通知
首先,你需要在你的服务器上创建一个公开的 HTTP 端点,接受来自微信支付的回调通知。
diff --git a/core/src/main/java/com/wechat/pay/java/core/AbstractRSAConfig.java b/core/src/main/java/com/wechat/pay/java/core/AbstractRSAConfig.java
index 7e78092b..0d58f3f9 100644
--- a/core/src/main/java/com/wechat/pay/java/core/AbstractRSAConfig.java
+++ b/core/src/main/java/com/wechat/pay/java/core/AbstractRSAConfig.java
@@ -14,11 +14,13 @@
import com.wechat.pay.java.core.cipher.Signer;
import com.wechat.pay.java.core.util.PemUtil;
import java.security.PrivateKey;
+import java.security.PublicKey;
import java.security.cert.X509Certificate;
/** RSAConfig抽象类 */
public abstract class AbstractRSAConfig implements Config {
+ /** 使用微信支付平台证书验签 */
protected AbstractRSAConfig(
String merchantId,
PrivateKey privateKey,
@@ -28,6 +30,23 @@ protected AbstractRSAConfig(
this.privateKey = privateKey;
this.merchantSerialNumber = merchantSerialNumber;
this.certificateProvider = certificateProvider;
+ this.publicKey = null;
+ this.publicKeyId = null;
+ }
+
+ /** 使用微信支付公钥验签 */
+ protected AbstractRSAConfig(
+ String merchantId,
+ PrivateKey privateKey,
+ String merchantSerialNumber,
+ PublicKey publicKey,
+ String publicKeyId) {
+ this.merchantId = merchantId;
+ this.privateKey = privateKey;
+ this.merchantSerialNumber = merchantSerialNumber;
+ this.certificateProvider = null;
+ this.publicKey = publicKey;
+ this.publicKeyId = publicKeyId;
}
/** 商户号 */
@@ -42,8 +61,17 @@ protected AbstractRSAConfig(
/** 微信支付平台证书Provider */
private final CertificateProvider certificateProvider;
+ /** 微信支付平台公钥 */
+ private final PublicKey publicKey;
+
+ /** 微信支付平台公钥Id */
+ private final String publicKeyId;
+
@Override
public PrivacyEncryptor createEncryptor() {
+ if (publicKey != null) {
+ return new RSAPrivacyEncryptor(publicKey, publicKeyId);
+ }
X509Certificate certificate = certificateProvider.getAvailableCertificate();
return new RSAPrivacyEncryptor(
certificate.getPublicKey(), PemUtil.getSerialNumber(certificate));
@@ -61,6 +89,9 @@ public Credential createCredential() {
@Override
public Validator createValidator() {
+ if (publicKey != null) {
+ return new WechatPay2Validator(new RSAVerifier(publicKey, publicKeyId));
+ }
return new WechatPay2Validator(new RSAVerifier(certificateProvider));
}
diff --git a/core/src/main/java/com/wechat/pay/java/core/RSAPublicKeyConfig.java b/core/src/main/java/com/wechat/pay/java/core/RSAPublicKeyConfig.java
new file mode 100644
index 00000000..cc73aef8
--- /dev/null
+++ b/core/src/main/java/com/wechat/pay/java/core/RSAPublicKeyConfig.java
@@ -0,0 +1,120 @@
+package com.wechat.pay.java.core;
+
+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.cipher.RSAVerifier;
+import com.wechat.pay.java.core.cipher.Verifier;
+import com.wechat.pay.java.core.notification.NotificationConfig;
+import com.wechat.pay.java.core.util.PemUtil;
+import java.nio.charset.StandardCharsets;
+import java.security.PublicKey;
+
+/** 使用微信支付平台公钥的RSA配置类。 每次构造都要求传入平台公钥以及平台公钥id,如果使用平台证书建议用RSAAutoCertificateConfig类 */
+public final class RSAPublicKeyConfig extends AbstractRSAConfig implements NotificationConfig {
+
+ private final PublicKey publicKey;
+ private final AeadCipher aeadCipher;
+ private final String publicKeyId;
+
+ private RSAPublicKeyConfig(Builder builder) {
+ super(
+ builder.merchantId,
+ builder.privateKey,
+ builder.merchantSerialNumber,
+ builder.publicKey,
+ builder.publicKeyId);
+ this.publicKey = builder.publicKey;
+ this.publicKeyId = builder.publicKeyId;
+ this.aeadCipher = new AeadAesCipher(builder.apiV3Key);
+ }
+
+ /**
+ * 获取签名类型
+ *
+ * @return 签名类型
+ */
+ @Override
+ public String getSignType() {
+ return RSA_SIGN_TYPE;
+ }
+
+ /**
+ * 获取认证加解密器类型
+ *
+ * @return 认证加解密器类型
+ */
+ @Override
+ public String getCipherType() {
+ return AES_CIPHER_ALGORITHM;
+ }
+
+ /**
+ * 创建验签器
+ *
+ * @return 验签器
+ */
+ @Override
+ public Verifier createVerifier() {
+ return new RSAVerifier(publicKey, publicKeyId);
+ }
+
+ /**
+ * 创建认证加解密器
+ *
+ * @return 认证加解密器
+ */
+ @Override
+ public AeadCipher createAeadCipher() {
+ return aeadCipher;
+ }
+
+ public static class Builder extends AbstractRSAConfigBuilder {
+ protected byte[] apiV3Key;
+ protected PublicKey publicKey;
+ protected String publicKeyId;
+
+ public Builder apiV3Key(String apiV3Key) {
+ this.apiV3Key = apiV3Key.getBytes(StandardCharsets.UTF_8);
+ return self();
+ }
+
+ public Builder publicKey(String publicKey) {
+ this.publicKey = PemUtil.loadPublicKeyFromString(publicKey);
+ return self();
+ }
+
+ public Builder publicKey(PublicKey publicKey) {
+ this.publicKey = publicKey;
+ return self();
+ }
+
+ public Builder publicKeyFromPath(String publicKeyPath) {
+ this.publicKey = PemUtil.loadPublicKeyFromPath(publicKeyPath);
+ return self();
+ }
+
+ public Builder publicKeyId(String publicKeyId) {
+ this.publicKeyId = publicKeyId;
+ return self();
+ }
+
+ @Override
+ protected Builder self() {
+ return this;
+ }
+
+ public RSAPublicKeyConfig build() {
+ requireNonNull(merchantId);
+ requireNonNull(publicKey);
+ requireNonNull(publicKeyId);
+ requireNonNull(privateKey);
+ requireNonNull(merchantSerialNumber);
+
+ return new RSAPublicKeyConfig(this);
+ }
+ }
+}
diff --git a/core/src/main/java/com/wechat/pay/java/core/certificate/CertificateDownloader.java b/core/src/main/java/com/wechat/pay/java/core/certificate/CertificateDownloader.java
index 65a99ca7..ae5fa35d 100644
--- a/core/src/main/java/com/wechat/pay/java/core/certificate/CertificateDownloader.java
+++ b/core/src/main/java/com/wechat/pay/java/core/certificate/CertificateDownloader.java
@@ -6,13 +6,13 @@
import com.wechat.pay.java.core.certificate.model.DownloadCertificateResponse;
import com.wechat.pay.java.core.certificate.model.EncryptCertificate;
import com.wechat.pay.java.core.cipher.AeadCipher;
+import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.http.Constant;
import com.wechat.pay.java.core.http.HttpClient;
import com.wechat.pay.java.core.http.HttpMethod;
import com.wechat.pay.java.core.http.HttpRequest;
import com.wechat.pay.java.core.http.HttpResponse;
import com.wechat.pay.java.core.http.MediaType;
-import com.wechat.pay.java.core.util.PemUtil;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.Base64;
@@ -77,16 +77,17 @@ public Map download() {
.addHeader(Constant.ACCEPT, " */*")
.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue())
.build();
- HttpResponse httpResponse =
- httpClient.execute(httpRequest, DownloadCertificateResponse.class);
-
- Map downloaded = decryptCertificate(httpResponse);
- validateCertificate(downloaded);
- return downloaded;
- }
-
- private void validateCertificate(Map certificates) {
- certificates.forEach((serialNo, cert) -> certificateHandler.validateCertPath(cert));
+ try {
+ HttpResponse httpResponse =
+ httpClient.execute(httpRequest, DownloadCertificateResponse.class);
+ return decryptCertificate(httpResponse);
+ } catch (ServiceException e) {
+ // 如果证书不存在,可能是切换为平台公钥,该处不报错
+ if (e.getErrorCode().equals("NOT_FOUND")) {
+ return new HashMap<>();
+ }
+ throw e;
+ }
}
/**
@@ -109,7 +110,7 @@ private Map decryptCertificate(
Base64.getDecoder().decode(encryptCertificate.getCiphertext()));
certificate = certificateHandler.generateCertificate(decryptCertificate);
- downloadCertMap.put(PemUtil.getSerialNumber(certificate), certificate);
+ downloadCertMap.put(data.getSerialNo(), certificate);
}
return downloadCertMap;
}
diff --git a/core/src/main/java/com/wechat/pay/java/core/certificate/CertificateHandler.java b/core/src/main/java/com/wechat/pay/java/core/certificate/CertificateHandler.java
index bf43e218..cf12bd10 100644
--- a/core/src/main/java/com/wechat/pay/java/core/certificate/CertificateHandler.java
+++ b/core/src/main/java/com/wechat/pay/java/core/certificate/CertificateHandler.java
@@ -14,7 +14,7 @@ public interface CertificateHandler {
X509Certificate generateCertificate(String certificate);
/**
- * * 验证证书链
+ * * 验证证书链(不推荐验证,如果证书过期不及时更换会导致验证失败,从而影响业务)
*
* @param certificate 微信支付平台证书
* @throws com.wechat.pay.java.core.exception.ValidationException 证书验证失败
diff --git a/core/src/main/java/com/wechat/pay/java/core/certificate/RSACertificateHandler.java b/core/src/main/java/com/wechat/pay/java/core/certificate/RSACertificateHandler.java
index c9ad3818..cecd753f 100644
--- a/core/src/main/java/com/wechat/pay/java/core/certificate/RSACertificateHandler.java
+++ b/core/src/main/java/com/wechat/pay/java/core/certificate/RSACertificateHandler.java
@@ -1,44 +1,10 @@
package com.wechat.pay.java.core.certificate;
-import com.wechat.pay.java.core.exception.ValidationException;
import com.wechat.pay.java.core.util.PemUtil;
import java.security.cert.*;
-import java.util.*;
final class RSACertificateHandler implements CertificateHandler {
- private static final X509Certificate tenpayCACert =
- PemUtil.loadX509FromString(
- "-----BEGIN CERTIFICATE-----\n"
- + "MIIEcDCCA1igAwIBAgIUG9QiDlDbwEsGrTl1SYRsAcPo69IwDQYJKoZIhvcNAQEL\n"
- + "BQAwcDELMAkGA1UEBhMCQ04xEzARBgNVBAoMCmlUcnVzQ2hpbmExHDAaBgNVBAsM\n"
- + "E0NoaW5hIFRydXN0IE5ldHdvcmsxLjAsBgNVBAMMJWlUcnVzQ2hpbmEgQ2xhc3Mg\n"
- + "MiBFbnRlcnByaXNlIENBIC0gRzMwHhcNMTcwODA5MDkxNTU1WhcNMzIwODA5MDkx\n"
- + "NTU1WjBeMQswCQYDVQQGEwJDTjETMBEGA1UEChMKVGVucGF5LmNvbTEdMBsGA1UE\n"
- + "CxMUVGVucGF5LmNvbSBDQSBDZW50ZXIxGzAZBgNVBAMTElRlbnBheS5jb20gUm9v\n"
- + "dCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALvnPD6k39BdPYAH\n"
- + "+6lnWPjuHH+2pcmZUf2E8cNFQFNr+ECRZylYV2iKyItCQt3I2/7VIDZl6aR9TE7n\n"
- + "sZrtSmOXCw635QOrq2yF9LTSDotAhf3ER0+216w3age/VzGcNVQpTf6gRCHCuQIk\n"
- + "8pe/oh06JagGvX0wERa+I6NfuG58ZHQY9d6RqLXKQl0Up95v73HDsG487z8k6jcn\n"
- + "qpGngmHQxdWiWRJugqxNRUD+awv2/DUsqGOffPX4jzJ6rLSJSlQXvuniDYxmaiaD\n"
- + "cK0bUbB5aM+1zMwogoHSYxWj/6B+vgcnHQCUrwGdiQR5+F+yRWzy5bO09IzaFgeO\n"
- + "PNPLPOsCAwEAAaOCARIwggEOMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/\n"
- + "BAQDAgEGMCAGA1UdEQQZMBekFTATMREwDwYDVQQDDAhzd2JlLTI2NjAdBgNVHQ4E\n"
- + "FgQUTFo4GLdm9oHX52HcWnzuL4tui2gwHwYDVR0jBBgwFoAUK1vVxWgI69vN5LA5\n"
- + "MqJf/8dPmEUwRgYDVR0gBD8wPTA7BgoqgRyG7xcBAQECMC0wKwYIKwYBBQUHAgEW\n"
- + "H2h0dHBzOi8vd3d3Lml0cnVzLmNvbS5jbi9jdG5jcHMwPgYDVR0fBDcwNTAzoDGg\n"
- + "L4YtaHR0cDovL3RvcGNhLml0cnVzLmNvbS5jbi9jcmwvaXRydXNjMmNhZzMuY3Js\n"
- + "MA0GCSqGSIb3DQEBCwUAA4IBAQBwZhL/eiOQmMyo1D0IR9mu1DPWl5J3XXhjc4R6\n"
- + "mFgsN/FCeVP9M4U9y2FJH6i5Ha5YCecKGw5pwhA0rjZr/6okWwo22GF+nzI/gQiz\n"
- + "6ugAKs5VjFbeiEb04Ncz4HT8FP1idK3tyCjqCUTkLNt0U3tR7wy26hgOqlT2wCZ9\n"
- + "X4MfT8dUMdt9nCZx4ujN5yZOzaLOCHmzoGDGxgKg91bbu0TG2Yzd2ylhrxxRtFH9\n"
- + "aZ/J1x5UoF7uwhTM8P92DuAldWC1/bX1kciOtQvQEZeAy+9y/1BtFxoBnmDxnqkX\n"
- + "+lirIUYTLDaL7HaLrOLECUlaxZCU/Nkwm3tmqQxtCh+XQBdd\n"
- + "-----END CERTIFICATE-----");
-
- private static final Set trustAnchor =
- new LinkedHashSet<>(Collections.singletonList(new TrustAnchor(tenpayCACert, null)));
-
@Override
public X509Certificate generateCertificate(String certificate) {
return PemUtil.loadX509FromString(certificate);
@@ -46,24 +12,6 @@ public X509Certificate generateCertificate(String certificate) {
@Override
public void validateCertPath(X509Certificate certificate) {
- try {
- PKIXParameters params = new PKIXParameters(trustAnchor);
- params.setRevocationEnabled(false);
-
- List certs = new ArrayList<>();
- certs.add(certificate);
-
- CertificateFactory cf = CertificateFactory.getInstance("X.509");
- CertPath certPath = cf.generateCertPath(certs);
-
- CertPathValidator validator = CertPathValidator.getInstance("PKIX");
- validator.validate(certPath, params);
- } catch (Exception e) {
- throw new ValidationException(
- String.format(
- "certificate[%s] validation failed: %s",
- PemUtil.getSerialNumber(certificate), e.getMessage()),
- e);
- }
+ // 为防止证书过期导致验签失败,从而影响业务,后续不再验证证书信任链
}
}
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 d5f25f14..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
@@ -6,6 +6,7 @@
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.X509Certificate;
@@ -17,7 +18,8 @@ public abstract class AbstractVerifier implements Verifier {
protected static final Logger logger = LoggerFactory.getLogger(AbstractVerifier.class);
protected final CertificateProvider certificateProvider;
-
+ protected final PublicKey publicKey;
+ protected final String publicKeyId;
protected final String algorithmName;
/**
@@ -29,6 +31,41 @@ public abstract class AbstractVerifier implements Verifier {
protected AbstractVerifier(String algorithmName, CertificateProvider certificateProvider) {
this.certificateProvider = requireNonNull(certificateProvider);
this.algorithmName = requireNonNull(algorithmName);
+ this.publicKey = null;
+ this.publicKeyId = null;
+ }
+
+ /**
+ * AbstractVerifier 构造函数
+ *
+ * @param algorithmName 获取Signature对象时指定的算法,例如SHA256withRSA
+ * @param publicKey 验签使用的微信支付平台公钥,非空
+ * @param publicKeyId 验签使用的微信支付平台公钥id
+ */
+ protected AbstractVerifier(String algorithmName, PublicKey publicKey, String publicKeyId) {
+ this.publicKey = requireNonNull(publicKey);
+ this.publicKeyId = publicKeyId;
+ this.algorithmName = requireNonNull(algorithmName);
+ 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) {
@@ -47,12 +84,42 @@ protected boolean verify(X509Certificate certificate, String message, String sig
}
}
+ private boolean verify(String message, String signature) {
+ try {
+ Signature sign = Signature.getInstance(algorithmName);
+ sign.initVerify(publicKey);
+ sign.update(message.getBytes(StandardCharsets.UTF_8));
+ return sign.verify(Base64.getDecoder().decode(signature));
+ } catch (SignatureException e) {
+ return false;
+ } catch (InvalidKeyException e) {
+ throw new IllegalArgumentException("verify uses an illegal publickey.", e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new UnsupportedOperationException(
+ "The current Java environment does not support " + algorithmName, e);
+ }
+ }
+
@Override
public boolean verify(String serialNumber, String message, String signature) {
+ // 如果公钥不为空,使用公钥验签
+ if (publicKey != null) {
+ if (serialNumber.equals(publicKeyId)) {
+ return verify(message, signature);
+ }
+ // 如果证书为空,则说明是传入的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 184822ff..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
@@ -3,6 +3,7 @@
import static com.wechat.pay.java.core.cipher.Constant.SHA256WITHRSA;
import com.wechat.pay.java.core.certificate.CertificateProvider;
+import java.security.PublicKey;
/** RSA验签器 */
public final class RSAVerifier extends AbstractVerifier {
@@ -10,4 +11,12 @@ public final class RSAVerifier extends AbstractVerifier {
public RSAVerifier(CertificateProvider provider) {
super(SHA256WITHRSA, 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/cipher/Verifier.java b/core/src/main/java/com/wechat/pay/java/core/cipher/Verifier.java
index 1e4fbcc2..e7b1b8a3 100644
--- a/core/src/main/java/com/wechat/pay/java/core/cipher/Verifier.java
+++ b/core/src/main/java/com/wechat/pay/java/core/cipher/Verifier.java
@@ -6,7 +6,7 @@ public interface Verifier {
/**
* 验证签名
*
- * @param serialNumber 用于验证签名的证书序列号
+ * @param serialNumber 用于验证签名的证书序列号或者公钥id
* @param message 签名信息
* @param signature 待验证的签名
* @return 是否验证通过
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..cc580bc6
--- /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 publicKeyFromPath(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..2277482b
--- /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 publicKeyFromPath(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)));
+ }
+ }
+}
diff --git a/core/src/main/java/com/wechat/pay/java/core/util/PemUtil.java b/core/src/main/java/com/wechat/pay/java/core/util/PemUtil.java
index 9873a5a6..022bf987 100644
--- a/core/src/main/java/com/wechat/pay/java/core/util/PemUtil.java
+++ b/core/src/main/java/com/wechat/pay/java/core/util/PemUtil.java
@@ -12,11 +12,13 @@
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
+import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/** PEM工具 */
@@ -71,6 +73,28 @@ public static PrivateKey loadPrivateKeyFromString(
}
}
+ /**
+ * 从字符串中加载RSA公钥。
+ *
+ * @param keyString 公钥字符串
+ * @return RSA公钥
+ */
+ public static PublicKey loadPublicKeyFromString(String keyString) {
+ try {
+ keyString =
+ keyString
+ .replace("-----BEGIN PUBLIC KEY-----", "")
+ .replace("-----END PUBLIC KEY-----", "")
+ .replaceAll("\\s+", "");
+ return KeyFactory.getInstance("RSA")
+ .generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(keyString)));
+ } catch (NoSuchAlgorithmException e) {
+ throw new UnsupportedOperationException(e);
+ } catch (InvalidKeySpecException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
/**
* 从文件路径加载RSA私钥
*
@@ -78,7 +102,7 @@ public static PrivateKey loadPrivateKeyFromString(
* @return RSA私钥
*/
public static PrivateKey loadPrivateKeyFromPath(String keyPath) {
- return loadPrivateKeyFromString(readPrivateKeyStringFromPath(keyPath));
+ return loadPrivateKeyFromString(readKeyStringFromPath(keyPath));
}
/**
@@ -91,10 +115,20 @@ public static PrivateKey loadPrivateKeyFromPath(String keyPath) {
*/
public static PrivateKey loadPrivateKeyFromPath(
String keyPath, String algorithm, String provider) {
- return loadPrivateKeyFromString(readPrivateKeyStringFromPath(keyPath), algorithm, provider);
+ return loadPrivateKeyFromString(readKeyStringFromPath(keyPath), algorithm, provider);
+ }
+
+ /**
+ * 从文件路径加载RSA公钥
+ *
+ * @param keyPath 公钥路径
+ * @return RSA公钥
+ */
+ public static PublicKey loadPublicKeyFromPath(String keyPath) {
+ return loadPublicKeyFromString(readKeyStringFromPath(keyPath));
}
- private static String readPrivateKeyStringFromPath(String keyPath) {
+ private static String readKeyStringFromPath(String keyPath) {
try (FileInputStream inputStream = new FileInputStream(keyPath)) {
return IOUtil.toString(inputStream);
} catch (IOException e) {
diff --git a/core/src/test/java/com/wechat/pay/java/core/RSAPublicKeyConfigTest.java b/core/src/test/java/com/wechat/pay/java/core/RSAPublicKeyConfigTest.java
new file mode 100644
index 00000000..b84940d6
--- /dev/null
+++ b/core/src/test/java/com/wechat/pay/java/core/RSAPublicKeyConfigTest.java
@@ -0,0 +1,94 @@
+package com.wechat.pay.java.core;
+
+import static com.wechat.pay.java.core.model.TestConfig.API_V3_KEY;
+import static com.wechat.pay.java.core.model.TestConfig.MERCHANT_CERTIFICATE_SERIAL_NUMBER;
+import static com.wechat.pay.java.core.model.TestConfig.MERCHANT_ID;
+import static com.wechat.pay.java.core.model.TestConfig.MERCHANT_PRIVATE_KEY;
+import static com.wechat.pay.java.core.model.TestConfig.MERCHANT_PRIVATE_KEY_PATH;
+import static com.wechat.pay.java.core.model.TestConfig.MERCHANT_PRIVATE_KEY_STRING;
+import static com.wechat.pay.java.core.model.TestConfig.WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER;
+import static com.wechat.pay.java.core.model.TestConfig.WECHAT_PAY_PUBLIC_KEY;
+import static com.wechat.pay.java.core.model.TestConfig.WECHAT_PAY_PUBLIC_KEY_PATH;
+import static com.wechat.pay.java.core.model.TestConfig.WECHAT_PAY_PUBLIC_KEY_STRING;
+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 org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.wechat.pay.java.core.RSAPublicKeyConfig.Builder;
+import com.wechat.pay.java.core.util.NonceUtil;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class RSAPublicKeyConfigTest implements ConfigTest {
+
+ @ParameterizedTest
+ @MethodSource("BuilderProvider")
+ void testConfigWithBuilderProvider(Builder builder) {
+ RSAPublicKeyConfig config = builder.build();
+ assertNotNull(config.createValidator());
+ assertNotNull(config.createCredential());
+ assertNotNull(config.createEncryptor());
+ assertNotNull(config.createDecryptor());
+ assertNotNull(config.createAeadCipher());
+ assertNotNull(config.createVerifier());
+
+ assertEquals(RSA_SIGN_TYPE, config.getSignType());
+ assertEquals(AES_CIPHER_ALGORITHM, config.getCipherType());
+ }
+
+ static Stream BuilderProvider() {
+ return Stream.of(
+ // from string
+ new Builder()
+ .merchantId("123456")
+ .privateKey(MERCHANT_PRIVATE_KEY_STRING)
+ .merchantSerialNumber(MERCHANT_CERTIFICATE_SERIAL_NUMBER)
+ .publicKey(WECHAT_PAY_PUBLIC_KEY_STRING)
+ .publicKeyId(WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER)
+ .apiV3Key(API_V3_KEY),
+
+ // from path
+ new Builder()
+ .merchantId("223456")
+ .privateKeyFromPath(MERCHANT_PRIVATE_KEY_PATH)
+ .merchantSerialNumber(MERCHANT_CERTIFICATE_SERIAL_NUMBER)
+ .publicKeyFromPath(WECHAT_PAY_PUBLIC_KEY_PATH)
+ .publicKeyId(WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER)
+ .apiV3Key(API_V3_KEY),
+
+ // with publickey
+ new Builder()
+ .merchantId("1123456")
+ .privateKeyFromPath(MERCHANT_PRIVATE_KEY_PATH)
+ .merchantSerialNumber(MERCHANT_CERTIFICATE_SERIAL_NUMBER)
+ .publicKey(WECHAT_PAY_PUBLIC_KEY)
+ .publicKeyId(WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER)
+ .apiV3Key(API_V3_KEY));
+ }
+
+ @Test
+ void testBuildConfigWithoutEnoughParam() {
+ Builder builder =
+ new Builder()
+ .merchantId(MERCHANT_ID)
+ .privateKey(MERCHANT_PRIVATE_KEY)
+ .merchantSerialNumber(MERCHANT_CERTIFICATE_SERIAL_NUMBER);
+ assertThrows(NullPointerException.class, builder::build);
+ }
+
+ @Override
+ public Config createConfig() {
+ return new Builder()
+ .apiV3Key(API_V3_KEY)
+ .merchantId(NonceUtil.createNonce(6))
+ .privateKey(MERCHANT_PRIVATE_KEY)
+ .merchantSerialNumber(MERCHANT_CERTIFICATE_SERIAL_NUMBER)
+ .publicKeyFromPath(WECHAT_PAY_PUBLIC_KEY_PATH)
+ .publicKeyId(WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER)
+ .build();
+ }
+}
diff --git a/core/src/test/java/com/wechat/pay/java/core/certificate/RSACertificateHandlerTest.java b/core/src/test/java/com/wechat/pay/java/core/certificate/RSACertificateHandlerTest.java
deleted file mode 100644
index cdf5ac06..00000000
--- a/core/src/test/java/com/wechat/pay/java/core/certificate/RSACertificateHandlerTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package com.wechat.pay.java.core.certificate;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-import com.wechat.pay.java.core.exception.ValidationException;
-import com.wechat.pay.java.core.util.PemUtil;
-import java.security.cert.X509Certificate;
-import org.junit.jupiter.api.Test;
-
-class RSACertificateHandlerTest {
-
- @Test
- void testValidateCertPath() {
- String validCertificate =
- "-----BEGIN CERTIFICATE-----\n"
- + "MIIEFDCCAvygAwIBAgIUXeoQ71WHfjz2wzJVwufe//fkJ+wwDQYJKoZIhvcNAQEL\n"
- + "BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT\n"
- + "FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg\n"
- + "Q0EwHhcNMjMwOTE5MTUxNTU4WhcNMjgwOTE3MTUxNTU4WjBuMRgwFgYDVQQDDA9U\n"
- + "ZW5wYXkuY29tIHNpZ24xEzARBgNVBAoMClRlbnBheS5jb20xHTAbBgNVBAsMFFRl\n"
- + "bnBheS5jb20gQ0EgQ2VudGVyMQswCQYDVQQGDAJDTjERMA8GA1UEBwwIU2hlblpo\n"
- + "ZW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3BqqoSXo3OhSFZzEE\n"
- + "ZwGRqDy59WvxKywe5XaGJh/ohYkSCn8zzCrTOO4QFu112WfT2iXWXeDHSuMgB9XY\n"
- + "9UtVnwel3q7PUnsMZu1Fa7OKVI+SDtIvTSadrWir9BQ0At2ythSB7mbfkqzsnPnm\n"
- + "yJXQk5GnuT/tqRJiLzGXXLbo8muP+vJJXOVPcqu4yLn85+ToeH/tsJVhGDzg0McV\n"
- + "yyGKomEtvq67uH67cYi3+4NsJaI7hkUa15Rj7s2ccEDE792mD1GR2K+oy1m98BHU\n"
- + "RnKmZWNdlAKjZhR+ZLeYoeZcyoqmI7P4G1Vr/yOssXoBImtbLph+G3naJIUdIrWj\n"
- + "Zff3AgMBAAGjgbkwgbYwCQYDVR0TBAIwADALBgNVHQ8EBAMCA/gwgZsGA1UdHwSB\n"
- + "kzCBkDCBjaCBiqCBh4aBhGh0dHA6Ly9ldmNhLml0cnVzLmNvbS5jbi9wdWJsaWMv\n"
- + "aXRydXNjcmw/Q0E9MUJENDIyMEU1MERCQzA0QjA2QUQzOTc1NDk4NDZDMDFDM0U4\n"
- + "RUJEMiZzZz1IQUNDNDcxQjY1NDIyRTEyQjI3QTlEMzNBODdBRDFDREY1OTI2RTE0\n"
- + "MDM3MTANBgkqhkiG9w0BAQsFAAOCAQEAHnjuI/OubrLb2UjYrUJfmv3OWwIacBzQ\n"
- + "jl+0fpPviUkMEXHnwi4sKd5slGK30IeLocfRU+Tl+De7N4PrdiaAswVuMHSbiqPp\n"
- + "0wEkogVqunMDyXX6eBa0ouKavyhbKP169dbbGqbqTgBFL0LuSD7finNbM23BVQ5E\n"
- + "jep2M4Uqz5uDuZuMMiGYqx1cVkit4w196yoPOSgzaBNlKIAwwHICEgnj18oXIskn\n"
- + "l2nzYY/ub+zw78jrkSLec259/Bby2LmhcJNL3Eo2TS0OI95Z6UbnHuHWP60yvPMs\n"
- + "ck9llwSj1J9zyEWrG9TCtwdr38U8VmwIz6RQ9k6CK3Yq8tw/a5pYQQ==\n"
- + "-----END CERTIFICATE-----";
- X509Certificate certificate = PemUtil.loadX509FromString(validCertificate);
-
- CertificateHandler handler = new RSACertificateHandler();
- assertDoesNotThrow(
- () -> {
- handler.validateCertPath(certificate);
- });
- }
-
- @Test
- void testInvalidCertPath() {
- String validCertificate =
- "-----BEGIN CERTIFICATE-----\n"
- + "MIIEhDCCA2ygAwIBAgIUaZrpo6ACKL/hM/qaSpnjHU0lcbQwDQYJKoZIhvcNAQEF\n"
- + "BQAwRjEbMBkGA1UEAwwSVGVucGF5LmNvbSBVc2VyIENBMRIwEAYDVQQLDAlDQSBD\n"
- + "ZW50ZXIxEzARBgNVBAoMClRlbnBheS5jb20wHhcNMjExMTExMDI1NjM2WhcNMjYx\n"
- + "MTEwMDI1NjM2WjCBlTEYMBYGA1UEAwwPVGVucGF5LmNvbSBzaWduMSUwIwYJKoZI\n"
- + "hvcNAQkBFhZzdXBwb3J0QHN6aXRydXMuY29tLmNuMR0wGwYDVQQLDBRUZW5wYXku\n"
- + "Y29tIENBIENlbnRlcjETMBEGA1UECgwKVGVucGF5LmNvbTERMA8GA1UEBwwIU2hl\n"
- + "blpoZW4xCzAJBgNVBAYTAkNOMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\n"
- + "AQEA7GvNxHf0MdTI0D4sWjL8fHc2HZ50WzuRwRItt1e7YmLpm4lKjlKNmKLRtrJO\n"
- + "vmTXwqOmJNLKz6QHMnR0az9l5pIk8ZkzurexUpydHwTKs/OOD5ZKTtiaiwYy1kSC\n"
- + "GTVzgEJ7Dw3PzqRMdM80+G30h+RwIDQIXMIZS3W0iLa9pxMXZVzD17N6BiBIpDup\n"
- + "M/yErfWyxBd7jq1crvBoHrbyPh5ag4uiV4E0BptmWbn2nOIMq1vuY/LacozhxPcx\n"
- + "nUVVPKLxWwxppvNQpWrJ0VjCxwgjhFU/DxZuqr50uyB0g4OEGAvlJiX7/l625ded\n"
- + "AJmbiYoJWrOohcqauHdqJaIZ+QIDAQABo4IBGDCCARQwCQYDVR0TBAIwADALBgNV\n"
- + "HQ8EBAMCBsAwTwYIKwYBBQUHAQEEQzBBMD8GCCsGAQUFBzAChjNvY3NwLGh0dHA6\n"
- + "Ly9Zb3VyX1NlcnZlcl9OYW1lOlBvcnQvVG9wQ0EvbG9kcF9CYXNlRE4waQYDVR0f\n"
- + "BGIwYDBeoFygWoZYaHR0cDovLzkuMTkuMTYxLjQ6ODA4MC9Ub3BDQS9wdWJsaWMv\n"
- + "aXRydXNjcmw/Q0E9MzlCNDk3QUJDOEFFODg1NzQ1QkY1NjgxRTRGMDNCOEI2NDdG\n"
- + "MjhFQTAfBgNVHSMEGDAWgBROc805tvupF/jOiYapcvSklvPrLjAdBgNVHQ4EFgQU\n"
- + "SGGfum0liSULBRlrThkdsFe3au4wDQYJKoZIhvcNAQEFBQADggEBAHQBdNMRbLRA\n"
- + "TaBnWvk9InV1R7WaO5uIKk3nx41SvBSiKTyKNKGTgro+1PL9aHPHCmnPZ0tQWSXe\n"
- + "b78mFAmwCrz7LW7L9zQa2K+3Fk/X4A3ESlDpS4VY+xvFmujK7XfmzbqzvR5z/tFe\n"
- + "HAMZ/NMqKc6rah9WcKfRn3EQ0DWfufQmpGPTuX5ZPl84TuPZG7MdApn3Vz4xhxGA\n"
- + "5ohYCoCoBK8YNAcLeHNkmatb6GJfS8U+fVcNdDzbnurISYzJvH15yo1iaGNVAqjP\n"
- + "Fwb9+n3hVZV6Jm1N9VIDgSmAaeBLj3Dm+T0og37FmLQ1cz148OJ+ScVJFjZ3I+9v\n"
- + "IQz4B2jCWH4=\n"
- + "-----END CERTIFICATE-----";
- X509Certificate certificate = PemUtil.loadX509FromString(validCertificate);
-
- CertificateHandler handler = new RSACertificateHandler();
- assertThrows(
- ValidationException.class,
- () -> {
- handler.validateCertPath(certificate);
- });
- }
-}
diff --git a/core/src/test/java/com/wechat/pay/java/core/cipher/RSAVerifierTest.java b/core/src/test/java/com/wechat/pay/java/core/cipher/RSAVerifierTest.java
index cabf513c..cdf41d32 100644
--- a/core/src/test/java/com/wechat/pay/java/core/cipher/RSAVerifierTest.java
+++ b/core/src/test/java/com/wechat/pay/java/core/cipher/RSAVerifierTest.java
@@ -2,6 +2,8 @@
import static com.wechat.pay.java.core.model.TestConfig.MERCHANT_CERTIFICATE;
import static com.wechat.pay.java.core.model.TestConfig.MERCHANT_CERTIFICATE_SERIAL_NUMBER;
+import static com.wechat.pay.java.core.model.TestConfig.WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER;
+import static com.wechat.pay.java.core.model.TestConfig.WECHAT_PAY_PUBLIC_KEY;
import com.wechat.pay.java.core.certificate.InMemoryCertificateProvider;
import java.security.cert.X509Certificate;
@@ -13,7 +15,8 @@
public class RSAVerifierTest {
- private static Verifier rsaVerifier;
+ private static Verifier certificateRsaVerifier;
+ private static Verifier publicKeyRsaVerifier;
private static final String MESSAGE = "message";
/** signature为使用RSASigner和测试商户证书私钥对MESSAGE签名得到的结果 */
@@ -29,13 +32,22 @@ public class RSAVerifierTest {
public static void init() {
List list = new Vector<>();
list.add(MERCHANT_CERTIFICATE);
- rsaVerifier = new RSAVerifier(new InMemoryCertificateProvider(list));
+ certificateRsaVerifier = new RSAVerifier(new InMemoryCertificateProvider(list));
+ publicKeyRsaVerifier =
+ new RSAVerifier(WECHAT_PAY_PUBLIC_KEY, WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER);
}
@Test
- public void testVerify() {
+ public void testCertificateVerify() {
Assert.assertTrue(
- rsaVerifier.verify(
+ certificateRsaVerifier.verify(
MERCHANT_CERTIFICATE_SERIAL_NUMBER, MESSAGE, SIGNATURE_RESULT.getSign()));
}
+
+ @Test
+ public void testPublicKeyVerify() {
+ Assert.assertFalse(
+ publicKeyRsaVerifier.verify(
+ WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER, MESSAGE, SIGNATURE_RESULT.getSign()));
+ }
}
diff --git a/core/src/test/java/com/wechat/pay/java/core/model/TestConfig.java b/core/src/test/java/com/wechat/pay/java/core/model/TestConfig.java
index 1024321d..aa360ca3 100644
--- a/core/src/test/java/com/wechat/pay/java/core/model/TestConfig.java
+++ b/core/src/test/java/com/wechat/pay/java/core/model/TestConfig.java
@@ -3,6 +3,7 @@
import com.wechat.pay.java.core.util.IOUtil;
import com.wechat.pay.java.core.util.PemUtil;
import java.security.PrivateKey;
+import java.security.PublicKey;
import java.security.cert.X509Certificate;
public class TestConfig {
@@ -14,12 +15,15 @@ public class TestConfig {
public static final String MERCHANT_CERTIFICATE_STRING;
public static final String WECHAT_PAY_PRIVATE_KEY_PATH;
public static final String WECHAT_PAY_CERTIFICATE_PATH;
+ public static final String WECHAT_PAY_PUBLIC_KEY_PATH;
public static final String WECHAT_PAY_PRIVATE_KEY_STRING;
public static final String WECHAT_PAY_CERTIFICATE_STRING;
+ public static final String WECHAT_PAY_PUBLIC_KEY_STRING;
public static final PrivateKey MERCHANT_PRIVATE_KEY;
public static final X509Certificate MERCHANT_CERTIFICATE;
public static final PrivateKey WECHAT_PAY_PRIVATE_KEY;
public static final X509Certificate WECHAT_PAY_CERTIFICATE;
+ public static final PublicKey WECHAT_PAY_PUBLIC_KEY;
public static final String MERCHANT_CERTIFICATE_SERIAL_NUMBER;
public static final String WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER;
public static final String MERCHANT_ID;
@@ -52,6 +56,7 @@ public class TestConfig {
MERCHANT_CERTIFICATE_PATH = RESOURCES_DIR + "/merchant_certificate.pem";
WECHAT_PAY_PRIVATE_KEY_PATH = RESOURCES_DIR + "/wechat_pay_private_key.pem";
WECHAT_PAY_CERTIFICATE_PATH = RESOURCES_DIR + "/wechat_pay_certificate.pem";
+ WECHAT_PAY_PUBLIC_KEY_PATH = RESOURCES_DIR + "/wechat_pay_public_key.pem";
MERCHANT_PRIVATE_KEY_STRING = IOUtil.loadStringFromPath(MERCHANT_PRIVATE_KEY_PATH);
MERCHANT_PRIVATE_KEY = PemUtil.loadPrivateKeyFromString(MERCHANT_PRIVATE_KEY_STRING);
MERCHANT_CERTIFICATE_STRING = IOUtil.loadStringFromPath(MERCHANT_CERTIFICATE_PATH);
@@ -60,6 +65,8 @@ public class TestConfig {
WECHAT_PAY_PRIVATE_KEY = PemUtil.loadPrivateKeyFromString(WECHAT_PAY_PRIVATE_KEY_STRING);
WECHAT_PAY_CERTIFICATE_STRING = IOUtil.loadStringFromPath(WECHAT_PAY_CERTIFICATE_PATH);
WECHAT_PAY_CERTIFICATE = PemUtil.loadX509FromString(WECHAT_PAY_CERTIFICATE_STRING);
+ WECHAT_PAY_PUBLIC_KEY_STRING = IOUtil.loadStringFromPath(WECHAT_PAY_PUBLIC_KEY_PATH);
+ WECHAT_PAY_PUBLIC_KEY = PemUtil.loadPublicKeyFromString(WECHAT_PAY_PUBLIC_KEY_STRING);
MERCHANT_CERTIFICATE_SERIAL_NUMBER = "5F1C72E2A8931B72A2E13AF8DEE92471EB397115";
WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER = "440024045C4A427599D09BB4E3DE0279F2E813FD";
MERCHANT_ID = "1234567891";
diff --git a/core/src/test/java/com/wechat/pay/java/core/notification/RSACombinedNotificationConfigTest.java b/core/src/test/java/com/wechat/pay/java/core/notification/RSACombinedNotificationConfigTest.java
new file mode 100644
index 00000000..657c07a9
--- /dev/null
+++ b/core/src/test/java/com/wechat/pay/java/core/notification/RSACombinedNotificationConfigTest.java
@@ -0,0 +1,137 @@
+package com.wechat.pay.java.core.notification;
+
+import static com.wechat.pay.java.core.model.TestConfig.API_V3_KEY;
+import static com.wechat.pay.java.core.model.TestConfig.DOWNLOAD_CERTIFICATE_RESPONSE;
+import static com.wechat.pay.java.core.model.TestConfig.MERCHANT_CERTIFICATE_SERIAL_NUMBER;
+import static com.wechat.pay.java.core.model.TestConfig.MERCHANT_ID;
+import static com.wechat.pay.java.core.model.TestConfig.MERCHANT_PRIVATE_KEY;
+import static com.wechat.pay.java.core.model.TestConfig.MERCHANT_PRIVATE_KEY_STRING;
+import static com.wechat.pay.java.core.model.TestConfig.WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER;
+import static com.wechat.pay.java.core.model.TestConfig.WECHAT_PAY_PUBLIC_KEY;
+import static com.wechat.pay.java.core.model.TestConfig.WECHAT_PAY_PUBLIC_KEY_PATH;
+import static com.wechat.pay.java.core.model.TestConfig.WECHAT_PAY_PUBLIC_KEY_STRING;
+import static java.net.HttpURLConnection.HTTP_OK;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.wechat.pay.java.core.auth.Validator;
+import com.wechat.pay.java.core.auth.WechatPay2Credential;
+import com.wechat.pay.java.core.cipher.RSASigner;
+import com.wechat.pay.java.core.http.HttpClient;
+import com.wechat.pay.java.core.http.HttpHeaders;
+import com.wechat.pay.java.core.http.okhttp.OkHttpClientAdapter;
+import com.wechat.pay.java.core.notification.RSACombinedNotificationConfig.Builder;
+import java.util.stream.Stream;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Protocol;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class RSACombinedNotificationConfigTest implements NotificationConfigTest {
+ static HttpClient httpClient;
+
+ @BeforeAll
+ static void initHttpClient() {
+ Validator validator =
+ new Validator() {
+ @Override
+ public boolean validate(HttpHeaders responseHeaders, String body) {
+ return true;
+ }
+ };
+ OkHttpClient okHttpClient =
+ new OkHttpClient.Builder()
+ .addInterceptor(
+ chain ->
+ new Response.Builder()
+ .request(chain.request())
+ .code(HTTP_OK)
+ .header("key", "val")
+ .message("ok")
+ .protocol(Protocol.HTTP_1_1)
+ .body(
+ ResponseBody.create(
+ DOWNLOAD_CERTIFICATE_RESPONSE,
+ MediaType.parse(
+ com.wechat.pay.java.core.http.MediaType.APPLICATION_JSON
+ .getValue())))
+ .build())
+ .build();
+ httpClient =
+ new OkHttpClientAdapter(
+ new WechatPay2Credential(
+ MERCHANT_ID,
+ new RSASigner(MERCHANT_CERTIFICATE_SERIAL_NUMBER, MERCHANT_PRIVATE_KEY)),
+ validator,
+ okHttpClient);
+ }
+
+ @ParameterizedTest
+ @MethodSource("BuilderProvider")
+ void testConfigWithBuilderProvider(Builder builder) {
+ RSACombinedNotificationConfig c = builder.build();
+
+ assertNotNull(c);
+ assertNotNull(c.createAeadCipher());
+ assertNotNull(c.createVerifier());
+ assertNotNull(c.getCipherType());
+ assertNotNull(c.getSignType());
+ }
+
+ static Stream BuilderProvider() {
+ return Stream.of(
+ // from string
+ new Builder()
+ .merchantId(MERCHANT_ID)
+ .privateKey(MERCHANT_PRIVATE_KEY_STRING)
+ .merchantSerialNumber(MERCHANT_CERTIFICATE_SERIAL_NUMBER)
+ .publicKey(WECHAT_PAY_PUBLIC_KEY_STRING)
+ .publicKeyId(WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER)
+ .httpClient(httpClient)
+ .apiV3Key(API_V3_KEY),
+
+ // from path
+ new Builder()
+ .merchantId(MERCHANT_ID)
+ .privateKey(MERCHANT_PRIVATE_KEY_STRING)
+ .merchantSerialNumber(MERCHANT_CERTIFICATE_SERIAL_NUMBER)
+ .publicKeyFromPath(WECHAT_PAY_PUBLIC_KEY_PATH)
+ .publicKeyId(WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER)
+ .httpClient(httpClient)
+ .apiV3Key(API_V3_KEY),
+
+ // with publickey
+ new Builder()
+ .merchantId(MERCHANT_ID)
+ .privateKey(MERCHANT_PRIVATE_KEY_STRING)
+ .merchantSerialNumber(MERCHANT_CERTIFICATE_SERIAL_NUMBER)
+ .publicKey(WECHAT_PAY_PUBLIC_KEY)
+ .publicKeyId(WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER)
+ .httpClient(httpClient)
+ .apiV3Key(API_V3_KEY));
+ }
+
+ @Test
+ void testBuildConfigWithoutEnoughParam() {
+ Builder builder = new Builder().apiV3Key(API_V3_KEY);
+ assertThrows(NullPointerException.class, builder::build);
+ }
+
+ @Override
+ public NotificationConfig buildNotificationConfig() {
+ return new Builder()
+ .merchantId(MERCHANT_ID)
+ .privateKey(MERCHANT_PRIVATE_KEY_STRING)
+ .merchantSerialNumber(MERCHANT_CERTIFICATE_SERIAL_NUMBER)
+ .publicKey(WECHAT_PAY_PUBLIC_KEY)
+ .publicKeyId(WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER)
+ .apiV3Key(API_V3_KEY)
+ .httpClient(httpClient)
+ .build();
+ }
+}
diff --git a/core/src/test/java/com/wechat/pay/java/core/notification/RSAPublicKeyNotificationConfigTest.java b/core/src/test/java/com/wechat/pay/java/core/notification/RSAPublicKeyNotificationConfigTest.java
new file mode 100644
index 00000000..baaf8d5e
--- /dev/null
+++ b/core/src/test/java/com/wechat/pay/java/core/notification/RSAPublicKeyNotificationConfigTest.java
@@ -0,0 +1,66 @@
+package com.wechat.pay.java.core.notification;
+
+import static com.wechat.pay.java.core.model.TestConfig.API_V3_KEY;
+import static com.wechat.pay.java.core.model.TestConfig.WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER;
+import static com.wechat.pay.java.core.model.TestConfig.WECHAT_PAY_PUBLIC_KEY;
+import static com.wechat.pay.java.core.model.TestConfig.WECHAT_PAY_PUBLIC_KEY_PATH;
+import static com.wechat.pay.java.core.model.TestConfig.WECHAT_PAY_PUBLIC_KEY_STRING;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.wechat.pay.java.core.notification.RSAPublicKeyNotificationConfig.Builder;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class RSAPublicKeyNotificationConfigTest implements NotificationConfigTest {
+
+ @ParameterizedTest
+ @MethodSource("BuilderProvider")
+ void testConfigWithBuilderProvider(Builder builder) {
+ RSAPublicKeyNotificationConfig c = builder.build();
+
+ assertNotNull(c);
+ assertNotNull(c.createAeadCipher());
+ assertNotNull(c.createVerifier());
+ assertNotNull(c.getCipherType());
+ assertNotNull(c.getSignType());
+ }
+
+ static Stream BuilderProvider() {
+ return Stream.of(
+ // from string
+ new Builder()
+ .publicKey(WECHAT_PAY_PUBLIC_KEY_STRING)
+ .publicKeyId(WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER)
+ .apiV3Key(API_V3_KEY),
+
+ // from path
+ new Builder()
+ .publicKeyFromPath(WECHAT_PAY_PUBLIC_KEY_PATH)
+ .publicKeyId(WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER)
+ .apiV3Key(API_V3_KEY),
+
+ // with publickey
+ new Builder()
+ .publicKey(WECHAT_PAY_PUBLIC_KEY)
+ .publicKeyId(WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER)
+ .apiV3Key(API_V3_KEY));
+ }
+
+ @Test
+ void testBuildConfigWithoutEnoughParam() {
+ Builder builder = new Builder().apiV3Key(API_V3_KEY);
+ assertThrows(NullPointerException.class, builder::build);
+ }
+
+ @Override
+ public NotificationConfig buildNotificationConfig() {
+ return new Builder()
+ .publicKey(WECHAT_PAY_PUBLIC_KEY)
+ .publicKeyId(WECHAT_PAY_CERTIFICATE_SERIAL_NUMBER)
+ .apiV3Key(API_V3_KEY)
+ .build();
+ }
+}
diff --git a/core/src/test/resources/wechat_pay_public_key.pem b/core/src/test/resources/wechat_pay_public_key.pem
new file mode 100644
index 00000000..b3787dd4
--- /dev/null
+++ b/core/src/test/resources/wechat_pay_public_key.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4qBVumAat3oWPdWVuU24
+fkTVJ7hn6Lvp6m4dCq+d/txH9BDaE2ztUtuAYTb+WeugVoDZKphrQX5tismQq/aN
+/rptyvBX8xCAzsmXFsYXWIsiG9cdU4FKk227akp5Yu3d74TXUjDyijwDampQ2HZT
+u8O6CeyuuYYxHcpW0VkkStaIHXztqfBGV8K9EM67cbSmC+nq+KY+OIo9rHZUkCnc
+LrrBdHf59Ik/KdRsEU1EzzUqPe5dVYDsuiPxWQvQf88SgKu/+/15YFimlpEya9Pd
+kCcTV0xy/OSIisIFHek6j9Ii5jLvqhVwaSa+5zkhrrSq4/b10kXLjh3IPsTDenYt
+7wIDAQAB
+-----END PUBLIC KEY-----
diff --git a/gradle.properties b/gradle.properties
index 3902fe8b..69081712 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,5 +1,5 @@
projectPropGroup=com.github.wechatpay-apiv3
-projectPropVersion=0.2.12
+projectPropVersion=0.2.14
slf4jVersion=1.7.36
junitVersion=4.13.2