证书管理

This commit is contained in:
liulu 2024-11-08 10:42:14 +08:00
parent fdcc750eee
commit e4af8ecbf4
10 changed files with 221 additions and 66 deletions

View File

@ -2,7 +2,7 @@ package com.sunyard.chsm.mapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sunyard.chsm.model.entity.Cert;
import com.sunyard.chsm.model.entity.AppCert;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@ -14,13 +14,13 @@ import java.util.List;
* @since 2024/11/6
*/
@Mapper
public interface CertMapper extends BaseMapper<Cert> {
public interface AppCertMapper extends BaseMapper<AppCert> {
default Cert selectBySN(String sn) {
default AppCert selectBySN(String sn) {
Assert.hasText(sn, "证书序列号不能为空");
List<Cert> certs = selectList(new LambdaQueryWrapper<Cert>()
.eq(Cert::getSerialNumber, sn)
List<AppCert> certs = selectList(new LambdaQueryWrapper<AppCert>()
.eq(AppCert::getSerialNumber, sn)
);
if (CollectionUtils.isEmpty(certs)) {
return null;

View File

@ -11,8 +11,8 @@ import java.util.Date;
* @since 2024/11/6
*/
@Data
@TableName("sp_cert")
public class Cert {
@TableName("sp_app_cert")
public class AppCert {
private Long id;
private Long applicationId;
@ -22,6 +22,7 @@ public class Cert {
private String keyAlg;
// ENC, SIGN, CERT_CHAIN
private String certType;
private String status;
private Boolean single;
private String version;
private String subject;

View File

@ -0,0 +1,57 @@
package com.sunyard.chsm.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sunyard.chsm.dto.CertDTO;
import com.sunyard.chsm.model.R;
import com.sunyard.chsm.service.AppCertService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
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("/app/cert")
public class AppCertController {
@Resource
private AppCertService appCertService;
/**
* 分页查询应用证书列表
*
* @param query 查询条件
* @return 应用列表
*/
@GetMapping("/pageList")
public R<Page<CertDTO.ACView>> queryPageList(CertDTO.Query query) {
Page<CertDTO.ACView> page = appCertService.selectPageList(query);
return R.data(page);
}
/**
* 导入证书
*
* @param importCert 证书
*/
@PostMapping("/import")
public void importCert(@Valid @RequestBody CertDTO.ImportCert importCert) {
appCertService.importCert(importCert);
}
}

View File

@ -1,40 +0,0 @@
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.RequestBody;
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
public class CertController {
@Resource
private CertService certService;
/**
* 导入证书
*
* @param importCert 证书
*/
@PostMapping("/app/cert/import")
public void importCert(@Valid @RequestBody CertDTO.ImportCert importCert) {
certService.importCert(importCert);
}
}

View File

@ -1,9 +1,13 @@
package com.sunyard.chsm.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
@ -11,6 +15,53 @@ import javax.validation.constraints.NotNull;
*/
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 {

View File

@ -1,13 +1,15 @@
package com.sunyard.chsm.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sunyard.chsm.dto.CertDTO;
/**
* @author liulu
* @since 2024/11/6
*/
public interface CertService {
public interface AppCertService {
Page<CertDTO.ACView> selectPageList(CertDTO.Query query);
void importCert(CertDTO.ImportCert importCert);
}

View File

@ -1,17 +1,22 @@
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.extension.plugins.pagination.Page;
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.AppCertMapper;
import com.sunyard.chsm.mapper.ApplicationMapper;
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.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.CertService;
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;
@ -29,10 +34,13 @@ 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.data.util.Pair;
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;
@ -42,7 +50,11 @@ 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
@ -51,10 +63,12 @@ import java.util.Objects;
@Slf4j
@Service
@Transactional
public class CertServiceImpl implements CertService {
public class AppCertServiceImpl implements AppCertService {
@Resource
private CertMapper certMapper;
private AppCertMapper appCertMapper;
@Resource
private ApplicationMapper applicationMapper;
@Resource
private SpKeyRecordMapper spKeyRecordMapper;
@Resource
@ -62,6 +76,45 @@ public class CertServiceImpl implements CertService {
@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()) {
@ -98,11 +151,11 @@ public class CertServiceImpl implements CertService {
&& now.isBefore(keyInfo.getExpiredTime()),
"此证书对应的密钥ID:" + keyInfo.getId() + "不是启用状态,无法导入");
Cert exist = certMapper.selectBySN(x509Cert.getSerialNumber().toString());
AppCert exist = appCertMapper.selectBySN(x509Cert.getSerialNumber().toString());
Assert.isNull(exist, "此证书已经存在");
Cert cert = genCert(x509Cert, keyInfo.getApplicationId(), record, importCert);
certMapper.insert(cert);
AppCert cert = genCert(x509Cert, keyInfo.getApplicationId(), record, importCert);
appCertMapper.insert(cert);
}
@ -140,28 +193,28 @@ public class CertServiceImpl implements CertService {
}
Assert.isTrue(Objects.equals(encPkHex, keys.getFirst()), "加密证书和私钥不匹配");
Cert exist = certMapper.selectBySN(signCert.getSerialNumber().toString());
AppCert exist = appCertMapper.selectBySN(signCert.getSerialNumber().toString());
Assert.isNull(exist, "签名证书已经存在");
exist = certMapper.selectBySN(encCert.getSerialNumber().toString());
exist = appCertMapper.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);
AppCert sign = genCert(signCert, keyInfo.getApplicationId(), record, importCert);
appCertMapper.insert(sign);
importCert.setCertType(KeyUsage.ENCRYPT_DECRYPT.getCode());
importCert.setCertText(importCert.getEncCertText());
Cert enc = genCert(encCert, keyInfo.getApplicationId(), record, importCert);
AppCert 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);
appCertMapper.insert(enc);
}
private Cert genCert(X509Certificate x509Cert, Long appId, KeyRecord record, CertDTO.ImportCert importCert) {
Cert cert = new Cert();
private AppCert genCert(X509Certificate x509Cert, Long appId, KeyRecord record, CertDTO.ImportCert importCert) {
AppCert cert = new AppCert();
cert.setApplicationId(appId);
cert.setKeyId(record.getKeyId());
cert.setKeyRecordId(record.getId());

View File

@ -168,7 +168,7 @@ public class KeyInfoServiceImpl implements KeyInfoService {
Assert.notNull(keyTemplate, "密钥模版不存在");
Application app = applicationMapper.selectById(save.getApplicationId());
Assert.notNull(app, "所属应用不存在");
Assert.isTrue(EnableStatus.DISABLED.getCode().equals(app.getStatus()), "应用不是启用状态");
Assert.isTrue(EnableStatus.ENABLED.getCode().equals(app.getStatus()), "应用不是启用状态");
LocalDateTime now = LocalDateTime.now();

View File

@ -54,6 +54,9 @@ spring:
max-file-size: 50MB
#默认上传文件总大小是10MB
max-request-size: 50MB
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
#开启切面
aop:
proxy-target-class: true

View File

@ -160,6 +160,7 @@ CREATE TABLE sp_key_record (
key_id BIGINT NOT NULL COMMENT '密钥id',
key_index VARCHAR(100) NOT NULL DEFAULT '' COMMENT '密钥索引',
key_data VARCHAR(255) NOT NULL DEFAULT '' COMMENT '密钥密文',
pub_idx VARCHAR(10) NOT NULL DEFAULT '' COMMENT '公钥',
pub_key VARCHAR(400) NOT NULL DEFAULT '' COMMENT '公钥',
check_alg VARCHAR(30) NOT NULL DEFAULT '' COMMENT '校验算法',
check_value VARCHAR(255) NOT NULL DEFAULT '' COMMENT '校验值',
@ -170,6 +171,8 @@ CREATE TABLE sp_key_record (
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
PRIMARY KEY (id)
);
CREATE INDEX idx_kid ON sp_key_record(key_id);
CREATE INDEX idx_pk ON sp_key_record(pub_idx);
-- 证书请求记录
CREATE TABLE sp_key_csr (
@ -185,4 +188,29 @@ CREATE TABLE sp_key_csr (
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
PRIMARY KEY (id)
);
-- 证书
CREATE TABLE sp_app_cert (
id BIGINT NOT NULL COMMENT 'id',
application_id BIGINT NOT NULL COMMENT '应用id',
key_id BIGINT NOT NULL COMMENT '密钥id',
key_record_id BIGINT NOT NULL COMMENT '密钥记录id',
key_alg VARCHAR(30) NOT NULL DEFAULT '' COMMENT '密钥算法',
cert_type VARCHAR(30) NOT NULL DEFAULT '' COMMENT '证书类型,加密|签名',
status VARCHAR(30) DEFAULT '' COMMENT '状态',
single TINYINT NOT NULL DEFAULT 0 COMMENT '是否单证',
version VARCHAR(10) NOT NULL DEFAULT '' COMMENT '证书版本',
subject VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'DN',
serial_number VARCHAR(255) NOT NULL DEFAULT '' COMMENT '证书号',
issuer_dn VARCHAR(255) NOT NULL DEFAULT '' COMMENT '颁发者',
not_before TIMESTAMP NOT NULL COMMENT '开始时间',
not_after TIMESTAMP NOT NULL COMMENT '结束时间',
key_usage VARCHAR(200) NOT NULL DEFAULT '' COMMENT '密钥用途',
pub_key VARCHAR(255) NOT NULL DEFAULT '' COMMENT '公钥',
enc_pri_key VARCHAR(255) NOT NULL DEFAULT '' COMMENT '加密后的私钥',
cert_text VARCHAR(4099) NOT NULL DEFAULT '' COMMENT '证书',
remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注',
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
PRIMARY KEY (id)
);