证书管理
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
package com.sunyard.chsm.controller;
|
||||
|
||||
import com.sunyard.chsm.dto.CertDTO;
|
||||
import com.sunyard.chsm.service.CertService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
* 应用证书管理接口
|
||||
*
|
||||
* @author liulu
|
||||
* @since 2024/11/6
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/cert")
|
||||
public class CertController {
|
||||
|
||||
@Resource
|
||||
private CertService certService;
|
||||
|
||||
|
||||
/**
|
||||
* 导入证书
|
||||
*
|
||||
* @param importCert 证书
|
||||
*/
|
||||
@PostMapping("/import")
|
||||
public void importCert(@Valid CertDTO.ImportCert importCert) {
|
||||
certService.importCert(importCert);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.sunyard.chsm.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* @author liulu
|
||||
* @since 2024/11/6
|
||||
*/
|
||||
public abstract class CertDTO {
|
||||
|
||||
|
||||
@Data
|
||||
public static class ImportCert {
|
||||
/**
|
||||
* 密钥算法 目前支持: SM2
|
||||
*/
|
||||
@NotEmpty(message = "密钥算法不能为空")
|
||||
private String keyAlg;
|
||||
/**
|
||||
* 是否单证,
|
||||
*/
|
||||
@NotNull(message = "单双证不能为空")
|
||||
private Boolean single;
|
||||
/**
|
||||
* 证书类型: encrypt_decrypt 加密证书, sign_verify 签名证书
|
||||
*/
|
||||
private String certType;
|
||||
/**
|
||||
* 单证证书内容
|
||||
*/
|
||||
private String certText;
|
||||
/**
|
||||
* 签名证书内容
|
||||
*/
|
||||
private String signCertText;
|
||||
/**
|
||||
* 加密证书内容
|
||||
*/
|
||||
private String encCertText;
|
||||
/**
|
||||
* 加密密钥信封
|
||||
*/
|
||||
private String envelopedKey;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.sunyard.chsm.service;
|
||||
|
||||
import com.sunyard.chsm.dto.CertDTO;
|
||||
|
||||
/**
|
||||
* @author liulu
|
||||
* @since 2024/11/6
|
||||
*/
|
||||
public interface CertService {
|
||||
|
||||
void importCert(CertDTO.ImportCert importCert);
|
||||
|
||||
}
|
||||
+1
-1
@@ -151,7 +151,7 @@ public class ApplicationServiceImpl implements ApplicationService {
|
||||
.eq(Application::getName, name)
|
||||
|
||||
);
|
||||
Assert.notNull(exist, "应用名称已存在");
|
||||
Assert.isNull(exist, "应用名称已存在");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
package com.sunyard.chsm.service.impl;
|
||||
|
||||
import com.sunyard.chsm.dto.CertDTO;
|
||||
import com.sunyard.chsm.enums.KeyCategory;
|
||||
import com.sunyard.chsm.enums.KeyStatus;
|
||||
import com.sunyard.chsm.enums.KeyUsage;
|
||||
import com.sunyard.chsm.mapper.CertMapper;
|
||||
import com.sunyard.chsm.mapper.KeyInfoMapper;
|
||||
import com.sunyard.chsm.mapper.SpKeyRecordMapper;
|
||||
import com.sunyard.chsm.model.entity.Cert;
|
||||
import com.sunyard.chsm.model.entity.KeyInfo;
|
||||
import com.sunyard.chsm.model.entity.KeyRecord;
|
||||
import com.sunyard.chsm.sdf.SdfApiService;
|
||||
import com.sunyard.chsm.service.CertService;
|
||||
import com.sunyard.chsm.utils.gm.BCECUtils;
|
||||
import com.sunyard.chsm.utils.gm.BCSM2Utils;
|
||||
import com.sunyard.chsm.utils.gm.BCSM4Utils;
|
||||
import com.sunyard.chsm.utils.gm.cert.BCSM2CertUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bouncycastle.asn1.ASN1BitString;
|
||||
import org.bouncycastle.asn1.ASN1Integer;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.ASN1OctetString;
|
||||
import org.bouncycastle.asn1.ASN1Sequence;
|
||||
import org.bouncycastle.asn1.DLSequence;
|
||||
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
|
||||
import org.bouncycastle.util.BigIntegers;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author liulu
|
||||
* @since 2024/11/6
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@Transactional
|
||||
public class CertServiceImpl implements CertService {
|
||||
|
||||
@Resource
|
||||
private CertMapper certMapper;
|
||||
@Resource
|
||||
private SpKeyRecordMapper spKeyRecordMapper;
|
||||
@Resource
|
||||
private KeyInfoMapper keyInfoMapper;
|
||||
@Resource
|
||||
private SdfApiService sdfApiService;
|
||||
|
||||
@Override
|
||||
public void importCert(CertDTO.ImportCert importCert) {
|
||||
if (importCert.getSingle()) {
|
||||
importSingleCert(importCert);
|
||||
return;
|
||||
}
|
||||
importDoubleCert(importCert);
|
||||
}
|
||||
|
||||
|
||||
private void importSingleCert(CertDTO.ImportCert importCert) {
|
||||
|
||||
Assert.hasText(importCert.getCertText(), "证书内容不能为空");
|
||||
Assert.hasText(importCert.getCertType(), "证书类型不能为空");
|
||||
|
||||
|
||||
X509Certificate x509Cert;
|
||||
try {
|
||||
x509Cert = BCSM2CertUtils.getX509Cert(importCert.getCertText());
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException("证书内容格式错误,无法解析");
|
||||
}
|
||||
PublicKey publicKey = x509Cert.getPublicKey();
|
||||
String hexPubKey = BCECUtils.getHexPubKey((BCECPublicKey) publicKey);
|
||||
|
||||
KeyRecord record = spKeyRecordMapper.selectByPubKey(hexPubKey);
|
||||
Assert.notNull(record, "此证书不是本系统生成P10请求生成的, 无法导入");
|
||||
KeyInfo keyInfo = keyInfoMapper.selectById(record.getKeyId());
|
||||
Assert.notNull(keyInfo, "密钥信息为空, 数据异常");
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
Assert.isTrue(KeyCategory.ASYM_KEY.getCode().equals(keyInfo.getKeyType())
|
||||
&& KeyStatus.ENABLED.getCode().equals(keyInfo.getStatus())
|
||||
&& now.isAfter(keyInfo.getEffectiveTime())
|
||||
&& now.isBefore(keyInfo.getExpiredTime()),
|
||||
"此证书对应的密钥ID:" + keyInfo.getId() + "不是启用状态,无法导入");
|
||||
|
||||
Cert exist = certMapper.selectBySN(x509Cert.getSerialNumber().toString());
|
||||
Assert.isNull(exist, "此证书已经存在");
|
||||
|
||||
Cert cert = genCert(x509Cert, keyInfo.getApplicationId(), record, importCert);
|
||||
certMapper.insert(cert);
|
||||
}
|
||||
|
||||
|
||||
private void importDoubleCert(CertDTO.ImportCert importCert) {
|
||||
|
||||
Assert.hasText(importCert.getSignCertText(), "签名证书内容不能为空");
|
||||
Assert.hasText(importCert.getEncCertText(), "加密证书不能为空");
|
||||
Assert.hasText(importCert.getEnvelopedKey(), "加密密钥信封不能为空");
|
||||
|
||||
X509Certificate signCert, encCert;
|
||||
try {
|
||||
signCert = BCSM2CertUtils.getX509Cert(importCert.getSignCertText());
|
||||
encCert = BCSM2CertUtils.getX509Cert(importCert.getEncCertText());
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException("证书内容格式错误,无法解析");
|
||||
}
|
||||
PublicKey signPk = signCert.getPublicKey();
|
||||
String signPkHex = BCECUtils.getHexPubKey((BCECPublicKey) signPk);
|
||||
String encPkHex = BCECUtils.getHexPubKey((BCECPublicKey) encCert.getPublicKey());
|
||||
|
||||
KeyRecord record = spKeyRecordMapper.selectByPubKey(signPkHex);
|
||||
Assert.notNull(record, "签名证书不是本系统生成P10请求生成的, 无法导入");
|
||||
KeyInfo keyInfo = keyInfoMapper.selectById(record.getKeyId());
|
||||
Assert.notNull(keyInfo, "密钥信息为空, 数据异常");
|
||||
|
||||
// 验证数字信封
|
||||
byte[] priKey = sdfApiService.decryptByTMK(Hex.decode(record.getKeyData()));
|
||||
byte[] enveloped = Base64.getDecoder().decode(importCert.getEnvelopedKey());
|
||||
Pair<String, byte[]> keys;
|
||||
try {
|
||||
keys = decryptEnvelopedKey(priKey, enveloped);
|
||||
} catch (Exception ex) {
|
||||
log.error("解密加密密钥信封异常", ex);
|
||||
throw new IllegalArgumentException("加密密钥信封格式错误,解密失败");
|
||||
}
|
||||
Assert.isTrue(Objects.equals(encPkHex, keys.getFirst()), "加密证书和私钥不匹配");
|
||||
|
||||
Cert exist = certMapper.selectBySN(signCert.getSerialNumber().toString());
|
||||
Assert.isNull(exist, "签名证书已经存在");
|
||||
exist = certMapper.selectBySN(encCert.getSerialNumber().toString());
|
||||
Assert.isNull(exist, "加密证书已经存在");
|
||||
|
||||
importCert.setCertType(KeyUsage.SIGN_VERIFY.getCode());
|
||||
importCert.setCertText(importCert.getSignCertText());
|
||||
Cert sign = genCert(signCert, keyInfo.getApplicationId(), record, importCert);
|
||||
certMapper.insert(sign);
|
||||
|
||||
importCert.setCertType(KeyUsage.ENCRYPT_DECRYPT.getCode());
|
||||
importCert.setCertText(importCert.getEncCertText());
|
||||
Cert enc = genCert(encCert, keyInfo.getApplicationId(), record, importCert);
|
||||
enc.setPubKey(keys.getFirst());
|
||||
byte[] encPri = sdfApiService.encryptByTMK(keys.getSecond());
|
||||
enc.setEncPriKey(Hex.toHexString(encPri));
|
||||
certMapper.insert(enc);
|
||||
|
||||
}
|
||||
|
||||
private Cert genCert(X509Certificate x509Cert, Long appId, KeyRecord record, CertDTO.ImportCert importCert) {
|
||||
Cert cert = new Cert();
|
||||
cert.setApplicationId(appId);
|
||||
cert.setKeyId(record.getKeyId());
|
||||
cert.setKeyRecordId(record.getId());
|
||||
|
||||
cert.setKeyAlg(importCert.getKeyAlg());
|
||||
cert.setCertType(importCert.getCertType());
|
||||
cert.setSingle(importCert.getSingle());
|
||||
cert.setCertText(importCert.getCertText());
|
||||
|
||||
cert.setVersion(String.valueOf(x509Cert.getVersion()));
|
||||
cert.setSubject(x509Cert.getSubjectX500Principal().getName());
|
||||
cert.setSerialNumber(x509Cert.getSerialNumber().toString());
|
||||
cert.setIssuerDn(x509Cert.getIssuerX500Principal().getName());
|
||||
cert.setNotBefore(x509Cert.getNotBefore());
|
||||
cert.setNotAfter(x509Cert.getNotAfter());
|
||||
|
||||
cert.setPubKey(record.getPubKey());
|
||||
cert.setEncPriKey(record.getKeyData());
|
||||
return cert;
|
||||
}
|
||||
|
||||
|
||||
private static Pair<String, byte[]> decryptEnvelopedKey(byte[] d, byte[] envelopedKey) throws Exception {
|
||||
ECPrivateKeyParameters pri = BCECUtils.createECPrivateKeyParameters(Hex.decode(d));
|
||||
ASN1Sequence seq = DLSequence.getInstance(envelopedKey);
|
||||
|
||||
AlgorithmIdentifier oid = AlgorithmIdentifier.getInstance(seq.getObjectAt(0));
|
||||
ASN1ObjectIdentifier sm4 = GMObjectIdentifiers.sm_scheme.branch("104");
|
||||
Assert.isTrue(oid.getAlgorithm().equals(sm4) || oid.getAlgorithm().on(sm4), "不支持的对称算法:" + oid.getAlgorithm());
|
||||
|
||||
ASN1Sequence ccseq = DLSequence.getInstance(seq.getObjectAt(1));
|
||||
BigInteger x = ASN1Integer.getInstance(ccseq.getObjectAt(0)).getValue();
|
||||
BigInteger y = ASN1Integer.getInstance(ccseq.getObjectAt(1)).getValue();
|
||||
byte[] m = ASN1OctetString.getInstance(ccseq.getObjectAt(2)).getOctets();
|
||||
byte[] c = ASN1OctetString.getInstance(ccseq.getObjectAt(3)).getOctets();
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(65 + m.length + c.length);
|
||||
buffer.put((byte) 4);
|
||||
buffer.put(BigIntegers.asUnsignedByteArray(32, x));
|
||||
buffer.put(BigIntegers.asUnsignedByteArray(32, y));
|
||||
buffer.put(m);
|
||||
buffer.put(c);
|
||||
|
||||
byte[] symk = BCSM2Utils.decrypt(pri, buffer.array());
|
||||
|
||||
byte[] xy = ASN1BitString.getInstance(seq.getObjectAt(2)).getBytes();
|
||||
byte[] ed = ASN1BitString.getInstance(seq.getObjectAt(3)).getBytes();
|
||||
|
||||
byte[] pd = BCSM4Utils.decrypt_ECB_NoPadding(symk, ed);
|
||||
|
||||
if (xy.length == 65) {
|
||||
xy = Arrays.copyOfRange(xy, 1, xy.length);
|
||||
}
|
||||
|
||||
return Pair.of(Hex.toHexString(xy), pd);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,8 +26,6 @@ import com.sunyard.chsm.service.KeyInfoService;
|
||||
import com.sunyard.chsm.utils.JsonUtils;
|
||||
import com.sunyard.chsm.utils.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();
|
||||
|
||||
Reference in New Issue
Block a user