package com.alipay.easysdk.kms.aliyun;
|
|
import com.alipay.easysdk.kernel.AlipayConstants;
|
import com.alipay.easysdk.kms.aliyun.models.AsymmetricSignRequest;
|
import com.alipay.easysdk.kms.aliyun.models.AsymmetricSignResponse;
|
import com.alipay.easysdk.kms.aliyun.models.GetPublicKeyRequest;
|
import com.alipay.easysdk.kms.aliyun.models.GetPublicKeyResponse;
|
import com.alipay.easysdk.kms.aliyun.models.RuntimeOptions;
|
import com.aliyun.tea.TeaConverter;
|
import com.aliyun.tea.TeaModel;
|
import com.aliyun.tea.TeaPair;
|
import org.bouncycastle.asn1.gm.GMNamedCurves;
|
import org.bouncycastle.asn1.x9.X9ECParameters;
|
import org.bouncycastle.crypto.Digest;
|
import org.bouncycastle.crypto.digests.SM3Digest;
|
import org.bouncycastle.crypto.params.ECDomainParameters;
|
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.math.ec.ECFieldElement;
|
import org.bouncycastle.util.encoders.Base64;
|
|
import java.security.KeyFactory;
|
import java.security.MessageDigest;
|
import java.security.PublicKey;
|
import java.security.Security;
|
import java.security.spec.X509EncodedKeySpec;
|
import java.util.HashMap;
|
import java.util.Map;
|
|
/**
|
* 实现KMS的Client
|
*
|
* @author aliyunkms
|
* @version $Id: AliyunKMSClient.java, v 0.1 2020年05月08日 10:53 PM aliyunkms Exp $
|
*/
|
public class AliyunKMSClient extends AliyunRpcClient {
|
//支付宝signType与KMS签名算法映射表
|
private static final Map<String, String> signAlgs = new HashMap<>();
|
private static final Map<String, String> digestAlgs = new HashMap<>();
|
private static final Map<String, String> namedCurves = new HashMap<>();
|
|
static {
|
digestAlgs.put("RSA_PKCS1_SHA_256", "SHA-256");
|
digestAlgs.put("RSA_PSS_SHA_256", "SHA-256");
|
digestAlgs.put("ECDSA_SHA_256", "SHA-256");
|
|
namedCurves.put("SM2DSA", "sm2p256v1");
|
|
signAlgs.put("RSA2", "RSA_PKCS1_SHA_256");
|
}
|
|
private String keyId;
|
private String keyVersionId;
|
private String algorithm;
|
private PublicKey publicKey;
|
private String protocol;
|
private String method;
|
private String version;
|
private Integer connectTimeout;
|
private Integer readTimeout;
|
private Integer maxAttempts;
|
private boolean ignoreSSL;
|
|
public AliyunKMSClient(Map<String, Object> config) throws Exception {
|
super(config);
|
this.keyId = (String) config.get("kmsKeyId");
|
this.keyVersionId = (String) config.get("kmsKeyVersionId");
|
this.algorithm = signAlgs.get((String) config.get(AlipayConstants.SIGN_TYPE_CONFIG_KEY));
|
this.publicKey = null;
|
this.protocol = "HTTPS";
|
this.method = "POST";
|
this.version = "2016-01-20";
|
this.connectTimeout = 15000;
|
this.readTimeout = 15000;
|
this.maxAttempts = 3;
|
this.ignoreSSL = false;
|
}
|
|
private GetPublicKeyResponse _getPublicKey(GetPublicKeyRequest request) throws Exception {
|
validateModel(request);
|
RuntimeOptions runtime = RuntimeOptions.build(TeaConverter.buildMap(
|
new TeaPair("connectTimeout", this.connectTimeout),
|
new TeaPair("readTimeout", this.readTimeout),
|
new TeaPair("maxAttempts", this.maxAttempts),
|
new TeaPair("ignoreSSL", this.ignoreSSL)
|
));
|
return TeaModel.toModel(
|
this.doRequest("GetPublicKey", this.protocol, this.method, this.version, TeaModel.buildMap(request), null, runtime),
|
new GetPublicKeyResponse());
|
}
|
|
private PublicKey getPublicKey(String keyId, String keyVersionId) throws Exception {
|
GetPublicKeyRequest request = GetPublicKeyRequest.build(TeaConverter.buildMap(
|
new TeaPair("KeyId", keyId),
|
new TeaPair("KeyVersionId", keyVersionId)
|
));
|
GetPublicKeyResponse response = _getPublicKey(request);
|
String pemKey = response.publicKey;
|
pemKey = pemKey.replaceFirst("-----BEGIN PUBLIC KEY-----", "");
|
pemKey = pemKey.replaceFirst("-----END PUBLIC KEY-----", "");
|
pemKey = pemKey.replaceAll("\\s", "");
|
byte[] derKey = Base64.decode(pemKey);
|
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(derKey);
|
Security.addProvider(new BouncyCastleProvider());
|
return KeyFactory.getInstance("EC", "BC").generatePublic(keySpec);
|
}
|
|
private byte[] getZ(ECPublicKeyParameters ecPublicKeyParameters, ECDomainParameters ecDomainParameters) {
|
Digest digest = new SM3Digest();
|
digest.reset();
|
|
String userID = "1234567812345678";
|
addUserID(digest, userID.getBytes());
|
|
addFieldElement(digest, ecDomainParameters.getCurve().getA());
|
addFieldElement(digest, ecDomainParameters.getCurve().getB());
|
addFieldElement(digest, ecDomainParameters.getG().getAffineXCoord());
|
addFieldElement(digest, ecDomainParameters.getG().getAffineYCoord());
|
addFieldElement(digest, ecPublicKeyParameters.getQ().getAffineXCoord());
|
addFieldElement(digest, ecPublicKeyParameters.getQ().getAffineYCoord());
|
|
byte[] result = new byte[digest.getDigestSize()];
|
digest.doFinal(result, 0);
|
return result;
|
}
|
|
private void addUserID(Digest digest, byte[] userID) {
|
int len = userID.length * 8;
|
digest.update((byte) (len >> 8 & 0xFF));
|
digest.update((byte) (len & 0xFF));
|
digest.update(userID, 0, userID.length);
|
}
|
|
private void addFieldElement(Digest digest, ECFieldElement v) {
|
byte[] p = v.getEncoded();
|
digest.update(p, 0, p.length);
|
}
|
|
private byte[] calcSM3Digest(PublicKey pubKey, byte[] message) {
|
X9ECParameters x9ECParameters = GMNamedCurves.getByName(namedCurves.get(this.algorithm));
|
ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(),
|
x9ECParameters.getN());
|
BCECPublicKey localECPublicKey = (BCECPublicKey) pubKey;
|
ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(localECPublicKey.getQ(), ecDomainParameters);
|
byte[] z = getZ(ecPublicKeyParameters, ecDomainParameters);
|
Digest digest = new SM3Digest();
|
digest.update(z, 0, z.length);
|
digest.update(message, 0, message.length);
|
byte[] result = new byte[digest.getDigestSize()];
|
digest.doFinal(result, 0);
|
return result;
|
}
|
|
private AsymmetricSignResponse _asymmetricSign(AsymmetricSignRequest request) throws Exception {
|
validateModel(request);
|
RuntimeOptions runtime = RuntimeOptions.build(TeaConverter.buildMap(
|
new TeaPair("connectTimeout", this.connectTimeout),
|
new TeaPair("readTimeout", this.readTimeout),
|
new TeaPair("maxAttempts", this.maxAttempts),
|
new TeaPair("ignoreSSL", this.ignoreSSL)
|
));
|
return TeaModel.toModel(
|
this.doRequest("AsymmetricSign", this.protocol, this.method, this.version, TeaModel.buildMap(request), null, runtime),
|
new AsymmetricSignResponse());
|
}
|
|
private String asymmetricSign(String keyId, String keyVersionId, String algorithm, byte[] message) throws Exception {
|
byte[] digest;
|
if (algorithm.equals("SM2DSA")) {
|
if (this.publicKey == null) {
|
this.publicKey = getPublicKey(keyId, keyVersionId);
|
}
|
digest = calcSM3Digest(this.publicKey, message);
|
} else {
|
digest = MessageDigest.getInstance(digestAlgs.get(algorithm)).digest(message);
|
}
|
|
AsymmetricSignRequest request = AsymmetricSignRequest.build(TeaConverter.buildMap(
|
new TeaPair("keyId", keyId),
|
new TeaPair("keyVersionId", keyVersionId),
|
new TeaPair("algorithm", algorithm),
|
new TeaPair("digest", Base64.toBase64String(digest))
|
));
|
AsymmetricSignResponse response = _asymmetricSign(request);
|
return response.value;
|
}
|
|
/**
|
* 计算签名
|
*
|
* @param content 待签名的内容
|
* @return 签名值的Base64串
|
*/
|
public String sign(String content) throws Exception {
|
return asymmetricSign(this.keyId, this.keyVersionId, this.algorithm, content.getBytes(AlipayConstants.DEFAULT_CHARSET));
|
}
|
|
public String getAlgorithm() {
|
return this.algorithm;
|
}
|
|
public void setAlgorithm(String algorithm) {
|
this.algorithm = algorithm;
|
}
|
|
public String getKeyId() {
|
return this.keyId;
|
}
|
|
public void setKeyId(String keyId) {
|
this.keyId = keyId;
|
}
|
|
public String getKeyVersionId() {
|
return this.keyVersionId;
|
}
|
|
public void setKeyVersionId(String keyVersionId) {
|
this.keyVersionId = keyVersionId;
|
}
|
|
public PublicKey getPublicKey() {
|
return this.publicKey;
|
}
|
|
public void setPublicKey(PublicKey publicKey) {
|
this.publicKey = publicKey;
|
}
|
|
public String getProtocol() {
|
return this.protocol;
|
}
|
|
public void setProtocol(String protocol) {
|
this.protocol = protocol;
|
}
|
|
public String getMethod() {
|
return this.method;
|
}
|
|
public void setMethod(String method) {
|
this.method = method;
|
}
|
|
public String getVersion() {
|
return this.version;
|
}
|
|
public void setVersion(String version) {
|
this.version = version;
|
}
|
|
public Integer getConnectTimeout() {
|
return this.connectTimeout;
|
}
|
|
public void setConnectTimeout(Integer connectTimeout) {
|
this.connectTimeout = connectTimeout;
|
}
|
|
public Integer getReadTimeout() {
|
return this.readTimeout;
|
}
|
|
public void setReadTimeout(Integer readTimeout) {
|
this.readTimeout = readTimeout;
|
}
|
|
public Integer getMaxAttempts() {
|
return this.maxAttempts;
|
}
|
|
public void setMaxAttempts(Integer maxAttempts) {
|
this.maxAttempts = maxAttempts;
|
}
|
|
public boolean getIgnoreSSL() {
|
return this.ignoreSSL;
|
}
|
|
public void setIgnoreSSL(boolean ignoreSSL) {
|
this.ignoreSSL = ignoreSSL;
|
}
|
}
|