diff --git a/src/main/java/com/cisd/tms/integration/crypto/pcie/model/UserKeySm2SignRequest.java b/src/main/java/com/cisd/tms/integration/crypto/pcie/model/UserKeySm2SignRequest.java new file mode 100644 index 0000000..8ed7626 --- /dev/null +++ b/src/main/java/com/cisd/tms/integration/crypto/pcie/model/UserKeySm2SignRequest.java @@ -0,0 +1,32 @@ +package com.cisd.tms.integration.crypto.pcie.model; + +public class UserKeySm2SignRequest { + + private int keyIndex; + private byte[] data; + private byte[] userId; + + 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[] getUserId() { + return userId; + } + + public void setUserId(byte[] userId) { + this.userId = userId; + } +} diff --git a/src/main/java/com/cisd/tms/integration/crypto/pcie/model/UserKeySm2VerifyRequest.java b/src/main/java/com/cisd/tms/integration/crypto/pcie/model/UserKeySm2VerifyRequest.java new file mode 100644 index 0000000..42b728d --- /dev/null +++ b/src/main/java/com/cisd/tms/integration/crypto/pcie/model/UserKeySm2VerifyRequest.java @@ -0,0 +1,41 @@ +package com.cisd.tms.integration.crypto.pcie.model; + +public class UserKeySm2VerifyRequest { + + private int keyIndex; + private byte[] data; + private byte[] userId; + 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[] getUserId() { + return userId; + } + + public void setUserId(byte[] userId) { + this.userId = userId; + } + + public byte[] getSignature() { + return signature; + } + + public void setSignature(byte[] signature) { + this.signature = signature; + } +} 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 21308e0..b0209a5 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 @@ -81,6 +81,8 @@ 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.UserKeyDecryptRequest; import com.cisd.tms.integration.crypto.pcie.model.UserKeyEncryptRequest; +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.model.UserKeySignRequest; import com.cisd.tms.integration.crypto.pcie.model.UserKeyVerifyRequest; import com.sun.jna.Memory; @@ -102,6 +104,7 @@ public class JnaPcieCryptoService implements PcieCryptoService { 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 String DEFAULT_PRIVATE_KEY_ACCESS_PASSWORD = "88888888"; + private static final byte[] DEFAULT_SM2_USER_ID = "1234567812345678".getBytes(StandardCharsets.UTF_8); private final PcieSessionTemplate sessionTemplate; private final boolean strictAlgIdValidation; @@ -1697,6 +1700,29 @@ public class JnaPcieCryptoService implements PcieCryptoService { eccInternalVerify(verifyRequest); } + @Override + public BackupDataResult userKeySignWithSm2Sm3(UserKeySm2SignRequest request) { + UserKeySm2SignRequest req = requireRequest("request", request); + int keyIndex = requireNonNegative("keyIndex", req.getKeyIndex()); + byte[] digest = digestWithSm2Sm3(keyIndex, requireBytes("data", req.getData()), normalizeSm2UserId(req.getUserId())); + + EccInternalSignRequest signRequest = new EccInternalSignRequest(); + signRequest.setKeyIndex(keyIndex); + signRequest.setData(digest); + return withDefaultPrivateKeyAccess(keyIndex, () -> eccInternalSign(signRequest)); + } + + @Override + public void userKeyVerifyWithSm2Sm3(UserKeySm2VerifyRequest request) { + UserKeySm2VerifyRequest req = requireRequest("request", request); + int keyIndex = requireNonNegative("keyIndex", req.getKeyIndex()); + EccInternalVerifyRequest verifyRequest = new EccInternalVerifyRequest(); + verifyRequest.setKeyIndex(keyIndex); + verifyRequest.setData(digestWithSm2Sm3(keyIndex, requireBytes("data", req.getData()), normalizeSm2UserId(req.getUserId()))); + verifyRequest.setSignature(requireNonEmptyBytes("signature", req.getSignature())); + eccInternalVerify(verifyRequest); + } + @Override public HmacResult hmac(HmacRequest request) { HmacRequest req = requireRequest("request", request); @@ -2203,4 +2229,29 @@ public class JnaPcieCryptoService implements PcieCryptoService { digestRequest.setOutBufferSize(32); return requireNonEmptyBytes("sm3Digest", digest(digestRequest).getData()); } + + private byte[] digestWithSm2Sm3(int keyIndex, byte[] data, byte[] userId) { + BackupDataResult publicKeyResult = exportSignPublicKeyEcc(keyIndex); + EccRefPublicKey publicKey = toEccRefPublicKey("publicKey", publicKeyResult.getData()); + return sessionTemplate.withSession("SDF_HashFinal_SM2", (lib, deviceHandle, sessionHandle) -> { + sessionTemplate.ensureSuccess( + "SDF_HashInit", + lib.SDF_HashInit(sessionHandle, Gm0018AlgorithmIds.SM3, publicKey, userId, userId.length) + ); + if (data.length > 0) { + sessionTemplate.ensureSuccess("SDF_HashUpdate", lib.SDF_HashUpdate(sessionHandle, data, data.length)); + } + byte[] out = new byte[32]; + IntByReference outLen = new IntByReference(out.length); + sessionTemplate.ensureSuccess("SDF_HashFinal", lib.SDF_HashFinal(sessionHandle, out, outLen)); + return requireNonEmptyBytes("sm2Sm3Digest", toBackupResult("SDF_HashFinal_SM2", out, outLen.getValue()).getData()); + }); + } + + private static byte[] normalizeSm2UserId(byte[] userId) { + if (userId == null || userId.length == 0) { + return Arrays.copyOf(DEFAULT_SM2_USER_ID, DEFAULT_SM2_USER_ID.length); + } + return Arrays.copyOf(userId, userId.length); + } } diff --git a/src/main/java/com/cisd/tms/integration/crypto/pcie/service/MockPcieCryptoService.java b/src/main/java/com/cisd/tms/integration/crypto/pcie/service/MockPcieCryptoService.java index d61a866..d5afec0 100644 --- a/src/main/java/com/cisd/tms/integration/crypto/pcie/service/MockPcieCryptoService.java +++ b/src/main/java/com/cisd/tms/integration/crypto/pcie/service/MockPcieCryptoService.java @@ -69,6 +69,8 @@ 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.UserKeyDecryptRequest; import com.cisd.tms.integration.crypto.pcie.model.UserKeyEncryptRequest; +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.model.UserKeySignRequest; import com.cisd.tms.integration.crypto.pcie.model.UserKeyVerifyRequest; import java.nio.charset.StandardCharsets; @@ -748,6 +750,24 @@ public class MockPcieCryptoService implements PcieCryptoService { eccInternalVerify(verifyRequest); } + @Override + public BackupDataResult userKeySignWithSm2Sm3(UserKeySm2SignRequest request) { + UserKeySm2SignRequest req = requireRequest("request", request); + requireNonNegative("keyIndex", req.getKeyIndex()); + requireBytes("data", req.getData()); + requireBytes("userId", req.getUserId()); + return mockBackup(new EccSignature().size(), 0x4A); + } + + @Override + public void userKeyVerifyWithSm2Sm3(UserKeySm2VerifyRequest request) { + UserKeySm2VerifyRequest req = requireRequest("request", request); + requireNonNegative("keyIndex", req.getKeyIndex()); + requireBytes("data", req.getData()); + requireBytes("userId", req.getUserId()); + requireNonEmptyBytes("signature", req.getSignature()); + } + @Override public HmacResult hmac(HmacRequest request) { HmacRequest req = requireRequest("request", request); diff --git a/src/main/java/com/cisd/tms/integration/crypto/pcie/service/PcieCryptoService.java b/src/main/java/com/cisd/tms/integration/crypto/pcie/service/PcieCryptoService.java index 94825cf..f71984a 100644 --- a/src/main/java/com/cisd/tms/integration/crypto/pcie/service/PcieCryptoService.java +++ b/src/main/java/com/cisd/tms/integration/crypto/pcie/service/PcieCryptoService.java @@ -59,6 +59,8 @@ 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.UserKeyDecryptRequest; import com.cisd.tms.integration.crypto.pcie.model.UserKeyEncryptRequest; +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.model.UserKeySignRequest; import com.cisd.tms.integration.crypto.pcie.model.UserKeyVerifyRequest; @@ -479,6 +481,16 @@ public interface PcieCryptoService { */ void userKeyVerifyWithSm3(UserKeyVerifyRequest request); + /** + * 按标准 SM2-with-SM3 流程计算 ZA || 原文摘要,再使用用户签名密钥完成内部 ECC 签名。 + */ + BackupDataResult userKeySignWithSm2Sm3(UserKeySm2SignRequest request); + + /** + * 按标准 SM2-with-SM3 流程计算 ZA || 原文摘要,再使用用户签名公钥完成内部 ECC 验签。 + */ + void userKeyVerifyWithSm2Sm3(UserKeySm2VerifyRequest request); + /** * 使用 SD FE HMAC 流程计算 HMAC。 */ diff --git a/src/main/java/com/cisd/tms/modules/cert/dto/Subject.java b/src/main/java/com/cisd/tms/modules/cert/dto/Subject.java index f2dd675..09788a5 100644 --- a/src/main/java/com/cisd/tms/modules/cert/dto/Subject.java +++ b/src/main/java/com/cisd/tms/modules/cert/dto/Subject.java @@ -1,14 +1,15 @@ package com.cisd.tms.modules.cert.dto; import lombok.Data; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.X500NameBuilder; +import org.bouncycastle.asn1.x500.style.BCStyle; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @Data public class Subject { - private static final String COMMA = ","; - private String commonName; private String country; private String province; @@ -17,28 +18,32 @@ public class Subject { private String orgUnit; public String getDN() { + return toX500Name().toString(); + } + + public X500Name toX500Name() { Assert.hasText(commonName, "commonName must not be blank"); - StringBuilder builder = new StringBuilder(); + X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE); if (StringUtils.hasText(commonName)) { - builder.append("CN=").append(commonName).append(COMMA); + builder.addRDN(BCStyle.CN, commonName); } if (StringUtils.hasText(orgUnit)) { - builder.append("OU=").append(orgUnit).append(COMMA); + builder.addRDN(BCStyle.OU, orgUnit); } if (StringUtils.hasText(org)) { - builder.append("O=").append(org).append(COMMA); + builder.addRDN(BCStyle.O, org); } if (StringUtils.hasText(city)) { - builder.append("L=").append(city).append(COMMA); + builder.addRDN(BCStyle.L, city); } if (StringUtils.hasText(province)) { - builder.append("ST=").append(province).append(COMMA); + builder.addRDN(BCStyle.ST, province); } if (StringUtils.hasText(country)) { - builder.append("C=").append(country).append(COMMA); + Assert.isTrue(country.length() == 2, "country must be a 2-letter country code"); + builder.addRDN(BCStyle.C, country); } - builder.deleteCharAt(builder.length() - 1); - return builder.toString(); + return builder.build(); } public static Subject fromDN(String dn) { 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 038137d..ad1d0ed 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 @@ -6,7 +6,7 @@ import com.cisd.tms.common.exception.BizException; import com.cisd.tms.integration.crypto.pcie.PcieUserKeyType; import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult; import com.cisd.tms.integration.crypto.pcie.model.GenerateKeyPairRequest; -import com.cisd.tms.integration.crypto.pcie.model.UserKeySignRequest; +import com.cisd.tms.integration.crypto.pcie.model.UserKeySm2SignRequest; import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; import com.cisd.tms.modules.cert.dto.EntityCreateRequest; import com.cisd.tms.modules.cert.dto.EntityCreateResponse; @@ -44,6 +44,7 @@ import java.util.List; public class EntityService { private static final String SM2 = "SM2"; + private static final byte[] DEFAULT_SM2_USER_ID = "1234567812345678".getBytes(StandardCharsets.UTF_8); private final KeyEntityRepository keyEntityRepository; private final CertificateRepository certificateRepository; @@ -132,10 +133,11 @@ public class EntityService { public String generateP10(EntityGenP10Request request) { KeyEntity entity = getById(request.getId()); Subject subject = request.getSubject(); - String subjectDn = subject.getDN(); + X500Name subjectName = subject.toX500Name(); + String subjectDn = subjectName.toString(); PublicKey publicKey = Sm2KeySupport.toSm2PublicKey(Base64.getDecoder().decode(entity.getPublicKey())); PKCS10CertificationRequestBuilder builder = - new JcaPKCS10CertificationRequestBuilder(new X500Name(subjectDn), publicKey); + new JcaPKCS10CertificationRequestBuilder(subjectName, publicKey); PKCS10CertificationRequest csr = builder.build(new CardContentSigner(entity.getKeyIdx(), pcieCryptoService)); String pem = encodePkcs10Pem(csr); entity.setSubjectDn(subjectDn); @@ -229,10 +231,11 @@ public class EntityService { @Override public byte[] getSignature() { - UserKeySignRequest request = new UserKeySignRequest(); + UserKeySm2SignRequest request = new UserKeySm2SignRequest(); request.setKeyIndex(keyIdx); request.setData(output.toByteArray()); - return Sm2KeySupport.rawSignatureToDer(pcieCryptoService.userKeySignWithSm3(request).getData()); + request.setUserId(DEFAULT_SM2_USER_ID); + return Sm2KeySupport.rawSignatureToDer(pcieCryptoService.userKeySignWithSm2Sm3(request).getData()); } } } diff --git a/src/main/java/com/cisd/tms/modules/device/controller/CryptoCardController.java b/src/main/java/com/cisd/tms/modules/device/controller/CryptoCardController.java index 9bc1bff..a7611b2 100644 --- a/src/main/java/com/cisd/tms/modules/device/controller/CryptoCardController.java +++ b/src/main/java/com/cisd/tms/modules/device/controller/CryptoCardController.java @@ -41,6 +41,8 @@ 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.UserKeyDecryptRequest; import com.cisd.tms.integration.crypto.pcie.model.UserKeyEncryptRequest; +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.model.UserKeySignRequest; import com.cisd.tms.integration.crypto.pcie.model.UserKeyVerifyRequest; import com.cisd.tms.integration.crypto.pcie.service.PcieDeviceStatusFormatter; @@ -78,6 +80,7 @@ import org.springframework.web.bind.annotation.RestController; public class CryptoCardController { private static final String DEFAULT_USER_KEY_ACCESS_PASSWORD = "88888888"; + private static final byte[] DEFAULT_SM2_USER_ID = "1234567812345678".getBytes(StandardCharsets.UTF_8); private final PcieCryptoService pcieCryptoService; @@ -598,6 +601,44 @@ public class CryptoCardController { return ApiResponse.success(Map.of("passed", true)); } + @PostMapping("/user-key/sm2-sign") + @Operation(summary = "用户密钥 SM2-with-SM3 签名", description = "使用用户签名密钥按标准 SM2 ZA + SM3 流程完成内部 ECC 签名。") + public ApiResponse> signUserKeyWithSm2Sm3( + @Valid @RequestBody CryptoUserKeySm2SignInternalRequest request + ) { + UserKeySm2SignRequest signRequest = new UserKeySm2SignRequest(); + signRequest.setKeyIndex(request.getKeyIndex()); + signRequest.setData(Base64.getDecoder().decode(request.getDataBase64())); + signRequest.setUserId(request.getUserIdBase64() == null || request.getUserIdBase64().isBlank() + ? DEFAULT_SM2_USER_ID + : Base64.getDecoder().decode(request.getUserIdBase64())); + BackupDataResult result = pcieCryptoService.userKeySignWithSm2Sm3(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/sm2-verify") + @Operation(summary = "用户密钥 SM2-with-SM3 验签", description = "使用用户签名公钥按标准 SM2 ZA + SM3 流程完成内部 ECC 验签。") + public ApiResponse> verifyUserKeyWithSm2Sm3( + @Valid @RequestBody CryptoUserKeySm2VerifyInternalRequest request + ) { + UserKeySm2VerifyRequest verifyRequest = new UserKeySm2VerifyRequest(); + verifyRequest.setKeyIndex(request.getKeyIndex()); + verifyRequest.setData(Base64.getDecoder().decode(request.getDataBase64())); + verifyRequest.setUserId(request.getUserIdBase64() == null || request.getUserIdBase64().isBlank() + ? DEFAULT_SM2_USER_ID + : Base64.getDecoder().decode(request.getUserIdBase64())); + verifyRequest.setSignature(Base64.getDecoder().decode(request.getSignatureBase64())); + pcieCryptoService.userKeyVerifyWithSm2Sm3(verifyRequest); + return ApiResponse.success(Map.of("passed", true)); + } + @PostMapping("/user-key/self-test") @Operation(summary = "执行用户密钥全流程自测", description = "依次执行 ECC 用户密钥生成、备份、删除、重建槽位、恢复和状态校验,返回备份 HEX 与关键状态。") public ApiResponse> selfTestUserKey( diff --git a/src/main/java/com/cisd/tms/modules/device/dto/crypto/request/CryptoUserKeySm2SignInternalRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/crypto/request/CryptoUserKeySm2SignInternalRequest.java new file mode 100644 index 0000000..4a195a1 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/crypto/request/CryptoUserKeySm2SignInternalRequest.java @@ -0,0 +1,39 @@ +package com.cisd.tms.modules.device.dto.crypto.request; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; + +public class CryptoUserKeySm2SignInternalRequest { + + @Min(0) + private int keyIndex; + + @NotBlank + private String dataBase64; + + private String userIdBase64; + + 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 getUserIdBase64() { + return userIdBase64; + } + + public void setUserIdBase64(String userIdBase64) { + this.userIdBase64 = userIdBase64; + } +} diff --git a/src/main/java/com/cisd/tms/modules/device/dto/crypto/request/CryptoUserKeySm2VerifyInternalRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/crypto/request/CryptoUserKeySm2VerifyInternalRequest.java new file mode 100644 index 0000000..b1fe307 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/crypto/request/CryptoUserKeySm2VerifyInternalRequest.java @@ -0,0 +1,50 @@ +package com.cisd.tms.modules.device.dto.crypto.request; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; + +public class CryptoUserKeySm2VerifyInternalRequest { + + @Min(0) + private int keyIndex; + + @NotBlank + private String dataBase64; + + private String userIdBase64; + + @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 getUserIdBase64() { + return userIdBase64; + } + + public void setUserIdBase64(String userIdBase64) { + this.userIdBase64 = userIdBase64; + } + + public String getSignatureBase64() { + return signatureBase64; + } + + public void setSignatureBase64(String signatureBase64) { + this.signatureBase64 = signatureBase64; + } +} diff --git a/src/test/java/com/cisd/tms/modules/cert/service/EntityServiceTest.java b/src/test/java/com/cisd/tms/modules/cert/service/EntityServiceTest.java new file mode 100644 index 0000000..7caded4 --- /dev/null +++ b/src/test/java/com/cisd/tms/modules/cert/service/EntityServiceTest.java @@ -0,0 +1,136 @@ +package com.cisd.tms.modules.cert.service; + +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.UserKeySignRequest; +import com.cisd.tms.integration.crypto.pcie.model.UserKeySm2SignRequest; +import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; +import com.cisd.tms.modules.cert.dto.EntityGenP10Request; +import com.cisd.tms.modules.cert.dto.Subject; +import com.cisd.tms.modules.cert.entity.KeyEntity; +import com.cisd.tms.modules.cert.repository.CertificateRepository; +import com.cisd.tms.modules.cert.repository.KeyEntityRepository; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import java.io.StringReader; +import java.security.KeyPairGenerator; +import java.security.Security; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECGenParameterSpec; +import java.util.Base64; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; + +class EntityServiceTest { + + @Test + void shouldGenerateP10WithStandardSm2Sm3Signer() throws Exception { + KeyEntityRepository keyEntityRepository = Mockito.mock(KeyEntityRepository.class); + CertificateRepository certificateRepository = Mockito.mock(CertificateRepository.class); + PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); + EntityService service = new EntityService(keyEntityRepository, certificateRepository, pcieCryptoService, new ObjectMapper()); + Mockito.when(keyEntityRepository.findById(10L)).thenReturn(Optional.of(keyEntity(10L, 5))); + Mockito.when(keyEntityRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); + Mockito.when(pcieCryptoService.userKeySignWithSm3(any())).thenReturn(signatureResult()); + Mockito.when(pcieCryptoService.userKeySignWithSm2Sm3(any())).thenReturn(signatureResult()); + + service.generateP10(genP10Request("TMS-001")); + + ArgumentCaptor sm2Captor = ArgumentCaptor.forClass(UserKeySm2SignRequest.class); + Mockito.verify(pcieCryptoService).userKeySignWithSm2Sm3(sm2Captor.capture()); + Mockito.verify(pcieCryptoService, Mockito.never()).userKeySignWithSm3(any(UserKeySignRequest.class)); + assertEquals(5, sm2Captor.getValue().getKeyIndex()); + assertEquals("1234567812345678", new String(sm2Captor.getValue().getUserId())); + } + + @Test + void shouldPreserveSpecialCharactersInP10Subject() throws Exception { + KeyEntityRepository keyEntityRepository = Mockito.mock(KeyEntityRepository.class); + CertificateRepository certificateRepository = Mockito.mock(CertificateRepository.class); + PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); + EntityService service = new EntityService(keyEntityRepository, certificateRepository, pcieCryptoService, new ObjectMapper()); + Mockito.when(keyEntityRepository.findById(10L)).thenReturn(Optional.of(keyEntity(10L, 5))); + Mockito.when(keyEntityRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); + Mockito.when(pcieCryptoService.userKeySignWithSm3(any())).thenReturn(signatureResult()); + Mockito.when(pcieCryptoService.userKeySignWithSm2Sm3(any())).thenReturn(signatureResult()); + + String pem = service.generateP10(genP10Request("TMS,Entity+001")); + + PKCS10CertificationRequest csr = (PKCS10CertificationRequest) new PEMParser(new StringReader(pem)).readObject(); + String commonName = csr.getSubject().getRDNs(BCStyle.CN)[0].getFirst().getValue().toString(); + assertEquals("TMS,Entity+001", commonName); + } + + private EntityGenP10Request genP10Request(String commonName) { + Subject subject = new Subject(); + subject.setCommonName(commonName); + subject.setOrgUnit("Cert"); + subject.setOrg("CISD"); + subject.setCity("南南市"); + subject.setProvince("陕西省"); + subject.setCountry("CN"); + EntityGenP10Request request = new EntityGenP10Request(); + request.setId(10L); + request.setSubject(subject); + return request; + } + + private KeyEntity keyEntity(Long id, int keyIdx) throws Exception { + KeyEntity entity = new KeyEntity(); + entity.setId(id); + entity.setKeyIdx(keyIdx); + entity.setPublicKey(Base64.getEncoder().encodeToString(gm0018EccRefPublicKey(generateSm2PublicKey()))); + return entity; + } + + private BackupDataResult signatureResult() { + EccSignature signature = new EccSignature(); + signature.r[EccRefPublicKey.MAX_LEN - 1] = 0x01; + signature.s[EccRefPublicKey.MAX_LEN - 1] = 0x02; + signature.write(); + BackupDataResult result = new BackupDataResult(); + result.setLength(signature.size()); + result.setData(signature.getPointer().getByteArray(0, signature.size())); + return result; + } + + private ECPublicKey generateSm2PublicKey() throws Exception { + if (Security.getProvider("BC") == null) { + Security.addProvider(new BouncyCastleProvider()); + } + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC"); + keyPairGenerator.initialize(new ECGenParameterSpec("sm2p256v1")); + return (ECPublicKey) keyPairGenerator.generateKeyPair().getPublic(); + } + + private byte[] gm0018EccRefPublicKey(ECPublicKey publicKey) { + byte[] x = toFixed32(publicKey.getW().getAffineX().toByteArray()); + byte[] y = toFixed32(publicKey.getW().getAffineY().toByteArray()); + byte[] result = new byte[new EccRefPublicKey().size()]; + result[0] = 0x00; + result[1] = 0x01; + result[2] = 0x00; + result[3] = 0x00; + System.arraycopy(x, 0, result, 4 + 32, 32); + System.arraycopy(y, 0, result, 4 + 64 + 32, 32); + return result; + } + + private byte[] toFixed32(byte[] source) { + byte[] result = new byte[32]; + int copyLen = Math.min(source.length, 32); + System.arraycopy(source, source.length - copyLen, result, 32 - copyLen, copyLen); + return result; + } +} diff --git a/src/test/java/com/cisd/tms/modules/device/controller/CryptoCardControllerTest.java b/src/test/java/com/cisd/tms/modules/device/controller/CryptoCardControllerTest.java index f2179b7..650abb7 100644 --- a/src/test/java/com/cisd/tms/modules/device/controller/CryptoCardControllerTest.java +++ b/src/test/java/com/cisd/tms/modules/device/controller/CryptoCardControllerTest.java @@ -15,6 +15,8 @@ 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.UserKeyDecryptRequest; import com.cisd.tms.integration.crypto.pcie.model.UserKeyEncryptRequest; +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.model.UserKeySignRequest; import com.cisd.tms.integration.crypto.pcie.model.UserKeyVerifyRequest; import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; @@ -393,6 +395,66 @@ class CryptoCardControllerTest { assertArrayEquals(new byte[] {0x11, 0x22, 0x33, 0x44}, captor.getValue().getSignature()); } + @Test + void shouldSignUserKeyWithStandardSm2Sm3Endpoint() 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.userKeySignWithSm2Sm3(Mockito.any())).thenReturn(result); + + MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new CryptoCardController(pcieCryptoService)) + .setControllerAdvice(new GlobalExceptionHandler()) + .build(); + + mockMvc.perform(post("/api/v1/device/crypto/user-key/sm2-sign") + .contentType("application/json") + .content(""" + { + "keyIndex": 7, + "dataBase64": "Y3Ny", + "userIdBase64": "MTIzNDU2NzgxMjM0NTY3OA==" + } + """)) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("\"length\":4"))) + .andExpect(content().string(containsString("\"signatureBase64\":\"ESIzRA==\""))); + + ArgumentCaptor captor = ArgumentCaptor.forClass(UserKeySm2SignRequest.class); + Mockito.verify(pcieCryptoService).userKeySignWithSm2Sm3(captor.capture()); + assertEquals(7, captor.getValue().getKeyIndex()); + assertArrayEquals("csr".getBytes(java.nio.charset.StandardCharsets.UTF_8), captor.getValue().getData()); + assertArrayEquals("1234567812345678".getBytes(java.nio.charset.StandardCharsets.UTF_8), captor.getValue().getUserId()); + } + + @Test + void shouldVerifyUserKeyWithStandardSm2Sm3Endpoint() 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/sm2-verify") + .contentType("application/json") + .content(""" + { + "keyIndex": 7, + "dataBase64": "Y3Ny", + "signatureBase64": "ESIzRA==" + } + """)) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("\"passed\":true"))); + + ArgumentCaptor captor = ArgumentCaptor.forClass(UserKeySm2VerifyRequest.class); + Mockito.verify(pcieCryptoService).userKeyVerifyWithSm2Sm3(captor.capture()); + assertEquals(7, captor.getValue().getKeyIndex()); + assertArrayEquals("csr".getBytes(java.nio.charset.StandardCharsets.UTF_8), captor.getValue().getData()); + assertArrayEquals("1234567812345678".getBytes(java.nio.charset.StandardCharsets.UTF_8), captor.getValue().getUserId()); + assertArrayEquals(new byte[] {0x11, 0x22, 0x33, 0x44}, captor.getValue().getSignature()); + } + @Test void shouldRunUserKeyCryptoSelfTestFlow() throws Exception { PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class);