用户密钥加解密签名验签接口

This commit is contained in:
waner
2026-04-18 10:23:24 +08:00
parent a02c9959b7
commit 350fa4d89d
17 changed files with 1122 additions and 5 deletions
@@ -0,0 +1,23 @@
package com.cisd.tms.integration.crypto.pcie.model;
public class UserKeyDecryptRequest {
private int keyIndex;
private byte[] cipherBlob;
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
public byte[] getCipherBlob() {
return cipherBlob;
}
public void setCipherBlob(byte[] cipherBlob) {
this.cipherBlob = cipherBlob;
}
}
@@ -0,0 +1,23 @@
package com.cisd.tms.integration.crypto.pcie.model;
public class UserKeyEncryptRequest {
private int keyIndex;
private byte[] data;
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
}
@@ -0,0 +1,23 @@
package com.cisd.tms.integration.crypto.pcie.model;
public class UserKeySignRequest {
private int keyIndex;
private byte[] data;
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
}
@@ -0,0 +1,32 @@
package com.cisd.tms.integration.crypto.pcie.model;
public class UserKeyVerifyRequest {
private int keyIndex;
private byte[] data;
private byte[] signature;
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
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;
}
}
@@ -79,6 +79,10 @@ import com.cisd.tms.integration.crypto.pcie.model.SymmetricCryptoRequest;
import com.cisd.tms.integration.crypto.pcie.model.SymmetricKekCryptoRequest; import com.cisd.tms.integration.crypto.pcie.model.SymmetricKekCryptoRequest;
import com.cisd.tms.integration.crypto.pcie.model.StandardKeyPairResult; import com.cisd.tms.integration.crypto.pcie.model.StandardKeyPairResult;
import com.cisd.tms.integration.crypto.pcie.model.UpdateBackedKeyRequest; import com.cisd.tms.integration.crypto.pcie.model.UpdateBackedKeyRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeyDecryptRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeyEncryptRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeySignRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeyVerifyRequest;
import com.sun.jna.Memory; import com.sun.jna.Memory;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
import com.sun.jna.Structure; import com.sun.jna.Structure;
@@ -86,6 +90,7 @@ import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference; import com.sun.jna.ptr.PointerByReference;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.function.Supplier;
public class JnaPcieCryptoService implements PcieCryptoService { public class JnaPcieCryptoService implements PcieCryptoService {
@@ -96,6 +101,7 @@ public class JnaPcieCryptoService implements PcieCryptoService {
private static final int IK_KEY_TYPE_AUTH = 2; private static final int IK_KEY_TYPE_AUTH = 2;
private static final int HMAC_MAX_LENGTH = 64; private static final int HMAC_MAX_LENGTH = 64;
private static final int ECC_CIPHER_PREFIX_LENGTH = (EccRefPublicKey.MAX_LEN * 2) + 32 + Integer.BYTES; private static final int ECC_CIPHER_PREFIX_LENGTH = (EccRefPublicKey.MAX_LEN * 2) + 32 + Integer.BYTES;
private static final String DEFAULT_PRIVATE_KEY_ACCESS_PASSWORD = "88888888";
private final PcieSessionTemplate sessionTemplate; private final PcieSessionTemplate sessionTemplate;
private final boolean strictAlgIdValidation; private final boolean strictAlgIdValidation;
@@ -1642,6 +1648,55 @@ public class JnaPcieCryptoService implements PcieCryptoService {
}); });
} }
@Override
public BackupDataResult userKeyEncrypt(UserKeyEncryptRequest request) {
UserKeyEncryptRequest req = requireRequest("request", request);
EccInternalEncryptRequest encryptRequest = new EccInternalEncryptRequest();
encryptRequest.setKeyIndex(requireNonNegative("keyIndex", req.getKeyIndex()));
encryptRequest.setData(requireBytes("data", req.getData()));
return sdfeInternalEncryptEcc(encryptRequest);
}
@Override
public BackupDataResult userKeyDecrypt(UserKeyDecryptRequest request) {
UserKeyDecryptRequest req = requireRequest("request", request);
int keyIndex = requireNonNegative("keyIndex", req.getKeyIndex());
byte[] cipherBlob = requireNonEmptyBytes("cipherBlob", req.getCipherBlob());
EccCipher cipher = toEccCipher("cipherBlob", cipherBlob);
int outBufferSize = cipher.l;
if (outBufferSize <= 0) {
throw new IllegalArgumentException("cipherBlob contains invalid plaintext length: " + outBufferSize);
}
EccInternalDecryptRequest decryptRequest = new EccInternalDecryptRequest();
decryptRequest.setKeyIndex(keyIndex);
decryptRequest.setCipherBlob(cipherBlob);
decryptRequest.setOutBufferSize(outBufferSize);
return withDefaultPrivateKeyAccess(keyIndex, () -> sdfeInternalDecryptEcc(decryptRequest));
}
@Override
public BackupDataResult userKeySignWithSm3(UserKeySignRequest request) {
UserKeySignRequest req = requireRequest("request", request);
int keyIndex = requireNonNegative("keyIndex", req.getKeyIndex());
byte[] digest = digestWithSm3(requireBytes("data", req.getData()));
EccInternalSignRequest signRequest = new EccInternalSignRequest();
signRequest.setKeyIndex(keyIndex);
signRequest.setData(digest);
return withDefaultPrivateKeyAccess(keyIndex, () -> eccInternalSign(signRequest));
}
@Override
public void userKeyVerifyWithSm3(UserKeyVerifyRequest request) {
UserKeyVerifyRequest req = requireRequest("request", request);
EccInternalVerifyRequest verifyRequest = new EccInternalVerifyRequest();
verifyRequest.setKeyIndex(requireNonNegative("keyIndex", req.getKeyIndex()));
verifyRequest.setData(digestWithSm3(requireBytes("data", req.getData())));
verifyRequest.setSignature(requireNonEmptyBytes("signature", req.getSignature()));
eccInternalVerify(verifyRequest);
}
@Override @Override
public HmacResult hmac(HmacRequest request) { public HmacResult hmac(HmacRequest request) {
HmacRequest req = requireRequest("request", request); HmacRequest req = requireRequest("request", request);
@@ -2128,4 +2183,24 @@ public class JnaPcieCryptoService implements PcieCryptoService {
} }
return request; return request;
} }
private <T> T withDefaultPrivateKeyAccess(int keyIndex, Supplier<T> action) {
PrivateKeyAccessRequest accessRequest = new PrivateKeyAccessRequest();
accessRequest.setKeyIndex(keyIndex);
accessRequest.setPassword(DEFAULT_PRIVATE_KEY_ACCESS_PASSWORD.getBytes(StandardCharsets.UTF_8));
getPrivateKeyAccessRight(accessRequest);
try {
return action.get();
} finally {
releasePrivateKeyAccessRight(keyIndex);
}
}
private byte[] digestWithSm3(byte[] data) {
DigestRequest digestRequest = new DigestRequest();
digestRequest.setAlgId(Gm0018AlgorithmIds.SM3);
digestRequest.setData(data);
digestRequest.setOutBufferSize(32);
return requireNonEmptyBytes("sm3Digest", digest(digestRequest).getData());
}
} }
@@ -1,5 +1,6 @@
package com.cisd.tms.integration.crypto.pcie.service; package com.cisd.tms.integration.crypto.pcie.service;
import com.cisd.tms.integration.crypto.pcie.Gm0018AlgorithmIds;
import com.cisd.tms.integration.crypto.pcie.PcieKeyState; import com.cisd.tms.integration.crypto.pcie.PcieKeyState;
import com.cisd.tms.integration.crypto.pcie.PcieUserKeyType; import com.cisd.tms.integration.crypto.pcie.PcieUserKeyType;
import com.cisd.tms.integration.crypto.pcie.jna.EccCipher; import com.cisd.tms.integration.crypto.pcie.jna.EccCipher;
@@ -66,13 +67,20 @@ import com.cisd.tms.integration.crypto.pcie.model.SymmetricCryptoRequest;
import com.cisd.tms.integration.crypto.pcie.model.SymmetricKekCryptoRequest; import com.cisd.tms.integration.crypto.pcie.model.SymmetricKekCryptoRequest;
import com.cisd.tms.integration.crypto.pcie.model.StandardKeyPairResult; import com.cisd.tms.integration.crypto.pcie.model.StandardKeyPairResult;
import com.cisd.tms.integration.crypto.pcie.model.UpdateBackedKeyRequest; import com.cisd.tms.integration.crypto.pcie.model.UpdateBackedKeyRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeyDecryptRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeyEncryptRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeySignRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeyVerifyRequest;
import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.function.Supplier;
import javax.crypto.Mac; import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
public class MockPcieCryptoService implements PcieCryptoService { public class MockPcieCryptoService implements PcieCryptoService {
private static final int ECC_KEY_BITS = 256; private static final int ECC_KEY_BITS = 256;
private static final String DEFAULT_PRIVATE_KEY_ACCESS_PASSWORD = "88888888";
@Override @Override
public DeviceCountResult getDeviceCount() { public DeviceCountResult getDeviceCount() {
@@ -701,6 +709,45 @@ public class MockPcieCryptoService implements PcieCryptoService {
return mockBackup(size, 0x76); return mockBackup(size, 0x76);
} }
@Override
public BackupDataResult userKeyEncrypt(UserKeyEncryptRequest request) {
UserKeyEncryptRequest req = requireRequest("request", request);
EccInternalEncryptRequest encryptRequest = new EccInternalEncryptRequest();
encryptRequest.setKeyIndex(requireNonNegative("keyIndex", req.getKeyIndex()));
encryptRequest.setData(requireBytes("data", req.getData()));
return sdfeInternalEncryptEcc(encryptRequest);
}
@Override
public BackupDataResult userKeyDecrypt(UserKeyDecryptRequest request) {
UserKeyDecryptRequest req = requireRequest("request", request);
byte[] cipherBlob = requireNonEmptyBytes("cipherBlob", req.getCipherBlob());
EccInternalDecryptRequest decryptRequest = new EccInternalDecryptRequest();
decryptRequest.setKeyIndex(requireNonNegative("keyIndex", req.getKeyIndex()));
decryptRequest.setCipherBlob(cipherBlob);
decryptRequest.setOutBufferSize(Math.max(1, cipherBlob.length));
return withDefaultPrivateKeyAccess(req.getKeyIndex(), () -> sdfeInternalDecryptEcc(decryptRequest));
}
@Override
public BackupDataResult userKeySignWithSm3(UserKeySignRequest request) {
UserKeySignRequest req = requireRequest("request", request);
EccInternalSignRequest signRequest = new EccInternalSignRequest();
signRequest.setKeyIndex(requireNonNegative("keyIndex", req.getKeyIndex()));
signRequest.setData(digestWithSm3(requireBytes("data", req.getData())));
return withDefaultPrivateKeyAccess(req.getKeyIndex(), () -> eccInternalSign(signRequest));
}
@Override
public void userKeyVerifyWithSm3(UserKeyVerifyRequest request) {
UserKeyVerifyRequest req = requireRequest("request", request);
EccInternalVerifyRequest verifyRequest = new EccInternalVerifyRequest();
verifyRequest.setKeyIndex(requireNonNegative("keyIndex", req.getKeyIndex()));
verifyRequest.setData(digestWithSm3(requireBytes("data", req.getData())));
verifyRequest.setSignature(requireNonEmptyBytes("signature", req.getSignature()));
eccInternalVerify(verifyRequest);
}
@Override @Override
public HmacResult hmac(HmacRequest request) { public HmacResult hmac(HmacRequest request) {
HmacRequest req = requireRequest("request", request); HmacRequest req = requireRequest("request", request);
@@ -926,4 +973,24 @@ public class MockPcieCryptoService implements PcieCryptoService {
} }
return request; return request;
} }
private <T> T withDefaultPrivateKeyAccess(int keyIndex, Supplier<T> action) {
PrivateKeyAccessRequest accessRequest = new PrivateKeyAccessRequest();
accessRequest.setKeyIndex(requireNonNegative("keyIndex", keyIndex));
accessRequest.setPassword(DEFAULT_PRIVATE_KEY_ACCESS_PASSWORD.getBytes(StandardCharsets.UTF_8));
getPrivateKeyAccessRight(accessRequest);
try {
return action.get();
} finally {
releasePrivateKeyAccessRight(keyIndex);
}
}
private byte[] digestWithSm3(byte[] data) {
DigestRequest digestRequest = new DigestRequest();
digestRequest.setAlgId(Gm0018AlgorithmIds.SM3);
digestRequest.setData(data);
digestRequest.setOutBufferSize(32);
return digest(digestRequest).getData();
}
} }
@@ -57,6 +57,10 @@ import com.cisd.tms.integration.crypto.pcie.model.SymmetricCryptoRequest;
import com.cisd.tms.integration.crypto.pcie.model.SymmetricKekCryptoRequest; import com.cisd.tms.integration.crypto.pcie.model.SymmetricKekCryptoRequest;
import com.cisd.tms.integration.crypto.pcie.model.StandardKeyPairResult; import com.cisd.tms.integration.crypto.pcie.model.StandardKeyPairResult;
import com.cisd.tms.integration.crypto.pcie.model.UpdateBackedKeyRequest; import com.cisd.tms.integration.crypto.pcie.model.UpdateBackedKeyRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeyDecryptRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeyEncryptRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeySignRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeyVerifyRequest;
/** /**
* PCIe 密码卡强类型服务接口。 * PCIe 密码卡强类型服务接口。
@@ -455,6 +459,26 @@ public interface PcieCryptoService {
*/ */
BackupDataResult sdfeInternalDecryptEcc(EccInternalDecryptRequest request); BackupDataResult sdfeInternalDecryptEcc(EccInternalDecryptRequest request);
/**
* 使用指定用户加密密钥对原文执行内部 ECC 加密。
*/
BackupDataResult userKeyEncrypt(UserKeyEncryptRequest request);
/**
* 使用指定用户加密密钥对密文执行内部 ECC 解密,并自动处理私钥访问授权。
*/
BackupDataResult userKeyDecrypt(UserKeyDecryptRequest request);
/**
* 先对原文执行 SM3 摘要,再使用指定用户签名密钥完成内部 ECC 签名,并自动处理私钥访问授权。
*/
BackupDataResult userKeySignWithSm3(UserKeySignRequest request);
/**
* 先对原文执行 SM3 摘要,再使用指定用户签名公钥完成内部 ECC 验签。
*/
void userKeyVerifyWithSm3(UserKeyVerifyRequest request);
/** /**
* 使用 SD FE HMAC 流程计算 HMAC。 * 使用 SD FE HMAC 流程计算 HMAC。
*/ */
@@ -4,6 +4,7 @@ import static com.cisd.tms.modules.device.support.crypto.CryptoPayloadCodec.*;
import static com.cisd.tms.modules.device.support.crypto.CryptoRequestMapper.*; import static com.cisd.tms.modules.device.support.crypto.CryptoRequestMapper.*;
import com.cisd.tms.common.api.ApiResponse; import com.cisd.tms.common.api.ApiResponse;
import com.cisd.tms.integration.crypto.pcie.Gm0018AlgorithmIds;
import com.cisd.tms.modules.device.dto.crypto.request.*; import com.cisd.tms.modules.device.dto.crypto.request.*;
import com.cisd.tms.integration.crypto.pcie.model.AgreementDataAndKeyEccResult; import com.cisd.tms.integration.crypto.pcie.model.AgreementDataAndKeyEccResult;
import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult; import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult;
@@ -14,6 +15,10 @@ import com.cisd.tms.integration.crypto.pcie.model.DeviceStatusResult;
import com.cisd.tms.integration.crypto.pcie.model.DigestRequest; import com.cisd.tms.integration.crypto.pcie.model.DigestRequest;
import com.cisd.tms.integration.crypto.pcie.model.EccAgreementSessionKeyRequest; import com.cisd.tms.integration.crypto.pcie.model.EccAgreementSessionKeyRequest;
import com.cisd.tms.integration.crypto.pcie.model.EccAgreementSessionKeyResult; import com.cisd.tms.integration.crypto.pcie.model.EccAgreementSessionKeyResult;
import com.cisd.tms.integration.crypto.pcie.model.EccInternalDecryptRequest;
import com.cisd.tms.integration.crypto.pcie.model.EccInternalEncryptRequest;
import com.cisd.tms.integration.crypto.pcie.model.EccInternalSignRequest;
import com.cisd.tms.integration.crypto.pcie.model.EccInternalVerifyRequest;
import com.cisd.tms.integration.crypto.pcie.model.ExchangeDigitEnvelopeEccRequest; import com.cisd.tms.integration.crypto.pcie.model.ExchangeDigitEnvelopeEccRequest;
import com.cisd.tms.integration.crypto.pcie.model.ExchangeDigitEnvelopeRsaRequest; import com.cisd.tms.integration.crypto.pcie.model.ExchangeDigitEnvelopeRsaRequest;
import com.cisd.tms.integration.crypto.pcie.model.ExportKekComponentRequest; import com.cisd.tms.integration.crypto.pcie.model.ExportKekComponentRequest;
@@ -29,18 +34,25 @@ import com.cisd.tms.integration.crypto.pcie.model.HmacRequest;
import com.cisd.tms.integration.crypto.pcie.model.HmacResult; import com.cisd.tms.integration.crypto.pcie.model.HmacResult;
import com.cisd.tms.integration.crypto.pcie.model.KeyStatusResult; import com.cisd.tms.integration.crypto.pcie.model.KeyStatusResult;
import com.cisd.tms.integration.crypto.pcie.model.MacCalcRequest; import com.cisd.tms.integration.crypto.pcie.model.MacCalcRequest;
import com.cisd.tms.integration.crypto.pcie.model.PrivateKeyAccessRequest;
import com.cisd.tms.integration.crypto.pcie.model.RecoverUserKeyRequest; import com.cisd.tms.integration.crypto.pcie.model.RecoverUserKeyRequest;
import com.cisd.tms.integration.crypto.pcie.model.SymmetricCryptoRequest; import com.cisd.tms.integration.crypto.pcie.model.SymmetricCryptoRequest;
import com.cisd.tms.integration.crypto.pcie.model.SymmetricKekCryptoRequest; import com.cisd.tms.integration.crypto.pcie.model.SymmetricKekCryptoRequest;
import com.cisd.tms.integration.crypto.pcie.model.StandardKeyPairResult; import com.cisd.tms.integration.crypto.pcie.model.StandardKeyPairResult;
import com.cisd.tms.integration.crypto.pcie.model.UserKeyDecryptRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeyEncryptRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeySignRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeyVerifyRequest;
import com.cisd.tms.integration.crypto.pcie.service.PcieDeviceStatusFormatter; import com.cisd.tms.integration.crypto.pcie.service.PcieDeviceStatusFormatter;
import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService;
import com.cisd.tms.security.internal.ReplayProtected; import com.cisd.tms.security.internal.ReplayProtected;
import com.google.gson.Gson;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Min;
import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -65,6 +77,8 @@ import org.springframework.web.bind.annotation.RestController;
@Tag(name = "密码卡调试", description = "密码卡设备调试、密钥操作和文件管理接口") @Tag(name = "密码卡调试", description = "密码卡设备调试、密钥操作和文件管理接口")
public class CryptoCardController { public class CryptoCardController {
private static final String DEFAULT_USER_KEY_ACCESS_PASSWORD = "88888888";
private final PcieCryptoService pcieCryptoService; private final PcieCryptoService pcieCryptoService;
@GetMapping("/device-count") @GetMapping("/device-count")
@@ -514,6 +528,76 @@ public class CryptoCardController {
return ApiResponse.success(hexPayload("dataHex", result.getData(), result.getLength())); return ApiResponse.success(hexPayload("dataHex", result.getData(), result.getLength()));
} }
@PostMapping("/user-key/encrypt")
@Operation(summary = "用户密钥加密", description = "使用用户加密密钥完成内部 ECC 加密,输入输出均使用 Base64。")
public ApiResponse<Map<String, Object>> encryptUserKey(
@Valid @RequestBody CryptoUserKeyEncryptInternalRequest request
) {
UserKeyEncryptRequest encryptRequest = new UserKeyEncryptRequest();
encryptRequest.setKeyIndex(request.getKeyIndex());
encryptRequest.setData(Base64.getDecoder().decode(request.getDataBase64()));
BackupDataResult result = pcieCryptoService.userKeyEncrypt(encryptRequest);
int length = result.getLength() > 0 && result.getLength() <= result.getData().length
? result.getLength()
: result.getData().length;
byte[] cipherBlob = Arrays.copyOf(result.getData(), length);
return ApiResponse.success(Map.of(
"length", length,
"cipherBase64", Base64.getEncoder().encodeToString(cipherBlob)
));
}
@PostMapping("/user-key/decrypt")
@Operation(summary = "用户密钥解密", description = "使用用户加密密钥完成内部 ECC 解密,接口内部自动申请和释放私钥访问权限。")
public ApiResponse<Map<String, Object>> decryptUserKey(
@Valid @RequestBody CryptoUserKeyDecryptInternalRequest request
) {
UserKeyDecryptRequest decryptRequest = new UserKeyDecryptRequest();
decryptRequest.setKeyIndex(request.getKeyIndex());
decryptRequest.setCipherBlob(Base64.getDecoder().decode(request.getCipherBase64()));
BackupDataResult result = pcieCryptoService.userKeyDecrypt(decryptRequest);
int length = result.getLength() > 0 && result.getLength() <= result.getData().length
? result.getLength()
: result.getData().length;
byte[] plain = Arrays.copyOf(result.getData(), length);
return ApiResponse.success(Map.of(
"length", length,
"dataBase64", Base64.getEncoder().encodeToString(plain)
));
}
@PostMapping("/user-key/sign")
@Operation(summary = "用户密钥签名", description = "使用用户签名密钥对原文先做 SM3,再完成内部 ECC 签名。")
public ApiResponse<Map<String, Object>> signUserKey(
@Valid @RequestBody CryptoUserKeySignInternalRequest request
) {
UserKeySignRequest signRequest = new UserKeySignRequest();
signRequest.setKeyIndex(request.getKeyIndex());
signRequest.setData(Base64.getDecoder().decode(request.getDataBase64()));
BackupDataResult result = pcieCryptoService.userKeySignWithSm3(signRequest);
int length = result.getLength() > 0 && result.getLength() <= result.getData().length
? result.getLength()
: result.getData().length;
byte[] signature = Arrays.copyOf(result.getData(), length);
return ApiResponse.success(Map.of(
"length", length,
"signatureBase64", Base64.getEncoder().encodeToString(signature)
));
}
@PostMapping("/user-key/verify")
@Operation(summary = "用户密钥验签", description = "使用用户签名密钥对原文先做 SM3,再完成内部 ECC 验签。")
public ApiResponse<Map<String, Object>> verifyUserKey(
@Valid @RequestBody CryptoUserKeyVerifyInternalRequest request
) {
UserKeyVerifyRequest verifyRequest = new UserKeyVerifyRequest();
verifyRequest.setKeyIndex(request.getKeyIndex());
verifyRequest.setData(Base64.getDecoder().decode(request.getDataBase64()));
verifyRequest.setSignature(Base64.getDecoder().decode(request.getSignatureBase64()));
pcieCryptoService.userKeyVerifyWithSm3(verifyRequest);
return ApiResponse.success(Map.of("passed", true));
}
@PostMapping("/user-key/self-test") @PostMapping("/user-key/self-test")
@Operation(summary = "执行用户密钥全流程自测", description = "依次执行 ECC 用户密钥生成、备份、删除、重建槽位、恢复和状态校验,返回备份 HEX 与关键状态。") @Operation(summary = "执行用户密钥全流程自测", description = "依次执行 ECC 用户密钥生成、备份、删除、重建槽位、恢复和状态校验,返回备份 HEX 与关键状态。")
public ApiResponse<Map<String, Object>> selfTestUserKey( public ApiResponse<Map<String, Object>> selfTestUserKey(
@@ -556,6 +640,132 @@ public class CryptoCardController {
return ApiResponse.success(payload); return ApiResponse.success(payload);
} }
@PostMapping("/user-key/crypto-self-test")
@Operation(summary = "执行用户密钥算法全流程自测", description = "分阶段生成 ECC 用户加密钥和签名钥,完成内部加解密、签名验签,并在每个阶段结束后销毁槽位密钥。")
public ApiResponse<Map<String, Object>> cryptoSelfTestUserKey(
@Valid @RequestBody CryptoUserKeyCryptoSelfTestInternalRequest request
) {
byte[] encryptPlain = Base64.getDecoder().decode(request.getEncryptPlainBase64());
byte[] signData = Base64.getDecoder().decode(request.getSignDataBase64());
GenerateKeyPairRequest encGenerateRequest = new GenerateKeyPairRequest();
encGenerateRequest.setKeyBits(256);
encGenerateRequest.setKeyIndex(request.getKeyIndex());
encGenerateRequest.setKeyType(1);
GenerateKeyPairRequest signGenerateRequest = new GenerateKeyPairRequest();
signGenerateRequest.setKeyBits(256);
signGenerateRequest.setKeyIndex(request.getKeyIndex());
signGenerateRequest.setKeyType(0);
boolean encDeleted = false;
boolean signDeleted = false;
try {
pcieCryptoService.generateKeyPairEcc(encGenerateRequest);
EccInternalEncryptRequest encryptRequest = new EccInternalEncryptRequest();
encryptRequest.setKeyIndex(request.getKeyIndex());
encryptRequest.setData(encryptPlain);
BackupDataResult cipherResult = pcieCryptoService.sdfeInternalEncryptEcc(encryptRequest);
int cipherLength = cipherResult.getLength() > 0 && cipherResult.getLength() <= cipherResult.getData().length
? cipherResult.getLength()
: cipherResult.getData().length;
byte[] cipherBlob = Arrays.copyOf(cipherResult.getData(), cipherLength);
EccInternalDecryptRequest decryptRequest = new EccInternalDecryptRequest();
decryptRequest.setKeyIndex(request.getKeyIndex());
decryptRequest.setCipherBlob(cipherBlob);
decryptRequest.setOutBufferSize(encryptPlain.length);
PrivateKeyAccessRequest decryptAccessRequest = new PrivateKeyAccessRequest();
decryptAccessRequest.setKeyIndex(request.getKeyIndex());
decryptAccessRequest.setPassword(DEFAULT_USER_KEY_ACCESS_PASSWORD.getBytes(StandardCharsets.UTF_8));
BackupDataResult decryptResult;
pcieCryptoService.getPrivateKeyAccessRight(decryptAccessRequest);
try {
decryptResult = pcieCryptoService.sdfeInternalDecryptEcc(decryptRequest);
} finally {
pcieCryptoService.releasePrivateKeyAccessRight(request.getKeyIndex());
}
int decryptLength = decryptResult.getLength() > 0 && decryptResult.getLength() <= decryptResult.getData().length
? decryptResult.getLength()
: decryptResult.getData().length;
byte[] decryptPlain = Arrays.copyOf(decryptResult.getData(), decryptLength);
Map<String, Object> payload = new LinkedHashMap<>();
payload.put("keyIndex", request.getKeyIndex());
payload.put("encryptPlainBase64", Base64.getEncoder().encodeToString(encryptPlain));
payload.put("cipherLength", cipherLength);
payload.put("cipherBase64", Base64.getEncoder().encodeToString(cipherBlob));
payload.put("decryptLength", decryptLength);
payload.put("decryptBase64", Base64.getEncoder().encodeToString(decryptPlain));
payload.put("decryptMatched", Arrays.equals(encryptPlain, decryptPlain));
log.info("keyIndex:{}", request.getKeyIndex());
log.info("encryptPlainBase64:{}", Base64.getEncoder().encodeToString(encryptPlain));
log.info("cipherLength:{}", cipherLength);
log.info("cipherBase64:{}", Base64.getEncoder().encodeToString(cipherBlob));
log.info("decryptLength:{}", decryptLength);
log.info("decryptBase64:{}", Base64.getEncoder().encodeToString(decryptPlain));
log.info("decryptMatched:{}", Arrays.equals(encryptPlain, decryptPlain));
pcieCryptoService.deleteUserKey(request.getKeyIndex());
encDeleted = true;
KeyStatusResult encDeleteStatus = pcieCryptoService.statusUserKeyEcc(request.getKeyIndex(), 1);
payload.put("encDeleteStatus", encDeleteStatus);
pcieCryptoService.generateKeyPairEcc(signGenerateRequest);
DigestRequest digestRequest = new DigestRequest();
digestRequest.setAlgId(Gm0018AlgorithmIds.SM3);
digestRequest.setData(signData);
byte[] signDigest = pcieCryptoService.digest(digestRequest).getData();
EccInternalSignRequest signRequest = new EccInternalSignRequest();
signRequest.setKeyIndex(request.getKeyIndex());
signRequest.setData(signDigest);
PrivateKeyAccessRequest signAccessRequest = new PrivateKeyAccessRequest();
signAccessRequest.setKeyIndex(request.getKeyIndex());
signAccessRequest.setPassword(DEFAULT_USER_KEY_ACCESS_PASSWORD.getBytes(StandardCharsets.UTF_8));
BackupDataResult signatureResult;
pcieCryptoService.getPrivateKeyAccessRight(signAccessRequest);
try {
signatureResult = pcieCryptoService.eccInternalSign(signRequest);
} finally {
pcieCryptoService.releasePrivateKeyAccessRight(request.getKeyIndex());
}
int signatureLength = signatureResult.getLength() > 0 && signatureResult.getLength() <= signatureResult.getData().length
? signatureResult.getLength()
: signatureResult.getData().length;
byte[] signature = Arrays.copyOf(signatureResult.getData(), signatureLength);
EccInternalVerifyRequest verifyRequest = new EccInternalVerifyRequest();
verifyRequest.setKeyIndex(request.getKeyIndex());
verifyRequest.setData(signDigest);
verifyRequest.setSignature(signature);
pcieCryptoService.eccInternalVerify(verifyRequest);
pcieCryptoService.deleteUserKey(request.getKeyIndex());
signDeleted = true;
KeyStatusResult signDeleteStatus = pcieCryptoService.statusUserKeyEcc(request.getKeyIndex(), 0);
payload.put("signatureLength", signatureLength);
payload.put("signatureBase64", Base64.getEncoder().encodeToString(signature));
payload.put("signDigestBase64", Base64.getEncoder().encodeToString(signDigest));
payload.put("verifyPassed", true);
payload.put("signDeleteStatus", signDeleteStatus);
return ApiResponse.success(payload);
} finally {
if (!signDeleted) {
try {
pcieCryptoService.deleteUserKey(request.getKeyIndex());
} catch (Exception cleanupError) {
log.warn("failed to cleanup user key after crypto self test, keyIndex={}", request.getKeyIndex(), cleanupError);
}
}
}
}
@PostMapping("/user-key/recover") @PostMapping("/user-key/recover")
@Operation(summary = "恢复用户密钥", description = "从备份数据恢复用户密钥。") @Operation(summary = "恢复用户密钥", description = "从备份数据恢复用户密钥。")
public ApiResponse<Void> recoverUserKey( public ApiResponse<Void> recoverUserKey(
@@ -0,0 +1,40 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class CryptoUserKeyCryptoSelfTestInternalRequest {
@Min(0)
private int keyIndex;
@NotBlank
private String encryptPlainBase64;
@NotBlank
private String signDataBase64;
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
public String getEncryptPlainBase64() {
return encryptPlainBase64;
}
public void setEncryptPlainBase64(String encryptPlainBase64) {
this.encryptPlainBase64 = encryptPlainBase64;
}
public String getSignDataBase64() {
return signDataBase64;
}
public void setSignDataBase64(String signDataBase64) {
this.signDataBase64 = signDataBase64;
}
}
@@ -0,0 +1,29 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class CryptoUserKeyDecryptInternalRequest {
@Min(0)
private int keyIndex;
@NotBlank
private String cipherBase64;
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
public String getCipherBase64() {
return cipherBase64;
}
public void setCipherBase64(String cipherBase64) {
this.cipherBase64 = cipherBase64;
}
}
@@ -0,0 +1,29 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class CryptoUserKeyEncryptInternalRequest {
@Min(0)
private int keyIndex;
@NotBlank
private String dataBase64;
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
public String getDataBase64() {
return dataBase64;
}
public void setDataBase64(String dataBase64) {
this.dataBase64 = dataBase64;
}
}
@@ -0,0 +1,29 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class CryptoUserKeySignInternalRequest {
@Min(0)
private int keyIndex;
@NotBlank
private String dataBase64;
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
public String getDataBase64() {
return dataBase64;
}
public void setDataBase64(String dataBase64) {
this.dataBase64 = dataBase64;
}
}
@@ -0,0 +1,40 @@
package com.cisd.tms.modules.device.dto.crypto.request;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
public class CryptoUserKeyVerifyInternalRequest {
@Min(0)
private int keyIndex;
@NotBlank
private String dataBase64;
@NotBlank
private String signatureBase64;
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
public String getDataBase64() {
return dataBase64;
}
public void setDataBase64(String dataBase64) {
this.dataBase64 = dataBase64;
}
public String getSignatureBase64() {
return signatureBase64;
}
public void setSignatureBase64(String signatureBase64) {
this.signatureBase64 = signatureBase64;
}
}
@@ -12,16 +12,24 @@ import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult;
import com.cisd.tms.integration.crypto.pcie.model.DigestRequest; import com.cisd.tms.integration.crypto.pcie.model.DigestRequest;
import com.cisd.tms.integration.crypto.pcie.model.EccInternalDecryptRequest; import com.cisd.tms.integration.crypto.pcie.model.EccInternalDecryptRequest;
import com.cisd.tms.integration.crypto.pcie.model.EccInternalEncryptRequest; import com.cisd.tms.integration.crypto.pcie.model.EccInternalEncryptRequest;
import com.cisd.tms.integration.crypto.pcie.model.EccInternalSignRequest;
import com.cisd.tms.integration.crypto.pcie.model.EccInternalVerifyRequest;
import com.cisd.tms.integration.crypto.pcie.model.FirmwareInfoResult; import com.cisd.tms.integration.crypto.pcie.model.FirmwareInfoResult;
import com.cisd.tms.integration.crypto.pcie.model.IkSignRequest; import com.cisd.tms.integration.crypto.pcie.model.IkSignRequest;
import com.cisd.tms.integration.crypto.pcie.model.IkVerifyRequest; import com.cisd.tms.integration.crypto.pcie.model.IkVerifyRequest;
import com.cisd.tms.integration.crypto.pcie.model.SymmetricCryptoRequest; import com.cisd.tms.integration.crypto.pcie.model.SymmetricCryptoRequest;
import com.cisd.tms.integration.crypto.pcie.model.PrivateKeyAccessRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeyDecryptRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeyEncryptRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeySignRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeyVerifyRequest;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference; import com.sun.jna.ptr.IntByReference;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito; import org.mockito.Mockito;
class JnaPcieCryptoServiceTest { class JnaPcieCryptoServiceTest {
@@ -452,4 +460,129 @@ class JnaPcieCryptoServiceTest {
Assertions.assertArrayEquals(new byte[] {0x31, 0x32}, data); Assertions.assertArrayEquals(new byte[] {0x31, 0x32}, data);
Mockito.verify(sessionTemplate).withSession(Mockito.eq("SDFE_InternalDecrypt_ECC"), Mockito.any()); Mockito.verify(sessionTemplate).withSession(Mockito.eq("SDFE_InternalDecrypt_ECC"), Mockito.any());
} }
@Test
void shouldEncryptViaHighLevelUserKeyEncrypt() {
PcieSessionTemplate sessionTemplate = Mockito.mock(PcieSessionTemplate.class);
CryptoCardProperties properties = new CryptoCardProperties();
JnaPcieCryptoService service = Mockito.spy(new JnaPcieCryptoService(sessionTemplate, properties));
BackupDataResult encrypted = new BackupDataResult();
encrypted.setData(new byte[] {0x01, 0x02, 0x03});
encrypted.setLength(3);
Mockito.doReturn(encrypted).when(service).sdfeInternalEncryptEcc(Mockito.any(EccInternalEncryptRequest.class));
UserKeyEncryptRequest request = new UserKeyEncryptRequest();
request.setKeyIndex(3);
request.setData(new byte[] {0x11, 0x22});
BackupDataResult result = service.userKeyEncrypt(request);
Assertions.assertArrayEquals(new byte[] {0x01, 0x02, 0x03}, result.getData());
ArgumentCaptor<EccInternalEncryptRequest> captor = ArgumentCaptor.forClass(EccInternalEncryptRequest.class);
Mockito.verify(service).sdfeInternalEncryptEcc(captor.capture());
Assertions.assertEquals(3, captor.getValue().getKeyIndex());
Assertions.assertArrayEquals(new byte[] {0x11, 0x22}, captor.getValue().getData());
}
@Test
void shouldDecryptViaHighLevelUserKeyDecrypt() {
PcieSessionTemplate sessionTemplate = Mockito.mock(PcieSessionTemplate.class);
CryptoCardProperties properties = new CryptoCardProperties();
JnaPcieCryptoService service = Mockito.spy(new JnaPcieCryptoService(sessionTemplate, properties));
byte[] cipherBlob = new byte[164];
cipherBlob[160] = 0x05;
BackupDataResult decrypted = new BackupDataResult();
decrypted.setData(new byte[] {0x31, 0x32});
decrypted.setLength(2);
Mockito.doNothing().when(service).getPrivateKeyAccessRight(Mockito.any());
Mockito.doNothing().when(service).releasePrivateKeyAccessRight(5);
Mockito.doReturn(decrypted).when(service).sdfeInternalDecryptEcc(Mockito.any(EccInternalDecryptRequest.class));
UserKeyDecryptRequest request = new UserKeyDecryptRequest();
request.setKeyIndex(5);
request.setCipherBlob(cipherBlob);
BackupDataResult result = service.userKeyDecrypt(request);
Assertions.assertArrayEquals(new byte[] {0x31, 0x32}, result.getData());
ArgumentCaptor<PrivateKeyAccessRequest> accessCaptor = ArgumentCaptor.forClass(PrivateKeyAccessRequest.class);
ArgumentCaptor<EccInternalDecryptRequest> decryptCaptor = ArgumentCaptor.forClass(EccInternalDecryptRequest.class);
Mockito.verify(service).getPrivateKeyAccessRight(accessCaptor.capture());
Mockito.verify(service).sdfeInternalDecryptEcc(decryptCaptor.capture());
Mockito.verify(service).releasePrivateKeyAccessRight(5);
Assertions.assertEquals(5, accessCaptor.getValue().getKeyIndex());
Assertions.assertArrayEquals("88888888".getBytes(StandardCharsets.UTF_8), accessCaptor.getValue().getPassword());
Assertions.assertEquals(5, decryptCaptor.getValue().getKeyIndex());
Assertions.assertArrayEquals(cipherBlob, decryptCaptor.getValue().getCipherBlob());
Assertions.assertEquals(5, decryptCaptor.getValue().getOutBufferSize());
}
@Test
void shouldSignViaHighLevelUserKeySignWithSm3() {
PcieSessionTemplate sessionTemplate = Mockito.mock(PcieSessionTemplate.class);
CryptoCardProperties properties = new CryptoCardProperties();
JnaPcieCryptoService service = Mockito.spy(new JnaPcieCryptoService(sessionTemplate, properties));
BackupDataResult digest = new BackupDataResult();
digest.setData(new byte[] {0x01, 0x02, 0x03});
digest.setLength(3);
BackupDataResult signature = new BackupDataResult();
signature.setData(new byte[] {0x11, 0x22});
signature.setLength(2);
Mockito.doReturn(digest).when(service).digest(Mockito.any(DigestRequest.class));
Mockito.doNothing().when(service).getPrivateKeyAccessRight(Mockito.any());
Mockito.doNothing().when(service).releasePrivateKeyAccessRight(6);
Mockito.doReturn(signature).when(service).eccInternalSign(Mockito.any(EccInternalSignRequest.class));
UserKeySignRequest request = new UserKeySignRequest();
request.setKeyIndex(6);
request.setData("plain".getBytes(StandardCharsets.UTF_8));
BackupDataResult result = service.userKeySignWithSm3(request);
Assertions.assertArrayEquals(new byte[] {0x11, 0x22}, result.getData());
ArgumentCaptor<DigestRequest> digestCaptor = ArgumentCaptor.forClass(DigestRequest.class);
ArgumentCaptor<PrivateKeyAccessRequest> accessCaptor = ArgumentCaptor.forClass(PrivateKeyAccessRequest.class);
ArgumentCaptor<EccInternalSignRequest> signCaptor = ArgumentCaptor.forClass(EccInternalSignRequest.class);
Mockito.verify(service).digest(digestCaptor.capture());
Mockito.verify(service).getPrivateKeyAccessRight(accessCaptor.capture());
Mockito.verify(service).eccInternalSign(signCaptor.capture());
Mockito.verify(service).releasePrivateKeyAccessRight(6);
Assertions.assertEquals(com.cisd.tms.integration.crypto.pcie.Gm0018AlgorithmIds.SM3, digestCaptor.getValue().getAlgId());
Assertions.assertArrayEquals("plain".getBytes(StandardCharsets.UTF_8), digestCaptor.getValue().getData());
Assertions.assertEquals(32, digestCaptor.getValue().getOutBufferSize());
Assertions.assertEquals(6, accessCaptor.getValue().getKeyIndex());
Assertions.assertArrayEquals("88888888".getBytes(StandardCharsets.UTF_8), accessCaptor.getValue().getPassword());
Assertions.assertEquals(6, signCaptor.getValue().getKeyIndex());
Assertions.assertArrayEquals(new byte[] {0x01, 0x02, 0x03}, signCaptor.getValue().getData());
}
@Test
void shouldVerifyViaHighLevelUserKeyVerifyWithSm3() {
PcieSessionTemplate sessionTemplate = Mockito.mock(PcieSessionTemplate.class);
CryptoCardProperties properties = new CryptoCardProperties();
JnaPcieCryptoService service = Mockito.spy(new JnaPcieCryptoService(sessionTemplate, properties));
BackupDataResult digest = new BackupDataResult();
digest.setData(new byte[] {0x21, 0x22, 0x23});
digest.setLength(3);
Mockito.doReturn(digest).when(service).digest(Mockito.any(DigestRequest.class));
Mockito.doNothing().when(service).eccInternalVerify(Mockito.any(EccInternalVerifyRequest.class));
UserKeyVerifyRequest request = new UserKeyVerifyRequest();
request.setKeyIndex(7);
request.setData("verify".getBytes(StandardCharsets.UTF_8));
request.setSignature(new byte[] {0x31, 0x32});
service.userKeyVerifyWithSm3(request);
ArgumentCaptor<DigestRequest> digestCaptor = ArgumentCaptor.forClass(DigestRequest.class);
ArgumentCaptor<EccInternalVerifyRequest> verifyCaptor = ArgumentCaptor.forClass(EccInternalVerifyRequest.class);
Mockito.verify(service).digest(digestCaptor.capture());
Mockito.verify(service).eccInternalVerify(verifyCaptor.capture());
Assertions.assertEquals(com.cisd.tms.integration.crypto.pcie.Gm0018AlgorithmIds.SM3, digestCaptor.getValue().getAlgId());
Assertions.assertArrayEquals("verify".getBytes(StandardCharsets.UTF_8), digestCaptor.getValue().getData());
Assertions.assertEquals(32, digestCaptor.getValue().getOutBufferSize());
Assertions.assertEquals(7, verifyCaptor.getValue().getKeyIndex());
Assertions.assertArrayEquals(new byte[] {0x21, 0x22, 0x23}, verifyCaptor.getValue().getData());
Assertions.assertArrayEquals(new byte[] {0x31, 0x32}, verifyCaptor.getValue().getSignature());
}
} }
@@ -1,11 +1,22 @@
package com.cisd.tms.modules.device.controller; package com.cisd.tms.modules.device.controller;
import com.cisd.tms.common.exception.GlobalExceptionHandler; import com.cisd.tms.common.exception.GlobalExceptionHandler;
import com.cisd.tms.integration.crypto.pcie.Gm0018AlgorithmIds;
import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult; import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult;
import com.cisd.tms.integration.crypto.pcie.model.DeviceStatusResult; import com.cisd.tms.integration.crypto.pcie.model.DeviceStatusResult;
import com.cisd.tms.integration.crypto.pcie.model.DigestRequest;
import com.cisd.tms.integration.crypto.pcie.model.EccInternalDecryptRequest;
import com.cisd.tms.integration.crypto.pcie.model.EccInternalEncryptRequest;
import com.cisd.tms.integration.crypto.pcie.model.EccInternalSignRequest;
import com.cisd.tms.integration.crypto.pcie.model.EccInternalVerifyRequest;
import com.cisd.tms.integration.crypto.pcie.model.GenerateKeyPairRequest; import com.cisd.tms.integration.crypto.pcie.model.GenerateKeyPairRequest;
import com.cisd.tms.integration.crypto.pcie.model.KeyStatusResult; import com.cisd.tms.integration.crypto.pcie.model.KeyStatusResult;
import com.cisd.tms.integration.crypto.pcie.model.PrivateKeyAccessRequest;
import com.cisd.tms.integration.crypto.pcie.model.RecoverUserKeyRequest; import com.cisd.tms.integration.crypto.pcie.model.RecoverUserKeyRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeyDecryptRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeyEncryptRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeySignRequest;
import com.cisd.tms.integration.crypto.pcie.model.UserKeyVerifyRequest;
import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -252,20 +263,241 @@ class CryptoCardControllerTest {
inOrder.verify(pcieCryptoService).backupUserKey(1, 0, 120); inOrder.verify(pcieCryptoService).backupUserKey(1, 0, 120);
inOrder.verify(pcieCryptoService).deleteUserKey(1); inOrder.verify(pcieCryptoService).deleteUserKey(1);
inOrder.verify(pcieCryptoService).statusUserKeyEcc(1, 0); inOrder.verify(pcieCryptoService).statusUserKeyEcc(1, 0);
inOrder.verify(pcieCryptoService).generateKeyPairEcc(generateCaptor.capture());
inOrder.verify(pcieCryptoService).recoverUserKey(recoverCaptor.capture()); inOrder.verify(pcieCryptoService).recoverUserKey(recoverCaptor.capture());
inOrder.verify(pcieCryptoService).statusUserKeyEcc(1, 0); inOrder.verify(pcieCryptoService).statusUserKeyEcc(1, 0);
assertEquals(2, generateCaptor.getAllValues().size()); assertEquals(1, generateCaptor.getAllValues().size());
assertEquals(256, generateCaptor.getAllValues().get(0).getKeyBits()); assertEquals(256, generateCaptor.getAllValues().get(0).getKeyBits());
assertEquals(1, generateCaptor.getAllValues().get(0).getKeyIndex()); assertEquals(1, generateCaptor.getAllValues().get(0).getKeyIndex());
assertEquals(0, generateCaptor.getAllValues().get(0).getKeyType()); assertEquals(0, generateCaptor.getAllValues().get(0).getKeyType());
assertEquals(256, generateCaptor.getAllValues().get(1).getKeyBits());
assertEquals(1, generateCaptor.getAllValues().get(1).getKeyIndex());
assertEquals(0, generateCaptor.getAllValues().get(1).getKeyType());
assertEquals(1, recoverCaptor.getValue().getKeyIndex()); assertEquals(1, recoverCaptor.getValue().getKeyIndex());
assertEquals(0, recoverCaptor.getValue().getKeyType()); assertEquals(0, recoverCaptor.getValue().getKeyType());
assertEquals(1, recoverCaptor.getValue().getStoreFlag()); assertEquals(1, recoverCaptor.getValue().getStoreFlag());
assertArrayEquals(new byte[] {0x01, 0x23, 0x45, 0x67}, recoverCaptor.getValue().getEncryptedKey()); assertArrayEquals(new byte[] {0x01, 0x23, 0x45, 0x67}, recoverCaptor.getValue().getEncryptedKey());
} }
@Test
void shouldEncryptUserKeyViaHighLevelEndpoint() throws Exception {
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
BackupDataResult result = new BackupDataResult();
result.setLength(3);
result.setData(new byte[] {0x21, 0x43, 0x65});
Mockito.when(pcieCryptoService.userKeyEncrypt(Mockito.any())).thenReturn(result);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new CryptoCardController(pcieCryptoService))
.setControllerAdvice(new GlobalExceptionHandler())
.build();
mockMvc.perform(post("/api/v1/device/crypto/user-key/encrypt")
.contentType("application/json")
.content("""
{
"keyIndex": 7,
"dataBase64": "aGVsbG8="
}
"""))
.andExpect(status().isOk())
.andExpect(content().string(containsString("\"length\":3")))
.andExpect(content().string(containsString("\"cipherBase64\":\"IUNl\"")));
ArgumentCaptor<UserKeyEncryptRequest> captor = ArgumentCaptor.forClass(UserKeyEncryptRequest.class);
Mockito.verify(pcieCryptoService).userKeyEncrypt(captor.capture());
assertEquals(7, captor.getValue().getKeyIndex());
assertArrayEquals("hello".getBytes(java.nio.charset.StandardCharsets.UTF_8), captor.getValue().getData());
}
@Test
void shouldDecryptUserKeyViaHighLevelEndpoint() throws Exception {
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
BackupDataResult result = new BackupDataResult();
result.setLength(5);
result.setData("hello".getBytes(java.nio.charset.StandardCharsets.UTF_8));
Mockito.when(pcieCryptoService.userKeyDecrypt(Mockito.any())).thenReturn(result);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new CryptoCardController(pcieCryptoService))
.setControllerAdvice(new GlobalExceptionHandler())
.build();
mockMvc.perform(post("/api/v1/device/crypto/user-key/decrypt")
.contentType("application/json")
.content("""
{
"keyIndex": 7,
"cipherBase64": "IUNl"
}
"""))
.andExpect(status().isOk())
.andExpect(content().string(containsString("\"length\":5")))
.andExpect(content().string(containsString("\"dataBase64\":\"aGVsbG8=\"")));
ArgumentCaptor<UserKeyDecryptRequest> captor = ArgumentCaptor.forClass(UserKeyDecryptRequest.class);
Mockito.verify(pcieCryptoService).userKeyDecrypt(captor.capture());
assertEquals(7, captor.getValue().getKeyIndex());
assertArrayEquals(new byte[] {0x21, 0x43, 0x65}, captor.getValue().getCipherBlob());
}
@Test
void shouldSignUserKeyViaHighLevelEndpoint() throws Exception {
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
BackupDataResult result = new BackupDataResult();
result.setLength(4);
result.setData(new byte[] {0x11, 0x22, 0x33, 0x44});
Mockito.when(pcieCryptoService.userKeySignWithSm3(Mockito.any())).thenReturn(result);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new CryptoCardController(pcieCryptoService))
.setControllerAdvice(new GlobalExceptionHandler())
.build();
mockMvc.perform(post("/api/v1/device/crypto/user-key/sign")
.contentType("application/json")
.content("""
{
"keyIndex": 7,
"dataBase64": "c2lnbg=="
}
"""))
.andExpect(status().isOk())
.andExpect(content().string(containsString("\"length\":4")))
.andExpect(content().string(containsString("\"signatureBase64\":\"ESIzRA==\"")));
ArgumentCaptor<UserKeySignRequest> captor = ArgumentCaptor.forClass(UserKeySignRequest.class);
Mockito.verify(pcieCryptoService).userKeySignWithSm3(captor.capture());
assertEquals(7, captor.getValue().getKeyIndex());
assertArrayEquals("sign".getBytes(java.nio.charset.StandardCharsets.UTF_8), captor.getValue().getData());
}
@Test
void shouldVerifyUserKeyViaHighLevelEndpoint() throws Exception {
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new CryptoCardController(pcieCryptoService))
.setControllerAdvice(new GlobalExceptionHandler())
.build();
mockMvc.perform(post("/api/v1/device/crypto/user-key/verify")
.contentType("application/json")
.content("""
{
"keyIndex": 7,
"dataBase64": "c2lnbg==",
"signatureBase64": "ESIzRA=="
}
"""))
.andExpect(status().isOk())
.andExpect(content().string(containsString("\"passed\":true")));
ArgumentCaptor<UserKeyVerifyRequest> captor = ArgumentCaptor.forClass(UserKeyVerifyRequest.class);
Mockito.verify(pcieCryptoService).userKeyVerifyWithSm3(captor.capture());
assertEquals(7, captor.getValue().getKeyIndex());
assertArrayEquals("sign".getBytes(java.nio.charset.StandardCharsets.UTF_8), captor.getValue().getData());
assertArrayEquals(new byte[] {0x11, 0x22, 0x33, 0x44}, captor.getValue().getSignature());
}
@Test
void shouldRunUserKeyCryptoSelfTestFlow() throws Exception {
PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);
BackupDataResult cipher = new BackupDataResult();
cipher.setLength(3);
cipher.setData(new byte[] {0x21, 0x43, 0x65});
BackupDataResult decrypt = new BackupDataResult();
decrypt.setLength(5);
decrypt.setData("hello".getBytes(java.nio.charset.StandardCharsets.UTF_8));
BackupDataResult signature = new BackupDataResult();
signature.setLength(4);
signature.setData(new byte[] {0x11, 0x22, 0x33, 0x44});
BackupDataResult digest = new BackupDataResult();
digest.setLength(32);
digest.setData(new byte[] {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20
});
KeyStatusResult deleted = new KeyStatusResult();
deleted.setKeyState(0);
deleted.setKeyStateName("FREE");
Mockito.when(pcieCryptoService.sdfeInternalEncryptEcc(Mockito.any())).thenReturn(cipher);
Mockito.when(pcieCryptoService.sdfeInternalDecryptEcc(Mockito.any())).thenReturn(decrypt);
Mockito.when(pcieCryptoService.digest(Mockito.any())).thenReturn(digest);
Mockito.when(pcieCryptoService.eccInternalSign(Mockito.any())).thenReturn(signature);
Mockito.when(pcieCryptoService.statusUserKeyEcc(7, 0)).thenReturn(deleted);
Mockito.when(pcieCryptoService.statusUserKeyEcc(7, 1)).thenReturn(deleted);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new CryptoCardController(pcieCryptoService))
.setControllerAdvice(new GlobalExceptionHandler())
.build();
mockMvc.perform(post("/api/v1/device/crypto/user-key/crypto-self-test")
.contentType("application/json")
.content("""
{
"keyIndex": 7,
"encryptPlainBase64": "aGVsbG8=",
"signDataBase64": "c2lnbg=="
}
"""))
.andExpect(status().isOk())
.andExpect(content().string(containsString("\"keyIndex\":7")))
.andExpect(content().string(containsString("\"encryptPlainBase64\":\"aGVsbG8=\"")))
.andExpect(content().string(containsString("\"cipherLength\":3")))
.andExpect(content().string(containsString("\"cipherBase64\":\"IUNl\"")))
.andExpect(content().string(containsString("\"decryptLength\":5")))
.andExpect(content().string(containsString("\"decryptBase64\":\"aGVsbG8=\"")))
.andExpect(content().string(containsString("\"decryptMatched\":true")))
.andExpect(content().string(containsString("\"signatureLength\":4")))
.andExpect(content().string(containsString("\"signatureBase64\":\"ESIzRA==\"")))
.andExpect(content().string(containsString("\"signDigestBase64\":\"AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA=\"")))
.andExpect(content().string(containsString("\"verifyPassed\":true")))
.andExpect(content().string(containsString("\"signDeleteStatus\":{\"keyState\":0,\"keyStateName\":\"FREE\"}")))
.andExpect(content().string(containsString("\"encDeleteStatus\":{\"keyState\":0,\"keyStateName\":\"FREE\"}")));
ArgumentCaptor<GenerateKeyPairRequest> generateCaptor = ArgumentCaptor.forClass(GenerateKeyPairRequest.class);
ArgumentCaptor<DigestRequest> digestCaptor = ArgumentCaptor.forClass(DigestRequest.class);
ArgumentCaptor<EccInternalEncryptRequest> encryptCaptor = ArgumentCaptor.forClass(EccInternalEncryptRequest.class);
ArgumentCaptor<EccInternalDecryptRequest> decryptCaptor = ArgumentCaptor.forClass(EccInternalDecryptRequest.class);
ArgumentCaptor<EccInternalSignRequest> signCaptor = ArgumentCaptor.forClass(EccInternalSignRequest.class);
ArgumentCaptor<EccInternalVerifyRequest> verifyCaptor = ArgumentCaptor.forClass(EccInternalVerifyRequest.class);
ArgumentCaptor<PrivateKeyAccessRequest> accessCaptor = ArgumentCaptor.forClass(PrivateKeyAccessRequest.class);
InOrder inOrder = Mockito.inOrder(pcieCryptoService);
inOrder.verify(pcieCryptoService).generateKeyPairEcc(generateCaptor.capture());
inOrder.verify(pcieCryptoService).sdfeInternalEncryptEcc(encryptCaptor.capture());
inOrder.verify(pcieCryptoService).getPrivateKeyAccessRight(accessCaptor.capture());
inOrder.verify(pcieCryptoService).sdfeInternalDecryptEcc(decryptCaptor.capture());
inOrder.verify(pcieCryptoService).releasePrivateKeyAccessRight(7);
inOrder.verify(pcieCryptoService).deleteUserKey(7);
inOrder.verify(pcieCryptoService).statusUserKeyEcc(7, 1);
inOrder.verify(pcieCryptoService).generateKeyPairEcc(generateCaptor.capture());
inOrder.verify(pcieCryptoService).digest(digestCaptor.capture());
inOrder.verify(pcieCryptoService).getPrivateKeyAccessRight(accessCaptor.capture());
inOrder.verify(pcieCryptoService).eccInternalSign(signCaptor.capture());
inOrder.verify(pcieCryptoService).releasePrivateKeyAccessRight(7);
inOrder.verify(pcieCryptoService).eccInternalVerify(verifyCaptor.capture());
inOrder.verify(pcieCryptoService).deleteUserKey(7);
inOrder.verify(pcieCryptoService).statusUserKeyEcc(7, 0);
assertEquals(2, generateCaptor.getAllValues().size());
assertEquals(256, generateCaptor.getAllValues().get(0).getKeyBits());
assertEquals(7, generateCaptor.getAllValues().get(0).getKeyIndex());
assertEquals(1, generateCaptor.getAllValues().get(0).getKeyType());
assertEquals(256, generateCaptor.getAllValues().get(1).getKeyBits());
assertEquals(7, generateCaptor.getAllValues().get(1).getKeyIndex());
assertEquals(0, generateCaptor.getAllValues().get(1).getKeyType());
assertEquals(7, encryptCaptor.getValue().getKeyIndex());
assertArrayEquals("hello".getBytes(java.nio.charset.StandardCharsets.UTF_8), encryptCaptor.getValue().getData());
assertEquals(7, decryptCaptor.getValue().getKeyIndex());
assertArrayEquals(new byte[] {0x21, 0x43, 0x65}, decryptCaptor.getValue().getCipherBlob());
assertEquals(5, decryptCaptor.getValue().getOutBufferSize());
assertEquals(Gm0018AlgorithmIds.SM3, digestCaptor.getValue().getAlgId());
assertArrayEquals("sign".getBytes(java.nio.charset.StandardCharsets.UTF_8), digestCaptor.getValue().getData());
assertEquals(2, accessCaptor.getAllValues().size());
assertEquals(7, accessCaptor.getAllValues().get(0).getKeyIndex());
assertArrayEquals("88888888".getBytes(java.nio.charset.StandardCharsets.UTF_8), accessCaptor.getAllValues().get(0).getPassword());
assertEquals(7, accessCaptor.getAllValues().get(1).getKeyIndex());
assertArrayEquals("88888888".getBytes(java.nio.charset.StandardCharsets.UTF_8), accessCaptor.getAllValues().get(1).getPassword());
assertEquals(7, signCaptor.getValue().getKeyIndex());
assertArrayEquals(digest.getData(), signCaptor.getValue().getData());
assertEquals(7, verifyCaptor.getValue().getKeyIndex());
assertArrayEquals(digest.getData(), verifyCaptor.getValue().getData());
assertArrayEquals(new byte[] {0x11, 0x22, 0x33, 0x44}, verifyCaptor.getValue().getSignature());
}
} }
@@ -0,0 +1,31 @@
package com.cisd.tms.modules.device.controller;
import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.common.exception.GlobalExceptionHandler;
import com.cisd.tms.modules.device.service.NetworkConfigService;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class NetworkConfigControllerTest {
@Test
void shouldReturnBadRequestWhenBondModeDoesNotExist() throws Exception {
NetworkConfigService networkConfigService = Mockito.mock(NetworkConfigService.class);
Mockito.when(networkConfigService.getBondMode("bond0")).thenReturn(null);
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new NetworkConfigController(networkConfigService))
.setControllerAdvice(new GlobalExceptionHandler())
.build();
mockMvc.perform(get("/api/v1/device/network-config/bond/mode/bond0"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.code").value(ErrorCode.BAD_REQUEST.getCode()))
.andExpect(jsonPath("$.msg").value("未找到该连接的 Bond 模式"));
}
}
@@ -0,0 +1,77 @@
package com.cisd.tms.security.internal;
import com.cisd.tms.common.config.properties.TmsSecurityProperties;
import com.cisd.tms.common.enums.ErrorCode;
import com.cisd.tms.modules.auth.entity.AuthSessionEntity;
import com.cisd.tms.modules.auth.repository.AuthSessionRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Clock;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Optional;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
class InternalApiAuthInterceptorTest {
private static final Clock FIXED_CLOCK = Clock.fixed(Instant.parse("2026-04-17T08:30:00Z"), ZoneOffset.UTC);
@Test
void shouldReturnSessionInvalidWhenSessionTokenIsMissing() throws Exception {
AuthSessionRepository authSessionRepository = Mockito.mock(AuthSessionRepository.class);
InternalApiAuthInterceptor interceptor = new InternalApiAuthInterceptor(
authSessionRepository,
enabledSecurityProperties(),
new ObjectMapper(),
FIXED_CLOCK
);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/api/v1/auth/me");
MockHttpServletResponse response = new MockHttpServletResponse();
boolean allowed = interceptor.preHandle(request, response, new Object());
Assertions.assertFalse(allowed);
Assertions.assertEquals(401, response.getStatus());
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.SESSION_INVALID.getCode()));
Assertions.assertTrue(response.getContentAsString().contains("session token missing"));
Mockito.verifyNoInteractions(authSessionRepository);
}
@Test
void shouldReturnSessionInvalidWhenSessionIsExpired() throws Exception {
AuthSessionRepository authSessionRepository = Mockito.mock(AuthSessionRepository.class);
AuthSessionEntity expired = new AuthSessionEntity();
expired.setSessionToken("token-expired-001");
expired.setRoleCode("AUDIT_ADMIN");
expired.setAuthLevel("LIMITED");
expired.setExpiresAt(LocalDateTime.ofInstant(FIXED_CLOCK.instant(), ZoneOffset.UTC).minusMinutes(1));
Mockito.when(authSessionRepository.findBySessionToken("token-expired-001")).thenReturn(Optional.of(expired));
InternalApiAuthInterceptor interceptor = new InternalApiAuthInterceptor(
authSessionRepository,
enabledSecurityProperties(),
new ObjectMapper(),
FIXED_CLOCK
);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/api/v1/auth/me");
request.addHeader("X-Session-Token", "token-expired-001");
MockHttpServletResponse response = new MockHttpServletResponse();
boolean allowed = interceptor.preHandle(request, response, new Object());
Assertions.assertFalse(allowed);
Assertions.assertEquals(401, response.getStatus());
Assertions.assertTrue(response.getContentAsString().contains("\"code\":" + ErrorCode.SESSION_INVALID.getCode()));
Assertions.assertTrue(response.getContentAsString().contains("session expired"));
}
private static TmsSecurityProperties enabledSecurityProperties() {
TmsSecurityProperties properties = new TmsSecurityProperties();
properties.getInternalAuth().setEnabled(true);
return properties;
}
}