Compare commits

...

10 Commits

Author SHA1 Message Date
Cheney
385a2a6ecc 密钥值查找测试 2024-11-18 15:01:21 +08:00
Cheney
5ce75ed4ce Merge remote-tracking branch 'origin/chsm-v1.0' into chsm-v1.0 2024-11-14 15:31:33 +08:00
Cheney
a98659f3a9 实现 BCSdfApiService 中 sm4 加解密接口 2024-11-14 15:31:11 +08:00
liulu
5413fa2545 证书管理 2024-11-14 14:14:00 +08:00
liulu
ba6c06a8b8 文件移动 2024-11-14 10:46:22 +08:00
liulu
b500a4e9cc fix 2024-11-14 09:18:16 +08:00
liulu
81759ed6c6 add 2024-11-14 09:13:52 +08:00
liulu
a9b35329ce ca管理 2024-11-12 14:50:37 +08:00
liulu
c18cfda28b 初始化主密钥 2024-11-11 14:14:32 +08:00
liulu
4ae46ed32d 初始化主密钥 2024-11-11 11:05:46 +08:00
48 changed files with 1498 additions and 73 deletions

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>

View File

@ -0,0 +1,36 @@
package com.sunyard.chsm.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.Objects;
/**
* 算法的轮模式
* @author Cheney
*/
@Getter
@AllArgsConstructor
public enum AlgMode {
ECB("ECB", "ECB"),
CBC( "CBC", "CBC"),
;
private final String code;
private final String desc;
public static AlgMode of(String code) {
if (code == null || code.trim().isEmpty()) {
return null;
}
return Arrays.stream(AlgMode.values())
.filter(it -> Objects.equals(it.getCode(), code))
.findFirst()
.orElse(null);
}
}

View File

@ -0,0 +1,34 @@
package com.sunyard.chsm.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.Objects;
/**
* 数据的填充模式
* @author Cheney
*/
@Getter
@AllArgsConstructor
public enum Padding {
NOPadding("NoPadding", "NoPadding"),
PCKS5Padding( "PKCS5Padding", "PKCS5Padding"),
;
private final String code;
private final String desc;
public static Padding of(String code) {
if (code == null || code.trim().isEmpty()) {
return null;
}
return Arrays.stream(Padding.values())
.filter(it -> Objects.equals(it.getCode(), code))
.findFirst()
.orElse(null);
}
}

View File

@ -0,0 +1,15 @@
package com.sunyard.chsm.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sunyard.chsm.model.entity.CaCert;
import org.apache.ibatis.annotations.Mapper;
/**
* @author liulu
* @since 2024/11/6
*/
@Mapper
public interface CaCertMapper extends BaseMapper<CaCert> {
}

View File

@ -0,0 +1,14 @@
package com.sunyard.chsm.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sunyard.chsm.model.entity.KeyInfo;
import com.sunyard.chsm.model.entity.KeyRecord;
import org.apache.ibatis.annotations.Mapper;
/**
* @author liulu
* @since 2024/10/22
*/
@Mapper
public interface KeyRecordMapper extends BaseMapper<KeyRecord> {
}

View File

@ -1,13 +1,25 @@
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.Device;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* @author liulu
* @since 2024/10/17
*/
@Mapper
public interface SpDeviceMapper extends BaseMapper<Device> {
default List<Device> selectConnedList() {
return selectList(
new LambdaQueryWrapper<Device>()
.eq(Device::getConnected, true)
);
}
}

View File

@ -1,4 +1,4 @@
package com.sunyard.chsm.dto;
package com.sunyard.chsm.model.dto;
import com.sunyard.chsm.model.PageQuery;
import lombok.Data;
@ -95,8 +95,54 @@ public abstract class CertDTO {
* 加密密钥信封
*/
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;
}
}

View File

@ -1,4 +1,4 @@
package com.sunyard.chsm.dto;
package com.sunyard.chsm.model.dto;
import com.sunyard.chsm.model.PageQuery;
import com.sunyard.chsm.model.Subject;
@ -21,6 +21,7 @@ public abstract class KeyInfoDTO {
@EqualsAndHashCode(callSuper = true)
@Data
public static class Query extends PageQuery {
private Long appId;
private String status;
private String keyType;
}

View File

@ -0,0 +1,38 @@
package com.sunyard.chsm.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Date;
/**
* @author liulu
* @since 2024/11/11
*/
@Data
@TableName("sp_ca_cert")
public class CaCert {
private Long id;
private String caName;
private String caUrl;
// SM2, RSA
private String keyAlg;
private String status;
private String subject;
private String serialNumber;
private String issuerDn;
private Date notBefore;
private Date notAfter;
private String pubKey;
private String certText;
private String remark;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

View File

@ -17,6 +17,7 @@ public class TmkInfo {
private String deviceSerial;
private String encTmk;
private String pubKey;
private String remark;
private LocalDateTime createTime;

View File

@ -0,0 +1,61 @@
package com.sunyard.chsm.sdf;
import com.sunyard.chsm.enums.AlgMode;
import com.sunyard.chsm.enums.KeyAlg;
import com.sunyard.chsm.enums.Padding;
import java.util.HashMap;
import java.util.Map;
import static com.sunyard.chsm.utils.gm.BCSM4Utils.DEFAULT_KEY_SIZE;
/**
* 实现 SdfApiService 接口中的通用逻辑
* 处理参数缺失时的默认参数确保下层入参没有 null
* @author Cheney
*/
public abstract class AbstractSdfApiService implements SdfApiService {
private static final Map<KeyAlg, Integer> algKeyLen = new HashMap<>();
static {
algKeyLen.put( KeyAlg.SM4, DEFAULT_KEY_SIZE );
}
/**
* 处理算法的默认密钥长度
* @param alg 算法只支持对称算法
* 密钥长度根据 alg 指定的算法自动选择
* @return 密钥
*/
@Override
public byte[] genSymKey(KeyAlg alg) {
return genSymKey( alg, algKeyLen.get( alg ) );
}
/**
* 对称加密
* 内部实现和解密共用逻辑
* @param alg 算法只支持对称算法
* @param key 密钥值明文
* @param data 原始数据
*/
@Override
public byte[] symEncrypt(KeyAlg alg, byte[] key, byte[] data){
return symEncrypt( alg, AlgMode.ECB, Padding.PCKS5Padding, key, data );
}
/**
* 对称解密
* 内部实现和加密共用逻辑
* @param alg 算法只支持对称算法
* @param key 密钥值明文
* @param data 密文数据
*/
@Override
public byte[] symDecrypt(KeyAlg alg, byte[] key, byte[] data) {
return symDecrypt( alg, AlgMode.ECB, Padding.PCKS5Padding, key, data );
}
}

View File

@ -1,30 +1,50 @@
package com.sunyard.chsm.sdf;
import com.sunyard.chsm.enums.AlgMode;
import com.sunyard.chsm.enums.KeyAlg;
import com.sunyard.chsm.enums.KeyCategory;
import com.sunyard.chsm.enums.Padding;
import com.sunyard.chsm.sdf.model.EccKey;
import com.sunyard.chsm.sdf.model.EccPriKey;
import com.sunyard.chsm.sdf.model.EccPubKey;
import com.sunyard.chsm.sdf.util.LangUtils;
import com.sunyard.chsm.utils.gm.BCSM2Utils;
import com.sunyard.chsm.utils.gm.BCSM3Utils;
import com.sunyard.chsm.utils.gm.BCSM4Utils;
import lombok.SneakyThrows;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.util.BigIntegers;
import org.springframework.stereotype.Service;
import java.security.KeyPair;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.*;
/**
* @author liulu
* 基于 BC 库的国密软算法实现
* 当前实现类的接口返回的对称密钥和私钥认为是明文在上层 Service 进行使用和存储时的加密和解密运算
* <p>
* 主密钥
* - 生成 软随机数项目启动时不存在则自动生成
* - 存储 存储在 SC_PARAM_CONF , KEY mk 的字段值为固定的 48 字节 HEX 格式 96 字节存储
* 16 字节为主密钥值 32 字节为主密钥值使用 SM3 计算的校验值
* - 同步基于数据库进行导入导出和备份恢复多台应用服务器连接同一个数据库无需同步
* - 使用顶层密钥用于保护其他存于数据的密钥
*
* @author liulu Cheney
* @since 2024/10/23
*/
@Slf4j
@Service
public class BCSdfApiService implements SdfApiService {
public class BCSdfApiService extends AbstractSdfApiService {
public BCSdfApiService() {
super();
}
@Override
public byte[] generateRandom(int len) {
@ -33,6 +53,82 @@ public class BCSdfApiService implements SdfApiService {
return res;
}
/**
* 生成对称算法密钥
*
* @param alg 算法只支持对称算法
* @param keyLen 密钥长度(bit)只针对密钥长度可变的算法
* 禁止传 null
* @return
*/
@Override
public byte[] genSymKey(KeyAlg alg, Integer keyLen) {
switch (alg) {
case SM4: {
if (null == keyLen || keyLen != BCSM4Utils.DEFAULT_KEY_SIZE) {
throw new IllegalArgumentException("Invalid key length: " + keyLen);
}
try {
return BCSM4Utils.generateKey();
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
throw new RuntimeException("算法实现错误", e);
}
}
default: {
throw new IllegalArgumentException("Unsupported algorithm: " + alg);
}
}
}
@Override
public byte[] symEncrypt(KeyAlg alg, AlgMode mode, Padding padding, byte[] key, byte[] data) {
return symCalc(Cipher.ENCRYPT_MODE, alg, mode, padding, key, data);
}
@Override
public byte[] symDecrypt(KeyAlg alg, AlgMode mode, Padding padding, byte[] key, byte[] data) {
return symCalc(Cipher.DECRYPT_MODE, alg, mode, padding, key, data);
}
/**
* 对称加解密的统一实现
*
* @param alg 算法只支持对称算法
* @param key 密钥值明文
* @param data 加密时明文数据解密时为密文数据
*/
private byte[] symCalc(int cipherMode, KeyAlg alg, AlgMode mode, Padding padding, byte[] key, byte[] data) {
if (alg.getCategory() != KeyCategory.SYM_KEY) {
throw new IllegalArgumentException("Must SYM_KEY, unsupported algorithm: " + alg);
}
// 算法
String algName = null;
if (alg == KeyAlg.SM4) {
algName = "SM4/";
} else {
throw new IllegalArgumentException("Unsupported algorithm: " + alg);
}
// 算法轮模式
algName += mode.getCode() + "/";
// 填充模式
algName += padding.getCode();
Cipher cipher = null;
try {
cipher = BCSM4Utils.generateECBCipher(algName, cipherMode, key);
return cipher.doFinal(data);
} catch (IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException | InvalidKeyException e) {
throw new RuntimeException("算法执行错误", e);
}
}
@SneakyThrows
@Override
public EccKey genKeyPairEcc() {
@ -43,8 +139,7 @@ public class BCSdfApiService implements SdfApiService {
byte[] x = pubKey.getQ().getXCoord().getEncoded();
byte[] y = pubKey.getQ().getYCoord().getEncoded();
byte[] d = BigIntegers.asUnsignedByteArray(32, priKey.getD());
return new EccKey(new EccPubKey(256, x, y), new EccPriKey(256, d));
return new EccKey(LangUtils.merge(x, y), d);
}
@ -55,14 +150,7 @@ public class BCSdfApiService implements SdfApiService {
@Override
public byte[] hmac(byte[] key, byte[] srcData) {
KeyParameter keyParameter = new KeyParameter(key);
SM3Digest digest = new SM3Digest();
HMac mac = new HMac(digest);
mac.init(keyParameter);
mac.update(srcData, 0, srcData.length);
byte[] result = new byte[mac.getMacSize()];
mac.doFinal(result, 0);
return result;
return BCSM3Utils.hmac(key, srcData);
}
@Override

View File

@ -1,8 +1,12 @@
package com.sunyard.chsm.sdf;
import com.sunyard.chsm.enums.AlgMode;
import com.sunyard.chsm.enums.KeyAlg;
import com.sunyard.chsm.enums.Padding;
import com.sunyard.chsm.sdf.model.EccKey;
/**
* @author liulu
* @since 2024/10/23
@ -18,6 +22,44 @@ public interface SdfApiService {
*/
byte[] generateRandom(int len);
/**
* 生成对称密钥
* @param alg 算法只支持对称算法
* @param keyLen 密钥长度(bit)只针对密钥长度可变的算法
* 禁止传 null
* @return 对称密钥
*/
byte[] genSymKey(KeyAlg alg, Integer keyLen);
byte[] genSymKey(KeyAlg alg);
/**
* 对称加密
* @param alg 算法只支持对称算法
* @param mode 轮模式
* @param padding 填充模式
* @param key 密钥值明文
* @param data 原始数据
*/
byte[] symEncrypt(KeyAlg alg, AlgMode mode, Padding padding, byte[] key, byte[] data);
byte[] symEncrypt(KeyAlg alg, byte[] key, byte[] data);
/**
* 对称解密
* @param alg 算法只支持对称算法
* @param key 密钥值明文
* @param mode 轮模式
* @param padding 填充模式
* @param data 密文数据
*/
byte[] symDecrypt(KeyAlg alg, AlgMode mode, Padding padding, byte[] key, byte[] data);
byte[] symDecrypt(KeyAlg alg, byte[] key, byte[] data);
/**
* 产生ECC密钥对并输出
*

View File

@ -2,14 +2,16 @@ package com.sunyard.chsm.sdf.adapter;
import com.sunyard.chsm.sdf.model.DeviceInfo;
import com.sunyard.chsm.sdf.model.EccKey;
import com.sunyard.chsm.sdf.model.EccPriKey;
import com.sunyard.chsm.sdf.model.EccPubKey;
import com.sunyard.chsm.sdf.util.LangUtils;
import com.sunyard.chsm.utils.gm.BCECUtils;
import com.sunyard.chsm.utils.gm.BCSM2Utils;
import lombok.SneakyThrows;
import org.bouncycastle.crypto.InvalidCipherTextException;
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.math.ec.ECPoint;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.BigIntegers;
import java.math.BigInteger;
@ -29,7 +31,7 @@ public class BcSdfApiAdaptor implements SdfApiAdapter {
deviceInfo = new DeviceInfo();
deviceInfo.setIssuerName("BC");
deviceInfo.setDeviceName("BC-3000");
deviceInfo.setDeviceSerial("BC00202411051037");
deviceInfo.setDeviceSerial("BC202411051037");
deviceInfo.setDeviceVersion(1);
deviceInfo.setStandardVersion(1);
deviceInfo.setAsymAlgAbility(new long[]{7493065891348563935L, 3000543215027029126L});
@ -70,10 +72,10 @@ public class BcSdfApiAdaptor implements SdfApiAdapter {
}
@Override
public EccPubKey exportEncPublicKeyECC(String sessionHandle, int uiKeyIndex) {
public byte[] exportEncPublicKeyECC(String sessionHandle, int uiKeyIndex) {
BigInteger d = new BigInteger(1, getD());
ECPoint q = BCSM2Utils.G_POINT.multiply(d).normalize();
return new EccPubKey(256, q.getXCoord().getEncoded(), q.getYCoord().getEncoded());
return LangUtils.merge(q.getXCoord().getEncoded(), q.getYCoord().getEncoded());
}
private byte[] getD() {
@ -93,7 +95,29 @@ public class BcSdfApiAdaptor implements SdfApiAdapter {
byte[] x = pubKey.getQ().getXCoord().getEncoded();
byte[] y = pubKey.getQ().getYCoord().getEncoded();
byte[] d = BigIntegers.asUnsignedByteArray(32, priKey.getD());
return new EccKey(new EccPubKey(256, x, y), new EccPriKey(256, d));
return new EccKey(LangUtils.merge(x, y), d);
}
@Override
public byte[] exchangeDigitEnvelopeBaseOnECC(String sessionHandle, int uiKeyIndex, byte[] pubKey, byte[] pucEncDateIn) {
return new byte[0];
}
@Override
public byte[] externalEncryptECC(String sessionHandle, byte[] pubKey, byte[] pucData) {
if (pubKey[0] == 4) {
pubKey = Arrays.copyOfRange(pubKey, 1, 65);
}
ECPublicKeyParameters parameters = BCECUtils.createECPublicKeyParameters(
Arrays.copyOfRange(pubKey, 0, 32),
Arrays.copyOfRange(pubKey, 32, 64)
);
try {
byte[] encrypt = BCSM2Utils.encrypt(parameters, pucData);
return Arrays.copyOfRange(encrypt, 1, encrypt.length);
} catch (InvalidCipherTextException e) {
throw new IllegalArgumentException(e);
}
}

View File

@ -2,11 +2,12 @@ package com.sunyard.chsm.sdf.adapter;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.PointerByReference;
import com.sunyard.chsm.sdf.context.AlgId;
import com.sunyard.chsm.sdf.lib.SdfLibrary;
import com.sunyard.chsm.sdf.model.DeviceInfo;
import com.sunyard.chsm.sdf.model.EccKey;
import com.sunyard.chsm.sdf.model.EccPubKey;
import com.sunyard.chsm.sdf.model.SDF_DeviceInfo;
import com.sunyard.chsm.sdf.util.LangUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
@ -117,15 +118,28 @@ public abstract class JnaSdfAdaptor implements SdfApiAdapter {
}
@Override
public EccPubKey exportEncPublicKeyECC(String sessionHandle, int uiKeyIndex) {
public byte[] exportEncPublicKeyECC(String sessionHandle, int uiKeyIndex) {
byte[] pubKey = new byte[132];
Pointer hSessionHandle = getSessionHandle(sessionHandle);
sdfLibrary.SDF_ExportEncPublicKey_ECC(hSessionHandle, uiKeyIndex, pubKey);
return new EccPubKey(256, Arrays.copyOfRange(pubKey, 36, 68), Arrays.copyOfRange(pubKey, 100, 132));
return LangUtils.merge(Arrays.copyOfRange(pubKey, 36, 68), Arrays.copyOfRange(pubKey, 100, 132));
}
@Override
public EccKey generateKeyPairECC(String sessionHandle, String alg, int uiKeyBits) {
return null;
}
@Override
public byte[] exchangeDigitEnvelopeBaseOnECC(String sessionHandle, int uiKeyIndex, byte[] pubKey, byte[] pucEncDateIn) {
return new byte[0];
}
@Override
public byte[] externalEncryptECC(String sessionHandle, byte[] pubKey, byte[] pucData) {
byte[] encData = new byte[128 + 32 + 4 + pucData.length];
Pointer hSessionHandle = getSessionHandle(sessionHandle);
sdfLibrary.SDF_ExternalEncrypt_ECC(hSessionHandle, getAlgId(AlgId.SGD_SM2_3), pubKey, pucData, pucData.length, encData);
return encData;
}
}

View File

@ -2,7 +2,6 @@ package com.sunyard.chsm.sdf.adapter;
import com.sunyard.chsm.sdf.model.DeviceInfo;
import com.sunyard.chsm.sdf.model.EccKey;
import com.sunyard.chsm.sdf.model.EccPubKey;
/**
* @author liulu
@ -54,9 +53,9 @@ public interface SdfApiAdapter {
* 导出ECC加密公钥
*
* @param uiKeyIndex 密码设备存储的ECC密钥对索引值
* @return pucPublicKeyEcc 返回ECC加密公钥
* @return pucPublicKeyEcc 返回ECC加密公钥x+y
*/
EccPubKey exportEncPublicKeyECC(String sessionHandle, int uiKeyIndex);
byte[] exportEncPublicKeyECC(String sessionHandle, int uiKeyIndex);
/**
* 产生ECC密钥对并输出
@ -68,4 +67,10 @@ public interface SdfApiAdapter {
EccKey generateKeyPairECC(String sessionHandle, String alg, int uiKeyBits);
byte[] exchangeDigitEnvelopeBaseOnECC(String sessionHandle, int uiKeyIndex, byte[] pubKey, byte[] pucEncDateIn);
byte[] externalEncryptECC(String sessionHandle, byte[] pubKey, byte[] pucData);
}

View File

@ -12,10 +12,10 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor
public class EccKey {
private EccPubKey pubKey;
private EccPriKey priKey;
private byte[] pubKey;
private byte[] priKey;
public EccKey(EccPubKey pubKey, EccPriKey priKey) {
public EccKey(byte[] pubKey, byte[] priKey) {
this.pubKey = pubKey;
this.priKey = priKey;
}

View File

@ -1,7 +1,7 @@
package com.sunyard.chsm.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sunyard.chsm.dto.CertDTO;
import com.sunyard.chsm.model.dto.CertDTO;
/**
* @author liulu
@ -12,4 +12,6 @@ public interface AppCertService {
Page<CertDTO.ACView> selectPageList(CertDTO.Query query);
void importCert(CertDTO.ImportCert importCert);
void delete(Long id);
}

View File

@ -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);
}

View File

@ -1,7 +1,7 @@
package com.sunyard.chsm.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sunyard.chsm.dto.KeyInfoDTO;
import com.sunyard.chsm.model.dto.KeyInfoDTO;
import java.io.InputStream;
import java.util.List;

View File

@ -0,0 +1,15 @@
package com.sunyard.chsm.service;
import com.sunyard.chsm.model.entity.KeyRecord;
public interface KeyRecordService {
KeyRecord selectById(Long id);
/**
* 根据 keyId 查找密钥
* @param keyId keyId
* @return 可用值或 null
*/
KeyRecord selectByKeyId(Long keyId);
}

View File

@ -4,7 +4,6 @@ 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.dto.CertDTO;
import com.sunyard.chsm.enums.KeyCategory;
import com.sunyard.chsm.enums.KeyStatus;
import com.sunyard.chsm.enums.KeyUsage;
@ -12,6 +11,7 @@ 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;
@ -23,6 +23,7 @@ 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;
@ -36,7 +37,6 @@ 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;
@ -173,6 +173,8 @@ public class AppCertServiceImpl implements AppCertService {
} 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());
@ -192,7 +194,7 @@ public class AppCertServiceImpl implements AppCertService {
log.error("解密加密密钥信封异常", ex);
throw new IllegalArgumentException("加密密钥信封格式错误,解密失败");
}
Assert.isTrue(Objects.equals(encPkHex, keys.getFirst()), "加密证书和私钥不匹配");
Assert.isTrue(Objects.equals(encPkHex, keys.getLeft()), "加密证书和私钥不匹配");
AppCert exist = appCertMapper.selectBySN(signCert.getSerialNumber().toString());
Assert.isNull(exist, "签名证书已经存在");
@ -207,8 +209,8 @@ public class AppCertServiceImpl implements AppCertService {
importCert.setCertType(KeyUsage.ENCRYPT_DECRYPT.getCode());
importCert.setCertText(importCert.getEncCertText());
AppCert enc = genCert(encCert, keyInfo.getApplicationId(), record, importCert);
enc.setPubKey(keys.getFirst());
byte[] encPri = sdfApiService.encryptByTMK(keys.getSecond());
enc.setPubKey(keys.getLeft());
byte[] encPri = sdfApiService.encryptByTMK(keys.getRight());
enc.setEncPriKey(Hex.toHexString(encPri));
appCertMapper.insert(enc);
@ -274,4 +276,25 @@ public class AppCertServiceImpl implements AppCertService {
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()));
}
}

View File

@ -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);
}
}

View File

@ -5,7 +5,6 @@ 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.dto.KeyInfoDTO;
import com.sunyard.chsm.enums.EnableStatus;
import com.sunyard.chsm.enums.KeyCategory;
import com.sunyard.chsm.enums.KeyStatus;
@ -15,6 +14,7 @@ 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;
@ -26,7 +26,6 @@ import com.sunyard.chsm.service.KeyInfoService;
import com.sunyard.chsm.utils.JsonUtils;
import com.sunyard.chsm.utils.gm.BCECUtils;
import com.sunyard.chsm.utils.gm.cert.CommonCertUtils;
import com.sunyard.ssp.common.exception.SspwebException;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.crypto.params.ECDomainParameters;
@ -94,6 +93,7 @@ public class KeyInfoServiceImpl implements KeyInfoService {
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);
@ -123,7 +123,7 @@ public class KeyInfoServiceImpl implements KeyInfoService {
return new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
}
List<Long> appIds = records.stream().map(KeyInfo::getId).collect(Collectors.toList());
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));
@ -282,13 +282,13 @@ public class KeyInfoServiceImpl implements KeyInfoService {
record.setCheckValue(checkHash);
} else {
EccKey eccKey = sdfApiService.genKeyPairEcc();
byte[] d = eccKey.getPriKey().getD();
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().getPubKeyBytes();
byte[] pubKeyBytes = eccKey.getPubKey();
record.setPubKey(Hex.toHexString(pubKeyBytes));
record.setPubIdx(record.getPubKey().substring(0, 8));
}
@ -329,7 +329,7 @@ public class KeyInfoServiceImpl implements KeyInfoService {
try {
if ((line = reader.readLine()) == null) break;
} catch (IOException e) {
throw new SspwebException("文件读取异常");
throw new IllegalArgumentException("文件读取异常");
}
if (ObjectUtils.isEmpty(line)) {
continue;
@ -357,7 +357,7 @@ public class KeyInfoServiceImpl implements KeyInfoService {
}
}
} catch (IOException e) {
throw new SspwebException(e.getMessage());
throw new IllegalArgumentException(e.getMessage());
}
return String.format("恢复完成,共%d条数据,跳过已经存在的密钥%d条,恢复成功%d条,解析失败%d条", count, exd, suc, err);
}

View File

@ -0,0 +1,45 @@
package com.sunyard.chsm.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.sunyard.chsm.mapper.KeyRecordMapper;
import com.sunyard.chsm.model.entity.KeyRecord;
import com.sunyard.chsm.service.KeyRecordService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
/**
* @author Cheney
*/
@Slf4j
@Service
@Transactional
public class KeyRecordServiceImpl implements KeyRecordService {
@Resource
private KeyRecordMapper keyRecordMapper;
@Override
public KeyRecord selectById(Long id) {
return keyRecordMapper.selectById(id);
}
@Override
public KeyRecord selectByKeyId(Long keyId) {
LocalDateTime now = LocalDateTime.now();
LambdaQueryWrapper<KeyRecord> queryWrapper = new LambdaQueryWrapper<>();
// 添加 keyId 的条件
queryWrapper.eq(KeyRecord::getKeyId, keyId);
// 添加 effectiveTime 小于当前时间的条件
queryWrapper.lt(KeyRecord::getEffectiveTime, now);
// 添加 expiredTime 大于当前时间的条件
queryWrapper.gt(KeyRecord::getExpiredTime, now);
return keyRecordMapper.selectOne(queryWrapper);
}
}

View File

@ -170,7 +170,7 @@ public class BCSM4Utils extends GMBaseUtil {
return mac.doFinal();
}
private static Cipher generateECBCipher(String algorithmName, int mode, byte[] key)
public static Cipher generateECBCipher(String algorithmName, int mode, byte[] key)
throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
InvalidKeyException {
Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
@ -179,7 +179,7 @@ public class BCSM4Utils extends GMBaseUtil {
return cipher;
}
private static Cipher generateCBCCipher(String algorithmName, int mode, byte[] key, byte[] iv)
public static Cipher generateCBCCipher(String algorithmName, int mode, byte[] key, byte[] iv)
throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
NoSuchProviderException, NoSuchPaddingException {
Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);

View File

@ -70,23 +70,12 @@
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<!--动态库调用依赖-->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>

View File

@ -1,10 +1,13 @@
package com.sunyard.chsm.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sunyard.chsm.dto.CertDTO;
import com.sunyard.chsm.constant.AuditLogConst;
import com.sunyard.chsm.model.R;
import com.sunyard.chsm.model.dto.CertDTO;
import com.sunyard.chsm.service.AppCertService;
import com.sunyard.ssp.common.annotation.AuditControllerLog;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@ -53,5 +56,17 @@ public class AppCertController {
appCertService.importCert(importCert);
}
/**
* 删除证书
*
* @param id id
* @return void
*/
@DeleteMapping
@AuditControllerLog(description = "删除证书", operateType = AuditLogConst.DELETE)
public R<Void> delete(Long id) {
appCertService.delete(id);
return R.ok();
}
}

View File

@ -0,0 +1,87 @@
package com.sunyard.chsm.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sunyard.chsm.constant.AuditLogConst;
import com.sunyard.chsm.model.R;
import com.sunyard.chsm.model.dto.CertDTO;
import com.sunyard.chsm.service.CaCertService;
import com.sunyard.ssp.common.annotation.AuditControllerLog;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
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;
/**
* CA管理接口
*
* @author liulu
* @since 2024/11/11
*/
@Slf4j
@RestController
@RequestMapping("/ca/cert")
public class CaController {
@Resource
private CaCertService caCertService;
/**
* 分页查询CA列表
*
* @param query query
* @return 列表
*/
@GetMapping("/pageList")
public R<Page<CertDTO.CAView>> queryPageList(CertDTO.CAQuery query) {
Page<CertDTO.CAView> page = caCertService.selectPageList(query);
return R.data(page);
}
/**
* 新增CA
*
* @param caInfo 证书
*/
@PostMapping
public R<Long> save(@Valid @RequestBody CertDTO.CaInfo caInfo) {
Long id = caCertService.save(caInfo);
return R.data(id);
}
/**
* 修改CA
*
* @param update 参数
* @return void
*/
@PutMapping
@AuditControllerLog(description = "修改CA", operateType = AuditLogConst.UPDATE)
public R<Void> update(@Valid @RequestBody CertDTO.CaInfo update) {
caCertService.update(update);
return R.ok();
}
/**
* 删除CA
*
* @param id id
* @return void
*/
@DeleteMapping
@AuditControllerLog(description = "删除CA", operateType = AuditLogConst.DELETE)
public R<Void> delete(Long id) {
caCertService.delete(id);
return R.ok();
}
}

View File

@ -1,9 +1,9 @@
package com.sunyard.chsm.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sunyard.chsm.dto.KeyInfoDTO;
import com.sunyard.chsm.enums.KeyCategory;
import com.sunyard.chsm.model.R;
import com.sunyard.chsm.model.dto.KeyInfoDTO;
import com.sunyard.chsm.service.KeyInfoService;
import com.sunyard.chsm.utils.DateFormat;
import org.springframework.core.io.ByteArrayResource;

View File

@ -1,10 +1,10 @@
package com.sunyard.chsm.controller;
import com.sunyard.chsm.constant.AuditLogConst;
import com.sunyard.chsm.dto.KeyInfoDTO;
import com.sunyard.chsm.enums.KeyStatus;
import com.sunyard.chsm.model.Option;
import com.sunyard.chsm.model.R;
import com.sunyard.chsm.model.dto.KeyInfoDTO;
import com.sunyard.chsm.service.KeyInfoService;
import com.sunyard.ssp.common.annotation.AuditControllerLog;
import com.sunyard.ssp.common.exception.SspwebException;

View File

@ -1,9 +1,9 @@
package com.sunyard.chsm.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sunyard.chsm.dto.KeyInfoDTO;
import com.sunyard.chsm.enums.KeyCategory;
import com.sunyard.chsm.model.R;
import com.sunyard.chsm.model.dto.KeyInfoDTO;
import com.sunyard.chsm.service.KeyInfoService;
import com.sunyard.chsm.utils.DateFormat;
import org.springframework.beans.factory.annotation.Autowired;

View File

@ -0,0 +1,44 @@
package com.sunyard.chsm.controller;
import com.sunyard.chsm.dto.TmkStatus;
import com.sunyard.chsm.model.R;
import com.sunyard.chsm.service.DeviceService;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 主密钥管理
*
* @author liulu
* @since 2024/11/8
*/
@Slf4j
@RestController
@RequestMapping("/tmk")
public class TmkController {
@Resource
private DeviceService deviceService;
@GetMapping("/status")
public R<TmkStatus> getTMKStatus() {
TmkStatus status = deviceService.getTMKStatus();
return R.data(status);
}
/**
* 初始化主密钥
*/
@PostMapping("/init")
public R<Void> initTmk() {
deviceService.initTmk();
return R.ok();
}
}

View File

@ -0,0 +1,22 @@
package com.sunyard.chsm.dto;
import lombok.Data;
/**
* @author liulu
* @since 2024/11/11
*/
@Data
public class TmkStatus {
/**
* 是否已经存在设备
*/
private boolean hasDevice;
/**
* 主密钥是否初始化
*/
private boolean tmkInit;
}

View File

@ -2,6 +2,7 @@ package com.sunyard.chsm.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sunyard.chsm.dto.DeviceDTO;
import com.sunyard.chsm.dto.TmkStatus;
import java.util.List;
@ -21,4 +22,8 @@ public interface DeviceService {
void update(DeviceDTO.DeviceSave update);
void delete(Long id);
TmkStatus getTMKStatus();
void initTmk();
}

View File

@ -5,12 +5,19 @@ 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.dto.DeviceDTO;
import com.sunyard.chsm.dto.TmkStatus;
import com.sunyard.chsm.enums.ManufacturerEnum;
import com.sunyard.chsm.enums.ManufacturerModelEnum;
import com.sunyard.chsm.mapper.SpDeviceMapper;
import com.sunyard.chsm.mapper.TmkInfoMapper;
import com.sunyard.chsm.model.entity.Device;
import com.sunyard.chsm.model.entity.TmkInfo;
import com.sunyard.chsm.sdf.adapter.BcSdfApiAdaptor;
import com.sunyard.chsm.service.DeviceService;
import com.sunyard.ssp.modules.sysconf.paramconf.entity.ParamConf;
import com.sunyard.ssp.modules.sysconf.paramconf.mapper.ParamConfMapper;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -36,6 +43,10 @@ public class DeviceServiceImpl implements DeviceService {
@Resource
private SpDeviceMapper spDeviceMapper;
@Resource
private ParamConfMapper paramConfMapper;
@Resource
private TmkInfoMapper tmkInfoMapper;
@Override
public Page<DeviceDTO.DeviceView> selectPageList(DeviceDTO.Query query) {
@ -171,6 +182,91 @@ public class DeviceServiceImpl implements DeviceService {
spDeviceMapper.deleteById(id);
}
@Override
public TmkStatus getTMKStatus() {
TmkStatus status = new TmkStatus();
ParamConf tmkInit = paramConfMapper.selectByKey("tmk_init");
if (tmkInit != null && "true".equals(tmkInit.getValue())) {
status.setHasDevice(true);
status.setTmkInit(false);
return status;
}
status.setTmkInit(false);
Long c = spDeviceMapper.selectCount(new LambdaQueryWrapper<>());
status.setHasDevice(c > 0L);
return status;
}
@Override
public void initTmk() {
ParamConf tmkInit = paramConfMapper.selectByKey("tmk_init");
Assert.isTrue(tmkInit == null || !"true".equals(tmkInit.getValue()), "主密钥已经初始化");
// List<Device> conned = spDeviceMapper.selectConnedList();
//
LocalDateTime now = LocalDateTime.now();
// if (CollectionUtils.isEmpty(conned)) {
//
BcSdfApiAdaptor sdfApi = new BcSdfApiAdaptor();
byte[] sk = sdfApi.generateRandom("", 16);
byte[] publicKey = sdfApi.exportEncPublicKeyECC("", 1);
byte[] encSk = sdfApi.externalEncryptECC("", publicKey, sk);
TmkInfo info = new TmkInfo();
info.setId(IdWorker.getId());
info.setCreateTime(now);
info.setDeviceSerial(sdfApi.getDeviceInfo("").getDeviceSerial());
info.setEncTmk(Hex.toHexString(encSk));
info.setPubKey(Hex.toHexString(publicKey));
tmkInfoMapper.insert(info);
// return;
// }
// Device device = conned.iterator().next();
//
// DeviceContext context = new DeviceContext();
// context.setManufacturer(device.getManufacturer());
// context.setManufacturerModel(device.getManufacturerModel());
// context.setServiceIp(device.getServiceIp());
// context.setServicePort(device.getServicePort());
// SdfApiAdapter sdfApi = SdfApiAdapterFactory.newInstance(context);
// String dh = sdfApi.openDevice();
// String sh = sdfApi.openSession(dh);
// DeviceInfo deviceInfo = sdfApi.getDeviceInfo(sh);
//
// byte[] sk = sdfApi.generateRandom(sh, 16);
// byte[] publicKey = sdfApi.exportEncPublicKeyECC(sh, 1);
// byte[] encSk = sdfApi.externalEncryptECC(sh, publicKey, sk);
//
// TmkInfo info = new TmkInfo();
// info.setId(IdWorker.getId());
// info.setCreateTime(now);
// info.setDeviceSerial(deviceInfo.getDeviceSerial());
// info.setEncTmk(Hex.toHexString(encSk));
// info.setPubKey(Hex.toHexString(publicKey));
// tmkInfoMapper.insert(info);
//
// BcSdfApiAdaptor bcApi = new BcSdfApiAdaptor();
// byte[] bcPubK = bcApi.exportEncPublicKeyECC("", 1);
// byte[] bcEncSk = sdfApi.exchangeDigitEnvelopeBaseOnECC(sh, 1, bcPubK, encSk);
//
// TmkInfo bcinfo = new TmkInfo();
// bcinfo.setId(IdWorker.getId());
// bcinfo.setCreateTime(now);
// bcinfo.setDeviceSerial(bcApi.getDeviceInfo("").getDeviceSerial());
// bcinfo.setEncTmk(Hex.toHexString(bcEncSk));
// bcinfo.setPubKey(Hex.toHexString(bcPubK));
// tmkInfoMapper.insert(bcinfo);
// sdfApi.closeSession(sh);
// sdfApi.closeDevice(dh);
ParamConf conf = new ParamConf();
conf.setKey("tmk_init");
conf.setValue("true");
conf.setCreatTime(LocalDateTime.now());
paramConfMapper.insert(conf);
}
private void checkName(String name) {
LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<Device>()
.eq(Device::getName, name);

View File

@ -9,7 +9,6 @@ import com.sunyard.chsm.sdf.adapter.SdfApiAdapter;
import com.sunyard.chsm.sdf.adapter.SdfApiAdapterFactory;
import com.sunyard.chsm.sdf.context.DeviceContext;
import com.sunyard.chsm.sdf.model.DeviceInfo;
import com.sunyard.chsm.sdf.model.EccPubKey;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.beans.factory.InitializingBean;
@ -65,8 +64,8 @@ public class DeviceTask implements InitializingBean {
String sh = sdfApiAdapter.openSession(dh);
DeviceInfo info = sdfApiAdapter.getDeviceInfo(sh);
log.info("get DeviceInfo: {}", info);
EccPubKey eccPubKey = sdfApiAdapter.exportEncPublicKeyECC(sh, 2);
log.info("exportEncPublicKeyECC: {}", Hex.toHexString(eccPubKey.getPubKeyBytes()));
byte[] eccPubKey = sdfApiAdapter.exportEncPublicKeyECC(sh, 2);
log.info("exportEncPublicKeyECC: {}", Hex.toHexString(eccPubKey));
sdfApiAdapter.closeSession(sh);
sdfApiAdapter.closeDevice(dh);
connected = true;

View File

@ -17,4 +17,39 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- JUnit 5 API (仅单元测试使用)-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.0</version>
<scope>test</scope>
</dependency>
<!-- Spring Boot Test (仅单元测试使用) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sunyard.chsm</groupId>
<artifactId>chsm-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.sunyard.chsm</groupId>
<artifactId>chsm-params</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.dm</groupId>
<artifactId>DmJdbcDriver</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,25 @@
package com.sunyard.chsm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author liulu
* @since 2024/10/25
*/
@Slf4j
@SpringBootApplication
public class WebServerApp {
public static void main(String[] args) {
SpringApplication.run(WebServerApp.class, args);
log.info("---------------------WebServerApp 启动完成-------------------");
}
}

View File

@ -0,0 +1,22 @@
package com.sunyard.chsm.config;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author liulu
* @since 2024/11/13
*/
public class AuthHandler implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
}

View File

@ -0,0 +1,17 @@
package com.sunyard.chsm.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 应用Token服务接口
*
* @author liulu
* @version V1.0
* @since 2023/8/4
*/
@RestController
@RequestMapping
public class AppLoginController {
}

View File

@ -0,0 +1,62 @@
server:
port: 89
tomcat:
uri-encoding: UTF-8
threads:
max: 1000
min-spare: 30
spring:
main:
allow-circular-references: true
# 数据源
datasource:
driverClassName: dm.jdbc.driver.DmDriver
url: jdbc:dm://172.16.17.236:5236?schema=SSP&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=UTF-8
username: SUNYARD
# Jasypt加密 可到common-utils中找到JasyptUtil加解密工具类生成加密结果 格式为ENC(加密结果)
password: 123456
hikari:
minimum-idle: 5
maximum-pool-size: 100
idle-timeout: 600000 # 空闲连接的最大等待时间,单位为毫秒 (10 分钟)
max-lifetime: 1800000 # 连接池中连接的最大存活时间,单位为毫秒 (30 分钟)
connection-timeout: 30000 # 获取连接的超时时间,单位为毫秒 (30 秒)
leak-detection-threshold: 2000 # 连接泄漏检测阈值,单位为毫秒 (2 秒)
# 连接测试配置,确保连接有效性
connection-test-query: SELECT 1
validation-timeout: 5000 # 验证连接的超时时间,单位为毫秒 (5 秒)
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
mybatis-plus:
mapper-locations: classpath*:mapper/**/*Mapper.xml
# 原生配置
configuration:
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
map-underscore-to-camel-case: true
cache-enabled: false
lazy-loading-enabled: false
global-config:
# 数据库相关配置
db-config:
#主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
id-type: AUTO
#驼峰下划线转换
table-underline: true
#是否开启大写命名,默认不开启
capital-mode: true
#逻辑删除配置
#logic-delete-value: 1
#logic-not-delete-value: 0
logging:
level:
root: info
com.sunyard.chsm.mapper: debug
# org.springframework.web: trace
# config: classpath:log4j2.xml

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--<include resource="org/springframework/boot/logging/logback/base.xml"/>-->
<!-- 定义log文件的目录 -->
<property name="LOG_HOME" value="logs"/>
<!-- 加载 Spring 配置文件信息 -->
<springProperty scope="context" name="applicationName" source="spring.application.name" defaultValue="localhost"/>
<!-- 日志输出格式 -->
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %X{tl} [%thread] %-5level [%logger{0}:%L]- %msg%n%ex{15}"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!--日志文件输出格式-->
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset> <!-- 设置字符集 -->
</encoder>
</appender>
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/%d{yyyy-MM}/INFO-%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<!--单个文件-->
<maxFileSize>10MB</maxFileSize>
<!--文件保存时间(天)-->
<maxHistory>30</maxHistory>
<!--总文件日志最大的大小-->
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<!--日志文件输出格式-->
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset> <!-- 设置字符集 -->
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
</appender>
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/debug.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/%d{yyyy-MM}/sspweb-DEBUG-%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<!--单个文件-->
<maxFileSize>10MB</maxFileSize>
<!--文件保存时间(天)-->
<maxHistory>30</maxHistory>
<!--总文件日志最大的大小-->
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<!--日志文件输出格式-->
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset> <!-- 设置字符集 -->
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
</appender>
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/%d{yyyy-MM}/ERROR-%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<!--单个文件-->
<maxFileSize>10MB</maxFileSize>
<!--文件保存时间(天)-->
<maxHistory>30</maxHistory>
<!--总文件日志最大的大小-->
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<!--日志文件输出格式-->
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset> <!-- 设置字符集 -->
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<root level="INFO">
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="DEBUG_FILE"/>
<appender-ref ref="ERROR_FILE"/>
<appender-ref ref="STDOUT"/>
</root>
</configuration>

View File

@ -0,0 +1,28 @@
package com.sunyard.chsm.service;
import com.sunyard.chsm.WebServerApp;
import com.sunyard.chsm.model.dto.KeyInfoDTO;
import com.sunyard.chsm.model.entity.KeyRecord;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@Slf4j
@SpringBootTest(classes = WebServerApp.class)
public class SYMEncryptControllerTest {
private static final long TEST_APP_ID = 1852232967292882946L;
private static final long TEST_KEY_ID = 1852232967292882945L;
@Autowired
private KeyRecordService keyRecordService;
@Test
void testSelectKey() {
KeyRecord key = keyRecordService.selectByKeyId(TEST_KEY_ID);
System.out.println(key);
}
}

View File

@ -0,0 +1,34 @@
package sdf;
import com.sunyard.chsm.WebServerApp;
import com.sunyard.chsm.enums.KeyAlg;
import com.sunyard.chsm.sdf.BCSdfApiService;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Random;
@Slf4j
@SpringBootTest(classes = WebServerApp.class)
public class BCSdfApiServiceTest {
@Autowired
private BCSdfApiService bcSdfApiService;
// 对称加密解密测试
@Test
void testSym() {
byte[] data = new byte[128];
new Random().nextBytes( data );
byte[] key = bcSdfApiService.genSymKey( KeyAlg.SM4 );
byte[] enData = bcSdfApiService.symEncrypt( KeyAlg.SM4, key, data );
byte[] deData = bcSdfApiService.symDecrypt( KeyAlg.SM4, key, enData );
Assert.assertArrayEquals( data, deData );
}
}

View File

@ -0,0 +1,62 @@
server:
port: 89
tomcat:
uri-encoding: UTF-8
threads:
max: 1000
min-spare: 30
spring:
main:
allow-circular-references: true
# 数据源
datasource:
driverClassName: dm.jdbc.driver.DmDriver
url: jdbc:dm://172.16.17.236:5236?schema=SSP&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=UTF-8
username: SUNYARD
# Jasypt加密 可到common-utils中找到JasyptUtil加解密工具类生成加密结果 格式为ENC(加密结果)
password: 123456
hikari:
minimum-idle: 5
maximum-pool-size: 100
idle-timeout: 600000 # 空闲连接的最大等待时间,单位为毫秒 (10 分钟)
max-lifetime: 1800000 # 连接池中连接的最大存活时间,单位为毫秒 (30 分钟)
connection-timeout: 30000 # 获取连接的超时时间,单位为毫秒 (30 秒)
leak-detection-threshold: 2000 # 连接泄漏检测阈值,单位为毫秒 (2 秒)
# 连接测试配置,确保连接有效性
connection-test-query: SELECT 1
validation-timeout: 5000 # 验证连接的超时时间,单位为毫秒 (5 秒)
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
mybatis-plus:
mapper-locations: classpath*:mapper/**/*Mapper.xml
# 原生配置
configuration:
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
map-underscore-to-camel-case: true
cache-enabled: false
lazy-loading-enabled: false
global-config:
# 数据库相关配置
db-config:
#主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
id-type: AUTO
#驼峰下划线转换
table-underline: true
#是否开启大写命名,默认不开启
capital-mode: true
#逻辑删除配置
#logic-delete-value: 1
#logic-not-delete-value: 0
logging:
level:
root: info
com.sunyard.chsm.mapper: debug
# org.springframework.web: trace
# config: classpath:log4j2.xml

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--<include resource="org/springframework/boot/logging/logback/base.xml"/>-->
<!-- 定义log文件的目录 -->
<property name="LOG_HOME" value="logs"/>
<!-- 加载 Spring 配置文件信息 -->
<springProperty scope="context" name="applicationName" source="spring.application.name" defaultValue="localhost"/>
<!-- 日志输出格式 -->
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %X{tl} [%thread] %-5level [%logger{0}:%L]- %msg%n%ex{15}"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!--日志文件输出格式-->
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset> <!-- 设置字符集 -->
</encoder>
</appender>
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/%d{yyyy-MM}/INFO-%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<!--单个文件-->
<maxFileSize>10MB</maxFileSize>
<!--文件保存时间(天)-->
<maxHistory>30</maxHistory>
<!--总文件日志最大的大小-->
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<!--日志文件输出格式-->
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset> <!-- 设置字符集 -->
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
</appender>
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/debug.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/%d{yyyy-MM}/sspweb-DEBUG-%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<!--单个文件-->
<maxFileSize>10MB</maxFileSize>
<!--文件保存时间(天)-->
<maxHistory>30</maxHistory>
<!--总文件日志最大的大小-->
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<!--日志文件输出格式-->
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset> <!-- 设置字符集 -->
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
</appender>
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/%d{yyyy-MM}/ERROR-%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<!--单个文件-->
<maxFileSize>10MB</maxFileSize>
<!--文件保存时间(天)-->
<maxHistory>30</maxHistory>
<!--总文件日志最大的大小-->
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<!--日志文件输出格式-->
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset> <!-- 设置字符集 -->
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<root level="INFO">
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="DEBUG_FILE"/>
<appender-ref ref="ERROR_FILE"/>
<appender-ref ref="STDOUT"/>
</root>
</configuration>

View File

@ -189,7 +189,7 @@ CREATE TABLE sp_key_csr (
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',
@ -214,3 +214,22 @@ CREATE TABLE sp_app_cert (
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
PRIMARY KEY (id)
);
-- CA证书
CREATE TABLE sp_ca_cert (
id BIGINT NOT NULL COMMENT 'id',
ca_name VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'ca name',
ca_url VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'url',
key_alg VARCHAR(30) NOT NULL DEFAULT '' COMMENT '密钥算法',
status VARCHAR(30) 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 '结束时间',
pub_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)
);