diff --git a/pom.xml b/pom.xml index 4b97e1a..d9f6468 100644 --- a/pom.xml +++ b/pom.xml @@ -118,6 +118,13 @@ 1.10.0 + + cn.hutool + hutool-all + 5.8.26 + + + com.sunyard.cisd device-fingerprint-tool diff --git a/src/main/java/com/cisd/tms/TmsApplication.java b/src/main/java/com/cisd/tms/TmsApplication.java index fb73702..9764663 100644 --- a/src/main/java/com/cisd/tms/TmsApplication.java +++ b/src/main/java/com/cisd/tms/TmsApplication.java @@ -1,16 +1,32 @@ package com.cisd.tms; import com.cisd.tms.common.crypto.config.ConfigCryptoCli; -import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import com.cisd.tms.modules.log.entity.OperationAuditLogEntity; +import com.cisd.tms.modules.log.enums.ActionType; +import com.cisd.tms.modules.log.enums.AuthLevel; +import com.cisd.tms.modules.log.enums.AuditStatus; +import com.cisd.tms.modules.log.enums.ModuleCode; +import com.cisd.tms.modules.log.enums.OperationResult; +import com.cisd.tms.modules.log.enums.OperatorRoleCode; +import com.cisd.tms.modules.log.repository.OperationAuditLogRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.scheduling.annotation.EnableScheduling; +import java.time.LocalDateTime; +import java.util.UUID; + @SpringBootApplication @ConfigurationPropertiesScan @EnableScheduling public class TmsApplication { + private static final Logger log = LoggerFactory.getLogger(TmsApplication.class); + public static void main(String[] args) { if (ConfigCryptoCli.shouldHandle(args)) { int exitCode = ConfigCryptoCli.run(args, System.in, System.out, System.err); diff --git a/src/main/java/com/cisd/tms/infrastructure/persistence/mybatis/CommonMetaObjectHandler.java b/src/main/java/com/cisd/tms/infrastructure/persistence/mybatis/CommonMetaObjectHandler.java index 69d9f79..bb9f3e9 100644 --- a/src/main/java/com/cisd/tms/infrastructure/persistence/mybatis/CommonMetaObjectHandler.java +++ b/src/main/java/com/cisd/tms/infrastructure/persistence/mybatis/CommonMetaObjectHandler.java @@ -2,20 +2,44 @@ package com.cisd.tms.infrastructure.persistence.mybatis; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import java.time.LocalDateTime; + +import com.cisd.tms.modules.datamac.helper.Sm3MacCalculator; import org.apache.ibatis.reflection.MetaObject; +import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @Component +@Primary public class CommonMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { - this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); - this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); + if ( metaObject.hasSetter("createTime") ) { + this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); + } + + if ( metaObject.hasSetter("updateTime") ) { + this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); + } + + // 插入时,直接基于当前实体对象计算 MAC + if (metaObject.hasSetter("mac")) { + Object entity = metaObject.getOriginalObject(); + String mac = Sm3MacCalculator.calculateForEntity(entity); + this.strictInsertFill(metaObject, "mac", String.class, mac); + } } @Override public void updateFill(MetaObject metaObject) { - this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); + if ( metaObject.hasSetter("updateTime") ) { + this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); + } + // 插入时,直接基于当前实体对象计算 MAC + if (metaObject.hasSetter("mac")) { + Object entity = metaObject.getOriginalObject(); + String mac = Sm3MacCalculator.calculateForEntity(entity); + this.strictInsertFill(metaObject, "mac", String.class, mac); + } } } diff --git a/src/main/java/com/cisd/tms/modules/cert/entity/KeyEntity.java b/src/main/java/com/cisd/tms/modules/cert/entity/KeyEntity.java index 4afe9d2..e5bf231 100644 --- a/src/main/java/com/cisd/tms/modules/cert/entity/KeyEntity.java +++ b/src/main/java/com/cisd/tms/modules/cert/entity/KeyEntity.java @@ -1,5 +1,7 @@ package com.cisd.tms.modules.cert.entity; +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import com.cisd.tms.infrastructure.persistence.entity.BaseEntity; import lombok.Data; @@ -26,4 +28,8 @@ public class KeyEntity extends BaseEntity { private String allowIps; private String signData; private String createBy; + + // 标记此字段在插入和更新时都需要自动填充 + @TableField(fill = FieldFill.INSERT_UPDATE) + private String mac; } diff --git a/src/main/java/com/cisd/tms/modules/datamac/entity/DataMacEntity.java b/src/main/java/com/cisd/tms/modules/datamac/entity/DataMacEntity.java new file mode 100644 index 0000000..9611ab5 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/datamac/entity/DataMacEntity.java @@ -0,0 +1,12 @@ +package com.cisd.tms.modules.datamac.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.Data; + +@Data +public class DataMacEntity { + // 标记此字段在插入和更新时都需要自动填充 + @TableField(fill = FieldFill.INSERT_UPDATE) + private String mac; +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/datamac/hanlder/DataMacMetaObjectHandler.java b/src/main/java/com/cisd/tms/modules/datamac/hanlder/DataMacMetaObjectHandler.java new file mode 100644 index 0000000..a0038d9 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/datamac/hanlder/DataMacMetaObjectHandler.java @@ -0,0 +1,45 @@ +package com.cisd.tms.modules.datamac.hanlder; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.cisd.tms.modules.datamac.helper.Sm3MacCalculator; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +@Component +public class DataMacMetaObjectHandler implements MetaObjectHandler { + + @Override + public void insertFill(MetaObject metaObject) { + // 插入时,直接基于当前实体对象计算 MAC + Object entity = metaObject.getOriginalObject(); + String mac = Sm3MacCalculator.calculateForEntity(entity); + this.strictInsertFill(metaObject, "mac", String.class, mac); + } + + @Override + public void updateFill(MetaObject metaObject) { + + } + +// @Override +// public void updateFill(MetaObject metaObject) { +// // 更新时,需要先获取 ID,再查询完整数据 +// Object entity = metaObject.getOriginalObject(); +// Object id = this.getFieldValByName("id", metaObject); +// if (id == null) { +// // 若没有 ID,则降级使用当前对象中的字段(不完整) +// String mac = Sm3MacCalculator.calculateForEntity(entity); +// this.strictUpdateFill(metaObject, "mac", String.class, mac); +// return; +// } +// +// // 通过 AOP 或手动调用 Service,在此处获取完整实体 +// Object fullEntity = fetchFullEntityFromService(entity.getClass(), id); +// if (fullEntity != null) { +// // 将当前更新内容合并到完整实体 +// mergeChanges(fullEntity, entity); +// String mac = Sm3MacCalculator.calculateForEntity(fullEntity); +// this.strictUpdateFill(metaObject, "mac", String.class, mac); +// } +// } +} diff --git a/src/main/java/com/cisd/tms/modules/datamac/helper/Sm3MacCalculator.java b/src/main/java/com/cisd/tms/modules/datamac/helper/Sm3MacCalculator.java new file mode 100644 index 0000000..9787377 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/datamac/helper/Sm3MacCalculator.java @@ -0,0 +1,39 @@ +package com.cisd.tms.modules.datamac.helper; +import cn.hutool.crypto.SmUtil; +import cn.hutool.core.util.ArrayUtil; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Comparator; + + + +public class Sm3MacCalculator { + public static String calculateForEntity(Object entity) { + // 1. 获取所有字段并按字段名排序,保证不同实体计算的顺序一致 + Field[] fields = entity.getClass().getDeclaredFields(); + Arrays.sort(fields, Comparator.comparing(Field::getName)); + + // 2. 构建待摘要的数据 + StringBuilder dataBuilder = new StringBuilder(); + for (Field field : fields) { + // 排除 mac 字段自身 + if ("mac".equals(field.getName())) { + continue; + } + try { + field.setAccessible(true); + Object value = field.get(entity); + dataBuilder.append(field.getName()).append("=") + .append(value != null ? value.toString() : "null") + .append(";"); + } catch (IllegalAccessException e) { + // 适当的日志记录 + throw new RuntimeException("Failed to read field: " + field.getName(), e); + } + } + + // 3. 生成最终的 SM3 哈希值 + return SmUtil.sm3(dataBuilder.toString()); + } +} diff --git a/src/main/java/com/cisd/tms/modules/datamac/interceptor/MacValidationInterceptor.java b/src/main/java/com/cisd/tms/modules/datamac/interceptor/MacValidationInterceptor.java new file mode 100644 index 0000000..2fb25d9 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/datamac/interceptor/MacValidationInterceptor.java @@ -0,0 +1,42 @@ +package com.cisd.tms.modules.datamac.interceptor; + + +import com.cisd.tms.modules.datamac.entity.DataMacEntity; +import com.cisd.tms.modules.datamac.helper.Sm3MacCalculator; +import org.apache.ibatis.executor.resultset.ResultSetHandler; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Signature; +import org.springframework.stereotype.Component; + +import java.sql.Statement; +import java.util.List; + + +@Intercepts(@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})) +@Component +public class MacValidationInterceptor implements Interceptor { + + @Override + public Object intercept(Invocation invocation) throws Throwable { + Object result = invocation.proceed(); + if (result instanceof List) { + ((List) result).forEach(this::validateEntity); + } else { + validateEntity(result); + } + return result; + } + + private void validateEntity(Object obj) { + if (obj instanceof DataMacEntity) { + DataMacEntity entity = (DataMacEntity) obj; + String storedMac = entity.getMac(); + String calculatedMac = Sm3MacCalculator.calculateForEntity(entity); + if (!storedMac.equals(calculatedMac)) { + throw new RuntimeException("Data integrity validation failed. The data has been tampered with."); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/log/entity/OperationAuditLogEntity.java b/src/main/java/com/cisd/tms/modules/log/entity/OperationAuditLogEntity.java index fe0acb8..633cce5 100644 --- a/src/main/java/com/cisd/tms/modules/log/entity/OperationAuditLogEntity.java +++ b/src/main/java/com/cisd/tms/modules/log/entity/OperationAuditLogEntity.java @@ -1,8 +1,10 @@ package com.cisd.tms.modules.log.entity; import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import com.cisd.tms.modules.datamac.entity.DataMacEntity; import com.cisd.tms.modules.log.enums.*; import lombok.Data; import lombok.experimental.Accessors; @@ -12,7 +14,7 @@ import java.time.LocalDateTime; @Data @Accessors(chain = true) @TableName("tms_operation_audit_log") -public class OperationAuditLogEntity { +public class OperationAuditLogEntity extends DataMacEntity { @TableId(type = IdType.ASSIGN_ID) private Long id; private String logId; diff --git a/src/main/java/com/cisd/tms/modules/mk/config/MasterKeyActivateProperties.java b/src/main/java/com/cisd/tms/modules/mk/config/MasterKeyActivateProperties.java index e14a10a..60a7e89 100644 --- a/src/main/java/com/cisd/tms/modules/mk/config/MasterKeyActivateProperties.java +++ b/src/main/java/com/cisd/tms/modules/mk/config/MasterKeyActivateProperties.java @@ -7,9 +7,9 @@ import org.springframework.boot.context.properties.ConfigurationProperties; public class MasterKeyActivateProperties { //todo 后续添加到配置文件 - private String activateUrl = "http://127.0.0.1:8080/device/activate"; + private String platformUrl = "http://127.0.0.1:8080/device/active"; - public String getActivateUrl() { return activateUrl; } + public String getPlatformUrl() { return platformUrl; } - public void setActivateUrl(String activateUrl) { this.activateUrl = activateUrl; } + public void setPlatformUrl(String platformUrl) { this.platformUrl = platformUrl; } } diff --git a/src/main/java/com/cisd/tms/modules/mk/service/impl/MasterKeyActivateServiceImpl.java b/src/main/java/com/cisd/tms/modules/mk/service/impl/MasterKeyActivateServiceImpl.java index 34d92b2..ab3977d 100644 --- a/src/main/java/com/cisd/tms/modules/mk/service/impl/MasterKeyActivateServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/mk/service/impl/MasterKeyActivateServiceImpl.java @@ -21,26 +21,24 @@ import com.sunyard.cisd.device.tool.DeviceFingerprint; import com.sunyard.cisd.device.tool.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.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; -import java.security.KeyPair; -import java.security.KeyPairGenerator; +import java.security.PublicKey; import java.security.SecureRandom; import java.security.Security; +import java.security.Signature; +import java.security.spec.X509EncodedKeySpec; import java.util.Base64; import java.util.Optional; @@ -48,6 +46,10 @@ import java.util.Optional; @RequiredArgsConstructor public class MasterKeyActivateServiceImpl implements MasterKeyActivateService { + private static final String DEVICE_DIR = "/home/tms/device"; + public static final String SIGN_FILE = DEVICE_DIR + "/dev.sign"; + private static final String ACTIVATION_PUBLIC_KEY_FILE = DEVICE_DIR + "/dev.active.pub"; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final SecureRandom SECURE_RANDOM = new SecureRandom(); @@ -59,6 +61,9 @@ public class MasterKeyActivateServiceImpl implements MasterKeyActivateService { private final DeviceService deviceService; private final MasterKeyActivateRepository masterKeyActivateRepository; + private static final String PROVIDER = "BC"; + private static final String SIGNATURE_ALGO = "SM3withSM2"; + static { if (Security.getProvider("BC") == null) { Security.addProvider(new BouncyCastleProvider()); @@ -127,90 +132,67 @@ public class MasterKeyActivateServiceImpl implements MasterKeyActivateService { 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 - ); - + byte[] deviceFingerprintBytes = readFileBytes(SIGN_FILE); + String deviceFingerprintJson = Base64.getEncoder().encodeToString(deviceFingerprintBytes); 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; + params.setUserId(userId); + params.setCredential(credential); + params.setDeviceActivationCode(activationCode); + params.setDeviceSerialNumber(deviceSerial); + params.setDeviceFingerprintJson(deviceFingerprintJson); + params.setTimestamp(timestamp); + params.setNonce(nonce); ActivateRequest request = new ActivateRequest(); - request.params = params; - request.hash = hash; + request.setParams(params); + request.setHash(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 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()); -// } + String json = OBJECT_MAPPER.writeValueAsString(request); +// System.out.print(json); + HttpRequest httpRequest = HttpRequest.newBuilder() + .uri(URI.create(masterKeyActivateProperties.getPlatformUrl())) + .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 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 Hash = dataResponse.getHash(); + String deviceActivationPublicKey = dataResponse.getDeviceActivationPublicKey(); + String deviceActivationResult = dataResponse.getDeviceActivationResult(); + verifySm2Signature(Hash, deviceActivationResult, deviceActivationPublicKey); + masterKeyActivateEntity.setActivationStatus(true); + masterKeyActivateRepository.update(masterKeyActivateEntity); + writeFile(ACTIVATION_PUBLIC_KEY_FILE, deviceActivationPublicKey.getBytes(StandardCharsets.UTF_8)); + } else { + throw new Exception(result.getMsg()); + } } catch (Exception e) { masterKeyActivateEntity.setActivationStatus(false); @@ -220,63 +202,6 @@ public class MasterKeyActivateServiceImpl implements MasterKeyActivateService { } - - - 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); @@ -306,80 +231,49 @@ public class MasterKeyActivateServiceImpl implements MasterKeyActivateService { } - private boolean sm2VerifyActivationResult( - String inputHash, - String activationResult, - ECPublicKeyParameters publicKeyParameters - ) { + public boolean verifySm2Signature(String data, String signatureBase64, String publicKeyBase64) { try { + byte[] keyBytes = Base64.getDecoder().decode(publicKeyBase64); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + java.security.KeyFactory keyFactory = java.security.KeyFactory.getInstance("EC", PROVIDER); + PublicKey publicKey = keyFactory.generatePublic(keySpec); - 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); - + Signature signature = Signature.getInstance(SIGNATURE_ALGO, PROVIDER); + signature.initVerify(publicKey); + signature.update(data.getBytes(StandardCharsets.UTF_8)); + byte[] signatureBytes = Base64.getDecoder().decode(signatureBase64); + return signature.verify(signatureBytes); } catch (Exception e) { - e.printStackTrace(); - return false; + throw new RuntimeException("SM2验签失败", e); } } - private byte[] parseP1Signature(String activationResult) { - String value = activationResult.trim(); - if (value.startsWith("P1:")) { - value = value.substring(3); - } - return Base64.getDecoder().decode(value); - } + + + @Data 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; + private Params params; + private String hash; } @JsonIgnoreProperties(ignoreUnknown = true) @Data public static class ServerResponse { - public String status; - public String msg; - public Integer code; - public MasterKeyActivateData data; + private String status; + private String msg; + private Integer code; + private 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 hash; + private String deviceActivationPublicKey; + private String deviceActivationResult; } private String loadTmsVersion() { @@ -387,7 +281,100 @@ public class MasterKeyActivateServiceImpl implements MasterKeyActivateService { return version.map(DeviceSoftwareVersionEntity::getCurrentVersion).map(MasterKeyActivateServiceImpl::trim).orElse(""); } + + public static byte[] readFileBytes(String filePath) throws Exception { + File file = new File(filePath); + try (FileInputStream fis = new FileInputStream(file)) { + byte[] content = new byte[(int) file.length()]; + fis.read(content); + return content; + } + } + + public static void writeFile(String filePath, byte[] content) throws Exception { + File file = new File(filePath); + File parentDir = file.getParentFile(); + if (parentDir != null && !parentDir.exists()) { + parentDir.mkdirs(); + } + try (OutputStream os = new FileOutputStream(file)) { + os.write(content); + } + } + + private static String trim(String value) { return value == null ? "" : value.trim(); } + + + + public static class Params { + private String userId; + private String credential; + private String deviceActivationCode; + private String deviceSerialNumber; + private String deviceFingerprintJson; + private String timestamp; + private String nonce; + + public Params() { + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getCredential() { + return credential; + } + + public void setCredential(String credential) { + this.credential = credential; + } + + public String getDeviceActivationCode() { + return deviceActivationCode; + } + + public void setDeviceActivationCode(String deviceActivationCode) { + this.deviceActivationCode = deviceActivationCode; + } + + public String getDeviceSerialNumber() { + return deviceSerialNumber; + } + + public void setDeviceSerialNumber(String deviceSerialNumber) { + this.deviceSerialNumber = deviceSerialNumber; + } + + public String getDeviceFingerprintJson() { + return deviceFingerprintJson; + } + + public void setDeviceFingerprintJson(String deviceFingerprintJson) { + this.deviceFingerprintJson = deviceFingerprintJson; + } + + public String getTimestamp() { + return timestamp; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } + + public String getNonce() { + return nonce; + } + + public void setNonce(String nonce) { + this.nonce = nonce; + } + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 377f673..27a97e6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -334,6 +334,9 @@ tms: old-pin-base64: ${TMS_MK_INIT_IDENTIFY_OLD_PIN_BASE64:NjY2NjY2} # SDFE_InitIdentify 新 PIN(Base64 编码);主密钥初始化接口会读取该值。 new-pin-base64: ${TMS_MK_INIT_IDENTIFY_NEW_PIN_BASE64:NjY2NjY2} + activate: + # 激活服务平台路径 + platform-url: ${TMS_MK_ACTIVATE_PLATFORM_URL:http://127.0.0.1:8080/device/active} security: # 内部接口鉴权 token(前后端内部调用需一致)。 internal-token: ${TMS_INTERNAL_TOKEN:change-me-internal-token} diff --git a/src/main/resources/db/migration/V1__tms_schema_full.sql b/src/main/resources/db/migration/V1__tms_schema_full.sql index a9c6890..cb8c691 100644 --- a/src/main/resources/db/migration/V1__tms_schema_full.sql +++ b/src/main/resources/db/migration/V1__tms_schema_full.sql @@ -706,3 +706,7 @@ INSERT IGNORE INTO tms_master_key_activate ( ever_initialized, activation_status ) VALUES (1, true, true); + + +ALTER TABLE TMS.tms_operation_audit_log ADD mac varchar(100) NULL COMMENT '完整性校验值'; +ALTER TABLE TMS.tms_key_entity ADD mac varchar(100) NULL COMMENT '完整性校验值';