设备激活

This commit is contained in:
xydkj 2026-05-14 15:31:40 +08:00
parent e7f9c1d183
commit 284b2bb9f6
16 changed files with 658 additions and 65 deletions

View File

@ -115,6 +115,13 @@
<artifactId>commons-csv</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>com.sunyard.cisd</groupId>
<artifactId>device-fingerprint-tool</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<build>

View File

@ -29,7 +29,6 @@ public class NetworkConfigController {
@Operation(summary = "获取所有网卡名", description = "获取系统中所有网卡名称")
@GetMapping("/card/eth-names")
@AuditedOperation(module = ModuleCode.DEVICE, action = ActionType.CREATE, summary = "获取所有网卡名")
public ApiResponse<List<String>> getEthDeviceNames() {
return ApiResponse.success(networkConfigService.getEthDeviceNames());
}

View File

@ -0,0 +1,15 @@
package com.cisd.tms.modules.mk.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "tms.mk.activate")
public class MasterKeyActivateProperties {
//todo 后续添加到配置文件
private String activateUrl = "http://127.0.0.1:8080/device/activate";
public String getActivateUrl() { return activateUrl; }
public void setActivateUrl(String activateUrl) { this.activateUrl = activateUrl; }
}

View File

@ -6,12 +6,9 @@ import com.cisd.tms.common.exception.BizException;
import com.cisd.tms.modules.auth.enums.AuthLevel;
import com.cisd.tms.modules.auth.enums.RoleCode;
import com.cisd.tms.modules.auth.security.RequireInternalAuth;
import com.cisd.tms.modules.mk.dto.MasterKeyBackupPacket;
import com.cisd.tms.modules.mk.dto.MasterKeyRecoverRequest;
import com.cisd.tms.modules.mk.dto.MasterKeyRecoverPacketsRequest;
import com.cisd.tms.modules.mk.dto.MasterKeyStateResult;
import com.cisd.tms.modules.mk.enums.MasterKeyStatus;
import com.cisd.tms.modules.mk.dto.*;
import com.cisd.tms.modules.mk.service.LmkService;
import com.cisd.tms.modules.mk.service.MasterKeyActivateService;
import com.cisd.tms.security.internal.ReplayProtected;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@ -34,6 +31,7 @@ import java.util.List;
public class LmkController {
private final LmkService lmkService;
private final MasterKeyActivateService masterKeyActivateService;
@PostMapping("/masterKey/init")
@Operation(summary = "初始化主密钥", description = "执行密码卡初始化识别、主密钥生成加载、内部密钥生成,并返回主密钥状态与校验值。")
@ -70,6 +68,21 @@ public class LmkController {
return ApiResponse.success();
}
@PostMapping("/masterKey/activate")
@Operation(summary = "设备激活")
public ApiResponse<Void> Activate(@RequestBody MasterKeyActivateRequest masterKeyActivateRequest) {
masterKeyActivateService.activate(masterKeyActivateRequest);
return ApiResponse.success();
}
@GetMapping("/masterKey/activate/info")
@Operation(summary = "查询设备激活信息", description = "返回设备型号、设备名称、设备序列号、设备版本、设备状态、异常原因。")
public ApiResponse<MasterKeyActivateInfoResponse> getMasterKeyActivateInfo() {
return ApiResponse.success(masterKeyActivateService.getMasterKeyActivateInfo());
}
private void validatePacketIndexes(List<MasterKeyBackupPacket> packets) {
if (packets == null || packets.size() != 2) {
throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "packets数量必须为2");

View File

@ -0,0 +1,22 @@
package com.cisd.tms.modules.mk.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "设备激活信息响应")
public class MasterKeyActivateInfoResponse {
@Schema(description = "设备型号", example = "SYD7108")
private String deviceModel;
@Schema(description = "设备名称", example = "跨境支付终端一体机")
private String deviceName;
@Schema(description = "设备版本", example = "1.0.0")
private String version;
@Schema(description = "设备序列号", example = "1234567890ABCDEF")
private String serialNumber;
@Schema(description = "设备状态:normal/abnormal", example = "normal")
private String deviceStatus;
@Schema(description = "异常原因")
private String exceptionMessage;
}

View File

@ -0,0 +1,16 @@
package com.cisd.tms.modules.mk.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "设备激活请求")
public class MasterKeyActivateRequest {
@Schema(description = "使用者ID", example = "1234567")
private String userId;
@Schema(description = "身份凭证", example = "1234567")
private String credential;
@Schema(description = "设备激活码", example = "1234567")
private String activationCode;
}

View File

@ -0,0 +1,26 @@
package com.cisd.tms.modules.mk.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("tms_master_key_activate")
public class MasterKeyActivateEntity {
private Long id;
private Boolean everInitialized;
private Boolean activationStatus;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,9 @@
package com.cisd.tms.modules.mk.mapper;
import com.cisd.tms.infrastructure.persistence.mapper.BaseMapperX;
import com.cisd.tms.modules.mk.entity.MasterKeyActivateEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MasterKeyActivateMapper extends BaseMapperX<MasterKeyActivateEntity> {
}

View File

@ -0,0 +1,10 @@
package com.cisd.tms.modules.mk.repository;
import com.cisd.tms.modules.mk.entity.MasterKeyActivateEntity;
import java.util.Optional;
public interface MasterKeyActivateRepository {
Optional<MasterKeyActivateEntity> find();
void update(MasterKeyActivateEntity masterKeyActivateEntity);
}

View File

@ -0,0 +1,53 @@
package com.cisd.tms.modules.mk.repository.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.cisd.tms.modules.mk.entity.MasterKeyActivateEntity;
import com.cisd.tms.modules.mk.mapper.MasterKeyActivateMapper;
import com.cisd.tms.modules.mk.repository.MasterKeyActivateRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public class MasterKeyActivateRepositoryImpl implements MasterKeyActivateRepository {
private final MasterKeyActivateMapper masterKeyActivateMapper;
public MasterKeyActivateRepositoryImpl(MasterKeyActivateMapper masterKeyActivateMapper) {
this.masterKeyActivateMapper = masterKeyActivateMapper;
}
@Override
public Optional<MasterKeyActivateEntity> find() {
LambdaQueryWrapper<MasterKeyActivateEntity> wrapper = new LambdaQueryWrapper<>();
wrapper.last("LIMIT 1");
MasterKeyActivateEntity entity = masterKeyActivateMapper.selectOne(wrapper);
return Optional.ofNullable(entity);
}
@Override
public void update(MasterKeyActivateEntity masterKeyActivateEntity) {
// LambdaQueryWrapper<MasterKeyActivateEntity> wrapper = new LambdaQueryWrapper<>();
//
// wrapper.last("LIMIT 1");
//
// MasterKeyActivateEntity entity = masterKeyActivateMapper.selectOne(wrapper);
//
// entity.setEverInitialized(everInitialized);
masterKeyActivateMapper.updateById(masterKeyActivateEntity);
}
// @Override
// public void updateActivateStatus(Boolean activateStatus) {
// LambdaQueryWrapper<MasterKeyActivateEntity> wrapper = new LambdaQueryWrapper<>();
//
// wrapper.last("LIMIT 1");
//
// MasterKeyActivateEntity entity = masterKeyActivateMapper.selectOne(wrapper);
//
// entity.setEverInitialized(activateStatus);
// masterKeyActivateMapper.updateById(entity);
// }
}

View File

@ -0,0 +1,11 @@
package com.cisd.tms.modules.mk.service;
import com.cisd.tms.modules.mk.dto.MasterKeyActivateInfoResponse;
import com.cisd.tms.modules.mk.dto.MasterKeyActivateRequest;
public interface MasterKeyActivateService {
void activate(MasterKeyActivateRequest masterKeyActivateRequest);
MasterKeyActivateInfoResponse getMasterKeyActivateInfo();
}

View File

@ -4,39 +4,26 @@ import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.common.exception.BizException;
import com.cisd.tms.common.util.DivisionUtils;
import com.cisd.tms.integration.crypto.pcie.Gm0018AlgorithmIds;
import com.cisd.tms.integration.crypto.pcie.PcieUserKeyType;
import com.cisd.tms.integration.crypto.pcie.jna.EccRefPublicKey;
import com.cisd.tms.integration.crypto.pcie.jna.EccSignature;
import com.cisd.tms.integration.crypto.pcie.model.*;
import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService;
import com.cisd.tms.modules.auth.service.AuthSecurityResetService;
import com.cisd.tms.modules.mk.common.LMKConstant;
import com.cisd.tms.modules.mk.config.MasterKeyInitProperties;
import com.cisd.tms.modules.mk.dto.MasterKeyBackupMaterial;
import com.cisd.tms.modules.mk.dto.MasterKeyBackupPacket;
import com.cisd.tms.modules.mk.dto.MasterKeyPacketIkPart;
import com.cisd.tms.modules.mk.dto.MasterKeyPacketLmkPart;
import com.cisd.tms.modules.mk.dto.MasterKeyPacketUserKeyPart;
import com.cisd.tms.modules.mk.dto.MasterKeyStateResult;
import com.cisd.tms.modules.mk.dto.*;
import com.cisd.tms.modules.mk.entity.MasterKeyActivateEntity;
import com.cisd.tms.modules.mk.enums.IKEnums;
import com.cisd.tms.modules.mk.enums.MasterKeyStatus;
import com.cisd.tms.modules.mk.repository.MasterKeyActivateRepository;
import com.cisd.tms.modules.mk.service.LmkService;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Comparator;
import java.util.HashSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* 主密钥生命周期服务实现
*/
@ -70,20 +57,23 @@ public class LmkServiceImpl implements LmkService {
private final AuthSecurityResetService authSecurityResetService;
private final Object backupRoundLock = new Object();
private CachedBackupRound cachedBackupRound;
private final MasterKeyActivateRepository masterKeyActivateRepository;
@Autowired
public LmkServiceImpl(
PcieCryptoService pcieCryptoService,
MasterKeyInitProperties masterKeyInitProperties,
AuthSecurityResetService authSecurityResetService
AuthSecurityResetService authSecurityResetService,
MasterKeyActivateRepository masterKeyActivateRepository
) {
this.pcieCryptoService = pcieCryptoService;
this.masterKeyInitProperties = masterKeyInitProperties;
this.authSecurityResetService = authSecurityResetService;
this.masterKeyActivateRepository = masterKeyActivateRepository;
}
public LmkServiceImpl(PcieCryptoService pcieCryptoService, MasterKeyInitProperties masterKeyInitProperties) {
this(pcieCryptoService, masterKeyInitProperties, () -> { });
public LmkServiceImpl(PcieCryptoService pcieCryptoService, MasterKeyInitProperties masterKeyInitProperties, MasterKeyActivateRepository masterKeyActivateRepository) {
this(pcieCryptoService, masterKeyInitProperties, () -> { }, masterKeyActivateRepository);
}
@Override
@ -91,6 +81,17 @@ public class LmkServiceImpl implements LmkService {
if (pcieCryptoService.checkLmk()) {
throw new BizException(ErrorCode.CONFLICT.getCode(), "主密钥已存在,拒绝重新初始化");
}
MasterKeyActivateEntity masterKeyActivateEntity = masterKeyActivateRepository.find().
orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "激活记录不存在"));
boolean everInitialized = masterKeyActivateEntity.getEverInitialized();
boolean activationStatus = masterKeyActivateEntity.getActivationStatus();
if (!everInitialized) {
if (!activationStatus) {
throw new BizException(ErrorCode.FORBIDDEN.getCode(), "需要先进行激活");
}
}
// 密钥体系一旦重建之前导出的两份备份包就全部失效必须清空当前轮次缓存
clearCachedBackupRound();
byte[] oldPin = decodeConfiguredPin("tms.mk.init-identify.old-pin-base64", masterKeyInitProperties.getOldPinBase64());
@ -100,6 +101,10 @@ public class LmkServiceImpl implements LmkService {
result.setStatus(true);
result.setSeedMac(Hex.toHexString(pcieCryptoService.initializeMasterKey(oldPin, newPin)));
authSecurityResetService.resetAfterMasterKeyInitialized();
masterKeyActivateEntity.setActivationStatus(false);
masterKeyActivateEntity.setEverInitialized(false);
masterKeyActivateRepository.update(masterKeyActivateEntity);
return result;
}

View File

@ -0,0 +1,385 @@
package com.cisd.tms.modules.mk.service.impl;
import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.common.exception.BizException;
import com.cisd.tms.integration.crypto.pcie.model.DeviceInfoResult;
import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService;
import com.cisd.tms.modules.device.config.DeviceProfileProperties;
import com.cisd.tms.modules.device.dto.DeviceInfoResponse;
import com.cisd.tms.modules.device.entity.DeviceSoftwareVersionEntity;
import com.cisd.tms.modules.device.repository.DeviceSoftwareVersionRepository;
import com.cisd.tms.modules.device.service.DeviceService;
import com.cisd.tms.modules.mk.config.MasterKeyActivateProperties;
import com.cisd.tms.modules.mk.dto.MasterKeyActivateInfoResponse;
import com.cisd.tms.modules.mk.dto.MasterKeyActivateRequest;
import com.cisd.tms.modules.mk.entity.MasterKeyActivateEntity;
import com.cisd.tms.modules.mk.repository.MasterKeyActivateRepository;
import com.cisd.tms.modules.mk.service.MasterKeyActivateService;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sunyard.cisd.DeviceFingerprint;
import com.sunyard.cisd.DeviceFingerprintService;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.bouncycastle.crypto.CipherParameters;
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.crypto.params.ParametersWithRandom;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.interfaces.ECPrivateKey;
import org.bouncycastle.jce.interfaces.ECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.springframework.stereotype.Service;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Base64;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class MasterKeyActivateServiceImpl implements MasterKeyActivateService {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
private final PcieCryptoService pcieCryptoService;
private final MasterKeyActivateProperties masterKeyActivateProperties;
private final DeviceSoftwareVersionRepository deviceSoftwareVersionRepository;
private final DeviceProfileProperties deviceProfileProperties;
private final DeviceService deviceService;
private final MasterKeyActivateRepository masterKeyActivateRepository;
static {
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}
}
@Override
public MasterKeyActivateInfoResponse getMasterKeyActivateInfo() {
DeviceInfoResponse deviceInfoResponse = deviceService.info();
DeviceInfoResult deviceInfo = pcieCryptoService.getDeviceInfo();
MasterKeyActivateInfoResponse masterKeyActivateInfo = new MasterKeyActivateInfoResponse();
if (deviceInfoResponse.getMasterKeyStatus()) {
masterKeyActivateInfo.setDeviceStatus("正常");
} else {
masterKeyActivateInfo.setDeviceStatus("异常");
masterKeyActivateInfo.setExceptionMessage("密钥异常");
}
if (deviceInfo == null){
masterKeyActivateInfo.setSerialNumber("");
} else {
masterKeyActivateInfo.setSerialNumber(deviceInfo.getDeviceSerial());
}
masterKeyActivateInfo.setDeviceModel(trim(deviceProfileProperties.getModel()));
masterKeyActivateInfo.setDeviceName(trim(deviceProfileProperties.getName()));
masterKeyActivateInfo.setVersion(loadTmsVersion());
return masterKeyActivateInfo;
}
@Override
public void activate(MasterKeyActivateRequest masterKeyActivateRequest){
MasterKeyActivateEntity masterKeyActivateEntity = masterKeyActivateRepository.find().orElseThrow(() -> new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "激活记录不存在"));
Boolean everInitialized = masterKeyActivateEntity.getEverInitialized();
if (everInitialized) {
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "从未进行初始化密钥, 无需激活");
}
if (masterKeyActivateRequest.getUserId() == null){
throw new IllegalArgumentException("使用者ID不能为空");
}
if (masterKeyActivateRequest.getCredential() == null){
throw new IllegalArgumentException("身份凭证不能为空");
}
if (masterKeyActivateRequest.getActivationCode() == null){
throw new IllegalArgumentException("设备激活码不能为空");
}
try {
DeviceFingerprintService deviceFingerprintService = new DeviceFingerprintService();
DeviceFingerprint deviceFingerprint = deviceFingerprintService.getDeviceFingerprint();
String deviceSerial = deviceFingerprint.getDeviceSerialNumber();
String motherboardSerial = deviceFingerprint.getMotherboardSerialNumber();
String cpuModel = deviceFingerprint.getCpuModel();
String cpuSerial = deviceFingerprint.getCpuSerialNumber();
String memorySize = deviceFingerprint.getMemorySize();
String macAddress = deviceFingerprint.getFirstNetworkMacAddress();
String diskSerial = deviceFingerprint.getHardDiskSerialNumber();
String timestamp = String.valueOf(System.currentTimeMillis());
String nonce = randomHex(16);
//todo 后面读取文件
MasterKeyActivateServiceImpl.Sm2KeyPair sm2KeyPair = generateSm2KeyPair();
String deviceFingerprintSource =
deviceSerial +
motherboardSerial +
cpuModel +
cpuSerial +
memorySize +
macAddress +
diskSerial;
String deviceFingerprintSm2 = sm2SignBase64(
deviceFingerprintSource.getBytes(StandardCharsets.UTF_8),
sm2KeyPair.privateKey
);
String userId = trim(masterKeyActivateRequest.getUserId());
String credential = trim(masterKeyActivateRequest.getCredential());
String activationCode = trim(masterKeyActivateRequest.getActivationCode());
String hashSource = userId + credential + activationCode + deviceSerial +
deviceFingerprint + timestamp + nonce;
String hash = sm3Hex(hashSource);
Params params = new Params();
params.userId = userId;
params.credential = credential;
params.activationCode = activationCode;
params.deviceSerial = deviceSerial;
params.deviceFingerprint = deviceFingerprintSm2;
params.timestamp = timestamp;
params.nonce = nonce;
ActivateRequest request = new ActivateRequest();
request.params = params;
request.hash = hash;
//todo 先写死后续改实际的激活服务
masterKeyActivateEntity.setActivationStatus(true);
masterKeyActivateRepository.update(masterKeyActivateEntity);
// String json = OBJECT_MAPPER.writeValueAsString(request);
//// System.out.print(json);
// HttpRequest httpRequest = HttpRequest.newBuilder()
// .uri(URI.create(masterKeyActivateProperties.getActivateUrl()))
// .header("Content-Type", "application/json;charset=UTF-8")
// .header("Accept", "application/json")
// .POST(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8))
// .build();
//
// HttpClient client = HttpClient.newHttpClient();
//
// HttpResponse<String> response = client.send(
// httpRequest,
// HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)
// );
//
// ServerResponse result =
// OBJECT_MAPPER.readValue(response.body(), ServerResponse.class);
//
// if ("ok".equals(result.status)) {
// MasterKeyActivateData dataResponse = result.getData();
// String inputHash = dataResponse.getInputHash();
// String deviceActivationPublicKey = dataResponse.getDeviceActivationPublicKey();
// String activationResult = dataResponse.getActivationResult();
//
// } else {
// throw new Exception(result.getMsg());
// }
} catch (Exception e) {
masterKeyActivateEntity.setActivationStatus(false);
masterKeyActivateRepository.update(masterKeyActivateEntity);
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "激活失败:" + e.getMessage());
}
}
private String sm2SignBase64(
byte[] data,
ECPrivateKeyParameters privateKey
) throws Exception {
SM2Signer signer = new SM2Signer(new SM3Digest());
CipherParameters parameters = new ParametersWithRandom(privateKey, SECURE_RANDOM);
signer.init(true, parameters);
signer.update(data, 0, data.length);
byte[] signature = signer.generateSignature();
return Base64.getEncoder().encodeToString(signature);
}
private Sm2KeyPair generateSm2KeyPair(){
try{
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC");
ECParameterSpec sm2Spec = ECNamedCurveTable.getParameterSpec("sm2p256v1");
keyPairGenerator.initialize(sm2Spec, SECURE_RANDOM);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
BigInteger d = privateKey.getD();
ECDomainParameters domainParameters = new ECDomainParameters(
sm2Spec.getCurve(),
sm2Spec.getG(),
sm2Spec.getN(),
sm2Spec.getH()
);
ECPrivateKeyParameters privateKeyParameters =
new ECPrivateKeyParameters(d, domainParameters);
ECPublicKeyParameters publicParameters =
new ECPublicKeyParameters(publicKey.getQ(), domainParameters);
Sm2KeyPair sm2KeyPair = new Sm2KeyPair();
sm2KeyPair.privateKey = privateKeyParameters;
sm2KeyPair.publicKeyParameters = publicParameters;
sm2KeyPair.publicKey = publicKey;
return sm2KeyPair;
} catch (Exception e){
throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "sm2密钥对生成失败");
}
}
private String sm3Hex(String input) {
byte[] data = input.getBytes(StandardCharsets.UTF_8);
SM3Digest digest = new SM3Digest();
digest.update(data, 0, data.length);
byte[] result = new byte[digest.getDigestSize()];
digest.doFinal(result, 0);
return bytesToHex(result);
}
private String randomHex(int byteLength) {
byte[] bytes = new byte[byteLength];
SECURE_RANDOM.nextBytes(bytes);
return bytesToHex(bytes);
}
private String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
}
private boolean sm2VerifyActivationResult(
String inputHash,
String activationResult,
ECPublicKeyParameters publicKeyParameters
) {
try {
byte[] signatureBytes = parseP1Signature(activationResult);
SM2Signer signer = new SM2Signer(new SM3Digest());
signer.init(false, publicKeyParameters);
byte[] data = inputHash.getBytes(StandardCharsets.UTF_8);
signer.update(data, 0, data.length);
return signer.verifySignature(signatureBytes);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
private byte[] parseP1Signature(String activationResult) {
String value = activationResult.trim();
if (value.startsWith("P1:")) {
value = value.substring(3);
}
return Base64.getDecoder().decode(value);
}
public static class ActivateRequest {
public Params params;
public String hash;
}
public static class Params {
public String userId;
public String credential;
public String activationCode;
public String deviceSerial;
public String deviceFingerprint;
public String timestamp;
public String nonce;
}
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
public static class ServerResponse {
public String status;
public String msg;
public Integer code;
public MasterKeyActivateData data;
}
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
public static class MasterKeyActivateData {
public String inputHash;
public String deviceActivationPublicKey;
public String activationResult;
}
private static class Sm2KeyPair {
private ECPrivateKeyParameters privateKey;
private ECPublicKeyParameters publicKeyParameters;
private ECPublicKey publicKey;
}
private String loadTmsVersion() {
Optional<DeviceSoftwareVersionEntity> version = deviceSoftwareVersionRepository.findByComponentCode("TMS");
return version.map(DeviceSoftwareVersionEntity::getCurrentVersion).map(MasterKeyActivateServiceImpl::trim).orElse("");
}
private static String trim(String value) {
return value == null ? "" : value.trim();
}
}

View File

@ -714,3 +714,20 @@ CREATE TABLE IF NOT EXISTS tms_backup_config (
sign_data varchar(255) NOT NULL COMMENT '其他字段的签名值',
PRIMARY KEY (`id`)
)
CREATE TABLE tms_master_key_activate (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
ever_initialized BOOLEAN NOT NULL,
activation_status BOOLEAN NOT NULL,
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)
);
INSERT INTO tms_master_key_activate (
ever_initialized,
activation_status
) VALUES (true,false);

View File

@ -3,6 +3,7 @@ package com.cisd.tms.modules.mk.controller;
import com.cisd.tms.common.exception.GlobalExceptionHandler;
import com.cisd.tms.modules.mk.dto.MasterKeyStateResult;
import com.cisd.tms.modules.mk.service.LmkService;
import com.cisd.tms.modules.mk.service.MasterKeyActivateService;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.http.MediaType;
@ -18,11 +19,12 @@ class LmkControllerTest {
@Test
void shouldReturnRecoveredMasterKeySeedMac() throws Exception {
LmkService lmkService = Mockito.mock(LmkService.class);
MasterKeyActivateService masterKeyActivateService = Mockito.mock(MasterKeyActivateService.class);
MasterKeyStateResult result = new MasterKeyStateResult();
result.setStatus(true);
result.setSeedMac("1112131415161718");
Mockito.when(lmkService.recoverKeyPackets(Mockito.any())).thenReturn(result);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new LmkController(lmkService))
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new LmkController(lmkService, masterKeyActivateService))
.setControllerAdvice(new GlobalExceptionHandler())
.build();
@ -62,7 +64,8 @@ class LmkControllerTest {
@Test
void shouldRejectRecoverPacketsWithPacketIndexesOtherThanOneAndTwo() throws Exception {
LmkService lmkService = Mockito.mock(LmkService.class);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new LmkController(lmkService))
MasterKeyActivateService masterKeyActivateService = Mockito.mock(MasterKeyActivateService.class);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new LmkController(lmkService, masterKeyActivateService))
.setControllerAdvice(new GlobalExceptionHandler())
.build();

View File

@ -1,51 +1,41 @@
package com.cisd.tms.modules.mk.service;
import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.common.exception.BizException;
import com.cisd.tms.integration.crypto.pcie.Gm0018AlgorithmIds;
import com.cisd.tms.integration.crypto.pcie.jna.EccRefPublicKey;
import com.cisd.tms.integration.crypto.pcie.jna.SdfeIkComponent;
import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult;
import com.cisd.tms.integration.crypto.pcie.model.DigestRequest;
import com.cisd.tms.integration.crypto.pcie.model.DeviceStatusResult;
import com.cisd.tms.integration.crypto.pcie.model.EccInternalSignRequest;
import com.cisd.tms.integration.crypto.pcie.model.EccInternalVerifyRequest;
import com.cisd.tms.integration.crypto.pcie.model.IkSignRequest;
import com.cisd.tms.integration.crypto.pcie.model.IkVerifyRequest;
import com.cisd.tms.integration.crypto.pcie.model.MasterKeyRecoveryRequest;
import com.cisd.tms.integration.crypto.pcie.model.MasterKeyRecoveryResult;
import com.cisd.tms.integration.crypto.pcie.model.RecoverUserKeyRequest;
import com.cisd.tms.integration.crypto.pcie.model.*;
import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService;
import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.common.exception.BizException;
import com.cisd.tms.modules.auth.service.AuthSecurityResetService;
import com.cisd.tms.modules.mk.config.MasterKeyInitProperties;
import com.cisd.tms.modules.mk.dto.MasterKeyBackupPacket;
import com.cisd.tms.modules.mk.dto.MasterKeyPacketIkPart;
import com.cisd.tms.modules.mk.dto.MasterKeyPacketLmkPart;
import com.cisd.tms.modules.mk.dto.MasterKeyPacketUserKeyPart;
import com.cisd.tms.modules.mk.dto.MasterKeyStateResult;
import com.cisd.tms.modules.mk.dto.*;
import com.cisd.tms.modules.mk.repository.MasterKeyActivateRepository;
import com.cisd.tms.modules.mk.service.impl.LmkServiceImpl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mockito;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
class LmkServiceTest {
@Test
void shouldInitMasterKeyWithConfiguredPinsAndReturnSeedMac() {
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
MasterKeyActivateRepository masterKeyActivateRepository = Mockito.mock(MasterKeyActivateRepository.class);
MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE=");
Mockito.when(pcieCryptoService.initializeMasterKey(Mockito.any(), Mockito.any())).thenReturn(new byte[] {0x01, 0x23, 0x45, 0x67});
LmkService service = new LmkServiceImpl(pcieCryptoService, properties);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties, masterKeyActivateRepository);
MasterKeyStateResult result = service.initMasterKey();
@ -61,11 +51,12 @@ class LmkServiceTest {
@Test
void shouldResetAuthSecurityStateAfterMasterKeyInitializationSucceeds() {
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
MasterKeyActivateRepository masterKeyActivateRepository = Mockito.mock(MasterKeyActivateRepository.class);
AuthSecurityResetService authSecurityResetService = Mockito.mock(AuthSecurityResetService.class);
MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE=");
Mockito.when(pcieCryptoService.initializeMasterKey(Mockito.any(), Mockito.any())).thenReturn(new byte[] {0x01, 0x23});
LmkService service = new LmkServiceImpl(pcieCryptoService, properties, authSecurityResetService);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties, authSecurityResetService, masterKeyActivateRepository);
service.initMasterKey();
@ -77,11 +68,12 @@ class LmkServiceTest {
@Test
void shouldPropagateInitMasterKeyFailure() {
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
MasterKeyActivateRepository masterKeyActivateRepository = Mockito.mock(MasterKeyActivateRepository.class);
MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE=");
Mockito.when(pcieCryptoService.initializeMasterKey(Mockito.any(), Mockito.any()))
.thenThrow(new IllegalStateException("主密钥初始化失败"));
LmkService service = new LmkServiceImpl(pcieCryptoService, properties);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties, masterKeyActivateRepository);
IllegalStateException exception = Assertions.assertThrows(IllegalStateException.class, service::initMasterKey);
@ -91,10 +83,11 @@ class LmkServiceTest {
@Test
void shouldRejectInitMasterKeyWhenMasterKeyAlreadyExists() {
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
MasterKeyActivateRepository masterKeyActivateRepository = Mockito.mock(MasterKeyActivateRepository.class);
MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE=");
Mockito.when(pcieCryptoService.checkLmk()).thenReturn(true);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties, masterKeyActivateRepository);
BizException exception = Assertions.assertThrows(BizException.class, service::initMasterKey);
@ -106,8 +99,9 @@ class LmkServiceTest {
@Test
void shouldDestroyMasterKeyAfterDeletingUserKey() {
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
MasterKeyActivateRepository masterKeyActivateRepository = Mockito.mock(MasterKeyActivateRepository.class);
MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE=");
LmkService service = new LmkServiceImpl(pcieCryptoService, properties);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties, masterKeyActivateRepository);
service.destroyMasterKey();
@ -121,10 +115,11 @@ class LmkServiceTest {
@Test
void shouldReturnStatusFalseWhenMasterKeyDoesNotExist() {
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
MasterKeyActivateRepository masterKeyActivateRepository = Mockito.mock(MasterKeyActivateRepository.class);
MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE=");
Mockito.when(pcieCryptoService.checkLmk()).thenReturn(false);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties, masterKeyActivateRepository);
MasterKeyStateResult result = service.queryMasterKeyStatus();
@ -136,11 +131,12 @@ class LmkServiceTest {
@Test
void shouldReturnStatusTrueAndSeedMacWhenMasterKeyExists() {
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
MasterKeyActivateRepository masterKeyActivateRepository = Mockito.mock(MasterKeyActivateRepository.class);
MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE=");
Mockito.when(pcieCryptoService.checkLmk()).thenReturn(true);
Mockito.when(pcieCryptoService.exportLmkSeedMac()).thenReturn(new byte[] {(byte) 0xAA, (byte) 0xBB});
LmkService service = new LmkServiceImpl(pcieCryptoService, properties);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties, masterKeyActivateRepository);
MasterKeyStateResult result = service.queryMasterKeyStatus();
@ -151,13 +147,14 @@ class LmkServiceTest {
@Test
void shouldExportIkPublicKeyHexViaDedicatedIkApi() {
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
MasterKeyActivateRepository masterKeyActivateRepository = Mockito.mock(MasterKeyActivateRepository.class);
MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE=");
BackupDataResult publicKey = new BackupDataResult();
publicKey.setLength(new EccRefPublicKey().size());
publicKey.setData(sequentialBytes(new EccRefPublicKey().size(), 0x31));
Mockito.when(pcieCryptoService.exportIkPublicKey(2)).thenReturn(publicKey);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties, masterKeyActivateRepository);
String result = service.exportIkPublicKeyHex();
@ -169,6 +166,7 @@ class LmkServiceTest {
@Test
void shouldBuildBackupPacketForPacketContract() throws Exception {
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
MasterKeyActivateRepository masterKeyActivateRepository = Mockito.mock(MasterKeyActivateRepository.class);
MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE=");
BackupDataResult lmkBackup = new BackupDataResult();
lmkBackup.setLength(20);
@ -196,7 +194,7 @@ class LmkServiceTest {
Mockito.when(pcieCryptoService.backupUserKey(1, 0, 120)).thenReturn(userSignBackup);
Mockito.when(pcieCryptoService.backupUserKey(1, 1, 120)).thenReturn(userEncBackup);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties, masterKeyActivateRepository);
MasterKeyBackupPacket packet = service.buildBackupPacket(2);
int componentBytesLength = 3 * 64;
@ -226,6 +224,7 @@ class LmkServiceTest {
@Test
void shouldReuseSingleBackupRoundAcrossTwoPacketBuilds() throws Exception {
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
MasterKeyActivateRepository masterKeyActivateRepository = Mockito.mock(MasterKeyActivateRepository.class);
MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE=");
BackupDataResult lmkBackupRound1 = new BackupDataResult();
@ -279,7 +278,7 @@ class LmkServiceTest {
Mockito.when(pcieCryptoService.backupUserKey(1, 0, 120)).thenReturn(userSignRound1, userSignRound2);
Mockito.when(pcieCryptoService.backupUserKey(1, 1, 120)).thenReturn(userEncRound1, userEncRound2);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties, masterKeyActivateRepository);
MasterKeyBackupPacket packet1 = service.buildBackupPacket(1);
MasterKeyBackupPacket packet2 = service.buildBackupPacket(2);
@ -334,12 +333,13 @@ class LmkServiceTest {
@Test
void shouldRecoverAllKeyPacketsInOrder() {
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
MasterKeyActivateRepository masterKeyActivateRepository = Mockito.mock(MasterKeyActivateRepository.class);
MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE=");
MasterKeyRecoveryResult recoveryResult = new MasterKeyRecoveryResult();
recoveryResult.setLmkSeedMac(hex("1112131415161718"));
recoveryResult.setDeviceStatus(new DeviceStatusResult());
Mockito.when(pcieCryptoService.recoverMasterKeyMaterial(Mockito.any())).thenReturn(recoveryResult);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties, masterKeyActivateRepository);
MasterKeyBackupPacket packet1 = recoverPacketFixture(1);
MasterKeyBackupPacket packet2 = recoverPacketFixture(2);
@ -372,6 +372,7 @@ class LmkServiceTest {
@Test
void shouldHashRawDataBeforeInternalSign() {
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
MasterKeyActivateRepository masterKeyActivateRepository = Mockito.mock(MasterKeyActivateRepository.class);
MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE=");
BackupDataResult digestResult = new BackupDataResult();
digestResult.setLength(32);
@ -387,7 +388,7 @@ class LmkServiceTest {
Mockito.when(pcieCryptoService.digest(Mockito.any())).thenReturn(digestResult);
Mockito.when(pcieCryptoService.signIk(Mockito.any())).thenReturn(signResult);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties, masterKeyActivateRepository);
String signatureHex = service.signIk("1223");
@ -406,6 +407,7 @@ class LmkServiceTest {
@Test
void shouldHashRawDataBeforeInternalVerify() {
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
MasterKeyActivateRepository masterKeyActivateRepository = Mockito.mock(MasterKeyActivateRepository.class);
MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE=");
BackupDataResult digestResult = new BackupDataResult();
digestResult.setLength(32);
@ -417,7 +419,7 @@ class LmkServiceTest {
});
Mockito.when(pcieCryptoService.digest(Mockito.any())).thenReturn(digestResult);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties);
LmkService service = new LmkServiceImpl(pcieCryptoService, properties, masterKeyActivateRepository);
service.verifyIk("1223", "aabb");