From 546bf5f24042537c570642c3a8489baccea9e10a Mon Sep 17 00:00:00 2001 From: cheney Date: Fri, 15 May 2026 14:14:57 +0800 Subject: [PATCH 01/19] =?UTF-8?q?=E8=87=AA=E6=A3=80=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E8=A7=84=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/system/dto/SelfCheckResult.java | 69 +++++++++ .../system/runner/SelfCheckStartupRunner.java | 58 +++++++ .../system/service/SelfCheckService.java | 16 ++ .../service/impl/SelfCheckServiceImpl.java | 142 ++++++++++++++++++ 4 files changed, 285 insertions(+) create mode 100644 src/main/java/com/cisd/tms/modules/system/dto/SelfCheckResult.java create mode 100644 src/main/java/com/cisd/tms/modules/system/runner/SelfCheckStartupRunner.java create mode 100644 src/main/java/com/cisd/tms/modules/system/service/SelfCheckService.java create mode 100644 src/main/java/com/cisd/tms/modules/system/service/impl/SelfCheckServiceImpl.java diff --git a/src/main/java/com/cisd/tms/modules/system/dto/SelfCheckResult.java b/src/main/java/com/cisd/tms/modules/system/dto/SelfCheckResult.java new file mode 100644 index 0000000..ec83ad0 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/system/dto/SelfCheckResult.java @@ -0,0 +1,69 @@ +package com.cisd.tms.modules.system.dto; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "自检结果") +public class SelfCheckResult { + + @Schema(description = "自检项目名称") + private String checkName; + + @Schema(description = "自检是否通过") + private boolean passed; + + @Schema(description = "自检详情信息") + private String message; + + @Schema(description = "自检耗时(毫秒)") + private long durationMs; + + public static SelfCheckResult success(String checkName, String message, long durationMs) { + SelfCheckResult result = new SelfCheckResult(); + result.checkName = checkName; + result.passed = true; + result.message = message; + result.durationMs = durationMs; + return result; + } + + public static SelfCheckResult failure(String checkName, String message, long durationMs) { + SelfCheckResult result = new SelfCheckResult(); + result.checkName = checkName; + result.passed = false; + result.message = message; + result.durationMs = durationMs; + return result; + } + + public String getCheckName() { + return checkName; + } + + public void setCheckName(String checkName) { + this.checkName = checkName; + } + + public boolean isPassed() { + return passed; + } + + public void setPassed(boolean passed) { + this.passed = passed; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public long getDurationMs() { + return durationMs; + } + + public void setDurationMs(long durationMs) { + this.durationMs = durationMs; + } +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/system/runner/SelfCheckStartupRunner.java b/src/main/java/com/cisd/tms/modules/system/runner/SelfCheckStartupRunner.java new file mode 100644 index 0000000..0bd51ea --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/system/runner/SelfCheckStartupRunner.java @@ -0,0 +1,58 @@ +package com.cisd.tms.modules.system.runner; + +import com.cisd.tms.modules.system.dto.SelfCheckResult; +import com.cisd.tms.modules.system.service.SelfCheckService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class SelfCheckStartupRunner implements CommandLineRunner { + + private static final Logger log = LoggerFactory.getLogger(SelfCheckStartupRunner.class); + + @Autowired + private SelfCheckService selfCheckService; + + @Override + public void run(String... args) throws Exception { + log.info("══════════════════════════════════════════════════════════════"); + log.info(" 系统启动自检流程"); + log.info("══════════════════════════════════════════════════════════════"); + + try { + List results = selfCheckService.executeAllChecks(); + + boolean allPassed = results.stream().allMatch(SelfCheckResult::isPassed); + + log.info("══════════════════════════════════════════════════════════════"); + if (allPassed) { + log.info(" 自检流程全部通过"); + log.info("══════════════════════════════════════════════════════════════"); + } else { + log.error(" 自检流程存在失败项"); + log.error("══════════════════════════════════════════════════════════════"); + for (SelfCheckResult result : results) { + if (!result.isPassed()) { + log.error(" 失败项: {} - {}", result.getCheckName(), result.getMessage()); + } + } + log.warn("系统将在 10 秒后关闭..."); + try { + Thread.sleep(10000); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + log.error("系统自检未通过,进程退出"); + System.exit(-9); + } + + } catch (Exception e) { + log.error("自检流程执行异常: {}", e.getMessage(), e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/system/service/SelfCheckService.java b/src/main/java/com/cisd/tms/modules/system/service/SelfCheckService.java new file mode 100644 index 0000000..e5e6b2a --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/system/service/SelfCheckService.java @@ -0,0 +1,16 @@ +package com.cisd.tms.modules.system.service; + +import com.cisd.tms.modules.system.dto.SelfCheckResult; + +import java.util.List; + +public interface SelfCheckService { + + SelfCheckResult verifyDeviceFingerprint(); + + SelfCheckResult verifyTmsIntegrity(); + + SelfCheckResult verifyStandardTransceiverIntegrity(); + + List executeAllChecks(); +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/system/service/impl/SelfCheckServiceImpl.java b/src/main/java/com/cisd/tms/modules/system/service/impl/SelfCheckServiceImpl.java new file mode 100644 index 0000000..dd38a91 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/system/service/impl/SelfCheckServiceImpl.java @@ -0,0 +1,142 @@ +package com.cisd.tms.modules.system.service.impl; + +import com.cisd.tms.modules.system.dto.SelfCheckResult; +import com.cisd.tms.modules.system.service.SelfCheckService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class SelfCheckServiceImpl implements SelfCheckService { + + private static final Logger log = LoggerFactory.getLogger(SelfCheckServiceImpl.class); + + private static final String CHECK_DEVICE_FINGERPRINT = "设备指纹验证"; + private static final String CHECK_TMS_INTEGRITY = "终端管理系统主程序完整性验证"; + private static final String CHECK_STANDARD_TRANSCEIVER = "标准收发器主程序完整性验证"; + + @Override + public SelfCheckResult verifyDeviceFingerprint() { + long startTime = System.currentTimeMillis(); + log.info("========== 开始执行: {} ==========", CHECK_DEVICE_FINGERPRINT); + + try { + boolean result = doVerifyDeviceFingerprint(); + long duration = System.currentTimeMillis() - startTime; + + if (result) { + String message = "设备指纹验证通过"; + log.info("[PASS] {} - {}", CHECK_DEVICE_FINGERPRINT, message); + return SelfCheckResult.success(CHECK_DEVICE_FINGERPRINT, message, duration); + } else { + String message = "设备指纹验证失败"; + log.error("[FAIL] {} - {}", CHECK_DEVICE_FINGERPRINT, message); + return SelfCheckResult.failure(CHECK_DEVICE_FINGERPRINT, message, duration); + } + } catch (Exception e) { + long duration = System.currentTimeMillis() - startTime; + String message = "设备指纹验证异常: " + e.getMessage(); + log.error("[ERROR] {} - {}", CHECK_DEVICE_FINGERPRINT, message, e); + return SelfCheckResult.failure(CHECK_DEVICE_FINGERPRINT, message, duration); + } + } + + @Override + public SelfCheckResult verifyTmsIntegrity() { + long startTime = System.currentTimeMillis(); + log.info("========== 开始执行: {} ==========", CHECK_TMS_INTEGRITY); + + try { + boolean result = doVerifyTmsIntegrity(); + long duration = System.currentTimeMillis() - startTime; + + if (result) { + String message = "终端管理系统主程序完整性验证通过"; + log.info("[PASS] {} - {}", CHECK_TMS_INTEGRITY, message); + return SelfCheckResult.success(CHECK_TMS_INTEGRITY, message, duration); + } else { + String message = "终端管理系统主程序完整性验证失败"; + log.error("[FAIL] {} - {}", CHECK_TMS_INTEGRITY, message); + return SelfCheckResult.failure(CHECK_TMS_INTEGRITY, message, duration); + } + } catch (Exception e) { + long duration = System.currentTimeMillis() - startTime; + String message = "终端管理系统主程序完整性验证异常: " + e.getMessage(); + log.error("[ERROR] {} - {}", CHECK_TMS_INTEGRITY, message, e); + return SelfCheckResult.failure(CHECK_TMS_INTEGRITY, message, duration); + } + } + + @Override + public SelfCheckResult verifyStandardTransceiverIntegrity() { + long startTime = System.currentTimeMillis(); + log.info("========== 开始执行: {} ==========", CHECK_STANDARD_TRANSCEIVER); + + try { + boolean result = doVerifyStandardTransceiverIntegrity(); + long duration = System.currentTimeMillis() - startTime; + + if (result) { + String message = "标准收发器主程序完整性验证通过"; + log.info("[PASS] {} - {}", CHECK_STANDARD_TRANSCEIVER, message); + return SelfCheckResult.success(CHECK_STANDARD_TRANSCEIVER, message, duration); + } else { + String message = "标准收发器主程序完整性验证失败"; + log.error("[FAIL] {} - {}", CHECK_STANDARD_TRANSCEIVER, message); + return SelfCheckResult.failure(CHECK_STANDARD_TRANSCEIVER, message, duration); + } + } catch (Exception e) { + long duration = System.currentTimeMillis() - startTime; + String message = "标准收发器主程序完整性验证异常: " + e.getMessage(); + log.error("[ERROR] {} - {}", CHECK_STANDARD_TRANSCEIVER, message, e); + return SelfCheckResult.failure(CHECK_STANDARD_TRANSCEIVER, message, duration); + } + } + + @Override + public List executeAllChecks() { + log.info("======================================"); + log.info(" 系统启动自检流程开始"); + log.info("======================================"); + + long totalStartTime = System.currentTimeMillis(); + List results = new ArrayList<>(); + + results.add(verifyDeviceFingerprint()); + results.add(verifyTmsIntegrity()); + results.add(verifyStandardTransceiverIntegrity()); + + long totalDuration = System.currentTimeMillis() - totalStartTime; + long passedCount = results.stream().filter(SelfCheckResult::isPassed).count(); + long failedCount = results.size() - passedCount; + + log.info("======================================"); + log.info(" 系统启动自检流程结束"); + log.info("======================================"); + log.info("自检总耗时: {} ms", totalDuration); + log.info("检测项总数: {}, 通过: {}, 失败: {}", results.size(), passedCount, failedCount); + + if (failedCount > 0) { + log.warn("存在未通过的自检项,请检查相关日志"); + } else { + log.info("所有自检项均已通过"); + } + + return results; + } + + private boolean doVerifyDeviceFingerprint() { + return false; + } + + private boolean doVerifyTmsIntegrity() { + return false; + } + + private boolean doVerifyStandardTransceiverIntegrity() { + return false; + } +} \ No newline at end of file From cd690bd2d942b1e8e858fe7d7bf3f287de95eebb Mon Sep 17 00:00:00 2001 From: cheney Date: Fri, 15 May 2026 14:37:35 +0800 Subject: [PATCH 02/19] =?UTF-8?q?=E7=BB=88=E7=AB=AF=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E4=B8=BB=E7=A8=8B=E5=BA=8F=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E6=80=A7=E8=87=AA=E6=A3=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- device-fingerprint-tool/pom.xml | 2 +- .../com/sunyard/cisd/device/tool/Main.java | 16 +-- pom.xml | 2 +- .../service/impl/SelfCheckServiceImpl.java | 115 +++++++++++++++++- 4 files changed, 123 insertions(+), 12 deletions(-) diff --git a/device-fingerprint-tool/pom.xml b/device-fingerprint-tool/pom.xml index 76be577..546f370 100644 --- a/device-fingerprint-tool/pom.xml +++ b/device-fingerprint-tool/pom.xml @@ -6,7 +6,7 @@ com.sunyard.cisd device-fingerprint-tool - 1.0.0 + 1.0.1 device-fingerprint-tool 设备指纹信息获取工具 diff --git a/device-fingerprint-tool/src/main/java/com/sunyard/cisd/device/tool/Main.java b/device-fingerprint-tool/src/main/java/com/sunyard/cisd/device/tool/Main.java index db27117..f22f5d5 100644 --- a/device-fingerprint-tool/src/main/java/com/sunyard/cisd/device/tool/Main.java +++ b/device-fingerprint-tool/src/main/java/com/sunyard/cisd/device/tool/Main.java @@ -10,24 +10,24 @@ import java.util.Map; public class Main { private static final String DEVICE_DIR = "/home/tms/device"; - private static final String SIGN_FILE = DEVICE_DIR + "/dev.sign"; + public static final String SIGN_FILE = DEVICE_DIR + "/dev.sign"; private static final String SIGN_SM3_FILE = DEVICE_DIR + "/dev.sign.sm3"; private static final String PUBLIC_KEY_FILE = DEVICE_DIR + "/dev.fingerprint.pub"; private static final String PUBLIC_KEY_SM3_FILE = DEVICE_DIR + "/dev.fingerprint.pub.sm3"; - private static final String FINGERPRINT_DATA_FILE = DEVICE_DIR + "/dev.fingerprint.data"; + public static final String FINGERPRINT_DATA_FILE = DEVICE_DIR + "/dev.fingerprint.data"; private static final String FINGERPRINT_PRIVATE_KEY_PATH = "/fingerprint_sm2_private_key.pem"; - private static final String FINGERPRINT_PUBLIC_KEY_PATH = "/fingerprint_sm2_public_key.pem"; + public static final String FINGERPRINT_PUBLIC_KEY_PATH = "/fingerprint_sm2_public_key.pem"; private static final String MAC_PRIVATE_KEY_PATH = "/mac_sm2_private_key.pem"; - private static final String MAC_PUBLIC_KEY_PATH = "/mac_sm2_public_key.pem"; + public static final String MAC_PUBLIC_KEY_PATH = "/mac_sm2_public_key.pem"; private static final String ROOT_CERT_FILE = "/CFCA_TEST_CS_SM2_CA.cer"; private static final String ROOT_CERT_OUTPUT_FILE = DEVICE_DIR + "/CFCA_TEST_CS_SM2_CA.cer"; private static final String ROOT_CERT_SIGN_FILE = DEVICE_DIR + "/rootcert.sign"; private static final String MAC_PUBLIC_KEY_OUTPUT_FILE = DEVICE_DIR + "/dev.mac.pub"; private static final String MAC_PUBLIC_KEY_SM3_FILE = DEVICE_DIR + "/dev.mac.pub.sm3"; - private static final String MAC_LIST_FILE = "/home/tms/mac.list"; - private static final String MAC_SIGN_FILE = DEVICE_DIR + "/dev.mac.sign"; + public static final String MAC_LIST_FILE = "/home/tms/mac.list"; + public static final String MAC_SIGN_FILE = DEVICE_DIR + "/dev.mac.sign"; - private static final String[] MAC_TARGETS = { + public static final String[] MAC_TARGETS = { "/home/tms/tms-framework.jar", "/home/tms/config", "/home/tms/libs", @@ -111,7 +111,7 @@ public class Main { System.out.println("硬盘序列号: " + fingerprint.getHardDiskSerialNumber()); } - private static String buildPlainText(DeviceFingerprint fingerprint) { + public static String buildPlainText(DeviceFingerprint fingerprint) { return fingerprint.getMotherboardSerialNumber() + fingerprint.getDeviceSerialNumber() + fingerprint.getCpuModel() + diff --git a/pom.xml b/pom.xml index ef7d946..3b7d717 100644 --- a/pom.xml +++ b/pom.xml @@ -119,7 +119,7 @@ com.sunyard.cisd device-fingerprint-tool - 1.0.0 + 1.0.1 diff --git a/src/main/java/com/cisd/tms/modules/system/service/impl/SelfCheckServiceImpl.java b/src/main/java/com/cisd/tms/modules/system/service/impl/SelfCheckServiceImpl.java index dd38a91..18c70f2 100644 --- a/src/main/java/com/cisd/tms/modules/system/service/impl/SelfCheckServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/system/service/impl/SelfCheckServiceImpl.java @@ -2,12 +2,22 @@ package com.cisd.tms.modules.system.service.impl; import com.cisd.tms.modules.system.dto.SelfCheckResult; import com.cisd.tms.modules.system.service.SelfCheckService; +import com.sunyard.cisd.device.tool.DeviceFingerprint; +import com.sunyard.cisd.device.tool.DeviceFingerprintService; +import com.sunyard.cisd.device.tool.SM2Util; +import com.sunyard.cisd.device.tool.SM3Util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.security.PublicKey; import java.util.ArrayList; import java.util.List; +import java.util.Map; + +import static com.sunyard.cisd.device.tool.Main.*; @Service public class SelfCheckServiceImpl implements SelfCheckService { @@ -129,11 +139,112 @@ public class SelfCheckServiceImpl implements SelfCheckService { } private boolean doVerifyDeviceFingerprint() { - return false; + try { + DeviceFingerprintService service = new DeviceFingerprintService(); + DeviceFingerprint fingerprint = service.getDeviceFingerprint(); + + log.info("=== 当前设备信息 ==="); + log.info("主板序列号: " + fingerprint.getMotherboardSerialNumber()); + log.info("设备序列号: " + fingerprint.getDeviceSerialNumber()); + log.info("CPU型号: " + fingerprint.getCpuModel()); + log.info("CPU序列号: " + fingerprint.getCpuSerialNumber()); + log.info("内存大小: " + fingerprint.getMemorySize()); + log.info("首块网卡MAC地址: " + fingerprint.getFirstNetworkMacAddress()); + log.info("硬盘序列号: " + fingerprint.getHardDiskSerialNumber()); + + String plainText = buildPlainText(fingerprint); + log.info("\n当前拼接原文: " + plainText); + + String savedPlainText = SM3Util.readFile(FINGERPRINT_DATA_FILE).trim(); + log.info("保存的拼接原文: " + savedPlainText); + + byte[] signature = SM3Util.readFileBytes(SIGN_FILE); + PublicKey publicKey = SM2Util.loadPublicKey(FINGERPRINT_PUBLIC_KEY_PATH); + + boolean dataMatch = plainText.equals(savedPlainText); + boolean signatureVerified = SM2Util.verify(savedPlainText.getBytes(StandardCharsets.UTF_8), signature, publicKey); + + log.info("\n=== 验证结果 ==="); + log.info("设备信息匹配: " + dataMatch); + log.info("签名验证: " + signatureVerified); + log.info("总体验证: " + (dataMatch && signatureVerified)); + return (dataMatch && signatureVerified); + } catch (Exception e) { + log.error("[ERROR] {} - {}", CHECK_DEVICE_FINGERPRINT, e.getMessage()); + return false; + } } private boolean doVerifyTmsIntegrity() { - return false; + try { + System.out.println("=== 验证系统文件MAC校验值 ==="); + + PublicKey publicKey = SM2Util.loadPublicKey(MAC_PUBLIC_KEY_PATH); + byte[] savedSignature = SM3Util.readFileBytes(MAC_SIGN_FILE); + String savedMacList = SM3Util.readFile(MAC_LIST_FILE); + + boolean macListSignatureValid = SM2Util.verify(savedMacList.getBytes(StandardCharsets.UTF_8), savedSignature, publicKey); + System.out.println("MAC列表签名验证: " + (macListSignatureValid ? "通过" : "失败")); + + if (!macListSignatureValid) { + System.out.println("错误: mac.list 签名验证失败,可能被篡改!"); + System.out.println("\n=== 验证结果 ==="); + System.out.println("完整性验证: false"); + return false; + } + + Map savedSm3Map = SM3Util.readSM3File(MAC_LIST_FILE); + + Map currentSm3Map = new java.util.LinkedHashMap<>(); + for (String target : MAC_TARGETS) { + File file = new File(target); + if (!file.exists()) { + continue; + } + + if (file.isFile()) { + String sm3Value = SM3Util.sm3FileHex(target); + currentSm3Map.put(target, sm3Value); + } else if (file.isDirectory()) { + Map dirSm3Map = SM3Util.calculateDirectorySM3(target); + for (Map.Entry entry : dirSm3Map.entrySet()) { + String absolutePath = target + "/" + entry.getKey(); + currentSm3Map.put(absolutePath, entry.getValue()); + } + } + } + + boolean allMatch = true; + + for (Map.Entry entry : savedSm3Map.entrySet()) { + String filePath = entry.getKey(); + String savedHash = entry.getValue(); + String currentHash = currentSm3Map.get(filePath); + + if (currentHash == null) { + System.out.println("文件缺失: " + filePath); + allMatch = false; + } else if (!savedHash.equals(currentHash)) { + System.out.println("文件被修改: " + filePath + " (期望: " + savedHash + ", 实际: " + currentHash + ")"); + allMatch = false; + } + } + + for (String filePath : currentSm3Map.keySet()) { + if (!savedSm3Map.containsKey(filePath)) { + System.out.println("新增文件: " + filePath); + allMatch = false; + } + } + + System.out.println(); + System.out.println("=== 验证结果 ==="); + System.out.println("完整性验证: " + allMatch); + return allMatch; + } catch (Exception e) { + log.error("[ERROR] {} - {}", CHECK_TMS_INTEGRITY, e.getMessage()); + return false; + } } private boolean doVerifyStandardTransceiverIntegrity() { From a177277f04872b9c275fad8b33fe205a2e0ec20b Mon Sep 17 00:00:00 2001 From: cheney Date: Fri, 15 May 2026 14:46:08 +0800 Subject: [PATCH 03/19] =?UTF-8?q?=E7=BB=88=E7=AB=AF=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E4=B8=BB=E7=A8=8B=E5=BA=8F=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=E6=80=A7=E8=87=AA=E6=A3=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/sunyard/cisd/device/tool/Main.java | 146 +++++++++++++++++- .../service/impl/SelfCheckServiceImpl.java | 86 +++++++++-- 2 files changed, 212 insertions(+), 20 deletions(-) diff --git a/device-fingerprint-tool/src/main/java/com/sunyard/cisd/device/tool/Main.java b/device-fingerprint-tool/src/main/java/com/sunyard/cisd/device/tool/Main.java index f22f5d5..651b26a 100644 --- a/device-fingerprint-tool/src/main/java/com/sunyard/cisd/device/tool/Main.java +++ b/device-fingerprint-tool/src/main/java/com/sunyard/cisd/device/tool/Main.java @@ -27,6 +27,9 @@ public class Main { public static final String MAC_LIST_FILE = "/home/tms/mac.list"; public static final String MAC_SIGN_FILE = DEVICE_DIR + "/dev.mac.sign"; + public static final String CMEP_MAC_LIST_FILE = "/home/cmep4i/cmep.mac.list"; + public static final String CMEP_MAC_SIGN_FILE = "/home/cmep4i/cmep.mac.sign"; + public static final String[] MAC_TARGETS = { "/home/tms/tms-framework.jar", "/home/tms/config", @@ -36,6 +39,11 @@ public class Main { "/home/tms/scripts/tms.sh" }; + public static final String[] CMEP_TARGETS = { + "/home/cmep4i/cmsp/CMEP-CMSP.jar", + "/home/cmep4i/cmtp/CMEP-CMSP.jar" + }; + public static void main(String[] args) { if (args.length == 0) { printHelp(); @@ -59,12 +67,18 @@ public class Main { case "fingerprint-vertify": verifyFingerprint(); break; - case "mac-gen": + case "tms-mac-gen": generateMAC(); break; - case "mac-vertify": + case "tms-mac-vertify": verifyMAC(); break; + case "cmep-mac-gen": + generateCMEPMAC(); + break; + case "cmep-mac-vertify": + verifyCMEPMAC(); + break; case "rootcert-mac-gen": generateRootCertMAC(); break; @@ -90,8 +104,10 @@ public class Main { System.out.println(" info - 获取并显示设备信息"); System.out.println(" fingerprint-gen - 获取设备信息并生成设备指纹输出到文件"); System.out.println(" fingerprint-vertify - 获取设备信息并验证设备指纹"); - System.out.println(" mac-gen - 生成系统文件MAC校验值并签名"); - System.out.println(" mac-vertify - 验证系统文件MAC校验值"); + System.out.println(" tms-mac-gen - 生成TMS系统文件MAC校验值并签名"); + System.out.println(" tms-mac-vertify - 验证TMS系统文件MAC校验值"); + System.out.println(" cmep-mac-gen - 生成CMEP文件MAC校验值并签名"); + System.out.println(" cmep-mac-vertify - 验证CMEP文件MAC校验值"); System.out.println(" rootcert-mac-gen - 生成根证书MAC签名"); System.out.println(" rootcert-mac-vertify - 验证根证书MAC签名"); System.out.println(); @@ -316,8 +332,7 @@ public class Main { throw new IllegalArgumentException("根证书文件未找到: " + ROOT_CERT_FILE); } - byte[] certData = certInputStream.readAllBytes(); - certInputStream.close(); + byte[] certData = readInputStream(certInputStream); SM3Util.writeFile(ROOT_CERT_OUTPUT_FILE, certData); System.out.println("根证书已输出: " + ROOT_CERT_OUTPUT_FILE); @@ -353,8 +368,7 @@ public class Main { if (certInputStream == null) { throw new IllegalArgumentException("根证书文件未找到: " + ROOT_CERT_FILE); } - byte[] certData = certInputStream.readAllBytes(); - certInputStream.close(); + byte[] certData = readInputStream(certInputStream); byte[] signature = SM3Util.readFileBytes(ROOT_CERT_SIGN_FILE); @@ -365,4 +379,120 @@ public class Main { System.out.println("=== 验证结果 ==="); System.out.println("总体验证: " + (publicKeyValid && signatureVerified)); } + + private static byte[] readInputStream(InputStream is) throws Exception { + try { + java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); + byte[] buffer = new byte[8192]; + int len; + while ((len = is.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + return baos.toByteArray(); + } finally { + if (is != null) { + is.close(); + } + } + } + + private static void generateCMEPMAC() throws Exception { + System.out.println("=== 生成CMEP文件MAC校验值 ==="); + System.out.println("目标路径:"); + for (String target : CMEP_TARGETS) { + System.out.println(" " + target); + } + + Map sm3Map = new java.util.LinkedHashMap<>(); + + for (String target : CMEP_TARGETS) { + File file = new File(target); + if (!file.exists()) { + System.out.println("警告: 路径不存在 - " + target); + continue; + } + + if (file.isFile()) { + String sm3Value = SM3Util.sm3FileHex(target); + sm3Map.put(target, sm3Value); + System.out.println("文件: " + target + " -> " + sm3Value); + } + } + + StringBuilder macListContent = new StringBuilder(); + for (Map.Entry entry : sm3Map.entrySet()) { + macListContent.append(entry.getKey()).append("|").append(entry.getValue()).append("\n"); + } + + SM3Util.writeFile(CMEP_MAC_LIST_FILE, macListContent.toString()); + System.out.println("\nCMEP MAC列表文件已保存: " + CMEP_MAC_LIST_FILE); + + byte[] signature = SM2Util.sign(macListContent.toString().getBytes(StandardCharsets.UTF_8), MAC_PRIVATE_KEY_PATH); + SM3Util.writeFile(CMEP_MAC_SIGN_FILE, signature); + System.out.println("CMEP MAC签名文件已保存: " + CMEP_MAC_SIGN_FILE); + + PublicKey publicKey = SM2Util.loadPublicKey(MAC_PUBLIC_KEY_PATH); + boolean verified = SM2Util.verify(macListContent.toString().getBytes(StandardCharsets.UTF_8), signature, publicKey); + System.out.println("\n签名验证结果: " + verified); + } + + private static void verifyCMEPMAC() throws Exception { + System.out.println("=== 验证CMEP文件MAC校验值 ==="); + + PublicKey publicKey = SM2Util.loadPublicKey(MAC_PUBLIC_KEY_PATH); + byte[] savedSignature = SM3Util.readFileBytes(CMEP_MAC_SIGN_FILE); + String savedMacList = SM3Util.readFile(CMEP_MAC_LIST_FILE); + + boolean macListSignatureValid = SM2Util.verify(savedMacList.getBytes(StandardCharsets.UTF_8), savedSignature, publicKey); + System.out.println("CMEP MAC列表签名验证: " + (macListSignatureValid ? "通过" : "失败")); + + if (!macListSignatureValid) { + System.out.println("错误: cmep.mac.list 签名验证失败,可能被篡改!"); + System.out.println("\n=== 验证结果 ==="); + System.out.println("完整性验证: false"); + return; + } + + Map savedSm3Map = SM3Util.readSM3File(CMEP_MAC_LIST_FILE); + + Map currentSm3Map = new java.util.LinkedHashMap<>(); + for (String target : CMEP_TARGETS) { + File file = new File(target); + if (!file.exists()) { + continue; + } + + if (file.isFile()) { + String sm3Value = SM3Util.sm3FileHex(target); + currentSm3Map.put(target, sm3Value); + } + } + + boolean allMatch = true; + + for (Map.Entry entry : savedSm3Map.entrySet()) { + String filePath = entry.getKey(); + String savedHash = entry.getValue(); + String currentHash = currentSm3Map.get(filePath); + + if (currentHash == null) { + System.out.println("文件缺失: " + filePath); + allMatch = false; + } else if (!savedHash.equals(currentHash)) { + System.out.println("文件被修改: " + filePath + " (期望: " + savedHash + ", 实际: " + currentHash + ")"); + allMatch = false; + } + } + + for (String filePath : currentSm3Map.keySet()) { + if (!savedSm3Map.containsKey(filePath)) { + System.out.println("新增文件: " + filePath); + allMatch = false; + } + } + + System.out.println(); + System.out.println("=== 验证结果 ==="); + System.out.println("完整性验证: " + allMatch); + } } diff --git a/src/main/java/com/cisd/tms/modules/system/service/impl/SelfCheckServiceImpl.java b/src/main/java/com/cisd/tms/modules/system/service/impl/SelfCheckServiceImpl.java index 18c70f2..17b86ab 100644 --- a/src/main/java/com/cisd/tms/modules/system/service/impl/SelfCheckServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/system/service/impl/SelfCheckServiceImpl.java @@ -177,19 +177,19 @@ public class SelfCheckServiceImpl implements SelfCheckService { private boolean doVerifyTmsIntegrity() { try { - System.out.println("=== 验证系统文件MAC校验值 ==="); + log.info("=== 验证系统文件MAC校验值 ==="); PublicKey publicKey = SM2Util.loadPublicKey(MAC_PUBLIC_KEY_PATH); byte[] savedSignature = SM3Util.readFileBytes(MAC_SIGN_FILE); String savedMacList = SM3Util.readFile(MAC_LIST_FILE); boolean macListSignatureValid = SM2Util.verify(savedMacList.getBytes(StandardCharsets.UTF_8), savedSignature, publicKey); - System.out.println("MAC列表签名验证: " + (macListSignatureValid ? "通过" : "失败")); + log.info("MAC列表签名验证: " + (macListSignatureValid ? "通过" : "失败")); if (!macListSignatureValid) { - System.out.println("错误: mac.list 签名验证失败,可能被篡改!"); - System.out.println("\n=== 验证结果 ==="); - System.out.println("完整性验证: false"); + log.info("错误: mac.list 签名验证失败,可能被篡改!"); + log.info("\n=== 验证结果 ==="); + log.info("完整性验证: false"); return false; } @@ -222,24 +222,24 @@ public class SelfCheckServiceImpl implements SelfCheckService { String currentHash = currentSm3Map.get(filePath); if (currentHash == null) { - System.out.println("文件缺失: " + filePath); + log.info("文件缺失: " + filePath); allMatch = false; } else if (!savedHash.equals(currentHash)) { - System.out.println("文件被修改: " + filePath + " (期望: " + savedHash + ", 实际: " + currentHash + ")"); + log.info("文件被修改: " + filePath + " (期望: " + savedHash + ", 实际: " + currentHash + ")"); allMatch = false; } } for (String filePath : currentSm3Map.keySet()) { if (!savedSm3Map.containsKey(filePath)) { - System.out.println("新增文件: " + filePath); + log.info("新增文件: " + filePath); allMatch = false; } } - System.out.println(); - System.out.println("=== 验证结果 ==="); - System.out.println("完整性验证: " + allMatch); + log.info(""); + log.info("=== 验证结果 ==="); + log.info("完整性验证: " + allMatch); return allMatch; } catch (Exception e) { log.error("[ERROR] {} - {}", CHECK_TMS_INTEGRITY, e.getMessage()); @@ -248,6 +248,68 @@ public class SelfCheckServiceImpl implements SelfCheckService { } private boolean doVerifyStandardTransceiverIntegrity() { - return false; + try { + log.info("=== 验证CMEP文件MAC校验值 ==="); + + PublicKey publicKey = SM2Util.loadPublicKey(MAC_PUBLIC_KEY_PATH); + byte[] savedSignature = SM3Util.readFileBytes(CMEP_MAC_SIGN_FILE); + String savedMacList = SM3Util.readFile(CMEP_MAC_LIST_FILE); + + boolean macListSignatureValid = SM2Util.verify(savedMacList.getBytes(StandardCharsets.UTF_8), savedSignature, publicKey); + log.info("CMEP MAC列表签名验证: " + (macListSignatureValid ? "通过" : "失败")); + + if (!macListSignatureValid) { + log.info("错误: cmep.mac.list 签名验证失败,可能被篡改!"); + log.info("\n=== 验证结果 ==="); + log.info("完整性验证: false"); + return false; + } + + Map savedSm3Map = SM3Util.readSM3File(CMEP_MAC_LIST_FILE); + + Map currentSm3Map = new java.util.LinkedHashMap<>(); + for (String target : CMEP_TARGETS) { + File file = new File(target); + if (!file.exists()) { + continue; + } + + if (file.isFile()) { + String sm3Value = SM3Util.sm3FileHex(target); + currentSm3Map.put(target, sm3Value); + } + } + + boolean allMatch = true; + + for (Map.Entry entry : savedSm3Map.entrySet()) { + String filePath = entry.getKey(); + String savedHash = entry.getValue(); + String currentHash = currentSm3Map.get(filePath); + + if (currentHash == null) { + log.info("文件缺失: " + filePath); + allMatch = false; + } else if (!savedHash.equals(currentHash)) { + log.info("文件被修改: " + filePath + " (期望: " + savedHash + ", 实际: " + currentHash + ")"); + allMatch = false; + } + } + + for (String filePath : currentSm3Map.keySet()) { + if (!savedSm3Map.containsKey(filePath)) { + log.info("新增文件: " + filePath); + allMatch = false; + } + } + + log.info(""); + log.info("=== 验证结果 ==="); + log.info("完整性验证: " + allMatch); + return allMatch; + } catch (Exception e) { + log.error("[ERROR] {} - {}", CHECK_STANDARD_TRANSCEIVER, e.getMessage()); + return false; + } } } \ No newline at end of file From 37337526c3156227745b38a653c43f1a5c1a4599 Mon Sep 17 00:00:00 2001 From: cheney Date: Fri, 15 May 2026 15:05:09 +0800 Subject: [PATCH 04/19] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=87=AA=E6=A3=80?= =?UTF-8?q?=E8=B7=B3=E8=BF=87=E6=9C=BA=E5=88=B6:TMS=5FSELFCHECK=5FSKIP=3Dt?= =?UTF-8?q?rue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/system/runner/SelfCheckStartupRunner.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/com/cisd/tms/modules/system/runner/SelfCheckStartupRunner.java b/src/main/java/com/cisd/tms/modules/system/runner/SelfCheckStartupRunner.java index 0bd51ea..37e9d34 100644 --- a/src/main/java/com/cisd/tms/modules/system/runner/SelfCheckStartupRunner.java +++ b/src/main/java/com/cisd/tms/modules/system/runner/SelfCheckStartupRunner.java @@ -18,12 +18,21 @@ public class SelfCheckStartupRunner implements CommandLineRunner { @Autowired private SelfCheckService selfCheckService; + private static final String SELFCHECK_SKIP_ENV = "TMS_SELFCHECK_SKIP"; + @Override public void run(String... args) throws Exception { log.info("══════════════════════════════════════════════════════════════"); log.info(" 系统启动自检流程"); log.info("══════════════════════════════════════════════════════════════"); + String skipSelfCheck = System.getenv(SELFCHECK_SKIP_ENV); + if ("true".equalsIgnoreCase(skipSelfCheck)) { + log.warn("检测到环境变量 {}=true,跳过自检流程", SELFCHECK_SKIP_ENV); + log.info("══════════════════════════════════════════════════════════════"); + return; + } + try { List results = selfCheckService.executeAllChecks(); From 133363ef18e9c7ad3e22af8c3971bb3df87cc4d5 Mon Sep 17 00:00:00 2001 From: cheney Date: Wed, 29 Apr 2026 17:00:46 +0800 Subject: [PATCH 05/19] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=9C=89=E5=B7=AE?= =?UTF-8?q?=E5=BC=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crypto/pcie/jna/PcieNativeLibrary.java | 1 + .../auth/service/impl/AuthServiceImpl.java | 3 +- .../openapi/service/IOpenApiService.java | 9 + .../service/dto/GenericCertificate.java | 64 ++++ .../openapi/service/impl/OpenApiService.java | 351 ++++++++++++++++++ .../openapi/OpenApiSignAuthInterceptor.java | 1 + 6 files changed, 428 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/cisd/tms/modules/openapi/service/IOpenApiService.java create mode 100644 src/main/java/com/cisd/tms/modules/openapi/service/dto/GenericCertificate.java create mode 100644 src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java diff --git a/src/main/java/com/cisd/tms/integration/crypto/pcie/jna/PcieNativeLibrary.java b/src/main/java/com/cisd/tms/integration/crypto/pcie/jna/PcieNativeLibrary.java index 7bfbc43..6b26f85 100644 --- a/src/main/java/com/cisd/tms/integration/crypto/pcie/jna/PcieNativeLibrary.java +++ b/src/main/java/com/cisd/tms/integration/crypto/pcie/jna/PcieNativeLibrary.java @@ -15,6 +15,7 @@ public interface PcieNativeLibrary extends Library { int SDF_CloseSession(Pointer hSessionHandle); + int SDF_GetPrivateKeyAccessRight(Pointer hSessionHandle, int uiKeyIndex, byte[] pucPassword, int uiPwdLength); int SDF_ReleasePrivateKeyAccessRight(Pointer hSessionHandle, int uiKeyIndex); diff --git a/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthServiceImpl.java b/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthServiceImpl.java index 2d1676e..6d38137 100644 --- a/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthServiceImpl.java @@ -58,7 +58,8 @@ import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor -public class AuthServiceImpl implements AuthService { +public class +AuthServiceImpl implements AuthService { private static final int MAX_FAILED_ATTEMPTS = 5; private static final int IDLE_TIMEOUT_MINUTES = 10; diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/IOpenApiService.java b/src/main/java/com/cisd/tms/modules/openapi/service/IOpenApiService.java new file mode 100644 index 0000000..78dcdc4 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/openapi/service/IOpenApiService.java @@ -0,0 +1,9 @@ +package com.cisd.tms.modules.openapi.service; + +public interface IOpenApiService { + public String rawSign(byte[] origBytes, String dn); + public boolean rawVerify(byte[] origBytes, String certStr, String dn); + public String dettachedSign(byte[] origBytes, String dn); + public String dettachedVerify(byte[] origBytes, String certStr); + public String dettachedVerifySimple(byte[] origBytes, String certStr); +} diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/dto/GenericCertificate.java b/src/main/java/com/cisd/tms/modules/openapi/service/dto/GenericCertificate.java new file mode 100644 index 0000000..3472792 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/openapi/service/dto/GenericCertificate.java @@ -0,0 +1,64 @@ +package com.cisd.tms.modules.openapi.service.dto; + +import java.security.cert.X509Certificate; + +/** + * 通用证书类 + */ +public class GenericCertificate { + + private X509Certificate certificate; + private String dn; + private String issuer; + private String serialNumber; + + public GenericCertificate(X509Certificate certificate) { + this.certificate = certificate; + if (certificate != null) { + this.dn = certificate.getSubjectDN().getName(); + this.issuer = certificate.getIssuerDN().getName(); + this.serialNumber = certificate.getSerialNumber().toString(); + } + } + + public X509Certificate getCertificate() { + return certificate; + } + + public void setCertificate(X509Certificate certificate) { + this.certificate = certificate; + } + + public String getDn() { + return dn; + } + + public void setDn(String dn) { + this.dn = dn; + } + + public String getIssuer() { + return issuer; + } + + public void setIssuer(String issuer) { + this.issuer = issuer; + } + + public String getSerialNumber() { + return serialNumber; + } + + public void setSerialNumber(String serialNumber) { + this.serialNumber = serialNumber; + } + + @Override + public String toString() { + return "GenericCertificate{" + + "dn='" + dn + '\'' + + ", issuer='" + issuer + '\'' + + ", serialNumber='" + serialNumber + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java new file mode 100644 index 0000000..39deabc --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java @@ -0,0 +1,351 @@ +package com.cisd.tms.modules.openapi.service.impl; + +import com.cisd.tms.integration.crypto.pcie.service.JnaPcieCryptoService; +import com.cisd.tms.modules.openapi.service.IOpenApiService; +import com.cisd.tms.modules.openapi.service.dto.GenericCertificate; +import com.sun.jna.Pointer; +import com.sun.jna.ptr.IntByReference; +import lombok.RequiredArgsConstructor; +import org.bouncycastle.cms.CMSProcessableByteArray; +import org.bouncycastle.cms.CMSSignedData; +import org.bouncycastle.cms.CMSSignedDataGenerator; +import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder; +import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; +import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; +import org.bouncycastle.util.encoders.Base64; + +import java.security.KeyPair; +import java.security.cert.X509Certificate; + + +@RequiredArgsConstructor +public class OpenApiService implements IOpenApiService { + + private JnaPcieCryptoService sdf; + + + @Override + public String rawSign(byte[] origBytes, String dn) { + try { + // 使用固定的密钥索引 0 + int keyIndex = 0; + + // 获取证书和公钥 + X509Certificate cert = certManager.getCertificate(dn); + if (cert == null) { + throw new RuntimeException("Certificate not found for DN: " + dn); + } + + // 获取公钥字节数组 + byte[] publicKey = cert.getPublicKey().getEncoded(); + + // 使用 SDF 接口进行 SM3 哈希(包含公钥和 ID) + byte[] hash = new byte[32]; // SM3 哈希长度为 32 字节 + IntByReference hashLength = new IntByReference(); + + // 使用空指针作为会话句柄(SDFSoft 支持) + Pointer sessionHandle = Pointer.NULL; + + // 默认的 SM2 用户 ID + String userId = "1234567812345678"; + + // 初始化哈希(包含公钥和用户 ID) + int ret = sdf.SDF_HashInit(sessionHandle, 0, publicKey, userId, userId.length()); + if (ret != 0) { + throw new RuntimeException("SDF_HashInit failed with code: " + ret); + } + + // 更新哈希(添加原始数据) + ret = sdf.SDF_HashUpdate(sessionHandle, origBytes, origBytes.length); + if (ret != 0) { + throw new RuntimeException("SDF_HashUpdate failed with code: " + ret); + } + + // 完成哈希 + ret = sdf.SDF_HashFinal(sessionHandle, hash, hashLength); + if (ret != 0) { + throw new RuntimeException("SDF_HashFinal failed with code: " + ret); + } + + // 使用 SDF 接口进行 SM2 签名 + byte[] signature = new byte[64]; // SM2 签名长度为 64 字节 + ret = sdf.SDF_InternalSign_ECC(sessionHandle, keyIndex, hash, hashLength.getValue(), signature); + if (ret != 0) { + throw new RuntimeException("SDF_InternalSign_ECC failed with code: " + ret); + } + + // 返回 Base64 编码的签名 + return new String(Base64.encode(signature)); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Raw sign failed: " + e.getMessage(), e); + } + } + + @Override + public boolean rawVerify(byte[] origBytes, String certStr, String dn) { + try { + // 获取证书 + X509Certificate cert = certManager.getCertificate(dn); + if (cert == null) { + return false; + } + + // 解码签名 + byte[] signature = Base64.decode(certStr); + + // 获取公钥并转换为 X.509 编码格式 + byte[] publicKey = cert.getPublicKey().getEncoded(); + + // 使用 SDF 接口进行 SM3 哈希(包含公钥和 ID) + byte[] hash = new byte[32]; // SM3 哈希长度为 32 字节 + IntByReference hashLength = new IntByReference(); + + // 使用空指针作为会话句柄(SDFSoft 支持) + Pointer sessionHandle = Pointer.NULL; + + // 默认的 SM2 用户 ID(与签名时保持一致) + String userId = "1234567812345678"; + + // 初始化哈希(包含公钥和用户 ID) + int ret = sdf.SDF_HashInit(sessionHandle, 0, publicKey, userId, userId.length()); + if (ret != 0) { + return false; + } + + // 更新哈希(添加原始数据) + ret = sdf.SDF_HashUpdate(sessionHandle, origBytes, origBytes.length); + if (ret != 0) { + return false; + } + + // 完成哈希 + ret = sdf.SDF_HashFinal(sessionHandle, hash, hashLength); + if (ret != 0) { + return false; + } + + // 使用 SDF 接口进行 SM2 验签 + ret = sdf.SDF_ExternalVerify_ECC(sessionHandle, 0, publicKey, hash, hashLength.getValue(), signature); + return ret == 0; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + @Override + public String dettachedSign(byte[] origBytes, String dn) { + try { + // 获取密钥对和证书 + KeyPair keyPair = certManager.getKeyPair(dn); + X509Certificate cert = certManager.getCertificate(dn); + if (keyPair == null || cert == null) { + throw new RuntimeException("Certificate not found for DN: " + dn); + } + + // 创建 PKCS#7 签名 + CMSSignedDataGenerator generator = new CMSSignedDataGenerator(); + generator.addSignerInfoGenerator( + new JcaSignerInfoGeneratorBuilder( + new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()) + .build( + new org.bouncycastle.operator.jcajce.JcaContentSignerBuilder("SHA256withECDSA") + .setProvider("BC") + .build(keyPair.getPrivate()), + + cert) + ); + generator.addCertificate(new org.bouncycastle.cert.jcajce.JcaX509CertificateHolder(cert)); + + CMSSignedData signedData = generator.generate(new CMSProcessableByteArray(origBytes), false); + + // 返回 Base64 编码的签名 + return new String(Base64.encode(signedData.getEncoded())); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Detached sign failed: " + e.getMessage(), e); + } + } + + @Override + public String dettachedVerify(byte[] origBytes, String certStr) { + try { + // 解码签名 + byte[] signedDataBytes = Base64.decode(certStr); + + // 使用原始数据验证签名 + CMSSignedData signedData = new CMSSignedData(new CMSProcessableByteArray(origBytes), signedDataBytes); + + // 验证签名 + for (Object signerInfoObj : signedData.getSignerInfos().getSigners()) { + org.bouncycastle.cms.SignerInformation signerInfo = (org.bouncycastle.cms.SignerInformation) signerInfoObj; + + // 获取证书 + java.util.Collection certMatches = signedData.getCertificates().getMatches(signerInfo.getSID()); + org.bouncycastle.cert.X509CertificateHolder certHolder = + (org.bouncycastle.cert.X509CertificateHolder) certMatches.iterator().next(); + + X509Certificate cert = new org.bouncycastle.cert.jcajce.JcaX509CertificateConverter() + .setProvider("BC") + .getCertificate(certHolder); + + // 验证签名 + try { + if (signerInfo.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert.getPublicKey()))) { + // 验证证书有效性(检查有效期) + cert.checkValidity(new java.util.Date()); + + return new GenericCertificate(cert); + } + } catch (org.bouncycastle.cms.CMSSignerDigestMismatchException e) { + // 签名验证失败(数据不匹配),不打印堆栈信息 + continue; + } + } + + return null; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @Override + public String dettachedVerifySimple(byte[] origBytes, String certStr) { + try { + // 解码签名 + byte[] signedDataBytes = Base64.decode(certStr); + + // 使用原始数据验证签名 + CMSSignedData signedData = new CMSSignedData(new CMSProcessableByteArray(origBytes), signedDataBytes); + + // 验证签名(不验证证书有效性) + for (Object signerInfoObj : signedData.getSignerInfos().getSigners()) { + org.bouncycastle.cms.SignerInformation signerInfo = (org.bouncycastle.cms.SignerInformation) signerInfoObj; + + // 获取证书 + java.util.Collection certMatches = signedData.getCertificates().getMatches(signerInfo.getSID()); + org.bouncycastle.cert.X509CertificateHolder certHolder = + (org.bouncycastle.cert.X509CertificateHolder) certMatches.iterator().next(); + + X509Certificate cert = new org.bouncycastle.cert.jcajce.JcaX509CertificateConverter() + .setProvider("BC") + .getCertificate(certHolder); + + // 只验证签名是否正确,不检查证书有效期 + try { + if (signerInfo.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert.getPublicKey()))) { + return new GenericCertificate(cert); + } + } catch (org.bouncycastle.cms.CMSSignerDigestMismatchException e) { + // 签名验证失败(数据不匹配),不打印堆栈信息 + continue; + } + } + + return null; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 将 ASN.1 DER 编码的签名转换为 r||s 格式 + */ + private byte[] derToRs(byte[] derSignature) { + int offset = 2; // 跳过 0x30 和长度字节 + + // 读取 r + int rLength = derSignature[offset + 1] & 0xFF; + byte[] r = new byte[32]; + int rStart = offset + 2; + int rCopyLength = Math.min(rLength, 32); + if (rLength <= 32) { + System.arraycopy(derSignature, rStart, r, 32 - rLength, rCopyLength); + } else { + System.arraycopy(derSignature, rStart + (rLength - 32), r, 0, 32); + } + + // 读取 s + offset += 2 + rLength; + int sLength = derSignature[offset + 1] & 0xFF; + byte[] s = new byte[32]; + int sStart = offset + 2; + int sCopyLength = Math.min(sLength, 32); + if (sLength <= 32) { + System.arraycopy(derSignature, sStart, s, 32 - sLength, sCopyLength); + } else { + System.arraycopy(derSignature, sStart + (sLength - 32), s, 0, 32); + } + + // 组合 r||s + byte[] result = new byte[64]; + System.arraycopy(r, 0, result, 0, 32); + System.arraycopy(s, 0, result, 32, 32); + return result; + } + + /** + * 将 r||s 格式的签名转换为 ASN.1 DER 编码 + */ + private byte[] rsToDer(byte[] rsSignature) { + if (rsSignature.length < 64) { + return rsSignature; + } + + byte[] r = new byte[32]; + byte[] s = new byte[32]; + System.arraycopy(rsSignature, 0, r, 0, 32); + System.arraycopy(rsSignature, 32, s, 0, 32); + + r = stripLeadingZeros(r); + s = stripLeadingZeros(s); + + if ((r[0] & 0x80) != 0) { + byte[] temp = new byte[r.length + 1]; + System.arraycopy(r, 0, temp, 1, r.length); + r = temp; + } + if ((s[0] & 0x80) != 0) { + byte[] temp = new byte[s.length + 1]; + System.arraycopy(s, 0, temp, 1, s.length); + s = temp; + } + + int totalLength = 2 + 2 + r.length + 2 + s.length; + byte[] der = new byte[totalLength]; + int offset = 0; + + der[offset++] = 0x30; + der[offset++] = (byte) (totalLength - 2); + + der[offset++] = 0x02; + der[offset++] = (byte) r.length; + System.arraycopy(r, 0, der, offset, r.length); + offset += r.length; + + der[offset++] = 0x02; + der[offset++] = (byte) s.length; + System.arraycopy(s, 0, der, offset, s.length); + + return der; + } + + /** + * 去除字节数组前面的零字节 + */ + private byte[] stripLeadingZeros(byte[] data) { + int start = 0; + while (start < data.length && data[start] == 0) { + start++; + } + if (start == data.length) { + return new byte[]{0}; + } + byte[] result = new byte[data.length - start]; + System.arraycopy(data, start, result, 0, result.length); + return result; + } +} diff --git a/src/main/java/com/cisd/tms/security/openapi/OpenApiSignAuthInterceptor.java b/src/main/java/com/cisd/tms/security/openapi/OpenApiSignAuthInterceptor.java index 6908215..d538776 100644 --- a/src/main/java/com/cisd/tms/security/openapi/OpenApiSignAuthInterceptor.java +++ b/src/main/java/com/cisd/tms/security/openapi/OpenApiSignAuthInterceptor.java @@ -27,6 +27,7 @@ import org.springframework.web.servlet.HandlerInterceptor; @Component @RequiredArgsConstructor + public class OpenApiSignAuthInterceptor implements HandlerInterceptor { private static final String APP_ID_HEADER = "X-App-Id"; From 95bd0604b6fea615b59dbfcbc1d0784e7c232e3b Mon Sep 17 00:00:00 2001 From: cheney Date: Wed, 29 Apr 2026 17:29:49 +0800 Subject: [PATCH 06/19] =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=AF=81=E4=B9=A6?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tms/modules/cert/enums/CertUsage.java | 16 ++++ .../repository/CertificateRepository.java | 2 + .../impl/CertificateRepositoryImpl.java | 26 +++--- .../tms/modules/cert/support/CertUtil.java | 84 +++++++++++++++++++ .../openapi/service/impl/OpenApiService.java | 6 +- 5 files changed, 120 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/cisd/tms/modules/cert/enums/CertUsage.java create mode 100644 src/main/java/com/cisd/tms/modules/cert/support/CertUtil.java diff --git a/src/main/java/com/cisd/tms/modules/cert/enums/CertUsage.java b/src/main/java/com/cisd/tms/modules/cert/enums/CertUsage.java new file mode 100644 index 0000000..b9caf7e --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/enums/CertUsage.java @@ -0,0 +1,16 @@ +package com.cisd.tms.modules.cert.enums; + +/** + * 证书用途 + */ +public enum CertUsage { + digitalSignature, + nonRepudiation, + keyEncipherment, + dataEncipherment, + keyAgreement, + keyCertSign, + cRLSign, + encipherOnly, + decipherOnly +} diff --git a/src/main/java/com/cisd/tms/modules/cert/repository/CertificateRepository.java b/src/main/java/com/cisd/tms/modules/cert/repository/CertificateRepository.java index 501a4cb..6be1b0d 100644 --- a/src/main/java/com/cisd/tms/modules/cert/repository/CertificateRepository.java +++ b/src/main/java/com/cisd/tms/modules/cert/repository/CertificateRepository.java @@ -10,8 +10,10 @@ public interface CertificateRepository { void deleteById(Long id); Optional findById(Long id); Optional findByFingerprint(String fingerprint); + Optional findBySubjectDn(String subjectDn); Optional findByIssuerDnAndSerialNumber(String issuerDn, String serialNumber); boolean existsEntityCertByEntityId(Long entityId); long countByIssuerDn(String issuerDn); Page page(CertificateListRequest request); } + diff --git a/src/main/java/com/cisd/tms/modules/cert/repository/impl/CertificateRepositoryImpl.java b/src/main/java/com/cisd/tms/modules/cert/repository/impl/CertificateRepositoryImpl.java index 47ad9ae..e9e1aed 100644 --- a/src/main/java/com/cisd/tms/modules/cert/repository/impl/CertificateRepositoryImpl.java +++ b/src/main/java/com/cisd/tms/modules/cert/repository/impl/CertificateRepositoryImpl.java @@ -49,6 +49,15 @@ public class CertificateRepositoryImpl implements CertificateRepository { return Optional.ofNullable(mapper.selectOne(wrapper)); } + @Override + public Optional findBySubjectDn(String subjectDn) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(CertificateEntity::getSubjectDn, subjectDn) + .orderByDesc(CertificateEntity::getImportTime) + .last("LIMIT 1"); + return Optional.ofNullable(mapper.selectOne(wrapper)); + } + @Override public Optional findByIssuerDnAndSerialNumber(String issuerDn, String serialNumber) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper() @@ -75,20 +84,12 @@ public class CertificateRepositoryImpl implements CertificateRepository { @Override public Page page(CertificateListRequest request) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - if (StringUtils.hasText(request.getOrgCode())) { - wrapper.apply( - "entity_id IN (SELECT id FROM t_key_entity WHERE org_code LIKE CONCAT('%', {0}, '%'))", - request.getOrgCode() - ); - } - if (StringUtils.hasText(request.getAlgoType())) { - wrapper.eq(CertificateEntity::getAlgoType, request.getAlgoType().toUpperCase()); - } if (StringUtils.hasText(request.getCertType())) { - wrapper.eq(CertificateEntity::getCertType, request.getCertType().toUpperCase()); + wrapper.eq(CertificateEntity::getCertType, request.getCertType()); } - if (StringUtils.hasText(request.getSubject())) { - wrapper.like(CertificateEntity::getSubjectDn, request.getSubject()); + if (StringUtils.hasText(request.getKeyword())) { + wrapper.and(w -> w.like(CertificateEntity::getSubjectDn, request.getKeyword()) + .or().like(CertificateEntity::getIssuerDn, request.getKeyword())); } wrapper.orderByDesc(CertificateEntity::getImportTime); long total = mapper.selectCount(wrapper); @@ -100,3 +101,4 @@ public class CertificateRepositoryImpl implements CertificateRepository { return page; } } + diff --git a/src/main/java/com/cisd/tms/modules/cert/support/CertUtil.java b/src/main/java/com/cisd/tms/modules/cert/support/CertUtil.java new file mode 100644 index 0000000..f8cb5f3 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/cert/support/CertUtil.java @@ -0,0 +1,84 @@ +package com.cisd.tms.modules.cert.support; + + +import com.cisd.tms.modules.cert.enums.CertUsage; +import lombok.SneakyThrows; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.security.NoSuchProviderException; +import java.security.Security; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.HashSet; +import java.util.Set; +import java.util.Base64; + + +public class CertUtil { + static { + Security.addProvider(new BouncyCastleProvider()); + } + + @SneakyThrows + public static X509Certificate convertToX509Cert(String certificateString) { +// X509Certificate certificate = null; +// CertificateFactory cf = null; +// try { +// if (certificateString != null && !certificateString.trim().isEmpty()) { +// certificateString = certificateString.replace("-----BEGIN CERTIFICATE-----\n", "") +// .replace("-----END CERTIFICATE-----", ""); // NEED FOR PEM FORMAT CERT STRING +// byte[] certificateData = Base64.decode(certificateString); +// cf = CertificateFactory.getInstance("X509"); +// certificate = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(certificateData)); +// } +// } catch (CertificateException e) { +// throw new CertificateException(e); +// } +// return certificate; + CertificateFactory CF = CertificateFactory.getInstance("X.509", "BC"); // 从证书工厂中获取X.509的单例类 + return (X509Certificate) CF.generateCertificate(new ByteArrayInputStream(certificateString.getBytes())); // 将文件流的证书转化为证书类 + } + + public static String convertToPem(String cert) throws CertificateEncodingException { + + String cert_begin = "-----BEGIN CERTIFICATE-----\n"; + String end_cert = "\n-----END CERTIFICATE-----"; + + byte[] derCert = cert.getBytes(); + String pemCertPre = Base64.getMimeEncoder().encodeToString(derCert); + String pemCert = cert_begin + pemCertPre + end_cert; + return pemCert; + } + + + public static boolean certUsageCheck(String cert , CertUsage... us ) throws CertificateException, NoSuchProviderException { + if ( null == us || us.length == 0) { + return true; + } + + X509Certificate certificate = convertToX509Cert(cert); + boolean[] usage = certificate.getKeyUsage(); + + if ( null == usage ) { + return false; + } + + boolean checkRet = true; + Set uset = new HashSet(); + for ( CertUsage u : us ) { + uset.add( u.ordinal() ); + } + + for ( int i = 0; i < usage.length; i++ ) { + if ( usage[ i ] ) { + checkRet &= uset.contains( i ); + } + } + + return checkRet; + } + +} diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java index 39deabc..0ace31b 100644 --- a/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java +++ b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java @@ -1,6 +1,8 @@ package com.cisd.tms.modules.openapi.service.impl; import com.cisd.tms.integration.crypto.pcie.service.JnaPcieCryptoService; +import com.cisd.tms.modules.cert.repository.CertificateRepository; +import com.cisd.tms.modules.cert.service.CertificateService; import com.cisd.tms.modules.openapi.service.IOpenApiService; import com.cisd.tms.modules.openapi.service.dto.GenericCertificate; import com.sun.jna.Pointer; @@ -22,7 +24,7 @@ import java.security.cert.X509Certificate; public class OpenApiService implements IOpenApiService { private JnaPcieCryptoService sdf; - + private final CertificateService certificateService; @Override public String rawSign(byte[] origBytes, String dn) { @@ -31,7 +33,7 @@ public class OpenApiService implements IOpenApiService { int keyIndex = 0; // 获取证书和公钥 - X509Certificate cert = certManager.getCertificate(dn); + X509Certificate cert = certificateService.getBySubjectDn(dn); if (cert == null) { throw new RuntimeException("Certificate not found for DN: " + dn); } From 249d857dc10e98d50b66a70e8c757e64599d611a Mon Sep 17 00:00:00 2001 From: cheney Date: Thu, 30 Apr 2026 10:23:18 +0800 Subject: [PATCH 07/19] save --- .../tms/modules/cert/support/CertUtil.java | 4 +- .../openapi/service/impl/OpenApiService.java | 67 ++++++++++--------- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/cisd/tms/modules/cert/support/CertUtil.java b/src/main/java/com/cisd/tms/modules/cert/support/CertUtil.java index f8cb5f3..7124cab 100644 --- a/src/main/java/com/cisd/tms/modules/cert/support/CertUtil.java +++ b/src/main/java/com/cisd/tms/modules/cert/support/CertUtil.java @@ -42,12 +42,12 @@ public class CertUtil { return (X509Certificate) CF.generateCertificate(new ByteArrayInputStream(certificateString.getBytes())); // 将文件流的证书转化为证书类 } - public static String convertToPem(String cert) throws CertificateEncodingException { + public static String convertToPem(X509Certificate cert) throws CertificateEncodingException { String cert_begin = "-----BEGIN CERTIFICATE-----\n"; String end_cert = "\n-----END CERTIFICATE-----"; - byte[] derCert = cert.getBytes(); + byte[] derCert = cert.getEncoded(); String pemCertPre = Base64.getMimeEncoder().encodeToString(derCert); String pemCert = cert_begin + pemCertPre + end_cert; return pemCert; diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java index 0ace31b..77cc765 100644 --- a/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java +++ b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java @@ -1,8 +1,10 @@ package com.cisd.tms.modules.openapi.service.impl; +import com.cisd.tms.integration.crypto.pcie.PcieSessionTemplate; import com.cisd.tms.integration.crypto.pcie.service.JnaPcieCryptoService; import com.cisd.tms.modules.cert.repository.CertificateRepository; import com.cisd.tms.modules.cert.service.CertificateService; +import com.cisd.tms.modules.cert.support.CertUtil; import com.cisd.tms.modules.openapi.service.IOpenApiService; import com.cisd.tms.modules.openapi.service.dto.GenericCertificate; import com.sun.jna.Pointer; @@ -23,6 +25,7 @@ import java.security.cert.X509Certificate; @RequiredArgsConstructor public class OpenApiService implements IOpenApiService { + private final PcieSessionTemplate sessionTemplate; private JnaPcieCryptoService sdf; private final CertificateService certificateService; @@ -45,50 +48,52 @@ public class OpenApiService implements IOpenApiService { byte[] hash = new byte[32]; // SM3 哈希长度为 32 字节 IntByReference hashLength = new IntByReference(); - // 使用空指针作为会话句柄(SDFSoft 支持) - Pointer sessionHandle = Pointer.NULL; - // 默认的 SM2 用户 ID - String userId = "1234567812345678"; + final String userId = "1234567812345678"; - // 初始化哈希(包含公钥和用户 ID) - int ret = sdf.SDF_HashInit(sessionHandle, 0, publicKey, userId, userId.length()); - if (ret != 0) { - throw new RuntimeException("SDF_HashInit failed with code: " + ret); - } + return sessionTemplate.withSession("SM2Sign", (sdf, deviceHandle, sessionHandle) -> { - // 更新哈希(添加原始数据) - ret = sdf.SDF_HashUpdate(sessionHandle, origBytes, origBytes.length); - if (ret != 0) { - throw new RuntimeException("SDF_HashUpdate failed with code: " + ret); - } + // 初始化哈希(包含公钥和用户 ID) + sessionTemplate.ensureSuccess( + "SDF_HashInit", + sdf.SDF_HashInit(sessionHandle, 0, publicKey, userId.getBytes(), 16) + ); - // 完成哈希 - ret = sdf.SDF_HashFinal(sessionHandle, hash, hashLength); - if (ret != 0) { - throw new RuntimeException("SDF_HashFinal failed with code: " + ret); - } + // 更新哈希(添加原始数据) + sessionTemplate.ensureSuccess( + "SDF_HashUpdate", + sdf.SDF_HashUpdate(sessionHandle, origBytes, origBytes.length) + ); - // 使用 SDF 接口进行 SM2 签名 - byte[] signature = new byte[64]; // SM2 签名长度为 64 字节 - ret = sdf.SDF_InternalSign_ECC(sessionHandle, keyIndex, hash, hashLength.getValue(), signature); - if (ret != 0) { - throw new RuntimeException("SDF_InternalSign_ECC failed with code: " + ret); - } + // 完成哈希 + sessionTemplate.ensureSuccess( + "SDF_HashFinal", + sdf.SDF_HashFinal(sessionHandle, hash, hashLength) + ); + + // 使用 SDF 接口进行 SM2 签名 + byte[] signature = new byte[64]; // SM2 签名长度为 64 字节 + sessionTemplate.ensureSuccess( + "SDF_InternalSign_ECC", + sdf.SDF_InternalSign_ECC(sessionHandle, keyIndex, hash, hashLength.getValue(), signature) + ); + + // 返回 Base64 编码的签名 + return new String(Base64.encode(signature)); + }); - // 返回 Base64 编码的签名 - return new String(Base64.encode(signature)); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("Raw sign failed: " + e.getMessage(), e); } } + @Override public boolean rawVerify(byte[] origBytes, String certStr, String dn) { try { // 获取证书 - X509Certificate cert = certManager.getCertificate(dn); + X509Certificate cert = certificateService.getBySubjectDn(dn); if (cert == null) { return false; } @@ -141,7 +146,7 @@ public class OpenApiService implements IOpenApiService { try { // 获取密钥对和证书 KeyPair keyPair = certManager.getKeyPair(dn); - X509Certificate cert = certManager.getCertificate(dn); + X509Certificate cert = certificateService.getBySubjectDn(dn); if (keyPair == null || cert == null) { throw new RuntimeException("Certificate not found for DN: " + dn); } @@ -198,7 +203,7 @@ public class OpenApiService implements IOpenApiService { // 验证证书有效性(检查有效期) cert.checkValidity(new java.util.Date()); - return new GenericCertificate(cert); + return CertUtil.convertToPem(cert); } } catch (org.bouncycastle.cms.CMSSignerDigestMismatchException e) { // 签名验证失败(数据不匹配),不打印堆栈信息 @@ -238,7 +243,7 @@ public class OpenApiService implements IOpenApiService { // 只验证签名是否正确,不检查证书有效期 try { if (signerInfo.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert.getPublicKey()))) { - return new GenericCertificate(cert); + return CertUtil.convertToPem(cert); } } catch (org.bouncycastle.cms.CMSSignerDigestMismatchException e) { // 签名验证失败(数据不匹配),不打印堆栈信息 From f0fcd05acf2442cd8831ccb12902b4883a72c09e Mon Sep 17 00:00:00 2001 From: cheney Date: Thu, 30 Apr 2026 10:29:29 +0800 Subject: [PATCH 08/19] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20sdf=20=E8=B0=83?= =?UTF-8?q?=E7=94=A8=E9=A3=8E=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../openapi/service/impl/OpenApiService.java | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java index 77cc765..455eae6 100644 --- a/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java +++ b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java @@ -108,33 +108,34 @@ public class OpenApiService implements IOpenApiService { byte[] hash = new byte[32]; // SM3 哈希长度为 32 字节 IntByReference hashLength = new IntByReference(); - // 使用空指针作为会话句柄(SDFSoft 支持) - Pointer sessionHandle = Pointer.NULL; - // 默认的 SM2 用户 ID(与签名时保持一致) - String userId = "1234567812345678"; + final String userId = "1234567812345678"; - // 初始化哈希(包含公钥和用户 ID) - int ret = sdf.SDF_HashInit(sessionHandle, 0, publicKey, userId, userId.length()); - if (ret != 0) { - return false; - } + return sessionTemplate.withSession("SM2Verify", (sdf, deviceHandle, sessionHandle) -> { - // 更新哈希(添加原始数据) - ret = sdf.SDF_HashUpdate(sessionHandle, origBytes, origBytes.length); - if (ret != 0) { - return false; - } + // 初始化哈希(包含公钥和用户 ID) + sessionTemplate.ensureSuccess( + "SDF_HashInit", + sdf.SDF_HashInit(sessionHandle, 0, publicKey, userId.getBytes(), 16) + ); - // 完成哈希 - ret = sdf.SDF_HashFinal(sessionHandle, hash, hashLength); - if (ret != 0) { - return false; - } + // 更新哈希(添加原始数据) + sessionTemplate.ensureSuccess( + "SDF_HashUpdate", + sdf.SDF_HashUpdate(sessionHandle, origBytes, origBytes.length) + ); + + // 完成哈希 + sessionTemplate.ensureSuccess( + "SDF_HashFinal", + sdf.SDF_HashFinal(sessionHandle, hash, hashLength) + ); + + // 使用 SDF 接口进行 SM2 验签 + int ret = sdf.SDF_ExternalVerify_ECC(sessionHandle, 0, publicKey, hash, hashLength.getValue(), signature); + return ret == 0; + }); - // 使用 SDF 接口进行 SM2 验签 - ret = sdf.SDF_ExternalVerify_ECC(sessionHandle, 0, publicKey, hash, hashLength.getValue(), signature); - return ret == 0; } catch (Exception e) { e.printStackTrace(); return false; From 45f42e5683a0927cf41fd2e28cb5f39856e4adf6 Mon Sep 17 00:00:00 2001 From: cheney Date: Thu, 30 Apr 2026 10:50:14 +0800 Subject: [PATCH 09/19] =?UTF-8?q?=E5=85=88=E8=B0=83=E8=AF=95=E8=A3=B8?= =?UTF-8?q?=E7=AD=BE=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crypto/pcie/jna/EccRefPublicKey.java | 59 +++++++++++++++++ .../openapi/service/impl/OpenApiService.java | 64 +++++++------------ 2 files changed, 83 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/cisd/tms/integration/crypto/pcie/jna/EccRefPublicKey.java b/src/main/java/com/cisd/tms/integration/crypto/pcie/jna/EccRefPublicKey.java index 39615d5..c745d3b 100644 --- a/src/main/java/com/cisd/tms/integration/crypto/pcie/jna/EccRefPublicKey.java +++ b/src/main/java/com/cisd/tms/integration/crypto/pcie/jna/EccRefPublicKey.java @@ -1,6 +1,16 @@ package com.cisd.tms.integration.crypto.pcie.jna; import com.sun.jna.Structure; +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; +import org.bouncycastle.jce.spec.ECNamedCurveSpec; + +import java.security.PublicKey; +import java.security.Security; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.util.Arrays; import java.util.List; @Structure.FieldOrder({"bits", "x", "y"}) @@ -17,4 +27,53 @@ public class EccRefPublicKey extends Structure { protected List getFieldOrder() { return List.of("bits", "x", "y"); } + + public static EccRefPublicKey fromPublicKey(PublicKey publicKey) { + try { + if (Security.getProvider("BC") == null) { + Security.addProvider(new BouncyCastleProvider()); + } + + ECNamedCurveParameterSpec bcSpec = ECNamedCurveTable.getParameterSpec("sm2p256v1"); + ECNamedCurveSpec spec = new ECNamedCurveSpec("sm2p256v1", bcSpec.getCurve(), bcSpec.getG(), bcSpec.getN()); + + ECPoint w = ((java.security.interfaces.ECPublicKey) publicKey).getW(); + + EccRefPublicKey result = new EccRefPublicKey(); + result.bits = 256; + + byte[] xBytes = w.getAffineX().toByteArray(); + byte[] yBytes = w.getAffineY().toByteArray(); + + // 填充 x 坐标 + int xOffset = result.x.length - xBytes.length; + if (xOffset >= 0) { + System.arraycopy(xBytes, 0, result.x, xOffset, xBytes.length); + } else { + // 如果字节太长,去掉前面的0 + int start = 0; + while (start < xBytes.length && xBytes[start] == 0) { + start++; + } + System.arraycopy(xBytes, start, result.x, result.x.length - (xBytes.length - start), xBytes.length - start); + } + + // 填充 y 坐标 + int yOffset = result.y.length - yBytes.length; + if (yOffset >= 0) { + System.arraycopy(yBytes, 0, result.y, yOffset, yBytes.length); + } else { + // 如果字节太长,去掉前面的0 + int start = 0; + while (start < yBytes.length && yBytes[start] == 0) { + start++; + } + System.arraycopy(yBytes, start, result.y, result.y.length - (yBytes.length - start), yBytes.length - start); + } + + return result; + } catch (Exception e) { + throw new IllegalArgumentException("Failed to convert PublicKey to EccRefPublicKey", e); + } + } } diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java index 455eae6..fba2749 100644 --- a/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java +++ b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java @@ -1,7 +1,10 @@ package com.cisd.tms.modules.openapi.service.impl; import com.cisd.tms.integration.crypto.pcie.PcieSessionTemplate; +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.service.JnaPcieCryptoService; +import com.sun.jna.Structure; import com.cisd.tms.modules.cert.repository.CertificateRepository; import com.cisd.tms.modules.cert.service.CertificateService; import com.cisd.tms.modules.cert.support.CertUtil; @@ -41,8 +44,8 @@ public class OpenApiService implements IOpenApiService { throw new RuntimeException("Certificate not found for DN: " + dn); } - // 获取公钥字节数组 - byte[] publicKey = cert.getPublicKey().getEncoded(); + // 将公钥转换为 EccRefPublicKey + EccRefPublicKey eccRefPublicKey = EccRefPublicKey.fromPublicKey(cert.getPublicKey()); // 使用 SDF 接口进行 SM3 哈希(包含公钥和 ID) byte[] hash = new byte[32]; // SM3 哈希长度为 32 字节 @@ -56,7 +59,7 @@ public class OpenApiService implements IOpenApiService { // 初始化哈希(包含公钥和用户 ID) sessionTemplate.ensureSuccess( "SDF_HashInit", - sdf.SDF_HashInit(sessionHandle, 0, publicKey, userId.getBytes(), 16) + sdf.SDF_HashInit(sessionHandle, 0, eccRefPublicKey, userId.getBytes(), 16) ); // 更新哈希(添加原始数据) @@ -72,14 +75,18 @@ public class OpenApiService implements IOpenApiService { ); // 使用 SDF 接口进行 SM2 签名 - byte[] signature = new byte[64]; // SM2 签名长度为 64 字节 + EccSignature signature = new EccSignature(); sessionTemplate.ensureSuccess( "SDF_InternalSign_ECC", sdf.SDF_InternalSign_ECC(sessionHandle, keyIndex, hash, hashLength.getValue(), signature) ); + // 将 EccSignature 转换为字节数组 + signature.read(); + byte[] signatureBytes = signature.getPointer().getByteArray(0, signature.size()); + // 返回 Base64 编码的签名 - return new String(Base64.encode(signature)); + return new String(Base64.encode(signatureBytes)); }); } catch (Exception e) { @@ -99,10 +106,10 @@ public class OpenApiService implements IOpenApiService { } // 解码签名 - byte[] signature = Base64.decode(certStr); + byte[] signatureBytes = Base64.decode(certStr); - // 获取公钥并转换为 X.509 编码格式 - byte[] publicKey = cert.getPublicKey().getEncoded(); + // 将公钥转换为 EccRefPublicKey + EccRefPublicKey eccRefPublicKey = EccRefPublicKey.fromPublicKey(cert.getPublicKey()); // 使用 SDF 接口进行 SM3 哈希(包含公钥和 ID) byte[] hash = new byte[32]; // SM3 哈希长度为 32 字节 @@ -116,7 +123,7 @@ public class OpenApiService implements IOpenApiService { // 初始化哈希(包含公钥和用户 ID) sessionTemplate.ensureSuccess( "SDF_HashInit", - sdf.SDF_HashInit(sessionHandle, 0, publicKey, userId.getBytes(), 16) + sdf.SDF_HashInit(sessionHandle, 0, eccRefPublicKey, userId.getBytes(), 16) ); // 更新哈希(添加原始数据) @@ -131,8 +138,13 @@ public class OpenApiService implements IOpenApiService { sdf.SDF_HashFinal(sessionHandle, hash, hashLength) ); + // 将字节数组转换为 EccSignature + EccSignature signature = new EccSignature(); + signature.getPointer().write(0, signatureBytes, 0, signatureBytes.length); + signature.read(); + // 使用 SDF 接口进行 SM2 验签 - int ret = sdf.SDF_ExternalVerify_ECC(sessionHandle, 0, publicKey, hash, hashLength.getValue(), signature); + int ret = sdf.SDF_ExternalVerify_ECC(sessionHandle, 0, eccRefPublicKey, hash, hashLength.getValue(), signature); return ret == 0; }); @@ -144,36 +156,8 @@ public class OpenApiService implements IOpenApiService { @Override public String dettachedSign(byte[] origBytes, String dn) { - try { - // 获取密钥对和证书 - KeyPair keyPair = certManager.getKeyPair(dn); - X509Certificate cert = certificateService.getBySubjectDn(dn); - if (keyPair == null || cert == null) { - throw new RuntimeException("Certificate not found for DN: " + dn); - } - - // 创建 PKCS#7 签名 - CMSSignedDataGenerator generator = new CMSSignedDataGenerator(); - generator.addSignerInfoGenerator( - new JcaSignerInfoGeneratorBuilder( - new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()) - .build( - new org.bouncycastle.operator.jcajce.JcaContentSignerBuilder("SHA256withECDSA") - .setProvider("BC") - .build(keyPair.getPrivate()), - - cert) - ); - generator.addCertificate(new org.bouncycastle.cert.jcajce.JcaX509CertificateHolder(cert)); - - CMSSignedData signedData = generator.generate(new CMSProcessableByteArray(origBytes), false); - - // 返回 Base64 编码的签名 - return new String(Base64.encode(signedData.getEncoded())); - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException("Detached sign failed: " + e.getMessage(), e); - } + // 暂时不实现,避免编译错误 + throw new UnsupportedOperationException("dettachedSign is not implemented yet"); } @Override From 0997ddbae2333721cf229c183ecb8233cad29585 Mon Sep 17 00:00:00 2001 From: cheney Date: Wed, 6 May 2026 11:49:28 +0800 Subject: [PATCH 10/19] =?UTF-8?q?=E5=8F=98=E5=9F=BA=EF=BC=8C=E5=90=88?= =?UTF-8?q?=E5=B9=B6=E8=AF=81=E4=B9=A6=E7=AE=A1=E7=90=86=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 ++ .../cert/repository/impl/CertificateRepositoryImpl.java | 5 +---- .../cisd/tms/modules/cert/service/CertificateService.java | 8 ++++++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 3b7d717..4b97e1a 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,8 @@ 3.5.10.1 2.8.5 5.17.0 + UTF-8 + UTF-8 diff --git a/src/main/java/com/cisd/tms/modules/cert/repository/impl/CertificateRepositoryImpl.java b/src/main/java/com/cisd/tms/modules/cert/repository/impl/CertificateRepositoryImpl.java index e9e1aed..b7876b4 100644 --- a/src/main/java/com/cisd/tms/modules/cert/repository/impl/CertificateRepositoryImpl.java +++ b/src/main/java/com/cisd/tms/modules/cert/repository/impl/CertificateRepositoryImpl.java @@ -87,10 +87,7 @@ public class CertificateRepositoryImpl implements CertificateRepository { if (StringUtils.hasText(request.getCertType())) { wrapper.eq(CertificateEntity::getCertType, request.getCertType()); } - if (StringUtils.hasText(request.getKeyword())) { - wrapper.and(w -> w.like(CertificateEntity::getSubjectDn, request.getKeyword()) - .or().like(CertificateEntity::getIssuerDn, request.getKeyword())); - } + wrapper.orderByDesc(CertificateEntity::getImportTime); long total = mapper.selectCount(wrapper); long offset = (long) (request.getPageNum() - 1) * request.getPageSize(); diff --git a/src/main/java/com/cisd/tms/modules/cert/service/CertificateService.java b/src/main/java/com/cisd/tms/modules/cert/service/CertificateService.java index 69915fc..6807848 100644 --- a/src/main/java/com/cisd/tms/modules/cert/service/CertificateService.java +++ b/src/main/java/com/cisd/tms/modules/cert/service/CertificateService.java @@ -11,6 +11,7 @@ import com.cisd.tms.modules.cert.entity.CertificateEntity; import com.cisd.tms.modules.cert.repository.CertificateRepository; import com.cisd.tms.modules.cert.support.CertPemSupport; import com.cisd.tms.modules.cert.support.CertRuntimeStatusResolver; +import com.cisd.tms.modules.cert.support.CertUtil; import org.bouncycastle.asn1.x509.KeyPurposeId; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -200,6 +201,13 @@ public class CertificateService { .orElseThrow(() -> new BizException(ErrorCode.BAD_REQUEST.getCode(), "证书不存在")); } + public X509Certificate getBySubjectDn(String dn) { + CertificateEntity cert = certificateRepository.findBySubjectDn(dn) + .orElseThrow(() -> new BizException(ErrorCode.BAD_REQUEST.getCode(), "证书不存在")); + return CertUtil.convertToX509Cert( cert.getCertData() ); + } + + private void assertValidTime(X509Certificate certificate) { LocalDateTime now = LocalDateTime.now(); LocalDateTime notBefore = LocalDateTime.ofInstant(certificate.getNotBefore().toInstant(), ZoneId.systemDefault()); From 32907333d592542169396db139d088bdc8785e06 Mon Sep 17 00:00:00 2001 From: cheney Date: Tue, 12 May 2026 10:20:03 +0800 Subject: [PATCH 11/19] =?UTF-8?q?=E8=AF=81=E4=B9=A6=E8=B0=83=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cert/service/CertificateService.java | 22 +++++++++++++++---- .../cert/service/TrustedCertService.java | 19 +++++++++++++++- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cisd/tms/modules/cert/service/CertificateService.java b/src/main/java/com/cisd/tms/modules/cert/service/CertificateService.java index 6807848..cc0fc72 100644 --- a/src/main/java/com/cisd/tms/modules/cert/service/CertificateService.java +++ b/src/main/java/com/cisd/tms/modules/cert/service/CertificateService.java @@ -19,6 +19,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.security.cert.X509Certificate; import java.time.LocalDateTime; import java.time.ZoneId; @@ -60,13 +61,26 @@ public class CertificateService { result.setRecords(page.getRecords().stream().map(this::toItem).toList()); return result; } - @Transactional public ImportCertificateResponse importCertificate(MultipartFile file) { - try { - //todo file空值判断 + if ( null == file || file.isEmpty() || file.getSize() < 1) { + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "证书文件为空"); + } - X509Certificate certificate = CertPemSupport.parseCertificate(file.getBytes()); + byte[] data = null; + try { + data = file.getBytes(); + } catch (IOException e) { + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "证书文件读取时 IO 异常"); + } + + return importCertificate(data); + } + + @Transactional + public ImportCertificateResponse importCertificate(byte[] fileData) { + try { + X509Certificate certificate = CertPemSupport.parseCertificate(fileData); // 终端证书入库前先固定 PKI 边界:只接受当前有效、非 CA、用途满足业务认证的实体/用户证书。 assertValidTime(certificate); assertEndEntityCertificate(certificate); diff --git a/src/main/java/com/cisd/tms/modules/cert/service/TrustedCertService.java b/src/main/java/com/cisd/tms/modules/cert/service/TrustedCertService.java index 7f1a7a2..b9385a0 100644 --- a/src/main/java/com/cisd/tms/modules/cert/service/TrustedCertService.java +++ b/src/main/java/com/cisd/tms/modules/cert/service/TrustedCertService.java @@ -21,6 +21,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.Security; import java.security.cert.X509CRL; @@ -83,8 +84,24 @@ public class TrustedCertService { @Transactional public ImportCertificateResponse importTrusted(MultipartFile file, String alias) { + if ( null == file || file.isEmpty() || file.getSize() < 1) { + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "证书文件为空"); + } + + byte[] data = null; try { - X509Certificate certificate = CertPemSupport.parseCertificate(file.getBytes()); + data = file.getBytes(); + } catch (IOException e) { + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "证书文件读取时 IO 异常"); + } + + return importTrusted(data, alias); + } + + @Transactional + public ImportCertificateResponse importTrusted(byte[] fileData, String alias) { + try { + X509Certificate certificate = CertPemSupport.parseCertificate(fileData); // 可信库只保存 CA 证书;终端证书必须走 CertificateService,避免信任锚和业务证书混用。 assertValidTime(certificate); assertTrustedCaCertificate(certificate); From ea63a72a4d80e502df91026d33aafda4c5fa1550 Mon Sep 17 00:00:00 2001 From: cheney Date: Tue, 12 May 2026 11:36:30 +0800 Subject: [PATCH 12/19] =?UTF-8?q?openapi=20=E6=A8=A1=E5=9D=97=EF=BC=8C?= =?UTF-8?q?=E7=AD=BE=E5=90=8D=E9=AA=8C=E7=AD=BE=E4=BB=A3=E7=A0=81=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/cert/service/EntityService.java | 2 +- .../openapi/service/impl/OpenApiService.java | 225 ++++-------------- 2 files changed, 43 insertions(+), 184 deletions(-) diff --git a/src/main/java/com/cisd/tms/modules/cert/service/EntityService.java b/src/main/java/com/cisd/tms/modules/cert/service/EntityService.java index 62daed4..c6ac026 100644 --- a/src/main/java/com/cisd/tms/modules/cert/service/EntityService.java +++ b/src/main/java/com/cisd/tms/modules/cert/service/EntityService.java @@ -376,7 +376,7 @@ public class EntityService { } } - private static final class CardContentSigner implements ContentSigner { + public static final class CardContentSigner implements ContentSigner { private final java.io.ByteArrayOutputStream output = new java.io.ByteArrayOutputStream(); private final int keyIdx; diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java index fba2749..6aeced1 100644 --- a/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java +++ b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java @@ -1,103 +1,64 @@ package com.cisd.tms.modules.openapi.service.impl; import com.cisd.tms.integration.crypto.pcie.PcieSessionTemplate; -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.BackupDataResult; +import com.cisd.tms.integration.crypto.pcie.model.UserKeySm2SignRequest; +import com.cisd.tms.integration.crypto.pcie.model.UserKeySm2VerifyRequest; import com.cisd.tms.integration.crypto.pcie.service.JnaPcieCryptoService; -import com.sun.jna.Structure; -import com.cisd.tms.modules.cert.repository.CertificateRepository; import com.cisd.tms.modules.cert.service.CertificateService; -import com.cisd.tms.modules.cert.support.CertUtil; +import com.cisd.tms.modules.cert.service.EntityService; import com.cisd.tms.modules.openapi.service.IOpenApiService; -import com.cisd.tms.modules.openapi.service.dto.GenericCertificate; -import com.sun.jna.Pointer; -import com.sun.jna.ptr.IntByReference; import lombok.RequiredArgsConstructor; -import org.bouncycastle.cms.CMSProcessableByteArray; -import org.bouncycastle.cms.CMSSignedData; -import org.bouncycastle.cms.CMSSignedDataGenerator; -import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder; -import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; -import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; -import org.bouncycastle.util.encoders.Base64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.security.KeyPair; +import java.nio.charset.StandardCharsets; import java.security.cert.X509Certificate; @RequiredArgsConstructor public class OpenApiService implements IOpenApiService { + private static final byte[] DEFAULT_SM2_USER_ID = "1234567812345678".getBytes(StandardCharsets.UTF_8); + private static final Logger log = LoggerFactory.getLogger(OpenApiService.class); + private final PcieSessionTemplate sessionTemplate; private JnaPcieCryptoService sdf; + + + // 签名验签均从证书 dn 作为索引,所以引入证书服务类 private final CertificateService certificateService; + // 签名依赖实体,所以引入实体服务类 + private EntityService entityService; @Override public String rawSign(byte[] origBytes, String dn) { try { - // 使用固定的密钥索引 0 - int keyIndex = 0; - // 获取证书和公钥 - X509Certificate cert = certificateService.getBySubjectDn(dn); - if (cert == null) { - throw new RuntimeException("Certificate not found for DN: " + dn); - } + // 根据 dn 查到私钥索引号 + int keyIdx = 0; - // 将公钥转换为 EccRefPublicKey - EccRefPublicKey eccRefPublicKey = EccRefPublicKey.fromPublicKey(cert.getPublicKey()); + UserKeySm2SignRequest request = new UserKeySm2SignRequest(); + request.setKeyIndex(keyIdx); + request.setData(origBytes); + request.setUserId(DEFAULT_SM2_USER_ID); - // 使用 SDF 接口进行 SM3 哈希(包含公钥和 ID) - byte[] hash = new byte[32]; // SM3 哈希长度为 32 字节 - IntByReference hashLength = new IntByReference(); + BackupDataResult result = sdf.userKeySignWithSm2Sm3(request); - // 默认的 SM2 用户 ID - final String userId = "1234567812345678"; - - return sessionTemplate.withSession("SM2Sign", (sdf, deviceHandle, sessionHandle) -> { - - // 初始化哈希(包含公钥和用户 ID) - sessionTemplate.ensureSuccess( - "SDF_HashInit", - sdf.SDF_HashInit(sessionHandle, 0, eccRefPublicKey, userId.getBytes(), 16) - ); - - // 更新哈希(添加原始数据) - sessionTemplate.ensureSuccess( - "SDF_HashUpdate", - sdf.SDF_HashUpdate(sessionHandle, origBytes, origBytes.length) - ); - - // 完成哈希 - sessionTemplate.ensureSuccess( - "SDF_HashFinal", - sdf.SDF_HashFinal(sessionHandle, hash, hashLength) - ); - - // 使用 SDF 接口进行 SM2 签名 - EccSignature signature = new EccSignature(); - sessionTemplate.ensureSuccess( - "SDF_InternalSign_ECC", - sdf.SDF_InternalSign_ECC(sessionHandle, keyIndex, hash, hashLength.getValue(), signature) - ); - - // 将 EccSignature 转换为字节数组 - signature.read(); - byte[] signatureBytes = signature.getPointer().getByteArray(0, signature.size()); - - // 返回 Base64 编码的签名 - return new String(Base64.encode(signatureBytes)); - }); + // DER 格式返回 + // Sm2KeySupport.rawSignatureToDer(result.getData()); + // Base64 格式返回 + return java.util.Base64.getEncoder().encodeToString(result.getData()); } catch (Exception e) { - e.printStackTrace(); + log.error("Raw sign failed: " + e.getMessage(), e); throw new RuntimeException("Raw sign failed: " + e.getMessage(), e); } } @Override - public boolean rawVerify(byte[] origBytes, String certStr, String dn) { + public boolean rawVerify(byte[] origBytes, String signature, String dn) { try { // 获取证书 X509Certificate cert = certificateService.getBySubjectDn(dn); @@ -106,50 +67,18 @@ public class OpenApiService implements IOpenApiService { } // 解码签名 - byte[] signatureBytes = Base64.decode(certStr); + byte[] sign = java.util.Base64.getDecoder().decode( signature ); + // 验签 + UserKeySm2VerifyRequest request = new UserKeySm2VerifyRequest(); + request.setSignature( sign ); + request.setData(origBytes); + request.setUserId(DEFAULT_SM2_USER_ID); + sdf.userKeyVerifyWithSm2Sm3(request); - // 将公钥转换为 EccRefPublicKey - EccRefPublicKey eccRefPublicKey = EccRefPublicKey.fromPublicKey(cert.getPublicKey()); - - // 使用 SDF 接口进行 SM3 哈希(包含公钥和 ID) - byte[] hash = new byte[32]; // SM3 哈希长度为 32 字节 - IntByReference hashLength = new IntByReference(); - - // 默认的 SM2 用户 ID(与签名时保持一致) - final String userId = "1234567812345678"; - - return sessionTemplate.withSession("SM2Verify", (sdf, deviceHandle, sessionHandle) -> { - - // 初始化哈希(包含公钥和用户 ID) - sessionTemplate.ensureSuccess( - "SDF_HashInit", - sdf.SDF_HashInit(sessionHandle, 0, eccRefPublicKey, userId.getBytes(), 16) - ); - - // 更新哈希(添加原始数据) - sessionTemplate.ensureSuccess( - "SDF_HashUpdate", - sdf.SDF_HashUpdate(sessionHandle, origBytes, origBytes.length) - ); - - // 完成哈希 - sessionTemplate.ensureSuccess( - "SDF_HashFinal", - sdf.SDF_HashFinal(sessionHandle, hash, hashLength) - ); - - // 将字节数组转换为 EccSignature - EccSignature signature = new EccSignature(); - signature.getPointer().write(0, signatureBytes, 0, signatureBytes.length); - signature.read(); - - // 使用 SDF 接口进行 SM2 验签 - int ret = sdf.SDF_ExternalVerify_ECC(sessionHandle, 0, eccRefPublicKey, hash, hashLength.getValue(), signature); - return ret == 0; - }); + return true; } catch (Exception e) { - e.printStackTrace(); + log.error("Raw sign verify failed: {}", e.getMessage(), e); return false; } } @@ -162,87 +91,17 @@ public class OpenApiService implements IOpenApiService { @Override public String dettachedVerify(byte[] origBytes, String certStr) { - try { - // 解码签名 - byte[] signedDataBytes = Base64.decode(certStr); - - // 使用原始数据验证签名 - CMSSignedData signedData = new CMSSignedData(new CMSProcessableByteArray(origBytes), signedDataBytes); - - // 验证签名 - for (Object signerInfoObj : signedData.getSignerInfos().getSigners()) { - org.bouncycastle.cms.SignerInformation signerInfo = (org.bouncycastle.cms.SignerInformation) signerInfoObj; - - // 获取证书 - java.util.Collection certMatches = signedData.getCertificates().getMatches(signerInfo.getSID()); - org.bouncycastle.cert.X509CertificateHolder certHolder = - (org.bouncycastle.cert.X509CertificateHolder) certMatches.iterator().next(); - - X509Certificate cert = new org.bouncycastle.cert.jcajce.JcaX509CertificateConverter() - .setProvider("BC") - .getCertificate(certHolder); - - // 验证签名 - try { - if (signerInfo.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert.getPublicKey()))) { - // 验证证书有效性(检查有效期) - cert.checkValidity(new java.util.Date()); - - return CertUtil.convertToPem(cert); - } - } catch (org.bouncycastle.cms.CMSSignerDigestMismatchException e) { - // 签名验证失败(数据不匹配),不打印堆栈信息 - continue; - } - } - - return null; - } catch (Exception e) { - e.printStackTrace(); - return null; - } + // 暂时不实现,避免编译错误 + throw new UnsupportedOperationException("dettachedSign is not implemented yet"); } @Override public String dettachedVerifySimple(byte[] origBytes, String certStr) { - try { - // 解码签名 - byte[] signedDataBytes = Base64.decode(certStr); - - // 使用原始数据验证签名 - CMSSignedData signedData = new CMSSignedData(new CMSProcessableByteArray(origBytes), signedDataBytes); - - // 验证签名(不验证证书有效性) - for (Object signerInfoObj : signedData.getSignerInfos().getSigners()) { - org.bouncycastle.cms.SignerInformation signerInfo = (org.bouncycastle.cms.SignerInformation) signerInfoObj; - - // 获取证书 - java.util.Collection certMatches = signedData.getCertificates().getMatches(signerInfo.getSID()); - org.bouncycastle.cert.X509CertificateHolder certHolder = - (org.bouncycastle.cert.X509CertificateHolder) certMatches.iterator().next(); - - X509Certificate cert = new org.bouncycastle.cert.jcajce.JcaX509CertificateConverter() - .setProvider("BC") - .getCertificate(certHolder); - - // 只验证签名是否正确,不检查证书有效期 - try { - if (signerInfo.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert.getPublicKey()))) { - return CertUtil.convertToPem(cert); - } - } catch (org.bouncycastle.cms.CMSSignerDigestMismatchException e) { - // 签名验证失败(数据不匹配),不打印堆栈信息 - continue; - } - } - - return null; - } catch (Exception e) { - e.printStackTrace(); - return null; - } + // 暂时不实现,避免编译错误 + throw new UnsupportedOperationException("dettachedSign is not implemented yet"); } + /** * 将 ASN.1 DER 编码的签名转换为 r||s 格式 */ From cee2a74770ee17fa9b4f83791994bbbeee3d101c Mon Sep 17 00:00:00 2001 From: cheney Date: Tue, 12 May 2026 11:45:18 +0800 Subject: [PATCH 13/19] =?UTF-8?q?=E6=95=B4=E7=90=86=20controller=20?= =?UTF-8?q?=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/controller/OpenApiController.java | 36 +++++++++++++++++++ .../openapi/service/dto/RawSignRequest.java | 19 ++++++++++ 2 files changed, 55 insertions(+) create mode 100644 src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java create mode 100644 src/main/java/com/cisd/tms/modules/openapi/service/dto/RawSignRequest.java diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java b/src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java new file mode 100644 index 0000000..664d41d --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java @@ -0,0 +1,36 @@ +package com.cisd.tms.modules.openapi.service.controller; + + +import com.cisd.tms.common.api.ApiResponse; +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.cert.dto.CrlImportTaskDetailRequest; +import com.cisd.tms.modules.cert.dto.CrlImportTaskResponse; +import com.cisd.tms.modules.openapi.service.dto.RawSignRequest; +import com.cisd.tms.modules.openapi.service.impl.OpenApiService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +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; + +@RestController +@RequestMapping("/api/v1/openapi") +@Tag(name = "OpenApi 管理", description = "对外开放接口") +public class OpenApiController { + + private OpenApiService openApiService; + + + @PostMapping("/rawSign") + @Operation(summary = "裸签名", description = "基于 SM3 SM2 算法编制罗签名") + public ApiResponse rawSign(@Valid @RequestBody RawSignRequest request) { + + String signature = openApiService.rawSign(); + return ApiResponse.success(signature); + } + +} diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/dto/RawSignRequest.java b/src/main/java/com/cisd/tms/modules/openapi/service/dto/RawSignRequest.java new file mode 100644 index 0000000..7248293 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/openapi/service/dto/RawSignRequest.java @@ -0,0 +1,19 @@ +package com.cisd.tms.modules.openapi.service.dto; + +import lombok.Data; + +/** + * RawSignRequest + */ +@Data +public class RawSignRequest { + /** + * 证书 DN(Distinguished Name) + */ + private String dn; + /** + * 待签名原文的 Base64 编码 + */ + private String origBytes; + private String sessionId; +} From 90ed811c1d469b804b2a4f8f809c6f5fed35b24c Mon Sep 17 00:00:00 2001 From: cheney Date: Tue, 12 May 2026 16:02:56 +0800 Subject: [PATCH 14/19] =?UTF-8?q?=E7=AD=BE=E5=90=8D=E8=B0=83=E8=AF=95?= =?UTF-8?q?=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/controller/OpenApiController.java | 94 ++++++++++++++++-- .../openapi/service/dto/RawSignRequest.java | 14 +++ .../openapi/service/impl/OpenApiService.java | 97 ++++++++++++++++--- 3 files changed, 179 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java b/src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java index 664d41d..4520c07 100644 --- a/src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java +++ b/src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java @@ -1,36 +1,110 @@ package com.cisd.tms.modules.openapi.service.controller; - import com.cisd.tms.common.api.ApiResponse; -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.cert.dto.CrlImportTaskDetailRequest; -import com.cisd.tms.modules.cert.dto.CrlImportTaskResponse; import com.cisd.tms.modules.openapi.service.dto.RawSignRequest; +import com.cisd.tms.modules.openapi.service.dto.RawVerifyRequest; +import com.cisd.tms.modules.openapi.service.dto.RawVerifyResponse; import com.cisd.tms.modules.openapi.service.impl.OpenApiService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; 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 java.util.Base64; + +@RequiredArgsConstructor @RestController @RequestMapping("/api/v1/openapi") @Tag(name = "OpenApi 管理", description = "对外开放接口") public class OpenApiController { - private OpenApiService openApiService; + private static final Logger log = LoggerFactory.getLogger(OpenApiController.class); + + private final OpenApiService openApiService; @PostMapping("/rawSign") - @Operation(summary = "裸签名", description = "基于 SM3 SM2 算法编制罗签名") + @Operation(summary = "裸签名", description = "基于 SM3 SM2 算法进行签名,返回 Base64 编码的签名值") public ApiResponse rawSign(@Valid @RequestBody RawSignRequest request) { + log.info("收到裸签名请求, DN: {}", request.getDn()); - String signature = openApiService.rawSign(); - return ApiResponse.success(signature); + // 入参检查 + if (request.getOrigBytes() == null || request.getOrigBytes().trim().isEmpty()) { + log.error("签名请求失败:待签名数据为空"); + return ApiResponse.fail(400, "待签名数据不能为空", null); + } + if (request.getDn() == null || request.getDn().trim().isEmpty()) { + log.error("签名请求失败:证书DN为空"); + return ApiResponse.fail(400, "证书DN不能为空", null); + } + + try { + // Base64 解码原始数据 + byte[] origBytes = Base64.getDecoder().decode(request.getOrigBytes()); + log.info("待签名数据长度: {} bytes", origBytes.length); + + // 调用签名服务 + String signature = openApiService.rawSign(origBytes, request.getDn()); + + log.info("签名成功"); + return ApiResponse.success(signature); + } catch (IllegalArgumentException e) { + log.error("签名请求参数错误: {}", e.getMessage()); + return ApiResponse.fail(400, e.getMessage(), null); + } catch (Exception e) { + log.error("签名失败: {}", e.getMessage(), e); + return ApiResponse.fail(500, "签名失败: " + e.getMessage(), null); + } + } + + @PostMapping("/rawVerify") + @Operation(summary = "裸验签", description = "基于 SM3 SM2 算法进行验签,验证签名的有效性") + public ApiResponse rawVerify(@Valid @RequestBody RawVerifyRequest request) { + log.info("收到裸验签请求, DN: {}", request.getDn()); + + // 入参检查 + if (request.getOrigBytes() == null || request.getOrigBytes().trim().isEmpty()) { + log.error("验签请求失败:待验签数据为空"); + return ApiResponse.fail(400, "待验签数据不能为空", null); + } + if (request.getSignature() == null || request.getSignature().trim().isEmpty()) { + log.error("验签请求失败:签名值为空"); + return ApiResponse.fail(400, "签名值不能为空", null); + } + if (request.getDn() == null || request.getDn().trim().isEmpty()) { + log.error("验签请求失败:证书DN为空"); + return ApiResponse.fail(400, "证书DN不能为空", null); + } + + try { + // Base64 解码原始数据 + byte[] origBytes = Base64.getDecoder().decode(request.getOrigBytes()); + log.info("待验签数据长度: {} bytes", origBytes.length); + log.info("签名数据长度: {} bytes", request.getSignature().length()); + + // 调用验签服务 + boolean verified = openApiService.rawVerify(origBytes, request.getSignature(), request.getDn()); + + if (verified) { + log.info("验签成功"); + return ApiResponse.success(RawVerifyResponse.success(request.getDn())); + } else { + log.warn("验签失败"); + return ApiResponse.success(RawVerifyResponse.failure(request.getDn(), "签名验证失败")); + } + } catch (IllegalArgumentException e) { + log.error("验签请求参数错误: {}", e.getMessage()); + return ApiResponse.fail(400, e.getMessage(), null); + } catch (Exception e) { + log.error("验签失败: {}", e.getMessage(), e); + return ApiResponse.success(RawVerifyResponse.failure(request.getDn(), e.getMessage())); + } } } diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/dto/RawSignRequest.java b/src/main/java/com/cisd/tms/modules/openapi/service/dto/RawSignRequest.java index 7248293..8fbf51a 100644 --- a/src/main/java/com/cisd/tms/modules/openapi/service/dto/RawSignRequest.java +++ b/src/main/java/com/cisd/tms/modules/openapi/service/dto/RawSignRequest.java @@ -1,19 +1,33 @@ package com.cisd.tms.modules.openapi.service.dto; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import lombok.Data; /** * RawSignRequest + * 签名请求参数 */ @Data +@Schema(description = "签名请求") public class RawSignRequest { /** * 证书 DN(Distinguished Name) */ + @NotBlank(message = "证书DN不能为空") + @Schema(description = "证书DN", example = "CN=Initial Entity,OU=Initial OU,O=Initial Org,L=Beijing,ST=Beijing,C=CN") private String dn; + /** * 待签名原文的 Base64 编码 */ + @NotBlank(message = "待签名原文不能为空") + @Schema(description = "待签名原文的Base64编码", example = "dGVzdCBtZXNzYWdl") private String origBytes; + + /** + * 会话ID(可选) + */ + @Schema(description = "会话ID", example = "session-12345") private String sessionId; } diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java index 6aeced1..b2ca414 100644 --- a/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java +++ b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java @@ -1,21 +1,25 @@ package com.cisd.tms.modules.openapi.service.impl; +import com.cisd.tms.common.enums.ErrorCode; +import com.cisd.tms.common.exception.BizException; import com.cisd.tms.integration.crypto.pcie.PcieSessionTemplate; import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult; import com.cisd.tms.integration.crypto.pcie.model.UserKeySm2SignRequest; import com.cisd.tms.integration.crypto.pcie.model.UserKeySm2VerifyRequest; import com.cisd.tms.integration.crypto.pcie.service.JnaPcieCryptoService; +import com.cisd.tms.modules.cert.entity.KeyEntity; import com.cisd.tms.modules.cert.service.CertificateService; import com.cisd.tms.modules.cert.service.EntityService; import com.cisd.tms.modules.openapi.service.IOpenApiService; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; import java.nio.charset.StandardCharsets; import java.security.cert.X509Certificate; - +@Service @RequiredArgsConstructor public class OpenApiService implements IOpenApiService { @@ -23,62 +27,123 @@ public class OpenApiService implements IOpenApiService { private static final Logger log = LoggerFactory.getLogger(OpenApiService.class); private final PcieSessionTemplate sessionTemplate; - private JnaPcieCryptoService sdf; - + private final JnaPcieCryptoService sdf; // 签名验签均从证书 dn 作为索引,所以引入证书服务类 private final CertificateService certificateService; // 签名依赖实体,所以引入实体服务类 - private EntityService entityService; + private final EntityService entityService; @Override public String rawSign(byte[] origBytes, String dn) { - try { + log.info("===== 开始执行 SM2 签名 ====="); + + // 入参检查 + if (origBytes == null || origBytes.length == 0) { + log.error("签名失败:待签名数据为空"); + throw new IllegalArgumentException("待签名数据不能为空"); + } + if (dn == null || dn.trim().isEmpty()) { + log.error("签名失败:证书DN为空"); + throw new IllegalArgumentException("证书DN不能为空"); + } - // 根据 dn 查到私钥索引号 - int keyIdx = 0; + log.info("待签名数据长度: {} bytes", origBytes.length); + log.info("证书DN: {}", dn); + + try { + // 根据 dn 查找证书 + X509Certificate cert = certificateService.getBySubjectDn(dn); + log.info("找到证书: {}", cert.getSubjectDN()); + + // 根据公钥查找实体 + byte[] publicKeyBytes = cert.getPublicKey().getEncoded(); + var entityIdOpt = entityService.findByCertificatePublicKey(publicKeyBytes); + + if (entityIdOpt.isEmpty()) { + log.error("签名失败:证书 {} 未绑定实体", dn); + throw new BizException(ErrorCode.BAD_REQUEST.getCode(), "证书未绑定实体,无法进行签名"); + } + + Long entityId = entityIdOpt.get(); + KeyEntity entity = entityService.getById(entityId); + int keyIdx = entity.getKeyIdx(); + log.info("找到实体密钥索引: {}", keyIdx); UserKeySm2SignRequest request = new UserKeySm2SignRequest(); request.setKeyIndex(keyIdx); request.setData(origBytes); request.setUserId(DEFAULT_SM2_USER_ID); + log.info("调用密码卡 SM2 签名接口..."); BackupDataResult result = sdf.userKeySignWithSm2Sm3(request); - // DER 格式返回 - // Sm2KeySupport.rawSignatureToDer(result.getData()); - // Base64 格式返回 - return java.util.Base64.getEncoder().encodeToString(result.getData()); + String signature = java.util.Base64.getEncoder().encodeToString(result.getData()); + log.info("签名成功,签名长度: {} bytes", result.getLength()); + log.info("===== SM2 签名完成 ====="); + + return signature; + } catch (BizException e) { + log.error("签名业务异常: {}", e.getMessage()); + throw e; } catch (Exception e) { - log.error("Raw sign failed: " + e.getMessage(), e); - throw new RuntimeException("Raw sign failed: " + e.getMessage(), e); + log.error("签名失败: {}", e.getMessage(), e); + throw new RuntimeException("签名失败: " + e.getMessage(), e); } } @Override public boolean rawVerify(byte[] origBytes, String signature, String dn) { + log.info("===== 开始执行 SM2 验签 ====="); + + // 入参检查 + if (origBytes == null || origBytes.length == 0) { + log.error("验签失败:待验签数据为空"); + throw new IllegalArgumentException("待验签数据不能为空"); + } + if (signature == null || signature.trim().isEmpty()) { + log.error("验签失败:签名值为空"); + throw new IllegalArgumentException("签名值不能为空"); + } + if (dn == null || dn.trim().isEmpty()) { + log.error("验签失败:证书DN为空"); + throw new IllegalArgumentException("证书DN不能为空"); + } + + log.info("待验签数据长度: {} bytes", origBytes.length); + log.info("证书DN: {}", dn); + try { // 获取证书 X509Certificate cert = certificateService.getBySubjectDn(dn); if (cert == null) { + log.error("验签失败:未找到DN为 {} 的证书", dn); return false; } + log.info("找到证书: {}", cert.getSubjectDN()); + // 解码签名 - byte[] sign = java.util.Base64.getDecoder().decode( signature ); + byte[] sign = java.util.Base64.getDecoder().decode(signature); + log.info("签名数据长度: {} bytes", sign.length); + // 验签 UserKeySm2VerifyRequest request = new UserKeySm2VerifyRequest(); - request.setSignature( sign ); + request.setSignature(sign); request.setData(origBytes); request.setUserId(DEFAULT_SM2_USER_ID); + + log.info("调用密码卡 SM2 验签接口..."); sdf.userKeyVerifyWithSm2Sm3(request); + log.info("验签成功"); + log.info("===== SM2 验签完成 ====="); return true; } catch (Exception e) { - log.error("Raw sign verify failed: {}", e.getMessage(), e); + log.error("验签失败: {}", e.getMessage(), e); return false; } } From caf29a3084c1a3f5bc1312338e9208794b95dd83 Mon Sep 17 00:00:00 2001 From: cheney Date: Tue, 12 May 2026 16:21:03 +0800 Subject: [PATCH 15/19] =?UTF-8?q?=E5=85=AC=E9=92=A5=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E7=A1=AE=E8=AE=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../openapi/service/impl/OpenApiService.java | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java index b2ca414..2f7b534 100644 --- a/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java +++ b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java @@ -2,10 +2,11 @@ package com.cisd.tms.modules.openapi.service.impl; 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.PcieSessionTemplate; import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult; +import com.cisd.tms.integration.crypto.pcie.model.EccExternalVerifyRequest; import com.cisd.tms.integration.crypto.pcie.model.UserKeySm2SignRequest; -import com.cisd.tms.integration.crypto.pcie.model.UserKeySm2VerifyRequest; import com.cisd.tms.integration.crypto.pcie.service.JnaPcieCryptoService; import com.cisd.tms.modules.cert.entity.KeyEntity; import com.cisd.tms.modules.cert.service.CertificateService; @@ -14,7 +15,6 @@ import com.cisd.tms.modules.openapi.service.IOpenApiService; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Service; import java.nio.charset.StandardCharsets; import java.security.cert.X509Certificate; @@ -96,7 +96,7 @@ public class OpenApiService implements IOpenApiService { @Override public boolean rawVerify(byte[] origBytes, String signature, String dn) { - log.info("===== 开始执行 SM2 验签 ====="); + log.info("===== 开始执行 SM2 外部公钥验签 ====="); // 入参检查 if (origBytes == null || origBytes.length == 0) { @@ -125,21 +125,26 @@ public class OpenApiService implements IOpenApiService { log.info("找到证书: {}", cert.getSubjectDN()); + // 获取证书中的公钥 + byte[] publicKeyBytes = cert.getPublicKey().getEncoded(); + log.info("公钥数据长度: {} bytes", publicKeyBytes.length); + // 解码签名 byte[] sign = java.util.Base64.getDecoder().decode(signature); log.info("签名数据长度: {} bytes", sign.length); - // 验签 - UserKeySm2VerifyRequest request = new UserKeySm2VerifyRequest(); - request.setSignature(sign); + // 使用外部公钥验签 + EccExternalVerifyRequest request = new EccExternalVerifyRequest(); + request.setAlgId(Gm0018AlgorithmIds.SM2_SIGN); + request.setPublicKeyBlob(publicKeyBytes); request.setData(origBytes); - request.setUserId(DEFAULT_SM2_USER_ID); - - log.info("调用密码卡 SM2 验签接口..."); - sdf.userKeyVerifyWithSm2Sm3(request); + request.setSignature(sign); + + log.info("调用密码卡 SM2 外部公钥验签接口..."); + sdf.eccExternalVerify(request); log.info("验签成功"); - log.info("===== SM2 验签完成 ====="); + log.info("===== SM2 外部公钥验签完成 ====="); return true; } catch (Exception e) { From aa538ee1d70690c21c6d08a4a6d7104983a6006c Mon Sep 17 00:00:00 2001 From: cheney Date: Tue, 12 May 2026 17:11:25 +0800 Subject: [PATCH 16/19] =?UTF-8?q?=E9=AA=8C=E7=AD=BE=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=EF=BC=8C=E5=85=A5=E5=8F=82=E6=95=B0=E6=8D=AE=E9=95=BF=E5=BA=A6?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/crypto/pcie/Gm0018AlgorithmIds.java | 1 + .../integration/crypto/pcie/jna/EccRefPublicKey.java | 9 +++++++++ .../modules/openapi/service/impl/OpenApiService.java | 12 +++++++----- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/cisd/tms/integration/crypto/pcie/Gm0018AlgorithmIds.java b/src/main/java/com/cisd/tms/integration/crypto/pcie/Gm0018AlgorithmIds.java index 4e18a5e..decdeb8 100644 --- a/src/main/java/com/cisd/tms/integration/crypto/pcie/Gm0018AlgorithmIds.java +++ b/src/main/java/com/cisd/tms/integration/crypto/pcie/Gm0018AlgorithmIds.java @@ -32,6 +32,7 @@ public final class Gm0018AlgorithmIds { public static final int RSA_ENCRYPT = 0x00010002; public static final int SM2_SIGN = 0x00020100; + public static final int SM2_SIGN_1 = 0x00020200; public static final int SM2_KEY_EXCHANGE = 0x00020200; public static final int SM2_ENCRYPT = 0x00020400; diff --git a/src/main/java/com/cisd/tms/integration/crypto/pcie/jna/EccRefPublicKey.java b/src/main/java/com/cisd/tms/integration/crypto/pcie/jna/EccRefPublicKey.java index c745d3b..4fd2e1a 100644 --- a/src/main/java/com/cisd/tms/integration/crypto/pcie/jna/EccRefPublicKey.java +++ b/src/main/java/com/cisd/tms/integration/crypto/pcie/jna/EccRefPublicKey.java @@ -27,6 +27,15 @@ public class EccRefPublicKey extends Structure { protected List getFieldOrder() { return List.of("bits", "x", "y"); } + public static byte[] fromPublicKeyToBlob(PublicKey publicKey){ + EccRefPublicKey eccRefPublicKey = EccRefPublicKey.fromPublicKey(publicKey); + return toBytes(eccRefPublicKey); + } + + private static byte[] toBytes(Structure structure) { + structure.read(); + return structure.getPointer().getByteArray(0, structure.size()); + } public static EccRefPublicKey fromPublicKey(PublicKey publicKey) { try { diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java index 2f7b534..8b2ab9e 100644 --- a/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java +++ b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java @@ -4,6 +4,7 @@ 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.PcieSessionTemplate; +import com.cisd.tms.integration.crypto.pcie.jna.EccRefPublicKey; import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult; import com.cisd.tms.integration.crypto.pcie.model.EccExternalVerifyRequest; import com.cisd.tms.integration.crypto.pcie.model.UserKeySm2SignRequest; @@ -15,6 +16,7 @@ import com.cisd.tms.modules.openapi.service.IOpenApiService; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; import java.nio.charset.StandardCharsets; import java.security.cert.X509Certificate; @@ -125,9 +127,9 @@ public class OpenApiService implements IOpenApiService { log.info("找到证书: {}", cert.getSubjectDN()); - // 获取证书中的公钥 - byte[] publicKeyBytes = cert.getPublicKey().getEncoded(); - log.info("公钥数据长度: {} bytes", publicKeyBytes.length); + // 将证书中的公钥转换为 EccRefPublicKey 结构 + byte[] publicKeyBlob = EccRefPublicKey.fromPublicKeyToBlob(cert.getPublicKey()); + log.info("公钥Blob长度: {} bytes", publicKeyBlob.length); // 解码签名 byte[] sign = java.util.Base64.getDecoder().decode(signature); @@ -135,8 +137,8 @@ public class OpenApiService implements IOpenApiService { // 使用外部公钥验签 EccExternalVerifyRequest request = new EccExternalVerifyRequest(); - request.setAlgId(Gm0018AlgorithmIds.SM2_SIGN); - request.setPublicKeyBlob(publicKeyBytes); + request.setAlgId(Gm0018AlgorithmIds.SM2_SIGN_1); + request.setPublicKeyBlob(publicKeyBlob); request.setData(origBytes); request.setSignature(sign); From e15ccf59731b226d6aae76275b0859cc62784257 Mon Sep 17 00:00:00 2001 From: cheney Date: Wed, 13 May 2026 09:51:22 +0800 Subject: [PATCH 17/19] =?UTF-8?q?=E9=AA=8C=E7=AD=BE=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qodana.yaml | 43 +++++++++++++ .../crypto/pcie/jna/EccRefPublicKey.java | 2 +- .../pcie/model/ExternalSm2VerifyRequest.java | 61 +++++++++++++++++++ .../pcie/service/JnaPcieCryptoService.java | 2 +- .../openapi/service/dto/RawVerifyRequest.java | 19 ++++++ .../service/dto/RawVerifyResponse.java | 42 +++++++++++++ .../openapi/service/impl/OpenApiService.java | 36 +++++++++-- 7 files changed, 199 insertions(+), 6 deletions(-) create mode 100644 qodana.yaml create mode 100644 src/main/java/com/cisd/tms/integration/crypto/pcie/model/ExternalSm2VerifyRequest.java create mode 100644 src/main/java/com/cisd/tms/modules/openapi/service/dto/RawVerifyRequest.java create mode 100644 src/main/java/com/cisd/tms/modules/openapi/service/dto/RawVerifyResponse.java diff --git a/qodana.yaml b/qodana.yaml new file mode 100644 index 0000000..84cc32f --- /dev/null +++ b/qodana.yaml @@ -0,0 +1,43 @@ +#-------------------------------------------------------------------------------# +# Qodana analysis is configured by qodana.yaml file # +# https://www.jetbrains.com/help/qodana/qodana-yaml.html # +#-------------------------------------------------------------------------------# +version: "1.0" + +#Specify inspection profile for code analysis +profile: + name: qodana.starter + +#Enable inspections +#include: +# - name: + +#Disable inspections +#exclude: +# - name: +# paths: +# - + +projectJDK: "17" #(Applied in CI/CD pipeline) + +#Execute shell command before Qodana execution (Applied in CI/CD pipeline) +#bootstrap: sh ./prepare-qodana.sh + +#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) +#plugins: +# - id: #(plugin id can be found at https://plugins.jetbrains.com) + +# Quality gate. Will fail the CI/CD pipeline if any condition is not met +# severityThresholds - configures maximum thresholds for different problem severities +# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code +# Code Coverage is available in Ultimate and Ultimate Plus plans +#failureConditions: +# severityThresholds: +# any: 15 +# critical: 5 +# testCoverageThresholds: +# fresh: 70 +# total: 50 + +#Specify Qodana linter for analysis (Applied in CI/CD pipeline) +linter: jetbrains/qodana-jvm:2025.2 diff --git a/src/main/java/com/cisd/tms/integration/crypto/pcie/jna/EccRefPublicKey.java b/src/main/java/com/cisd/tms/integration/crypto/pcie/jna/EccRefPublicKey.java index 4fd2e1a..4ae3726 100644 --- a/src/main/java/com/cisd/tms/integration/crypto/pcie/jna/EccRefPublicKey.java +++ b/src/main/java/com/cisd/tms/integration/crypto/pcie/jna/EccRefPublicKey.java @@ -33,7 +33,7 @@ public class EccRefPublicKey extends Structure { } private static byte[] toBytes(Structure structure) { - structure.read(); + structure.write(); return structure.getPointer().getByteArray(0, structure.size()); } diff --git a/src/main/java/com/cisd/tms/integration/crypto/pcie/model/ExternalSm2VerifyRequest.java b/src/main/java/com/cisd/tms/integration/crypto/pcie/model/ExternalSm2VerifyRequest.java new file mode 100644 index 0000000..3192b8b --- /dev/null +++ b/src/main/java/com/cisd/tms/integration/crypto/pcie/model/ExternalSm2VerifyRequest.java @@ -0,0 +1,61 @@ +package com.cisd.tms.integration.crypto.pcie.model; + +/** + * ExternalSm2VerifyRequest + * 外部 SM2 验签请求参数 + * 包含数据的 SM3 哈希处理(带公钥)和验签逻辑 + */ +public class ExternalSm2VerifyRequest { + + /** + * 外部公钥(原始字节,如从证书中获取的 PublicKey.getEncoded()) + */ + private byte[] publicKey; + + /** + * 待验签的数据 + */ + private byte[] data; + + /** + * 签名值(DER 格式或原始格式的签名) + */ + private byte[] signature; + + /** + * SM2 用户 ID(可选,默认 "1234567812345678") + */ + private byte[] userId; + + public byte[] getPublicKey() { + return publicKey; + } + + public void setPublicKey(byte[] publicKey) { + this.publicKey = publicKey; + } + + public byte[] getData() { + return data; + } + + public void setData(byte[] data) { + this.data = data; + } + + public byte[] getSignature() { + return signature; + } + + public void setSignature(byte[] signature) { + this.signature = signature; + } + + public byte[] getUserId() { + return userId; + } + + public void setUserId(byte[] userId) { + this.userId = userId; + } +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/integration/crypto/pcie/service/JnaPcieCryptoService.java b/src/main/java/com/cisd/tms/integration/crypto/pcie/service/JnaPcieCryptoService.java index 53af823..8aa3295 100644 --- a/src/main/java/com/cisd/tms/integration/crypto/pcie/service/JnaPcieCryptoService.java +++ b/src/main/java/com/cisd/tms/integration/crypto/pcie/service/JnaPcieCryptoService.java @@ -2620,4 +2620,4 @@ public class JnaPcieCryptoService implements PcieCryptoService { } return Arrays.copyOf(userId, userId.length); } -} +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/dto/RawVerifyRequest.java b/src/main/java/com/cisd/tms/modules/openapi/service/dto/RawVerifyRequest.java new file mode 100644 index 0000000..0a6f39b --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/openapi/service/dto/RawVerifyRequest.java @@ -0,0 +1,19 @@ +package com.cisd.tms.modules.openapi.service.dto; + + +import lombok.Data; + +@Data +public class RawVerifyRequest { + /** + * 证书 DN 或机构号 + */ + private String dn; + private String origBytes; + private String sessionId; + /** + * 裸签名串(Base64) + */ + private String signature; + +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/dto/RawVerifyResponse.java b/src/main/java/com/cisd/tms/modules/openapi/service/dto/RawVerifyResponse.java new file mode 100644 index 0000000..c3186d2 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/openapi/service/dto/RawVerifyResponse.java @@ -0,0 +1,42 @@ +package com.cisd.tms.modules.openapi.service.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * RawVerifyResponse + * 验签响应结果 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "验签响应") +public class RawVerifyResponse { + /** + * 验签结果:true表示验签通过,false表示验签失败 + */ + @Schema(description = "验签结果", example = "true") + private boolean verified; + + /** + * 结果描述信息 + */ + @Schema(description = "结果描述", example = "验签成功") + private String message; + + /** + * 证书DN + */ + @Schema(description = "证书DN", example = "CN=Initial Entity,OU=Initial OU,O=Initial Org,L=Beijing,ST=Beijing,C=CN") + private String dn; + + public static RawVerifyResponse success(String dn) { + return new RawVerifyResponse(true, "验签成功", dn); + } + + public static RawVerifyResponse failure(String dn, String reason) { + return new RawVerifyResponse(false, "验签失败: " + reason, dn); + } +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java index 8b2ab9e..3da54c1 100644 --- a/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java +++ b/src/main/java/com/cisd/tms/modules/openapi/service/impl/OpenApiService.java @@ -5,6 +5,7 @@ import com.cisd.tms.common.exception.BizException; import com.cisd.tms.integration.crypto.pcie.Gm0018AlgorithmIds; import com.cisd.tms.integration.crypto.pcie.PcieSessionTemplate; 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.BackupDataResult; import com.cisd.tms.integration.crypto.pcie.model.EccExternalVerifyRequest; import com.cisd.tms.integration.crypto.pcie.model.UserKeySm2SignRequest; @@ -13,6 +14,7 @@ import com.cisd.tms.modules.cert.entity.KeyEntity; import com.cisd.tms.modules.cert.service.CertificateService; import com.cisd.tms.modules.cert.service.EntityService; import com.cisd.tms.modules.openapi.service.IOpenApiService; +import com.sun.jna.ptr.IntByReference; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -94,7 +96,13 @@ public class OpenApiService implements IOpenApiService { throw new RuntimeException("签名失败: " + e.getMessage(), e); } } - + private static String toHex(byte[] bytes) { + StringBuilder builder = new StringBuilder(bytes.length * 2); + for (byte b : bytes) { + builder.append(String.format("%02x", b)); + } + return builder.toString(); + } @Override public boolean rawVerify(byte[] origBytes, String signature, String dn) { @@ -128,18 +136,38 @@ public class OpenApiService implements IOpenApiService { log.info("找到证书: {}", cert.getSubjectDN()); // 将证书中的公钥转换为 EccRefPublicKey 结构 + EccRefPublicKey publicKey = EccRefPublicKey.fromPublicKey(cert.getPublicKey()); + log.info("公钥 x: {}, y: {}", toHex(publicKey.x), toHex(publicKey.y)); byte[] publicKeyBlob = EccRefPublicKey.fromPublicKeyToBlob(cert.getPublicKey()); - log.info("公钥Blob长度: {} bytes", publicKeyBlob.length); + log.info("公钥Blob {} 长度: {} bytes", toHex(publicKeyBlob), publicKeyBlob.length); + // 解码签名 byte[] sign = java.util.Base64.getDecoder().decode(signature); log.info("签名数据长度: {} bytes", sign.length); - // 使用外部公钥验签 + // 使用带公钥的 SM3 计算数据哈希 + log.info("使用带公钥的 SM3 计算数据哈希..."); + byte[] digest = sessionTemplate.withSession("SDF_HashFinal_SM2", (lib, deviceHandle, sessionHandle) -> { + sessionTemplate.ensureSuccess( + "SDF_HashInit", + lib.SDF_HashInit(sessionHandle, Gm0018AlgorithmIds.SM3, publicKey, DEFAULT_SM2_USER_ID, DEFAULT_SM2_USER_ID.length) + ); + if (origBytes.length > 0) { + sessionTemplate.ensureSuccess("SDF_HashUpdate", lib.SDF_HashUpdate(sessionHandle, origBytes, origBytes.length)); + } + byte[] out = new byte[32]; + IntByReference outLen = new IntByReference(out.length); + sessionTemplate.ensureSuccess("SDF_HashFinal", lib.SDF_HashFinal(sessionHandle, out, outLen)); + return out; + }); + log.info("SM3 哈希计算完成,哈希{} 长度: {} bytes", toHex(digest), digest.length); + + // 使用外部公钥验签(注意:传递的是哈希值而不是原始数据) EccExternalVerifyRequest request = new EccExternalVerifyRequest(); request.setAlgId(Gm0018AlgorithmIds.SM2_SIGN_1); request.setPublicKeyBlob(publicKeyBlob); - request.setData(origBytes); + request.setData(digest); request.setSignature(sign); log.info("调用密码卡 SM2 外部公钥验签接口..."); From b071c133ae152fe23237edc8059a137774492056 Mon Sep 17 00:00:00 2001 From: cheney Date: Wed, 13 May 2026 10:09:19 +0800 Subject: [PATCH 18/19] =?UTF-8?q?=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/controller/OpenApiController.java | 8 ++++++-- .../openapi/service/dto/RawSignResponse.java | 13 +++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/cisd/tms/modules/openapi/service/dto/RawSignResponse.java diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java b/src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java index 4520c07..5bd047e 100644 --- a/src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java +++ b/src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java @@ -2,6 +2,7 @@ package com.cisd.tms.modules.openapi.service.controller; import com.cisd.tms.common.api.ApiResponse; import com.cisd.tms.modules.openapi.service.dto.RawSignRequest; +import com.cisd.tms.modules.openapi.service.dto.RawSignResponse; import com.cisd.tms.modules.openapi.service.dto.RawVerifyRequest; import com.cisd.tms.modules.openapi.service.dto.RawVerifyResponse; import com.cisd.tms.modules.openapi.service.impl.OpenApiService; @@ -31,7 +32,7 @@ public class OpenApiController { @PostMapping("/rawSign") @Operation(summary = "裸签名", description = "基于 SM3 SM2 算法进行签名,返回 Base64 编码的签名值") - public ApiResponse rawSign(@Valid @RequestBody RawSignRequest request) { + public ApiResponse rawSign(@Valid @RequestBody RawSignRequest request) { log.info("收到裸签名请求, DN: {}", request.getDn()); // 入参检查 @@ -53,7 +54,7 @@ public class OpenApiController { String signature = openApiService.rawSign(origBytes, request.getDn()); log.info("签名成功"); - return ApiResponse.success(signature); + return ApiResponse.success(new RawSignResponse(signature)); } catch (IllegalArgumentException e) { log.error("签名请求参数错误: {}", e.getMessage()); return ApiResponse.fail(400, e.getMessage(), null); @@ -107,4 +108,7 @@ public class OpenApiController { } } + + + } diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/dto/RawSignResponse.java b/src/main/java/com/cisd/tms/modules/openapi/service/dto/RawSignResponse.java new file mode 100644 index 0000000..b9f0dd8 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/openapi/service/dto/RawSignResponse.java @@ -0,0 +1,13 @@ +package com.cisd.tms.modules.openapi.service.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class RawSignResponse { + /** + * 裸签名串(PKCS#1 格式,Base64 编码) + */ + private String signature; +} From dc126e58128589962641b21fda0cd56dc7e6c321 Mon Sep 17 00:00:00 2001 From: cheney Date: Wed, 13 May 2026 10:20:46 +0800 Subject: [PATCH 19/19] =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/controller/OpenApiController.java | 75 ++++++++++++++++++- .../service/dto/DettachedSignRequest.java | 20 +++++ .../service/dto/DettachedVerifyRequest.java | 11 +++ .../service/dto/UploadCertRequest.java | 16 ++++ 4 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/cisd/tms/modules/openapi/service/dto/DettachedSignRequest.java create mode 100644 src/main/java/com/cisd/tms/modules/openapi/service/dto/DettachedVerifyRequest.java create mode 100644 src/main/java/com/cisd/tms/modules/openapi/service/dto/UploadCertRequest.java diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java b/src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java index 5bd047e..17c50c8 100644 --- a/src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java +++ b/src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java @@ -1,10 +1,15 @@ package com.cisd.tms.modules.openapi.service.controller; import com.cisd.tms.common.api.ApiResponse; +import com.cisd.tms.modules.cert.dto.ImportCertificateResponse; +import com.cisd.tms.modules.cert.service.CertificateService; +import com.cisd.tms.modules.openapi.service.dto.DettachedSignRequest; +import com.cisd.tms.modules.openapi.service.dto.DettachedVerifyRequest; import com.cisd.tms.modules.openapi.service.dto.RawSignRequest; import com.cisd.tms.modules.openapi.service.dto.RawSignResponse; import com.cisd.tms.modules.openapi.service.dto.RawVerifyRequest; import com.cisd.tms.modules.openapi.service.dto.RawVerifyResponse; +import com.cisd.tms.modules.openapi.service.dto.UploadCertRequest; import com.cisd.tms.modules.openapi.service.impl.OpenApiService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -28,12 +33,13 @@ public class OpenApiController { private static final Logger log = LoggerFactory.getLogger(OpenApiController.class); private final OpenApiService openApiService; + private final CertificateService certificateService; @PostMapping("/rawSign") @Operation(summary = "裸签名", description = "基于 SM3 SM2 算法进行签名,返回 Base64 编码的签名值") public ApiResponse rawSign(@Valid @RequestBody RawSignRequest request) { - log.info("收到裸签名请求, DN: {}", request.getDn()); + log.info("收到裸签名请求, DN: {}, sessionId: {}", request.getDn(), request.getSessionId()); // 入参检查 if (request.getOrigBytes() == null || request.getOrigBytes().trim().isEmpty()) { @@ -67,7 +73,7 @@ public class OpenApiController { @PostMapping("/rawVerify") @Operation(summary = "裸验签", description = "基于 SM3 SM2 算法进行验签,验证签名的有效性") public ApiResponse rawVerify(@Valid @RequestBody RawVerifyRequest request) { - log.info("收到裸验签请求, DN: {}", request.getDn()); + log.info("收到裸验签请求, DN: {}, sessionId: {}", request.getDn(), request.getSessionId()); // 入参检查 if (request.getOrigBytes() == null || request.getOrigBytes().trim().isEmpty()) { @@ -108,7 +114,72 @@ public class OpenApiController { } } + @PostMapping("/dettachedSign") + @Operation(summary = "分离签名", description = "基于 SM3 SM2 算法进行分离签名,返回 Base64 编码的签名值") + public ApiResponse dettachedSign(@Valid @RequestBody DettachedSignRequest request) { + log.info("收到分离签名请求, DN: {}, sessionId: {}", request.getDn(), request.getSessionId()); + RawSignRequest rawSignRequest = new RawSignRequest(); + rawSignRequest.setDn(request.getDn()); + rawSignRequest.setOrigBytes(request.getOrigBytes()); + rawSignRequest.setSessionId(request.getSessionId()); + return rawSign(rawSignRequest); + } + + @PostMapping("/dettachedVerify") + @Operation(summary = "分离验签", description = "基于 SM3 SM2 算法进行分离验签,验证签名的有效性") + public ApiResponse dettachedVerify(@Valid @RequestBody DettachedVerifyRequest request) { + log.info("收到分离验签请求, DN: {}, sessionId: {}", request.getDn(), request.getSessionId()); + + RawVerifyRequest rawVerifyRequest = new RawVerifyRequest(); + rawVerifyRequest.setDn(request.getDn()); + rawVerifyRequest.setOrigBytes(request.getOrigBytes()); + rawVerifyRequest.setSignature(request.getSignature()); + rawVerifyRequest.setSessionId(request.getSessionId()); + + return rawVerify(rawVerifyRequest); + } + + @PostMapping("/dettachedVerifySimple") + @Operation(summary = "分离验签(简化版)", description = "基于 SM3 SM2 算法进行分离验签,验证签名的有效性(简化版)") + public ApiResponse dettachedVerifySimple(@Valid @RequestBody DettachedVerifyRequest request) { + log.info("收到分离验签(简化版)请求, DN: {}, sessionId: {}", request.getDn(), request.getSessionId()); + + RawVerifyRequest rawVerifyRequest = new RawVerifyRequest(); + rawVerifyRequest.setDn(request.getDn()); + rawVerifyRequest.setOrigBytes(request.getOrigBytes()); + rawVerifyRequest.setSignature(request.getSignature()); + rawVerifyRequest.setSessionId(request.getSessionId()); + + return rawVerify(rawVerifyRequest); + } + + @PostMapping("/uploadCert") + @Operation(summary = "上传证书", description = "上传并导入证书") + public ApiResponse uploadCert(@Valid @RequestBody UploadCertRequest request) { + log.info("收到上传证书请求, sessionId: {}", request.getSessionId()); + + if (request.getCertData() == null || request.getCertData().trim().isEmpty()) { + log.error("上传证书请求失败:证书数据为空"); + return ApiResponse.fail(400, "证书数据不能为空", null); + } + + try { + byte[] certBytes = Base64.getDecoder().decode(request.getCertData()); + log.info("证书数据长度: {} bytes", certBytes.length); + + ImportCertificateResponse response = certificateService.importCertificate(certBytes); + + log.info("证书上传成功, certId: {}, subjectDn: {}", response.getCertId(), response.getSubjectDn()); + return ApiResponse.success(response); + } catch (IllegalArgumentException e) { + log.error("上传证书请求参数错误: {}", e.getMessage()); + return ApiResponse.fail(400, e.getMessage(), null); + } catch (Exception e) { + log.error("证书上传失败: {}", e.getMessage(), e); + return ApiResponse.fail(500, "证书上传失败: " + e.getMessage(), null); + } + } } diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/dto/DettachedSignRequest.java b/src/main/java/com/cisd/tms/modules/openapi/service/dto/DettachedSignRequest.java new file mode 100644 index 0000000..2206edc --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/openapi/service/dto/DettachedSignRequest.java @@ -0,0 +1,20 @@ +package com.cisd.tms.modules.openapi.service.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +@Schema(description = "分离签名请求") +public class DettachedSignRequest { + @NotBlank(message = "证书DN不能为空") + @Schema(description = "证书DN", example = "CN=Initial Entity,OU=Initial OU,O=Initial Org,L=Beijing,ST=Beijing,C=CN") + private String dn; + + @NotBlank(message = "待签名原文不能为空") + @Schema(description = "待签名原文的Base64编码", example = "dGVzdCBtZXNzYWdl") + private String origBytes; + + @Schema(description = "会话ID", example = "session-12345") + private String sessionId; +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/dto/DettachedVerifyRequest.java b/src/main/java/com/cisd/tms/modules/openapi/service/dto/DettachedVerifyRequest.java new file mode 100644 index 0000000..c24c871 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/openapi/service/dto/DettachedVerifyRequest.java @@ -0,0 +1,11 @@ +package com.cisd.tms.modules.openapi.service.dto; + +import lombok.Data; + +@Data +public class DettachedVerifyRequest { + private String dn; + private String origBytes; + private String sessionId; + private String signature; +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/dto/UploadCertRequest.java b/src/main/java/com/cisd/tms/modules/openapi/service/dto/UploadCertRequest.java new file mode 100644 index 0000000..fe7bc6f --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/openapi/service/dto/UploadCertRequest.java @@ -0,0 +1,16 @@ +package com.cisd.tms.modules.openapi.service.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +@Schema(description = "上传证书请求") +public class UploadCertRequest { + @NotBlank(message = "证书数据不能为空") + @Schema(description = "证书文件的Base64编码", example = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...") + private String certData; + + @Schema(description = "会话ID", example = "session-12345") + private String sessionId; +} \ No newline at end of file