Compare commits

...

76 Commits

Author SHA1 Message Date
Cheney
91c496ffcd 测试 CICD 流程测试
Some checks failed
Actions Demo / Auto-Build-Actions (push) Failing after 8h30m27s
2025-03-12 18:22:43 +08:00
Cheney
7deb678f91 测试 CICD 流程测试
Some checks failed
Actions Demo / Auto-Build-Actions (push) Failing after 8h35m31s
2025-03-12 17:54:46 +08:00
Cheney
16160bbb4d 测试 CICD 流程测试
Some checks failed
Actions Demo / Auto-Build-Actions (push) Failing after 8h30m25s
2025-03-12 17:43:47 +08:00
Cheney
1a6aa93768 测试 CICD 流程测试
Some checks failed
Actions Demo / Auto-Build-Actions (push) Failing after 8h35m4s
2025-03-12 17:33:10 +08:00
Cheney
9207d48b8d 测试 CICD 流程测试
Some checks failed
Actions Demo / Auto-Build-Actions (push) Failing after 8h31m58s
2025-03-12 17:14:09 +08:00
Cheney
492f9929c6 测试 CICD 流程测试
Some checks failed
Actions Demo / Auto-Build-Actions (push) Failing after 8h32m13s
2025-03-12 16:34:08 +08:00
Cheney
e6f38748b7 添加 CICD 流程测试
Some checks failed
Actions Demo / Auto-Build-Actions (push) Failing after 8h30m18s
2025-03-12 12:01:05 +08:00
liulu
3fdf2f28b0 add sh 2025-01-23 17:12:45 +08:00
liulu
3e1572dcad 代码扫描问题解决 2025-01-14 17:26:50 +08:00
liulu
334607a896 fix 2025-01-03 15:42:35 +08:00
liulu
f84de95e9a fix 2025-01-03 15:32:50 +08:00
liulu
ad679f48fd 主密钥校验值 2025-01-03 11:38:25 +08:00
liulu
8be178317a 白名单 2025-01-02 15:29:49 +08:00
liulu
7d2d5c9e3c update Dockerfile 2024-12-31 10:46:40 +08:00
liulu
48b9fba0c3 docker构建 2024-12-31 10:05:14 +08:00
liulu
5f9680c19b docker构建 2024-12-30 16:57:58 +08:00
liulu
baf38de11a docker构建 2024-12-30 15:32:56 +08:00
liulu
c05423c0a3 导出公钥和证书 2024-12-30 09:06:11 +08:00
liulu
265d81a7f0 fix 2024-12-27 14:57:18 +08:00
liulu
b6fc524b3f 非对称运算 2024-12-26 17:30:10 +08:00
liulu
6cdf222def hash 运算 2024-12-25 15:45:35 +08:00
liulu
182918a7b9 带签名数字信封加密和解密 2024-12-25 14:22:39 +08:00
liulu
da77d42f89 数字信封加密和解密 2024-12-24 17:34:08 +08:00
liulu
17bc552dc5 应用增加白名单 2024-12-24 16:24:00 +08:00
yihzhou
7af2547027 证书类接口暂存 2024-12-24 15:47:47 +08:00
liulu
81b0ea4c3b 非对称签名验签 2024-12-24 14:25:54 +08:00
liulu
61f94f39cb 非对称签名验签 2024-12-23 15:38:09 +08:00
liulu
7fe6482983 非对称加解密 2024-12-20 15:03:22 +08:00
liulu
0ba56c7c25 修改测试 2024-12-20 11:03:01 +08:00
liulu
b982446dab fix 2024-12-20 10:37:59 +08:00
liulu
e3bf63ddef fix 2024-12-20 10:34:08 +08:00
liulu
427df73a40 对称密钥测试 2024-12-19 16:42:47 +08:00
liulu
36a2066542 fix 2024-12-18 11:00:39 +08:00
liulu
f19891c39d 增加权限码 2024-12-18 09:35:30 +08:00
liulu
9cb9b43052 对称运算类接口 2024-12-18 09:25:41 +08:00
liulu
bd099ba04e 枚举转换 2024-12-17 11:17:38 +08:00
liulu
be7bb30fd4 密钥管理接口 2024-12-17 10:41:33 +08:00
liulu
6893a1aca3 service api code 2024-12-17 08:54:46 +08:00
liulu
34b7b79bf6 service api code 2024-12-16 16:14:15 +08:00
liulu
1a6f527fbd sdf api test 2024-12-16 09:51:46 +08:00
liulu
0a8c49f533 sdf api 2024-12-13 15:00:10 +08:00
liulu
8f997d2a00 sdf api 2024-12-13 14:32:58 +08:00
liulu
f2720fdb83 sdf api 2024-12-12 17:41:32 +08:00
liulu
36bf16353e sdf api 2024-12-12 15:11:21 +08:00
liulu
44161e70bb sdf api 2024-12-12 10:05:37 +08:00
liulu
2250a6234b sdf api 2024-12-11 17:07:34 +08:00
liulu
3dc061907f sdf api 2024-12-11 11:49:40 +08:00
liulu
63fce073dd sdf api 2024-12-10 17:21:17 +08:00
liulu
4febd0520d add task 2024-12-10 11:43:46 +08:00
liulu
b229e6abdf add task 2024-12-10 11:41:29 +08:00
liulu
aabc9c56e1 sdf fix 2024-12-10 11:10:05 +08:00
liulu
29c5fb261f 设备连接检查 2024-12-10 10:02:54 +08:00
liulu
f4361d2fd5 主密钥同步 2024-12-09 17:15:57 +08:00
liulu
258cd3ed81 add code 2024-12-09 15:27:46 +08:00
liulu
3646cce811 sdf fix 2024-12-09 15:17:15 +08:00
liulu
ff59a4fe30 去掉打印 2024-12-09 11:11:27 +08:00
liulu
9e8b3fa5c8 sdf 2024-12-09 11:10:09 +08:00
liulu
48fb1db044 权限管理 2024-12-06 16:39:30 +08:00
liulu
04cfab2f82 设备和token 2024-12-06 15:38:22 +08:00
liulu
bcf8dfb5f3 sdf 2024-12-05 17:33:52 +08:00
liulu
0f61f4be5b sdf 2024-12-05 16:49:32 +08:00
liulu
b87165b850 增加白名单配置 2024-12-02 18:20:55 +08:00
liulu
00829ae822 增加白名单配置 2024-12-02 16:51:51 +08:00
liulu
f64f225b2e 增加白名单配置 2024-12-02 16:40:19 +08:00
liulu
ff10930668 fix 2024-12-02 11:29:21 +08:00
liulu
f868a72c99 fix 2024-11-27 14:26:08 +08:00
liulu
7be43afa8b fix 2024-11-25 14:19:45 +08:00
liulu
856723e729 白名单 2024-11-25 10:20:13 +08:00
liulu
8716e97f04 设备组负载 2024-11-22 14:34:05 +08:00
liulu
d003b1d971 fix 2024-11-21 16:05:04 +08:00
liulu
90750ac3b9 fix 2024-11-20 17:26:09 +08:00
liulu
dc349561bf fix 2024-11-19 16:18:31 +08:00
liulu
f154af5352 rename 2024-11-19 16:05:24 +08:00
liulu
1a12f4a7bf 整理初始化sql 2024-11-18 16:47:33 +08:00
liulu
493e30b318 fix 2024-11-15 15:28:39 +08:00
liulu
84aaa824c5 fix 2024-11-14 17:23:38 +08:00
234 changed files with 11105 additions and 1348 deletions

27
.gitea/workflows/main.yml Normal file
View File

@ -0,0 +1,27 @@
name: Actions Demo
run-name: ${{ gitea.actor }} is testing out Actions 🚀
on: [push]
jobs:
Auto-Build-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event. 1"
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- name: Check out repository code
uses: actions/checkout@v4
with:
token: ${{ secrets.TOKEN }}
repository: Sunyard/chsm-server # 你的仓库路径
endpoint: http://git.fullstack.club/
ref: chsm-v1.0 # Gitea实例地址
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
- name: Build Docker image
run: |
docker build -t ${{ gitea.repository }}:latest .
- run: echo "This job's status is ${{ job.status }} 1 ."

27
build.cmd Normal file
View File

@ -0,0 +1,27 @@
@echo off
SETLOCAL
chcp 65001
del /S *.log
call mvn clean -DskipTests=true package
cd chsm-web-manage
echo ">>>>>>>>>>>begin build docker image ...<<<<<<<<<<<<<"
docker build -t chsm-web-manager:latest .
echo ">>>>>>>>>>>build docker image success<<<<<<<<<<<<<"
cd ../chsm-web-server
echo ">>>>>>>>>>>begin build docker image ...<<<<<<<<<<<<<"
docker build -t chsm-web-server:latest .
echo ">>>>>>>>>>>build docker image success<<<<<<<<<<<<<"
cd ..
echo ">>>>>>>>>>>begin build offline tar ...<<<<<<<<<<<<<"
docker save -o chsm-web-manager.tar chsm-web-manager:latest
docker save -o chsm-web-server.tar chsm-web-server:latest
echo ">>>>>>>>>>>build docker offline tar success<<<<<<<<<<<<<"
pause

20
build.sh Normal file
View File

@ -0,0 +1,20 @@
#!/bin/bash
call /app/maven/bin/mvn clean -DskipTests=true package
cd chsm-web-manage
echo ">>>>>>>>>>>begin build docker image ...<<<<<<<<<<<<<"
docker build -t chsm-web-manager:latest .
echo ">>>>>>>>>>>build docker image success<<<<<<<<<<<<<"
cd ../chsm-web-server
echo ">>>>>>>>>>>begin build docker image ...<<<<<<<<<<<<<"
docker build -t chsm-web-server:latest .
echo ">>>>>>>>>>>build docker image success<<<<<<<<<<<<<"
cd ..
echo ">>>>>>>>>>>begin build offline tar ...<<<<<<<<<<<<<"
docker save -o chsm-web-manager.tar chsm-web-manager:latest
docker save -o chsm-web-server.tar chsm-web-server:latest
echo ">>>>>>>>>>>build docker offline tar success<<<<<<<<<<<<<"

View File

@ -20,6 +20,11 @@
<dependencies>
<dependency>
<groupId>com.sunyard.chsm</groupId>
<artifactId>chsm-params</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
@ -49,13 +54,21 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
</dependency>
<dependency>
<groupId>com.github.briandilley.jsonrpc4j</groupId>
<artifactId>jsonrpc4j</artifactId>
<version>1.6</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,52 @@
package com.sunyard.chsm.constant;
/**
* @author liulu
* @since 2024/12/16
*/
public interface AuthCodeConst {
// 密钥管理
String key_info = "key_info";
String key_list = "key_list";
String key_create = "key_create";
String key_update = "key_update";
String key_enable = "key_enable";
String key_disable = "key_disable";
String key_archive = "key_archive";
String key_destroy = "key_destroy";
// 对称运算
String sym_enc = "sym_enc";
String sym_dec = "sym_dec";
String cal_hmac = "cal_hmac";
String check_hmac = "check_hmac";
String cal_mac = "cal_mac";
String check_mac = "check_mac";
String gen_random = "gen_random";
// 非对称运算
String asym_enc = "asym_enc";
String asym_dec = "asym_dec";
String sign_raw = "sign_raw";
String verify_raw = "verify_raw";
String sign_p1 = "sign_p1";
String verify_p1 = "verify_p1";
String sign_P7Attach = "sign_P7Attach";
String verify_P7Attach = "verify_P7Attach";
String sign_P7Detach = "sign_P7Detach";
String verify_P7Detach = "verify_P7Detach";
String envelope_seal = "envelope_enc";
String envelope_unseal = "envelope_dec";
String signed_envelope_seal = "signed_envelope_enc";
String signed_envelope_unseal = "signed_envelope_dec";
String cal_hash = "cal_hash";
// 证书
String cert_info = "cert_info";
String cert_exinfo = "cert_exinfo";
String cert_check = "cert_check";
String cert_upload = "cert_upload";
String cert_remove = "cert_remove";
}

View File

@ -0,0 +1,17 @@
package com.sunyard.chsm.constant;
import com.sunyard.chsm.utils.CodecUtils;
/**
* @author liulu
* @since 2024/12/19
*/
public interface CryptoConst {
byte[] USER_ID = CodecUtils.decodeHex("31323334353637383132333435363738");
static byte[] iv() {
return CodecUtils.decodeHex("30303030303030303030303030303030");
}
}

View File

@ -1,4 +1,4 @@
package com.sunyard.ssp.common.constant;
package com.sunyard.chsm.constant;
/**
* @author:fyc
@ -12,6 +12,13 @@ public interface ParamConfKeyConstant {
*/
String IP_WHITELIST_SWITCH = "ipWhitelistSwitch";
String TMK_INIT = "tmk_init";
String TMK_CHECK_VALUE = "tmk_check_value";
String ENABLE_SOFT_DEVICE = "enable_soft_device";
String SOFT_ENC_TMK = "soft_enc_tmk";
/**
* 通讯超时时间
*/
@ -105,18 +112,20 @@ public interface ParamConfKeyConstant {
/**
* 系统初始化配置文件路劲
*/
String SYS_PARAM_CONFIG_FILE_PATH = System.getProperty("user.dir") + "/config/sysParam.config.json";
String SYS_PARAM_CONFIG_FILE_PATH = System.getProperty("user.dir") + "/config/sysParam.config.json";
/**
* 调试模式开关枚举
*/
enum SYS_DEBUG_SWITCH_VALUE{
enum SYS_DEBUG_SWITCH_VALUE {
DEV("dev"), CONFIG("config"), PRODUCT("product");
private String value;
private SYS_DEBUG_SWITCH_VALUE(String value){
private SYS_DEBUG_SWITCH_VALUE(String value) {
this.value = value;
}
public String getValue(){
public String getValue() {
return value;
}
}

View File

@ -1,4 +1,4 @@
package com.sunyard.ssp.common.constant;
package com.sunyard.chsm.constant;
/**
* @author Exrickx
@ -20,6 +20,8 @@ public interface SecurityConstant {
*/
String HEADER = "accessToken";
String ATTRIBUTE_APP_USER = "ATTRIBUTE_APP_USER";
/**
* 权限参数头
*/

View File

@ -1,5 +1,6 @@
package com.sunyard.chsm.enums;
import com.sunyard.chsm.constant.AuthCodeConst;
import lombok.AllArgsConstructor;
import lombok.Getter;
@ -11,24 +12,46 @@ import lombok.Getter;
@AllArgsConstructor
public enum ApiFunEnum {
sym_enc(ApiGroupEnum.SYM_API,"sym_enc", "对称加密"),
sym_dec(ApiGroupEnum.ASYM_API,"sym_dec", "对称解密"),
cal_hmac(ApiGroupEnum.SYM_API,"cal_hmac", "计算Hmac"),
check_hmac(ApiGroupEnum.ASYM_API,"check_hmac", "验证Hmac"),
cal_mac(ApiGroupEnum.SYM_API,"cal_mac", "计算mac"),
check_mac(ApiGroupEnum.ASYM_API,"check_mac", "验证mac"),
gen_random(ApiGroupEnum.SYM_API,"gen_random", "生成随机数"),
key_info(ApiGroupEnum.KEY_MANAGE_API, AuthCodeConst.key_info, "查询密钥详情"),
key_list(ApiGroupEnum.KEY_MANAGE_API, AuthCodeConst.key_list, "查询密钥列表"),
key_create(ApiGroupEnum.KEY_MANAGE_API, AuthCodeConst.key_create, "创建密钥"),
key_update(ApiGroupEnum.KEY_MANAGE_API, AuthCodeConst.key_update, "更新密钥"),
key_enable(ApiGroupEnum.KEY_MANAGE_API, AuthCodeConst.key_enable, "启用密钥"),
key_disable(ApiGroupEnum.KEY_MANAGE_API, AuthCodeConst.key_disable, "停用密钥"),
key_archive(ApiGroupEnum.KEY_MANAGE_API, AuthCodeConst.key_archive, "归档密钥"),
key_destroy(ApiGroupEnum.KEY_MANAGE_API, AuthCodeConst.key_destroy, "销毁密钥"),
sym_enc(ApiGroupEnum.SYM_API, AuthCodeConst.sym_enc, "对称加密"),
sym_dec(ApiGroupEnum.SYM_API, AuthCodeConst.sym_dec, "对称解密"),
cal_hmac(ApiGroupEnum.SYM_API, AuthCodeConst.cal_hmac, "计算Hmac"),
check_hmac(ApiGroupEnum.SYM_API, AuthCodeConst.check_hmac, "验证Hmac"),
cal_mac(ApiGroupEnum.SYM_API, AuthCodeConst.cal_mac, "计算mac"),
check_mac(ApiGroupEnum.SYM_API, AuthCodeConst.check_mac, "验证mac"),
gen_random(ApiGroupEnum.SYM_API, AuthCodeConst.gen_random, "生成随机数"),
asym_enc(ApiGroupEnum.ASYM_API, AuthCodeConst.asym_enc, "非对称加密"),
asym_dec(ApiGroupEnum.ASYM_API, AuthCodeConst.asym_dec, "非对称解密"),
sign_raw(ApiGroupEnum.ASYM_API, AuthCodeConst.sign_raw, "RAW签名"),
verify_raw(ApiGroupEnum.ASYM_API, AuthCodeConst.verify_raw, "RAW验签"),
sign_p1(ApiGroupEnum.ASYM_API, AuthCodeConst.sign_p1, "P1签名"),
verify_p1(ApiGroupEnum.ASYM_API, AuthCodeConst.verify_p1, "P1验签"),
sign_P7Attach(ApiGroupEnum.ASYM_API, AuthCodeConst.sign_P7Attach, "P7 Attach签名"),
verify_P7Attach(ApiGroupEnum.ASYM_API, AuthCodeConst.verify_P7Attach, "P7 Attach验签"),
sign_P7Detach(ApiGroupEnum.ASYM_API, AuthCodeConst.sign_P7Detach, "P7 Detach签名"),
verify_P7Detach(ApiGroupEnum.ASYM_API, AuthCodeConst.verify_P7Detach, "P7 Detach验签"),
envelope_seal(ApiGroupEnum.ASYM_API, AuthCodeConst.envelope_seal, "P7数字信封加封"),
envelope_unseal(ApiGroupEnum.ASYM_API, AuthCodeConst.envelope_unseal, "P7数字信封解封"),
signed_envelope_seal(ApiGroupEnum.ASYM_API, AuthCodeConst.signed_envelope_seal, "带签名的数字信封加封"),
signed_envelope_unseal(ApiGroupEnum.ASYM_API, AuthCodeConst.signed_envelope_unseal, "带签名的数字信封解封"),
cal_hash(ApiGroupEnum.HASH_API, AuthCodeConst.cal_hash, "计算Hash"),
cert_info(ApiGroupEnum.CERT_API, AuthCodeConst.cert_info, "获取证书信息"),
cert_exinfo(ApiGroupEnum.CERT_API, AuthCodeConst.cert_exinfo, "获取证书拓展信"),
cert_check(ApiGroupEnum.CERT_API, AuthCodeConst.cert_check, "验证证书"),
cert_upload(ApiGroupEnum.CERT_API, AuthCodeConst.cert_upload, "上传用户证书"),
cert_remove(ApiGroupEnum.CERT_API, AuthCodeConst.cert_remove, "删除用户证书"),
asym_enc(ApiGroupEnum.ASYM_API,"asym_enc", "非对称加密"),
asym_dec(ApiGroupEnum.ASYM_API,"asym_dec", "非对称解密"),
sign_raw(ApiGroupEnum.ASYM_API,"sign_raw", "RAW签名"),
verify_raw(ApiGroupEnum.ASYM_API,"verify_raw", "RAW验签"),
sign_p1(ApiGroupEnum.ASYM_API,"sign_p1", "P1签名"),
verify_p1(ApiGroupEnum.ASYM_API,"verify_p1", "P1验签"),
sign_P7Attach(ApiGroupEnum.ASYM_API,"sign_P7Attach", "P7 Attach签名"),
verify_P7Attach(ApiGroupEnum.ASYM_API,"verify_P7Attach", "P7 Attach验签"),
sign_P7Detach(ApiGroupEnum.ASYM_API,"sign_P7Detach", "P7 Detach签名"),
verify_P7Detach(ApiGroupEnum.ASYM_API,"verify_P7Detach", "P7 Detach验签"),
;
private final ApiGroupEnum group;

View File

@ -14,9 +14,12 @@ import java.util.Objects;
@AllArgsConstructor
public enum ApiGroupEnum {
KEY_MANAGE_API("key_manage_api", "密钥管理接口"),
SYM_API("sym_api", "对称密钥计算接口"),
ASYM_API("asym_api", "非对称密钥计算接口"),
;
HASH_API("hash_api", "杂凑计算接口"),
CERT_API("cert_api", "证书接口"),
;
private final String code;
private final String name;

View File

@ -0,0 +1,14 @@
package com.sunyard.chsm.enums;
/**
* @author liulu
* @since 2024/12/7
*/
public enum DeviceTmkStatus {
device_error,
key_error,
available,
finished,
}

View File

@ -14,7 +14,7 @@ import java.util.Objects;
@AllArgsConstructor
public enum ManufacturerModelEnum {
enc001(ManufacturerEnum.SUNYARD, "enc001", "服务器密码机enc001"),
enc001(ManufacturerEnum.SUNYARD, "SYD-001", "服务器密码机"),
;
private final ManufacturerEnum manufacturer;

View File

@ -2,6 +2,8 @@ package com.sunyard.chsm.mapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sunyard.chsm.enums.KeyUsage;
import com.sunyard.chsm.model.Subject;
import com.sunyard.chsm.model.entity.AppCert;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.util.Assert;
@ -28,5 +30,31 @@ public interface AppCertMapper extends BaseMapper<AppCert> {
return certs.iterator().next();
}
default AppCert selectSignBySubject(String dn) {
Assert.hasText(dn, "证书序列号不能为空");
String subject = Subject.fromDN(dn).getDN();
List<AppCert> certs = selectList(new LambdaQueryWrapper<AppCert>()
.eq(AppCert::getSubject, subject)
.eq(AppCert::getCertType, KeyUsage.SIGN_VERIFY.getCode())
);
if (CollectionUtils.isEmpty(certs)) {
return null;
}
return certs.iterator().next();
}
default AppCert selectByTypeAndDn(String type,String dn) {
Assert.hasText(dn, "证书序列号不能为空");
String subject = Subject.fromDN(dn).getDN();
List<AppCert> certs = selectList(new LambdaQueryWrapper<AppCert>()
.eq(AppCert::getSubject, subject)
.eq(AppCert::getCertType, type)
);
if (CollectionUtils.isEmpty(certs)) {
return null;
}
return certs.iterator().next();
}
}

View File

@ -1,8 +1,13 @@
package com.sunyard.chsm.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sunyard.chsm.model.entity.Application;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import java.util.List;
/**
* @author liulu
@ -10,4 +15,15 @@ import org.apache.ibatis.annotations.Mapper;
*/
@Mapper
public interface ApplicationMapper extends BaseMapper<Application> {
default Application selectByAppKey(String appKey) {
List<Application> apps = selectList(new QueryWrapper<Application>().eq("app_key", appKey));
if (CollectionUtils.isEmpty(apps)) {
return null;
}
Assert.isTrue(apps.size() == 1, "app 数据异常");
return apps.iterator().next();
}
}

View File

@ -1,8 +1,13 @@
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.CryptoServiceApi;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.List;
/**
* @author liulu
@ -10,4 +15,17 @@ import org.apache.ibatis.annotations.Mapper;
*/
@Mapper
public interface CryptoServiceApiMapper extends BaseMapper<CryptoServiceApi> {
default List<CryptoServiceApi> selectByServiceIds(List<Long> serviceIds) {
if (CollectionUtils.isEmpty(serviceIds)) {
return Collections.emptyList();
}
return selectList(
new LambdaQueryWrapper<CryptoServiceApi>()
.in(CryptoServiceApi::getCryptoServiceId, serviceIds)
);
}
}

View File

@ -0,0 +1,36 @@
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.IpWhitelist;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.List;
/**
* @author liulu
* @since 2024/11/6
*/
@Mapper
public interface IpWhitelisttMapper extends BaseMapper<IpWhitelist> {
default List<IpWhitelist> selectByAppIds(List<Long> appIds) {
if (CollectionUtils.isEmpty(appIds)) {
return Collections.emptyList();
}
return selectList(
new LambdaQueryWrapper<IpWhitelist>()
.in(IpWhitelist::getAppId, appIds)
);
}
default void deleteByAppId(Long appId) {
delete(
new LambdaQueryWrapper<IpWhitelist>()
.eq(IpWhitelist::getAppId, appId)
);
}
}

View File

@ -1,7 +1,7 @@
package com.sunyard.ssp.modules.sysconf.paramconf.mapper;
package com.sunyard.chsm.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sunyard.ssp.modules.sysconf.paramconf.entity.ParamConf;
import com.sunyard.chsm.model.entity.ParamConf;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

View File

@ -2,6 +2,8 @@ package com.sunyard.chsm.mapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sunyard.chsm.enums.DeviceTmkStatus;
import com.sunyard.chsm.model.entity.Device;
import org.apache.ibatis.annotations.Mapper;
@ -14,12 +16,20 @@ import java.util.List;
@Mapper
public interface SpDeviceMapper extends BaseMapper<Device> {
default List<Device> selectConnedList() {
return selectList(
new LambdaQueryWrapper<Device>()
.eq(Device::getConnected, true)
default Device selectOneByStatus(DeviceTmkStatus status) {
Page<Device> devicePage = selectPage(
Page.of(1L, 1L),
new LambdaQueryWrapper<Device>().eq(Device::getTmkStatus, status.name())
);
}
List<Device> records = devicePage.getRecords();
if (records.isEmpty()) {
return null;
}
return records.iterator().next();
};
}

View File

@ -72,7 +72,7 @@ public class R<T> {
R<T> r = new R<>();
r.setSuccess(false);
r.setMessage(msg);
r.setCode(500);
r.setCode(400);
return r;
}

View File

@ -32,6 +32,7 @@ public abstract class CertDTO {
private Long id;
private Long applicationId;
private String appName;
private Boolean single;
/**
* 密钥算法
*/

View File

@ -0,0 +1,24 @@
package com.sunyard.chsm.model.dto;
import com.sunyard.chsm.enums.DeviceTmkStatus;
import com.sunyard.chsm.sdf.adapter.SdfApiAdapter;
import lombok.Data;
/**
* @author liulu
* @since 2024/12/10
*/
@Data
public class DeviceCheckRes {
private DeviceTmkStatus status;
private String deviceSerial;
private String pubKey;
private String encTmk;
private boolean hasError = false;
private String message;
private SdfApiAdapter sdfApiAdapter;
}

View File

@ -1,4 +1,4 @@
package com.sunyard.chsm.dto;
package com.sunyard.chsm.model.dto;
import lombok.Data;
@ -17,6 +17,9 @@ public class TmkStatus {
* 主密钥是否初始化
*/
private boolean tmkInit;
/**
* 主密钥校验值
*/
private String checkValue;
}

View File

@ -15,8 +15,8 @@ public class CryptoService {
private Long id;
private String name;
private Long deviceGroupId;
private String deviceGroupName;
// private Long deviceGroupId;
// private String deviceGroupName;
private String status;
private Long creatorId;

View File

@ -21,16 +21,20 @@ public class Device {
private Integer managePort;
private String manufacturer;
private String manufacturerModel;
private Integer encKeyIdx;
private String accessCredentials;
private Boolean connected;
private LocalDateTime lastCheckTime;
private LocalDateTime lastConnectedTime;
private Long groupId;
private String groupName;
private Integer weight;
private String tmkStatus;
private String deviceSerial;
private String pubKey;
private String encTmk;
private Boolean connected;
private LocalDateTime lastCheckTime;
private LocalDateTime lastConnectedTime;
private String remark;
private LocalDateTime createTime;
private LocalDateTime updateTime;

View File

@ -0,0 +1,28 @@
package com.sunyard.chsm.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
/**
* @author liulu
* @since 2024/11/22
*/
@Data
@TableName("sp_ip_whitelist")
public class IpWhitelist {
private Long id;
private Long appId;
private String ip;
private String scope;
private String status;
private String creator;
private String remark;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

View File

@ -1,10 +1,8 @@
package com.sunyard.ssp.modules.sysconf.paramconf.entity;
package com.sunyard.chsm.model.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@ -24,41 +22,32 @@ import java.time.LocalDateTime;
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("SC_PARAM_CONF")
@ApiModel(value="", description="")
public class ParamConf implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "主键")
@TableId("ID")
private Long id;
@ApiModelProperty(value = "数据大类")
@TableField("ITEM")
private Integer item;
@ApiModelProperty(value = "数据名称")
// @TableField("`KEY`") mysql需加
@TableField("KEY")
private String key;
@ApiModelProperty(value = "数据值")
@TableField("VALUE")
private String value;
@ApiModelProperty(value = "数据值类型")
@TableField("TYPE")
private String type;
@ApiModelProperty(value = "状态")
@TableField("STATUS")
private Integer status;
@ApiModelProperty(value = "创建时间")
@TableField("CREAT_TIME")
@TableField("CREATE_TIME")
private LocalDateTime creatTime;
@ApiModelProperty(value = "备注")
@TableField("MEMO")
private String memo;

View File

@ -1,8 +1,6 @@
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;
@ -27,10 +25,10 @@ public abstract class AbstractSdfApiService implements SdfApiService {
* 密钥长度根据 alg 指定的算法自动选择
* @return 密钥
*/
@Override
public byte[] genSymKey(KeyAlg alg) {
return genSymKey( alg, algKeyLen.get( alg ) );
}
// @Override
// public byte[] genSymKey(KeyAlg alg) {
// return genSymKey( alg, algKeyLen.get( alg ) );
// }
/**
@ -40,10 +38,10 @@ public abstract class AbstractSdfApiService implements SdfApiService {
* @param key 密钥值明文
* @param data 原始数据
*/
@Override
public byte[] symEncrypt(KeyAlg alg, byte[] key, byte[] data){
return symEncrypt( alg, AlgMode.ECB, Padding.PCKS5Padding, key, data );
}
// @Override
// public byte[] symEncrypt(KeyAlg alg, byte[] key, byte[] data){
// return symEncrypt( alg, AlgMode.ECB, Padding.PCKS5Padding, key, data );
// }
/**
@ -53,9 +51,9 @@ public abstract class AbstractSdfApiService implements SdfApiService {
* @param key 密钥值明文
* @param data 密文数据
*/
@Override
public byte[] symDecrypt(KeyAlg alg, byte[] key, byte[] data) {
return symDecrypt( alg, AlgMode.ECB, Padding.PCKS5Padding, key, data );
}
// @Override
// public byte[] symDecrypt(KeyAlg alg, byte[] key, byte[] data) {
// return symDecrypt( alg, AlgMode.ECB, Padding.PCKS5Padding, key, data );
// }
}

View File

@ -1,171 +1,186 @@
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.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 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 javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.*;
/**
* 基于 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 extends AbstractSdfApiService {
public BCSdfApiService() {
super();
}
@Override
public byte[] generateRandom(int len) {
byte[] res = new byte[len];
new SecureRandom().nextBytes(res);
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() {
// 生成密钥对
KeyPair keyPair = BCSM2Utils.generateKeyPair();
BCECPublicKey pubKey = (BCECPublicKey) keyPair.getPublic();
BCECPrivateKey priKey = (BCECPrivateKey) keyPair.getPrivate();
byte[] x = pubKey.getQ().getXCoord().getEncoded();
byte[] y = pubKey.getQ().getYCoord().getEncoded();
byte[] d = BigIntegers.asUnsignedByteArray(32, priKey.getD());
return new EccKey(LangUtils.merge(x, y), d);
}
@Override
public byte[] calculateMAC(byte[] symKey, byte[] pucIv, byte[] pucData) {
return new byte[0];
}
@Override
public byte[] hmac(byte[] key, byte[] srcData) {
return BCSM3Utils.hmac(key, srcData);
}
@Override
public byte[] hash(byte[] pucData) {
return BCSM3Utils.hash(pucData);
}
@Override
public byte[] encryptByTMK(byte[] data) {
return data;
}
@Override
public byte[] decryptByTMK(byte[] data) {
return data;
}
}
//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.context.AlgId;
//import com.sunyard.chsm.sdf.model.EccKey;
//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 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 javax.crypto.BadPaddingException;
//import javax.crypto.Cipher;
//import javax.crypto.IllegalBlockSizeException;
//import javax.crypto.NoSuchPaddingException;
//import java.security.InvalidKeyException;
//import java.security.KeyPair;
//import java.security.NoSuchAlgorithmException;
//import java.security.NoSuchProviderException;
//import java.security.SecureRandom;
//
//
///**
// * 基于 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 extends AbstractSdfApiService {
//
// public BCSdfApiService() {
// super();
// }
//
// @Override
// public byte[] generateRandom(int len) {
// byte[] res = new byte[len];
// new SecureRandom().nextBytes(res);
// 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[] symEncrypt(AlgId alg, byte[] key, byte[] iv, byte[] data) {
// return new byte[0];
// }
//
//
// @Override
// public byte[] symDecrypt(KeyAlg alg, AlgMode mode, Padding padding, byte[] key, byte[] data) {
// return symCalc(Cipher.DECRYPT_MODE, alg, mode, padding, key, data);
// }
//
// @Override
// public byte[] symDecrypt(AlgId alg, byte[] key, byte[] iv, byte[] data) {
// return new byte[0];
// }
//
//
// /**
// * 对称加解密的统一实现
// *
// * @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() {
// // 生成密钥对
// KeyPair keyPair = BCSM2Utils.generateKeyPair();
// BCECPublicKey pubKey = (BCECPublicKey) keyPair.getPublic();
// BCECPrivateKey priKey = (BCECPrivateKey) keyPair.getPrivate();
// byte[] x = pubKey.getQ().getXCoord().getEncoded();
// byte[] y = pubKey.getQ().getYCoord().getEncoded();
// byte[] d = BigIntegers.asUnsignedByteArray(32, priKey.getD());
// return new EccKey(LangUtils.merge(x, y), d);
// }
//
//
// @Override
// public byte[] calculateMAC(byte[] symKey, byte[] pucIv, byte[] pucData) {
// return new byte[0];
// }
//
// @Override
// public byte[] hmac(byte[] key, byte[] srcData) {
// return BCSM3Utils.hmac(key, srcData);
// }
//
// @Override
// public byte[] hash(byte[] pucData) {
// return BCSM3Utils.hash(pucData);
// }
//
// @Override
// public byte[] encryptByTMK(byte[] data) {
// return data;
// }
//
// @Override
// public byte[] decryptByTMK(byte[] data) {
// return data;
// }
//
//}

View File

@ -1,10 +1,11 @@
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.context.AlgId;
import com.sunyard.chsm.sdf.model.EccCipher;
import com.sunyard.chsm.sdf.model.EccKey;
import com.sunyard.chsm.sdf.model.EccSignature;
/**
@ -23,41 +24,47 @@ public interface SdfApiService {
byte[] generateRandom(int len);
/**
* 生成对称密钥
* @param alg 算法只支持对称算法
*
* @param alg 算法只支持对称算法
* @param keyLen 密钥长度(bit)只针对密钥长度可变的算法
* 禁止传 null
* @return 对称密钥
*/
byte[] genSymKey(KeyAlg alg, Integer keyLen);
byte[] genSymKey(KeyAlg alg);
// byte[] genSymKey(KeyAlg alg, Integer keyLen);
//
// byte[] genSymKey(KeyAlg alg);
/**
* 对称加密
* @param alg 算法只支持对称算法
* @param mode 轮模式
* @param padding 填充模式
* @param key 密钥值明文
* @param data 原始数据
*
* @param alg 算法只支持对称算法
* @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);
// byte[] symEncrypt(KeyAlg alg, AlgMode mode, Padding padding, byte[] key, byte[] data);
//
// byte[] symEncrypt(KeyAlg alg, byte[] key, byte[] data);
byte[] symEncrypt(AlgId alg, Padding padding, byte[] key, byte[] iv, byte[] data);
/**
* 对称解密
* @param alg 算法只支持对称算法
* @param key 密钥值明文
* @param mode 轮模式
*
* @param mode 轮模式
* @param alg 算法只支持对称算法
* @param padding 填充模式
* @param data 密文数据
* @param key 密钥值明文
* @param data 密文数据
*/
byte[] symDecrypt(KeyAlg alg, AlgMode mode, Padding padding, byte[] key, byte[] data);
byte[] symDecrypt(KeyAlg alg, byte[] key, byte[] data);
// byte[] symDecrypt(KeyAlg alg, AlgMode mode, Padding padding, byte[] key, byte[] data);
//
// byte[] symDecrypt(KeyAlg alg, byte[] key, byte[] data);
byte[] symDecrypt(AlgId alg, Padding padding, byte[] key, byte[] iv, byte[] data);
/**
@ -67,15 +74,61 @@ public interface SdfApiService {
*/
EccKey genKeyPairEcc();
/**
* 外部密钥ECC签名
*
* @param privateKey ECC私钥
* @param pucData 缓冲区指针用于存放外部输入的数据
* @param userId 签名者id
* @return pucSignature 返回签名值数据
*/
EccSignature externalSignWithIdECC(byte[] privateKey, byte[] pucData, byte[] userId);
EccSignature externalSignECC(byte[] privateKey, byte[] pucData);
/**
* 外部密钥ECC验证
*
* @param publicKey ECC公钥
* @param pubData 原文
* @param userId 签名者id
* @param signData 外部签名数据
* @return 0 成功; 非0 失败,返回错误代码
*/
boolean externalVerifyWithIdECC(byte[] publicKey, byte[] pubData, byte[] signData, byte[] userId);
boolean externalVerifyECC(byte[] publicKey, byte[] pubData, byte[] signData);
/**
* 外部密钥ECC公钥加密
*
* @param publicKey 外部ECC公钥结构
* @param pucData 缓冲区指针用于存放外部输入的数据
* @return pucEncData 返回数据密文
*/
EccCipher externalEncryptECC(byte[] publicKey, byte[] pucData);
/**
* 外部密钥ECC私钥解密
*
* @param privateKey 外部ECC私钥结构
* @param encData 缓冲区指针用于存放输入的数据密文
* @return pucData 返回数据明文
*/
byte[] externalDecryptECC(byte[] privateKey, byte[] encData);
/**
* 计算MAC
*
* @param algId algId
* @param padding padding
* @param symKey 用户指定的密钥
* @param pucIv 缓冲区指针用于存放输入和返回的IV数据
* @param pucData 缓冲区指针用于存放输入的数据明文
* @return pucEncData 返回MAC值 | puiLength 返回MAC值长度
*/
byte[] calculateMAC(byte[] symKey, byte[] pucIv, byte[] pucData);
byte[] calculateMAC(AlgId algId, Padding padding, byte[] symKey, byte[] pucIv, byte[] pucData);
byte[] hmac(byte[] key, byte[] srcData);
@ -83,8 +136,12 @@ public interface SdfApiService {
* 杂凑运算
*
* @param pucData 缓冲区指针用于存放输入的数据明文
* @param pubKey 执行预处理的公钥
* @param userId 执行预处理的userId
* @return hash值
*/
byte[] hash(byte[] pucData, byte[] pubKey, byte[] userId);
byte[] hash(byte[] pucData);
byte[] encryptByTMK(byte[] data);

View File

@ -1,22 +1,36 @@
package com.sunyard.chsm.sdf.adapter;
import com.sunyard.chsm.sdf.context.AlgId;
import com.sunyard.chsm.sdf.model.DeviceInfo;
import com.sunyard.chsm.sdf.model.EccCipher;
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.model.EccSignature;
import com.sunyard.chsm.sdf.util.LangUtils;
import com.sunyard.chsm.utils.gm.BCECUtils;
import com.sunyard.chsm.utils.gm.BCSM2Utils;
import com.sunyard.chsm.utils.gm.BCSM4Utils;
import lombok.SneakyThrows;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
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.math.ec.ECPoint;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.BigIntegers;
import org.springframework.util.Assert;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.SecureRandom;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author liulu
@ -24,6 +38,7 @@ import java.security.SecureRandom;
*/
public class BcSdfApiAdaptor implements SdfApiAdapter {
protected static final Map<String, byte[]> KEY_HANDLE_CONTEXT = new ConcurrentHashMap<>();
private static final DeviceInfo deviceInfo;
@ -39,6 +54,8 @@ public class BcSdfApiAdaptor implements SdfApiAdapter {
deviceInfo.setHashAlgAbility(7010608454676760881L);
}
protected static final BcSdfApiAdaptor INSTANCE = new BcSdfApiAdaptor();
@Override
public String openDevice() {
return "c95a78d9c04a557b7b46dbcb5f36cc66";
@ -51,7 +68,7 @@ public class BcSdfApiAdaptor implements SdfApiAdapter {
@Override
public String openSession(String deviceHandle) {
return "6975feaffaa35b31b6d4e4555ac403a1";
return UUID.randomUUID().toString();
}
@Override
@ -72,10 +89,10 @@ public class BcSdfApiAdaptor implements SdfApiAdapter {
}
@Override
public byte[] exportEncPublicKeyECC(String sessionHandle, int uiKeyIndex) {
public EccPubKey exportEncPublicKeyECC(String sessionHandle, int uiKeyIndex) {
BigInteger d = new BigInteger(1, getD());
ECPoint q = BCSM2Utils.G_POINT.multiply(d).normalize();
return LangUtils.merge(q.getXCoord().getEncoded(), q.getYCoord().getEncoded());
return new EccPubKey(256, q.getXCoord().getEncoded(), q.getYCoord().getEncoded());
}
private byte[] getD() {
@ -87,7 +104,7 @@ public class BcSdfApiAdaptor implements SdfApiAdapter {
@SneakyThrows
@Override
public EccKey generateKeyPairECC(String sessionHandle, String alg, int uiKeyBits) {
public EccKey generateKeyPairECC(String sessionHandle, AlgId alg) {
// 生成密钥对
KeyPair keyPair = BCSM2Utils.generateKeyPair();
BCECPublicKey pubKey = (BCECPublicKey) keyPair.getPublic();
@ -99,26 +116,203 @@ public class BcSdfApiAdaptor implements SdfApiAdapter {
}
@Override
public byte[] exchangeDigitEnvelopeBaseOnECC(String sessionHandle, int uiKeyIndex, byte[] pubKey, byte[] pucEncDateIn) {
return new byte[0];
public boolean getPrivateKeyAccessRight(String hSessionHandle, int uiKeyIndex, byte[] pucPassword) {
return true;
}
@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)
);
public EccCipher exchangeDigitEnvelopeBaseOnECC(String sessionHandle, int uiKeyIndex, EccPubKey pubKey, EccCipher pucEncDateIn) {
ECPublicKeyParameters pub = BCECUtils.createECPublicKeyParameters(pubKey.getX(), pubKey.getY());
ECPrivateKeyParameters pri = BCECUtils.createECPrivateKeyParameters(getD());
try {
byte[] encrypt = BCSM2Utils.encrypt(parameters, pucData);
return Arrays.copyOfRange(encrypt, 1, encrypt.length);
byte[] k = BCSM2Utils.decrypt(pri, LangUtils.merge(new byte[]{0x04}, pucEncDateIn.getC1C3C2Bytes()));
byte[] encrypt = BCSM2Utils.encrypt(pub, k);
return EccCipher.fromBytes(encrypt);
} catch (InvalidCipherTextException e) {
throw new IllegalArgumentException(e);
}
}
private static final SecureRandom RANDOM = new SecureRandom();
@Override
public EccSignature externalSignECC(String sessionHandle, EccPriKey privateKey, byte[] pucData) {
ECPrivateKeyParameters pri = BCECUtils.createECPrivateKeyParameters(privateKey.getD());
BigInteger n = pri.getParameters().getN();
BigInteger d = pri.getD();
BigInteger e = new BigInteger(1, pucData);
BigInteger r, s = null;
do {
BigInteger k;
do {
k = new BigInteger(n.bitLength(), RANDOM);
} while (k.compareTo(BigInteger.ONE) < 0 || k.compareTo(n) >= 0);
ECPoint kG = pri.getParameters().getG().multiply(k).normalize();
BigInteger x1 = kG.getAffineXCoord().toBigInteger();
r = e.add(x1).mod(n);
if (r.equals(BigInteger.ZERO) || r.add(k).equals(n)) {
continue;
}
BigInteger dPlus1Inv = d.add(BigInteger.ONE).modInverse(n);
s = dPlus1Inv.multiply(k.subtract(r.multiply(d))).mod(n);
} while (Objects.equals(s, BigInteger.ZERO));
return new EccSignature(BigIntegers.asUnsignedByteArray(32, r),
BigIntegers.asUnsignedByteArray(32, s));
}
@Override
public boolean externalVerifyECC(String sessionHandle, EccPubKey publicKey, byte[] pucData, EccSignature pucSignature) {
ECPublicKeyParameters pub = BCECUtils.createECPublicKeyParameters(publicKey.getX(), publicKey.getY());
if (pucData.length != 32) {
throw new IllegalArgumentException("Hash length must be 32 bytes");
}
ECDomainParameters domainParams = pub.getParameters();
ECPoint G = domainParams.getG();
BigInteger n = domainParams.getN();
ECPoint Q = pub.getQ();
BigInteger e = new BigInteger(1, pucData);
BigInteger r = new BigInteger(1, pucSignature.getR());
BigInteger s = new BigInteger(1, pucSignature.getS());
if (r.compareTo(BigIntegers.ONE) < 0 || r.compareTo(n) >= 0 || s.compareTo(BigIntegers.ONE) < 0 || s.compareTo(n) >= 0) {
return false;
}
BigInteger t = r.add(s).mod(n);
if (t.equals(BigInteger.ZERO)) {
return false;
}
ECPoint point = G.multiply(s).add(Q.multiply(t)).normalize();
BigInteger R = e.add(point.getAffineXCoord().toBigInteger()).mod(n);
return R.equals(r);
}
@Override
public EccCipher externalEncryptECC(String sessionHandle, EccPubKey pubKey, byte[] pucData) {
ECPublicKeyParameters pub = BCECUtils.createECPublicKeyParameters(pubKey.getX(), pubKey.getY());
try {
byte[] encrypt = BCSM2Utils.encrypt(pub, pucData);
return EccCipher.fromBytes(encrypt);
} catch (InvalidCipherTextException e) {
throw new IllegalArgumentException(e);
}
}
@Override
public byte[] externalDecryptECC(String sessionHandle, EccPriKey pucPrivateKeyEcc, EccCipher pucEncData) {
ECPrivateKeyParameters pri = BCECUtils.createECPrivateKeyParameters(pucPrivateKeyEcc.getD());
try {
return BCSM2Utils.decrypt(pri, LangUtils.merge(new byte[]{0x04}, pucEncData.getC1C3C2Bytes()));
} catch (InvalidCipherTextException e) {
throw new IllegalArgumentException(e);
}
}
@Override
public String importKeyWithISKECC(String sessionHandle, int uiIskIndex, EccCipher eccCipher) {
ECPrivateKeyParameters pri = BCECUtils.createECPrivateKeyParameters(getD());
try {
byte[] pucKey = BCSM2Utils.decrypt(pri, LangUtils.merge(new byte[]{0x04}, eccCipher.getC1C3C2Bytes()));
Assert.isTrue(pucKey.length == 16, "密钥长度错误");
String key = UUID.randomUUID().toString();
KEY_HANDLE_CONTEXT.put(key, pucKey);
return key;
} catch (InvalidCipherTextException e) {
throw new IllegalArgumentException(e);
}
}
@Override
public String importKey(String sessionHandle, byte[] pucKey) {
Assert.isTrue(pucKey.length == 16, "密钥长度错误");
String key = UUID.randomUUID().toString();
KEY_HANDLE_CONTEXT.put(key, pucKey);
return key;
}
@Override
public int destroyKey(String sessionHandle, String hKeyHandle) {
KEY_HANDLE_CONTEXT.remove(hKeyHandle);
return 0;
}
@Override
@SneakyThrows
public byte[] symEncrypt(String sessionHandle, String keyHandle, AlgId alg, byte[] pucIv, byte[] pucData) {
byte[] symKey = getSymKey(keyHandle);
switch (alg) {
case SGD_SM4_ECB:
return BCSM4Utils.encrypt_ECB_NoPadding(symKey, pucData);
case SGD_SM4_CBC:
return BCSM4Utils.encrypt_CBC_NoPadding(symKey, pucIv, pucData);
default:
throw new IllegalArgumentException("不支持的算法:" + alg.name());
}
}
@Override
@SneakyThrows
public byte[] symDecrypt(String sessionHandle, String keyHandle, AlgId alg, byte[] pucIv, byte[] pucEncData) {
byte[] symKey = getSymKey(keyHandle);
switch (alg) {
case SGD_SM4_ECB:
return BCSM4Utils.decrypt_ECB_NoPadding(symKey, pucEncData);
case SGD_SM4_CBC:
return BCSM4Utils.decrypt_CBC_NoPadding(symKey, pucIv, pucEncData);
default:
throw new IllegalArgumentException("不支持的算法:" + alg.name());
}
}
protected byte[] getSymKey(String keyHandle) {
byte[] key = KEY_HANDLE_CONTEXT.get(keyHandle);
Assert.notNull(key, "密钥句柄不存在");
return key;
}
@Override
@SneakyThrows
public byte[] calculateMAC(String sessionHandle, String keyHandle, AlgId uiAlg, byte[] pucIv, byte[] pucData) {
byte[] symKey = getSymKey(keyHandle);
return BCSM4Utils.doCBCMac(symKey, pucIv, null, pucData);
}
protected static final Map<String, SM3Digest> HASH_CONTEXT = new ConcurrentHashMap<>();
@Override
public int hashInit(String sessionHandle, AlgId alg, EccPubKey pucPublicKey, byte[] pucID) {
HASH_CONTEXT.put(sessionHandle, new SM3Digest());
return 0;
}
@Override
public int hashUpdate(String sessionHandle, byte[] pucData) {
Optional.ofNullable(HASH_CONTEXT.get(sessionHandle))
.ifPresent(it -> it.update(pucData, 0, pucData.length));
return 0;
}
@Override
public byte[] hashFinish(String sessionHandle) {
SM3Digest digest = HASH_CONTEXT.remove(sessionHandle);
Assert.notNull(digest, "session异常");
byte[] hash = new byte[digest.getDigestSize()];
digest.doFinal(hash, 0);
return hash;
}
}

View File

@ -1,19 +1,25 @@
package com.sunyard.chsm.sdf.adapter;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;
import com.sunyard.chsm.sdf.context.AlgId;
import com.sunyard.chsm.sdf.context.SdrCode;
import com.sunyard.chsm.sdf.lib.SdfLibrary;
import com.sunyard.chsm.sdf.model.DeviceInfo;
import com.sunyard.chsm.sdf.model.EccCipher;
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.model.EccSignature;
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;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@ -31,7 +37,7 @@ public abstract class JnaSdfAdaptor implements SdfApiAdapter {
protected final SdfLibrary sdfLibrary;
protected abstract int getAlgId(String alg);
protected abstract int getAlgId(AlgId alg);
@Override
public String openDevice() {
@ -58,6 +64,7 @@ public abstract class JnaSdfAdaptor implements SdfApiAdapter {
PointerByReference phSessionHandle = new PointerByReference();
sdfLibrary.SDF_OpenSession(device, phSessionHandle);
Assert.notNull(phSessionHandle.getValue(), "SDF_OpenSession failed");
String key = UUID.randomUUID().toString();
SESSION_HANDLE_CONTEXT.put(key, phSessionHandle.getValue());
@ -97,9 +104,9 @@ public abstract class JnaSdfAdaptor implements SdfApiAdapter {
sdfLibrary.SDF_GetDeviceInfo(getSessionHandle(sessionHandle), sdfInfo);
DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.setIssuerName(new String(sdfInfo.IssuerName));
deviceInfo.setDeviceName(new String(sdfInfo.DeviceName));
deviceInfo.setDeviceSerial(new String(sdfInfo.DeviceSerial));
deviceInfo.setIssuerName(new String(LangUtils.removeLastZero(sdfInfo.IssuerName)));
deviceInfo.setDeviceName(new String(LangUtils.removeLastZero(sdfInfo.DeviceName)));
deviceInfo.setDeviceSerial(new String(LangUtils.removeLastZero(sdfInfo.DeviceSerial)));
deviceInfo.setDeviceVersion(sdfInfo.DeviceVersion);
deviceInfo.setStandardVersion(sdfInfo.StandardVersion);
deviceInfo.setAsymAlgAbility(new long[]{sdfInfo.AsymAlgAbility[0], sdfInfo.AsymAlgAbility[1]});
@ -118,28 +125,166 @@ public abstract class JnaSdfAdaptor implements SdfApiAdapter {
}
@Override
public byte[] exportEncPublicKeyECC(String sessionHandle, int uiKeyIndex) {
public EccPubKey exportEncPublicKeyECC(String sessionHandle, int uiKeyIndex) {
byte[] pubKey = new byte[132];
Pointer hSessionHandle = getSessionHandle(sessionHandle);
sdfLibrary.SDF_ExportEncPublicKey_ECC(hSessionHandle, uiKeyIndex, pubKey);
return LangUtils.merge(Arrays.copyOfRange(pubKey, 36, 68), Arrays.copyOfRange(pubKey, 100, 132));
return EccPubKey.fromBytes(pubKey);
}
@Override
public EccKey generateKeyPairECC(String sessionHandle, String alg, int uiKeyBits) {
return null;
public EccKey generateKeyPairECC(String sessionHandle, AlgId alg) {
byte[] sdfPubKey = new byte[132];
byte[] sdfPriKey = new byte[68];
Pointer hSessionHandle = getSessionHandle(sessionHandle);
sdfLibrary.SDF_GenerateKeyPair_ECC(hSessionHandle, getAlgId(alg), 256, sdfPubKey, sdfPriKey);
return new EccKey(EccPubKey.fromBytes(sdfPubKey).getPubKeyBytes(), EccPriKey.fromBytes(sdfPriKey).getD());
}
@Override
public byte[] exchangeDigitEnvelopeBaseOnECC(String sessionHandle, int uiKeyIndex, byte[] pubKey, byte[] pucEncDateIn) {
return new byte[0];
public boolean getPrivateKeyAccessRight(String sessionHandle, int uiKeyIndex, byte[] pucPassword) {
Pointer hSessionHandle = getSessionHandle(sessionHandle);
int res = sdfLibrary.SDF_GetPrivateKeyAccessRight(hSessionHandle, uiKeyIndex, pucPassword, pucPassword.length);
return res == 0;
}
@Override
public byte[] externalEncryptECC(String sessionHandle, byte[] pubKey, byte[] pucData) {
public String importKeyWithISKECC(String sessionHandle, int uiIskIndex, EccCipher eccCipher) {
Pointer hSessionHandle = getSessionHandle(sessionHandle);
PointerByReference phKeyHandle = new PointerByReference();
sdfLibrary.SDF_ImportKeyWithISK_ECC(hSessionHandle, uiIskIndex, eccCipher.toSdfData(), phKeyHandle);
String key = UUID.randomUUID().toString();
KEY_HANDLE_CONTEXT.put(key, phKeyHandle.getValue());
return key;
}
@Override
public String importKey(String sessionHandle, byte[] pucKey) {
Pointer hSessionHandle = getSessionHandle(sessionHandle);
PointerByReference phKeyHandle = new PointerByReference();
sdfLibrary.SDF_ImportKey(hSessionHandle, pucKey, pucKey.length, phKeyHandle);
String key = UUID.randomUUID().toString();
KEY_HANDLE_CONTEXT.put(key, phKeyHandle.getValue());
return key;
}
@Override
public int destroyKey(String sessionHandle, String hKeyHandle) {
Pointer hSessionHandle = getSessionHandle(sessionHandle);
Pointer keyHandle = KEY_HANDLE_CONTEXT.remove(hKeyHandle);
return sdfLibrary.SDF_DestroyKey(hSessionHandle, keyHandle);
}
@Override
public EccCipher exchangeDigitEnvelopeBaseOnECC(String sessionHandle, int uiKeyIndex, EccPubKey pubKey, EccCipher pucEncDateIn) {
Pointer hSessionHandle = getSessionHandle(sessionHandle);
byte[] pucEncDateOut = new byte[164 + pucEncDateIn.getL()];
sdfLibrary.SDF_ExchangeDigitEnvelopeBaseOnECC(hSessionHandle, uiKeyIndex, getAlgId(AlgId.SGD_SM2_1),
pubKey.toSdfData(), pucEncDateIn.toSdfData(), pucEncDateOut);
return EccCipher.fromBytes(pucEncDateOut);
}
@Override
public EccSignature externalSignECC(String sessionHandle, EccPriKey privateKey, byte[] pucData) {
byte[] eccSignature = new byte[128];
int uiDataLength = pucData.length;
Pointer hSessionHandle = getSessionHandle(sessionHandle);
sdfLibrary.SDF_ExternalSign_ECC(hSessionHandle, getAlgId(AlgId.SGD_SM2_1), privateKey.toSdfData(),
pucData, uiDataLength, eccSignature);
return EccSignature.fromBytes(eccSignature);
}
@Override
public boolean externalVerifyECC(String sessionHandle, EccPubKey publicKey, byte[] pucData, EccSignature pucSignature) {
int uiDataLength = pucData.length;
Pointer hSessionHandle = getSessionHandle(sessionHandle);
int result = sdfLibrary.SDF_ExternalVerify_ECC(hSessionHandle, getAlgId(AlgId.SGD_SM2_1), publicKey.toSdfData(),
pucData, uiDataLength, pucSignature.toSdfData());
return Objects.equals(result, SdrCode.SDR_OK.getCode());
}
@Override
public EccCipher externalEncryptECC(String sessionHandle, EccPubKey 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;
sdfLibrary.SDF_ExternalEncrypt_ECC(hSessionHandle, getAlgId(AlgId.SGD_SM2_3), pubKey.toSdfData(), pucData, pucData.length, encData);
return EccCipher.fromBytes(encData);
}
@Override
public byte[] externalDecryptECC(String sessionHandle, EccPriKey priKey, EccCipher pucEncData) {
Pointer hSessionHandle = getSessionHandle(sessionHandle);
byte[] pucData = new byte[pucEncData.getL()];
IntByReference puiLength = new IntByReference();
sdfLibrary.SDF_ExternalDecrypt_ECC(hSessionHandle, getAlgId(AlgId.SGD_SM2_3), priKey.toSdfData(), pucEncData.toSdfData(), pucData, puiLength);
return pucData;
}
@Override
public byte[] symEncrypt(String sessionHandle, String keyHandle, AlgId alg, byte[] pucIv, byte[] pucData) {
Pointer hSessionHandle = getSessionHandle(sessionHandle);
Pointer hKeyHandle = getKeyHandle(keyHandle);
int uiDataLength = pucData.length;
byte[] pucEncData = new byte[uiDataLength];
IntByReference puiLength = new IntByReference();
//加密
sdfLibrary.SDF_Encrypt(hSessionHandle, hKeyHandle, getAlgId(alg),
pucIv, pucData, uiDataLength, pucEncData, puiLength);
return pucEncData;
}
@Override
public byte[] symDecrypt(String sessionHandle, String keyHandle, AlgId alg, byte[] pucIv, byte[] pucEncData) {
Pointer hSessionHandle = getSessionHandle(sessionHandle);
Pointer hKeyHandle = getKeyHandle(keyHandle);
int uiEncDataLength = pucEncData.length;
byte[] pucData = new byte[uiEncDataLength];
//解密
IntByReference puiLength = new IntByReference();
sdfLibrary.SDF_Decrypt(hSessionHandle, hKeyHandle, getAlgId(alg),
pucIv, pucEncData, uiEncDataLength, pucData, puiLength);
return pucData;
}
@Override
public byte[] calculateMAC(String sessionHandle, String keyHandle, AlgId uiAlg, byte[] pucIv, byte[] pucData) {
Pointer hSessionHandle = getSessionHandle(sessionHandle);
Pointer hKeyHandle = getKeyHandle(keyHandle);
int uiMacLength = pucData.length;
byte[] pucMac = new byte[16];
IntByReference puiLength = new IntByReference();
sdfLibrary.SDF_CalculateMAC(hSessionHandle, hKeyHandle, getAlgId(uiAlg), pucIv, pucData, uiMacLength, pucMac, puiLength);
return pucMac;
}
@Override
public int hashInit(String sessionHandle, AlgId alg, EccPubKey pucPublicKey, byte[] pucID) {
Pointer hSessionHandle = getSessionHandle(sessionHandle);
byte[] pubPub = pucPublicKey == null ? null : pucPublicKey.toSdfData();
return sdfLibrary.SDF_HashInit(hSessionHandle, getAlgId(AlgId.SGD_SM3), pubPub, pucID, pucID == null ? 0 : pucID.length);
}
@Override
public int hashUpdate(String sessionHandle, byte[] pucData) {
Pointer hSessionHandle = getSessionHandle(sessionHandle);
return sdfLibrary.SDF_HashUpdate(hSessionHandle, pucData, pucData.length);
}
@Override
public byte[] hashFinish(String sessionHandle) {
Pointer hSessionHandle = getSessionHandle(sessionHandle);
byte[] pucHash = new byte[32];
IntByReference puiLength = new IntByReference();
sdfLibrary.SDF_HashFinal(hSessionHandle, pucHash, puiLength);
return pucHash;
}
}

View File

@ -0,0 +1,11 @@
package com.sunyard.chsm.sdf.adapter;
/**
* @author liulu
* @since 2024/12/12
*/
public interface RpcSdfAdapter extends SdfApiAdapter {
String openDevice(String ip, int port, int connTimeout, int dealTimeout, int ipMode);
}

View File

@ -1,7 +1,12 @@
package com.sunyard.chsm.sdf.adapter;
import com.sunyard.chsm.sdf.context.AlgId;
import com.sunyard.chsm.sdf.model.DeviceInfo;
import com.sunyard.chsm.sdf.model.EccCipher;
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.model.EccSignature;
/**
* @author liulu
@ -48,29 +53,150 @@ public interface SdfApiAdapter {
* @return pucRandom 返回随机数
*/
byte[] generateRandom(String sessionHandle, int uiLength);
/**
* 导出ECC加密公钥
*
* @param uiKeyIndex 密码设备存储的ECC密钥对索引值
* @return pucPublicKeyEcc 返回ECC加密公钥x+y
*/
byte[] exportEncPublicKeyECC(String sessionHandle, int uiKeyIndex);
EccPubKey exportEncPublicKeyECC(String sessionHandle, int uiKeyIndex);
/**
* 产生ECC密钥对并输出
* 产生ECC密钥对并输出 密钥模长 256
*
* @param alg 指定算法标识 SGD_SM2_1
* @param uiKeyBits 指定密钥模长
* @return pucPublicKeyEcc 返回公钥 | pucPrivateKeyEcc 返回私钥
*/
EccKey generateKeyPairECC(String sessionHandle, String alg, int uiKeyBits);
EccKey generateKeyPairECC(String sessionHandle, AlgId alg);
boolean getPrivateKeyAccessRight(String hSessionHandle, int uiKeyIndex, byte[] pucPassword);
byte[] exchangeDigitEnvelopeBaseOnECC(String sessionHandle, int uiKeyIndex, byte[] pubKey, byte[] pucEncDateIn);
/**
* 导入会话密钥并用内部ECC私钥解密
*
* @param uiIskIndex 密码设备内部存储加密私钥的索引值对应于加密时的公钥
* @param eccCipher 缓冲区指针用于存放返回的密钥密文
* @return phKeyHandle 返回密钥句柄
*/
String importKeyWithISKECC(String sessionHandle, int uiIskIndex, EccCipher eccCipher);
/**
* 导入明文会话密钥
*
* @param sessionHandle 与设备建立的会话句柄
* @param pucKey 缓冲区指针用于存放输入的密钥密文
* @return phKeyHandle 返回密钥句柄
*/
String importKey(String sessionHandle, byte[] pucKey);
byte[] externalEncryptECC(String sessionHandle, byte[] pubKey, byte[] pucData);
/**
* 销毁会话密钥
*
* @param hKeyHandle 密钥句柄
* @return 0 成功; 非0 失败,返回错误代码
*/
int destroyKey(String sessionHandle, String hKeyHandle);
EccCipher exchangeDigitEnvelopeBaseOnECC(String sessionHandle, int uiKeyIndex, EccPubKey pubKey, EccCipher pucEncDateIn);
/**
* 外部密钥ECC签名
*
* @param privateKey ECC私钥
* @param pucData 缓冲区指针用于存放外部输入的数据
* @return pucSignature 返回签名值数据
*/
EccSignature externalSignECC(String sessionHandle, EccPriKey privateKey, byte[] pucData);
/**
* 外部密钥ECC验证
*
* @param publicKey ECC公钥
* @param pucData 缓冲区指针用于存放外部输入的数据
* @param pucSignature 缓冲区指针用于存放输入的签名值数据
* @return 0 成功; 非0 失败,返回错误代码
*/
boolean externalVerifyECC(String sessionHandle, EccPubKey publicKey, byte[] pucData, EccSignature pucSignature);
/**
* 外部密钥ECC公钥加密
*
* @param pubKey 外部ECC公钥结构
* @param pucData 缓冲区指针用于存放外部输入的数据
* @return pucEncData 返回数据密文
*/
EccCipher externalEncryptECC(String sessionHandle, EccPubKey pubKey, byte[] pucData);
/**
* 外部密钥ECC私钥解密
*
* @param pucPrivateKeyEcc 外部ECC私钥结构
* @param pucEncData 缓冲区指针用于存放输入的数据密文
* @return pucData 返回数据明文
*/
byte[] externalDecryptECC(String sessionHandle, EccPriKey pucPrivateKeyEcc, EccCipher pucEncData);
/**
* 对称加密
*
* @param sessionHandle 与设备建立的会话句柄
* @param keyHandle 指定的密钥句柄
* @param alg 算法标识指定对称加密算法 SGD_SMS4_ECB
* @param pucIv IV数据
* @param pucData 数据明文
* @return 数据密文
*/
byte[] symEncrypt(String sessionHandle, String keyHandle, AlgId alg, byte[] pucIv, byte[] pucData);
/**
* 对称解密
*
* @param sessionHandle 与设备建立的会话句柄
* @param keyHandle 指定的密钥句柄
* @param alg 算法标识指定对称加密算法 SGD_SMS4_ECB
* @param pucIv IV数据
* @param pucEncData 数据密文
* @return 数据明文
*/
byte[] symDecrypt(String sessionHandle, String keyHandle, AlgId alg, byte[] pucIv, byte[] pucEncData);
/**
* @param sessionHandle 与设备建立的会话句柄
* @param keyHandle 指定的密钥句柄
* @param uiAlg 算法标识指定MAC算法 SGD_SMS4_MAC
* @param pucIv IV数据
* @param pucData 数据明文
* @return MAC值
*/
byte[] calculateMAC(String sessionHandle, String keyHandle, AlgId uiAlg, byte[] pucIv, byte[] pucData);
/**
* 杂凑运算初始化
*
* @param sessionHandle 与设备建立的会话句柄
* @param alg 当前杂凑算法标识 SGD_SM3
* @param pucPublicKey 签名者公钥当uiAlgID为SGD_SM3时有效
* @param pucID 签名者的ID值当uiAlgID为SGD_SM3时有效
* @return 0 成功; 非0 失败,返回错误代码
*/
int hashInit(String sessionHandle, AlgId alg, EccPubKey pucPublicKey, byte[] pucID);
/**
* 多包杂凑运算
*
* @param sessionHandle sessionHandle
* @param pucData 数据明文
* @return 0 成功; 非0 失败,返回错误代码
*/
int hashUpdate(String sessionHandle, byte[] pucData);
/**
* 杂凑运算结束
*
* @param sessionHandle 与设备建立的会话句柄
* @return 杂凑数据
*/
byte[] hashFinish(String sessionHandle);
}

View File

@ -1,35 +1,79 @@
package com.sunyard.chsm.sdf.adapter;
import com.googlecode.jsonrpc4j.JsonRpcHttpClient;
import com.googlecode.jsonrpc4j.ProxyUtil;
import com.sun.jna.Platform;
import com.sunyard.chsm.enums.ManufacturerModelEnum;
import com.sunyard.chsm.sdf.context.DeviceContext;
import com.sunyard.chsm.utils.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Objects;
/**
* @author liulu
* @since 2024/11/5
*/
@Slf4j
public class SdfApiAdapterFactory {
public static SdfApiAdapter newInstance(DeviceContext device) {
public static SdfApiAdapter getBcAdapter() {
return BcSdfApiAdaptor.INSTANCE;
}
Assert.hasText(device.getManufacturerModel(), "设备型号不能为空");
ManufacturerModelEnum model = ManufacturerModelEnum.of(device.getManufacturerModel());
public static SdfApiAdapter newInstance(String model, String ip, Integer port) {
if (Objects.isNull(model) && Objects.equals(BouncyCastleProvider.PROVIDER_NAME, device.getManufacturerModel())) {
Assert.hasText(model, "设备型号不能为空");
ManufacturerModelEnum modelEnum = ManufacturerModelEnum.of(model);
if (Objects.isNull(modelEnum) && Objects.equals(BouncyCastleProvider.PROVIDER_NAME, model)) {
// bc adaptor
return new BcSdfApiAdaptor();
return BcSdfApiAdaptor.INSTANCE;
}
switch (model) {
case enc001:
return new SunyardJnaSdfAdaptor(device.getServiceIp(), device.getServicePort());
default:
throw new UnsupportedOperationException("不支持的设备型号: " + device.getManufacturerModel());
try {
switch (modelEnum) {
case enc001:
return Platform.isMac() ? getProxyRcpAdapter(ip, port) : new SunyardJnaSdfAdaptor(ip, port);
default:
throw new UnsupportedOperationException("不支持的设备型号: " + model);
}
} catch (Throwable ex) {
log.warn("build SdfApiAdapter error", ex);
throw new IllegalArgumentException("build SdfApiAdapter error");
}
}
private static final RpcSdfAdapter rpcSdfAdapter;
static {
try {
JsonRpcHttpClient client = new JsonRpcHttpClient(JsonUtils.objectMapper(),
new URL("http://172.16.18.46:9989/sdf/adapter"), Collections.emptyMap());
rpcSdfAdapter = ProxyUtil.createClientProxy(
ClassUtils.getDefaultClassLoader(), RpcSdfAdapter.class, client);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
private static SdfApiAdapter getProxyRcpAdapter(String ip, Integer port) {
return (SdfApiAdapter) Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(),
new Class[]{SdfApiAdapter.class},
(proxy, method, args) -> {
if (Objects.equals(method.getName(), "openDevice")) {
return rpcSdfAdapter.openDevice(ip, port, 3000, 10000, 0);
}
return method.invoke(rpcSdfAdapter, args);
}
);
}
}

View File

@ -2,10 +2,19 @@ package com.sunyard.chsm.sdf.adapter;
import com.sun.jna.Native;
import com.sun.jna.ptr.PointerByReference;
import com.sunyard.chsm.sdf.context.AlgId;
import com.sunyard.chsm.sdf.context.SdrCode;
import com.sunyard.chsm.sdf.context.SunyardAlgId;
import com.sunyard.chsm.sdf.lib.SdfLibrary;
import com.sunyard.chsm.sdf.lib.SunyardSdfLibrary;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ClassUtils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@ -13,26 +22,52 @@ import java.util.concurrent.ConcurrentHashMap;
* @author liulu
* @since 2024/11/4
*/
@Slf4j
public class SunyardJnaSdfAdaptor extends JnaSdfAdaptor {
private static final Map<String, SunyardSdfLibrary> SDF_LIB_MAP = new ConcurrentHashMap<>();
private final String ip;
private final Integer port;
private final Integer connTimeout;
private final Integer dealTimeout;
public SunyardJnaSdfAdaptor(String ip, int port) {
this(ip, port, "sdf");
this(ip, port, 3000, 10000);
}
public SunyardJnaSdfAdaptor(String ip, int port, String libName) {
this(ip, port, libName, 3000, 3000);
}
public SunyardJnaSdfAdaptor(String ip, int port, int connTimeout, int dealTimeout) {
super((SdfLibrary) Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(),
new Class[]{SdfLibrary.class},
new InvocationHandler() {
private final SunyardSdfLibrary sunyardSdfLibrary;
public SunyardJnaSdfAdaptor(String ip, int port, String libName, int connTimeout, int dealTimeout) {
super(SDF_LIB_MAP.computeIfAbsent(libName, k -> Native.load(libName, SunyardSdfLibrary.class)));
{
sunyardSdfLibrary = SDF_LIB_MAP.computeIfAbsent("sdf", k -> Native.load(k, SunyardSdfLibrary.class));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = method.invoke(sunyardSdfLibrary, args);
if (!(res instanceof Integer)) {
return res;
}
int resInt = (Integer) res;
if (Objects.equals("SDF_ExternalVerify_ECC", method.getName())) {
log.info("SDF_ExternalVerify_ECC: {}, 返回值: {}", method.getName(), Integer.toHexString(resInt));
return resInt;
}
if (method.getName().startsWith("SDF_")) {
if (resInt != SdrCode.SDR_OK.getCode()) {
log.warn("调用异常: {}, 返回值: {} -{}", method.getName(), Integer.toHexString(resInt), resInt);
SdrCode sdrCode = SdrCode.of((Integer) res);
throw new IllegalArgumentException(sdrCode != null ? sdrCode.getMsg() : "调用sdf接口异常: " + Integer.toHexString(resInt));
}
}
return res;
}
}
));
this.ip = ip;
this.port = port;
this.connTimeout = connTimeout;
@ -41,7 +76,7 @@ public class SunyardJnaSdfAdaptor extends JnaSdfAdaptor {
@Override
public String openDevice() {
SunyardSdfLibrary sunyardSdfLibrary = (SunyardSdfLibrary) sdfLibrary;
SunyardSdfLibrary sunyardSdfLibrary = SDF_LIB_MAP.computeIfAbsent("sdf", k -> Native.load(k, SunyardSdfLibrary.class));
PointerByReference phDeviceHandle = new PointerByReference();
sunyardSdfLibrary.SDF_OpenDevice(phDeviceHandle, safeStringBytes(ip), port, connTimeout, dealTimeout, 0);
String key = UUID.randomUUID().toString();
@ -50,10 +85,11 @@ public class SunyardJnaSdfAdaptor extends JnaSdfAdaptor {
}
@Override
protected int getAlgId(String alg) {
return SunyardAlgId.valueOf(alg).getValue();
protected int getAlgId(AlgId alg) {
return SunyardAlgId.valueOf(alg.name()).getValue();
}
public static byte[] safeStringBytes(String str) {
if (null == str || str.isEmpty()) {
return new byte[]{0x30};

View File

@ -4,15 +4,15 @@ package com.sunyard.chsm.sdf.context;
* @author liulu
* @since 2024/11/5
*/
public interface AlgId {
public enum AlgId {
SGD_SM3,
String SGD_SM3 = "SGD_SM3";
SGD_SM4_CBC,
SGD_SM4_ECB,
SGD_SM4_MAC,
String SGD_SM4_CBC = "SGD_SMS4_CBC";
String SGD_SM4_ECB = "SGD_SMS4_ECB";
String SGD_SM4_MAC = "SGD_SMS4_MAC";
String SGD_SM2_1 = "SGD_SM2_1"; // 签名验签
String SGD_SM2_2 = "SGD_SM2_2";
String SGD_SM2_3 = "SGD_SM2_3"; // 加密解密
SGD_SM2_1,// 签名验签
SGD_SM2_2,
SGD_SM2_3,// 加密解密
;
}

View File

@ -1,22 +0,0 @@
package com.sunyard.chsm.sdf.context;
import lombok.Data;
/**
* @author liulu
* @since 2024/11/5
*/
@Data
public class DeviceContext {
private String serviceIp;
private Integer servicePort;
private String manufacturer;
private String manufacturerModel;
private String accessCredentials;
private String jnaLibName;
private Integer weight;
}

View File

@ -0,0 +1,114 @@
package com.sunyard.chsm.sdf.context;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
import java.util.Objects;
/**
* @author liulu
* @since 2024/12/7
*/
@Getter
@RequiredArgsConstructor
public enum SdrCode {
SDR_OK(0x0, "操作成功"),
SDR_BASE(0x01000000, "错误码基础值"),
SDR_UNKNOWER(SDR_BASE.code + 0x00000001, "未知错误"),
SDR_NOTSUPPORT(SDR_BASE.code + 0x00000002, "不支持的接口调用"),
SDR_COMMFAIL(SDR_BASE.code + 0x00000003, "与设备通信失败"),
SDR_HARDFAIL(SDR_BASE.code + 0x00000004, "运算模块无响应"),
SDR_OPENDEVICE(SDR_BASE.code + 0x00000005, "打开设备失败"),
SDR_OPENSESSION(SDR_BASE.code + 0x00000006, "创建会话失败"),
SDR_PARDENY(SDR_BASE.code + 0x00000007, "无私钥使用权限"),
SDR_KEYNOTEXIST(SDR_BASE.code + 0x00000008, "不存在的密钥调用"),
SDR_ALGNOTSUPPORT(SDR_BASE.code + 0x00000009, "不支持的算法调用"),
SDR_ALGMODNOTSUPPORT(SDR_BASE.code + 0x0000000A, "不支持的算法模式调用"),
SDR_PKOPERR(SDR_BASE.code + 0x0000000B, "公钥运算失败"),
SDR_SKOPERR(SDR_BASE.code + 0x0000000C, "私钥运算失败"),
SDR_SIGNERR(SDR_BASE.code + 0x0000000D, "签名运算失败"),
SDR_VERIFYERR(SDR_BASE.code + 0x0000000E, "验证签名失败"),
SDR_SYMOPERR(SDR_BASE.code + 0x0000000F, "对称算法运算失败"),
SDR_STEPERR(SDR_BASE.code + 0x00000010, "多步运算步骤错误"),
SDR_FILESIZEERR(SDR_BASE.code + 0x00000011, "文件长度超出限制"),
SDR_FILENOEXIST(SDR_BASE.code + 0x00000012, "指定的文件不存在"),
SDR_FILEOFSERR(SDR_BASE.code + 0x00000013, "文件起始位置错误"),
SDR_KEYTYPEERR(SDR_BASE.code + 0x00000014, "密钥类型错误"),
SDR_KEYERR(SDR_BASE.code + 0x00000015, "密钥错误"),
SDR_ENCDATAERR(SDR_BASE.code + 0x00000016, "ECC加密数据错误"),
SDR_RANDERR(SDR_BASE.code + 0x00000017, "随机数产生失败"),
SDR_PRKERR(SDR_BASE.code + 0x00000018, "私钥使用权限获取失败"),
SDR_MACERR(SDR_BASE.code + 0x00000019, "MAC运算失败"),
SDR_FILEEXISTS(SDR_BASE.code + 0x0000001A, "指定文件已存在"),
SDR_FILEWERR(SDR_BASE.code + 0x0000001B, "文件写入失败"),
SDR_NOBUFFER(SDR_BASE.code + 0x0000001C, "存储空间不足"),
SDR_INARGERR(SDR_BASE.code + 0x0000001D, "输入参数错误"),
SDR_OUTARGERR(SDR_BASE.code + 0x0000001E, "输出参数错误"),
SDR_FILERDERR(SDR_BASE.code + 0x0000001F, "文件读取失败"),
SDR_CONSULTERR(SDR_BASE.code + 0x00000020, "密钥协商错误"),
SDR_LEVELERR(SDR_BASE.code + 0x00000021, "权限不足"),
SDR_INDEXERR(SDR_BASE.code + 0x00000022, "索引错误"),
SDR_KEYLENERR(SDR_BASE.code + 0x00000023, "密钥长度错误"),
SDR_DATALENERR(SDR_BASE.code + 0x00000024, "数据长度错误"),
SDR_RIGHTLENERR(SDR_BASE.code + 0x00000025, "私钥授权码长度错误"),
SDR_RIGHTERR(SDR_BASE.code + 0x00000026, "私钥授权码错误"),
SDR_FILEEMPTYERR(SDR_BASE.code + 0x00000027, "文件为空"),
SDR_ERRSTATE(SDR_BASE.code + 0x00000028, "处于错误状态"),
SDR_INITSTATE(SDR_BASE.code + 0x00000029, "处于初始状态"),
SDR_RPRKERR(SDR_BASE.code + 0x0000002A, "私钥使用权限释放失败"),
SDR_SIGNPRKERR(SDR_BASE.code + 0x0000002B, "签名公钥导出失败"),
SDR_ENCPRKERR(SDR_BASE.code + 0x0000002C, "加密公钥导出失败"),
SDR_KPERR(SDR_BASE.code + 0x0000002D, "密钥对生成失败"),
SDR_GENERATEKEYERR(SDR_BASE.code + 0x0000002E, "会话密钥生成失败"),
SDR_IMKEYERR(SDR_BASE.code + 0x0000002F, "导入密钥失败"),
SDR_INTOEXERR(SDR_BASE.code + 0x00000030, "数字信封转换失败"),
/* API新增错误码 */
SRD_DEV_FILE_CONFIG_ERR(SDR_BASE.code + 0x00000040, "配置文件信息错误(请检查配置文件的信息是否有误)"),
SDR_DEVICEHANDLE_INVAILD(SDR_BASE.code + 0x00000041, "设备句柄错误(请检查句柄是否初始化或已经被释放)"),
SDR_SESSIONHANLE_INVAILD(SDR_BASE.code + 0x00000042, "会话句柄错误(请检查句柄是否初始化或已经被释放)"),
SDR_KEYHANLE_INVAILD(SDR_BASE.code + 0x00000043, "密钥句柄错误(请检查句柄是否初始化或已经被释放)"),
SDR_AGREEMENTHANLE_INVAILD(SDR_BASE.code + 0x00000044, "协商句柄错误(请检查句柄是否初始化或已经被释放)"),
SDR_UNKNOWN_HANDLE(SDR_BASE.code + 0x00000045, "未知的句柄"),
SDR_NO_AVAILABL_DEVICE(SDR_BASE.code + 0x00000046, "此设备无法连接, 请检查IP和端口是否正确"),
SYD_KEYSCHEMEERR(0x26, "错误密钥方案"),
SYD_PRIKEYPWDERR(0x02, "私钥授权码验证错误"),
SYD_HASHERR(0x29, "哈希失败(请联系管理员)"),
SYD_CARDERR(0x47, "密码卡计算错误(请联系管理员)"),
SYD_PACKAGELENERR(0x80, "数据包长度错误(请联系管理员)"),
SYD_GETKEYERR(0x21, "获取密钥失败,检查密钥状态"),
SYD_LMKCALCERR(0x13, "主密钥计算失败(请联系管理员)"),
SYD_PKGPARAMERR(0x15, "输入参数错误(请联系管理员)"),
SYD_AGREEMENTERR(0x06, "ECC密钥协商失败请联系管理员"),
SYD_LMKDECPRIKEYERR(0x18, "主密钥解密私钥错误(请联系管理员)"),
SYD_SYMENCDECERR(0x14, "对称加解密错误(请联系管理员)"),
SYD_FILEEXISTS(0x71, "文件已存在"),
SYD_FILENOEXIST(0x72, "文件不存在"),
SYD_FILECREATEERR(0x73, "文件创建失败(请联系管理员)"),
SYD_FILERDERR(0x74, "文件读取失败"),
SYD_FILEWERR(0x75, "文件写入失败"),
SYD_FILEDELEERR(0x76, "文件删除失败(请联系管理员)"),
SYD_FILESIZEERR(0x77, "超出文件大小限制"),
SYD_FILEOPENEERR(0x78, "文件打开失败(请联系管理员)"),
SYD_FILEEMPTYERR(0x79, "文件为空"),
SYD_VERIFYERR(0x8002, "验签错误"),
;
private final int code;
private final String msg;
public static SdrCode of(int code) {
return Arrays.stream(SdrCode.values())
.filter(it -> Objects.equals(it.code, code))
.findFirst()
.orElse(null);
}
}

View File

@ -179,6 +179,27 @@ public interface SdfLibrary extends Library {
*/
int SDF_ExchangeDigitEnvelopeBaseOnECC(Pointer hSessionHandle, int uiKeyIndex, int uiAlgID, byte[] pucPublicKey, byte[] pucEncDateIn, byte[] pucEncDateOut);
/**
*
* @param hSessionHandle 与设备建立的会话句柄
* @param pucKey 缓冲区指针用于存放输入的密钥密文
* @param puiKeyLength 返回的密钥明文长度
* @param phKeyHandle 返回的密钥句柄
* @return 0 成功; 非0 失败,返回错误代码
*/
int SDF_ImportKey(Pointer hSessionHandle, byte[] pucKey, int puiKeyLength, PointerByReference phKeyHandle);
/**
* 获取私钥使用权限
*
* @param hSessionHandle 与设备建立的会话句柄
* @param uiKeyIndex 密码设备存储私钥的索引值
* @param pucPassword 使用私钥权限的标识码
* @param uiPwdLength 私钥访问控制码长度不少于8字节
* @return 0 成功; 非0 失败,返回错误代码
*/
int SDF_GetPrivateKeyAccessRight(Pointer hSessionHandle, int uiKeyIndex, byte[] pucPassword, int uiPwdLength);
/**
* 导入会话密钥并用内部ECC私钥解密
*
@ -200,7 +221,7 @@ public interface SdfLibrary extends Library {
* @param phKeyhandle
* @return
*/
int SDF_DestroyKey(Pointer phSessionHandle, PointerByReference phKeyhandle);
int SDF_DestroyKey(Pointer phSessionHandle, Pointer phKeyhandle);
/**
@ -262,7 +283,7 @@ public interface SdfLibrary extends Library {
* @param pucIDlength 签名者ID的长度
* @return int 响应码
*/
int SDF_HashInit(Pointer phSessionHandle, int uiAlgID, byte[] pucPublicKey, String pucID, int pucIDlength);
int SDF_HashInit(Pointer phSessionHandle, int uiAlgID, byte[] pucPublicKey, byte[] pucID, int pucIDlength);
/**
* 多包杂凑运算

View File

@ -8,7 +8,6 @@ import com.sun.jna.ptr.PointerByReference;
*/
public interface SunyardSdfLibrary extends SdfLibrary {
/**
* 打开设备
* @param phDeviceHandle 设备句柄

View File

@ -1,8 +1,14 @@
package com.sunyard.chsm.sdf.model;
import com.sunyard.chsm.utils.CodecUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.util.Assert;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
/**
* @author liulu
@ -25,6 +31,9 @@ public class EccCipher {
// 密文数据
private byte[] C;
public String getC1C3C2Hex() {
return CodecUtils.encodeHex(getC1C3C2Bytes());
}
public byte[] getC1C3C2Bytes() {
int xLen = x.length;
@ -41,4 +50,44 @@ public class EccCipher {
return rawData;
}
public byte[] toSdfData() {
byte[] d = new byte[164 + L];
byte[] l = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(L).array();
System.arraycopy(x, 0, d, 32, 32);
System.arraycopy(y, 0, d, 96, 32);
System.arraycopy(M, 0, d, 128, 32);
System.arraycopy(l, 0, d, 160, 4);
System.arraycopy(C, 0, d, 164, L);
return d;
}
public static EccCipher fromHex(String hex) {
return fromBytes(CodecUtils.decodeHex(hex));
}
public static EccCipher fromBytes(byte[] cipher) {
Assert.notNull(cipher, "Ecc密文数据不能为null");
if (cipher[0] == 0x04) {
cipher = Arrays.copyOfRange(cipher, 1, cipher.length);
}
Assert.isTrue(cipher.length > 96, "Ecc加密数据格式错误");
if (cipher.length > 164
&& Arrays.equals(Arrays.copyOf(cipher, 32), new byte[32])
&& Arrays.equals(Arrays.copyOfRange(cipher, 64, 96), new byte[32])) {
int L = cipher.length - 164;
byte[] x = Arrays.copyOfRange(cipher, 32, 64);
byte[] y = Arrays.copyOfRange(cipher, 96, 128);
byte[] m = Arrays.copyOfRange(cipher, 128, 160);
byte[] c = Arrays.copyOfRange(cipher, 164, cipher.length);
return new EccCipher(x, y, m, L, c);
}
int L = cipher.length - 96;
byte[] x = Arrays.copyOfRange(cipher, 0, 32);
byte[] y = Arrays.copyOfRange(cipher, 32, 64);
byte[] m = Arrays.copyOfRange(cipher, 64, 96);
byte[] c = Arrays.copyOfRange(cipher, 96, cipher.length);
return new EccCipher(x, y, m, L, c);
}
}

View File

@ -3,6 +3,11 @@ package com.sunyard.chsm.sdf.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.util.Assert;
import java.util.Arrays;
import java.util.Objects;
/**
* @author liulu
@ -18,5 +23,24 @@ public class EccPriKey {
//
private byte[] D;
public byte[] toSdfData() {
byte[] sdf = new byte[68];
System.arraycopy(Hex.decode("00010000"), 0, sdf, 0, 4);
System.arraycopy(D, 0, sdf, 36, 32);
return sdf;
}
public static EccPriKey fromBytes(byte[] priKey) {
Assert.notNull(priKey, "私钥数据不能为null");
if (Objects.equals(priKey.length, 68)) {
// 00010000
priKey = Arrays.copyOfRange(priKey, 4, priKey.length);
}
Assert.isTrue(Objects.equals(priKey.length, 32) || Objects.equals(priKey.length, 64), "Ecc私钥数据格式错误");
if (Objects.equals(priKey.length, 32)) {
return new EccPriKey(256, priKey);
}
return new EccPriKey(256, Arrays.copyOfRange(priKey, 32, 64));
}
}

View File

@ -3,6 +3,11 @@ package com.sunyard.chsm.sdf.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.util.Assert;
import java.util.Arrays;
import java.util.Objects;
/**
* @author liulu
@ -20,6 +25,9 @@ public class EccPubKey {
// y
private byte[] y;
public String getPubKeyHex() {
return Hex.toHexString(getPubKeyBytes());
}
public byte[] getPubKeyBytes() {
byte[] rawKey = new byte[x.length + y.length];
@ -28,4 +36,38 @@ public class EccPubKey {
return rawKey;
}
public byte[] toSdfData() {
byte[] sdf = new byte[132];
System.arraycopy(Hex.decode("00010000"), 0, sdf, 0, 4);
System.arraycopy(x, 0, sdf, 36, 32);
System.arraycopy(y, 0, sdf, 100, 32);
return sdf;
}
public static EccPubKey fromHex(String pubKeyHex) {
return fromBytes(Hex.decode(pubKeyHex));
}
public static EccPubKey fromBytes(byte[] pubKey) {
Assert.notNull(pubKey, "公钥数据不能为null");
if (Objects.equals(pubKey.length, 65)) {
Assert.isTrue(Objects.equals(pubKey[0], 0x04), "Ecc公钥数据格式错误,必须04开头");
pubKey = Arrays.copyOfRange(pubKey, 1, pubKey.length);
} else if (Objects.equals(pubKey.length, 132)) {
// 00010000
pubKey = Arrays.copyOfRange(pubKey, 4, pubKey.length);
}
Assert.isTrue(Objects.equals(pubKey.length, 64) || Objects.equals(pubKey.length, 128), "Ecc公钥数据格式错误");
byte[] x = new byte[32];
byte[] y = new byte[32];
if (Objects.equals(pubKey.length, 64)) {
System.arraycopy(pubKey, 0, x, 0, 32);
System.arraycopy(pubKey, 32, y, 0, 32);
} else if (Objects.equals(pubKey.length, 128)) {
System.arraycopy(pubKey, 32, x, 0, 32);
System.arraycopy(pubKey, 96, y, 0, 32);
}
return new EccPubKey(256, x, y);
}
}

View File

@ -1,9 +1,14 @@
package com.sunyard.chsm.sdf.model;
import com.sunyard.chsm.utils.CodecUtils;
import com.sunyard.chsm.utils.gm.BCSM2Utils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.IOException;
import java.util.Arrays;
/**
* @author liulu
* @version V1.0
@ -20,6 +25,19 @@ public class EccSignature {
// 签名的 s 部分
private byte[] s;
public String getRawSignHex() {
return CodecUtils.encodeHex(getRawSignBytes());
}
public byte[] getDerSignBytes() {
try {
return BCSM2Utils.encodeSM2SignToDER(getRawSignBytes());
} catch (IOException e) {
throw new IllegalArgumentException("ECC签名数据格式错误");
}
}
public byte[] getRawSignBytes() {
byte[] signData = new byte[r.length + s.length];
System.arraycopy(r, 0, signData, 0, r.length);
@ -27,5 +45,32 @@ public class EccSignature {
return signData;
}
public byte[] toSdfData() {
byte[] res = new byte[128];
System.arraycopy(r, 0, res, 32, 32);
System.arraycopy(s, 0, res, 96, 32);
return res;
}
public static EccSignature fromBytes(byte[] sign) {
if (sign == null || sign.length == 0) {
throw new IllegalArgumentException("ECC签名数据格式错误");
}
if (sign.length >= 70 && sign.length <= 75) {
sign = BCSM2Utils.decodeDERSM2Sign(sign);
}
if (sign.length == 64) {
byte[] r = Arrays.copyOfRange(sign, 0, 32);
byte[] s = Arrays.copyOfRange(sign, 32, 64);
return new EccSignature(r, s);
} else if (sign.length == 128) {
byte[] r = Arrays.copyOfRange(sign, 32, 64);
byte[] s = Arrays.copyOfRange(sign, 96, 128);
return new EccSignature(r, s);
} else {
throw new IllegalArgumentException("ECC签名数据格式错误");
}
}
}

View File

@ -10,6 +10,20 @@ import java.util.Arrays;
*/
public abstract class LangUtils {
public static byte[] removeLastZero(byte[] bytes) {
if (bytes == null) {
return bytes;
}
int len = bytes.length;
for (int i = len - 1; i >= 0; i--) {
if (bytes[i] != 0) {
return Arrays.copyOfRange(bytes, 0, i + 1);
}
}
return bytes;
}
public static byte[] merge(byte[]... bytes) {
int newLen = Arrays.stream(bytes).mapToInt(it -> it.length).sum();
byte[] res = new byte[newLen];

View File

@ -1,10 +1,38 @@
package com.sunyard.chsm.sdf.util;
import com.sunyard.chsm.enums.Padding;
/**
* @author liulu
*/
public abstract class PaddingUtil {
public static byte[] padding(Padding padding, byte[] data) {
switch (padding) {
case NOPadding:
return data;
case PCKS5Padding:
return PKCS5Padding(data);
case PCKS7Padding:
return PKCS7Padding(data);
}
return null;
}
public static byte[] unpadding(Padding padding, byte[] data) {
switch (padding) {
case NOPadding:
return data;
case PCKS5Padding:
return PKCS5Unpadding(data);
case PCKS7Padding:
return PKCS7Unpadding(data);
}
return null;
}
public static byte[] PKCS7Padding(byte[] content, int blockSize) {
if (content == null || blockSize < 8)
throw new IllegalStateException("parameter error");
@ -22,7 +50,7 @@ public abstract class PaddingUtil {
return padded;
}
public static byte[] PKCS7Padding(byte[] content) {
public static byte[] PKCS7Padding(byte[] content) {
try {
return PKCS7Padding(content, 16);
} catch (Exception e) {
@ -30,14 +58,13 @@ public abstract class PaddingUtil {
}
}
public static byte[] PKCS5Padding(byte[] content) throws Exception {
public static byte[] PKCS5Padding(byte[] content) {
return PKCS7Padding(content, 8);
}
public static byte[] PKCS7Unpadding(byte[] content, int blockSize)
throws Exception {
public static byte[] PKCS7Unpadding(byte[] content, int blockSize) {
if (blockSize < 8 || content == null || content.length % blockSize != 0)
throw new Exception("parameter error");
throw new IllegalStateException("parameter error");
return PKCS7Unpadding(content);
}
@ -57,9 +84,8 @@ public abstract class PaddingUtil {
return unpadded;
}
public static byte[] PKCS5Unpadding(byte[] content) throws Exception {
public static byte[] PKCS5Unpadding(byte[] content) {
return PKCS7Unpadding(content, 8);
}
}

View File

@ -14,7 +14,9 @@ public interface KeyInfoService {
Page<KeyInfoDTO.KeyView> selectPageList(KeyInfoDTO.Query query);
Long save(KeyInfoDTO.KeySave save);
KeyInfoDTO.KeyView selectById(Long id);
List<Long> save(KeyInfoDTO.KeySave save);
void update(KeyInfoDTO.KeyUpdate update);

View File

@ -0,0 +1,405 @@
package com.sunyard.chsm.service;
import com.sunyard.chsm.constant.ParamConfKeyConstant;
import com.sunyard.chsm.enums.DeviceTmkStatus;
import com.sunyard.chsm.mapper.ParamConfMapper;
import com.sunyard.chsm.mapper.SpDeviceMapper;
import com.sunyard.chsm.model.dto.DeviceCheckRes;
import com.sunyard.chsm.model.dto.TmkStatus;
import com.sunyard.chsm.model.entity.Device;
import com.sunyard.chsm.model.entity.ParamConf;
import com.sunyard.chsm.sdf.adapter.SdfApiAdapter;
import com.sunyard.chsm.sdf.adapter.SdfApiAdapterFactory;
import com.sunyard.chsm.sdf.context.AlgId;
import com.sunyard.chsm.sdf.model.DeviceInfo;
import com.sunyard.chsm.sdf.model.EccCipher;
import com.sunyard.chsm.sdf.model.EccPubKey;
import com.sunyard.chsm.sdf.util.LangUtils;
import com.sunyard.chsm.utils.CodecUtils;
import com.sunyard.chsm.utils.gm.BCECUtils;
import com.sunyard.chsm.utils.gm.BCSM2Utils;
import com.sunyard.chsm.utils.gm.BCSM3Utils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
/**
* @author liulu
* @since 2024/12/10
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class TmkService {
private final SpDeviceMapper spDeviceMapper;
private final ParamConfMapper paramConfMapper;
@Transactional
public void initTmk() {
boolean tmkInit = isTmkInit();
Assert.isTrue(!tmkInit, "主密钥已经初始化");
Device device = getOneByStatus(DeviceTmkStatus.available);
Assert.notNull(device, "没有可以用于初始化主密钥的设备");
SdfApiAdapter sdfApi = SdfApiAdapterFactory.newInstance(device.getManufacturerModel(), device.getServiceIp(), device.getServicePort());
String hd = sdfApi.openDevice();
String hs = sdfApi.openSession(hd);
byte[] rk = sdfApi.generateRandom(hs, 16);
EccPubKey pubKey = sdfApi.exportEncPublicKeyECC(hs, device.getEncKeyIdx());
EccCipher cipher = sdfApi.externalEncryptECC(hs, pubKey, rk);
sdfApi.getPrivateKeyAccessRight(hs, device.getEncKeyIdx(), device.getAccessCredentials().getBytes());
String hk = sdfApi.importKeyWithISKECC(hs, device.getEncKeyIdx(), cipher);
byte[] encrk = sdfApi.symEncrypt(hs, hk, AlgId.SGD_SM4_ECB, new byte[0], rk);
byte[] prk = sdfApi.symDecrypt(hs, hk, AlgId.SGD_SM4_ECB, new byte[0], encrk);
Assert.isTrue(Arrays.equals(rk, prk), "密码机加解密异常");
byte[] hash = BCSM3Utils.hash(rk);
sdfApi.destroyKey(hs, hk);
sdfApi.closeSession(hs);
sdfApi.closeDevice(hd);
if (Objects.equals(device.getManufacturerModel(), BouncyCastleProvider.PROVIDER_NAME)) {
updateSoftDeviceEncTmk(cipher.getC1C3C2Bytes());
} else {
Device up = new Device();
up.setId(device.getId());
up.setEncTmk(cipher.getC1C3C2Hex());
up.setTmkStatus(DeviceTmkStatus.finished.name());
spDeviceMapper.updateById(up);
}
updateTmkInit(true, hash);
}
public TmkStatus getTMKStatus() {
TmkStatus status = new TmkStatus();
boolean init = isTmkInit();
if (init) {
status.setHasDevice(true);
status.setTmkInit(true);
ParamConf paramConf = paramConfMapper.selectByKey(ParamConfKeyConstant.TMK_CHECK_VALUE);
if (paramConf != null) {
status.setCheckValue(paramConf.getValue());
}
return status;
}
status.setTmkInit(false);
Device device = getOneByStatus(DeviceTmkStatus.available);
status.setHasDevice(device != null);
return status;
}
public String backup(String pubKey) {
boolean tmkInit = isTmkInit();
Assert.isTrue(tmkInit, "主密钥未初始化");
Device device = getOneByStatus(DeviceTmkStatus.finished);
Assert.notNull(device, "没有可以用于备份主密钥的设备");
byte[] xy;
try {
String pubBase6 = pubKey.replace("-----BEGIN ECDSA PUBLIC KEY-----", "").replace("-----END ECDSA PUBLIC KEY-----", "")
.replace("\n", "");
byte[] pubBytes = CodecUtils.decodeBase64(pubBase6);
SubjectPublicKeyInfo keyInfo = SubjectPublicKeyInfo.getInstance(pubBytes);
BCECPublicKey key = BCECUtils.createPublicKeyFromSubjectPublicKeyInfo(keyInfo);
xy = LangUtils.merge(key.getQ().getXCoord().getEncoded(), key.getQ().getYCoord().getEncoded());
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
SdfApiAdapter sdfApi = SdfApiAdapterFactory.newInstance(device.getManufacturerModel(), device.getServiceIp(), device.getServicePort());
String hd = sdfApi.openDevice();
String hs = sdfApi.openSession(hd);
sdfApi.getPrivateKeyAccessRight(hs, device.getEncKeyIdx(), device.getAccessCredentials().getBytes());
EccCipher cipher = sdfApi.exchangeDigitEnvelopeBaseOnECC(hs, device.getEncKeyIdx(), EccPubKey.fromBytes(xy), EccCipher.fromHex(device.getEncTmk()));
try {
byte[] der = BCSM2Utils.encodeSM2CipherToDER(LangUtils.merge(new byte[]{0x04}, cipher.getC1C3C2Bytes()));
return CodecUtils.encodeBase64(der);
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
public Pair<Long, String> getDevicePubKey() {
Device device = getOneByStatus(DeviceTmkStatus.available);
Assert.notNull(device, "没有可以用于导入主密钥的设备");
SdfApiAdapter sdfApi = SdfApiAdapterFactory.newInstance(device.getManufacturerModel(), device.getServiceIp(), device.getServicePort());
String hd = sdfApi.openDevice();
String hs = sdfApi.openSession(hd);
EccPubKey pubKey = sdfApi.exportEncPublicKeyECC(hs, device.getEncKeyIdx());
BCECPublicKey publicKey = BCECUtils.createPublicKey(pubKey.getPubKeyHex());
return Pair.of(device.getId(), CodecUtils.encodeBase64(publicKey.getEncoded()));
}
@Transactional
public void importTmk(Long deviceId, String encTmk) {
boolean tmkInit = isTmkInit();
Assert.isTrue(!tmkInit, "主密钥已经初始化");
Device device = getOneByStatus(DeviceTmkStatus.available);
Assert.notNull(device, "没有可以用于导入主密钥的设备");
SdfApiAdapter sdfApi = SdfApiAdapterFactory.newInstance(device.getManufacturerModel(), device.getServiceIp(), device.getServicePort());
String hd = sdfApi.openDevice();
String hs = sdfApi.openSession(hd);
byte[] rk = CodecUtils.decodeBase64(encTmk);
EccPubKey pubKey = sdfApi.exportEncPublicKeyECC(hs, device.getEncKeyIdx());
EccCipher cipher = sdfApi.externalEncryptECC(hs, pubKey, rk);
sdfApi.getPrivateKeyAccessRight(hs, device.getEncKeyIdx(), device.getAccessCredentials().getBytes());
String hk = sdfApi.importKeyWithISKECC(hs, device.getEncKeyIdx(), cipher);
byte[] encrk = sdfApi.symEncrypt(hs, hk, AlgId.SGD_SM4_ECB, new byte[0], rk);
byte[] prk = sdfApi.symDecrypt(hs, hk, AlgId.SGD_SM4_ECB, new byte[0], encrk);
Assert.isTrue(Arrays.equals(rk, prk), "密码机加解密异常");
byte[] hash = BCSM3Utils.hash(rk);
sdfApi.destroyKey(hs, hk);
sdfApi.closeSession(hs);
sdfApi.closeDevice(hd);
if (Objects.equals(device.getManufacturerModel(), BouncyCastleProvider.PROVIDER_NAME)) {
updateSoftDeviceEncTmk(cipher.getC1C3C2Bytes());
} else {
Device up = new Device();
up.setId(device.getId());
up.setEncTmk(cipher.getC1C3C2Hex());
up.setTmkStatus(DeviceTmkStatus.finished.name());
spDeviceMapper.updateById(up);
}
updateTmkInit(true, hash);
}
public DeviceCheckRes checkDevice(Device check) {
log.debug("==========> begin check device : {}", check);
DeviceCheckRes res = new DeviceCheckRes();
SdfApiAdapter sdfApi = SdfApiAdapterFactory.newInstance(check.getManufacturerModel(), check.getServiceIp(), check.getServicePort());
String hd = sdfApi.openDevice();
String hs = null;
try {
hs = sdfApi.openSession(hd);
DeviceInfo info = sdfApi.getDeviceInfo(hs);
log.debug("get DeviceInfo: {}", info);
res.setDeviceSerial(info.getDeviceSerial());
res.setStatus(DeviceTmkStatus.key_error);
} catch (Exception e) {
log.warn("check device connect error: {}:{}", check.getServiceIp(), check.getServicePort(), e);
res.setHasError(true);
res.setStatus(DeviceTmkStatus.device_error);
res.setMessage(e.getMessage());
}
if (res.isHasError()) {
Optional.ofNullable(hs).ifPresent(sdfApi::closeSession);
Optional.ofNullable(hd).ifPresent(sdfApi::closeDevice);
log.debug("==========> end check device : {}", res);
return res;
}
try {
EccPubKey pubKey = sdfApi.exportEncPublicKeyECC(hs, check.getEncKeyIdx());
if (Arrays.equals(pubKey.getPubKeyBytes(), new byte[64])) {
throw new IllegalArgumentException("加密密钥索引不正确");
}
res.setPubKey(pubKey.getPubKeyHex());
log.debug("export enc pubKey at index: {}, {}", check.getEncKeyIdx(), res.getPubKey());
sdfApi.getPrivateKeyAccessRight(hs, check.getEncKeyIdx(), check.getAccessCredentials().getBytes());
if (isTmkInit()) {
log.debug("tmk is init...");
EccCipher cipher;
if (Objects.equals(check.getDeviceSerial(), res.getDeviceSerial())
&& Objects.equals(check.getPubKey(), res.getPubKey())
&& StringUtils.hasText(check.getEncTmk())) {
cipher = EccCipher.fromHex(check.getEncTmk());
log.debug("device serial, pubKey not changed, use origin device enc tmk");
} else {
if (DeviceTmkStatus.finished.name().equals(check.getTmkStatus())) {
res.setStatus(DeviceTmkStatus.available);
return res;
}
log.debug("device serial, pubKey is changed, or no tmk in origin device");
Device device = getOneByStatus(DeviceTmkStatus.finished);
Assert.notNull(device, "系统主密钥设备异常,请联系管理员排查");
log.debug("get device from finished: {}", device);
SdfApiAdapter tmkAdapter = SdfApiAdapterFactory.newInstance(device.getManufacturerModel(), device.getServiceIp(), device.getServicePort());
String tmkHd = tmkAdapter.openDevice();
String tmkHs = tmkAdapter.openSession(tmkHd);
tmkAdapter.getPrivateKeyAccessRight(tmkHs, device.getEncKeyIdx(), device.getAccessCredentials().getBytes());
cipher = tmkAdapter.exchangeDigitEnvelopeBaseOnECC(tmkHs, device.getEncKeyIdx(), pubKey, EccCipher.fromHex(device.getEncTmk()));
log.debug("exchanged envelope from finished success ...");
}
String hk = sdfApi.importKeyWithISKECC(hs, check.getEncKeyIdx(), cipher);
sdfApi.destroyKey(hs, hk);
if (!Objects.equals(check.getEncTmk(), cipher.getC1C3C2Hex())) {
res.setEncTmk(cipher.getC1C3C2Hex());
}
res.setStatus(DeviceTmkStatus.finished);
} else {
log.debug("tmk not init, start encrypt and import test ...");
byte[] random = sdfApi.generateRandom(hs, 16);
EccCipher eccCipher = sdfApi.externalEncryptECC(hs, pubKey, random);
String hk = sdfApi.importKeyWithISKECC(hs, check.getEncKeyIdx(), eccCipher);
sdfApi.destroyKey(hs, hk);
res.setStatus(DeviceTmkStatus.available);
}
} catch (Exception e) {
log.warn("check device encKey error: {}:{}", check.getServiceIp(), check.getServicePort(), e);
res.setHasError(true);
res.setStatus(DeviceTmkStatus.key_error);
res.setMessage(e.getMessage());
}
Optional.ofNullable(hs).ifPresent(sdfApi::closeSession);
Optional.ofNullable(hd).ifPresent(sdfApi::closeDevice);
res.setSdfApiAdapter(sdfApi);
log.debug("==========> end check device : {}", res);
return res;
}
public void checkSoftDeviceTmk() {
if (!isTmkInit() || !isEnableSoftDevice()) {
return;
}
if (Objects.nonNull(getSoftDeviceEncTmk())) {
return;
}
log.warn("enabled soft device but no tmk in soft");
Device device = getOneByStatus(DeviceTmkStatus.finished);
if (device == null || Objects.equals(device.getManufacturerModel(), BouncyCastleProvider.PROVIDER_NAME)) {
log.warn("data error, no tmk found in system");
return;
}
SdfApiAdapter softAdapter = SdfApiAdapterFactory.getBcAdapter();
EccPubKey pubKey = softAdapter.exportEncPublicKeyECC("", 1);
SdfApiAdapter tmkAdapter = SdfApiAdapterFactory.newInstance(device.getManufacturerModel(), device.getServiceIp(), device.getServicePort());
String tmkHd = tmkAdapter.openDevice();
String tmkHs = tmkAdapter.openSession(tmkHd);
tmkAdapter.getPrivateKeyAccessRight(tmkHs, device.getEncKeyIdx(), device.getAccessCredentials().getBytes());
EccCipher cipher = tmkAdapter.exchangeDigitEnvelopeBaseOnECC(tmkHs, device.getEncKeyIdx(), pubKey, EccCipher.fromHex(device.getEncTmk()));
updateSoftDeviceEncTmk(cipher.getC1C3C2Bytes());
}
private Device getOneByStatus(DeviceTmkStatus status) {
Device device = spDeviceMapper.selectOneByStatus(status);
if (Objects.nonNull(device)) {
return device;
}
if (isEnableSoftDevice()) {
device = new Device();
device.setId(0L);
device.setManufacturerModel(BouncyCastleProvider.PROVIDER_NAME);
device.setEncKeyIdx(1);
device.setServiceIp("127.0.0.1");
device.setServicePort(8889);
device.setAccessCredentials("543");
if (isTmkInit()) {
device.setEncTmk(CodecUtils.encodeHex(getSoftDeviceEncTmk()));
}
return device;
}
return null;
}
private boolean tmkInit;
private boolean enableSoftDevice;
private byte[] softEncTmk;
public synchronized boolean isTmkInitCached() {
if (tmkInit) {
return true;
}
ParamConf conf = paramConfMapper.selectByKey(ParamConfKeyConstant.TMK_INIT);
tmkInit = conf != null && String.valueOf(true).equals(conf.getValue());
return tmkInit;
}
public boolean isTmkInit() {
ParamConf conf = paramConfMapper.selectByKey(ParamConfKeyConstant.TMK_INIT);
return conf != null && String.valueOf(true).equals(conf.getValue());
}
public boolean isEnableSoftDevice() {
ParamConf conf = paramConfMapper.selectByKey(ParamConfKeyConstant.ENABLE_SOFT_DEVICE);
return conf != null && String.valueOf(true).equals(conf.getValue());
}
private void updateTmkInit(boolean value, byte[] hash) {
ParamConf conf = paramConfMapper.selectByKey(ParamConfKeyConstant.TMK_INIT);
if (conf == null) {
conf = new ParamConf();
conf.setValue(String.valueOf(value));
conf.setKey(ParamConfKeyConstant.TMK_INIT);
conf.setCreatTime(LocalDateTime.now());
paramConfMapper.insert(conf);
} else {
conf.setValue(String.valueOf(value));
paramConfMapper.updateById(conf);
}
ParamConf check = paramConfMapper.selectByKey(ParamConfKeyConstant.TMK_CHECK_VALUE);
if (check == null) {
check = new ParamConf();
check.setValue(CodecUtils.encodeHex(hash));
check.setKey(ParamConfKeyConstant.TMK_CHECK_VALUE);
check.setCreatTime(LocalDateTime.now());
paramConfMapper.insert(check);
} else {
check.setValue(CodecUtils.encodeHex(hash));
paramConfMapper.updateById(check);
}
}
public synchronized byte[] getSoftDeviceEncTmk() {
boolean tmkInit = isTmkInit();
Assert.isTrue(tmkInit, "主密钥未初始化");
boolean enabled = isEnableSoftDevice();
Assert.isTrue(enabled, "未启用软设备");
if (softEncTmk != null) {
return softEncTmk;
}
ParamConf conf = paramConfMapper.selectByKey(ParamConfKeyConstant.SOFT_ENC_TMK);
if (conf == null || ObjectUtils.isEmpty(conf.getValue())) {
return null;
}
softEncTmk = CodecUtils.decodeBase64(conf.getValue());
return softEncTmk;
}
private void updateSoftDeviceEncTmk(byte[] encTmk) {
ParamConf conf = paramConfMapper.selectByKey(ParamConfKeyConstant.SOFT_ENC_TMK);
if (conf == null) {
conf = new ParamConf();
conf.setValue(CodecUtils.encodeBase64(encTmk));
conf.setKey(ParamConfKeyConstant.SOFT_ENC_TMK);
conf.setCreatTime(LocalDateTime.now());
paramConfMapper.insert(conf);
} else {
conf.setValue(CodecUtils.encodeBase64(encTmk));
paramConfMapper.updateById(conf);
}
}
}

View File

@ -11,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.Subject;
import com.sunyard.chsm.model.dto.CertDTO;
import com.sunyard.chsm.model.entity.AppCert;
import com.sunyard.chsm.model.entity.Application;
@ -24,12 +25,7 @@ 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.*;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
@ -49,12 +45,7 @@ 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.*;
import java.util.stream.Collectors;
/**
@ -154,6 +145,8 @@ public class AppCertServiceImpl implements AppCertService {
AppCert exist = appCertMapper.selectBySN(x509Cert.getSerialNumber().toString());
Assert.isNull(exist, "此证书已经存在");
exist = appCertMapper.selectByTypeAndDn(importCert.getCertType(), x509Cert.getSubjectX500Principal().getName());
Assert.isNull(exist, "此证书主题已经存在");
AppCert cert = genCert(x509Cert, keyInfo.getApplicationId(), record, importCert);
appCertMapper.insert(cert);
@ -229,7 +222,8 @@ public class AppCertServiceImpl implements AppCertService {
cert.setCertText(importCert.getCertText());
cert.setVersion(String.valueOf(x509Cert.getVersion()));
cert.setSubject(x509Cert.getSubjectX500Principal().getName());
Subject subject = Subject.fromDN(x509Cert.getSubjectX500Principal().getName());
cert.setSubject(subject.getDN());
cert.setSerialNumber(x509Cert.getSerialNumber().toString());
cert.setIssuerDn(x509Cert.getIssuerX500Principal().getName());
cert.setNotBefore(x509Cert.getNotBefore());

View File

@ -159,7 +159,36 @@ public class KeyInfoServiceImpl implements KeyInfoService {
}
@Override
public Long save(KeyInfoDTO.KeySave save) {
public KeyInfoDTO.KeyView selectById(Long id) {
KeyInfo keyInfo = keyInfoMapper.selectById(id);
Assert.notNull(keyInfo, "密钥ID不存在");
LocalDateTime now = LocalDateTime.now();
KeyInfoDTO.KeyView view = new KeyInfoDTO.KeyView();
BeanUtils.copyProperties(keyInfo, view);
Optional.of(keyInfo.getKeyType()).map(KeyCategory::of).map(KeyCategory::getDesc).ifPresent(view::setKeyTypeText);
Map<String, String> usageMap = KeyUsage.getUsage(keyInfo.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(keyInfo.getStatus());
if (KeyStatus.ENABLED == keyStatus) {
if (now.isBefore(keyInfo.getEffectiveTime())) {
view.setStatus(KeyStatus.WAIT_ENABLED.getCode());
view.setStatusText(KeyStatus.WAIT_ENABLED.getDesc());
} else if (now.isAfter(keyInfo.getExpiredTime())) {
view.setStatus(KeyStatus.EXPIRED.getCode());
view.setStatusText(KeyStatus.EXPIRED.getDesc());
}
}
if (ObjectUtils.isEmpty(view.getStatusText())) {
view.setStatusText(keyStatus.getDesc());
}
return view;
}
@Override
public List<Long> save(KeyInfoDTO.KeySave save) {
KeyTemplate keyTemplate = keyTemplateMapper.selectOne(
new LambdaQueryWrapper<KeyTemplate>()
@ -171,7 +200,7 @@ public class KeyInfoServiceImpl implements KeyInfoService {
Assert.isTrue(EnableStatus.ENABLED.getCode().equals(app.getStatus()), "应用不是启用状态");
LocalDateTime now = LocalDateTime.now();
List<Long> ids = new ArrayList<>();
for (int i = 0; i < save.getGenNumber(); i++) {
// 密钥信息
@ -200,9 +229,11 @@ public class KeyInfoServiceImpl implements KeyInfoService {
info.setCheckValue(record.getCheckValue());
keyInfoMapper.insert(info);
spKeyRecordMapper.insert(record);
ids.add(info.getId());
}
return 0L;
return ids;
}
@Override
@ -218,7 +249,7 @@ public class KeyInfoServiceImpl implements KeyInfoService {
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(",")));
log.warn("updateKey no exist key with ids: {}", ids.stream().map(String::valueOf).collect(Collectors.joining(",")));
return;
}
List<String> unNormalCodes = keyInfos.stream()

View File

@ -0,0 +1,74 @@
package com.sunyard.chsm.utils;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.ByteArrayInputStream;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import static com.sunyard.chsm.utils.SydCmsUtil.readContentInfo;
/**
* @author zyh
* @since 2024/12/19
*/
public class CertUtil {
static {
Security.addProvider(new BouncyCastleProvider());
}
public static X509Certificate convertToX509Cert(String certificateString) {
String pemCert;
if(certificateString.contains("-----BEGIN CERTIFICATE-----")){
pemCert =certificateString;
}else {
String cert_begin = "-----BEGIN CERTIFICATE-----\n";
String end_cert = "\n-----END CERTIFICATE-----";
pemCert = cert_begin + certificateString + end_cert;
}
try {
CertificateFactory CF = CertificateFactory.getInstance("X.509", "BC"); // 从证书工厂中获取X.509的单例类
return (X509Certificate) CF.generateCertificate(new ByteArrayInputStream(pemCert.getBytes())); // 将文件流的证书转化为证书类
} catch (CertificateException | NoSuchProviderException e) {
throw new RuntimeException(e);
}
}
public static String getSn(String cert) {
try {
if(cert.contains("-----BEGIN CERTIFICATE-----")){
String[] lines = cert.split("\n");
// 构建新的证书字符串去掉第一行和最后一行
StringBuilder cleanedCertificate = new StringBuilder();
for (int i = 1; i < lines.length - 1; i++) {
cleanedCertificate.append(lines[i]);
}
cert = cleanedCertificate.toString();
}
//从证书中获取sn
Object pkContent = readContentInfo(CodecUtils.decodeBase64(cert));
//逐级从结构体中获取需要的sn
ASN1Sequence pkSeq = ASN1Sequence.getInstance(pkContent);
ASN1Sequence pkSeq0 = (ASN1Sequence) pkSeq.getObjectAt(0).toASN1Primitive();
ASN1Integer sn = (ASN1Integer) pkSeq0.getObjectAt(1).toASN1Primitive();
String serNum = CodecUtils.encodeHex(sn.getEncoded());
//去掉长度
return serNum.substring(4);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,55 @@
package com.sunyard.chsm.utils;
import org.bouncycastle.util.encoders.Hex;
import java.util.Base64;
/**
* @author liulu
* @since 2024/12/6
*/
public abstract class CodecUtils {
public static String encodeBase64(byte[] data) {
return Base64.getEncoder().encodeToString(data);
}
public static byte[] decodeBase64(String str) {
if (str == null || str.isEmpty()) {
return null;
}
try {
return Base64.getDecoder().decode(str);
} catch (Exception var3) {
if (str.length() > 32) {
str = str.substring(0, 32) + "...";
}
String format = String.format("参数[%s]不是正确的base64格式", str);
throw new IllegalArgumentException(format);
}
}
public static String encodeHex(byte[] data) {
if (data == null) {
return "";
}
return Hex.toHexString(data);
}
public static byte[] decodeHex(String str) {
if (str == null || str.isEmpty()) {
return null;
}
try {
return Hex.decode(str);
} catch (Exception var3) {
if (str.length() > 32) {
str = str.substring(0, 32) + "...";
}
String format = String.format("参数[%s]不是正确的hex格式", str);
throw new IllegalArgumentException(format);
}
}
}

View File

@ -1,6 +1,8 @@
package com.sunyard.chsm.utils;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.util.Date;
/**
* @author liulu
@ -18,4 +20,10 @@ public abstract class DateFormat {
public static final DateTimeFormatter DATE = DateTimeFormatter.ofPattern(YYYY_MM_DD);
public static final DateTimeFormatter DATE_TIME = DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS);
public static String formDate(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
return sdf.format(date);
}
}

View File

@ -0,0 +1,52 @@
package com.sunyard.chsm.utils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
public class IpUtils {
/**
* 获取真实ip地址避免获取代理ip
*/
public static String getIpAddress(HttpServletRequest request) {
if (request == null) {
return "unknown";
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
if (StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个IP值第一个为真实IP
int index = ip.indexOf(',');
if (index != -1) {
ip = ip.substring(0, index);
}
}
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if("0:0:0:0:0:0:0:1".equals(ip)){
return "127.0.0.1";
}else {
if(ip.equals("127.0.0.1") || ip.equalsIgnoreCase("localhost") && ObjectUtils.isEmpty(request.getRemoteAddr())){
ip = request.getRemoteAddr();
}
}
return ip;
}
}

View File

@ -0,0 +1,106 @@
package com.sunyard.chsm.utils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.cms.EncryptedContentInfo;
import java.util.Enumeration;
/**
* <pre>
* SignedAndEnvelopedData ::=SEQUENCE {
* version Version,
* recipientInfos RecipientInfos,
* digestAlgorithms DigestAlgorithmIdentifiers
* encryptedContentInfo EncryptedContentInfo,
* certificates [0] IMPLICIT ExtendedCertificatesAndCertificates OPTIONAL,
* crls [1] IMPLICIT CertificateRevocationLists OPTIONAL,
* signerInfos SignerInfos
* }
* </pre>
*
* @author liulu
* @since 2024/12/25
*/
@Getter
@AllArgsConstructor
public class SignedAndEnvelopedData extends ASN1Object {
private ASN1Integer version;
private ASN1Set recipientInfos;
private ASN1Set digestAlgorithms;
private EncryptedContentInfo encryptedContentInfo;
private ASN1Set certificates;
private ASN1Set crls;
private ASN1Set signerInfos;
public static SignedAndEnvelopedData getInstance(
Object obj) {
if (obj instanceof SignedAndEnvelopedData) {
return (SignedAndEnvelopedData) obj;
}
if (obj != null) {
return new SignedAndEnvelopedData(ASN1Sequence.getInstance(obj));
}
return null;
}
private SignedAndEnvelopedData(ASN1Sequence seq) {
Enumeration e = seq.getObjects();
version = ASN1Integer.getInstance(e.nextElement());
recipientInfos = ((ASN1Set) e.nextElement());
digestAlgorithms = ((ASN1Set) e.nextElement());
encryptedContentInfo = EncryptedContentInfo.getInstance(e.nextElement());
ASN1Set sigInfs = null;
while (e.hasMoreElements()) {
ASN1Primitive o = (ASN1Primitive) e.nextElement();
if (o instanceof ASN1TaggedObject) {
ASN1TaggedObject tagged = (ASN1TaggedObject) o;
switch (tagged.getTagNo()) {
case 0:
certificates = ASN1Set.getInstance(tagged, false);
break;
case 1:
crls = ASN1Set.getInstance(tagged, false);
break;
default:
throw new IllegalArgumentException("unknown tag value " + tagged.getTagNo());
}
} else {
if (!(o instanceof ASN1Set)) {
throw new IllegalArgumentException("SET expected, not encountered");
}
sigInfs = (ASN1Set) o;
}
}
if (sigInfs == null) {
throw new IllegalArgumentException("signerInfos not set");
}
signerInfos = sigInfs;
}
@Override
public ASN1Primitive toASN1Primitive() {
ASN1EncodableVector v = new ASN1EncodableVector(7);
v.add(version);
v.add(recipientInfos);
v.add(digestAlgorithms);
v.add(encryptedContentInfo);
if (certificates != null) {
v.add(new DERTaggedObject(false, 0, certificates));
}
if (crls != null) {
v.add(new DERTaggedObject(false, 1, crls));
}
v.add(signerInfos);
return new DLSequence(v);
}
}

View File

@ -0,0 +1,140 @@
package com.sunyard.chsm.utils;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.cms.EncryptedContentInfo;
import org.bouncycastle.asn1.cms.OriginatorInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.cms.CMSException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class SydCmsUtil {
public static Object readContentInfo(
byte[] input) {
ASN1InputStream is = new ASN1InputStream(input);
Object v = null;
try {
v = is.readObject();
} catch (IOException e) {
e.printStackTrace();
}
return v;
}
public static Map<String, Object> getEncryptedContentInfo(ASN1Encodable obj){
Map<String, Object>map = new HashMap<String, Object>();
ASN1Sequence seq = ASN1Sequence.getInstance(obj );
if (seq.size() < 2)
{
throw new IllegalArgumentException("Truncated Sequence Found");
}
ASN1ObjectIdentifier contentType = (ASN1ObjectIdentifier)seq.getObjectAt(0);
map.put( "contentType", contentType );
AlgorithmIdentifier contentEncryptionAlgorithm = AlgorithmIdentifier.getInstance(
seq.getObjectAt(1));
map.put( "contentEncryptionAlgorithm", contentEncryptionAlgorithm );
if (seq.size() > 2)
{
ASN1OctetString encryptedContent = ASN1OctetString.getInstance(
(ASN1TaggedObject)seq.getObjectAt(2), false);
map.put( "encryptedContent", encryptedContent );
}
return map;
}
public static Map<String, Object> getOriginatorInfo(ASN1TaggedObject obj,
boolean explicit){
Map<String, Object>map = new HashMap<String, Object>();
ASN1Sequence seq = ASN1Sequence.getInstance(obj, explicit);
switch (seq.size())
{
case 0: // empty
break;
case 1:
ASN1TaggedObject o = (ASN1TaggedObject)seq.getObjectAt(0);
switch (o.getTagNo())
{
case 0 :
ASN1Set certs = ASN1Set.getInstance(o, false);
map.put("certs", certs);
break;
case 1 :
ASN1Set crls = ASN1Set.getInstance(o, false);
map.put("crls", crls);
break;
default:
throw new IllegalArgumentException("Bad tag in OriginatorInfo: " + o.getTagNo());
}
break;
case 2:
ASN1Set certs = ASN1Set.getInstance((ASN1TaggedObject)seq.getObjectAt(0), false);
map.put("certs", certs);
ASN1Set crls = ASN1Set.getInstance((ASN1TaggedObject)seq.getObjectAt(1), false);
map.put("crls", crls);
break;
default:
throw new IllegalArgumentException("OriginatorInfo too big");
}
return map;
}
// public static ContentInfo getContentInfo( byte[] input ) {
// Object content = readContentInfo( input );
// ASN1Sequence seq = ASN1Sequence.getInstance( content );
// return new ContentInfo(seq);
//
// }
public static Map<String, Object> readCmsInfo( byte[] input ) throws CMSException {
Map<String, Object>map = new HashMap<String, Object>();
Object content = readContentInfo( input );
ASN1Sequence seq = ASN1Sequence.getInstance( content );
ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) seq.getObjectAt(0);
map.put("oid", oid);
seq = ASN1Sequence.getInstance((ASN1TaggedObject) seq.getObjectAt(1), true);
int index = 0;
ASN1Integer version = (ASN1Integer)seq.getObjectAt(index++);
map.put( "version", version );
Object tmp = seq.getObjectAt(index++);
if (tmp instanceof ASN1TaggedObject)
{
OriginatorInfo originatorInfo = OriginatorInfo.getInstance((ASN1TaggedObject)tmp, false);
// Map<String, Object>originatorInfo = getOriginatorInfo((ASN1TaggedObject)tmp, false);
map.put( "originatorInfo", originatorInfo );
tmp = seq.getObjectAt(index++);
}
ASN1Set recipientInfos = ASN1Set.getInstance(tmp);
map.put( "recipientInfos", recipientInfos );
EncryptedContentInfo encryptedContentInfo = EncryptedContentInfo.getInstance( seq.getObjectAt(index++) );
// Map<String, Object> encryptedContentInfo = getEncryptedContentInfo(seq.getObjectAt(index++));
map.put( "encryptedContentInfo", encryptedContentInfo );
if(seq.size() > index)
{
ASN1Set unprotectedAttrs = ASN1Set.getInstance((ASN1TaggedObject)seq.getObjectAt(index), false);
map.put( "unprotectedAttrs", unprotectedAttrs );
}
return map;
}
}

View File

@ -61,7 +61,7 @@ import java.util.Arrays;
/**
* 这个工具类的方法也适用于其他基于BC库的ECC算法
*/
public class BCECUtils {
public class BCECUtils extends GMBaseUtil {
private static final String ALGO_NAME_EC = "EC";
private static final String PEM_STRING_PUBLIC = "PUBLIC KEY";
private static final String PEM_STRING_ECPRIVATEKEY = "EC PRIVATE KEY";

View File

@ -0,0 +1,294 @@
package com.sunyard.chsm.utils.gm;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithID;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.signers.DSAKCalculator;
import org.bouncycastle.crypto.signers.RandomDSAKCalculator;
import org.bouncycastle.math.ec.ECAlgorithms;
import org.bouncycastle.math.ec.ECConstants;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECMultiplier;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Hex;
import java.io.IOException;
import java.math.BigInteger;
/**
* 有的国密需求是用户可以自己做预处理签名验签只是对预处理的结果进行签名和验签
*/
public class SM2PreprocessSigner implements ECConstants {
private static final int DIGEST_LENGTH = 32; // bytes
private final DSAKCalculator kCalculator = new RandomDSAKCalculator();
private Digest digest = null;
private ECDomainParameters ecParams;
private ECPoint pubPoint;
private ECKeyParameters ecKey;
private byte[] userID;
/**
* 初始化
*
* @param forSigning true表示用于签名false表示用于验签
* @param param
*/
public void init(boolean forSigning, CipherParameters param) {
init(forSigning, new SM3Digest(), param);
}
/**
* 初始化
*
* @param forSigning true表示用于签名false表示用于验签
* @param digest SM2算法的话一般是采用SM3摘要算法
* @param param
* @throws RuntimeException
*/
public void init(boolean forSigning, Digest digest, CipherParameters param) throws RuntimeException {
CipherParameters baseParam;
if (digest.getDigestSize() != DIGEST_LENGTH) {
throw new RuntimeException("Digest size must be " + DIGEST_LENGTH);
}
this.digest = digest;
if (param instanceof ParametersWithID) {
baseParam = ((ParametersWithID) param).getParameters();
userID = ((ParametersWithID) param).getID();
} else {
baseParam = param;
userID = Hex.decode("31323334353637383132333435363738"); // the default value
}
if (forSigning) {
if (baseParam instanceof ParametersWithRandom) {
ParametersWithRandom rParam = (ParametersWithRandom) baseParam;
ecKey = (ECKeyParameters) rParam.getParameters();
ecParams = ecKey.getParameters();
kCalculator.init(ecParams.getN(), rParam.getRandom());
} else {
ecKey = (ECKeyParameters) baseParam;
ecParams = ecKey.getParameters();
kCalculator.init(ecParams.getN(), CryptoServicesRegistrar.getSecureRandom());
}
pubPoint = createBasePointMultiplier().multiply(ecParams.getG(), ((ECPrivateKeyParameters) ecKey).getD()).normalize();
} else {
ecKey = (ECKeyParameters) baseParam;
ecParams = ecKey.getParameters();
pubPoint = ((ECPublicKeyParameters) ecKey).getQ();
}
}
/**
* 预处理辅助方法
* ZA=H256(ENT LA IDA a b xG yG xA yA)
* M=ZA M
* e = Hv(M)
*
* @return
*/
public byte[] preprocess(byte[] m) {
return preprocess(m, 0, m.length);
}
public byte[] preprocess(byte[] m, int off, int len) {
byte[] z = getZ(userID);
digest.update(z, 0, z.length);
digest.update(m, off, len);
byte[] eHash = new byte[DIGEST_LENGTH];
digest.doFinal(eHash, 0);
return eHash;
}
public boolean verifySignature(byte[] eHash, byte[] signature) {
try {
BigInteger[] rs = derDecode(signature);
if (rs != null) {
return verifySignature(eHash, rs[0], rs[1]);
}
} catch (IOException e) {
}
return false;
}
public void reset() {
digest.reset();
}
public byte[] generateSignature(byte[] eHash) throws CryptoException {
BigInteger n = ecParams.getN();
BigInteger e = calculateE(eHash);
BigInteger d = ((ECPrivateKeyParameters) ecKey).getD();
BigInteger r, s;
ECMultiplier basePointMultiplier = createBasePointMultiplier();
// 5.2.1 Draft RFC: SM2 Public Key Algorithms
do // generate s
{
BigInteger k;
do // generate r
{
// A3
k = kCalculator.nextK();
// A4
ECPoint p = basePointMultiplier.multiply(ecParams.getG(), k).normalize();
// A5
r = e.add(p.getAffineXCoord().toBigInteger()).mod(n);
}
while (r.equals(ZERO) || r.add(k).equals(n));
// A6
BigInteger dPlus1ModN = d.add(ONE).modInverse(n);
s = k.subtract(r.multiply(d)).mod(n);
s = dPlus1ModN.multiply(s).mod(n);
}
while (s.equals(ZERO));
// A7
try {
return derEncode(r, s);
} catch (IOException ex) {
throw new CryptoException("unable to encode signature: " + ex.getMessage(), ex);
}
}
private boolean verifySignature(byte[] eHash, BigInteger r, BigInteger s) {
BigInteger n = ecParams.getN();
// 5.3.1 Draft RFC: SM2 Public Key Algorithms
// B1
if (r.compareTo(ONE) < 0 || r.compareTo(n) >= 0) {
return false;
}
// B2
if (s.compareTo(ONE) < 0 || s.compareTo(n) >= 0) {
return false;
}
// B3 eHash
// B4
BigInteger e = calculateE(eHash);
// B5
BigInteger t = r.add(s).mod(n);
if (t.equals(ZERO)) {
return false;
}
// B6
ECPoint q = ((ECPublicKeyParameters) ecKey).getQ();
ECPoint x1y1 = ECAlgorithms.sumOfTwoMultiplies(ecParams.getG(), s, q, t).normalize();
if (x1y1.isInfinity()) {
return false;
}
// B7
BigInteger expectedR = e.add(x1y1.getAffineXCoord().toBigInteger()).mod(n);
return expectedR.equals(r);
}
private byte[] digestDoFinal() {
byte[] result = new byte[digest.getDigestSize()];
digest.doFinal(result, 0);
reset();
return result;
}
private byte[] getZ(byte[] userID) {
digest.reset();
addUserID(digest, userID);
addFieldElement(digest, ecParams.getCurve().getA());
addFieldElement(digest, ecParams.getCurve().getB());
addFieldElement(digest, ecParams.getG().getAffineXCoord());
addFieldElement(digest, ecParams.getG().getAffineYCoord());
addFieldElement(digest, pubPoint.getAffineXCoord());
addFieldElement(digest, pubPoint.getAffineYCoord());
byte[] result = new byte[digest.getDigestSize()];
digest.doFinal(result, 0);
return result;
}
private void addUserID(Digest digest, byte[] userID) {
int len = userID.length * 8;
digest.update((byte) (len >> 8 & 0xFF));
digest.update((byte) (len & 0xFF));
digest.update(userID, 0, userID.length);
}
private void addFieldElement(Digest digest, ECFieldElement v) {
byte[] p = v.getEncoded();
digest.update(p, 0, p.length);
}
protected ECMultiplier createBasePointMultiplier() {
return new FixedPointCombMultiplier();
}
protected BigInteger calculateE(byte[] message) {
return new BigInteger(1, message);
}
protected BigInteger[] derDecode(byte[] encoding)
throws IOException {
ASN1Sequence seq = ASN1Sequence.getInstance(ASN1Primitive.fromByteArray(encoding));
if (seq.size() != 2) {
return null;
}
BigInteger r = ASN1Integer.getInstance(seq.getObjectAt(0)).getValue();
BigInteger s = ASN1Integer.getInstance(seq.getObjectAt(1)).getValue();
byte[] expectedEncoding = derEncode(r, s);
if (!Arrays.constantTimeAreEqual(expectedEncoding, encoding)) {
return null;
}
return new BigInteger[]{r, s};
}
protected byte[] derEncode(BigInteger r, BigInteger s)
throws IOException {
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(new ASN1Integer(r));
v.add(new ASN1Integer(s));
return new DERSequence(v).getEncoded(ASN1Encoding.DER);
}
}

View File

@ -67,7 +67,8 @@ public class CommonCertUtils extends GMBaseUtil {
}
public static PKCS10CertificationRequest createCSR(X500Name subject, BCECPublicKey pubKey, PrivateKey priKey) throws OperatorCreationException {
PKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder(subject, pubKey);
SM2PublicKey sm2SubPub = new SM2PublicKey(pubKey.getAlgorithm(), pubKey);
PKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder(subject, sm2SubPub);
ContentSigner signerBuilder = new JcaContentSignerBuilder("SM3withSM2")
.setProvider(BouncyCastleProvider.PROVIDER_NAME).build(priKey);
return csrBuilder.build(signerBuilder);

View File

@ -0,0 +1,44 @@
package com.sunyard.chsm.utils.gm.cert;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X9ECPoint;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.KeyUtil;
public class SM2PublicKey extends BCECPublicKey {
public static final ASN1ObjectIdentifier ID_SM2_PUBKEY_PARAM = new ASN1ObjectIdentifier("1.2.156.10197.1.301");
private boolean withCompression;
public SM2PublicKey(BCECPublicKey key) {
super(key.getAlgorithm(), key);
this.withCompression = false;
}
public SM2PublicKey(String algorithm, BCECPublicKey key) {
super(algorithm, key);
this.withCompression = false;
}
@Override
public byte[] getEncoded() {
ASN1OctetString p = ASN1OctetString.getInstance(
new X9ECPoint(getQ(), withCompression).toASN1Primitive());
// stored curve is null if ImplicitlyCa
SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, ID_SM2_PUBKEY_PARAM),
p.getOctets());
return KeyUtil.getEncodedSubjectPublicKeyInfo(info);
}
@Override
public void setPointFormat(String style) {
withCompression = !("UNCOMPRESSED".equalsIgnoreCase(style));
}
}

View File

@ -0,0 +1,283 @@
package com.sunyard.chsm.utils.gm.cert;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
public class SM2X509CertMaker {
private static enum CertLevel {
RootCA,
SubCA,
EndEntity
} // class CertLevel
public static final String SIGN_ALGO_SM3WITHSM2 = "SM3withSM2";
private long certExpire;
private X500Name issuerDN;
// private CertSNAllocator snAllocator;
private KeyPair issuerKeyPair;
/**
* @param issuerKeyPair 证书颁发者的密钥对
* 其实一般的CA的私钥都是要严格保护的
* 一般CA的私钥都会放在加密卡/加密机里证书的签名由加密卡/加密机完成
* 这里仅是为了演示BC库签发证书的用法所以暂时不作太多要求
* @param certExpire 证书有效时间单位毫秒
* @param issuer 证书颁发者信息
* @param snAllocator 维护/分配证书序列号的实例证书序列号应该递增且不重复
*/
public SM2X509CertMaker(KeyPair issuerKeyPair, long certExpire, X500Name issuer)
// , CertSNAllocator snAllocator)
{
this.issuerKeyPair = issuerKeyPair;
this.certExpire = certExpire;
this.issuerDN = issuer;
// this.snAllocator = snAllocator;
}
/**
* 生成根CA证书
*
* @param csr CSR
* @return 新的证书
* @throws Exception 如果错误发生
*/
public X509Certificate makeRootCACert(byte[] csr)
throws Exception {
KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign);
return makeCertificate(CertLevel.RootCA, null, csr, usage, null);
}
/**
* 生成SubCA证书
*
* @param csr CSR
* @return 新的证书
* @throws Exception 如果错误发生
*/
public X509Certificate makeSubCACert(byte[] csr)
throws Exception {
KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign);
return makeCertificate(CertLevel.SubCA, 0, csr, usage, null);
}
/**
* 生成SSL用户证书
*
* @param csr CSR
* @return 新的证书
* @throws Exception 如果错误发生
*/
public X509Certificate makeSSLEndEntityCert(byte[] csr)
throws Exception {
return makeEndEntityCert(csr,
new KeyPurposeId[]{KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth});
}
/**
* 生成用户证书
*
* @param csr CSR
* @param extendedKeyUsages 扩展指数用途
* @return 新的证书
* @throws Exception 如果错误发生
*/
public X509Certificate makeEndEntityCert(byte[] csr,
KeyPurposeId[] extendedKeyUsages)
throws Exception {
KeyUsage usage = new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyAgreement
| KeyUsage.dataEncipherment | KeyUsage.keyEncipherment);
return makeCertificate(CertLevel.EndEntity, null, csr, usage, extendedKeyUsages);
}
/**
* @param isCA 是否是颁发给CA的证书
* @param keyUsage 证书用途
* @param csr CSR
* @return
* @throws Exception
*/
private X509Certificate makeCertificate(CertLevel certLevel, Integer pathLenConstrain,
byte[] csr, KeyUsage keyUsage, KeyPurposeId[] extendedKeyUsages)
throws Exception {
if (certLevel == CertLevel.EndEntity) {
if (keyUsage.hasUsages(KeyUsage.keyCertSign)) {
throw new IllegalArgumentException(
"keyusage keyCertSign is not allowed in EndEntity Certificate");
}
}
PKCS10CertificationRequest request = new PKCS10CertificationRequest(csr);
SubjectPublicKeyInfo subPub = request.getSubjectPublicKeyInfo();
PrivateKey issPriv = issuerKeyPair.getPrivate();
PublicKey issPub = issuerKeyPair.getPublic();
X500Name subject = request.getSubject();
String email = null;
String commonName = null;
/*
* RFC 5280 §4.2.1.6 Subject
* Conforming implementations generating new certificates with
* electronic mail addresses MUST use the rfc822Name in the subject
* alternative name extension (Section 4.2.1.6) to describe such
* identities. Simultaneous inclusion of the emailAddress attribute in
* the subject distinguished name to support legacy implementations is
* deprecated but permitted.
*/
RDN[] rdns = subject.getRDNs();
List<RDN> newRdns = new ArrayList<>(rdns.length);
for (int i = 0; i < rdns.length; i++) {
RDN rdn = rdns[i];
AttributeTypeAndValue atv = rdn.getFirst();
ASN1ObjectIdentifier type = atv.getType();
if (BCStyle.EmailAddress.equals(type)) {
email = IETFUtils.valueToString(atv.getValue());
} else {
if (BCStyle.CN.equals(type)) {
commonName = IETFUtils.valueToString(atv.getValue());
}
newRdns.add(rdn);
}
}
List<GeneralName> subjectAltNames = new LinkedList<>();
if (email != null) {
subject = new X500Name(newRdns.toArray(new RDN[0]));
subjectAltNames.add(
new GeneralName(GeneralName.rfc822Name,
new DERIA5String(email, true)));
}
boolean selfSignedEECert = false;
switch (certLevel) {
case RootCA:
if (issuerDN.equals(subject)) {
subject = issuerDN;
} else {
throw new IllegalArgumentException("subject != issuer for certLevel " + CertLevel.RootCA);
}
break;
case SubCA:
if (issuerDN.equals(subject)) {
throw new IllegalArgumentException(
"subject MUST not equals issuer for certLevel " + certLevel);
}
break;
default:
if (issuerDN.equals(subject)) {
selfSignedEECert = true;
subject = issuerDN;
}
}
// BigInteger serialNumber = snAllocator.nextSerialNumber();
BigInteger serialNumber = new BigInteger(IdWorker.getIdStr());
Date notBefore = new Date();
Date notAfter = new Date(notBefore.getTime() + certExpire);
X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder(
issuerDN, serialNumber,
notBefore, notAfter,
subject, subPub);
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
v3CertGen.addExtension(Extension.subjectKeyIdentifier, false,
extUtils.createSubjectKeyIdentifier(subPub));
if (certLevel != CertLevel.RootCA && !selfSignedEECert) {
v3CertGen.addExtension(Extension.authorityKeyIdentifier, false,
extUtils.createAuthorityKeyIdentifier(SubjectPublicKeyInfo.getInstance(issPub.getEncoded())));
}
// RFC 5280 §4.2.1.9 Basic Constraints:
// Conforming CAs MUST include this extension in all CA certificates
// that contain public keys used to validate digital signatures on
// certificates and MUST mark the extension as critical in such
// certificates.
BasicConstraints basicConstraints;
if (certLevel == CertLevel.EndEntity) {
basicConstraints = new BasicConstraints(false);
} else {
basicConstraints = pathLenConstrain == null
? new BasicConstraints(true) : new BasicConstraints(pathLenConstrain.intValue());
}
v3CertGen.addExtension(Extension.basicConstraints, true, basicConstraints);
// RFC 5280 §4.2.1.3 Key Usage: When present, conforming CAs SHOULD mark this extension as critical.
v3CertGen.addExtension(Extension.keyUsage, true, keyUsage);
if (extendedKeyUsages != null) {
ExtendedKeyUsage xku = new ExtendedKeyUsage(extendedKeyUsages);
v3CertGen.addExtension(Extension.extendedKeyUsage, false, xku);
boolean forSSLServer = false;
for (KeyPurposeId purposeId : extendedKeyUsages) {
if (KeyPurposeId.id_kp_serverAuth.equals(purposeId)) {
forSSLServer = true;
break;
}
}
if (forSSLServer) {
if (commonName == null) {
throw new IllegalArgumentException("commonName must not be null");
}
GeneralName name = new GeneralName(GeneralName.dNSName,
new DERIA5String(commonName, true));
subjectAltNames.add(name);
}
}
if (!subjectAltNames.isEmpty()) {
v3CertGen.addExtension(Extension.subjectAlternativeName, false,
new GeneralNames(subjectAltNames.toArray(new GeneralName[0])));
}
JcaContentSignerBuilder contentSignerBuilder = makeContentSignerBuilder(issPub);
X509Certificate cert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME)
.getCertificate(v3CertGen.build(contentSignerBuilder.build(issPriv)));
cert.verify(issPub);
return cert;
}
private JcaContentSignerBuilder makeContentSignerBuilder(PublicKey issPub) throws Exception {
if (issPub.getAlgorithm().equals("EC")) {
JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder(SIGN_ALGO_SM3WITHSM2);
contentSignerBuilder.setProvider(BouncyCastleProvider.PROVIDER_NAME);
return contentSignerBuilder;
}
throw new Exception("Unsupported PublicKey Algorithm:" + issPub.getAlgorithm());
}
}

View File

@ -16,5 +16,13 @@
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,36 +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);
}
}
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,10 @@
package com.sunyard.chsm.enums;
/**
* @author liulu
* @since 2024/10/22
*/
public enum HashAlg {
SM3,
;
}

View File

@ -1,34 +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 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);
}
}
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"),
PCKS7Padding("PKCS7Padding", "PKCS7Padding"),
;
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,33 @@
package com.sunyard.chsm.param;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
* @author liulu
* @since 2024/11/13
*/
@Data
public class AppTokenReq {
/**
* 平台分配的appKey
*/
@NotBlank
@Size(min = 16, max = 64, message = "appKey长度必须为16-64")
private String appKey;
/**
* 时间戳毫秒数与服务器时间不能超过5分钟
*/
@NotNull
private Long timestamp;
/**
* 使用appKey+random+appSecret作为原文使用appSecret作为key根据HmacSM3算法计算得到hmac值,转为Hex编码
*/
@NotBlank
@Size(min = 32, max = 64, message = "hmac长度在60-64之间")
private String hmac;
}

View File

@ -0,0 +1,15 @@
package com.sunyard.chsm.param;
import lombok.Data;
/**
* @author liulu
* @since 2024/11/13
*/
@Data
public class AppTokenResp {
/**
* 获取的token的值
*/
private String token;
}

View File

@ -0,0 +1,30 @@
package com.sunyard.chsm.param;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
* @author Wangjingcheng
* @Version 1.0.0
* @Date 2023/8/4 9:56
*/
@Data
public class AsymDecryptReq {
// 密钥ID
@NotNull(message = "密钥ID不能为空")
private Long keyId;
// 密钥索引
@NotEmpty(message = "密钥索引不能为空")
@Size(min = 15, max = 24, message = "密钥索引长度在15-24")
private String keyIndex;
// 密文,使用Base64编码
@NotBlank(message = "密文不能为空")
private String cipherData;
}

View File

@ -0,0 +1,13 @@
package com.sunyard.chsm.param;
import lombok.Data;
@Data
public class AsymDecryptResp {
// 密钥ID
private Long keyId;
// 密钥索引
private String keyIndex;
// 明文,使用Base64编码
private String plainData;
}

View File

@ -0,0 +1,18 @@
package com.sunyard.chsm.param;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class AsymEncryptReq {
// 密钥ID
@NotNull(message = "密钥ID不能为空")
private Long keyId;
// 明文,使用Base64编码
@NotBlank(message = "明文不能为空")
private String plainData;
}

View File

@ -0,0 +1,16 @@
package com.sunyard.chsm.param;
import lombok.Data;
@Data
public class AsymEncryptResp {
// 密钥ID
private Long keyId;
// 密钥索引
private String keyIndex;
// 密文C1C3C2,使用Base64编码
private String cipherData;
}

View File

@ -0,0 +1,21 @@
package com.sunyard.chsm.param;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class AsymEnvelopeSealReq {
// 签名证书主题
private String signSubject;
// 接收者加密证书
@NotBlank(message = "加密证书不能为空")
private String encCert;
// 明文,使用Base64编码
@NotBlank(message = "明文不能为空")
private String plainData;
}

View File

@ -0,0 +1,11 @@
package com.sunyard.chsm.param;
import lombok.Data;
@Data
public class AsymEnvelopeSealResp {
// 数字信封使用Base64编码
private String envelopeData;
}

View File

@ -0,0 +1,21 @@
package com.sunyard.chsm.param;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class AsymEnvelopeUnsealReq {
/**
* 加密证书的主题
*/
// @NotBlank(message = "加密证书不能为空")
// private String subject;
// 数字信封使用Base64编码
@NotBlank(message = "数字信封不能为空")
private String envelopeData;
}

View File

@ -0,0 +1,12 @@
package com.sunyard.chsm.param;
import lombok.Data;
@Data
public class AsymEnvelopeUnsealResp {
// 明文,使用Base64编码
private String plainData;
}

View File

@ -0,0 +1,20 @@
package com.sunyard.chsm.param;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class AsymSignP7Req {
/**
* 签名证书的主题
*/
@NotBlank(message = "签名证书不能为空")
private String subject;
// 原文,使用Base64编码
@NotBlank(message = "原文不能为空")
private String plainData;
}

View File

@ -0,0 +1,10 @@
package com.sunyard.chsm.param;
import lombok.Data;
@Data
public class AsymSignP7Resp {
// 签名值 Base64编码
private String signData;
}

View File

@ -0,0 +1,24 @@
package com.sunyard.chsm.param;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class AsymSignRawReq {
// 密钥ID
@NotNull(message = "密钥ID不能为空")
private Long keyId;
// 原文,使用Base64编码
@NotBlank(message = "明文不能为空")
private String plainData;
// 是否进行预处理 默认是
private boolean preProcess = true;
// 预处理的userId,使用Base64编码
private String userId;
}

View File

@ -0,0 +1,16 @@
package com.sunyard.chsm.param;
import lombok.Data;
@Data
public class AsymSignRawResp {
// 密钥ID
private Long keyId;
// 密钥索引
private String keyIndex;
// 签名值 Base64编码
private String signData;
}

View File

@ -0,0 +1,23 @@
package com.sunyard.chsm.param;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class AsymVerifyP7Req {
/**
* 签名证书的主题
*/
// @NotBlank(message = "签名证书不能为空")
// private String subject;
// 原文,使用Base64编码
private String plainData;
// 签名值, 使用Base64编码
@NotBlank(message = "签名值不能为空")
private String signData;
}

View File

@ -0,0 +1,35 @@
package com.sunyard.chsm.param;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Data
public class AsymVerifyRawReq {
// 密钥ID
@NotNull(message = "密钥ID不能为空")
private Long keyId;
// 密钥索引
@NotEmpty(message = "密钥索引不能为空")
@Size(min = 15, max = 24, message = "密钥索引长度在15-24")
private String keyIndex;
// 签名值,使用Base64编码
@NotBlank(message = "密文不能为空")
private String signData;
// 原文,使用Base64编码
@NotBlank(message = "明文不能为空")
private String plainData;
// 是否进行预处理 默认是
private boolean preProcess = true;
// 预处理的userId,使用Base64编码
private String userId;
}

View File

@ -0,0 +1,33 @@
package com.sunyard.chsm.param;
import lombok.Data;
/**
* @author zyh
* @since 2024/12/20
*/
@Data
public class CertCheckResp {
/*
证书有效期
*/
private String NotBefore;
/*
证书过期时间
*/
private String NotAfter;
/*
证书签名
* /
*/
private String Signature;
/*
CRL
*/
private String CRL;
}

View File

@ -0,0 +1,37 @@
package com.sunyard.chsm.param;
import lombok.Data;
/**
* @author zyh
* @since 2024/12/19
*/
@Data
public class CertExinfoResp {
// /*
// 密钥用法
// */
// private String SubjectDN;
/*
签名算法OID
*/
private String SigAlgOID;
/*
证书签名
*/
private String Signature;
// /*
// 证书约束
// */
// private String SubjectType;
}

View File

@ -0,0 +1,55 @@
package com.sunyard.chsm.param;
import lombok.Data;
/**
* @author zyh
* @since 2024/12/19
*/
@Data
public class CertInfoResp {
/*
证书主题
*/
private String SubjectDN;
/*
证书颁发者
*/
private String IssuerDN;
/*
证书序列号
*/
private String SerialNumber;
/*
证书有效期
*/
private String NotBefore;
/*
证书过期时间
*/
private String NotAfter;
/*
证书签名算法
*/
private String SigAlgName;
/*
证书公钥 hex格式
*/
private String PublicKey;
/*
证书版本
*/
private int Version;
}

View File

@ -0,0 +1,26 @@
package com.sunyard.chsm.param;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @author liulu
* @since 2024/12/27
*/
@Data
public class ExportCertReq {
/**
* 证书的主题
*/
@NotBlank(message = "证书主题不能为空")
private String subject;
/**
* 证书类型: encrypt_decrypt 加密证书, sign_verify 签名证书
*/
@NotBlank(message = "证书类型不能为空")
private String certType;
}

View File

@ -0,0 +1,15 @@
package com.sunyard.chsm.param;
import lombok.Data;
/**
* @author liulu
* @since 2024/12/27
*/
@Data
public class ExportCertResp {
// 证书内容, base64编码
private String certText;
}

View File

@ -0,0 +1,18 @@
package com.sunyard.chsm.param;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author liulu
* @since 2024/12/27
*/
@Data
public class ExportPubKeyReq {
// 密钥ID
@NotNull(message = "密钥ID不能为空")
private Long keyId;
}

View File

@ -0,0 +1,21 @@
package com.sunyard.chsm.param;
import lombok.Data;
/**
* @author liulu
* @since 2024/12/27
*/
@Data
public class ExportPubKeyResp {
// 密钥ID
private Long keyId;
// 密钥索引
private String keyIndex;
// 公钥:x+y, base64编码
private String pubKey;
}

View File

@ -0,0 +1,23 @@
package com.sunyard.chsm.param;
import lombok.Data;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
/**
* @author liulu
* @since 2024/12/25
*/
@Data
public class GenRandomReq {
// 随机数长度, 最大10240
@NotNull(message = "随机数长度不能为空")
@Min(value = 1L, message = "随机数长度最小为1")
@Max(value = 1024L, message = "随机数长度最大为1024")
private Integer length;
}

View File

@ -0,0 +1,16 @@
package com.sunyard.chsm.param;
import lombok.Data;
/**
* @author liulu
* @since 2024/12/25
*/
@Data
public class GenRandomResp {
// 随机数, base64编码
private String random;
}

View File

@ -0,0 +1,23 @@
package com.sunyard.chsm.param;
import com.sunyard.chsm.enums.HashAlg;
import lombok.Data;
@Data
public class HashReq {
// 明文,使用Base64编码
private String plainData;
// 句柄, 多包计算Init后返回
private String handle;
// Hash算法, 默认SM3
private HashAlg alg = HashAlg.SM3;
// 公钥,使用Base64编码
private String pubKey;
// userId,使用Base64编码
private String userId;
}

View File

@ -0,0 +1,15 @@
package com.sunyard.chsm.param;
import lombok.Data;
@Data
public class HashResp {
// 句柄, 多包计算Init后返回
private String handle;
// mac值,使用Base64编码
private String hash;
}

View File

@ -0,0 +1,45 @@
package com.sunyard.chsm.param;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* @author liulu
* @since 2024/12/27
*/
@Data
public class ImportCertReq {
/**
* 密钥算法 目前支持: 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;
}

View File

@ -0,0 +1,32 @@
package com.sunyard.chsm.param;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
/**
* @author liulu
* @since 2024/12/17
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class KeyCreateReq {
/**
* 密钥模版编码
*/
@NotNull(message = "密钥模版不能为空")
private String keyTemplateCode;
/**
* 生成数量
*/
@NotNull(message = "生成数量不能为空")
@Max(value = 100, message = "一次最多生成100个密钥")
private Integer genNumber;
}

View File

@ -0,0 +1,32 @@
package com.sunyard.chsm.param;
import com.sunyard.chsm.enums.KeyCategory;
import com.sunyard.chsm.enums.KeyStatus;
import lombok.Data;
/**
* @author liulu
* @since 2024/12/17
*/
@Data
public class KeyInfoQuery {
/**
* 当前页
*/
private int pageNumber = 1;
/**
* 每页大小
*/
private int pageSize = 10;
/**
* 密钥状态
*/
private KeyStatus status;
/**
* 密钥类型
*/
private KeyCategory keyType;
}

View File

@ -0,0 +1,56 @@
package com.sunyard.chsm.param;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* @author liulu
* @since 2024/12/17
*/
@Data
public class KeyInfoResp {
/**
* 密钥ID
*/
private Long KeyId;
/**
* 密钥算法
*/
private String keyAlg;
/**
* 密钥类型
*/
private String keyType;
private String keyTypeText;
/**
* 密钥用途
*/
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;
}

Some files were not shown because too many files have changed in this diff Show More