diff --git a/.code.yml b/.code.yml index 43fb815b..75e587b9 100644 --- a/.code.yml +++ b/.code.yml @@ -3,7 +3,7 @@ source: # 提供产品代码库中编写的测试代码存放目录或文件名格式,以便后续代码统计环节进行排除等特殊处理 test_source: #用于匹配文件; 匹配方式为正则表达式 - filepath_regex: [ ".*Test.java" ] + filepath_regex: [ ".*/src/test/.*" ] # 提供产品代码库中工具或框架自动生成的且在代码库中的代码,没有可为空。以便后续代码统计环节进行排除等特殊处理。 auto_generate_source: diff --git a/core/build.gradle b/core/build.gradle index bbc9d736..f5426359 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -22,12 +22,14 @@ dependencies { testImplementation "junit:junit:${junitVersion}" testImplementation "org.mockito:mockito-inline:${mockitoInlineVersion}" + testImplementation "com.squareup.okhttp3:mockwebserver:${okhttpVersion}" + testImplementation "org.awaitility:awaitility:${awaitilityVersion}" testImplementation platform("org.junit:junit-bom:${junit5Version}") testImplementation 'org.junit.jupiter:junit-jupiter' - testImplementation "com.squareup.okhttp3:mockwebserver:${okhttpVersion}" - testRuntimeOnly("org.junit.vintage:junit-vintage-engine:${junit5Version}") + testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' + testRuntimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}" } test { @@ -97,4 +99,4 @@ signing { def signingPassword = System.getenv("SIGNING_PASSWORD") useInMemoryPgpKeys(signingKey, signingPassword) sign publishing.publications.maven -} \ No newline at end of file +} 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 969bec50..f3f9d685 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 @@ -1,7 +1,5 @@ package com.wechat.pay.java.core; -import static com.wechat.pay.java.core.cipher.Constant.HEX; - import com.wechat.pay.java.core.auth.Credential; import com.wechat.pay.java.core.auth.Validator; import com.wechat.pay.java.core.auth.WechatPay2Credential; @@ -14,6 +12,7 @@ import com.wechat.pay.java.core.cipher.RSASigner; import com.wechat.pay.java.core.cipher.RSAVerifier; import com.wechat.pay.java.core.cipher.Signer; +import com.wechat.pay.java.core.util.PemUtil; import java.security.PrivateKey; import java.security.cert.X509Certificate; @@ -44,7 +43,7 @@ protected AbstractRSAConfig( public PrivacyEncryptor createEncryptor() { X509Certificate certificate = certificateProvider.getAvailableCertificate(); return new RSAPrivacyEncryptor( - certificate.getPublicKey(), certificate.getSerialNumber().toString(HEX)); + certificate.getPublicKey(), PemUtil.getSerialNumber(certificate)); } @Override diff --git a/core/src/main/java/com/wechat/pay/java/core/certificate/AbstractAutoCertificateProvider.java b/core/src/main/java/com/wechat/pay/java/core/certificate/AbstractAutoCertificateProvider.java index 6706de50..d335cbba 100644 --- a/core/src/main/java/com/wechat/pay/java/core/certificate/AbstractAutoCertificateProvider.java +++ b/core/src/main/java/com/wechat/pay/java/core/certificate/AbstractAutoCertificateProvider.java @@ -1,24 +1,18 @@ package com.wechat.pay.java.core.certificate; -import static com.wechat.pay.java.core.cipher.Constant.HEX; - -import com.wechat.pay.java.core.auth.Validator; -import com.wechat.pay.java.core.auth.WechatPay2Validator; import com.wechat.pay.java.core.certificate.model.Data; 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.ValidationException; 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.JsonResponseBody; 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.ArrayList; import java.util.Base64; import java.util.HashMap; import java.util.List; @@ -30,19 +24,19 @@ /** 自动更新平台证书提供器抽象类 */ public abstract class AbstractAutoCertificateProvider implements CertificateProvider { private static final Logger log = LoggerFactory.getLogger(AbstractAutoCertificateProvider.class); - protected static final int UPDATE_INTERVAL_MINUTE = 60; // 定时更新时间,1小时 + protected static final int UPDATE_INTERVAL_MINUTE = 60; // 定时更新时间,60分钟,即1小时 protected final SafeSingleScheduleExecutor executor = SafeSingleScheduleExecutor.getInstance(); // 安全的单线程定时执行器实例 - protected String requestUrl; // 请求URl + protected String merchantId; // 商户号 protected CertificateHandler certificateHandler; // 证书处理器 protected AeadCipher aeadCipher; // 解密平台证书的aeadCipher; protected HttpClient httpClient; // 下载平台证书的httpClient private final HttpRequest httpRequest; // http请求 - private Validator validator; // 验证器 - private int updateTime; // 自动更新次数 + private int updateCount; // 自动更新次数 + private int succeedCount; // 成功次数 private final Map> certificateMap; // 证书map protected AbstractAutoCertificateProvider( @@ -52,6 +46,24 @@ protected AbstractAutoCertificateProvider( HttpClient httpClient, String merchantId, Map> wechatPayCertificateMap) { + this( + requestUrl, + certificateHandler, + aeadCipher, + httpClient, + merchantId, + wechatPayCertificateMap, + UPDATE_INTERVAL_MINUTE * 60); + } + + protected AbstractAutoCertificateProvider( + String requestUrl, + CertificateHandler certificateHandler, + AeadCipher aeadCipher, + HttpClient httpClient, + String merchantId, + Map> wechatPayCertificateMap, + int updateInterval) { this.merchantId = merchantId; synchronized (AbstractAutoCertificateProvider.class) { if (!wechatPayCertificateMap.containsKey(merchantId)) { @@ -61,7 +73,7 @@ protected AbstractAutoCertificateProvider( "The corresponding provider for the merchant already exists."); } } - this.requestUrl = requestUrl; + this.certificateHandler = certificateHandler; this.aeadCipher = aeadCipher; this.httpClient = httpClient; @@ -73,42 +85,49 @@ protected AbstractAutoCertificateProvider( .addHeader(Constant.ACCEPT, " */*") .addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue()) .build(); + // 下载证书,如果失败会抛出异常 downloadAndUpdate(wechatPayCertificateMap); + Runnable runnable = () -> { log.info( - "Begin update Certificates.merchantId:{},total updates:{}", merchantId, updateTime); - downloadAndUpdate(wechatPayCertificateMap); + "Begin update Certificates.merchantId:{},total updates:{}", merchantId, updateCount); + try { + updateCount++; + downloadAndUpdate(wechatPayCertificateMap); + succeedCount++; + } catch (Exception e) { + // 已经有证书了,失败暂时忽略 + log.error("Download and update WechatPay certificates failed.", e); + } + log.info( - "Finish update Certificates.merchantId:{},total updates:{}", merchantId, updateTime); + "Finish update Certificates.merchantId:{},total updates:{}, succeed updates:{}", + merchantId, + updateCount, + succeedCount); }; - executor.scheduleAtFixedRate( - runnable, UPDATE_INTERVAL_MINUTE, UPDATE_INTERVAL_MINUTE, TimeUnit.MINUTES); + executor.scheduleAtFixedRate(runnable, updateInterval, updateInterval, TimeUnit.SECONDS); } - /** 下载和更新证书 */ + /** + * 下载并更新证书 + * + * @param wechatPayCertificateMap 存放多商户对应证书的Map + */ protected void downloadAndUpdate( Map> wechatPayCertificateMap) { - try { - HttpResponse httpResponse = downloadCertificate(httpClient); - validateCertificate(httpResponse); - updateCertificate(httpResponse, wechatPayCertificateMap); - validator = - new WechatPay2Validator( - certificateHandler.generateVerifier( - new ArrayList<>(wechatPayCertificateMap.get(merchantId).values()))); - updateTime++; - } catch (Exception e) { - if (validator == null) { - throw e; - } - log.error("Download and update WechatPay certificates failed.", e); - } + HttpResponse httpResponse = downloadCertificate(httpClient); + + Map downloaded = decryptCertificate(httpResponse); + validateCertificate(downloaded); + wechatPayCertificateMap.put(merchantId, downloaded); } /** * 下载证书 * + * @param httpClient 下载使用的HttpClient * @return httpResponse */ protected HttpResponse downloadCertificate(HttpClient httpClient) { @@ -117,30 +136,18 @@ protected HttpResponse downloadCertificate(HttpClie return httpResponse; } - /** - * 校验下载证书 - * - * @param httpResponse httpResponse - */ - protected void validateCertificate(HttpResponse httpResponse) { - JsonResponseBody responseBody = (JsonResponseBody) (httpResponse.getBody()); - if (validator != null - && !validator.validate(httpResponse.getHeaders(), responseBody.getBody())) { - throw new ValidationException( - String.format( - "Validate response failed,the WechatPay signature is incorrect.responseHeader[%s]\tresponseBody[%.1024s]", - httpResponse.getHeaders(), httpResponse.getServiceResponse())); - } + protected void validateCertificate(Map certificates) { + certificates.forEach((serialNo, cert) -> certificateHandler.validateCertPath(cert)); } /** - * 更新证书 + * 从应答报文中解密证书 * * @param httpResponse httpResponse + * @return 应答报文解密后,生成X.509证书对象的Map */ - protected void updateCertificate( - HttpResponse httpResponse, - Map> wechatPayCertificateMap) { + protected Map decryptCertificate( + HttpResponse httpResponse) { List dataList = httpResponse.getServiceResponse().getData(); Map downloadCertMap = new HashMap<>(); for (Data data : dataList) { @@ -152,9 +159,9 @@ protected void updateCertificate( encryptCertificate.getNonce().getBytes(StandardCharsets.UTF_8), Base64.getDecoder().decode(encryptCertificate.getCiphertext())); certificate = certificateHandler.generateCertificate(decryptCertificate); - downloadCertMap.put(certificate.getSerialNumber().toString(HEX).toUpperCase(), certificate); + downloadCertMap.put(PemUtil.getSerialNumber(certificate), certificate); } - wechatPayCertificateMap.put(merchantId, downloadCertMap); + return downloadCertMap; } public X509Certificate getAvailableCertificate(Map certificateMap) { 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 a651ca04..bf43e218 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 @@ -1,8 +1,6 @@ package com.wechat.pay.java.core.certificate; -import com.wechat.pay.java.core.cipher.Verifier; import java.security.cert.X509Certificate; -import java.util.List; /** 证书处理器 */ public interface CertificateHandler { @@ -16,10 +14,10 @@ public interface CertificateHandler { X509Certificate generateCertificate(String certificate); /** - * 使用微信支付平台证书生成Verifier + * * 验证证书链 * - * @param certificateList 微信支付平台证书列表 - * @return verifier + * @param certificate 微信支付平台证书 + * @throws com.wechat.pay.java.core.exception.ValidationException 证书验证失败 */ - Verifier generateVerifier(List certificateList); + void validateCertPath(X509Certificate certificate); } 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 29e89f21..c9ad3818 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,12 +1,43 @@ package com.wechat.pay.java.core.certificate; -import com.wechat.pay.java.core.cipher.RSAVerifier; -import com.wechat.pay.java.core.cipher.Verifier; +import com.wechat.pay.java.core.exception.ValidationException; import com.wechat.pay.java.core.util.PemUtil; -import java.security.cert.X509Certificate; -import java.util.List; +import java.security.cert.*; +import java.util.*; -class RSACertificateHandler implements CertificateHandler { +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) { @@ -14,7 +45,25 @@ public X509Certificate generateCertificate(String certificate) { } @Override - public Verifier generateVerifier(List certificateList) { - return new RSAVerifier(new InMemoryCertificateProvider(certificateList)); + 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/util/PemUtil.java b/core/src/main/java/com/wechat/pay/java/core/util/PemUtil.java index c1e0f344..9873a5a6 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 @@ -1,5 +1,7 @@ package com.wechat.pay.java.core.util; +import static com.wechat.pay.java.core.cipher.Constant.HEX; + import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.IOException; @@ -190,4 +192,8 @@ public static X509Certificate loadX509FromString(String certificateString, Strin throw new UncheckedIOException(e); } } + + public static String getSerialNumber(X509Certificate certificate) { + return certificate.getSerialNumber().toString(HEX).toUpperCase(); + } } diff --git a/core/src/test/java/com/wechat/pay/java/core/RSAAutoCertificateConfigTest.java b/core/src/test/java/com/wechat/pay/java/core/RSAAutoCertificateConfigTest.java index 3a1168de..13d186d0 100644 --- a/core/src/test/java/com/wechat/pay/java/core/RSAAutoCertificateConfigTest.java +++ b/core/src/test/java/com/wechat/pay/java/core/RSAAutoCertificateConfigTest.java @@ -1,11 +1,6 @@ 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.*; 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.net.HttpURLConnection.HTTP_OK; @@ -14,19 +9,14 @@ import com.wechat.pay.java.core.RSAAutoCertificateConfig.Builder; import com.wechat.pay.java.core.auth.Validator; import com.wechat.pay.java.core.auth.WechatPay2Credential; -import com.wechat.pay.java.core.certificate.model.Data; -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.RSASigner; import com.wechat.pay.java.core.http.DefaultHttpClientBuilder; 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.util.GsonUtil; import com.wechat.pay.java.core.util.NonceUtil; import java.net.InetSocketAddress; import java.net.Proxy; -import java.util.ArrayList; import java.util.stream.Stream; import okhttp3.MediaType; import okhttp3.OkHttpClient; @@ -56,36 +46,20 @@ public boolean validate(HttpHeaders responseHeaders, String body) { okHttpClient = new OkHttpClient.Builder() .addInterceptor( - chain -> { - DownloadCertificateResponse downloadResp = new DownloadCertificateResponse(); - ArrayList dataList = new ArrayList<>(); - Data data = new Data(); - data.setEffectiveTime("2022-06-08T10:34:56+08:00"); - data.setExpireTime("2025-12-08T10:34:56+08:00"); - data.setSerialNo("440024045C4A427599D09BB4E3DE0279F2E813FD"); - EncryptCertificate encryptCertificate = new EncryptCertificate(); - encryptCertificate.setAlgorithm("AEAD_AES_256_GCM"); - encryptCertificate.setAssociatedData("certificate"); - encryptCertificate.setNonce("61f9c719728a"); - encryptCertificate.setCiphertext( - "9kRsp3GhB0VA31gv0QxUssTdPZn97GSnyMEBBk0S52lfnzSgo8v17lNbndISX+a970CjAPnx8lFhg/WKzv7v0uUno8W09mtj0l9ERsHkVnTkTWJ5dYUw+AADMVSteWRMuguLbmiwlohZRi1uMWEHbGwucy3dFYY3AF6EuCfklobSOHf6p+jvKjbN/+V6SYNE7ZYMFcHyL/IGTuk56Od9zR4CHemg93FCzxDIlymdfhadZg2Sj+X9T3L/opHtwjhsw/vg/IZEuaVCTmj6M9a7X9EN126CR88JuasauDKcVobiQyTjFlHp1iOSDU+7QxJGtpHduR06uNlgJr8P8zqACV76o4fHWACTWqYyh8CQUMxXL7GlJMQilwXx+4AOylFPPnJ7b1fvGIRA9i3ugc6ZR1qop9qeNhW0R+SAPxfWgy1xysVISe/dKLVY/UwxN/y83s7do6Eud+rm+qk7uVaaOY23681pxdq3WsnoaGsUxdTHSn0c9P8Zw7JvVvlYlN/gY49MX6JXPCI8PV4pWR8lWA4I9nxIzUDZOyZN9O3ZVQLiDJDJDFrGSwyUUEZDYmlv62myrqSoShkf8qEBa8J+nELTW7CHf/TQmnbs6DKchVOEpKMRotIBwULvoYU+Y/SoIAUbWWXjDh1m6lh40tLFsqbD+CKTlOWs17l18zIEyDqG7RP9QUxapdghNCiIvwzbBPpPvc1mZG22K3GVg3pYPeMu98AkCrOVWI+Xn7x7OCUMPp+9X0RUQo3vChD5mT5Ldg31Zjwyxhg7JSVRYG+BdS0Ova1T23Y5yBIVZFvhCTvJM2lG+uvgHE6Va25BZm6gxGAgYCgCrhQFCr6+Wd6QsM+qvsddh5jBaWOI5sO/Gt0DuAtn7WKiE7QaLsDe979+EIKYbrzaRNz3LBbSUECEjrfXmnhHuMLWInzFzwaZf7nkStwYeOLqukwV2UFGMqYvD7OYM0rOTQey7lGqId1XyVECA2XV/J4qOxGkMhYjVzLcvWltsgEqchWUrWfD9ZfA2Zcm5Utw36mBSEvDFrixkomVAS0Utj9NfUIX1e5BlZgK+8KCR+/PK5qo8aAZrXb907wpwXBZ5tEtikYcfg8b4s7K47BDeDvSf/padf6/x8vtsYGwy0hSTr69I5Uto4AoBjT0EH8h38CVVRbyDuW1qfh3vXNNd+By4vggucpzRYGhAOCdvLhKqn1qfa0ue9/wjSCoUh7/eGgBhmrvx/4mEsBbOvqJYP4vkFh8U7XqGE1myBKO2TtArVuSVjV511d+hBdPoLf2k6Z0L/FgybodrqmsqaZvCv7KgW/v7jpSwT/Ema/ZpK1WT6GxdyOomVA1xrXWiWg+wetaRnKvncgJvPAaKrv2B4JOGYhmSYC6A28kwU4BMm7CFnZuTo0PrFHl5k3ZDMAlxuKX5CHK5v52ERU2rpywavb1SlvGZXgXI0HW+dN7Mvjvnc2eWUrWLj7OZK4jPWuGXLkHvgB0pKU2lfjQn0Cyo2UbErwQiHgM8aZcKsTIl9sT/eX6OKHFSPQA/GfCM5qzPIHzBfPEbAkliQeIUXwB3gW9dd79DS+PXjt62oklcO6dPSNIlIV3kvtDBSpdaHKMll9C2VUPA+CYi/EldWDD/RxCCtK5sGf4niiHjO59F/oKGIi0kZB234hIpWSVq1tPNy58+7IPWOFKCzsK41Y18maD+M7higw+dkTeanvmP8Y/JWp/E7wiWpLmJdFV21cjrtjPIDUrg34gkJ/BaESaF+hTB/9gMCLFYidHxHBWGHqWPHM="); - data.setEncryptCertificate(encryptCertificate); - dataList.add(data); - downloadResp.setData(dataList); - return new Response.Builder() - .request(chain.request()) - .code(HTTP_OK) - .header("key", "val") - .message("ok") - .protocol(Protocol.HTTP_1_1) - .body( - ResponseBody.create( - GsonUtil.getGson().toJson(downloadResp), - MediaType.parse( - com.wechat.pay.java.core.http.MediaType.APPLICATION_JSON - .getValue()))) - .build(); - }) + 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( diff --git a/core/src/test/java/com/wechat/pay/java/core/certificate/AbstractAutoCertificateProviderTest.java b/core/src/test/java/com/wechat/pay/java/core/certificate/AbstractAutoCertificateProviderTest.java new file mode 100644 index 00000000..a3fd1bc8 --- /dev/null +++ b/core/src/test/java/com/wechat/pay/java/core/certificate/AbstractAutoCertificateProviderTest.java @@ -0,0 +1,359 @@ +package com.wechat.pay.java.core.certificate; + +import static org.awaitility.Awaitility.await; +import static org.awaitility.Awaitility.with; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.wechat.pay.java.core.auth.Credential; +import com.wechat.pay.java.core.auth.Validator; +import com.wechat.pay.java.core.cipher.AeadCipher; +import com.wechat.pay.java.core.http.DefaultHttpClientBuilder; +import com.wechat.pay.java.core.http.HttpClient; +import com.wechat.pay.java.core.http.HttpHeaders; +import com.wechat.pay.java.core.util.PemUtil; +import java.net.URI; +import java.security.cert.X509Certificate; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class AbstractAutoCertificateProviderTest { + + // 因为每个任务都在后台运行,所以需要mock服务一直存在 + static MockWebServer server = new MockWebServer(); + + static BlockingQueue newUpdateQueue() { + BlockingQueue queue = new LinkedBlockingQueue<>(); + queue.add( + new MockResponse() + .addHeader("Content-Type", "application/json; charset=utf-8") + .setBody( + "{\n" + + " \"data\": [\n" + + " {\n" + + " \"serial_no\": \"39FACCC173F3485C76C61FD5C0D662AE4A838AA9\",\n" + + " \"effective_time \": \"2018-06-08T10:34:56+08:00\",\n" + + " \"expire_time \": \"2018-12-08T10:34:56+08:00\",\n" + + " \"encrypt_certificate\": {\n" + + " \"algorithm\": \"AEAD_AES_256_GCM\",\n" + + " \"nonce\": \"61f9c719728a\",\n" + + " \"associated_data\": \"certificate\",\n" + + " \"ciphertext\": " + + "\"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVoRENDQTJ5Z0F3SUJBZ0lVT2ZyTXdYUHpTRngyeGgvVndOWmlya3FEaXFrd0RRWUpLb1pJaHZjTkFRRUYKQlFBd1JqRWJNQmtHQTFVRUF3d1NWR1Z1Y0dGNUxtTnZiU0JWYzJWeUlFTkJNUkl3RUFZRFZRUUxEQWxEUVNCRApaVzUwWlhJeEV6QVJCZ05WQkFvTUNsUmxibkJoZVM1amIyMHdIaGNOTWpFeE1URXhNREkxT0RRMldoY05Nall4Ck1URXdNREkxT0RRMldqQ0JsVEVZTUJZR0ExVUVBd3dQVkdWdWNHRjVMbU52YlNCemFXZHVNU1V3SXdZSktvWkkKaHZjTkFRa0JGaFp6ZFhCd2IzSjBRSE42YVhSeWRYTXVZMjl0TG1OdU1SMHdHd1lEVlFRTERCUlVaVzV3WVhrdQpZMjl0SUVOQklFTmxiblJsY2pFVE1CRUdBMVVFQ2d3S1ZHVnVjR0Y1TG1OdmJURVJNQThHQTFVRUJ3d0lVMmhsCmJscG9aVzR4Q3pBSkJnTlZCQVlUQWtOT01JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0MKQVFFQXY4Rkhvc0Z1QTJodWxvVHplUHRBV0dkWnE3Q2o0RDgzeE45UURManhGTktuR2h6enZJMVpZL2J1bVVrVwpFRzAxc1Z5dEdaRFlhWUd4ZlJlcTdTSVN0SUlWQ0ZORkdwMGYzMDkwQmp5TDliNlZka0NsVWE5Vk8rU0VTZnJqCldhVmgxczBSb1NHMlAwd3FKYlMrQ240REZrM3JLN1NWZEJnQjJJVTBTY1FBOERBL0J0K2hTK2xscDhld0RwRHkKbkRIVUNaV0xGY29TNWxDa1BkNWxneE9qNzV0K3pDSHhXMkFKMEFPS0FHTVZ5MGRVc3JGMzA4KzZBTHJuR0JLRQp3ckNHc1ZWOGp0eEtEMFZKa05LSXVrUDh4S0Z2emRuMDRIM3hFYTE2YUl0ZmRHemt2WFg4S1FVeVBYWDY1Z28rCk50MmYxVGNRZzVNdE9CRUwxNi92RTlmb3dRSURBUUFCbzRJQkdEQ0NBUlF3Q1FZRFZSMFRCQUl3QURBTEJnTlYKSFE4RUJBTUNCc0F3VHdZSUt3WUJCUVVIQVFFRVF6QkJNRDhHQ0NzR0FRVUZCekFDaGpOdlkzTndMR2gwZEhBNgpMeTlaYjNWeVgxTmxjblpsY2w5T1lXMWxPbEJ2Y25RdlZHOXdRMEV2Ykc5a2NGOUNZWE5sUkU0d2FRWURWUjBmCkJHSXdZREJlb0Z5Z1dvWllhSFIwY0Rvdkx6a3VNVGt1TVRZeExqUTZPREE0TUM5VWIzQkRRUzl3ZFdKc2FXTXYKYVhSeWRYTmpjbXcvUTBFOU16bENORGszUVVKRE9FRkZPRGcxTnpRMVFrWTFOamd4UlRSR01ETkNPRUkyTkRkRwpNamhGUVRBZkJnTlZIU01FR0RBV2dCUk9jODA1dHZ1cEYvak9pWWFwY3ZTa2x2UHJMakFkQmdOVkhRNEVGZ1FVCkdUTEU2YnhudG5obnBobFBOd1FBNkw3SGpMc3dEUVlKS29aSWh2Y05BUUVGQlFBRGdnRUJBR3ovWnRVTlZKWEYKUVZ1TGU5MFFGUCtkNmRQUjhaYmJscTVMcXRMYW1xcmFmclF5R0ZOZDZxdEdrY293YXVJM0xKWldPeitTZ0J6YwpjS2dyOXdYb0g1azNSL0pmYVhYU2F3Q3d1NjZIL3grbFlhcU45TXREYkVoVi9Sa0U3dTRwb2I4ZkJSOC9FNTllCnNOQnFmMVdHODhTcUZPbDZ3VDY5ajR0cHBjRVlldjFISCtaelpScDN4U3NJV3dSMHNtV21IOE9BMWZLSFYramsKY21IaFJwNDFTeTQ4bUREdkRuRGVsTU5UaU1wUjk2a1JONmNLWG1LeXZadkNhTkNUOXpyNExVdStMUHdmTFdiWgpBQzVtL1dHclBKakhWMXAyUCt4NmRYaEJybitWV2tyYzZYZTVqeXFmdnpBRVBvN1lsQVFXamh2T2I5d3U4TzNTCjNJVnZrTzNiSkhZPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0t\"\n" + + " }\n" + + " }\n" + + " ]\n" + + "}")); + // 删除证书A,新增证书B + queue.add( + new MockResponse() + .addHeader("Content-Type", "application/json; charset=utf-8") + .setBody( + "{\n" + + " \"data\": [\n" + + " {\n" + + " \"serial_no\": \"699AE9A3A00228BFE133FA9A4A99E31D4D2571B4\",\n" + + " \"effective_time \": \"2018-06-08T10:34:56+08:00\",\n" + + " \"expire_time \": \"2018-12-08T10:34:56+08:00\",\n" + + " \"encrypt_certificate\": {\n" + + " \"algorithm\": \"AEAD_AES_256_GCM\",\n" + + " \"nonce\": \"61f9c719728a\",\n" + + " \"associated_data\": \"certificate\",\n" + + " \"ciphertext\": " + + "\"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVoRENDQTJ5Z0F3SUJBZ0lVYVpycG82QUNLTC9oTS9xYVNwbmpIVTBsY2JRd0RRWUpLb1pJaHZjTkFRRUYKQlFBd1JqRWJNQmtHQTFVRUF3d1NWR1Z1Y0dGNUxtTnZiU0JWYzJWeUlFTkJNUkl3RUFZRFZRUUxEQWxEUVNCRApaVzUwWlhJeEV6QVJCZ05WQkFvTUNsUmxibkJoZVM1amIyMHdIaGNOTWpFeE1URXhNREkxTmpNMldoY05Nall4Ck1URXdNREkxTmpNMldqQ0JsVEVZTUJZR0ExVUVBd3dQVkdWdWNHRjVMbU52YlNCemFXZHVNU1V3SXdZSktvWkkKaHZjTkFRa0JGaFp6ZFhCd2IzSjBRSE42YVhSeWRYTXVZMjl0TG1OdU1SMHdHd1lEVlFRTERCUlVaVzV3WVhrdQpZMjl0SUVOQklFTmxiblJsY2pFVE1CRUdBMVVFQ2d3S1ZHVnVjR0Y1TG1OdmJURVJNQThHQTFVRUJ3d0lVMmhsCmJscG9aVzR4Q3pBSkJnTlZCQVlUQWtOT01JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0MKQVFFQTdHdk54SGYwTWRUSTBENHNXakw4ZkhjMkhaNTBXenVSd1JJdHQxZTdZbUxwbTRsS2psS05tS0xSdHJKTwp2bVRYd3FPbUpOTEt6NlFITW5SMGF6OWw1cElrOFprenVyZXhVcHlkSHdUS3MvT09ENVpLVHRpYWl3WXkxa1NDCkdUVnpnRUo3RHczUHpxUk1kTTgwK0czMGgrUndJRFFJWE1JWlMzVzBpTGE5cHhNWFpWekQxN042QmlCSXBEdXAKTS95RXJmV3l4QmQ3anExY3J2Qm9IcmJ5UGg1YWc0dWlWNEUwQnB0bVdibjJuT0lNcTF2dVkvTGFjb3poeFBjeApuVVZWUEtMeFd3eHBwdk5RcFdySjBWakN4d2dqaEZVL0R4WnVxcjUwdXlCMGc0T0VHQXZsSmlYNy9sNjI1ZGVkCkFKbWJpWW9KV3JPb2hjcWF1SGRxSmFJWitRSURBUUFCbzRJQkdEQ0NBUlF3Q1FZRFZSMFRCQUl3QURBTEJnTlYKSFE4RUJBTUNCc0F3VHdZSUt3WUJCUVVIQVFFRVF6QkJNRDhHQ0NzR0FRVUZCekFDaGpOdlkzTndMR2gwZEhBNgpMeTlaYjNWeVgxTmxjblpsY2w5T1lXMWxPbEJ2Y25RdlZHOXdRMEV2Ykc5a2NGOUNZWE5sUkU0d2FRWURWUjBmCkJHSXdZREJlb0Z5Z1dvWllhSFIwY0Rvdkx6a3VNVGt1TVRZeExqUTZPREE0TUM5VWIzQkRRUzl3ZFdKc2FXTXYKYVhSeWRYTmpjbXcvUTBFOU16bENORGszUVVKRE9FRkZPRGcxTnpRMVFrWTFOamd4UlRSR01ETkNPRUkyTkRkRwpNamhGUVRBZkJnTlZIU01FR0RBV2dCUk9jODA1dHZ1cEYvak9pWWFwY3ZTa2x2UHJMakFkQmdOVkhRNEVGZ1FVClNHR2Z1bTBsaVNVTEJSbHJUaGtkc0ZlM2F1NHdEUVlKS29aSWh2Y05BUUVGQlFBRGdnRUJBSFFCZE5NUmJMUkEKVGFCbld2azlJblYxUjdXYU81dUlLazNueDQxU3ZCU2lLVHlLTktHVGdybysxUEw5YUhQSENtblBaMHRRV1NYZQpiNzhtRkFtd0NyejdMVzdMOXpRYTJLKzNGay9YNEEzRVNsRHBTNFZZK3h2Rm11aks3WGZtemJxenZSNXovdEZlCkhBTVovTk1xS2M2cmFoOVdjS2ZSbjNFUTBEV2Z1ZlFtcEdQVHVYNVpQbDg0VHVQWkc3TWRBcG4zVno0eGh4R0EKNW9oWUNvQ29CSzhZTkFjTGVITmttYXRiNkdKZlM4VStmVmNOZER6Ym51cklTWXpKdkgxNXlvMWlhR05WQXFqUApGd2I5K24zaFZaVjZKbTFOOVZJRGdTbUFhZUJMajNEbStUMG9nMzdGbUxRMWN6MTQ4T0orU2NWSkZqWjNJKzl2CklRejRCMmpDV0g0PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0t\"\n" + + " }\n" + + " }\n" + + " ]\n" + + "}")); + return queue; + } + + static BlockingQueue newUpdateWith500Queue() { + BlockingQueue queue = new LinkedBlockingQueue<>(); + queue.add( + new MockResponse() + .addHeader("Content-Type", "application/json; charset=utf-8") + .setBody( + "{\n" + + " \"data\": [\n" + + " {\n" + + " \"serial_no\": \"39FACCC173F3485C76C61FD5C0D662AE4A838AA9\",\n" + + " \"effective_time \": \"2018-06-08T10:34:56+08:00\",\n" + + " \"expire_time \": \"2018-12-08T10:34:56+08:00\",\n" + + " \"encrypt_certificate\": {\n" + + " \"algorithm\": \"AEAD_AES_256_GCM\",\n" + + " \"nonce\": \"61f9c719728a\",\n" + + " \"associated_data\": \"certificate\",\n" + + " \"ciphertext\": " + + "\"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVoRENDQTJ5Z0F3SUJBZ0lVT2ZyTXdYUHpTRngyeGgvVndOWmlya3FEaXFrd0RRWUpLb1pJaHZjTkFRRUYKQlFBd1JqRWJNQmtHQTFVRUF3d1NWR1Z1Y0dGNUxtTnZiU0JWYzJWeUlFTkJNUkl3RUFZRFZRUUxEQWxEUVNCRApaVzUwWlhJeEV6QVJCZ05WQkFvTUNsUmxibkJoZVM1amIyMHdIaGNOTWpFeE1URXhNREkxT0RRMldoY05Nall4Ck1URXdNREkxT0RRMldqQ0JsVEVZTUJZR0ExVUVBd3dQVkdWdWNHRjVMbU52YlNCemFXZHVNU1V3SXdZSktvWkkKaHZjTkFRa0JGaFp6ZFhCd2IzSjBRSE42YVhSeWRYTXVZMjl0TG1OdU1SMHdHd1lEVlFRTERCUlVaVzV3WVhrdQpZMjl0SUVOQklFTmxiblJsY2pFVE1CRUdBMVVFQ2d3S1ZHVnVjR0Y1TG1OdmJURVJNQThHQTFVRUJ3d0lVMmhsCmJscG9aVzR4Q3pBSkJnTlZCQVlUQWtOT01JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0MKQVFFQXY4Rkhvc0Z1QTJodWxvVHplUHRBV0dkWnE3Q2o0RDgzeE45UURManhGTktuR2h6enZJMVpZL2J1bVVrVwpFRzAxc1Z5dEdaRFlhWUd4ZlJlcTdTSVN0SUlWQ0ZORkdwMGYzMDkwQmp5TDliNlZka0NsVWE5Vk8rU0VTZnJqCldhVmgxczBSb1NHMlAwd3FKYlMrQ240REZrM3JLN1NWZEJnQjJJVTBTY1FBOERBL0J0K2hTK2xscDhld0RwRHkKbkRIVUNaV0xGY29TNWxDa1BkNWxneE9qNzV0K3pDSHhXMkFKMEFPS0FHTVZ5MGRVc3JGMzA4KzZBTHJuR0JLRQp3ckNHc1ZWOGp0eEtEMFZKa05LSXVrUDh4S0Z2emRuMDRIM3hFYTE2YUl0ZmRHemt2WFg4S1FVeVBYWDY1Z28rCk50MmYxVGNRZzVNdE9CRUwxNi92RTlmb3dRSURBUUFCbzRJQkdEQ0NBUlF3Q1FZRFZSMFRCQUl3QURBTEJnTlYKSFE4RUJBTUNCc0F3VHdZSUt3WUJCUVVIQVFFRVF6QkJNRDhHQ0NzR0FRVUZCekFDaGpOdlkzTndMR2gwZEhBNgpMeTlaYjNWeVgxTmxjblpsY2w5T1lXMWxPbEJ2Y25RdlZHOXdRMEV2Ykc5a2NGOUNZWE5sUkU0d2FRWURWUjBmCkJHSXdZREJlb0Z5Z1dvWllhSFIwY0Rvdkx6a3VNVGt1TVRZeExqUTZPREE0TUM5VWIzQkRRUzl3ZFdKc2FXTXYKYVhSeWRYTmpjbXcvUTBFOU16bENORGszUVVKRE9FRkZPRGcxTnpRMVFrWTFOamd4UlRSR01ETkNPRUkyTkRkRwpNamhGUVRBZkJnTlZIU01FR0RBV2dCUk9jODA1dHZ1cEYvak9pWWFwY3ZTa2x2UHJMakFkQmdOVkhRNEVGZ1FVCkdUTEU2YnhudG5obnBobFBOd1FBNkw3SGpMc3dEUVlKS29aSWh2Y05BUUVGQlFBRGdnRUJBR3ovWnRVTlZKWEYKUVZ1TGU5MFFGUCtkNmRQUjhaYmJscTVMcXRMYW1xcmFmclF5R0ZOZDZxdEdrY293YXVJM0xKWldPeitTZ0J6YwpjS2dyOXdYb0g1azNSL0pmYVhYU2F3Q3d1NjZIL3grbFlhcU45TXREYkVoVi9Sa0U3dTRwb2I4ZkJSOC9FNTllCnNOQnFmMVdHODhTcUZPbDZ3VDY5ajR0cHBjRVlldjFISCtaelpScDN4U3NJV3dSMHNtV21IOE9BMWZLSFYramsKY21IaFJwNDFTeTQ4bUREdkRuRGVsTU5UaU1wUjk2a1JONmNLWG1LeXZadkNhTkNUOXpyNExVdStMUHdmTFdiWgpBQzVtL1dHclBKakhWMXAyUCt4NmRYaEJybitWV2tyYzZYZTVqeXFmdnpBRVBvN1lsQVFXamh2T2I5d3U4TzNTCjNJVnZrTzNiSkhZPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0t\"\n" + + " }\n" + + " }\n" + + " ]\n" + + "}")); + queue.add(new MockResponse().setResponseCode(500)); + return queue; + } + + static BlockingQueue newUpdateWithBadBodyQueue() { + BlockingQueue queue = new LinkedBlockingQueue<>(); + queue.add( + new MockResponse() + .addHeader("Content-Type", "application/json; charset=utf-8") + .setBody( + "{\n" + + " \"data\": [\n" + + " {\n" + + " \"serial_no\": \"39FACCC173F3485C76C61FD5C0D662AE4A838AA9\",\n" + + " \"effective_time \": \"2018-06-08T10:34:56+08:00\",\n" + + " \"expire_time \": \"2018-12-08T10:34:56+08:00\",\n" + + " \"encrypt_certificate\": {\n" + + " \"algorithm\": \"AEAD_AES_256_GCM\",\n" + + " \"nonce\": \"61f9c719728a\",\n" + + " \"associated_data\": \"certificate\",\n" + + " \"ciphertext\": " + + "\"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVoRENDQTJ5Z0F3SUJBZ0lVT2ZyTXdYUHpTRngyeGgvVndOWmlya3FEaXFrd0RRWUpLb1pJaHZjTkFRRUYKQlFBd1JqRWJNQmtHQTFVRUF3d1NWR1Z1Y0dGNUxtTnZiU0JWYzJWeUlFTkJNUkl3RUFZRFZRUUxEQWxEUVNCRApaVzUwWlhJeEV6QVJCZ05WQkFvTUNsUmxibkJoZVM1amIyMHdIaGNOTWpFeE1URXhNREkxT0RRMldoY05Nall4Ck1URXdNREkxT0RRMldqQ0JsVEVZTUJZR0ExVUVBd3dQVkdWdWNHRjVMbU52YlNCemFXZHVNU1V3SXdZSktvWkkKaHZjTkFRa0JGaFp6ZFhCd2IzSjBRSE42YVhSeWRYTXVZMjl0TG1OdU1SMHdHd1lEVlFRTERCUlVaVzV3WVhrdQpZMjl0SUVOQklFTmxiblJsY2pFVE1CRUdBMVVFQ2d3S1ZHVnVjR0Y1TG1OdmJURVJNQThHQTFVRUJ3d0lVMmhsCmJscG9aVzR4Q3pBSkJnTlZCQVlUQWtOT01JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0MKQVFFQXY4Rkhvc0Z1QTJodWxvVHplUHRBV0dkWnE3Q2o0RDgzeE45UURManhGTktuR2h6enZJMVpZL2J1bVVrVwpFRzAxc1Z5dEdaRFlhWUd4ZlJlcTdTSVN0SUlWQ0ZORkdwMGYzMDkwQmp5TDliNlZka0NsVWE5Vk8rU0VTZnJqCldhVmgxczBSb1NHMlAwd3FKYlMrQ240REZrM3JLN1NWZEJnQjJJVTBTY1FBOERBL0J0K2hTK2xscDhld0RwRHkKbkRIVUNaV0xGY29TNWxDa1BkNWxneE9qNzV0K3pDSHhXMkFKMEFPS0FHTVZ5MGRVc3JGMzA4KzZBTHJuR0JLRQp3ckNHc1ZWOGp0eEtEMFZKa05LSXVrUDh4S0Z2emRuMDRIM3hFYTE2YUl0ZmRHemt2WFg4S1FVeVBYWDY1Z28rCk50MmYxVGNRZzVNdE9CRUwxNi92RTlmb3dRSURBUUFCbzRJQkdEQ0NBUlF3Q1FZRFZSMFRCQUl3QURBTEJnTlYKSFE4RUJBTUNCc0F3VHdZSUt3WUJCUVVIQVFFRVF6QkJNRDhHQ0NzR0FRVUZCekFDaGpOdlkzTndMR2gwZEhBNgpMeTlaYjNWeVgxTmxjblpsY2w5T1lXMWxPbEJ2Y25RdlZHOXdRMEV2Ykc5a2NGOUNZWE5sUkU0d2FRWURWUjBmCkJHSXdZREJlb0Z5Z1dvWllhSFIwY0Rvdkx6a3VNVGt1TVRZeExqUTZPREE0TUM5VWIzQkRRUzl3ZFdKc2FXTXYKYVhSeWRYTmpjbXcvUTBFOU16bENORGszUVVKRE9FRkZPRGcxTnpRMVFrWTFOamd4UlRSR01ETkNPRUkyTkRkRwpNamhGUVRBZkJnTlZIU01FR0RBV2dCUk9jODA1dHZ1cEYvak9pWWFwY3ZTa2x2UHJMakFkQmdOVkhRNEVGZ1FVCkdUTEU2YnhudG5obnBobFBOd1FBNkw3SGpMc3dEUVlKS29aSWh2Y05BUUVGQlFBRGdnRUJBR3ovWnRVTlZKWEYKUVZ1TGU5MFFGUCtkNmRQUjhaYmJscTVMcXRMYW1xcmFmclF5R0ZOZDZxdEdrY293YXVJM0xKWldPeitTZ0J6YwpjS2dyOXdYb0g1azNSL0pmYVhYU2F3Q3d1NjZIL3grbFlhcU45TXREYkVoVi9Sa0U3dTRwb2I4ZkJSOC9FNTllCnNOQnFmMVdHODhTcUZPbDZ3VDY5ajR0cHBjRVlldjFISCtaelpScDN4U3NJV3dSMHNtV21IOE9BMWZLSFYramsKY21IaFJwNDFTeTQ4bUREdkRuRGVsTU5UaU1wUjk2a1JONmNLWG1LeXZadkNhTkNUOXpyNExVdStMUHdmTFdiWgpBQzVtL1dHclBKakhWMXAyUCt4NmRYaEJybitWV2tyYzZYZTVqeXFmdnpBRVBvN1lsQVFXamh2T2I5d3U4TzNTCjNJVnZrTzNiSkhZPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0t\"\n" + + " }\n" + + " }\n" + + " ]\n" + + "}")); + queue.add( + new MockResponse() + .addHeader("Content-Type", "application/json; charset=utf-8") + .setBody("{}")); + return queue; + } + + @BeforeAll + static void initAll() throws Exception { + final Dispatcher dispatcher = + new Dispatcher() { + + final BlockingQueue updateWith500Queue = newUpdateWith500Queue(); + + final BlockingQueue updateQueue = newUpdateQueue(); + + final BlockingQueue updateWithBadBodyQueue = newUpdateWithBadBodyQueue(); + + private MockResponse tryRemove(BlockingQueue queue) { + try { + return queue.remove(); + } catch (NoSuchElementException e) { + return new MockResponse().setResponseCode(404); + } + } + + @NotNull + @Override + public MockResponse dispatch(RecordedRequest request) { + + switch (Objects.requireNonNull(request.getPath())) { + case "/testUpdateWith500": + return tryRemove(updateWith500Queue); + case "/testUpdate": + return tryRemove(updateQueue); + case "/testUpdateWithBadBody": + return tryRemove(updateWithBadBodyQueue); + } + return new MockResponse().setResponseCode(404); + } + }; + server.setDispatcher(dispatcher); + server.start(); + } + + @AfterAll + static void tearDownAll() throws Exception { + server.shutdown(); + } + + static class FakeCertificateHandler implements CertificateHandler { + @Override + public X509Certificate generateCertificate(String certificate) { + return PemUtil.loadX509FromString(certificate); + } + + @Override + public void validateCertPath(X509Certificate certificate) {} + } + + static class FakeAeadCiper implements AeadCipher { + + @Override + public String encrypt(byte[] associatedData, byte[] nonce, byte[] plaintext) { + throw new UnsupportedOperationException(); + } + + @Override + public String decrypt(byte[] associatedData, byte[] nonce, byte[] ciphertext) { + return new String(ciphertext); + } + } + + static final Map> testMap = new ConcurrentHashMap<>(); + + static class FakeAutoCertificateProvider extends AbstractAutoCertificateProvider { + + public FakeAutoCertificateProvider( + String requestUrl, HttpClient httpClient, String merchantId, int updateInterval) { + super( + requestUrl, + new FakeCertificateHandler(), + new FakeAeadCiper(), + httpClient, + merchantId, + testMap, + updateInterval); + } + } + + static class FakeCredential implements Credential { + + @Override + public String getSchema() { + return "fake-schema"; + } + + @Override + public String getMerchantId() { + return "fake-merchant"; + } + + @Override + public String getAuthorization(URI uri, String httpMethod, String signBody) { + return "fake-auth"; + } + } + + static class FakeValidator implements Validator { + @Override + public boolean validate(HttpHeaders responseHeaders, String body) { + return true; + } + } + + @Test + void testUpdate() throws Exception { + Credential fakeCredential = new FakeCredential(); + Validator fakeHttpValidator = new FakeValidator(); + + HttpClient client = + new DefaultHttpClientBuilder() + .credential(fakeCredential) + .validator(fakeHttpValidator) + .build(); + + CertificateProvider provider = + new FakeAutoCertificateProvider( + server.url("/testUpdate").url().toString(), client, "testUpdate", 3); + + assertNotNull(provider.getAvailableCertificate()); + assertNotNull(provider.getCertificate("39FACCC173F3485C76C61FD5C0D662AE4A838AA9")); + + await() + .atLeast(3000, TimeUnit.MILLISECONDS) + .atMost(3500, TimeUnit.MILLISECONDS) + .untilAsserted( + () -> + assertNotNull(provider.getCertificate("699AE9A3A00228BFE133FA9A4A99E31D4D2571B4"))); + + assertNull(provider.getCertificate("39FACCC173F3485C76C61FD5C0D662AE4A838AA9")); + } + + @Test + void testUpdateWith500() throws Exception { + Credential fakeCredential = new FakeCredential(); + Validator fakeHttpValidator = new FakeValidator(); + + HttpClient client = + new DefaultHttpClientBuilder() + .credential(fakeCredential) + .validator(fakeHttpValidator) + .build(); + + CertificateProvider provider = + new FakeAutoCertificateProvider( + server.url("/testUpdateWith500").url().toString(), client, "testUpdateWith500", 3); + + assertNotNull(provider.getAvailableCertificate()); + assertNotNull(provider.getCertificate("39FACCC173F3485C76C61FD5C0D662AE4A838AA9")); + + with() + .pollDelay(3500, TimeUnit.MILLISECONDS) + .await() + .untilAsserted( + () -> + assertNotNull(provider.getCertificate("39FACCC173F3485C76C61FD5C0D662AE4A838AA9"))); + } + + @Test + void testUpdateWithBadBody() throws Exception { + Credential fakeCredential = new FakeCredential(); + Validator fakeHttpValidator = new FakeValidator(); + + HttpClient client = + new DefaultHttpClientBuilder() + .credential(fakeCredential) + .validator(fakeHttpValidator) + .build(); + + CertificateProvider provider = + new FakeAutoCertificateProvider( + server.url("testUpdateWithBadBody").url().toString(), + client, + "testUpdateWithBadBody", + 3); + + assertNotNull(provider.getAvailableCertificate()); + assertNotNull(provider.getCertificate("39FACCC173F3485C76C61FD5C0D662AE4A838AA9")); + + with() + .pollDelay(3500, TimeUnit.MILLISECONDS) + .await() + .untilAsserted( + () -> + assertNotNull(provider.getCertificate("39FACCC173F3485C76C61FD5C0D662AE4A838AA9"))); + } + + @Test + void testInitWithInvalidBody() throws Exception { + MockWebServer server = new MockWebServer(); + server.enqueue( + new MockResponse() + .addHeader("Content-Type", "application/json; charset=utf-8") + .setBody("{}")); + server.start(); + + Credential fakeCredential = new FakeCredential(); + Validator fakeHttpValidator = new FakeValidator(); + + HttpClient client = + new DefaultHttpClientBuilder() + .credential(fakeCredential) + .validator(fakeHttpValidator) + .build(); + + assertThrows( + Exception.class, + () -> + new FakeAutoCertificateProvider( + server.url("/v3/test/path").url().toString(), + client, + "testInitWithInvalidBody", + 3)); + server.shutdown(); + } +} diff --git a/core/src/test/java/com/wechat/pay/java/core/certificate/RSAAutoCertificateProviderTest.java b/core/src/test/java/com/wechat/pay/java/core/certificate/RSAAutoCertificateProviderTest.java index ab1063ef..7f6cae3b 100644 --- a/core/src/test/java/com/wechat/pay/java/core/certificate/RSAAutoCertificateProviderTest.java +++ b/core/src/test/java/com/wechat/pay/java/core/certificate/RSAAutoCertificateProviderTest.java @@ -1,6 +1,8 @@ package com.wechat.pay.java.core.certificate; 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.DOWNLOAD_CERTIFICATE_SERIAL_NUMBER; import static com.wechat.pay.java.core.model.TestConfig.MERCHANT_CERTIFICATE_SERIAL_NUMBER; import static com.wechat.pay.java.core.model.TestConfig.MERCHANT_PRIVATE_KEY; import static java.net.HttpURLConnection.HTTP_OK; @@ -10,17 +12,12 @@ import com.wechat.pay.java.core.auth.Validator; import com.wechat.pay.java.core.auth.WechatPay2Credential; import com.wechat.pay.java.core.certificate.RSAAutoCertificateProvider.Builder; -import com.wechat.pay.java.core.certificate.model.Data; -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.RSASigner; import com.wechat.pay.java.core.http.DefaultHttpClientBuilder; 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.util.GsonUtil; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.stream.Stream; import okhttp3.MediaType; import okhttp3.OkHttpClient; @@ -35,56 +32,46 @@ public class RSAAutoCertificateProviderTest implements CertificateProviderTest { static RSAAutoCertificateProvider spyAutoProvider; - - static OkHttpClient createOkHttpClient() { - return new OkHttpClient.Builder() - .addInterceptor( - chain -> { - DownloadCertificateResponse downloadResp = new DownloadCertificateResponse(); - ArrayList dataList = new ArrayList<>(); - Data data = new Data(); - data.setEffectiveTime("2022-06-08T10:34:56+08:00"); - data.setExpireTime("2025-12-08T10:34:56+08:00"); - data.setSerialNo("440024045C4A427599D09BB4E3DE0279F2E813FD"); - EncryptCertificate encryptCertificate = new EncryptCertificate(); - encryptCertificate.setAlgorithm("AEAD_AES_256_GCM"); - encryptCertificate.setAssociatedData("certificate"); - encryptCertificate.setNonce("61f9c719728a"); - encryptCertificate.setCiphertext( - "9kRsp3GhB0VA31gv0QxUssTdPZn97GSnyMEBBk0S52lfnzSgo8v17lNbndISX+a970CjAPnx8lFhg/WKzv7v0uUno8W09mtj0l9ERsHkVnTkTWJ5dYUw+AADMVSteWRMuguLbmiwlohZRi1uMWEHbGwucy3dFYY3AF6EuCfklobSOHf6p+jvKjbN/+V6SYNE7ZYMFcHyL/IGTuk56Od9zR4CHemg93FCzxDIlymdfhadZg2Sj+X9T3L/opHtwjhsw/vg/IZEuaVCTmj6M9a7X9EN126CR88JuasauDKcVobiQyTjFlHp1iOSDU+7QxJGtpHduR06uNlgJr8P8zqACV76o4fHWACTWqYyh8CQUMxXL7GlJMQilwXx+4AOylFPPnJ7b1fvGIRA9i3ugc6ZR1qop9qeNhW0R+SAPxfWgy1xysVISe/dKLVY/UwxN/y83s7do6Eud+rm+qk7uVaaOY23681pxdq3WsnoaGsUxdTHSn0c9P8Zw7JvVvlYlN/gY49MX6JXPCI8PV4pWR8lWA4I9nxIzUDZOyZN9O3ZVQLiDJDJDFrGSwyUUEZDYmlv62myrqSoShkf8qEBa8J+nELTW7CHf/TQmnbs6DKchVOEpKMRotIBwULvoYU+Y/SoIAUbWWXjDh1m6lh40tLFsqbD+CKTlOWs17l18zIEyDqG7RP9QUxapdghNCiIvwzbBPpPvc1mZG22K3GVg3pYPeMu98AkCrOVWI+Xn7x7OCUMPp+9X0RUQo3vChD5mT5Ldg31Zjwyxhg7JSVRYG+BdS0Ova1T23Y5yBIVZFvhCTvJM2lG+uvgHE6Va25BZm6gxGAgYCgCrhQFCr6+Wd6QsM+qvsddh5jBaWOI5sO/Gt0DuAtn7WKiE7QaLsDe979+EIKYbrzaRNz3LBbSUECEjrfXmnhHuMLWInzFzwaZf7nkStwYeOLqukwV2UFGMqYvD7OYM0rOTQey7lGqId1XyVECA2XV/J4qOxGkMhYjVzLcvWltsgEqchWUrWfD9ZfA2Zcm5Utw36mBSEvDFrixkomVAS0Utj9NfUIX1e5BlZgK+8KCR+/PK5qo8aAZrXb907wpwXBZ5tEtikYcfg8b4s7K47BDeDvSf/padf6/x8vtsYGwy0hSTr69I5Uto4AoBjT0EH8h38CVVRbyDuW1qfh3vXNNd+By4vggucpzRYGhAOCdvLhKqn1qfa0ue9/wjSCoUh7/eGgBhmrvx/4mEsBbOvqJYP4vkFh8U7XqGE1myBKO2TtArVuSVjV511d+hBdPoLf2k6Z0L/FgybodrqmsqaZvCv7KgW/v7jpSwT/Ema/ZpK1WT6GxdyOomVA1xrXWiWg+wetaRnKvncgJvPAaKrv2B4JOGYhmSYC6A28kwU4BMm7CFnZuTo0PrFHl5k3ZDMAlxuKX5CHK5v52ERU2rpywavb1SlvGZXgXI0HW+dN7Mvjvnc2eWUrWLj7OZK4jPWuGXLkHvgB0pKU2lfjQn0Cyo2UbErwQiHgM8aZcKsTIl9sT/eX6OKHFSPQA/GfCM5qzPIHzBfPEbAkliQeIUXwB3gW9dd79DS+PXjt62oklcO6dPSNIlIV3kvtDBSpdaHKMll9C2VUPA+CYi/EldWDD/RxCCtK5sGf4niiHjO59F/oKGIi0kZB234hIpWSVq1tPNy58+7IPWOFKCzsK41Y18maD+M7higw+dkTeanvmP8Y/JWp/E7wiWpLmJdFV21cjrtjPIDUrg34gkJ/BaESaF+hTB/9gMCLFYidHxHBWGHqWPHM="); - data.setEncryptCertificate(encryptCertificate); - dataList.add(data); - downloadResp.setData(dataList); - return new Response.Builder() - .request(chain.request()) - .code(HTTP_OK) - .header("key", "val") - .message("ok") - .protocol(Protocol.HTTP_1_1) - .body( - ResponseBody.create( - GsonUtil.getGson().toJson(downloadResp), - MediaType.parse( - com.wechat.pay.java.core.http.MediaType.APPLICATION_JSON.getValue()))) - .build(); - }) - .build(); - } + static OkHttpClient okHttpClient; static class EmtpyValidator implements Validator { @Override - public boolean validate(HttpHeaders responseHeaders, String body) { + public boolean validate(HttpHeaders responseHeaders, String body) { return true; } } + static class FalseValidator implements Validator { + @Override + public boolean validate(HttpHeaders responseHeaders, String body) { + return false; + } + } + @BeforeAll static void beforeAll() { - OkHttpClient okHttpClient = createOkHttpClient(); + 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 httpClient = new OkHttpClientAdapter( new WechatPay2Credential( - "7123456", new RSASigner(MERCHANT_CERTIFICATE_SERIAL_NUMBER, MERCHANT_PRIVATE_KEY)), + "5123456", new RSASigner(MERCHANT_CERTIFICATE_SERIAL_NUMBER, MERCHANT_PRIVATE_KEY)), new EmtpyValidator(), okHttpClient); spyAutoProvider = @@ -112,7 +99,7 @@ public void testCreateWithHttpClientBuilder() { DefaultHttpClientBuilder builder = new DefaultHttpClientBuilder() - .okHttpClient(createOkHttpClient()) + .okHttpClient(okHttpClient) .connectTimeoutMs(1000) .readTimeoutMs(1000) .writeTimeoutMs(1000); @@ -132,7 +119,6 @@ void testCreateProvider(Builder builder) { } static Stream BuilderProvider() { - OkHttpClient okHttpClient = createOkHttpClient(); return Stream.of( // httpclient builder with timeout new Builder() @@ -154,6 +140,12 @@ static Stream BuilderProvider() { .httpClientBuilder( new DefaultHttpClientBuilder() .okHttpClient(okHttpClient) - .validator(new EmtpyValidator()))); + .validator(new FalseValidator()))); + } + + @Override + @Test + public void testGetCertificates() { + assertNotNull(createCertificateProvider().getCertificate(DOWNLOAD_CERTIFICATE_SERIAL_NUMBER)); } } 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 new file mode 100644 index 00000000..9e3ce7c7 --- /dev/null +++ b/core/src/test/java/com/wechat/pay/java/core/certificate/RSACertificateHandlerTest.java @@ -0,0 +1,87 @@ +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" + + "MIID8TCCAtmgAwIBAgIUIrqFSZtm0AdWLS62jy3IyrpN7a0wDQYJKoZIhvcNAQEL\n" + + "BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT\n" + + "FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg\n" + + "Q0EwHhcNMTgxMDA5MTM0ODQ0WhcNMjMxMDA4MTM0ODQ0WjCBgjEYMBYGA1UEAwwP\n" + + "VGVucGF5LmNvbSBzaWduMRMwEQYDVQQKDApUZW5wYXkuY29tMR0wGwYDVQQLDBRU\n" + + "ZW5wYXkuY29tIENBIENlbnRlcjELMAkGA1UEBgwCQ04xEjAQBgNVBAgMCUd1YW5n\n" + + "RG9uZzERMA8GA1UEBwwIU2hlblpoZW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\n" + + "ggEKAoIBAQCsZQyHPpnVpWFKy1KIaZMH/GlmGTEZVlPoyBeqGgEoygCf+poL17t+\n" + + "xzJwQyxfcaAG1hG3gLcrB4zQbfkKj8GmnLG8EUUyDw+wQRUvJZVaWX4b36cTLjLV\n" + + "T0VbNf8l0i6kD+J62GArUArUIo4iKSdihx87hzjNwFQdyKwab/v7Z8G1rbA5pyCq\n" + + "Q0YnPoBMAkj9ZCGg8Q08pPdZKlnbqjpf7s4ol4PjRI01IpaYcihh29b4px6mste6\n" + + "fBpSut6zljd67NX/cL6GLbWxIeYXwtcabaYrwooq438mLmv1F1nhJyGoQjdhqRKQ\n" + + "0k8q2/5tuxOOt01MvFMtZj8Cd1DNVjy5AgMBAAGjgYEwfzAJBgNVHRMEAjAAMAsG\n" + + "A1UdDwQEAwIE8DBlBgNVHR8EXjBcMFqgWKBWhlRodHRwOi8vZXZjYS5pdHJ1cy5j\n" + + "b20uY24vcHVibGljL2l0cnVzY3JsP0NBPTFCRDQyMjBFNTBEQkMwNEIwNkFEMzk3\n" + + "NTQ5ODQ2QzAxQzNFOEVCRDIwDQYJKoZIhvcNAQELBQADggEBAHpWYY0N8HopUQEE\n" + + "Gc0YkgbCLfP3ZdJpGrwlAD2822PxviNwJNABwe8nONzbLXbD7gzEZ2oCyYRmKhGh\n" + + "m7q+A1NT+35hStj3fSDgeUyEyG4/qyIA9H4I8V3DLSHyvC15wqD+EDc+2lue4MyJ\n" + + "D7CAwJDOHqmHif2HsdGgdM0CWYetZPhWTwJcBnTfXNE24IEfE+D/x9ZW2+Q7wiLc\n" + + "fLdjss9a23EouDFsh5PAhCG8QPBqpxAj6W/JjIuuOE4eFwPfgvp9aq0ELJoUVhje\n" + + "IGpYaDjj8561zwKhsK4WRcIRTrLPeFaDa8gJjLNFR+5CNXCJIQ3gIIkY5NBho0Uy\n" + + "HoymOW4=\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/model/TestConfig.java b/core/src/test/java/com/wechat/pay/java/core/model/TestConfig.java index 8ac658c1..6e652215 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 @@ -25,6 +25,29 @@ public class TestConfig { public static final String MERCHANT_ID; public static final String API_V3_KEY = "a7cde1ZJB1kG2e7VfTs3jQzaWizur8Gb"; + public static final String DOWNLOAD_CERTIFICATE_RESPONSE = + "{\n" + + " \"data\": [\n" + + " {\n" + + " \"serial_no\": \"19FBBF2A24A3F3C97F5925FA855A850D6E4624AF\",\n" + + " \"effective_time \": \"2018-10-09T21:48:14+08:00\",\n" + + " \"expire_time \": \"2023-10-08T21:48:14+08:00\",\n" + + " \"encrypt_certificate\": {\n" + + " \"algorithm\": \"AEAD_AES_256_GCM\",\n" + + " \"nonce\": \"1543b1cd0eb9\",\n" + + " \"associated_data\": \"certificate\",\n" + + " \"ciphertext\": " + + "\"5ZQ4y+5Az+AjCgb+bg574jUQvBuQofsPzea3EKpfvvbiREnklJNkzNTVdkAX1SUz1Aaimwcnl8Jjg7R/57L30Gqz" + + "+QXHBPjer2wo5KwQspwpfkdxWdps9dF+G4VtyBNSqJd1EHlK5ao" + + "+/x2TsmcZIulBiz3f5civgFMjvtqLnJTPSqRClomJP6CZiNq00GkNJRrsMgND7VD0mzOq3vdt62tgOZNReahDbXOqj4uiHy0sLHWsiWVfOxMR0zV4FXgcvahi1L5yrJdTs4uNX1RQ+chsVAydXCeiiysJbLY/Bz0fwF1NEi0EGrAhO51qUCseGtFBJNJosCSJkn9nW1FqINJtxZ/vUliXlw7IW4gw6mKO2FqijjHe7kWeCT1KukNi8YBa+Z9CPjTBiMEKFx7SasFsAwA6cqu9mMfNGTcDFEIDfbDYGt8XMFWIOH3oOFU2k4cKRBuL9pB7A3netiZ2qUGtYMxkVAxDIz852XKo2P2+YG7TqhijX33qPygTqbo+jKhvj11joL9mHiYsWJi4zBCsxE7yR61lxpix0vnk9v+UuyZAJibVnhd6q9Lo2xFk0fuENYe0PStG5Ue4kdNNe16n+fhK/LaAhIZ5c9nHD7+7GTAifSZNrqzTnpQMuyktkK/DcMrkVPu3Z3N9+ZhcIOp2rP6utItJH52+7kYmOPk92ciMsvCi6wGOaP/kd9CQYchnL1KRjW2OuzGptMAsLOL5rULb3zfh8GGNUD9XUnT56hH6RrCYFrojjDsFzBJFfgTzfgDee+j3EZI1NeMBls2TFrNkHFsbb7XRoxffjnXzP7xoAlDuP/wkF70ASI8mdZf/4Qe0S+8h8M9PWKlQjtS0DJVnOK4ladj9QeV9zB2YycpgiAaMOsQF8mUbP7bssCX+ZZDTJNpmIgPapamb8eGYqnF7MV1R2uPUGBBnF0hcfxaA3iCESzKgeQEOpKIYE+vjXuej3S+bFk8HFd4/hzPNd8vJpaJkxtaETpAhcnmybwg7CZMdBQG6lQxQ8pYcG1ZTsAUFL0BD5QVjTL9U2N9xYGWUDutofZuEcuUqg4z7ljS0c41ORHJrsQKqzwGJGeDl/eKbDTkMkFdFRfEwLhouitJSfjdWQX5ii0KMIZcVLZ8nFq3Znh7VQ41MHg9GOeNQfKTwyI1kATSwZdNYg1lIyFEQ89GSmFBfKT1atBGZa+fo2jaj4GvMrt5bgOHLFF5qn3c3DoITKLD45SVAlvOWTvm3QhaNq89MIvsFdrVsRoSDPz+skNmNmuT6P1YqUnFGr3R8yVQvs6j355OddHOdYyFSdhUVY4sCxzOFdi8yNDs7WKx3H0BcdlNWPHIlb622e+uWtqe4sXO35T52a5tZ/UKlZsacNci6tVoZTbFnNBdV/M673jBKb5dnkr9upKp1O0ixhqrB7xuKuIjP6L4l6qzlpf3pbOv5rNK3Gv2+vpMz0OcguK0QjnIXmb+MJbHZDqE6ffRfhYJHczi3yF0tP9He0+2SB98KeF15HmXMRtgeTmzh/YUFfdKJGNBrV25lO/hK6kZ/cmp/wukTT5F/TIHIevEoMx9YnsDLxCYendxVTcvUf5NkAbRWkpLeAD2aTg5oUwu3yRqgmCGDcUW3E+SzIVar/N6lt7upwb6CUGcXgnF4a8NJM0V7woA/8+xmDdlvAkneZ+jULr2x4FD4W2rmZsM9IjrYyQTc6lQhJ/jDNNO9BpWf7kFiMbea1toeURJRuYYdXsGIfb3suBkuf3WTSoMtJI1UzL1RUuvgSjSGOGmnZhcMbBE4PwA+nvVzTVqbLoxy7sOoglXp251WIWGiVBuvJarCRyM5PYJd8nE6PNYogxp2l4JQpkEHcoRuyyzL/DtzKSJNd2JXCNkIqibsEraNgjUQpRLesEREUJT9iEb05FMBlQpJlcHYbzMT\"\n" + + " }\n" + + " }\n" + + " ]\n" + + "}\n"; + + public static final String DOWNLOAD_CERTIFICATE_SERIAL_NUMBER = + "19FBBF2A24A3F3C97F5925FA855A850D6E4624AF"; + static { try { RESOURCES_DIR = System.getProperty("user.dir") + "/src/test/resources"; diff --git a/core/src/test/java/com/wechat/pay/java/core/notification/RSAAutoCertificateNotificationConfigTest.java b/core/src/test/java/com/wechat/pay/java/core/notification/RSAAutoCertificateNotificationConfigTest.java index caab31a9..8d2af75b 100644 --- a/core/src/test/java/com/wechat/pay/java/core/notification/RSAAutoCertificateNotificationConfigTest.java +++ b/core/src/test/java/com/wechat/pay/java/core/notification/RSAAutoCertificateNotificationConfigTest.java @@ -1,27 +1,17 @@ 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.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.*; 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.certificate.model.Data; -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.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.AutoCertificateNotificationConfig.Builder; -import com.wechat.pay.java.core.util.GsonUtil; -import java.util.ArrayList; import java.util.stream.Stream; import okhttp3.MediaType; import okhttp3.OkHttpClient; @@ -48,36 +38,20 @@ public boolean validate(HttpHeaders responseHeaders, String body) { OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor( - chain -> { - DownloadCertificateResponse downloadResp = new DownloadCertificateResponse(); - ArrayList dataList = new ArrayList<>(); - Data data = new Data(); - data.setEffectiveTime("2022-06-08T10:34:56+08:00"); - data.setExpireTime("2025-12-08T10:34:56+08:00"); - data.setSerialNo("440024045C4A427599D09BB4E3DE0279F2E813FD"); - EncryptCertificate encryptCertificate = new EncryptCertificate(); - encryptCertificate.setAlgorithm("AEAD_AES_256_GCM"); - encryptCertificate.setAssociatedData("certificate"); - encryptCertificate.setNonce("61f9c719728a"); - encryptCertificate.setCiphertext( - "9kRsp3GhB0VA31gv0QxUssTdPZn97GSnyMEBBk0S52lfnzSgo8v17lNbndISX+a970CjAPnx8lFhg/WKzv7v0uUno8W09mtj0l9ERsHkVnTkTWJ5dYUw+AADMVSteWRMuguLbmiwlohZRi1uMWEHbGwucy3dFYY3AF6EuCfklobSOHf6p+jvKjbN/+V6SYNE7ZYMFcHyL/IGTuk56Od9zR4CHemg93FCzxDIlymdfhadZg2Sj+X9T3L/opHtwjhsw/vg/IZEuaVCTmj6M9a7X9EN126CR88JuasauDKcVobiQyTjFlHp1iOSDU+7QxJGtpHduR06uNlgJr8P8zqACV76o4fHWACTWqYyh8CQUMxXL7GlJMQilwXx+4AOylFPPnJ7b1fvGIRA9i3ugc6ZR1qop9qeNhW0R+SAPxfWgy1xysVISe/dKLVY/UwxN/y83s7do6Eud+rm+qk7uVaaOY23681pxdq3WsnoaGsUxdTHSn0c9P8Zw7JvVvlYlN/gY49MX6JXPCI8PV4pWR8lWA4I9nxIzUDZOyZN9O3ZVQLiDJDJDFrGSwyUUEZDYmlv62myrqSoShkf8qEBa8J+nELTW7CHf/TQmnbs6DKchVOEpKMRotIBwULvoYU+Y/SoIAUbWWXjDh1m6lh40tLFsqbD+CKTlOWs17l18zIEyDqG7RP9QUxapdghNCiIvwzbBPpPvc1mZG22K3GVg3pYPeMu98AkCrOVWI+Xn7x7OCUMPp+9X0RUQo3vChD5mT5Ldg31Zjwyxhg7JSVRYG+BdS0Ova1T23Y5yBIVZFvhCTvJM2lG+uvgHE6Va25BZm6gxGAgYCgCrhQFCr6+Wd6QsM+qvsddh5jBaWOI5sO/Gt0DuAtn7WKiE7QaLsDe979+EIKYbrzaRNz3LBbSUECEjrfXmnhHuMLWInzFzwaZf7nkStwYeOLqukwV2UFGMqYvD7OYM0rOTQey7lGqId1XyVECA2XV/J4qOxGkMhYjVzLcvWltsgEqchWUrWfD9ZfA2Zcm5Utw36mBSEvDFrixkomVAS0Utj9NfUIX1e5BlZgK+8KCR+/PK5qo8aAZrXb907wpwXBZ5tEtikYcfg8b4s7K47BDeDvSf/padf6/x8vtsYGwy0hSTr69I5Uto4AoBjT0EH8h38CVVRbyDuW1qfh3vXNNd+By4vggucpzRYGhAOCdvLhKqn1qfa0ue9/wjSCoUh7/eGgBhmrvx/4mEsBbOvqJYP4vkFh8U7XqGE1myBKO2TtArVuSVjV511d+hBdPoLf2k6Z0L/FgybodrqmsqaZvCv7KgW/v7jpSwT/Ema/ZpK1WT6GxdyOomVA1xrXWiWg+wetaRnKvncgJvPAaKrv2B4JOGYhmSYC6A28kwU4BMm7CFnZuTo0PrFHl5k3ZDMAlxuKX5CHK5v52ERU2rpywavb1SlvGZXgXI0HW+dN7Mvjvnc2eWUrWLj7OZK4jPWuGXLkHvgB0pKU2lfjQn0Cyo2UbErwQiHgM8aZcKsTIl9sT/eX6OKHFSPQA/GfCM5qzPIHzBfPEbAkliQeIUXwB3gW9dd79DS+PXjt62oklcO6dPSNIlIV3kvtDBSpdaHKMll9C2VUPA+CYi/EldWDD/RxCCtK5sGf4niiHjO59F/oKGIi0kZB234hIpWSVq1tPNy58+7IPWOFKCzsK41Y18maD+M7higw+dkTeanvmP8Y/JWp/E7wiWpLmJdFV21cjrtjPIDUrg34gkJ/BaESaF+hTB/9gMCLFYidHxHBWGHqWPHM="); - data.setEncryptCertificate(encryptCertificate); - dataList.add(data); - downloadResp.setData(dataList); - return new Response.Builder() - .request(chain.request()) - .code(HTTP_OK) - .header("key", "val") - .message("ok") - .protocol(Protocol.HTTP_1_1) - .body( - ResponseBody.create( - GsonUtil.getGson().toJson(downloadResp), - MediaType.parse( - com.wechat.pay.java.core.http.MediaType.APPLICATION_JSON - .getValue()))) - .build(); - }) + 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( diff --git a/gradle.properties b/gradle.properties index d3db2f62..cd1afdcf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,7 @@ junit5Version=5.9.1 okhttpVersion=4.10.0 gsonVersion=2.9.1 mockitoInlineVersion=4.8.1 +awaitilityVersion=4.2.0 spotlessVersion=6.11.0 shadowVersion=7.1.2 sonarqubeVersion=3.5.0.2730