证书管理
This commit is contained in:
parent
65c690de48
commit
f1b07786c0
@ -35,14 +35,12 @@
|
|||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.bouncycastle</groupId>
|
<groupId>org.bouncycastle</groupId>
|
||||||
<artifactId>bcprov-jdk15on</artifactId>
|
<artifactId>bcprov-jdk18on</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.bouncycastle</groupId>
|
<groupId>org.bouncycastle</groupId>
|
||||||
<artifactId>bcpkix-jdk15on</artifactId>
|
<artifactId>bcpkix-jdk18on</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.sunyard.chsm.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.sunyard.chsm.model.entity.Cert;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author liulu
|
||||||
|
* @since 2024/11/6
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface CertMapper extends BaseMapper<Cert> {
|
||||||
|
|
||||||
|
|
||||||
|
default Cert selectBySN(String sn) {
|
||||||
|
Assert.hasText(sn, "证书序列号不能为空");
|
||||||
|
List<Cert> certs = selectList(new LambdaQueryWrapper<Cert>()
|
||||||
|
.eq(Cert::getSerialNumber, sn)
|
||||||
|
);
|
||||||
|
if (CollectionUtils.isEmpty(certs)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return certs.iterator().next();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -49,23 +49,23 @@ public class Subject {
|
|||||||
public String getDN() {
|
public String getDN() {
|
||||||
Assert.notNull(commonName, "通用名不能为空");
|
Assert.notNull(commonName, "通用名不能为空");
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
if (StringUtils.hasText(country)) {
|
if (StringUtils.hasText(commonName)) {
|
||||||
builder.append("C=").append(country).append(COMMA);
|
builder.append("CN=").append(commonName).append(COMMA);
|
||||||
}
|
|
||||||
if (StringUtils.hasText(province)) {
|
|
||||||
builder.append("ST=").append(province).append(COMMA);
|
|
||||||
}
|
|
||||||
if (StringUtils.hasText(city)) {
|
|
||||||
builder.append("L=").append(city).append(COMMA);
|
|
||||||
}
|
|
||||||
if (StringUtils.hasText(org)) {
|
|
||||||
builder.append("O=").append(org).append(COMMA);
|
|
||||||
}
|
}
|
||||||
if (StringUtils.hasText(orgUnit)) {
|
if (StringUtils.hasText(orgUnit)) {
|
||||||
builder.append("OU=").append(orgUnit).append(COMMA);
|
builder.append("OU=").append(orgUnit).append(COMMA);
|
||||||
}
|
}
|
||||||
if (StringUtils.hasText(commonName)) {
|
if (StringUtils.hasText(org)) {
|
||||||
builder.append("CN=").append(commonName).append(COMMA);
|
builder.append("O=").append(org).append(COMMA);
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(city)) {
|
||||||
|
builder.append("L=").append(city).append(COMMA);
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(province)) {
|
||||||
|
builder.append("ST=").append(province).append(COMMA);
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(country)) {
|
||||||
|
builder.append("C=").append(country).append(COMMA);
|
||||||
}
|
}
|
||||||
builder.deleteCharAt(builder.lastIndexOf(COMMA));
|
builder.deleteCharAt(builder.lastIndexOf(COMMA));
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
package com.sunyard.chsm.model.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author liulu
|
||||||
|
* @since 2024/11/6
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("sp_cert")
|
||||||
|
public class Cert {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Long applicationId;
|
||||||
|
private Long keyId;
|
||||||
|
private Long keyRecordId;
|
||||||
|
// SM2, RSA
|
||||||
|
private String keyAlg;
|
||||||
|
// ENC, SIGN, CERT_CHAIN
|
||||||
|
private String certType;
|
||||||
|
private Boolean single;
|
||||||
|
private String version;
|
||||||
|
private String subject;
|
||||||
|
private String serialNumber;
|
||||||
|
private String issuerDn;
|
||||||
|
private Date notBefore;
|
||||||
|
private Date notAfter;
|
||||||
|
private String keyUsage;
|
||||||
|
private String pubKey;
|
||||||
|
private String encPriKey;
|
||||||
|
private String certText;
|
||||||
|
|
||||||
|
private String remark;
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
}
|
@ -34,7 +34,7 @@ import org.bouncycastle.jce.spec.ECParameterSpec;
|
|||||||
import org.bouncycastle.math.ec.ECCurve;
|
import org.bouncycastle.math.ec.ECCurve;
|
||||||
import org.bouncycastle.math.ec.ECPoint;
|
import org.bouncycastle.math.ec.ECPoint;
|
||||||
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
|
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
|
||||||
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
import org.bouncycastle.util.io.pem.PemObject;
|
import org.bouncycastle.util.io.pem.PemObject;
|
||||||
import org.bouncycastle.util.io.pem.PemReader;
|
import org.bouncycastle.util.io.pem.PemReader;
|
||||||
import org.bouncycastle.util.io.pem.PemWriter;
|
import org.bouncycastle.util.io.pem.PemWriter;
|
||||||
@ -56,6 +56,7 @@ import java.security.spec.ECGenParameterSpec;
|
|||||||
import java.security.spec.InvalidKeySpecException;
|
import java.security.spec.InvalidKeySpecException;
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
import java.security.spec.X509EncodedKeySpec;
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 这个工具类的方法,也适用于其他基于BC库的ECC算法
|
* 这个工具类的方法,也适用于其他基于BC库的ECC算法
|
||||||
@ -118,7 +119,7 @@ public class BCECUtils {
|
|||||||
*/
|
*/
|
||||||
public static ECPrivateKeyParameters createECPrivateKeyParameters(
|
public static ECPrivateKeyParameters createECPrivateKeyParameters(
|
||||||
String dHex, ECDomainParameters domainParameters) {
|
String dHex, ECDomainParameters domainParameters) {
|
||||||
return createECPrivateKeyParameters(ByteUtils.fromHexString(dHex), domainParameters);
|
return createECPrivateKeyParameters(Hex.decode(dHex), domainParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,6 +150,41 @@ public class BCECUtils {
|
|||||||
return new ECPrivateKeyParameters(d, domainParameters);
|
return new ECPrivateKeyParameters(d, domainParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getHexPubKey(BCECPublicKey publicKey) {
|
||||||
|
byte[] x = publicKey.getQ().getXCoord().getEncoded();
|
||||||
|
byte[] y = publicKey.getQ().getYCoord().getEncoded();
|
||||||
|
return Hex.toHexString(x) + Hex.toHexString(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BCECPublicKey createPublicKey(String hexPubKey) {
|
||||||
|
byte[] decode = Hex.decode(hexPubKey);
|
||||||
|
|
||||||
|
// 将字节数组转换为 BigInteger
|
||||||
|
BigInteger x = new BigInteger(1, Arrays.copyOfRange(decode, 0, 32));
|
||||||
|
BigInteger y = new BigInteger(1, Arrays.copyOfRange(decode, 32, 64));
|
||||||
|
|
||||||
|
// 创建ECPoint
|
||||||
|
ECDomainParameters params = BCSM2Utils.DOMAIN_PARAMS;
|
||||||
|
ECPoint q = params.getCurve().createPoint(x, y);
|
||||||
|
ECPublicKeyParameters pubKeyParam = new ECPublicKeyParameters(q, params);
|
||||||
|
|
||||||
|
ECParameterSpec sm2Spec = new ECParameterSpec(params.getCurve(), params.getG(), params.getN(), params.getH());
|
||||||
|
|
||||||
|
return new BCECPublicKey(ALGO_NAME_EC, pubKeyParam, sm2Spec, BouncyCastleProvider.CONFIGURATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BCECPrivateKey createPrivateKey(String hexd) {
|
||||||
|
return createPrivateKey(Hex.decode(hexd));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BCECPrivateKey createPrivateKey(byte[] dBytes) {
|
||||||
|
BigInteger d = new BigInteger(1, dBytes);
|
||||||
|
ECDomainParameters params = BCSM2Utils.DOMAIN_PARAMS;
|
||||||
|
ECPrivateKeyParameters priKeyParam = new ECPrivateKeyParameters(d, params);
|
||||||
|
ECParameterSpec sm2Spec = new ECParameterSpec(params.getCurve(), params.getG(), params.getN(), params.getH());
|
||||||
|
return new BCECPrivateKey(ALGO_NAME_EC, priKeyParam, null, sm2Spec, BouncyCastleProvider.CONFIGURATION);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据EC私钥构造EC公钥
|
* 根据EC私钥构造EC公钥
|
||||||
*
|
*
|
||||||
@ -182,7 +218,7 @@ public class BCECUtils {
|
|||||||
*/
|
*/
|
||||||
public static ECPublicKeyParameters createECPublicKeyParameters(
|
public static ECPublicKeyParameters createECPublicKeyParameters(
|
||||||
String xHex, String yHex, ECCurve curve, ECDomainParameters domainParameters) {
|
String xHex, String yHex, ECCurve curve, ECDomainParameters domainParameters) {
|
||||||
return createECPublicKeyParameters(ByteUtils.fromHexString(xHex), ByteUtils.fromHexString(yHex),
|
return createECPublicKeyParameters(Hex.decode(xHex), Hex.decode(yHex),
|
||||||
curve, domainParameters);
|
curve, domainParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,47 @@
|
|||||||
package com.sunyard.chsm.utils.gm;
|
package com.sunyard.chsm.utils.gm;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
|
import org.bouncycastle.util.io.pem.PemObject;
|
||||||
|
import org.bouncycastle.util.io.pem.PemWriter;
|
||||||
|
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.security.Provider;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class GMBaseUtil {
|
public class GMBaseUtil {
|
||||||
|
|
||||||
|
protected static final Provider BC = new BouncyCastleProvider();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
Security.addProvider(new BouncyCastleProvider());
|
Security.addProvider(BC);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected static byte[] readPem(String type, String pem) {
|
||||||
|
try {
|
||||||
|
String replaced = pem.replace("-----BEGIN " + type + "-----", "")
|
||||||
|
.replace("\n", "")
|
||||||
|
.replace(System.lineSeparator(), "")
|
||||||
|
.replace("-----END " + type + "-----", "")
|
||||||
|
.replace(" ", "+");
|
||||||
|
return Base64.getDecoder().decode(replaced);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("read pem error", ex);
|
||||||
|
throw new IllegalArgumentException("PEM内容格式错误,读物异常");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String writePem(String type, byte[] content) {
|
||||||
|
try (StringWriter str = new StringWriter(); PemWriter pemWriter = new PemWriter(str);) {
|
||||||
|
PemObject pem = new PemObject(type, content);
|
||||||
|
pemWriter.writeObject(pem);
|
||||||
|
return str.toString();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("write pem error", ex);
|
||||||
|
throw new IllegalArgumentException("PEM内容格式错误,写入异常");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,294 +0,0 @@
|
|||||||
package com.sunyard.chsm.utils.gm;
|
|
||||||
|
|
||||||
import org.bouncycastle.asn1.ASN1EncodableVector;
|
|
||||||
import org.bouncycastle.asn1.ASN1Encoding;
|
|
||||||
import org.bouncycastle.asn1.ASN1Integer;
|
|
||||||
import org.bouncycastle.asn1.ASN1Primitive;
|
|
||||||
import org.bouncycastle.asn1.ASN1Sequence;
|
|
||||||
import org.bouncycastle.asn1.DERSequence;
|
|
||||||
import org.bouncycastle.crypto.CipherParameters;
|
|
||||||
import org.bouncycastle.crypto.CryptoException;
|
|
||||||
import org.bouncycastle.crypto.CryptoServicesRegistrar;
|
|
||||||
import org.bouncycastle.crypto.Digest;
|
|
||||||
import org.bouncycastle.crypto.digests.SM3Digest;
|
|
||||||
import org.bouncycastle.crypto.params.ECDomainParameters;
|
|
||||||
import org.bouncycastle.crypto.params.ECKeyParameters;
|
|
||||||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
|
||||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
|
||||||
import org.bouncycastle.crypto.params.ParametersWithID;
|
|
||||||
import org.bouncycastle.crypto.params.ParametersWithRandom;
|
|
||||||
import org.bouncycastle.crypto.signers.DSAKCalculator;
|
|
||||||
import org.bouncycastle.crypto.signers.RandomDSAKCalculator;
|
|
||||||
import org.bouncycastle.math.ec.ECAlgorithms;
|
|
||||||
import org.bouncycastle.math.ec.ECConstants;
|
|
||||||
import org.bouncycastle.math.ec.ECFieldElement;
|
|
||||||
import org.bouncycastle.math.ec.ECMultiplier;
|
|
||||||
import org.bouncycastle.math.ec.ECPoint;
|
|
||||||
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
|
|
||||||
import org.bouncycastle.util.Arrays;
|
|
||||||
import org.bouncycastle.util.encoders.Hex;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.math.BigInteger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 有的国密需求是用户可以自己做预处理,签名验签只是对预处理的结果进行签名和验签
|
|
||||||
*/
|
|
||||||
public class SM2PreprocessSigner implements ECConstants {
|
|
||||||
private static final int DIGEST_LENGTH = 32; // bytes
|
|
||||||
|
|
||||||
private final DSAKCalculator kCalculator = new RandomDSAKCalculator();
|
|
||||||
private Digest digest = null;
|
|
||||||
|
|
||||||
private ECDomainParameters ecParams;
|
|
||||||
private ECPoint pubPoint;
|
|
||||||
private ECKeyParameters ecKey;
|
|
||||||
private byte[] userID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化
|
|
||||||
*
|
|
||||||
* @param forSigning true表示用于签名,false表示用于验签
|
|
||||||
* @param param
|
|
||||||
*/
|
|
||||||
public void init(boolean forSigning, CipherParameters param) {
|
|
||||||
init(forSigning, new SM3Digest(), param);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化
|
|
||||||
*
|
|
||||||
* @param forSigning true表示用于签名,false表示用于验签
|
|
||||||
* @param digest SM2算法的话,一般是采用SM3摘要算法
|
|
||||||
* @param param
|
|
||||||
* @throws RuntimeException
|
|
||||||
*/
|
|
||||||
public void init(boolean forSigning, Digest digest, CipherParameters param) throws RuntimeException {
|
|
||||||
CipherParameters baseParam;
|
|
||||||
|
|
||||||
if (digest.getDigestSize() != DIGEST_LENGTH) {
|
|
||||||
throw new RuntimeException("Digest size must be " + DIGEST_LENGTH);
|
|
||||||
}
|
|
||||||
this.digest = digest;
|
|
||||||
|
|
||||||
if (param instanceof ParametersWithID) {
|
|
||||||
baseParam = ((ParametersWithID) param).getParameters();
|
|
||||||
userID = ((ParametersWithID) param).getID();
|
|
||||||
} else {
|
|
||||||
baseParam = param;
|
|
||||||
userID = Hex.decode("31323334353637383132333435363738"); // the default value
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forSigning) {
|
|
||||||
if (baseParam instanceof ParametersWithRandom) {
|
|
||||||
ParametersWithRandom rParam = (ParametersWithRandom) baseParam;
|
|
||||||
|
|
||||||
ecKey = (ECKeyParameters) rParam.getParameters();
|
|
||||||
ecParams = ecKey.getParameters();
|
|
||||||
kCalculator.init(ecParams.getN(), rParam.getRandom());
|
|
||||||
} else {
|
|
||||||
ecKey = (ECKeyParameters) baseParam;
|
|
||||||
ecParams = ecKey.getParameters();
|
|
||||||
kCalculator.init(ecParams.getN(), CryptoServicesRegistrar.getSecureRandom());
|
|
||||||
}
|
|
||||||
pubPoint = createBasePointMultiplier().multiply(ecParams.getG(), ((ECPrivateKeyParameters) ecKey).getD()).normalize();
|
|
||||||
} else {
|
|
||||||
ecKey = (ECKeyParameters) baseParam;
|
|
||||||
ecParams = ecKey.getParameters();
|
|
||||||
pubPoint = ((ECPublicKeyParameters) ecKey).getQ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 预处理,辅助方法
|
|
||||||
* ZA=H256(ENT LA ∥ IDA ∥ a ∥ b ∥ xG ∥yG ∥ xA ∥ yA)。
|
|
||||||
* M=ZA ∥ M;
|
|
||||||
* e = Hv(M)
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public byte[] preprocess(byte[] m) {
|
|
||||||
return preprocess(m, 0, m.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public byte[] preprocess(byte[] m, int off, int len) {
|
|
||||||
byte[] z = getZ(userID);
|
|
||||||
digest.update(z, 0, z.length);
|
|
||||||
digest.update(m, off, len);
|
|
||||||
byte[] eHash = new byte[DIGEST_LENGTH];
|
|
||||||
digest.doFinal(eHash, 0);
|
|
||||||
return eHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean verifySignature(byte[] eHash, byte[] signature) {
|
|
||||||
try {
|
|
||||||
BigInteger[] rs = derDecode(signature);
|
|
||||||
if (rs != null) {
|
|
||||||
return verifySignature(eHash, rs[0], rs[1]);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reset() {
|
|
||||||
digest.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] generateSignature(byte[] eHash) throws CryptoException {
|
|
||||||
BigInteger n = ecParams.getN();
|
|
||||||
BigInteger e = calculateE(eHash);
|
|
||||||
BigInteger d = ((ECPrivateKeyParameters) ecKey).getD();
|
|
||||||
|
|
||||||
BigInteger r, s;
|
|
||||||
|
|
||||||
ECMultiplier basePointMultiplier = createBasePointMultiplier();
|
|
||||||
|
|
||||||
// 5.2.1 Draft RFC: SM2 Public Key Algorithms
|
|
||||||
do // generate s
|
|
||||||
{
|
|
||||||
BigInteger k;
|
|
||||||
do // generate r
|
|
||||||
{
|
|
||||||
// A3
|
|
||||||
k = kCalculator.nextK();
|
|
||||||
|
|
||||||
// A4
|
|
||||||
ECPoint p = basePointMultiplier.multiply(ecParams.getG(), k).normalize();
|
|
||||||
|
|
||||||
// A5
|
|
||||||
r = e.add(p.getAffineXCoord().toBigInteger()).mod(n);
|
|
||||||
}
|
|
||||||
while (r.equals(ZERO) || r.add(k).equals(n));
|
|
||||||
|
|
||||||
// A6
|
|
||||||
BigInteger dPlus1ModN = d.add(ONE).modInverse(n);
|
|
||||||
|
|
||||||
s = k.subtract(r.multiply(d)).mod(n);
|
|
||||||
s = dPlus1ModN.multiply(s).mod(n);
|
|
||||||
}
|
|
||||||
while (s.equals(ZERO));
|
|
||||||
|
|
||||||
// A7
|
|
||||||
try {
|
|
||||||
return derEncode(r, s);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
throw new CryptoException("unable to encode signature: " + ex.getMessage(), ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean verifySignature(byte[] eHash, BigInteger r, BigInteger s) {
|
|
||||||
BigInteger n = ecParams.getN();
|
|
||||||
|
|
||||||
// 5.3.1 Draft RFC: SM2 Public Key Algorithms
|
|
||||||
// B1
|
|
||||||
if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// B2
|
|
||||||
if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// B3 eHash
|
|
||||||
|
|
||||||
// B4
|
|
||||||
BigInteger e = calculateE(eHash);
|
|
||||||
|
|
||||||
// B5
|
|
||||||
BigInteger t = r.add(s).mod(n);
|
|
||||||
if (t.equals(ZERO)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// B6
|
|
||||||
ECPoint q = ((ECPublicKeyParameters) ecKey).getQ();
|
|
||||||
ECPoint x1y1 = ECAlgorithms.sumOfTwoMultiplies(ecParams.getG(), s, q, t).normalize();
|
|
||||||
if (x1y1.isInfinity()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// B7
|
|
||||||
BigInteger expectedR = e.add(x1y1.getAffineXCoord().toBigInteger()).mod(n);
|
|
||||||
|
|
||||||
return expectedR.equals(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] digestDoFinal() {
|
|
||||||
byte[] result = new byte[digest.getDigestSize()];
|
|
||||||
digest.doFinal(result, 0);
|
|
||||||
|
|
||||||
reset();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getZ(byte[] userID) {
|
|
||||||
digest.reset();
|
|
||||||
|
|
||||||
addUserID(digest, userID);
|
|
||||||
|
|
||||||
addFieldElement(digest, ecParams.getCurve().getA());
|
|
||||||
addFieldElement(digest, ecParams.getCurve().getB());
|
|
||||||
addFieldElement(digest, ecParams.getG().getAffineXCoord());
|
|
||||||
addFieldElement(digest, ecParams.getG().getAffineYCoord());
|
|
||||||
addFieldElement(digest, pubPoint.getAffineXCoord());
|
|
||||||
addFieldElement(digest, pubPoint.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ECMultiplier createBasePointMultiplier() {
|
|
||||||
return new FixedPointCombMultiplier();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected BigInteger calculateE(byte[] message) {
|
|
||||||
return new BigInteger(1, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected BigInteger[] derDecode(byte[] encoding)
|
|
||||||
throws IOException {
|
|
||||||
ASN1Sequence seq = ASN1Sequence.getInstance(ASN1Primitive.fromByteArray(encoding));
|
|
||||||
if (seq.size() != 2) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInteger r = ASN1Integer.getInstance(seq.getObjectAt(0)).getValue();
|
|
||||||
BigInteger s = ASN1Integer.getInstance(seq.getObjectAt(1)).getValue();
|
|
||||||
|
|
||||||
byte[] expectedEncoding = derEncode(r, s);
|
|
||||||
if (!Arrays.constantTimeAreEqual(expectedEncoding, encoding)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BigInteger[]{r, s};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected byte[] derEncode(BigInteger r, BigInteger s)
|
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
ASN1EncodableVector v = new ASN1EncodableVector();
|
|
||||||
v.add(new ASN1Integer(r));
|
|
||||||
v.add(new ASN1Integer(s));
|
|
||||||
return new DERSequence(v).getEncoded(ASN1Encoding.DER);
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,12 +15,14 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
|||||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||||
import org.bouncycastle.jce.spec.ECPublicKeySpec;
|
import org.bouncycastle.jce.spec.ECPublicKeySpec;
|
||||||
import org.bouncycastle.math.ec.ECPoint;
|
import org.bouncycastle.math.ec.ECPoint;
|
||||||
|
import org.bouncycastle.openssl.PEMParser;
|
||||||
import org.bouncycastle.operator.InputDecryptorProvider;
|
import org.bouncycastle.operator.InputDecryptorProvider;
|
||||||
import org.bouncycastle.pkcs.PKCS12PfxPdu;
|
import org.bouncycastle.pkcs.PKCS12PfxPdu;
|
||||||
import org.bouncycastle.pkcs.PKCS12SafeBag;
|
import org.bouncycastle.pkcs.PKCS12SafeBag;
|
||||||
import org.bouncycastle.pkcs.PKCS12SafeBagFactory;
|
import org.bouncycastle.pkcs.PKCS12SafeBagFactory;
|
||||||
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
|
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
|
||||||
import org.bouncycastle.pkcs.jcajce.JcePKCSPBEInputDecryptorProviderBuilder;
|
import org.bouncycastle.pkcs.jcajce.JcePKCSPBEInputDecryptorProviderBuilder;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -62,6 +64,12 @@ public class BCSM2CertUtils extends GMBaseUtil {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static X509Certificate getX509Cert(String certText) throws CertificateException, NoSuchProviderException {
|
||||||
|
Assert.hasText(certText, "证书内容不能为空");
|
||||||
|
byte[] bytes = readPem(PEMParser.TYPE_CERTIFICATE, certText);
|
||||||
|
return getX509Certificate(new ByteArrayInputStream(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
public static X509Certificate getX509Certificate(String certFilePath) throws IOException, CertificateException,
|
public static X509Certificate getX509Certificate(String certFilePath) throws IOException, CertificateException,
|
||||||
NoSuchProviderException {
|
NoSuchProviderException {
|
||||||
try (InputStream is = Files.newInputStream(Paths.get(certFilePath))) {
|
try (InputStream is = Files.newInputStream(Paths.get(certFilePath))) {
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
package com.sunyard.chsm.utils.gm.cert;
|
|
||||||
|
|
||||||
import java.math.BigInteger;
|
|
||||||
|
|
||||||
public interface CertSNAllocator {
|
|
||||||
BigInteger nextSerialNumber() throws Exception;
|
|
||||||
}
|
|
@ -1,12 +1,15 @@
|
|||||||
package com.sunyard.chsm.utils.gm.cert;
|
package com.sunyard.chsm.utils.gm.cert;
|
||||||
|
|
||||||
import com.sunyard.chsm.utils.gm.cert.exception.InvalidX500NameException;
|
import com.sunyard.chsm.utils.gm.GMBaseUtil;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||||
import org.bouncycastle.asn1.x500.X500Name;
|
import org.bouncycastle.asn1.x500.X500Name;
|
||||||
import org.bouncycastle.asn1.x500.X500NameBuilder;
|
import org.bouncycastle.asn1.x500.X500NameBuilder;
|
||||||
import org.bouncycastle.asn1.x500.style.BCStyle;
|
import org.bouncycastle.asn1.x500.style.BCStyle;
|
||||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||||
|
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
|
import org.bouncycastle.openssl.PEMParser;
|
||||||
import org.bouncycastle.operator.ContentSigner;
|
import org.bouncycastle.operator.ContentSigner;
|
||||||
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
|
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
|
||||||
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
|
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
|
||||||
@ -17,21 +20,22 @@ import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
|
|||||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
|
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
|
||||||
|
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class CommonCertUtils {
|
public class CommonCertUtils extends GMBaseUtil {
|
||||||
/**
|
/**
|
||||||
* 如果不知道怎么填充names,可以查看org.bouncycastle.asn1.x500.style.BCStyle这个类,
|
* 如果不知道怎么填充names,可以查看org.bouncycastle.asn1.x500.style.BCStyle这个类,
|
||||||
* names的key值必须是BCStyle.DefaultLookUp中存在的(可以不关心大小写)
|
* names的key值必须是BCStyle.DefaultLookUp中存在的(可以不关心大小写)
|
||||||
*
|
*
|
||||||
* @param names
|
* @param names
|
||||||
* @return
|
* @return
|
||||||
* @throws InvalidX500NameException
|
* @throws IllegalArgumentException
|
||||||
*/
|
*/
|
||||||
public static X500Name buildX500Name(Map<String, String> names) throws InvalidX500NameException {
|
public static X500Name buildX500Name(Map<String, String> names) {
|
||||||
if (names == null || names.size() == 0) {
|
if (names == null || names.size() == 0) {
|
||||||
throw new InvalidX500NameException("names can not be empty");
|
throw new IllegalArgumentException("names can not be empty");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
X500NameBuilder builder = new X500NameBuilder();
|
X500NameBuilder builder = new X500NameBuilder();
|
||||||
@ -45,14 +49,26 @@ public class CommonCertUtils {
|
|||||||
}
|
}
|
||||||
return builder.build();
|
return builder.build();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
throw new InvalidX500NameException(ex.getMessage(), ex);
|
throw new IllegalArgumentException(ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PKCS10CertificationRequest createCSR(X500Name subject, SM2PublicKey pubKey, PrivateKey priKey,
|
@SneakyThrows
|
||||||
String signAlgo) throws OperatorCreationException {
|
public static String generateP10(String dn, PublicKey pubKey, PrivateKey priKey) {
|
||||||
|
X500Name subject = new X500Name(dn);
|
||||||
|
JcaPKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder(subject, pubKey);
|
||||||
|
ContentSigner signer = new JcaContentSignerBuilder("SM3withSM2")
|
||||||
|
.setProvider(BC).build(priKey);
|
||||||
|
|
||||||
|
// 生成 P10 证书请求
|
||||||
|
PKCS10CertificationRequest csr = csrBuilder.build(signer);
|
||||||
|
|
||||||
|
return writePem(PEMParser.TYPE_CERTIFICATE_REQUEST, csr.getEncoded());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PKCS10CertificationRequest createCSR(X500Name subject, BCECPublicKey pubKey, PrivateKey priKey) throws OperatorCreationException {
|
||||||
PKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder(subject, pubKey);
|
PKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder(subject, pubKey);
|
||||||
ContentSigner signerBuilder = new JcaContentSignerBuilder(signAlgo)
|
ContentSigner signerBuilder = new JcaContentSignerBuilder("SM3withSM2")
|
||||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME).build(priKey);
|
.setProvider(BouncyCastleProvider.PROVIDER_NAME).build(priKey);
|
||||||
return csrBuilder.build(signerBuilder);
|
return csrBuilder.build(signerBuilder);
|
||||||
}
|
}
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
package com.sunyard.chsm.utils.gm.cert;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.RandomAccessFile;
|
|
||||||
import java.math.BigInteger;
|
|
||||||
|
|
||||||
public class FileSNAllocator implements CertSNAllocator {
|
|
||||||
private static final String SN_FILENAME = "sn.dat";
|
|
||||||
private static String snFilePath;
|
|
||||||
|
|
||||||
static {
|
|
||||||
ClassLoader loader = FileSNAllocator.class.getClassLoader();
|
|
||||||
snFilePath = loader.getResource(SN_FILENAME).getPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized BigInteger nextSerialNumber() throws Exception {
|
|
||||||
BigInteger sn = readSN();
|
|
||||||
writeSN(sn.add(BigInteger.ONE));
|
|
||||||
return sn;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigInteger readSN() throws IOException {
|
|
||||||
RandomAccessFile raf = null;
|
|
||||||
try {
|
|
||||||
raf = new RandomAccessFile(snFilePath, "r");
|
|
||||||
byte[] data = new byte[(int) raf.length()];
|
|
||||||
raf.read(data);
|
|
||||||
String snStr = new String(data);
|
|
||||||
return new BigInteger(snStr);
|
|
||||||
} finally {
|
|
||||||
if (raf != null) {
|
|
||||||
raf.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeSN(BigInteger sn) throws IOException {
|
|
||||||
RandomAccessFile raf = null;
|
|
||||||
try {
|
|
||||||
raf = new RandomAccessFile(snFilePath, "rw");
|
|
||||||
raf.writeBytes(sn.toString(10));
|
|
||||||
} finally {
|
|
||||||
if (raf != null) {
|
|
||||||
raf.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,78 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* This is simplified version of the RandomSerialNumberGenerator from the project
|
|
||||||
* https://github.com/xipki/xipki.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.sunyard.chsm.utils.gm.cert;
|
|
||||||
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Random serial number generator.
|
|
||||||
*
|
|
||||||
* This class is thread safe.
|
|
||||||
*
|
|
||||||
* @author Lijun Liao
|
|
||||||
*/
|
|
||||||
public class RandomSNAllocator implements CertSNAllocator {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The highest bit is always set to 1, so the effective bit length is bitLen - 1. To ensure that
|
|
||||||
* at least 64 bit entropy, bitLen must be at least 65.
|
|
||||||
*/
|
|
||||||
private final static int MIN_SERIALNUMBER_SIZE = 65;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Since serial number should be positive and maximal 20 bytes, the maximal value of bitLen is
|
|
||||||
* 159.
|
|
||||||
*/
|
|
||||||
private final static int MAX_SERIALNUMBER_SIZE = 159;
|
|
||||||
|
|
||||||
private static int[] AND_MASKS = new int[] {0xFF, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F};
|
|
||||||
|
|
||||||
private static int[] OR_MASKS = new int[] {0x80, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40};
|
|
||||||
|
|
||||||
private final SecureRandom random;
|
|
||||||
|
|
||||||
private final int bitLen;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor with the bitLen = 65.
|
|
||||||
*/
|
|
||||||
public RandomSNAllocator() {
|
|
||||||
this(MIN_SERIALNUMBER_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor with the specification of bitLen.
|
|
||||||
* @param bitLen bit length of the serial number. The highest bit is always set to 1, so the
|
|
||||||
* effective bit length is bitLen - 1. Valid value is [65, 159].
|
|
||||||
*/
|
|
||||||
public RandomSNAllocator(int bitLen) {
|
|
||||||
if (bitLen < MIN_SERIALNUMBER_SIZE || bitLen > MAX_SERIALNUMBER_SIZE) {
|
|
||||||
throw new IllegalArgumentException(String.format(
|
|
||||||
"%s may not be out of the range [%d, %d]: %d",
|
|
||||||
"bitLen", MIN_SERIALNUMBER_SIZE, MAX_SERIALNUMBER_SIZE, bitLen));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.random = new SecureRandom();
|
|
||||||
this.bitLen = bitLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BigInteger nextSerialNumber() {
|
|
||||||
final byte[] rdnBytes = new byte[(bitLen + 7) / 8];
|
|
||||||
final int ci = bitLen % 8;
|
|
||||||
|
|
||||||
random.nextBytes(rdnBytes);
|
|
||||||
if (ci != 0) {
|
|
||||||
rdnBytes[0] = (byte) (rdnBytes[0] & AND_MASKS[ci]);
|
|
||||||
}
|
|
||||||
rdnBytes[0] = (byte) (rdnBytes[0] | OR_MASKS[ci]);
|
|
||||||
|
|
||||||
return new BigInteger(1, rdnBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
package com.sunyard.chsm.utils.gm.cert;
|
|
||||||
|
|
||||||
import org.bouncycastle.asn1.ASN1Encodable;
|
|
||||||
import org.bouncycastle.asn1.ASN1Encoding;
|
|
||||||
import org.bouncycastle.asn1.ASN1Primitive;
|
|
||||||
import org.bouncycastle.asn1.DERBitString;
|
|
||||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
|
||||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
|
||||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
|
||||||
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
|
|
||||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
|
|
||||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
|
|
||||||
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
|
|
||||||
import org.bouncycastle.jcajce.provider.config.ProviderConfiguration;
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.spec.ECParameterSpec;
|
|
||||||
|
|
||||||
public class SM2PrivateKey extends BCECPrivateKey {
|
|
||||||
private transient DERBitString sm2PublicKey;
|
|
||||||
private boolean withCompression;
|
|
||||||
|
|
||||||
public SM2PrivateKey(BCECPrivateKey privateKey, BCECPublicKey publicKey) {
|
|
||||||
super(privateKey.getAlgorithm(), privateKey);
|
|
||||||
this.sm2PublicKey = getSM2PublicKeyDetails(new SM2PublicKey(publicKey.getAlgorithm(), publicKey));
|
|
||||||
this.withCompression = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPointFormat(String style) {
|
|
||||||
withCompression = !("UNCOMPRESSED".equalsIgnoreCase(style));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a PKCS8 representation of the key. The sequence returned
|
|
||||||
* represents a full PrivateKeyInfo object.
|
|
||||||
*
|
|
||||||
* @return a PKCS8 representation of the key.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public byte[] getEncoded() {
|
|
||||||
ECParameterSpec ecSpec = getParams();
|
|
||||||
ProviderConfiguration configuration = BouncyCastleProvider.CONFIGURATION;
|
|
||||||
ASN1Encodable params = SM2PublicKey.ID_SM2_PUBKEY_PARAM;
|
|
||||||
|
|
||||||
int orderBitLength;
|
|
||||||
if (ecSpec == null) {
|
|
||||||
orderBitLength = ECUtil.getOrderBitLength(configuration, null, this.getS());
|
|
||||||
} else {
|
|
||||||
orderBitLength = ECUtil.getOrderBitLength(configuration, ecSpec.getOrder(), this.getS());
|
|
||||||
}
|
|
||||||
|
|
||||||
PrivateKeyInfo info;
|
|
||||||
org.bouncycastle.asn1.sec.ECPrivateKey keyStructure;
|
|
||||||
|
|
||||||
if (sm2PublicKey != null) {
|
|
||||||
keyStructure = new org.bouncycastle.asn1.sec.ECPrivateKey(orderBitLength, this.getS(), sm2PublicKey, params);
|
|
||||||
} else {
|
|
||||||
keyStructure = new org.bouncycastle.asn1.sec.ECPrivateKey(orderBitLength, this.getS(), params);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
info = new PrivateKeyInfo(new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, params), keyStructure);
|
|
||||||
|
|
||||||
return info.getEncoded(ASN1Encoding.DER);
|
|
||||||
} catch (IOException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private DERBitString getSM2PublicKeyDetails(SM2PublicKey pub) {
|
|
||||||
try {
|
|
||||||
SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(ASN1Primitive.fromByteArray(pub.getEncoded()));
|
|
||||||
|
|
||||||
return info.getPublicKeyData();
|
|
||||||
} catch (IOException e) { // should never happen
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package com.sunyard.chsm.utils.gm.cert;
|
|
||||||
|
|
||||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
|
||||||
import org.bouncycastle.asn1.ASN1OctetString;
|
|
||||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
|
||||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
|
||||||
import org.bouncycastle.asn1.x9.X9ECPoint;
|
|
||||||
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
|
|
||||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
|
|
||||||
import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
|
|
||||||
|
|
||||||
public class SM2PublicKey extends BCECPublicKey {
|
|
||||||
public static final ASN1ObjectIdentifier ID_SM2_PUBKEY_PARAM = new ASN1ObjectIdentifier("1.2.156.10197.1.301");
|
|
||||||
|
|
||||||
private boolean withCompression;
|
|
||||||
|
|
||||||
public SM2PublicKey(BCECPublicKey key) {
|
|
||||||
super(key.getAlgorithm(), key);
|
|
||||||
this.withCompression = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SM2PublicKey(String algorithm, BCECPublicKey key) {
|
|
||||||
super(algorithm, key);
|
|
||||||
this.withCompression = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getEncoded() {
|
|
||||||
ASN1OctetString p = ASN1OctetString.getInstance(
|
|
||||||
new X9ECPoint(getQ(), withCompression).toASN1Primitive());
|
|
||||||
|
|
||||||
// stored curve is null if ImplicitlyCa
|
|
||||||
SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
|
|
||||||
new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, ID_SM2_PUBKEY_PARAM),
|
|
||||||
p.getOctets());
|
|
||||||
|
|
||||||
return KeyUtil.getEncodedSubjectPublicKeyInfo(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPointFormat(String style) {
|
|
||||||
withCompression = !("UNCOMPRESSED".equalsIgnoreCase(style));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,280 +0,0 @@
|
|||||||
package com.sunyard.chsm.utils.gm.cert;
|
|
||||||
|
|
||||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
|
||||||
import org.bouncycastle.asn1.DERIA5String;
|
|
||||||
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
|
|
||||||
import org.bouncycastle.asn1.x500.RDN;
|
|
||||||
import org.bouncycastle.asn1.x500.X500Name;
|
|
||||||
import org.bouncycastle.asn1.x500.style.BCStyle;
|
|
||||||
import org.bouncycastle.asn1.x500.style.IETFUtils;
|
|
||||||
import org.bouncycastle.asn1.x509.BasicConstraints;
|
|
||||||
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
|
|
||||||
import org.bouncycastle.asn1.x509.Extension;
|
|
||||||
import org.bouncycastle.asn1.x509.GeneralName;
|
|
||||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
|
||||||
import org.bouncycastle.asn1.x509.KeyPurposeId;
|
|
||||||
import org.bouncycastle.asn1.x509.KeyUsage;
|
|
||||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
|
||||||
import org.bouncycastle.cert.X509v3CertificateBuilder;
|
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
|
||||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
|
||||||
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class SM2X509CertMaker {
|
|
||||||
|
|
||||||
private static enum CertLevel {
|
|
||||||
RootCA,
|
|
||||||
SubCA,
|
|
||||||
EndEntity
|
|
||||||
} // class CertLevel
|
|
||||||
|
|
||||||
public static final String SIGN_ALGO_SM3WITHSM2 = "SM3withSM2";
|
|
||||||
|
|
||||||
private long certExpire;
|
|
||||||
private X500Name issuerDN;
|
|
||||||
private CertSNAllocator snAllocator;
|
|
||||||
private KeyPair issuerKeyPair;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param issuerKeyPair 证书颁发者的密钥对。
|
|
||||||
* 其实一般的CA的私钥都是要严格保护的。
|
|
||||||
* 一般CA的私钥都会放在加密卡/加密机里,证书的签名由加密卡/加密机完成。
|
|
||||||
* 这里仅是为了演示BC库签发证书的用法,所以暂时不作太多要求。
|
|
||||||
* @param certExpire 证书有效时间,单位毫秒
|
|
||||||
* @param issuer 证书颁发者信息
|
|
||||||
* @param snAllocator 维护/分配证书序列号的实例,证书序列号应该递增且不重复
|
|
||||||
*/
|
|
||||||
public SM2X509CertMaker(KeyPair issuerKeyPair, long certExpire, X500Name issuer,
|
|
||||||
CertSNAllocator snAllocator) {
|
|
||||||
this.issuerKeyPair = issuerKeyPair;
|
|
||||||
this.certExpire = certExpire;
|
|
||||||
this.issuerDN = issuer;
|
|
||||||
this.snAllocator = snAllocator;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成根CA证书
|
|
||||||
*
|
|
||||||
* @param csr CSR
|
|
||||||
* @return 新的证书
|
|
||||||
* @throws Exception 如果错误发生
|
|
||||||
*/
|
|
||||||
public X509Certificate makeRootCACert(byte[] csr)
|
|
||||||
throws Exception {
|
|
||||||
KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign);
|
|
||||||
return makeCertificate(CertLevel.RootCA, null, csr, usage, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成SubCA证书
|
|
||||||
*
|
|
||||||
* @param csr CSR
|
|
||||||
* @return 新的证书
|
|
||||||
* @throws Exception 如果错误发生
|
|
||||||
*/
|
|
||||||
public X509Certificate makeSubCACert(byte[] csr)
|
|
||||||
throws Exception {
|
|
||||||
KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign);
|
|
||||||
return makeCertificate(CertLevel.SubCA, 0, csr, usage, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成SSL用户证书
|
|
||||||
*
|
|
||||||
* @param csr CSR
|
|
||||||
* @return 新的证书
|
|
||||||
* @throws Exception 如果错误发生
|
|
||||||
*/
|
|
||||||
public X509Certificate makeSSLEndEntityCert(byte[] csr)
|
|
||||||
throws Exception {
|
|
||||||
return makeEndEntityCert(csr,
|
|
||||||
new KeyPurposeId[] {KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成用户证书
|
|
||||||
*
|
|
||||||
* @param csr CSR
|
|
||||||
* @param extendedKeyUsages 扩展指数用途。
|
|
||||||
* @return 新的证书
|
|
||||||
* @throws Exception 如果错误发生
|
|
||||||
*/
|
|
||||||
public X509Certificate makeEndEntityCert(byte[] csr,
|
|
||||||
KeyPurposeId[] extendedKeyUsages)
|
|
||||||
throws Exception {
|
|
||||||
KeyUsage usage = new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyAgreement
|
|
||||||
| KeyUsage.dataEncipherment | KeyUsage.keyEncipherment);
|
|
||||||
return makeCertificate(CertLevel.EndEntity, null, csr, usage, extendedKeyUsages);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param isCA 是否是颁发给CA的证书
|
|
||||||
* @param keyUsage 证书用途
|
|
||||||
* @param csr CSR
|
|
||||||
* @return
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
private X509Certificate makeCertificate(CertLevel certLevel, Integer pathLenConstrain,
|
|
||||||
byte[] csr, KeyUsage keyUsage, KeyPurposeId[] extendedKeyUsages)
|
|
||||||
throws Exception {
|
|
||||||
if (certLevel == CertLevel.EndEntity) {
|
|
||||||
if (keyUsage.hasUsages(KeyUsage.keyCertSign)) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"keyusage keyCertSign is not allowed in EndEntity Certificate");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PKCS10CertificationRequest request = new PKCS10CertificationRequest(csr);
|
|
||||||
SubjectPublicKeyInfo subPub = request.getSubjectPublicKeyInfo();
|
|
||||||
|
|
||||||
PrivateKey issPriv = issuerKeyPair.getPrivate();
|
|
||||||
PublicKey issPub = issuerKeyPair.getPublic();
|
|
||||||
|
|
||||||
X500Name subject = request.getSubject();
|
|
||||||
String email = null;
|
|
||||||
String commonName = null;
|
|
||||||
/*
|
|
||||||
* RFC 5280 §4.2.1.6 Subject
|
|
||||||
* Conforming implementations generating new certificates with
|
|
||||||
* electronic mail addresses MUST use the rfc822Name in the subject
|
|
||||||
* alternative name extension (Section 4.2.1.6) to describe such
|
|
||||||
* identities. Simultaneous inclusion of the emailAddress attribute in
|
|
||||||
* the subject distinguished name to support legacy implementations is
|
|
||||||
* deprecated but permitted.
|
|
||||||
*/
|
|
||||||
RDN[] rdns = subject.getRDNs();
|
|
||||||
List<RDN> newRdns = new ArrayList<>(rdns.length);
|
|
||||||
for (int i = 0; i < rdns.length; i++) {
|
|
||||||
RDN rdn = rdns[i];
|
|
||||||
|
|
||||||
AttributeTypeAndValue atv = rdn.getFirst();
|
|
||||||
ASN1ObjectIdentifier type = atv.getType();
|
|
||||||
if (BCStyle.EmailAddress.equals(type)) {
|
|
||||||
email = IETFUtils.valueToString(atv.getValue());
|
|
||||||
} else {
|
|
||||||
if (BCStyle.CN.equals(type)) {
|
|
||||||
commonName = IETFUtils.valueToString(atv.getValue());
|
|
||||||
}
|
|
||||||
newRdns.add(rdn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<GeneralName> subjectAltNames = new LinkedList<>();
|
|
||||||
if (email != null) {
|
|
||||||
subject = new X500Name(newRdns.toArray(new RDN[0]));
|
|
||||||
subjectAltNames.add(
|
|
||||||
new GeneralName(GeneralName.rfc822Name,
|
|
||||||
new DERIA5String(email, true)));
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean selfSignedEECert = false;
|
|
||||||
switch (certLevel) {
|
|
||||||
case RootCA:
|
|
||||||
if (issuerDN.equals(subject)) {
|
|
||||||
subject = issuerDN;
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("subject != issuer for certLevel " + CertLevel.RootCA);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SubCA:
|
|
||||||
if (issuerDN.equals(subject)) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"subject MUST not equals issuer for certLevel " + certLevel);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (issuerDN.equals(subject)) {
|
|
||||||
selfSignedEECert = true;
|
|
||||||
subject = issuerDN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInteger serialNumber = snAllocator.nextSerialNumber();
|
|
||||||
Date notBefore = new Date();
|
|
||||||
Date notAfter = new Date(notBefore.getTime() + certExpire);
|
|
||||||
X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder(
|
|
||||||
issuerDN, serialNumber,
|
|
||||||
notBefore, notAfter,
|
|
||||||
subject, subPub);
|
|
||||||
|
|
||||||
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
|
|
||||||
v3CertGen.addExtension(Extension.subjectKeyIdentifier, false,
|
|
||||||
extUtils.createSubjectKeyIdentifier(subPub));
|
|
||||||
if (certLevel != CertLevel.RootCA && !selfSignedEECert) {
|
|
||||||
v3CertGen.addExtension(Extension.authorityKeyIdentifier, false,
|
|
||||||
extUtils.createAuthorityKeyIdentifier(SubjectPublicKeyInfo.getInstance(issPub.getEncoded())));
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC 5280 §4.2.1.9 Basic Constraints:
|
|
||||||
// Conforming CAs MUST include this extension in all CA certificates
|
|
||||||
// that contain public keys used to validate digital signatures on
|
|
||||||
// certificates and MUST mark the extension as critical in such
|
|
||||||
// certificates.
|
|
||||||
BasicConstraints basicConstraints;
|
|
||||||
if (certLevel == CertLevel.EndEntity) {
|
|
||||||
basicConstraints = new BasicConstraints(false);
|
|
||||||
} else {
|
|
||||||
basicConstraints = pathLenConstrain == null
|
|
||||||
? new BasicConstraints(true) : new BasicConstraints(pathLenConstrain.intValue());
|
|
||||||
}
|
|
||||||
v3CertGen.addExtension(Extension.basicConstraints, true, basicConstraints);
|
|
||||||
|
|
||||||
// RFC 5280 §4.2.1.3 Key Usage: When present, conforming CAs SHOULD mark this extension as critical.
|
|
||||||
v3CertGen.addExtension(Extension.keyUsage, true, keyUsage);
|
|
||||||
|
|
||||||
if (extendedKeyUsages != null) {
|
|
||||||
ExtendedKeyUsage xku = new ExtendedKeyUsage(extendedKeyUsages);
|
|
||||||
v3CertGen.addExtension(Extension.extendedKeyUsage, false, xku);
|
|
||||||
|
|
||||||
boolean forSSLServer = false;
|
|
||||||
for (KeyPurposeId purposeId : extendedKeyUsages) {
|
|
||||||
if (KeyPurposeId.id_kp_serverAuth.equals(purposeId)) {
|
|
||||||
forSSLServer = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forSSLServer) {
|
|
||||||
if (commonName == null) {
|
|
||||||
throw new IllegalArgumentException("commonName must not be null");
|
|
||||||
}
|
|
||||||
GeneralName name = new GeneralName(GeneralName.dNSName,
|
|
||||||
new DERIA5String(commonName, true));
|
|
||||||
subjectAltNames.add(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!subjectAltNames.isEmpty()) {
|
|
||||||
v3CertGen.addExtension(Extension.subjectAlternativeName, false,
|
|
||||||
new GeneralNames(subjectAltNames.toArray(new GeneralName[0])));
|
|
||||||
}
|
|
||||||
|
|
||||||
JcaContentSignerBuilder contentSignerBuilder = makeContentSignerBuilder(issPub);
|
|
||||||
X509Certificate cert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
|
||||||
.getCertificate(v3CertGen.build(contentSignerBuilder.build(issPriv)));
|
|
||||||
cert.verify(issPub);
|
|
||||||
|
|
||||||
return cert;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JcaContentSignerBuilder makeContentSignerBuilder(PublicKey issPub) throws Exception {
|
|
||||||
if (issPub.getAlgorithm().equals("EC")) {
|
|
||||||
JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder(SIGN_ALGO_SM3WITHSM2);
|
|
||||||
contentSignerBuilder.setProvider(BouncyCastleProvider.PROVIDER_NAME);
|
|
||||||
return contentSignerBuilder;
|
|
||||||
}
|
|
||||||
throw new Exception("Unsupported PublicKey Algorithm:" + issPub.getAlgorithm());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
package com.sunyard.chsm.utils.gm.cert.exception;
|
|
||||||
|
|
||||||
public class InvalidX500NameException extends Exception {
|
|
||||||
private static final long serialVersionUID = 3192247087539921768L;
|
|
||||||
|
|
||||||
public InvalidX500NameException() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public InvalidX500NameException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public InvalidX500NameException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public InvalidX500NameException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,39 @@
|
|||||||
|
package com.sunyard.chsm.controller;
|
||||||
|
|
||||||
|
import com.sunyard.chsm.dto.CertDTO;
|
||||||
|
import com.sunyard.chsm.service.CertService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import javax.validation.Valid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用证书管理接口
|
||||||
|
*
|
||||||
|
* @author liulu
|
||||||
|
* @since 2024/11/6
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/cert")
|
||||||
|
public class CertController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CertService certService;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入证书
|
||||||
|
*
|
||||||
|
* @param importCert 证书
|
||||||
|
*/
|
||||||
|
@PostMapping("/import")
|
||||||
|
public void importCert(@Valid CertDTO.ImportCert importCert) {
|
||||||
|
certService.importCert(importCert);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package com.sunyard.chsm.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author liulu
|
||||||
|
* @since 2024/11/6
|
||||||
|
*/
|
||||||
|
public abstract class CertDTO {
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class ImportCert {
|
||||||
|
/**
|
||||||
|
* 密钥算法 目前支持: SM2
|
||||||
|
*/
|
||||||
|
@NotEmpty(message = "密钥算法不能为空")
|
||||||
|
private String keyAlg;
|
||||||
|
/**
|
||||||
|
* 是否单证,
|
||||||
|
*/
|
||||||
|
@NotNull(message = "单双证不能为空")
|
||||||
|
private Boolean single;
|
||||||
|
/**
|
||||||
|
* 证书类型: encrypt_decrypt 加密证书, sign_verify 签名证书
|
||||||
|
*/
|
||||||
|
private String certType;
|
||||||
|
/**
|
||||||
|
* 单证证书内容
|
||||||
|
*/
|
||||||
|
private String certText;
|
||||||
|
/**
|
||||||
|
* 签名证书内容
|
||||||
|
*/
|
||||||
|
private String signCertText;
|
||||||
|
/**
|
||||||
|
* 加密证书内容
|
||||||
|
*/
|
||||||
|
private String encCertText;
|
||||||
|
/**
|
||||||
|
* 加密密钥信封
|
||||||
|
*/
|
||||||
|
private String envelopedKey;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.sunyard.chsm.service;
|
||||||
|
|
||||||
|
import com.sunyard.chsm.dto.CertDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author liulu
|
||||||
|
* @since 2024/11/6
|
||||||
|
*/
|
||||||
|
public interface CertService {
|
||||||
|
|
||||||
|
void importCert(CertDTO.ImportCert importCert);
|
||||||
|
|
||||||
|
}
|
@ -151,7 +151,7 @@ public class ApplicationServiceImpl implements ApplicationService {
|
|||||||
.eq(Application::getName, name)
|
.eq(Application::getName, name)
|
||||||
|
|
||||||
);
|
);
|
||||||
Assert.notNull(exist, "应用名称已存在");
|
Assert.isNull(exist, "应用名称已存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,222 @@
|
|||||||
|
package com.sunyard.chsm.service.impl;
|
||||||
|
|
||||||
|
import com.sunyard.chsm.dto.CertDTO;
|
||||||
|
import com.sunyard.chsm.enums.KeyCategory;
|
||||||
|
import com.sunyard.chsm.enums.KeyStatus;
|
||||||
|
import com.sunyard.chsm.enums.KeyUsage;
|
||||||
|
import com.sunyard.chsm.mapper.CertMapper;
|
||||||
|
import com.sunyard.chsm.mapper.KeyInfoMapper;
|
||||||
|
import com.sunyard.chsm.mapper.SpKeyRecordMapper;
|
||||||
|
import com.sunyard.chsm.model.entity.Cert;
|
||||||
|
import com.sunyard.chsm.model.entity.KeyInfo;
|
||||||
|
import com.sunyard.chsm.model.entity.KeyRecord;
|
||||||
|
import com.sunyard.chsm.sdf.SdfApiService;
|
||||||
|
import com.sunyard.chsm.service.CertService;
|
||||||
|
import com.sunyard.chsm.utils.gm.BCECUtils;
|
||||||
|
import com.sunyard.chsm.utils.gm.BCSM2Utils;
|
||||||
|
import com.sunyard.chsm.utils.gm.BCSM4Utils;
|
||||||
|
import com.sunyard.chsm.utils.gm.cert.BCSM2CertUtils;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.bouncycastle.asn1.ASN1BitString;
|
||||||
|
import org.bouncycastle.asn1.ASN1Integer;
|
||||||
|
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||||
|
import org.bouncycastle.asn1.ASN1OctetString;
|
||||||
|
import org.bouncycastle.asn1.ASN1Sequence;
|
||||||
|
import org.bouncycastle.asn1.DLSequence;
|
||||||
|
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
|
||||||
|
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||||
|
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||||
|
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
|
||||||
|
import org.bouncycastle.util.BigIntegers;
|
||||||
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
|
import org.springframework.data.util.Pair;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author liulu
|
||||||
|
* @since 2024/11/6
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@Transactional
|
||||||
|
public class CertServiceImpl implements CertService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CertMapper certMapper;
|
||||||
|
@Resource
|
||||||
|
private SpKeyRecordMapper spKeyRecordMapper;
|
||||||
|
@Resource
|
||||||
|
private KeyInfoMapper keyInfoMapper;
|
||||||
|
@Resource
|
||||||
|
private SdfApiService sdfApiService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void importCert(CertDTO.ImportCert importCert) {
|
||||||
|
if (importCert.getSingle()) {
|
||||||
|
importSingleCert(importCert);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
importDoubleCert(importCert);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void importSingleCert(CertDTO.ImportCert importCert) {
|
||||||
|
|
||||||
|
Assert.hasText(importCert.getCertText(), "证书内容不能为空");
|
||||||
|
Assert.hasText(importCert.getCertType(), "证书类型不能为空");
|
||||||
|
|
||||||
|
|
||||||
|
X509Certificate x509Cert;
|
||||||
|
try {
|
||||||
|
x509Cert = BCSM2CertUtils.getX509Cert(importCert.getCertText());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new IllegalArgumentException("证书内容格式错误,无法解析");
|
||||||
|
}
|
||||||
|
PublicKey publicKey = x509Cert.getPublicKey();
|
||||||
|
String hexPubKey = BCECUtils.getHexPubKey((BCECPublicKey) publicKey);
|
||||||
|
|
||||||
|
KeyRecord record = spKeyRecordMapper.selectByPubKey(hexPubKey);
|
||||||
|
Assert.notNull(record, "此证书不是本系统生成P10请求生成的, 无法导入");
|
||||||
|
KeyInfo keyInfo = keyInfoMapper.selectById(record.getKeyId());
|
||||||
|
Assert.notNull(keyInfo, "密钥信息为空, 数据异常");
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
Assert.isTrue(KeyCategory.ASYM_KEY.getCode().equals(keyInfo.getKeyType())
|
||||||
|
&& KeyStatus.ENABLED.getCode().equals(keyInfo.getStatus())
|
||||||
|
&& now.isAfter(keyInfo.getEffectiveTime())
|
||||||
|
&& now.isBefore(keyInfo.getExpiredTime()),
|
||||||
|
"此证书对应的密钥ID:" + keyInfo.getId() + "不是启用状态,无法导入");
|
||||||
|
|
||||||
|
Cert exist = certMapper.selectBySN(x509Cert.getSerialNumber().toString());
|
||||||
|
Assert.isNull(exist, "此证书已经存在");
|
||||||
|
|
||||||
|
Cert cert = genCert(x509Cert, keyInfo.getApplicationId(), record, importCert);
|
||||||
|
certMapper.insert(cert);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void importDoubleCert(CertDTO.ImportCert importCert) {
|
||||||
|
|
||||||
|
Assert.hasText(importCert.getSignCertText(), "签名证书内容不能为空");
|
||||||
|
Assert.hasText(importCert.getEncCertText(), "加密证书不能为空");
|
||||||
|
Assert.hasText(importCert.getEnvelopedKey(), "加密密钥信封不能为空");
|
||||||
|
|
||||||
|
X509Certificate signCert, encCert;
|
||||||
|
try {
|
||||||
|
signCert = BCSM2CertUtils.getX509Cert(importCert.getSignCertText());
|
||||||
|
encCert = BCSM2CertUtils.getX509Cert(importCert.getEncCertText());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new IllegalArgumentException("证书内容格式错误,无法解析");
|
||||||
|
}
|
||||||
|
PublicKey signPk = signCert.getPublicKey();
|
||||||
|
String signPkHex = BCECUtils.getHexPubKey((BCECPublicKey) signPk);
|
||||||
|
String encPkHex = BCECUtils.getHexPubKey((BCECPublicKey) encCert.getPublicKey());
|
||||||
|
|
||||||
|
KeyRecord record = spKeyRecordMapper.selectByPubKey(signPkHex);
|
||||||
|
Assert.notNull(record, "签名证书不是本系统生成P10请求生成的, 无法导入");
|
||||||
|
KeyInfo keyInfo = keyInfoMapper.selectById(record.getKeyId());
|
||||||
|
Assert.notNull(keyInfo, "密钥信息为空, 数据异常");
|
||||||
|
|
||||||
|
// 验证数字信封
|
||||||
|
byte[] priKey = sdfApiService.decryptByTMK(Hex.decode(record.getKeyData()));
|
||||||
|
byte[] enveloped = Base64.getDecoder().decode(importCert.getEnvelopedKey());
|
||||||
|
Pair<String, byte[]> keys;
|
||||||
|
try {
|
||||||
|
keys = decryptEnvelopedKey(priKey, enveloped);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("解密加密密钥信封异常", ex);
|
||||||
|
throw new IllegalArgumentException("加密密钥信封格式错误,解密失败");
|
||||||
|
}
|
||||||
|
Assert.isTrue(Objects.equals(encPkHex, keys.getFirst()), "加密证书和私钥不匹配");
|
||||||
|
|
||||||
|
Cert exist = certMapper.selectBySN(signCert.getSerialNumber().toString());
|
||||||
|
Assert.isNull(exist, "签名证书已经存在");
|
||||||
|
exist = certMapper.selectBySN(encCert.getSerialNumber().toString());
|
||||||
|
Assert.isNull(exist, "加密证书已经存在");
|
||||||
|
|
||||||
|
importCert.setCertType(KeyUsage.SIGN_VERIFY.getCode());
|
||||||
|
importCert.setCertText(importCert.getSignCertText());
|
||||||
|
Cert sign = genCert(signCert, keyInfo.getApplicationId(), record, importCert);
|
||||||
|
certMapper.insert(sign);
|
||||||
|
|
||||||
|
importCert.setCertType(KeyUsage.ENCRYPT_DECRYPT.getCode());
|
||||||
|
importCert.setCertText(importCert.getEncCertText());
|
||||||
|
Cert enc = genCert(encCert, keyInfo.getApplicationId(), record, importCert);
|
||||||
|
enc.setPubKey(keys.getFirst());
|
||||||
|
byte[] encPri = sdfApiService.encryptByTMK(keys.getSecond());
|
||||||
|
enc.setEncPriKey(Hex.toHexString(encPri));
|
||||||
|
certMapper.insert(enc);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cert genCert(X509Certificate x509Cert, Long appId, KeyRecord record, CertDTO.ImportCert importCert) {
|
||||||
|
Cert cert = new Cert();
|
||||||
|
cert.setApplicationId(appId);
|
||||||
|
cert.setKeyId(record.getKeyId());
|
||||||
|
cert.setKeyRecordId(record.getId());
|
||||||
|
|
||||||
|
cert.setKeyAlg(importCert.getKeyAlg());
|
||||||
|
cert.setCertType(importCert.getCertType());
|
||||||
|
cert.setSingle(importCert.getSingle());
|
||||||
|
cert.setCertText(importCert.getCertText());
|
||||||
|
|
||||||
|
cert.setVersion(String.valueOf(x509Cert.getVersion()));
|
||||||
|
cert.setSubject(x509Cert.getSubjectX500Principal().getName());
|
||||||
|
cert.setSerialNumber(x509Cert.getSerialNumber().toString());
|
||||||
|
cert.setIssuerDn(x509Cert.getIssuerX500Principal().getName());
|
||||||
|
cert.setNotBefore(x509Cert.getNotBefore());
|
||||||
|
cert.setNotAfter(x509Cert.getNotAfter());
|
||||||
|
|
||||||
|
cert.setPubKey(record.getPubKey());
|
||||||
|
cert.setEncPriKey(record.getKeyData());
|
||||||
|
return cert;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Pair<String, byte[]> decryptEnvelopedKey(byte[] d, byte[] envelopedKey) throws Exception {
|
||||||
|
ECPrivateKeyParameters pri = BCECUtils.createECPrivateKeyParameters(Hex.decode(d));
|
||||||
|
ASN1Sequence seq = DLSequence.getInstance(envelopedKey);
|
||||||
|
|
||||||
|
AlgorithmIdentifier oid = AlgorithmIdentifier.getInstance(seq.getObjectAt(0));
|
||||||
|
ASN1ObjectIdentifier sm4 = GMObjectIdentifiers.sm_scheme.branch("104");
|
||||||
|
Assert.isTrue(oid.getAlgorithm().equals(sm4) || oid.getAlgorithm().on(sm4), "不支持的对称算法:" + oid.getAlgorithm());
|
||||||
|
|
||||||
|
ASN1Sequence ccseq = DLSequence.getInstance(seq.getObjectAt(1));
|
||||||
|
BigInteger x = ASN1Integer.getInstance(ccseq.getObjectAt(0)).getValue();
|
||||||
|
BigInteger y = ASN1Integer.getInstance(ccseq.getObjectAt(1)).getValue();
|
||||||
|
byte[] m = ASN1OctetString.getInstance(ccseq.getObjectAt(2)).getOctets();
|
||||||
|
byte[] c = ASN1OctetString.getInstance(ccseq.getObjectAt(3)).getOctets();
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(65 + m.length + c.length);
|
||||||
|
buffer.put((byte) 4);
|
||||||
|
buffer.put(BigIntegers.asUnsignedByteArray(32, x));
|
||||||
|
buffer.put(BigIntegers.asUnsignedByteArray(32, y));
|
||||||
|
buffer.put(m);
|
||||||
|
buffer.put(c);
|
||||||
|
|
||||||
|
byte[] symk = BCSM2Utils.decrypt(pri, buffer.array());
|
||||||
|
|
||||||
|
byte[] xy = ASN1BitString.getInstance(seq.getObjectAt(2)).getBytes();
|
||||||
|
byte[] ed = ASN1BitString.getInstance(seq.getObjectAt(3)).getBytes();
|
||||||
|
|
||||||
|
byte[] pd = BCSM4Utils.decrypt_ECB_NoPadding(symk, ed);
|
||||||
|
|
||||||
|
if (xy.length == 65) {
|
||||||
|
xy = Arrays.copyOfRange(xy, 1, xy.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pair.of(Hex.toHexString(xy), pd);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -26,8 +26,6 @@ import com.sunyard.chsm.service.KeyInfoService;
|
|||||||
import com.sunyard.chsm.utils.JsonUtils;
|
import com.sunyard.chsm.utils.JsonUtils;
|
||||||
import com.sunyard.chsm.utils.gm.BCECUtils;
|
import com.sunyard.chsm.utils.gm.BCECUtils;
|
||||||
import com.sunyard.chsm.utils.gm.cert.CommonCertUtils;
|
import com.sunyard.chsm.utils.gm.cert.CommonCertUtils;
|
||||||
import com.sunyard.chsm.utils.gm.cert.SM2PublicKey;
|
|
||||||
import com.sunyard.chsm.utils.gm.cert.SM2X509CertMaker;
|
|
||||||
import com.sunyard.ssp.common.exception.SspwebException;
|
import com.sunyard.ssp.common.exception.SspwebException;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.bouncycastle.asn1.x500.X500Name;
|
import org.bouncycastle.asn1.x500.X500Name;
|
||||||
@ -401,10 +399,9 @@ public class KeyInfoServiceImpl implements KeyInfoService {
|
|||||||
BCECPublicKey pubKey = new BCECPublicKey("EC", pubKeyParam, spec, BouncyCastleProvider.CONFIGURATION);
|
BCECPublicKey pubKey = new BCECPublicKey("EC", pubKeyParam, spec, BouncyCastleProvider.CONFIGURATION);
|
||||||
BCECPrivateKey priKey = new BCECPrivateKey("EC", priKeyParam, pubKey, spec, BouncyCastleProvider.CONFIGURATION);
|
BCECPrivateKey priKey = new BCECPrivateKey("EC", priKeyParam, pubKey, spec, BouncyCastleProvider.CONFIGURATION);
|
||||||
//
|
//
|
||||||
SM2PublicKey sm2SubPub = new SM2PublicKey(pubKey.getAlgorithm(), pubKey);
|
|
||||||
String csrTxt;
|
String csrTxt;
|
||||||
try {
|
try {
|
||||||
byte[] csr = CommonCertUtils.createCSR(dn, sm2SubPub, priKey, SM2X509CertMaker.SIGN_ALGO_SM3WITHSM2).getEncoded();
|
byte[] csr = CommonCertUtils.createCSR(dn, pubKey, priKey).getEncoded();
|
||||||
|
|
||||||
PemObject pem = new PemObject("CERTIFICATE REQUEST", csr);
|
PemObject pem = new PemObject("CERTIFICATE REQUEST", csr);
|
||||||
StringWriter str = new StringWriter();
|
StringWriter str = new StringWriter();
|
||||||
|
6
pom.xml
6
pom.xml
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<bcprov.version>1.69</bcprov.version>
|
<bcprov.version>1.79</bcprov.version>
|
||||||
<spring-boot.version>2.7.15</spring-boot.version>
|
<spring-boot.version>2.7.15</spring-boot.version>
|
||||||
|
|
||||||
<java.version>1.8</java.version>
|
<java.version>1.8</java.version>
|
||||||
@ -80,12 +80,12 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.bouncycastle</groupId>
|
<groupId>org.bouncycastle</groupId>
|
||||||
<artifactId>bcprov-jdk15on</artifactId>
|
<artifactId>bcprov-jdk18on</artifactId>
|
||||||
<version>${bcprov.version}</version>
|
<version>${bcprov.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.bouncycastle</groupId>
|
<groupId>org.bouncycastle</groupId>
|
||||||
<artifactId>bcpkix-jdk15on</artifactId>
|
<artifactId>bcpkix-jdk18on</artifactId>
|
||||||
<version>${bcprov.version}</version>
|
<version>${bcprov.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
Loading…
Reference in New Issue
Block a user