Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
new: #3402 【微信支付】支持配置微信支付公钥
Browse files Browse the repository at this point in the history
zhanyan-Ader1y authored Nov 29, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent c6a38ae commit 46dab3a
Showing 5 changed files with 267 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -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;

/**
* 微信支付配置
@@ -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 秘钥值.
*/
@@ -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);
@@ -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(),
@@ -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)
@@ -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());

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 {

@@ -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
@@ -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>

0 comments on commit 46dab3a

Please sign in to comment.