Merge remote-tracking branch 'origin/chsm-v1.0' into chsm-v1.0
This commit is contained in:
@@ -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,17 @@
|
||||
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);
|
||||
|
||||
void delete(Long id);
|
||||
}
|
||||
@@ -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,300 @@
|
||||
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("证书内容格式错误,无法解析");
|
||||
}
|
||||
Assert.isTrue(Objects.equals(signCert.getSubjectX500Principal().getName(), encCert.getSubjectX500Principal().getName()),
|
||||
"证书主题不一致");
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void delete(Long id) {
|
||||
|
||||
AppCert appCert = appCertMapper.selectById(id);
|
||||
Assert.notNull(appCert, "证书不存在");
|
||||
|
||||
if (appCert.getSingle()) {
|
||||
appCertMapper.deleteById(id);
|
||||
return;
|
||||
}
|
||||
|
||||
List<AppCert> appCerts = appCertMapper.selectList(
|
||||
new LambdaQueryWrapper<AppCert>()
|
||||
.eq(AppCert::getKeyId, appCert.getKeyId())
|
||||
.eq(AppCert::getSubject, appCert.getSubject())
|
||||
|
||||
);
|
||||
appCertMapper.deleteBatchIds(appCerts.stream().map(AppCert::getId).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
// );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user