From f1b07786c0d7ffb03262a76ba3b30edff92bcfee Mon Sep 17 00:00:00 2001 From: liulu Date: Thu, 7 Nov 2024 17:04:08 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AF=81=E4=B9=A6=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chsm-common/pom.xml | 6 +- .../com/sunyard/chsm/mapper/CertMapper.java | 32 ++ .../java/com/sunyard/chsm/model/Subject.java | 26 +- .../com/sunyard/chsm/model/entity/Cert.java | 41 +++ .../com/sunyard/chsm/utils/gm/BCECUtils.java | 42 ++- .../com/sunyard/chsm/utils/gm/GMBaseUtil.java | 38 ++- .../chsm/utils/gm/SM2PreprocessSigner.java | 294 ------------------ .../chsm/utils/gm/cert/BCSM2CertUtils.java | 8 + .../chsm/utils/gm/cert/CertSNAllocator.java | 7 - .../chsm/utils/gm/cert/CommonCertUtils.java | 34 +- .../chsm/utils/gm/cert/FileSNAllocator.java | 48 --- .../chsm/utils/gm/cert/RandomSNAllocator.java | 78 ----- .../chsm/utils/gm/cert/SM2PrivateKey.java | 81 ----- .../chsm/utils/gm/cert/SM2PublicKey.java | 44 --- .../chsm/utils/gm/cert/SM2X509CertMaker.java | 280 ----------------- .../exception/InvalidX500NameException.java | 21 -- .../chsm/controller/CertController.java | 39 +++ .../java/com/sunyard/chsm/dto/CertDTO.java | 51 +++ .../com/sunyard/chsm/service/CertService.java | 13 + .../service/impl/ApplicationServiceImpl.java | 2 +- .../chsm/service/impl/CertServiceImpl.java | 222 +++++++++++++ .../chsm/service/impl/KeyInfoServiceImpl.java | 5 +- pom.xml | 6 +- 23 files changed, 527 insertions(+), 891 deletions(-) create mode 100644 chsm-common/src/main/java/com/sunyard/chsm/mapper/CertMapper.java create mode 100644 chsm-common/src/main/java/com/sunyard/chsm/model/entity/Cert.java delete mode 100644 chsm-common/src/main/java/com/sunyard/chsm/utils/gm/SM2PreprocessSigner.java delete mode 100644 chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/CertSNAllocator.java delete mode 100644 chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/FileSNAllocator.java delete mode 100644 chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/RandomSNAllocator.java delete mode 100644 chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2PrivateKey.java delete mode 100644 chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2PublicKey.java delete mode 100644 chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/SM2X509CertMaker.java delete mode 100644 chsm-common/src/main/java/com/sunyard/chsm/utils/gm/cert/exception/InvalidX500NameException.java create mode 100644 chsm-web-manage/src/main/java/com/sunyard/chsm/controller/CertController.java create mode 100644 chsm-web-manage/src/main/java/com/sunyard/chsm/dto/CertDTO.java create mode 100644 chsm-web-manage/src/main/java/com/sunyard/chsm/service/CertService.java create mode 100644 chsm-web-manage/src/main/java/com/sunyard/chsm/service/impl/CertServiceImpl.java 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}