文件移动

This commit is contained in:
liulu
2024-11-14 10:46:22 +08:00
parent b500a4e9cc
commit ba6c06a8b8
15 changed files with 23 additions and 24 deletions
+4
View File
@@ -45,6 +45,10 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
@@ -0,0 +1,148 @@
package com.sunyard.chsm.model.dto;
import com.sunyard.chsm.model.PageQuery;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.Date;
/**
* @author liulu
* @since 2024/11/6
*/
public abstract class CertDTO {
@EqualsAndHashCode(callSuper = true)
@Data
public static class Query extends PageQuery {
private Long appId;
/**
* 证书类型: encrypt_decrypt 加密证书, sign_verify 签名证书
*/
private String certType;
}
@Data
public static class ACView {
private Long id;
private Long applicationId;
private String appName;
/**
* 密钥算法
*/
private String keyAlg;
// 证书类型
private String certType;
private String certTypeText;
/**
* 证书DN
*/
private String subject;
/**
* 序列号
*/
private String serialNumber;
/**
* 颁发着
*/
private String issuerDn;
/**
* 开始时间
*/
private Date notBefore;
/**
* 结束时间
*/
private Date notAfter;
private String remark;
private LocalDateTime createTime;
}
@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;
private String remark;
}
@Data
public static class CaInfo {
private Long id;
/**
* 密钥算法 目前支持: SM2
*/
@NotEmpty(message = "密钥算法不能为空")
private String keyAlg;
@NotEmpty(message = "CA名称不能为空")
private String caName;
private String caUrl;
/**
* 根证书内容
*/
@NotEmpty(message = "根证书不能为空")
private String certText;
private String remark;
}
@EqualsAndHashCode(callSuper = true)
@Data
public static class CAQuery extends PageQuery {
private String caName;
}
@Data
public static class CAView {
private Long id;
private String caName;
private String caUrl;
/**
* 密钥算法
*/
private String keyAlg;
/**
* 证书DN
*/
private String subject;
private String certText;
private String remark;
private LocalDateTime createTime;
}
}
@@ -0,0 +1,140 @@
package com.sunyard.chsm.model.dto;
import com.sunyard.chsm.model.PageQuery;
import com.sunyard.chsm.model.Subject;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
* @author liulu
* @since 2024/10/23
*/
public abstract class KeyInfoDTO {
@EqualsAndHashCode(callSuper = true)
@Data
public static class Query extends PageQuery {
private Long appId;
private String status;
private String keyType;
}
@Data
public static class KeySave {
@NotNull(message = "所属应用不能为空")
private Long applicationId;
/**
* 密钥模版编码
*/
@NotNull(message = "密钥模版不能为空")
private String keyTemplateCode;
/**
* 生成数量
*/
@NotNull(message = "生成数量不能为空")
@Max(value = 100, message = "一次最多生成100个密钥")
private Integer genNumber;
}
@Data
public static class KeyView {
private Long id;
private Long applicationId;
private String appName;
/**
* KEY_ID
*/
private String code;
private String keyType;
private String keyTypeText;
private String keyAlg;
private Integer keyLength;
private List<String> keyUsages;
private String keyUsageText;
/**
* 校验算法
*/
private String checkAlg;
/**
* 校验值
*/
private String checkValue;
private String status;
private String statusText;
/**
* 生效时间
*/
private LocalDateTime effectiveTime;
/**
* 过期时间
*/
private LocalDateTime expiredTime;
private LocalDateTime createTime;
}
@Data
public static class IDs {
/**
* 密钥id列表
*/
@Size(min = 1, max = 100, message = "密钥id列表长度在1-100之间")
@NotNull(message = "密钥id列表不能为空")
private List<Long> ids;
}
@EqualsAndHashCode(callSuper = true)
@Data
public static class KeyUpdate extends IDs {
/**
* 新密钥生效时间 yyyy-MM-dd
*/
private LocalDate effectiveTime;
}
@Data
public static class Backup {
/**
* @ignore
*/
private String keyType;
/**
* 开始日期 yyyy-MM-dd
*/
@NotNull(message = "开始日期不能为空")
private LocalDate startTime;
/**
* 结束日期 yyyy-MM-dd
*/
@NotNull(message = "结束日期不能为空")
private LocalDate endTime;
}
@EqualsAndHashCode(callSuper = true)
@Data
public static class CreateCsr extends Subject {
/**
* 密钥id
*/
@NotNull(message = "密钥id不能为空")
private Long id;
}
@Data
public static class CreateCSRResp {
// 证书请求内容
private String csr;
}
}
@@ -0,0 +1,15 @@
package com.sunyard.chsm.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sunyard.chsm.model.dto.CertDTO;
/**
* @author liulu
* @since 2024/11/6
*/
public interface AppCertService {
Page<CertDTO.ACView> selectPageList(CertDTO.Query query);
void importCert(CertDTO.ImportCert importCert);
}
@@ -0,0 +1,19 @@
package com.sunyard.chsm.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sunyard.chsm.model.dto.CertDTO;
/**
* @author liulu
* @since 2024/11/11
*/
public interface CaCertService {
Page<CertDTO.CAView> selectPageList(CertDTO.CAQuery query);
Long save(CertDTO.CaInfo caInfo);
void update(CertDTO.CaInfo update);
void delete(Long id);
}
@@ -0,0 +1,34 @@
package com.sunyard.chsm.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sunyard.chsm.model.dto.KeyInfoDTO;
import java.io.InputStream;
import java.util.List;
/**
* @author liulu
* @since 2024/10/23
*/
public interface KeyInfoService {
Page<KeyInfoDTO.KeyView> selectPageList(KeyInfoDTO.Query query);
Long save(KeyInfoDTO.KeySave save);
void update(KeyInfoDTO.KeyUpdate update);
byte[] backupKey(KeyInfoDTO.Backup backup);
String recoveryKey(InputStream is);
String createCsr(KeyInfoDTO.CreateCsr createCsr);
void enableKey(List<Long> ids);
void disableKey(List<Long> ids);
void archiveKey(List<Long> ids);
void destroyKey(List<Long> ids);
}
@@ -0,0 +1,277 @@
package com.sunyard.chsm.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sunyard.chsm.enums.KeyCategory;
import com.sunyard.chsm.enums.KeyStatus;
import com.sunyard.chsm.enums.KeyUsage;
import com.sunyard.chsm.mapper.AppCertMapper;
import com.sunyard.chsm.mapper.ApplicationMapper;
import com.sunyard.chsm.mapper.KeyInfoMapper;
import com.sunyard.chsm.mapper.SpKeyRecordMapper;
import com.sunyard.chsm.model.dto.CertDTO;
import com.sunyard.chsm.model.entity.AppCert;
import com.sunyard.chsm.model.entity.Application;
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.AppCertService;
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.apache.commons.lang3.tuple.Pair;
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.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
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.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @author liulu
* @since 2024/11/6
*/
@Slf4j
@Service
@Transactional
public class AppCertServiceImpl implements AppCertService {
@Resource
private AppCertMapper appCertMapper;
@Resource
private ApplicationMapper applicationMapper;
@Resource
private SpKeyRecordMapper spKeyRecordMapper;
@Resource
private KeyInfoMapper keyInfoMapper;
@Resource
private SdfApiService sdfApiService;
@Override
public Page<CertDTO.ACView> selectPageList(CertDTO.Query query) {
IPage<AppCert> page = appCertMapper.selectPage(
new Page<>(query.getPageNumber(), query.getPageSize()),
new LambdaQueryWrapper<AppCert>()
.eq(Objects.nonNull(query.getAppId()), AppCert::getApplicationId, query.getAppId())
.eq(StringUtils.hasText(query.getCertType()), AppCert::getCertType, query.getCertType())
.orderByDesc(AppCert::getCreateTime)
);
List<AppCert> records = page.getRecords();
if (CollectionUtils.isEmpty(records)) {
return new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
}
List<Long> appIds = records.stream().map(AppCert::getApplicationId).collect(Collectors.toList());
Map<Long, String> appNameMap = applicationMapper.selectBatchIds(appIds)
.stream().collect(Collectors.toMap(Application::getId, Application::getName));
List<CertDTO.ACView> viewList = records.stream()
.map(it -> {
CertDTO.ACView view = new CertDTO.ACView();
BeanUtils.copyProperties(it, view);
Optional.ofNullable(it.getApplicationId()).map(appNameMap::get).ifPresent(view::setAppName);
KeyUsage keyUsage = KeyUsage.valueOf(it.getCertType().toUpperCase());
switch (keyUsage) {
case ENCRYPT_DECRYPT:
view.setCertTypeText("加密证书");
break;
case SIGN_VERIFY:
view.setCertTypeText("签名证书");
break;
}
return view;
})
.collect(Collectors.toList());
return new Page<CertDTO.ACView>(page.getCurrent(), page.getSize(), page.getTotal()).setRecords(viewList);
}
@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() + "不是启用状态,无法导入");
AppCert exist = appCertMapper.selectBySN(x509Cert.getSerialNumber().toString());
Assert.isNull(exist, "此证书已经存在");
AppCert cert = genCert(x509Cert, keyInfo.getApplicationId(), record, importCert);
appCertMapper.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.getLeft()), "加密证书和私钥不匹配");
AppCert exist = appCertMapper.selectBySN(signCert.getSerialNumber().toString());
Assert.isNull(exist, "签名证书已经存在");
exist = appCertMapper.selectBySN(encCert.getSerialNumber().toString());
Assert.isNull(exist, "加密证书已经存在");
importCert.setCertType(KeyUsage.SIGN_VERIFY.getCode());
importCert.setCertText(importCert.getSignCertText());
AppCert sign = genCert(signCert, keyInfo.getApplicationId(), record, importCert);
appCertMapper.insert(sign);
importCert.setCertType(KeyUsage.ENCRYPT_DECRYPT.getCode());
importCert.setCertText(importCert.getEncCertText());
AppCert enc = genCert(encCert, keyInfo.getApplicationId(), record, importCert);
enc.setPubKey(keys.getLeft());
byte[] encPri = sdfApiService.encryptByTMK(keys.getRight());
enc.setEncPriKey(Hex.toHexString(encPri));
appCertMapper.insert(enc);
}
private AppCert genCert(X509Certificate x509Cert, Long appId, KeyRecord record, CertDTO.ImportCert importCert) {
AppCert cert = new AppCert();
cert.setId(IdWorker.getId());
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(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);
}
}
@@ -0,0 +1,143 @@
package com.sunyard.chsm.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sunyard.chsm.mapper.CaCertMapper;
import com.sunyard.chsm.model.dto.CertDTO;
import com.sunyard.chsm.model.entity.CaCert;
import com.sunyard.chsm.service.CaCertService;
import com.sunyard.chsm.utils.gm.BCECUtils;
import com.sunyard.chsm.utils.gm.cert.BCSM2CertUtils;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* @author liulu
* @since 2024/11/11
*/
@Slf4j
@Service
@Transactional
public class CaCertServiceImpl implements CaCertService {
@Resource
private CaCertMapper caCertMapper;
@Override
public Page<CertDTO.CAView> selectPageList(CertDTO.CAQuery query) {
IPage<CaCert> page = caCertMapper.selectPage(
new Page<>(query.getPageNumber(), query.getPageSize()),
new LambdaQueryWrapper<CaCert>()
.like(StringUtils.hasText(query.getCaName()), CaCert::getCaName, query.getCaName())
.orderByDesc(CaCert::getCreateTime)
);
List<CaCert> records = page.getRecords();
if (CollectionUtils.isEmpty(records)) {
return new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
}
List<CertDTO.CAView> viewList = records.stream()
.map(it -> {
CertDTO.CAView view = new CertDTO.CAView();
BeanUtils.copyProperties(it, view);
return view;
})
.collect(Collectors.toList());
return new Page<CertDTO.CAView>(page.getCurrent(), page.getSize(), page.getTotal()).setRecords(viewList);
}
@Override
public Long save(CertDTO.CaInfo caInfo) {
checkName(caInfo.getCaName());
X509Certificate x509Cert;
try {
x509Cert = BCSM2CertUtils.getX509Cert(caInfo.getCertText());
} catch (Exception ex) {
throw new IllegalArgumentException("证书内容格式错误,无法解析");
}
CaCert cert = genCaCert(caInfo, x509Cert);
cert.setId(IdWorker.getId());
cert.setCreateTime(LocalDateTime.now());
caCertMapper.insert(cert);
return cert.getId();
}
@Override
public void update(CertDTO.CaInfo update) {
Assert.notNull(update.getId(), "id不能为空");
CaCert exist = caCertMapper.selectById(update.getId());
Assert.notNull(exist, "id对应的CA不存在");
if (!Objects.equals(exist.getCaName(), update.getCaName())) {
checkName(update.getCaName());
}
X509Certificate x509Cert;
try {
x509Cert = BCSM2CertUtils.getX509Cert(update.getCertText());
} catch (Exception ex) {
throw new IllegalArgumentException("证书内容格式错误,无法解析");
}
CaCert cert = genCaCert(update, x509Cert);
cert.setId(update.getId());
cert.setUpdateTime(LocalDateTime.now());
caCertMapper.updateById(cert);
}
private static CaCert genCaCert(CertDTO.CaInfo caInfo, X509Certificate x509Cert) {
CaCert cert = new CaCert();
cert.setKeyAlg(caInfo.getKeyAlg());
cert.setCaName(caInfo.getCaName());
cert.setCaUrl(caInfo.getCaUrl());
cert.setCertText(caInfo.getCertText());
cert.setRemark(caInfo.getRemark());
cert.setSubject(x509Cert.getSubjectX500Principal().getName());
cert.setSerialNumber(x509Cert.getSerialNumber().toString());
cert.setIssuerDn(x509Cert.getIssuerX500Principal().getName());
cert.setNotBefore(x509Cert.getNotBefore());
cert.setNotAfter(x509Cert.getNotAfter());
PublicKey publicKey = x509Cert.getPublicKey();
String hexPubKey = BCECUtils.getHexPubKey((BCECPublicKey) publicKey);
cert.setPubKey(hexPubKey);
return cert;
}
private void checkName(String name) {
CaCert exist = caCertMapper.selectOne(
new LambdaQueryWrapper<CaCert>().eq(CaCert::getCaName, name)
);
Assert.isNull(exist, "CA名称已经存在");
}
@Override
public void delete(Long id) {
caCertMapper.deleteById(id);
}
}
@@ -0,0 +1,538 @@
package com.sunyard.chsm.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sunyard.chsm.enums.EnableStatus;
import com.sunyard.chsm.enums.KeyCategory;
import com.sunyard.chsm.enums.KeyStatus;
import com.sunyard.chsm.enums.KeyUsage;
import com.sunyard.chsm.mapper.ApplicationMapper;
import com.sunyard.chsm.mapper.KeyCsrMapper;
import com.sunyard.chsm.mapper.KeyInfoMapper;
import com.sunyard.chsm.mapper.KeyTemplateMapper;
import com.sunyard.chsm.mapper.SpKeyRecordMapper;
import com.sunyard.chsm.model.dto.KeyInfoDTO;
import com.sunyard.chsm.model.entity.Application;
import com.sunyard.chsm.model.entity.KeyCsr;
import com.sunyard.chsm.model.entity.KeyInfo;
import com.sunyard.chsm.model.entity.KeyRecord;
import com.sunyard.chsm.model.entity.KeyTemplate;
import com.sunyard.chsm.sdf.SdfApiService;
import com.sunyard.chsm.sdf.model.EccKey;
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 lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemWriter;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @author liulu
* @since 2024/10/23
*/
@Slf4j
@Service
@Transactional
public class KeyInfoServiceImpl implements KeyInfoService {
@Resource
private KeyCsrMapper keyCsrMapper;
@Resource
private KeyInfoMapper keyInfoMapper;
@Resource
private SpKeyRecordMapper spKeyRecordMapper;
@Resource
private KeyTemplateMapper keyTemplateMapper;
@Resource
private ApplicationMapper applicationMapper;
@Resource
private SdfApiService sdfApiService;
@Override
public Page<KeyInfoDTO.KeyView> selectPageList(KeyInfoDTO.Query query) {
LocalDateTime now = LocalDateTime.now();
LambdaQueryWrapper<KeyInfo> wrapper = new LambdaQueryWrapper<KeyInfo>()
.eq(StringUtils.hasText(query.getKeyType()), KeyInfo::getKeyType, query.getKeyType())
.eq(Objects.nonNull(query.getAppId()), KeyInfo::getApplicationId, query.getAppId())
// .eq(StringUtils.hasText(query.getStatus()), KeyInfo::getStatus, query.getStatus())
// .eq(KeyInfo::getDeleted, false)
.orderByDesc(KeyInfo::getCreateTime);
if (StringUtils.hasText(query.getStatus())) {
KeyStatus queryStatus = KeyStatus.of(query.getStatus());
if (KeyStatus.WAIT_ENABLED == queryStatus) {
wrapper.eq(KeyInfo::getStatus, KeyStatus.ENABLED.getCode())
.gt(KeyInfo::getEffectiveTime, now);
} else if (KeyStatus.EXPIRED == queryStatus) {
wrapper.eq(KeyInfo::getStatus, KeyStatus.ENABLED.getCode())
.lt(KeyInfo::getExpiredTime, now);
} else if (KeyStatus.ENABLED == queryStatus) {
wrapper.eq(KeyInfo::getStatus, KeyStatus.ENABLED.getCode())
.lt(KeyInfo::getEffectiveTime, now)
.gt(KeyInfo::getExpiredTime, now);
} else {
wrapper.eq(KeyInfo::getStatus, query.getStatus());
}
}
IPage<KeyInfo> page = keyInfoMapper.selectPage(
new Page<>(query.getPageNumber(), query.getPageSize()),
wrapper
);
List<KeyInfo> records = page.getRecords();
if (CollectionUtils.isEmpty(records)) {
return new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
}
List<Long> appIds = records.stream().map(KeyInfo::getApplicationId).collect(Collectors.toList());
Map<Long, String> appNameMap = applicationMapper.selectBatchIds(appIds)
.stream().collect(Collectors.toMap(Application::getId, Application::getName));
List<KeyInfoDTO.KeyView> viewList = records.stream()
.map(it -> {
KeyInfoDTO.KeyView view = new KeyInfoDTO.KeyView();
BeanUtils.copyProperties(it, view);
Optional.ofNullable(it.getApplicationId()).map(appNameMap::get).ifPresent(view::setAppName);
Optional.of(it.getKeyType()).map(KeyCategory::of).map(KeyCategory::getDesc).ifPresent(view::setKeyTypeText);
Map<String, String> usageMap = KeyUsage.getUsage(it.getKeyUsage())
.stream()
.collect(Collectors.toMap(KeyUsage::getCode, KeyUsage::getDesc));
view.setKeyUsages(new ArrayList<>(usageMap.keySet()));
view.setKeyUsageText(String.join(",", usageMap.values()));
KeyStatus keyStatus = KeyStatus.of(it.getStatus());
if (KeyStatus.ENABLED == keyStatus) {
if (now.isBefore(it.getEffectiveTime())) {
view.setStatus(KeyStatus.WAIT_ENABLED.getCode());
view.setStatusText(KeyStatus.WAIT_ENABLED.getDesc());
} else if (now.isAfter(it.getExpiredTime())) {
view.setStatus(KeyStatus.EXPIRED.getCode());
view.setStatusText(KeyStatus.EXPIRED.getDesc());
}
}
if (ObjectUtils.isEmpty(view.getStatusText())) {
view.setStatusText(keyStatus.getDesc());
}
return view;
})
.collect(Collectors.toList());
return new Page<KeyInfoDTO.KeyView>(page.getCurrent(), page.getSize(), page.getTotal()).setRecords(viewList);
}
@Override
public Long save(KeyInfoDTO.KeySave save) {
KeyTemplate keyTemplate = keyTemplateMapper.selectOne(
new LambdaQueryWrapper<KeyTemplate>()
.eq(KeyTemplate::getCode, save.getKeyTemplateCode())
);
Assert.notNull(keyTemplate, "密钥模版不存在");
Application app = applicationMapper.selectById(save.getApplicationId());
Assert.notNull(app, "所属应用不存在");
Assert.isTrue(EnableStatus.ENABLED.getCode().equals(app.getStatus()), "应用不是启用状态");
LocalDateTime now = LocalDateTime.now();
for (int i = 0; i < save.getGenNumber(); i++) {
// 密钥信息
KeyInfo info = new KeyInfo();
info.setId(IdWorker.getId());
info.setApplicationId(save.getApplicationId());
info.setKeyTemplateId(keyTemplate.getId());
info.setCode(String.valueOf(info.getId()));
info.setKeyType(keyTemplate.getKeyType());
info.setKeyAlg(keyTemplate.getKeyAlg());
info.setKeyLength(keyTemplate.getKeyLength());
info.setKeyUsage(keyTemplate.getKeyUsage());
info.setCheckAlg(keyTemplate.getCheckAlg());
info.setCreateTime(LocalDateTime.now());
info.setStatus(KeyStatus.ENABLED.getCode());
ChronoUnit startUnit = ChronoUnit.valueOf(keyTemplate.getStartAfterCreateUnit().toUpperCase());
info.setEffectiveTime(now.plusSeconds(startUnit.getDuration().getSeconds() * keyTemplate.getStartAfterCreateTime()));
ChronoUnit validUnit = ChronoUnit.valueOf(keyTemplate.getValidUnit().toUpperCase());
info.setExpiredTime(info.getEffectiveTime().plusSeconds(validUnit.getDuration().getSeconds() * keyTemplate.getValidTime()));
// 具体使用的密钥值
KeyRecord record = genKeyRecord(info);
info.setCheckValue(record.getCheckValue());
keyInfoMapper.insert(info);
spKeyRecordMapper.insert(record);
}
return 0L;
}
@Override
public void update(KeyInfoDTO.KeyUpdate update) {
List<Long> ids = update.getIds();
if (CollectionUtils.isEmpty(ids)) {
return;
}
LocalDateTime now = LocalDateTime.now();
LocalDateTime newEffectTime = LocalDateTime.of(update.getEffectiveTime(), LocalTime.MIN);
Assert.isTrue(!now.toLocalDate().isAfter(update.getEffectiveTime()), "生效日期必须在当前时间之后");
List<KeyInfo> keyInfos = keyInfoMapper.selectBatchIds(ids);
if (CollectionUtils.isEmpty(keyInfos)) {
log.warn("enableKey no exist key with ids: {}", ids.stream().map(String::valueOf).collect(Collectors.joining(",")));
return;
}
List<String> unNormalCodes = keyInfos.stream()
.filter(it -> now.isBefore(it.getEffectiveTime())
|| now.isAfter(it.getExpiredTime())
|| newEffectTime.isAfter(it.getExpiredTime())
|| !Objects.equals(KeyStatus.ENABLED.getCode(), it.getStatus())
)
.map(KeyInfo::getCode)
.collect(Collectors.toList());
Assert.isTrue(CollectionUtils.isEmpty(unNormalCodes),
"密钥id: " + String.join(",", unNormalCodes) + "不是启用状态或者新生效时间超过密钥过期时间, 无法更新");
List<KeyRecord> records = spKeyRecordMapper.selectList(
new LambdaQueryWrapper<KeyRecord>()
.gt(KeyRecord::getEffectiveTime, now)
.in(KeyRecord::getKeyId, ids)
);
String msgIds = records.stream().map(it -> String.valueOf(it.getKeyId())).collect(Collectors.joining(","));
Assert.isTrue(CollectionUtils.isEmpty(records), "密钥id: " + msgIds + "已经存在未使用的新密钥, 无法更新");
// 更新实际密钥值
spKeyRecordMapper.update(
new LambdaUpdateWrapper<KeyRecord>()
.set(KeyRecord::getExpiredTime, newEffectTime)
.in(KeyRecord::getKeyId, ids)
.lt(KeyRecord::getEffectiveTime, now)
.gt(KeyRecord::getExpiredTime, now)
);
for (KeyInfo info : keyInfos) {
// 具体使用的密钥值
KeyRecord record = genKeyRecord(info);
record.setEffectiveTime(newEffectTime);
KeyInfo upInfo = new KeyInfo();
upInfo.setId(info.getId());
upInfo.setCheckValue(record.getCheckValue());
keyInfoMapper.updateById(upInfo);
spKeyRecordMapper.insert(record);
}
}
private KeyRecord genKeyRecord(KeyInfo info) {
KeyRecord record = new KeyRecord();
record.setId(IdWorker.getId());
record.setKeyId(info.getId());
record.setKeyIndex(String.valueOf(record.getId()));
record.setCheckAlg(info.getCheckAlg());
record.setEffectiveTime(info.getEffectiveTime());
record.setExpiredTime(info.getExpiredTime());
record.setCreateTime(LocalDateTime.now());
if (KeyCategory.SYM_KEY.getCode().equals(info.getKeyType())) {
byte[] symKey = sdfApiService.generateRandom(16);
byte[] encSymKey = sdfApiService.encryptByTMK(symKey);
record.setKeyData(Hex.toHexString(encSymKey));
String checkHash = Hex.toHexString(sdfApiService.hash(symKey));
record.setCheckValue(checkHash);
} else {
EccKey eccKey = sdfApiService.genKeyPairEcc();
byte[] d = eccKey.getPriKey();
byte[] encD = sdfApiService.encryptByTMK(d);
record.setKeyData(Hex.toHexString(encD));
String checkHash = Hex.toHexString(sdfApiService.hash(d));
record.setCheckValue(checkHash);
byte[] pubKeyBytes = eccKey.getPubKey();
record.setPubKey(Hex.toHexString(pubKeyBytes));
record.setPubIdx(record.getPubKey().substring(0, 8));
}
return record;
}
@Override
public byte[] backupKey(KeyInfoDTO.Backup backup) {
LocalDate plussed = backup.getStartTime().plusDays(100);
Assert.isTrue(plussed.isAfter(backup.getEndTime()), "备份密钥创建时间范围不能超过100天");
List<KeyInfo> keyInfos = keyInfoMapper.selectList(
new LambdaQueryWrapper<KeyInfo>()
.eq(KeyInfo::getKeyType, backup.getKeyType())
.gt(KeyInfo::getCreateTime, LocalDateTime.of(backup.getStartTime(), LocalTime.MIN))
.lt(KeyInfo::getCreateTime, LocalDateTime.of(backup.getEndTime(), LocalTime.MAX))
);
Assert.isTrue(!CollectionUtils.isEmpty(keyInfos), "所选日期范围内,没有数据");
StringBuilder builder = new StringBuilder();
for (KeyInfo keyInfo : keyInfos) {
List<KeyRecord> records = spKeyRecordMapper.selectList(
new LambdaQueryWrapper<KeyRecord>()
.eq(KeyRecord::getKeyId, keyInfo.getId())
);
keyInfo.setRecords(records);
builder.append(Hex.toHexString(JsonUtils.toJsonBytes(keyInfo))).append(System.lineSeparator());
}
return builder.toString().getBytes(StandardCharsets.UTF_8);
}
@Override
public String recoveryKey(InputStream is) {
int suc = 0, count = 0, err = 0, exd = 0;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
String line;
while (true) {
try {
if ((line = reader.readLine()) == null) break;
} catch (IOException e) {
throw new IllegalArgumentException("文件读取异常");
}
if (ObjectUtils.isEmpty(line)) {
continue;
}
count++;
try {
KeyInfo info = JsonUtils.parse(Hex.decode(line), KeyInfo.class);
KeyInfo exist = keyInfoMapper.selectById(info.getId());
if (exist != null) {
exd++;
continue;
}
spKeyRecordMapper.delete(
new LambdaQueryWrapper<KeyRecord>()
.eq(KeyRecord::getKeyId, info.getId())
);
keyInfoMapper.insert(info);
if (!CollectionUtils.isEmpty(info.getRecords())) {
info.getRecords().forEach(spKeyRecordMapper::insert);
}
suc++;
} catch (Exception ex) {
log.error("解析恢复密钥失败, {}", line, ex);
err++;
}
}
} catch (IOException e) {
throw new IllegalArgumentException(e.getMessage());
}
return String.format("恢复完成,共%d条数据,跳过已经存在的密钥%d条,恢复成功%d条,解析失败%d条", count, exd, suc, err);
}
@Override
public String createCsr(KeyInfoDTO.CreateCsr createCsr) {
String subject = createCsr.getDN();
KeyCsr exist = keyCsrMapper.selectBySubject(subject);
if (Objects.nonNull(exist)) {
if (Objects.equals(exist.getKeyId(), createCsr.getId())) {
return exist.getCsrTxt();
}
throw new IllegalArgumentException("此证书主题已经被使用!");
}
KeyInfo keyInfo = keyInfoMapper.selectById(createCsr.getId());
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()),
"只能选择已启用的非对称密钥生成证书请求");
KeyRecord record = spKeyRecordMapper.selectUsedByKeyId(keyInfo.getId());
Assert.notNull(record, "数据异常,没有在使用中的密钥");
X500Name dn = new X500Name(subject);
byte[] xy = Hex.decode(record.getPubKey());
byte[] x = Arrays.copyOfRange(xy, 0, 32);
byte[] y = Arrays.copyOfRange(xy, 32, 64);
ECPublicKeyParameters pubKeyParam = BCECUtils.createECPublicKeyParameters(x, y);
byte[] priKeyBytes = sdfApiService.decryptByTMK(Hex.decode(record.getKeyData()));
// byte[][] pri18 = LangUtils.splitAverage(priKeyBytes);
ECPrivateKeyParameters priKeyParam = BCECUtils.createECPrivateKeyParameters(priKeyBytes);
ECDomainParameters domainParams = priKeyParam.getParameters();
ECParameterSpec spec = new ECParameterSpec(domainParams.getCurve(), domainParams.getG(), domainParams.getN(), domainParams.getH());
BCECPublicKey pubKey = new BCECPublicKey("EC", pubKeyParam, spec, BouncyCastleProvider.CONFIGURATION);
BCECPrivateKey priKey = new BCECPrivateKey("EC", priKeyParam, pubKey, spec, BouncyCastleProvider.CONFIGURATION);
//
String csrTxt;
try {
byte[] csr = CommonCertUtils.createCSR(dn, pubKey, priKey).getEncoded();
PemObject pem = new PemObject("CERTIFICATE REQUEST", csr);
StringWriter str = new StringWriter();
PemWriter pemWriter = new PemWriter(str);
pemWriter.writeObject(pem);
pemWriter.close();
str.close();
csrTxt = str.toString();
} catch (Exception e) {
log.error("生成CSR异常", e);
throw new IllegalArgumentException("生成证书请求异常");
}
KeyCsr csr = new KeyCsr();
csr.setId(IdWorker.getId());
csr.setApplicationId(keyInfo.getApplicationId());
csr.setKeyId(keyInfo.getId());
csr.setKeyRecordId(record.getId());
csr.setPubKey(record.getPubKey());
csr.setKeyData(record.getKeyData());
csr.setSubject(subject);
csr.setCreateTime(LocalDateTime.now());
csr.setCsrTxt(csrTxt);
keyCsrMapper.insert(csr);
return csrTxt;
}
@Override
public void enableKey(List<Long> ids) {
if (CollectionUtils.isEmpty(ids)) {
return;
}
List<KeyInfo> keyInfos = keyInfoMapper.selectBatchIds(ids);
if (CollectionUtils.isEmpty(keyInfos)) {
log.warn("enableKey no exist key with ids: {}", ids.stream().map(String::valueOf).collect(Collectors.joining(",")));
return;
}
List<String> unNormalCodes = keyInfos.stream()
.filter(it -> !Objects.equals(KeyStatus.DISABLED.getCode(), it.getStatus()))
.map(KeyInfo::getCode)
.collect(Collectors.toList());
Assert.isTrue(CollectionUtils.isEmpty(unNormalCodes),
"密钥id: " + String.join(",", unNormalCodes) + "当前状态不支持启用操作");
keyInfoMapper.update(
new LambdaUpdateWrapper<KeyInfo>()
.set(KeyInfo::getStatus, KeyStatus.ENABLED.getCode())
.in(KeyInfo::getId, ids)
);
}
@Override
public void disableKey(List<Long> ids) {
if (CollectionUtils.isEmpty(ids)) {
return;
}
List<KeyInfo> keyInfos = keyInfoMapper.selectBatchIds(ids);
if (CollectionUtils.isEmpty(keyInfos)) {
log.warn("disableKey no exist key with ids: {}", ids.stream().map(String::valueOf).collect(Collectors.joining(",")));
return;
}
List<String> unNormalCodes = keyInfos.stream()
.filter(it -> !Objects.equals(KeyStatus.ENABLED.getCode(), it.getStatus()))
.map(KeyInfo::getCode)
.collect(Collectors.toList());
Assert.isTrue(CollectionUtils.isEmpty(unNormalCodes),
"密钥id: " + String.join(",", unNormalCodes) + "当前状态不支持停用操作");
keyInfoMapper.update(
new LambdaUpdateWrapper<KeyInfo>()
.set(KeyInfo::getStatus, KeyStatus.DISABLED.getCode())
.in(KeyInfo::getId, ids)
);
}
@Override
public void archiveKey(List<Long> ids) {
if (CollectionUtils.isEmpty(ids)) {
return;
}
List<KeyInfo> keyInfos = keyInfoMapper.selectBatchIds(ids);
if (CollectionUtils.isEmpty(keyInfos)) {
log.warn("archiveKey no exist key with ids: {}", ids.stream().map(String::valueOf).collect(Collectors.joining(",")));
return;
}
LocalDateTime now = LocalDateTime.now();
List<String> unNormalCodes = keyInfos.stream()
.filter(it -> !(Objects.equals(KeyStatus.DISABLED.getCode(), it.getStatus())
|| (Objects.equals(KeyStatus.ENABLED.getCode(), it.getStatus()) && it.getExpiredTime().isBefore(now)))
)
.map(KeyInfo::getCode)
.collect(Collectors.toList());
Assert.isTrue(CollectionUtils.isEmpty(unNormalCodes),
"密钥id: " + String.join(",", unNormalCodes) + "当前状态不支持归档操作");
keyInfoMapper.update(
new LambdaUpdateWrapper<KeyInfo>()
.set(KeyInfo::getStatus, KeyStatus.ARCHIVED.getCode())
.in(KeyInfo::getId, ids)
);
}
@Override
public void destroyKey(List<Long> ids) {
if (CollectionUtils.isEmpty(ids)) {
return;
}
List<KeyInfo> keyInfos = keyInfoMapper.selectBatchIds(ids);
if (CollectionUtils.isEmpty(keyInfos)) {
log.warn("destroyKey no exist key with ids: {}", ids.stream().map(String::valueOf).collect(Collectors.joining(",")));
return;
}
LocalDateTime now = LocalDateTime.now();
List<String> unNormalCodes = keyInfos.stream()
.filter(it -> Objects.equals(KeyStatus.ENABLED.getCode(), it.getStatus())
&& now.isAfter(it.getEffectiveTime())
&& now.isBefore(it.getExpiredTime())
)
.map(KeyInfo::getCode)
.collect(Collectors.toList());
Assert.isTrue(CollectionUtils.isEmpty(unNormalCodes),
"密钥id: " + String.join(",", unNormalCodes) + "当前状态不支持销毁操作");
keyInfoMapper.deleteBatchIds(ids);
spKeyRecordMapper.delete(new LambdaUpdateWrapper<KeyRecord>().in(KeyRecord::getKeyId, ids));
// keyInfoMapper.update(
// new LambdaUpdateWrapper<KeyInfo>()
// .set(KeyInfo::getStatus, KeyStatus.DESTROY.getCode())
// .set(KeyInfo::getDeleted, true)
// .set(KeyInfo::getDeleteTime, now)
// .in(KeyInfo::getId, ids)
// );
}
}