From 8abbb0643e2654746744815b0056447037a27094 Mon Sep 17 00:00:00 2001 From: Xiaoyu PENG Date: Wed, 18 Jan 2023 15:28:27 +0800 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8=E8=AF=81?= =?UTF-8?q?=E4=B9=A6=E4=BF=A1=E4=BB=BB=E9=93=BE=E9=AA=8C=E8=AF=81=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E7=9A=84=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E8=AF=81=E4=B9=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AbstractAutoCertificateProvider.java | 84 +++++++++--------- .../core/certificate/CertificateHandler.java | 8 ++ .../certificate/RSACertificateHandler.java | 57 +++++++++++- .../core/RSAAutoCertificateConfigTest.java | 56 ++++-------- .../RSAAutoCertificateProviderTest.java | 86 ++++++++----------- .../pay/java/core/model/TestConfig.java | 23 +++++ ...AutoCertificateNotificationConfigTest.java | 56 ++++-------- 7 files changed, 192 insertions(+), 178 deletions(-) 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..fbdad913 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 @@ -2,8 +2,6 @@ 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; @@ -14,11 +12,9 @@ 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 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; @@ -33,16 +29,16 @@ public abstract class AbstractAutoCertificateProvider implements CertificateProv protected static final int UPDATE_INTERVAL_MINUTE = 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( @@ -61,7 +57,7 @@ protected AbstractAutoCertificateProvider( "The corresponding provider for the merchant already exists."); } } - this.requestUrl = requestUrl; + this.certificateHandler = certificateHandler; this.aeadCipher = aeadCipher; this.httpClient = httpClient; @@ -73,14 +69,27 @@ 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); @@ -89,21 +98,11 @@ protected AbstractAutoCertificateProvider( /** 下载和更新证书 */ 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); } /** @@ -117,30 +116,25 @@ 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) -> { + try { + certificateHandler.validateCertPath(cert); + } catch (ValidationException e) { + log.error(String.format("WeChatPay certificate (%s)", serialNo), e); + throw e; + } + }); } /** - * 更新证书 + * 从应答报文中解密证书 * * @param httpResponse httpResponse */ - 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) { @@ -154,7 +148,7 @@ protected void updateCertificate( certificate = certificateHandler.generateCertificate(decryptCertificate); downloadCertMap.put(certificate.getSerialNumber().toString(HEX).toUpperCase(), 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..b97eb4ca 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 @@ -22,4 +22,12 @@ public interface CertificateHandler { * @return verifier */ Verifier generateVerifier(List certificateList); + + /** + * * 验证证书链 + * + * @param certificate 微信支付平台证书 + * @throws com.wechat.pay.java.core.exception.ValidationException 证书验证失败 + */ + 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..d7d6301f 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 @@ -2,12 +2,45 @@ 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 { + 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); @@ -17,4 +50,24 @@ public X509Certificate generateCertificate(String certificate) { public Verifier generateVerifier(List certificateList) { return new RSAVerifier(new InMemoryCertificateProvider(certificateList)); } + + @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 validation failed: %s", e.getMessage()), e); + } + } } 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/RSAAutoCertificateProviderTest.java b/core/src/test/java/com/wechat/pay/java/core/certificate/RSAAutoCertificateProviderTest.java index ab1063ef..3c0eadfa 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,8 +1,6 @@ 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.MERCHANT_CERTIFICATE_SERIAL_NUMBER; -import static com.wechat.pay.java.core.model.TestConfig.MERCHANT_PRIVATE_KEY; +import static com.wechat.pay.java.core.model.TestConfig.*; import static java.net.HttpURLConnection.HTTP_OK; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -10,17 +8,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 +28,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 +95,7 @@ public void testCreateWithHttpClientBuilder() { DefaultHttpClientBuilder builder = new DefaultHttpClientBuilder() - .okHttpClient(createOkHttpClient()) + .okHttpClient(okHttpClient) .connectTimeoutMs(1000) .readTimeoutMs(1000) .writeTimeoutMs(1000); @@ -132,7 +115,6 @@ void testCreateProvider(Builder builder) { } static Stream BuilderProvider() { - OkHttpClient okHttpClient = createOkHttpClient(); return Stream.of( // httpclient builder with timeout new Builder() @@ -154,6 +136,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/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( From d561d7fdcbfb438b1bec76e9d95897d093e81b21 Mon Sep 17 00:00:00 2001 From: Xiaoyu PENG Date: Wed, 8 Feb 2023 15:27:14 +0800 Subject: [PATCH 2/8] test: add certificate validation test --- .../RSACertificateHandlerTest.java | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 core/src/test/java/com/wechat/pay/java/core/certificate/RSACertificateHandlerTest.java 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..22b7df48 --- /dev/null +++ b/core/src/test/java/com/wechat/pay/java/core/certificate/RSACertificateHandlerTest.java @@ -0,0 +1,85 @@ +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); + + assertDoesNotThrow( + () -> { + new RSACertificateHandler().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); + + assertThrows( + ValidationException.class, + () -> { + new RSACertificateHandler().validateCertPath(certificate); + }); + } +} From 369955bf626a2c376d33fabb0eb9f0bf1c0f65dd Mon Sep 17 00:00:00 2001 From: Xiaoyu PENG Date: Wed, 8 Feb 2023 16:24:53 +0800 Subject: [PATCH 3/8] test: fix code smells, only one invocation possibly throwing a runtime exception --- .../java/core/certificate/RSACertificateHandlerTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 index 22b7df48..9e3ce7c7 100644 --- 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 @@ -38,9 +38,10 @@ void testValidateCertPath() { + "-----END CERTIFICATE-----"; X509Certificate certificate = PemUtil.loadX509FromString(validCertificate); + CertificateHandler handler = new RSACertificateHandler(); assertDoesNotThrow( () -> { - new RSACertificateHandler().validateCertPath(certificate); + handler.validateCertPath(certificate); }); } @@ -76,10 +77,11 @@ void testInvalidCertPath() { + "-----END CERTIFICATE-----"; X509Certificate certificate = PemUtil.loadX509FromString(validCertificate); + CertificateHandler handler = new RSACertificateHandler(); assertThrows( ValidationException.class, () -> { - new RSACertificateHandler().validateCertPath(certificate); + handler.validateCertPath(certificate); }); } } From 0e91b7d138c02318bd1546e8b00701ffb4a0007a Mon Sep 17 00:00:00 2001 From: Xiaoyu PENG Date: Wed, 8 Feb 2023 16:40:26 +0800 Subject: [PATCH 4/8] fix: only throw exception, do not log and rethrown --- .../wechat/pay/java/core/AbstractRSAConfig.java | 5 ++--- .../AbstractAutoCertificateProvider.java | 16 +++------------- .../core/certificate/RSACertificateHandler.java | 5 ++++- .../com/wechat/pay/java/core/util/PemUtil.java | 6 ++++++ 4 files changed, 15 insertions(+), 17 deletions(-) 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 fbdad913..f5721c17 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,18 +1,16 @@ package com.wechat.pay.java.core.certificate; -import static com.wechat.pay.java.core.cipher.Constant.HEX; - 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.MediaType; +import com.wechat.pay.java.core.util.PemUtil; import java.nio.charset.StandardCharsets; import java.security.cert.X509Certificate; import java.util.Base64; @@ -117,15 +115,7 @@ protected HttpResponse downloadCertificate(HttpClie } protected void validateCertificate(Map certificates) { - certificates.forEach( - (serialNo, cert) -> { - try { - certificateHandler.validateCertPath(cert); - } catch (ValidationException e) { - log.error(String.format("WeChatPay certificate (%s)", serialNo), e); - throw e; - } - }); + certificates.forEach((serialNo, cert) -> certificateHandler.validateCertPath(cert)); } /** @@ -146,7 +136,7 @@ protected Map decryptCertificate( 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); } return downloadCertMap; } 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 d7d6301f..c9ac438e 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 @@ -67,7 +67,10 @@ public void validateCertPath(X509Certificate certificate) { validator.validate(certPath, params); } catch (Exception e) { throw new ValidationException( - String.format("certificate validation failed: %s", e.getMessage()), e); + 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(); + } } From 7651247057819ede3e82f52d875313f95fabd1c4 Mon Sep 17 00:00:00 2001 From: Xiaoyu PENG Date: Thu, 16 Feb 2023 14:51:15 +0800 Subject: [PATCH 5/8] =?UTF-8?q?test:=20=E9=87=8D=E5=86=99=E5=AE=9A?= =?UTF-8?q?=E6=97=B6=E4=B8=8B=E8=BD=BD=E5=B9=B6=E6=9B=B4=E6=96=B0=E8=AF=81?= =?UTF-8?q?=E4=B9=A6=E7=9A=84=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor: 不再为了验证而构造验证器,故移除不必要的接口方法 `CertificateHandler.generateVerifier()` --- core/build.gradle | 4 +- .../AbstractAutoCertificateProvider.java | 31 +- .../core/certificate/CertificateHandler.java | 10 - .../certificate/RSACertificateHandler.java | 9 +- .../AbstractAutoCertificateProviderTest.java | 347 ++++++++++++++++++ 5 files changed, 377 insertions(+), 24 deletions(-) create mode 100644 core/src/test/java/com/wechat/pay/java/core/certificate/AbstractAutoCertificateProviderTest.java diff --git a/core/build.gradle b/core/build.gradle index bbc9d736..db2b8bcc 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -27,7 +27,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation "com.squareup.okhttp3:mockwebserver:${okhttpVersion}" testRuntimeOnly("org.junit.vintage:junit-vintage-engine:${junit5Version}") - + testRuntimeOnly("org.slf4j:slf4j-simple:${slf4jVersion}") } test { @@ -97,4 +97,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/certificate/AbstractAutoCertificateProvider.java b/core/src/main/java/com/wechat/pay/java/core/certificate/AbstractAutoCertificateProvider.java index f5721c17..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 @@ -24,7 +24,7 @@ /** 自动更新平台证书提供器抽象类 */ 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(); // 安全的单线程定时执行器实例 @@ -46,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)) { @@ -89,11 +107,14 @@ protected AbstractAutoCertificateProvider( 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) { HttpResponse httpResponse = downloadCertificate(httpClient); @@ -106,6 +127,7 @@ protected void downloadAndUpdate( /** * 下载证书 * + * @param httpClient 下载使用的HttpClient * @return httpResponse */ protected HttpResponse downloadCertificate(HttpClient httpClient) { @@ -122,6 +144,7 @@ protected void validateCertificate(Map certificates) { * 从应答报文中解密证书 * * @param httpResponse httpResponse + * @return 应答报文解密后,生成X.509证书对象的Map */ protected Map decryptCertificate( HttpResponse httpResponse) { 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 b97eb4ca..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 { @@ -15,14 +13,6 @@ public interface CertificateHandler { */ X509Certificate generateCertificate(String certificate); - /** - * 使用微信支付平台证书生成Verifier - * - * @param certificateList 微信支付平台证书列表 - * @return verifier - */ - Verifier generateVerifier(List certificateList); - /** * * 验证证书链 * 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 c9ac438e..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,13 +1,11 @@ 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.*; import java.util.*; -class RSACertificateHandler implements CertificateHandler { +final class RSACertificateHandler implements CertificateHandler { private static final X509Certificate tenpayCACert = PemUtil.loadX509FromString( @@ -46,11 +44,6 @@ public X509Certificate generateCertificate(String certificate) { return PemUtil.loadX509FromString(certificate); } - @Override - public Verifier generateVerifier(List certificateList) { - return new RSAVerifier(new InMemoryCertificateProvider(certificateList)); - } - @Override public void validateCertPath(X509Certificate certificate) { try { 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..7f21f778 --- /dev/null +++ b/core/src/test/java/com/wechat/pay/java/core/certificate/AbstractAutoCertificateProviderTest.java @@ -0,0 +1,347 @@ +package com.wechat.pay.java.core.certificate; + +import static org.junit.jupiter.api.Assertions.*; + +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 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; +import org.junit.jupiter.api.Timeout; + +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 + @Timeout(10) + 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")); + + Thread.sleep(3500); + + assertNotNull(provider.getAvailableCertificate()); + assertNotNull(provider.getCertificate("699AE9A3A00228BFE133FA9A4A99E31D4D2571B4")); + assertNull(provider.getCertificate("39FACCC173F3485C76C61FD5C0D662AE4A838AA9")); + } + + @Test + @Timeout(5) + 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")); + + Thread.sleep(3500); + assertNotNull(provider.getCertificate("39FACCC173F3485C76C61FD5C0D662AE4A838AA9")); + } + + @Test + @Timeout(5) + 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")); + + Thread.sleep(3500); + 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(); + } +} From 41fc5a55f26695e02d4e1b5582c447f013d9f585 Mon Sep 17 00:00:00 2001 From: Xiaoyu PENG Date: Thu, 16 Feb 2023 16:15:44 +0800 Subject: [PATCH 6/8] =?UTF-8?q?test:=20=E4=BD=BF=E7=94=A8=20Awaitility=20?= =?UTF-8?q?=E6=9B=BF=E4=BB=A3=20Thread.Sleep()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/build.gradle | 8 +++-- .../AbstractAutoCertificateProviderTest.java | 32 ++++++++++++------- gradle.properties | 1 + 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/core/build.gradle b/core/build.gradle index db2b8bcc..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.slf4j:slf4j-simple:${slf4jVersion}") + testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' + + testRuntimeOnly "org.slf4j:slf4j-simple:${slf4jVersion}" } test { 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 index 7f21f778..037e6e06 100644 --- 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 @@ -1,5 +1,7 @@ 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.*; import com.wechat.pay.java.core.auth.Credential; @@ -17,6 +19,7 @@ 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; @@ -25,7 +28,6 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; class AbstractAutoCertificateProviderTest { @@ -242,7 +244,6 @@ public boolean validate(HttpHeaders responseHeaders, String body) { } @Test - @Timeout(10) void testUpdate() throws Exception { Credential fakeCredential = new FakeCredential(); Validator fakeHttpValidator = new FakeValidator(); @@ -260,15 +261,17 @@ void testUpdate() throws Exception { assertNotNull(provider.getAvailableCertificate()); assertNotNull(provider.getCertificate("39FACCC173F3485C76C61FD5C0D662AE4A838AA9")); - Thread.sleep(3500); + await() + .atLeast(3000, TimeUnit.MILLISECONDS) + .atMost(3500, TimeUnit.MILLISECONDS) + .untilAsserted( + () -> + assertNotNull(provider.getCertificate("699AE9A3A00228BFE133FA9A4A99E31D4D2571B4"))); - assertNotNull(provider.getAvailableCertificate()); - assertNotNull(provider.getCertificate("699AE9A3A00228BFE133FA9A4A99E31D4D2571B4")); assertNull(provider.getCertificate("39FACCC173F3485C76C61FD5C0D662AE4A838AA9")); } @Test - @Timeout(5) void testUpdateWith500() throws Exception { Credential fakeCredential = new FakeCredential(); Validator fakeHttpValidator = new FakeValidator(); @@ -286,12 +289,15 @@ void testUpdateWith500() throws Exception { assertNotNull(provider.getAvailableCertificate()); assertNotNull(provider.getCertificate("39FACCC173F3485C76C61FD5C0D662AE4A838AA9")); - Thread.sleep(3500); - assertNotNull(provider.getCertificate("39FACCC173F3485C76C61FD5C0D662AE4A838AA9")); + with() + .pollDelay(3500, TimeUnit.MILLISECONDS) + .await() + .untilAsserted( + () -> + assertNotNull(provider.getCertificate("39FACCC173F3485C76C61FD5C0D662AE4A838AA9"))); } @Test - @Timeout(5) void testUpdateWithBadBody() throws Exception { Credential fakeCredential = new FakeCredential(); Validator fakeHttpValidator = new FakeValidator(); @@ -312,8 +318,12 @@ void testUpdateWithBadBody() throws Exception { assertNotNull(provider.getAvailableCertificate()); assertNotNull(provider.getCertificate("39FACCC173F3485C76C61FD5C0D662AE4A838AA9")); - Thread.sleep(3500); - assertNotNull(provider.getCertificate("39FACCC173F3485C76C61FD5C0D662AE4A838AA9")); + with() + .pollDelay(3500, TimeUnit.MILLISECONDS) + .await() + .untilAsserted( + () -> + assertNotNull(provider.getCertificate("39FACCC173F3485C76C61FD5C0D662AE4A838AA9"))); } @Test 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 From 67303ac5cbca108fc39915843dd57ba1c6597b96 Mon Sep 17 00:00:00 2001 From: Xiaoyu PENG Date: Thu, 16 Feb 2023 16:48:30 +0800 Subject: [PATCH 7/8] =?UTF-8?q?chore:=20=E8=B0=83=E6=95=B4=E9=9D=99?= =?UTF-8?q?=E6=80=81=E6=89=AB=E6=8F=8F=E6=B5=8B=E8=AF=95=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E5=B1=8F=E8=94=BD=E7=9A=84=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .code.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From f1498f7cc22031ad4a6f5c8994ac20fdee5d2aad Mon Sep 17 00:00:00 2001 From: Xiaoyu PENG Date: Fri, 17 Feb 2023 19:27:31 +0800 Subject: [PATCH 8/8] =?UTF-8?q?style:=20import=E4=B8=8D=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E9=80=9A=E9=85=8D=E7=AC=A6=20docs:=20=E6=8A=8A=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=E5=86=99=E9=80=9A=E9=A1=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../certificate/AbstractAutoCertificateProviderTest.java | 6 ++++-- .../core/certificate/RSAAutoCertificateProviderTest.java | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) 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 index 037e6e06..a3fd1bc8 100644 --- 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 @@ -2,7 +2,9 @@ import static org.awaitility.Awaitility.await; import static org.awaitility.Awaitility.with; -import static org.junit.jupiter.api.Assertions.*; +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; @@ -31,7 +33,7 @@ class AbstractAutoCertificateProviderTest { - // 因为每个任务都在后台运行,所以一直mock服务需要存在 + // 因为每个任务都在后台运行,所以需要mock服务一直存在 static MockWebServer server = new MockWebServer(); static BlockingQueue newUpdateQueue() { 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 3c0eadfa..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,10 @@ package com.wechat.pay.java.core.certificate; -import static com.wechat.pay.java.core.model.TestConfig.*; +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; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertNotNull;