diff --git a/chsm-common/pom.xml b/chsm-common/pom.xml
index 207ee15..cfe4c33 100644
--- a/chsm-common/pom.xml
+++ b/chsm-common/pom.xml
@@ -35,14 +35,12 @@
org.bouncycastle
- bcprov-jdk15on
+ bcprov-jdk18on
org.bouncycastle
- bcpkix-jdk15on
+ bcpkix-jdk18on
-
-
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/mapper/CertMapper.java b/chsm-common/src/main/java/com/sunyard/chsm/mapper/CertMapper.java
new file mode 100644
index 0000000..1e64cd4
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/mapper/CertMapper.java
@@ -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 {
+
+
+ default Cert selectBySN(String sn) {
+ Assert.hasText(sn, "证书序列号不能为空");
+ List certs = selectList(new LambdaQueryWrapper()
+ .eq(Cert::getSerialNumber, sn)
+ );
+ if (CollectionUtils.isEmpty(certs)) {
+ return null;
+ }
+ return certs.iterator().next();
+ }
+
+
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/model/Subject.java b/chsm-common/src/main/java/com/sunyard/chsm/model/Subject.java
index 7322272..4b9fb8c 100644
--- a/chsm-common/src/main/java/com/sunyard/chsm/model/Subject.java
+++ b/chsm-common/src/main/java/com/sunyard/chsm/model/Subject.java
@@ -49,23 +49,23 @@ public class Subject {
public String getDN() {
Assert.notNull(commonName, "通用名不能为空");
StringBuilder builder = new StringBuilder();
- if (StringUtils.hasText(country)) {
- builder.append("C=").append(country).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(commonName)) {
+ builder.append("CN=").append(commonName).append(COMMA);
}
if (StringUtils.hasText(orgUnit)) {
builder.append("OU=").append(orgUnit).append(COMMA);
}
- if (StringUtils.hasText(commonName)) {
- builder.append("CN=").append(commonName).append(COMMA);
+ if (StringUtils.hasText(org)) {
+ 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));
return builder.toString();
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/model/entity/Cert.java b/chsm-common/src/main/java/com/sunyard/chsm/model/entity/Cert.java
new file mode 100644
index 0000000..599ad3d
--- /dev/null
+++ b/chsm-common/src/main/java/com/sunyard/chsm/model/entity/Cert.java
@@ -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;
+
+}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCECUtils.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCECUtils.java
index 884f00d..a35474b 100644
--- a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCECUtils.java
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCECUtils.java
@@ -34,7 +34,7 @@ import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
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.PemReader;
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.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
/**
* 这个工具类的方法,也适用于其他基于BC库的ECC算法
@@ -118,7 +119,7 @@ public class BCECUtils {
*/
public static ECPrivateKeyParameters createECPrivateKeyParameters(
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);
}
+ 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公钥
*
@@ -182,7 +218,7 @@ public class BCECUtils {
*/
public static ECPublicKeyParameters createECPublicKeyParameters(
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);
}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/GMBaseUtil.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/GMBaseUtil.java
index a44a47e..ce4a5a7 100644
--- a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/GMBaseUtil.java
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/GMBaseUtil.java
@@ -1,11 +1,47 @@
package com.sunyard.chsm.utils.gm;
+import lombok.extern.slf4j.Slf4j;
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.util.Base64;
+@Slf4j
public class GMBaseUtil {
+
+ protected static final Provider BC = new BouncyCastleProvider();
+
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内容格式错误,写入异常");
+ }
}
}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/SM2PreprocessSigner.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/SM2PreprocessSigner.java
deleted file mode 100644
index e479f63..0000000
--- a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/SM2PreprocessSigner.java
+++ /dev/null
@@ -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);
- }
-}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/BCSM2CertUtils.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/BCSM2CertUtils.java
index b9c6ce6..3799006 100644
--- a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/BCSM2CertUtils.java
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/BCSM2CertUtils.java
@@ -15,12 +15,14 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
+import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.pkcs.PKCS12PfxPdu;
import org.bouncycastle.pkcs.PKCS12SafeBag;
import org.bouncycastle.pkcs.PKCS12SafeBagFactory;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.pkcs.jcajce.JcePKCSPBEInputDecryptorProviderBuilder;
+import org.springframework.util.Assert;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -62,6 +64,12 @@ public class BCSM2CertUtils extends GMBaseUtil {
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,
NoSuchProviderException {
try (InputStream is = Files.newInputStream(Paths.get(certFilePath))) {
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/CertSNAllocator.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/CertSNAllocator.java
deleted file mode 100644
index 76f93e3..0000000
--- a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/CertSNAllocator.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.sunyard.chsm.utils.gm.cert;
-
-import java.math.BigInteger;
-
-public interface CertSNAllocator {
- BigInteger nextSerialNumber() throws Exception;
-}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/CommonCertUtils.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/CommonCertUtils.java
index 91e7ac1..20a3322 100644
--- a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/CommonCertUtils.java
+++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/CommonCertUtils.java
@@ -1,12 +1,15 @@
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.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
@@ -17,21 +20,22 @@ import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
import java.security.PrivateKey;
+import java.security.PublicKey;
import java.util.Iterator;
import java.util.Map;
-public class CommonCertUtils {
+public class CommonCertUtils extends GMBaseUtil {
/**
* 如果不知道怎么填充names,可以查看org.bouncycastle.asn1.x500.style.BCStyle这个类,
* names的key值必须是BCStyle.DefaultLookUp中存在的(可以不关心大小写)
*
* @param names
* @return
- * @throws InvalidX500NameException
+ * @throws IllegalArgumentException
*/
- public static X500Name buildX500Name(Map names) throws InvalidX500NameException {
+ public static X500Name buildX500Name(Map names) {
if (names == null || names.size() == 0) {
- throw new InvalidX500NameException("names can not be empty");
+ throw new IllegalArgumentException("names can not be empty");
}
try {
X500NameBuilder builder = new X500NameBuilder();
@@ -45,14 +49,26 @@ public class CommonCertUtils {
}
return builder.build();
} 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,
- String signAlgo) throws OperatorCreationException {
+ @SneakyThrows
+ 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);
- ContentSigner signerBuilder = new JcaContentSignerBuilder(signAlgo)
+ ContentSigner signerBuilder = new JcaContentSignerBuilder("SM3withSM2")
.setProvider(BouncyCastleProvider.PROVIDER_NAME).build(priKey);
return csrBuilder.build(signerBuilder);
}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/FileSNAllocator.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/FileSNAllocator.java
deleted file mode 100644
index 4d54f86..0000000
--- a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/FileSNAllocator.java
+++ /dev/null
@@ -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();
- }
- }
- }
-}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/RandomSNAllocator.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/RandomSNAllocator.java
deleted file mode 100644
index b7b7939..0000000
--- a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/RandomSNAllocator.java
+++ /dev/null
@@ -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);
- }
-
-}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2PrivateKey.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2PrivateKey.java
deleted file mode 100644
index 9f95d08..0000000
--- a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2PrivateKey.java
+++ /dev/null
@@ -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;
- }
- }
-}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2PublicKey.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2PublicKey.java
deleted file mode 100644
index 1433f04..0000000
--- a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2PublicKey.java
+++ /dev/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));
- }
-}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2X509CertMaker.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2X509CertMaker.java
deleted file mode 100644
index 943d781..0000000
--- a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2X509CertMaker.java
+++ /dev/null
@@ -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 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 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());
- }
-}
diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/exception/InvalidX500NameException.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/exception/InvalidX500NameException.java
deleted file mode 100644
index 92f5874..0000000
--- a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/exception/InvalidX500NameException.java
+++ /dev/null
@@ -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);
- }
-}
diff --git a/chsm-web-manage/src/main/java/com/sunyard/chsm/controller/CertController.java b/chsm-web-manage/src/main/java/com/sunyard/chsm/controller/CertController.java
new file mode 100644
index 0000000..00e2583
--- /dev/null
+++ b/chsm-web-manage/src/main/java/com/sunyard/chsm/controller/CertController.java
@@ -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);
+ }
+
+
+}
diff --git a/chsm-web-manage/src/main/java/com/sunyard/chsm/dto/CertDTO.java b/chsm-web-manage/src/main/java/com/sunyard/chsm/dto/CertDTO.java
new file mode 100644
index 0000000..d5530e7
--- /dev/null
+++ b/chsm-web-manage/src/main/java/com/sunyard/chsm/dto/CertDTO.java
@@ -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;
+
+ }
+
+
+}
diff --git a/chsm-web-manage/src/main/java/com/sunyard/chsm/service/CertService.java b/chsm-web-manage/src/main/java/com/sunyard/chsm/service/CertService.java
new file mode 100644
index 0000000..5e12771
--- /dev/null
+++ b/chsm-web-manage/src/main/java/com/sunyard/chsm/service/CertService.java
@@ -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);
+
+}
diff --git a/chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/ApplicationServiceImpl.java b/chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/ApplicationServiceImpl.java
index 2582784..fbba1cb 100644
--- a/chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/ApplicationServiceImpl.java
+++ b/chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/ApplicationServiceImpl.java
@@ -151,7 +151,7 @@ public class ApplicationServiceImpl implements ApplicationService {
.eq(Application::getName, name)
);
- Assert.notNull(exist, "应用名称已存在");
+ Assert.isNull(exist, "应用名称已存在");
}
@Override
diff --git a/chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/CertServiceImpl.java b/chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/CertServiceImpl.java
new file mode 100644
index 0000000..87c0d58
--- /dev/null
+++ b/chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/CertServiceImpl.java
@@ -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 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 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);
+ }
+
+}
diff --git a/chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/KeyInfoServiceImpl.java b/chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/KeyInfoServiceImpl.java
index d021963..6f843f1 100644
--- a/chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/KeyInfoServiceImpl.java
+++ b/chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/KeyInfoServiceImpl.java
@@ -26,8 +26,6 @@ import com.sunyard.chsm.service.KeyInfoService;
import com.sunyard.chsm.utils.JsonUtils;
import com.sunyard.chsm.utils.gm.BCECUtils;
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 lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.x500.X500Name;
@@ -401,10 +399,9 @@ public class KeyInfoServiceImpl implements KeyInfoService {
BCECPublicKey pubKey = new BCECPublicKey("EC", pubKeyParam, spec, BouncyCastleProvider.CONFIGURATION);
BCECPrivateKey priKey = new BCECPrivateKey("EC", priKeyParam, pubKey, spec, BouncyCastleProvider.CONFIGURATION);
//
- SM2PublicKey sm2SubPub = new SM2PublicKey(pubKey.getAlgorithm(), pubKey);
String csrTxt;
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);
StringWriter str = new StringWriter();
diff --git a/pom.xml b/pom.xml
index c6c0e56..b02573f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
- 1.69
+ 1.79
2.7.15
1.8
@@ -80,12 +80,12 @@
org.bouncycastle
- bcprov-jdk15on
+ bcprov-jdk18on
${bcprov.version}
org.bouncycastle
- bcpkix-jdk15on
+ bcpkix-jdk18on
${bcprov.version}