Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new: #3402 [微信支付]支持配置微信支付公钥 #3425

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,26 @@
import com.github.binarywang.wxpay.v3.WxPayV3HttpClientBuilder;
import com.github.binarywang.wxpay.v3.auth.*;
import com.github.binarywang.wxpay.v3.util.PemUtils;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.ssl.SSLContexts;

import javax.net.ssl.SSLContext;
import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Optional;
import javax.net.ssl.SSLContext;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.ssl.SSLContexts;

/**
* 微信支付配置
Expand Down Expand Up @@ -138,6 +138,25 @@ public class WxPayConfig {
*/
private byte[] privateCertContent;

/**
* 公钥ID
*/
private String publicKeyId;

/**
* pub_key.pem证书base64编码
*/
private String publicKeyString;

/**
* pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
*/
private String publicKeyPath;

/**
* pub_key.pem证书文件内容的字节数组.
*/
private byte[] publicKeyContent;
/**
* apiV3 秘钥值.
*/
Expand Down Expand Up @@ -241,7 +260,7 @@ public SSLContext initSSLContext() throws WxPayException {
}

try (InputStream inputStream = this.loadConfigInputStream(this.keyString, this.getKeyPath(),
this.keyContent, "p12证书");) {
this.keyContent, "p12证书")) {
KeyStore keystore = KeyStore.getInstance("PKCS12");
char[] partnerId2charArray = this.getMchId().toCharArray();
keystore.load(inputStream, partnerId2charArray);
Expand Down Expand Up @@ -284,7 +303,6 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException {
this.privateKeyContent, "privateKeyPath")) {
merchantPrivateKey = PemUtils.loadPrivateKey(keyInputStream);
}

}
if (certificate == null && StringUtils.isBlank(this.getCertSerialNo())) {
try (InputStream certInputStream = this.loadConfigInputStream(this.getPrivateCertString(), this.getPrivateCertPath(),
Expand All @@ -293,13 +311,28 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException {
}
this.certSerialNo = certificate.getSerialNumber().toString(16).toUpperCase();
}
PublicKey publicKey = null;
if (this.getPublicKeyString() != null || this.getPublicKeyPath() != null || this.publicKeyContent != null) {
try (InputStream pubInputStream =
this.loadConfigInputStream(this.getPublicKeyString(), this.getPublicKeyPath(),
this.publicKeyContent, "publicKeyPath")) {
publicKey = PemUtils.loadPublicKey(pubInputStream);
}
}

//构造Http Proxy正向代理
WxPayHttpProxy wxPayHttpProxy = getWxPayHttpProxy();

AutoUpdateCertificatesVerifier certificatesVerifier = new AutoUpdateCertificatesVerifier(
new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)),
this.getApiV3Key().getBytes(StandardCharsets.UTF_8), this.getCertAutoUpdateTime(), this.getPayBaseUrl(), wxPayHttpProxy);
Verifier certificatesVerifier;
if (publicKey == null) {
certificatesVerifier =
new AutoUpdateCertificatesVerifier(
new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)),
this.getApiV3Key().getBytes(StandardCharsets.UTF_8), this.getCertAutoUpdateTime(),
this.getPayBaseUrl(), wxPayHttpProxy);
} else {
certificatesVerifier = new PublicCertificateVerifier(publicKey, publicKeyId);
}

WxPayV3HttpClientBuilder wxPayV3HttpClientBuilder = WxPayV3HttpClientBuilder.create()
.withMerchant(mchId, certSerialNo, merchantPrivateKey)
Expand Down Expand Up @@ -422,7 +455,7 @@ private Object[] p12ToPem() {

// 分解p12证书文件
try (InputStream inputStream = this.loadConfigInputStream(this.keyString, this.getKeyPath(),
this.keyContent, "p12证书");) {
this.keyContent, "p12证书")) {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(inputStream, key.toCharArray());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.github.binarywang.wxpay.v3.auth;

import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Base64;
import me.chanjar.weixin.common.error.WxRuntimeException;

public class PublicCertificateVerifier implements Verifier{

private final PublicKey publicKey;

private final X509PublicCertificate publicCertificate;

public PublicCertificateVerifier(PublicKey publicKey, String publicId) {
this.publicKey = publicKey;
this.publicCertificate = new X509PublicCertificate(publicKey, publicId);
}

@Override
public boolean verify(String serialNumber, byte[] message, String signature) {
try {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initVerify(publicKey);
sign.update(message);
return sign.verify(Base64.getDecoder().decode(signature));
} catch (NoSuchAlgorithmException e) {
throw new WxRuntimeException("当前Java环境不支持SHA256withRSA", e);
} catch (SignatureException e) {
throw new WxRuntimeException("签名验证过程发生了错误", e);
} catch (InvalidKeyException e) {
throw new WxRuntimeException("无效的证书", e);
}
}

@Override
public X509Certificate getValidCertificate() {
return this.publicCertificate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package com.github.binarywang.wxpay.v3.auth;

import java.math.BigInteger;
import java.security.*;
import java.security.cert.*;
import java.util.Collections;
import java.util.Date;
import java.util.Set;

public class X509PublicCertificate extends X509Certificate {

private final PublicKey publicKey;

private final String publicId;

public X509PublicCertificate(PublicKey publicKey, String publicId) {
this.publicKey = publicKey;
this.publicId = publicId;
}

@Override
public PublicKey getPublicKey() {
return this.publicKey;
}

@Override
public BigInteger getSerialNumber() {
return new BigInteger(publicId.replace("PUB_KEY_ID_", ""), 16);
}

@Override
public void checkValidity() throws CertificateExpiredException, CertificateNotYetValidException {
}

@Override
public void checkValidity(Date date) throws CertificateExpiredException, CertificateNotYetValidException {

}

@Override
public int getVersion() {
return 0;
}

@Override
public Principal getIssuerDN() {
return null;
}

@Override
public Principal getSubjectDN() {
return null;
}

@Override
public Date getNotBefore() {
return null;
}

@Override
public Date getNotAfter() {
return null;
}

@Override
public byte[] getTBSCertificate() throws CertificateEncodingException {
return new byte[0];
}

@Override
public byte[] getSignature() {
return new byte[0];
}

@Override
public String getSigAlgName() {
return "";
}

@Override
public String getSigAlgOID() {
return "";
}

@Override
public byte[] getSigAlgParams() {
return new byte[0];
}

@Override
public boolean[] getIssuerUniqueID() {
return new boolean[0];
}

@Override
public boolean[] getSubjectUniqueID() {
return new boolean[0];
}

@Override
public boolean[] getKeyUsage() {
return new boolean[0];
}

@Override
public int getBasicConstraints() {
return 0;
}

@Override
public byte[] getEncoded() throws CertificateEncodingException {
return new byte[0];
}

@Override
public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException {

}

@Override
public void verify(PublicKey key, String sigProvider) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException {

}

@Override
public String toString() {
return "";
}


@Override
public boolean hasUnsupportedCriticalExtension() {
return false;
}

@Override
public Set<String> getCriticalExtensionOIDs() {
return Collections.emptySet();
}

@Override
public Set<String> getNonCriticalExtensionOIDs() {
return Collections.emptySet();
}

@Override
public byte[] getExtensionValue(String oid) {
return new byte[0];
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
package com.github.binarywang.wxpay.v3.util;

import me.chanjar.weixin.common.error.WxRuntimeException;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import me.chanjar.weixin.common.error.WxRuntimeException;

public class PemUtils {

Expand Down Expand Up @@ -59,4 +60,28 @@ public static X509Certificate loadCertificate(InputStream inputStream) {
throw new WxRuntimeException("无效的证书", e);
}
}

public static PublicKey loadPublicKey(InputStream inputStream){
try {
ByteArrayOutputStream array = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
array.write(buffer, 0, length);
}

String publicKey = array.toString("utf-8")
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s+", "");
return KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey)));
} catch (NoSuchAlgorithmException e) {
throw new WxRuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
throw new WxRuntimeException("无效的密钥格式");
} catch (IOException e) {
throw new WxRuntimeException("无效的密钥");
}
}
}
1 change: 1 addition & 0 deletions weixin-java-pay/src/test/resources/test-config.sample.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<certSerialNo>apiV3 证书序列号值</certSerialNo>
<privateKeyPath>apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径.</privateKeyPath>
<privateCertPath>apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径.</privateCertPath>
<publicKeyPath>pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.</publicKeyPath>

<!-- other配置 -->
<openid>某个openId</openid>
Expand Down