diff --git a/chsm-common/src/main/java/com/sunyard/chsm/constant/ParamConfKeyConstant.java b/chsm-common/src/main/java/com/sunyard/chsm/constant/ParamConfKeyConstant.java index 053c73f..607876d 100644 --- a/chsm-common/src/main/java/com/sunyard/chsm/constant/ParamConfKeyConstant.java +++ b/chsm-common/src/main/java/com/sunyard/chsm/constant/ParamConfKeyConstant.java @@ -13,6 +13,7 @@ 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"; diff --git a/chsm-common/src/main/java/com/sunyard/chsm/model/dto/TmkStatus.java b/chsm-common/src/main/java/com/sunyard/chsm/model/dto/TmkStatus.java index 422df63..4eb4a3a 100644 --- a/chsm-common/src/main/java/com/sunyard/chsm/model/dto/TmkStatus.java +++ b/chsm-common/src/main/java/com/sunyard/chsm/model/dto/TmkStatus.java @@ -17,6 +17,9 @@ public class TmkStatus { * 主密钥是否初始化 */ private boolean tmkInit; - + /** + * 主密钥校验值 + */ + private String checkValue; } diff --git a/chsm-common/src/main/java/com/sunyard/chsm/service/TmkService.java b/chsm-common/src/main/java/com/sunyard/chsm/service/TmkService.java index c835649..5074ba6 100644 --- a/chsm-common/src/main/java/com/sunyard/chsm/service/TmkService.java +++ b/chsm-common/src/main/java/com/sunyard/chsm/service/TmkService.java @@ -14,9 +14,16 @@ 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.util.Assert; @@ -59,6 +66,8 @@ public class TmkService { 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); @@ -72,7 +81,7 @@ public class TmkService { up.setTmkStatus(DeviceTmkStatus.finished.name()); spDeviceMapper.updateById(up); } - updateTmkInit(true); + updateTmkInit(true, hash); } @@ -82,6 +91,10 @@ public class TmkService { 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); @@ -90,6 +103,88 @@ public class TmkService { 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 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())); + } + + 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) { @@ -209,6 +304,7 @@ public class TmkService { } if (isEnableSoftDevice()) { device = new Device(); + device.setId(0L); device.setManufacturerModel(BouncyCastleProvider.PROVIDER_NAME); device.setEncKeyIdx(1); device.setServiceIp("127.0.0.1"); @@ -241,7 +337,7 @@ public class TmkService { return conf != null && String.valueOf(true).equals(conf.getValue()); } - private void updateTmkInit(boolean value) { + private void updateTmkInit(boolean value, byte[] hash) { ParamConf conf = paramConfMapper.selectByKey(ParamConfKeyConstant.TMK_INIT); if (conf == null) { conf = new ParamConf(); @@ -253,6 +349,18 @@ public class TmkService { 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(conf); + } else { + check.setValue(CodecUtils.encodeHex(hash)); + paramConfMapper.updateById(conf); + } } public synchronized byte[] getSoftDeviceEncTmk() { diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCECUtils.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCECUtils.java index a35474b..0d8a9d3 100644 --- a/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCECUtils.java +++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/gm/BCECUtils.java @@ -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"; diff --git a/chsm-web-manage/src/main/java/com/sunyard/chsm/controller/TmkController.java b/chsm-web-manage/src/main/java/com/sunyard/chsm/controller/TmkController.java index 6cc9705..23d44fc 100644 --- a/chsm-web-manage/src/main/java/com/sunyard/chsm/controller/TmkController.java +++ b/chsm-web-manage/src/main/java/com/sunyard/chsm/controller/TmkController.java @@ -3,13 +3,19 @@ package com.sunyard.chsm.controller; import com.sunyard.chsm.model.R; import com.sunyard.chsm.model.dto.TmkStatus; import com.sunyard.chsm.service.TmkService; +import lombok.Data; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.Pair; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; /** * 主密钥管理 @@ -45,5 +51,63 @@ public class TmkController { return R.ok(); } + /** + * 备份主密钥 + */ + @PostMapping("/backup") + public R backup(@Valid @RequestBody TmkBackupReq req) { + String en = tmkService.backup(req.pubKey); + return R.data(en); + } + + /** + * 获取设备公钥 + */ + @GetMapping("/devicePubKey") + public R getDevicePubKey() { + Pair pair = tmkService.getDevicePubKey(); + DevicePubKey res = new DevicePubKey(); + res.deviceId = pair.getLeft(); + res.pubKey = pair.getRight(); + return R.data(res); + } + + /** + * 导入主密钥 + */ + @PostMapping("/import") + public R importTmk(@Valid @RequestBody TmkImportReq req) { + tmkService.importTmk(req.deviceId, req.encTmk); + return R.ok(); + } + + @Data + public static class DevicePubKey { + private Long deviceId; + private String pubKey; + } + + @Data + public static class TmkBackupReq { + /** + * ukey公钥 + */ + @NotEmpty(message = "公钥不能为空") + private String pubKey; + } + + @Data + public static class TmkImportReq { + /** + * 设备id + */ + @NotNull(message = "设备id不能为空") + private Long deviceId; + /** + * 加密主密钥 + */ + @NotEmpty(message = "加密主密钥不能为空") + private String encTmk; + } } diff --git a/doc/ssp_dm.sql b/doc/ssp_dm.sql index 44d9e2e..3f638de 100644 --- a/doc/ssp_dm.sql +++ b/doc/ssp_dm.sql @@ -180,7 +180,7 @@ CREATE TABLE "SC_PERMISSION"( -- 初始化数据 -INSERT INTO SC_PARAM_CONF (ITEM, KEY, VALUE, TYPE, STATUS, MEMO) VALUES (0, 'ipWhitelistSwitch', 'true', 'OBJECT', 0, null); +INSERT INTO SC_PARAM_CONF (ITEM, KEY, VALUE, TYPE, STATUS, MEMO) VALUES (0, 'ipWhitelistSwitch', 'false', 'OBJECT', 0, null); INSERT INTO SC_PARAM_CONF (ITEM, KEY, VALUE, TYPE, STATUS, MEMO) VALUES (1, 'communicateTimeOut', '30', 'OBJECT', 0, null); INSERT INTO SC_PARAM_CONF (ITEM, KEY, VALUE, TYPE, STATUS, MEMO) VALUES (1, 'heartDetectTime', '5', 'OBJECT', 0, null); INSERT INTO SC_PARAM_CONF (ITEM, KEY, VALUE, TYPE, STATUS, MEMO) VALUES (1, 'ftpUploadPath', '/app/upload', 'OBJECT', 0, null); @@ -216,7 +216,7 @@ INSERT INTO SC_DICT (ID, TYPE, TITLE, SCOPE, SORT_ORDER, DESCRIPTION) VALUES (42 INSERT INTO SC_DICT (ID, TYPE, TITLE, SCOPE, SORT_ORDER, DESCRIPTION) VALUES (76, 'Publickey_format', '公钥格式', 1, 0, null); INSERT INTO SC_DICT (ID, TYPE, TITLE, SCOPE, SORT_ORDER, DESCRIPTION) VALUES (81, 'version', '系统版本号', 1, 0, ''); -SET IDENTITY_INSERT SC_DICT_DATA ON +SET IDENTITY_INSERT SC_DICT_DATA ON; INSERT INTO SC_DICT_DATA (ID, DICT_ID, TITLE, VALUE, SORT_ORDER, STATUS, DESCRIPTION) VALUES (25, 76, 'DER', 'DER', 1, 0, null); INSERT INTO SC_DICT_DATA (ID, DICT_ID, TITLE, VALUE, SORT_ORDER, STATUS, DESCRIPTION) VALUES (26, 76, 'BASE64', 'BASE64', 2, 0, null); INSERT INTO SC_DICT_DATA (ID, DICT_ID, TITLE, VALUE, SORT_ORDER, STATUS, DESCRIPTION) VALUES (27, 38, '30秒', '30000', 1, 0, null);