From 738c2d4ae6968af314433eb1d1a96bc3cc7b5f74 Mon Sep 17 00:00:00 2001 From: waner Date: Thu, 2 Apr 2026 13:59:29 +0800 Subject: [PATCH] =?UTF-8?q?MK=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pcie/model/RecoverUserKeyRequest.java | 2 + .../pcie/service/JnaPcieCryptoService.java | 4 +- .../pcie/service/PcieCryptoService.java | 2 +- .../service/impl/AuthAdminServiceImpl.java | 1 + .../controller/CryptoCardController.java | 48 +++++ .../CryptoUserKeySelfTestInternalRequest.java | 50 ++++++ .../mk/service/impl/LmkServiceImpl.java | 170 ++++++++++++++---- .../controller/CryptoCardControllerTest.java | 65 +++++++ .../modules/mk/service/LmkServiceTest.java | 114 +++++++++++- 9 files changed, 414 insertions(+), 42 deletions(-) create mode 100644 src/main/java/com/cisd/tms/modules/device/dto/crypto/request/CryptoUserKeySelfTestInternalRequest.java diff --git a/src/main/java/com/cisd/tms/integration/crypto/pcie/model/RecoverUserKeyRequest.java b/src/main/java/com/cisd/tms/integration/crypto/pcie/model/RecoverUserKeyRequest.java index 38d5541..5b5dc21 100644 --- a/src/main/java/com/cisd/tms/integration/crypto/pcie/model/RecoverUserKeyRequest.java +++ b/src/main/java/com/cisd/tms/integration/crypto/pcie/model/RecoverUserKeyRequest.java @@ -5,6 +5,8 @@ public class RecoverUserKeyRequest { private int keyIndex; private int keyType; private byte[] encryptedKey; + + //1:掉电保存;0:掉电不保存(防止flash写坏) private int storeFlag = 1; public int getKeyIndex() { 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 bc24677..1737265 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 @@ -205,7 +205,7 @@ public class JnaPcieCryptoService implements PcieCryptoService { public byte[] initializeMasterKey(byte[] oldPin, byte[] newPin) { byte[] oldPinValue = requireNonEmptyBytes("oldPin", oldPin); byte[] newPinValue = requireNonEmptyBytes("newPin", newPin); - //lmk初始化包含主密钥生成、加载;内部密钥生成;用户密钥生成;返回主密钥校验值。 + // 一次会话内完成初始化识别、LMK 生成加载、IK 生成和默认用户密钥生成,避免中途切换状态。 try { return sessionTemplate.withSession("SDFE_InitializeMasterKey", (lib, deviceHandle, sessionHandle) -> { sessionTemplate.ensureSuccess( @@ -1837,6 +1837,7 @@ public class JnaPcieCryptoService implements PcieCryptoService { return sessionTemplate.withSession("SDFE_BackupLMK_EX", (lib, deviceHandle, sessionHandle) -> { SdfeLmkComponent output = new SdfeLmkComponent(); sessionTemplate.ensureSuccess("SDFE_BackupLMK_EX", lib.SDFE_BackupLMK_EX(sessionHandle, output)); + // 这里返回的是完整的 LMK 扩展结构:8 字节校验值 + 3 个 16 字节分量。 byte[] outputBytes = toBytes(output); return toBackupResult("SDFE_BackupLMK_EX", outputBytes, outputBytes.length); }); @@ -1847,6 +1848,7 @@ public class JnaPcieCryptoService implements PcieCryptoService { SdfeLmkComponent input = toSdfeLmkComponent("lmkComponent", lmkComponent); sessionTemplate.withSession("SDFE_RecoverLMK_EX", (lib, deviceHandle, sessionHandle) -> { + // 输入必须是同一轮导出的 lmkMac + component1 + component2 + component3,否则底层会报 MACERR。 sessionTemplate.ensureSuccess("SDFE_RecoverLMK_EX", lib.SDFE_RecoverLMK_EX(sessionHandle, input)); return null; }); 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 9fc1176..892e27e 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 @@ -423,7 +423,7 @@ public interface PcieCryptoService { BackupDataResult digest(DigestRequest request); - //----------------------------------------SD FE 专用算法扩展接口------------------------------------------ + //----------------------------------------SDFE 专用算法扩展接口------------------------------------------ /** * 读取固件版本信息。 diff --git a/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthAdminServiceImpl.java b/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthAdminServiceImpl.java index 476f96a..37958e3 100644 --- a/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthAdminServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/auth/service/impl/AuthAdminServiceImpl.java @@ -140,6 +140,7 @@ public class AuthAdminServiceImpl implements AuthAdminService { String signValue = lmkService.signIk(toIssuePayload(dto, authKeyPair)); MasterKeyBackupPacket backupPacket = null; if (RoleCode.SUPER_ADMIN == targetRole) { + // 超级管理员需要 3 把 UKey;这里按 uid=1/2/3 取出同一轮备份中的对应分量包。 backupPacket = lmkService.buildBackupPacket(Integer.parseInt(request.getUid())); } return UKeySignResult.builder() 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 fa2cdf9..b7dc2a7 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 @@ -22,6 +22,7 @@ import com.cisd.tms.integration.crypto.pcie.model.FileReadRequest; import com.cisd.tms.integration.crypto.pcie.model.FileWriteRequest; import com.cisd.tms.integration.crypto.pcie.model.GenerateAgreementDataAndKeyEccRequest; import com.cisd.tms.integration.crypto.pcie.model.GenerateKekRequest; +import com.cisd.tms.integration.crypto.pcie.model.GenerateKeyPairRequest; import com.cisd.tms.integration.crypto.pcie.model.GenerateStandardEccKeyPairRequest; import com.cisd.tms.integration.crypto.pcie.model.GenerateStandardRsaKeyPairRequest; import com.cisd.tms.integration.crypto.pcie.model.HmacRequest; @@ -29,6 +30,7 @@ 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.LoginStatusResult; import com.cisd.tms.integration.crypto.pcie.model.MacCalcRequest; +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.SymmetricKekCryptoRequest; import com.cisd.tms.integration.crypto.pcie.model.StandardKeyPairResult; @@ -39,10 +41,13 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.Min; +import java.util.Arrays; import java.util.Base64; +import java.util.LinkedHashMap; import java.util.Map; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -52,6 +57,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @Validated +@Slf4j @RestController @RequestMapping("/api/v1/device/crypto") @RequiredArgsConstructor @@ -517,6 +523,48 @@ public class CryptoCardController { return ApiResponse.success(hexPayload("dataHex", result.getData(), result.getLength())); } + @PostMapping("/user-key/self-test") + @Operation(summary = "执行用户密钥全流程自测", description = "依次执行 ECC 用户密钥生成、备份、删除、重建槽位、恢复和状态校验,返回备份 HEX 与关键状态。") + public ApiResponse> selfTestUserKey( + @Valid @RequestBody CryptoUserKeySelfTestInternalRequest request + ) { + GenerateKeyPairRequest generateRequest = new GenerateKeyPairRequest(); + generateRequest.setKeyBits(256); + generateRequest.setKeyIndex(request.getKeyIndex()); + generateRequest.setKeyType(request.getKeyType()); + + pcieCryptoService.generateKeyPairEcc(generateRequest); + BackupDataResult backupResult = pcieCryptoService.backupUserKey( + request.getKeyIndex(), + request.getKeyType(), + request.getOutBufferSize() + ); + log.info("backupResult:{}", backupResult); + pcieCryptoService.deleteUserKey(request.getKeyIndex()); + KeyStatusResult deleteStatus = pcieCryptoService.statusUserKeyEcc(request.getKeyIndex(), request.getKeyType()); + +// pcieCryptoService.generateKeyPairEcc(generateRequest); + RecoverUserKeyRequest recoverRequest = new RecoverUserKeyRequest(); + int backupLength = backupResult.getLength() > 0 && backupResult.getLength() <= backupResult.getData().length + ? backupResult.getLength() + : backupResult.getData().length; + recoverRequest.setKeyIndex(request.getKeyIndex()); + recoverRequest.setKeyType(request.getKeyType()); + recoverRequest.setEncryptedKey(Arrays.copyOf(backupResult.getData(), backupLength)); + recoverRequest.setStoreFlag(request.getStoreFlag()); + pcieCryptoService.recoverUserKey(recoverRequest); + KeyStatusResult recoverStatus = pcieCryptoService.statusUserKeyEcc(request.getKeyIndex(), request.getKeyType()); + + Map payload = new LinkedHashMap<>(); + payload.put("keyIndex", request.getKeyIndex()); + payload.put("keyType", request.getKeyType()); + payload.put("backupLength", backupLength); + payload.put("backupHex", java.util.HexFormat.of().formatHex(Arrays.copyOf(backupResult.getData(), backupLength))); + payload.put("deleteStatus", deleteStatus); + payload.put("recoverStatus", recoverStatus); + return ApiResponse.success(payload); + } + @PostMapping("/user-key/recover") @Operation(summary = "恢复用户密钥", description = "从备份数据恢复用户密钥。") public ApiResponse recoverUserKey( diff --git a/src/main/java/com/cisd/tms/modules/device/dto/crypto/request/CryptoUserKeySelfTestInternalRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/crypto/request/CryptoUserKeySelfTestInternalRequest.java new file mode 100644 index 0000000..f56711b --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/crypto/request/CryptoUserKeySelfTestInternalRequest.java @@ -0,0 +1,50 @@ +package com.cisd.tms.modules.device.dto.crypto.request; + +import jakarta.validation.constraints.Min; + +public class CryptoUserKeySelfTestInternalRequest { + + @Min(0) + private int keyIndex; + + @Min(0) + private int keyType; + + @Min(1) + private int outBufferSize = 120; + + @Min(0) + private int storeFlag = 1; + + public int getKeyIndex() { + return keyIndex; + } + + public void setKeyIndex(int keyIndex) { + this.keyIndex = keyIndex; + } + + public int getKeyType() { + return keyType; + } + + public void setKeyType(int keyType) { + this.keyType = keyType; + } + + public int getOutBufferSize() { + return outBufferSize; + } + + public void setOutBufferSize(int outBufferSize) { + this.outBufferSize = outBufferSize; + } + + public int getStoreFlag() { + return storeFlag; + } + + public void setStoreFlag(int storeFlag) { + this.storeFlag = storeFlag; + } +} diff --git a/src/main/java/com/cisd/tms/modules/mk/service/impl/LmkServiceImpl.java b/src/main/java/com/cisd/tms/modules/mk/service/impl/LmkServiceImpl.java index 08ba671..3d19aa3 100644 --- a/src/main/java/com/cisd/tms/modules/mk/service/impl/LmkServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/mk/service/impl/LmkServiceImpl.java @@ -2,13 +2,10 @@ package com.cisd.tms.modules.mk.service.impl; import com.cisd.tms.common.util.DivisionUtils; import com.cisd.tms.integration.crypto.pcie.Gm0018AlgorithmIds; +import com.cisd.tms.integration.crypto.pcie.PcieUserKeyType; import com.cisd.tms.integration.crypto.pcie.jna.EccRefPublicKey; import com.cisd.tms.integration.crypto.pcie.jna.EccSignature; -import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult; -import com.cisd.tms.integration.crypto.pcie.model.DigestRequest; -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.RecoverUserKeyRequest; +import com.cisd.tms.integration.crypto.pcie.model.*; import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; import com.cisd.tms.modules.mk.common.LMKConstant; import com.cisd.tms.modules.mk.config.MasterKeyInitProperties; @@ -25,9 +22,11 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; +import java.util.HashSet; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.util.encoders.Hex; @@ -56,9 +55,13 @@ public class LmkServiceImpl implements LmkService { private final PcieCryptoService pcieCryptoService; private final MasterKeyInitProperties masterKeyInitProperties; + private final Object backupRoundLock = new Object(); + private CachedBackupRound cachedBackupRound; @Override public MasterKeyStateResult initMasterKey() { + // 密钥体系一旦重建,之前导出的三份备份包就全部失效,必须清空当前轮次缓存。 + clearCachedBackupRound(); byte[] oldPin = decodeConfiguredPin("tms.mk.init-identify.old-pin-base64", masterKeyInitProperties.getOldPinBase64()); byte[] newPin = decodeConfiguredPin("tms.mk.init-identify.new-pin-base64", masterKeyInitProperties.getNewPinBase64()); @@ -70,7 +73,13 @@ public class LmkServiceImpl implements LmkService { @Override public void destroyMasterKey() { + // 销毁后也不能再继续复用上一轮备份材料。 + clearCachedBackupRound(); + pcieCryptoService.destroyLmk(); + pcieCryptoService.destroyIk(IKEnums.KEY_TYPE_AUTH.getCode()); + pcieCryptoService.destroyIk(IKEnums.KEY_TYPE_DEVICE.getCode()); + pcieCryptoService.deleteUserKey(USER_KEY_INDEX); } @Override @@ -115,35 +124,27 @@ public class LmkServiceImpl implements LmkService { @Override public MasterKeyBackupPacket buildBackupPacket(int componentIndex) { int index = requireComponentIndex(componentIndex); - ParsedMasterKeyBackup lmkBackup = parseBackupMaterial(); - ParsedIkBackup authIkBackup = parseIkBackupMaterial(IKEnums.KEY_TYPE_AUTH.getCode()); - ParsedIkBackup deviceIkBackup = parseIkBackupMaterial(IKEnums.KEY_TYPE_DEVICE.getCode()); + synchronized (backupRoundLock) { + CachedBackupRound round = cachedBackupRound; + if (round == null) { + // 三次发给外部 UKey 的分量必须来自同一轮导出,否则恢复 LMK 时会出现 MACERR。 + round = new CachedBackupRound( + parseBackupMaterial(), + parseIkBackupMaterial(IKEnums.KEY_TYPE_AUTH.getCode()), + parseIkBackupMaterial(IKEnums.KEY_TYPE_DEVICE.getCode()), + parseUserKeyBackup(USER_KEY_INDEX, USER_KEY_TYPE_SIGN), + parseUserKeyBackup(USER_KEY_INDEX, USER_KEY_TYPE_ENC) + ); + cachedBackupRound = round; + } - MasterKeyBackupPacket packet = new MasterKeyBackupPacket(); - packet.setComponentIndex(index); - packet.setLmk(new MasterKeyPacketLmkPart( - lmkBackup.lmkMacHex(), - requiredComponent(lmkBackup.componentMap(), index, "lmk") - )); - packet.setIkAuth(new MasterKeyPacketIkPart( - IKEnums.KEY_TYPE_AUTH.getCode(), - authIkBackup.macHex(), - requiredComponent(authIkBackup.componentMap(), index, "ikAuth"), - authIkBackup.publicKeyHex(), - authIkBackup.publicKeyMacHex() - )); - packet.setIkDevice(new MasterKeyPacketIkPart( - IKEnums.KEY_TYPE_DEVICE.getCode(), - deviceIkBackup.macHex(), - requiredComponent(deviceIkBackup.componentMap(), index, "ikDevice"), - deviceIkBackup.publicKeyHex(), - deviceIkBackup.publicKeyMacHex() - )); - packet.setUserKeys(List.of( - buildUserKeyPacket(USER_KEY_INDEX, USER_KEY_TYPE_SIGN, index), - buildUserKeyPacket(USER_KEY_INDEX, USER_KEY_TYPE_ENC, index) - )); - return packet; + MasterKeyBackupPacket packet = round.buildPacket(index); + round.markServed(index); + if (round.isComplete()) { + cachedBackupRound = null; + } + return packet; + } } @Override @@ -159,21 +160,29 @@ public class LmkServiceImpl implements LmkService { @Override public void recoverKeyPackets(List packets) { + // 进入恢复流程后,旧的备份轮次缓存不再可信,避免和恢复后的新密钥体系混用。 + clearCachedBackupRound(); List orderedPackets = sortPackets(packets); byte[] oldPin = decodeConfiguredPin("tms.mk.init-identify.old-pin-base64", masterKeyInitProperties.getOldPinBase64()); byte[] newPin = decodeConfiguredPin("tms.mk.init-identify.new-pin-base64", masterKeyInitProperties.getNewPinBase64()); + // 恢复顺序必须先 LMK、再 LoadLMK、再 IK、最后用户密钥;后面的密钥材料都依赖前面的密钥体系。 pcieCryptoService.initIdentify(oldPin, newPin); pcieCryptoService.recoverLmkEx(Hex.decode(composeLmkHex(orderedPackets))); pcieCryptoService.loadLmk(); pcieCryptoService.recoverIkComponent(IKEnums.KEY_TYPE_AUTH.getCode(), Hex.decode(composeIkHex(orderedPackets, true))); pcieCryptoService.recoverIkComponent(IKEnums.KEY_TYPE_DEVICE.getCode(), Hex.decode(composeIkHex(orderedPackets, false))); + //务必先查询是否是就绪状态 + DeviceStatusResult result = pcieCryptoService.getDeviceStatus();; + log.info("device status:{}", result.getFsmState()); pcieCryptoService.recoverUserKey(toRecoverUserKeyRequest(orderedPackets, USER_KEY_INDEX, USER_KEY_TYPE_SIGN)); pcieCryptoService.recoverUserKey(toRecoverUserKeyRequest(orderedPackets, USER_KEY_INDEX, USER_KEY_TYPE_ENC)); } @Override public void recoverLmkEx(String lmkMacHex, List components) { + // 兼容旧的“仅恢复 LMK”入口;一旦恢复成功,旧缓存同样需要废弃。 + clearCachedBackupRound(); byte[] oldPin = decodeConfiguredPin("tms.mk.init-identify.old-pin-base64", masterKeyInitProperties.getOldPinBase64()); byte[] newPin = decodeConfiguredPin("tms.mk.init-identify.new-pin-base64", masterKeyInitProperties.getNewPinBase64()); String fullLmkHex = masterKeyCompose(lmkMacHex, components); @@ -215,6 +224,7 @@ public class LmkServiceImpl implements LmkService { String lmkMac = requireConsistentLmkMac(packets); StringBuilder builder = new StringBuilder(lmkMac); for (MasterKeyBackupPacket packet : packets) { + // 恢复扩展结构时必须按 1/2/3 顺序拼回 3 个分量。 builder.append(packet.getLmk().getComponent()); } return builder.toString(); @@ -245,10 +255,19 @@ public class LmkServiceImpl implements LmkService { return request; } + private GenerateKeyPairRequest newUserKeyRequest(int keyType) { + GenerateKeyPairRequest request = new GenerateKeyPairRequest(); + request.setKeyBits(256); + request.setKeyIndex(USER_KEY_INDEX); + request.setKeyType(keyType); + return request; + } + private String composeUserKeyHex(List packets, int keyIndex, int keyType) { StringBuilder builder = new StringBuilder(); for (MasterKeyBackupPacket packet : packets) { MasterKeyPacketUserKeyPart part = findUserKeyPart(packet, keyIndex, keyType); + // 用户密钥没有原生三分量结构,这里是把单把备份 blob 按 1/2/3 切开后再按顺序拼回。 builder.append(part.getComponent()); } return builder.toString(); @@ -296,11 +315,15 @@ public class LmkServiceImpl implements LmkService { return new ParsedIkBackup(Hex.toHexString(mac), componentMap, Hex.toHexString(publicKey), Hex.toHexString(publicKeyMac)); } - private MasterKeyPacketUserKeyPart buildUserKeyPacket(int keyIndex, int keyType, int componentIndex) { + private ParsedUserKeyBackup parseUserKeyBackup(int keyIndex, int keyType) { BackupDataResult backupData = pcieCryptoService.backupUserKey(keyIndex, keyType, USER_KEY_BACKUP_BUFFER_SIZE); byte[] normalized = normalizeBackupData(backupData); String[] components = DivisionUtils.equalDivision(Hex.toHexString(normalized), LMKConstant.COMPONENT_NUM); - return new MasterKeyPacketUserKeyPart(keyIndex, keyType, components[componentIndex - 1]); + Map componentMap = new HashMap<>(); + for (int i = 0; i < LMKConstant.COMPONENT_NUM; i++) { + componentMap.put(LMKConstant.KEY_ORDER[i], components[i]); + } + return new ParsedUserKeyBackup(keyIndex, keyType, componentMap); } private byte[] normalizeBackupData(BackupDataResult backupData) { @@ -443,9 +466,86 @@ public class LmkServiceImpl implements LmkService { return sorted; } + private void clearCachedBackupRound() { + synchronized (backupRoundLock) { + cachedBackupRound = null; + } + } + private record ParsedMasterKeyBackup(String lmkMacHex, Map componentMap) { } private record ParsedIkBackup(String macHex, Map componentMap, String publicKeyHex, String publicKeyMacHex) { } + + private record ParsedUserKeyBackup(int keyIndex, int keyType, Map componentMap) { + } + + private final class CachedBackupRound { + + private final ParsedMasterKeyBackup lmkBackup; + private final ParsedIkBackup authIkBackup; + private final ParsedIkBackup deviceIkBackup; + private final ParsedUserKeyBackup signUserKeyBackup; + private final ParsedUserKeyBackup encUserKeyBackup; + private final Set servedIndices = new HashSet<>(); + + private CachedBackupRound( + ParsedMasterKeyBackup lmkBackup, + ParsedIkBackup authIkBackup, + ParsedIkBackup deviceIkBackup, + ParsedUserKeyBackup signUserKeyBackup, + ParsedUserKeyBackup encUserKeyBackup + ) { + this.lmkBackup = lmkBackup; + this.authIkBackup = authIkBackup; + this.deviceIkBackup = deviceIkBackup; + this.signUserKeyBackup = signUserKeyBackup; + this.encUserKeyBackup = encUserKeyBackup; + } + + private MasterKeyBackupPacket buildPacket(int componentIndex) { + MasterKeyBackupPacket packet = new MasterKeyBackupPacket(); + packet.setComponentIndex(componentIndex); + packet.setLmk(new MasterKeyPacketLmkPart( + lmkBackup.lmkMacHex(), + requiredComponent(lmkBackup.componentMap(), componentIndex, "lmk") + )); + packet.setIkAuth(new MasterKeyPacketIkPart( + IKEnums.KEY_TYPE_AUTH.getCode(), + authIkBackup.macHex(), + requiredComponent(authIkBackup.componentMap(), componentIndex, "ikAuth"), + authIkBackup.publicKeyHex(), + authIkBackup.publicKeyMacHex() + )); + packet.setIkDevice(new MasterKeyPacketIkPart( + IKEnums.KEY_TYPE_DEVICE.getCode(), + deviceIkBackup.macHex(), + requiredComponent(deviceIkBackup.componentMap(), componentIndex, "ikDevice"), + deviceIkBackup.publicKeyHex(), + deviceIkBackup.publicKeyMacHex() + )); + packet.setUserKeys(List.of( + new MasterKeyPacketUserKeyPart( + signUserKeyBackup.keyIndex(), + signUserKeyBackup.keyType(), + requiredComponent(signUserKeyBackup.componentMap(), componentIndex, "userSign") + ), + new MasterKeyPacketUserKeyPart( + encUserKeyBackup.keyIndex(), + encUserKeyBackup.keyType(), + requiredComponent(encUserKeyBackup.componentMap(), componentIndex, "userEnc") + ) + )); + return packet; + } + + private void markServed(int componentIndex) { + servedIndices.add(componentIndex); + } + + private boolean isComplete() { + return servedIndices.size() == LMKConstant.COMPONENT_NUM; + } + } } 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 1402ded..0595620 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 @@ -3,10 +3,14 @@ package com.cisd.tms.modules.device.controller; import com.cisd.tms.common.exception.GlobalExceptionHandler; 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.GenerateKeyPairRequest; +import com.cisd.tms.integration.crypto.pcie.model.KeyStatusResult; import com.cisd.tms.integration.crypto.pcie.model.LoginStatusResult; +import com.cisd.tms.integration.crypto.pcie.model.RecoverUserKeyRequest; import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; import org.mockito.ArgumentCaptor; import org.junit.jupiter.api.Test; +import org.mockito.InOrder; import org.mockito.Mockito; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -207,4 +211,65 @@ class CryptoCardControllerTest { .andExpect(content().string(containsString("\"admin2\":0"))) .andExpect(content().string(containsString("\"admin3\":1"))); } + + @Test + void shouldRunFullUserKeySelfTestFlow() throws Exception { + PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); + BackupDataResult backup = new BackupDataResult(); + backup.setLength(4); + backup.setData(new byte[] {0x01, 0x23, 0x45, 0x67}); + KeyStatusResult deleted = new KeyStatusResult(); + deleted.setKeyState(0); + deleted.setKeyStateName("FREE"); + KeyStatusResult recovered = new KeyStatusResult(); + recovered.setKeyState(2); + recovered.setKeyStateName("RIGHT"); + Mockito.when(pcieCryptoService.backupUserKey(1, 0, 120)).thenReturn(backup); + Mockito.when(pcieCryptoService.statusUserKeyEcc(1, 0)).thenReturn(deleted, recovered); + + MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new CryptoCardController(pcieCryptoService)) + .setControllerAdvice(new GlobalExceptionHandler()) + .build(); + + mockMvc.perform(post("/api/v1/device/crypto/user-key/self-test") + .contentType("application/json") + .content(""" + { + "keyIndex": 1, + "keyType": 0, + "outBufferSize": 120, + "storeFlag": 1 + } + """)) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("\"keyIndex\":1"))) + .andExpect(content().string(containsString("\"keyType\":0"))) + .andExpect(content().string(containsString("\"backupLength\":4"))) + .andExpect(content().string(containsString("\"backupHex\":\"01234567\""))) + .andExpect(content().string(containsString("\"deleteStatus\":{\"keyState\":0,\"keyStateName\":\"FREE\"}"))) + .andExpect(content().string(containsString("\"recoverStatus\":{\"keyState\":2,\"keyStateName\":\"RIGHT\"}"))); + + ArgumentCaptor generateCaptor = ArgumentCaptor.forClass(GenerateKeyPairRequest.class); + ArgumentCaptor recoverCaptor = ArgumentCaptor.forClass(RecoverUserKeyRequest.class); + InOrder inOrder = Mockito.inOrder(pcieCryptoService); + inOrder.verify(pcieCryptoService).generateKeyPairEcc(generateCaptor.capture()); + inOrder.verify(pcieCryptoService).backupUserKey(1, 0, 120); + inOrder.verify(pcieCryptoService).deleteUserKey(1); + inOrder.verify(pcieCryptoService).statusUserKeyEcc(1, 0); + inOrder.verify(pcieCryptoService).generateKeyPairEcc(generateCaptor.capture()); + inOrder.verify(pcieCryptoService).recoverUserKey(recoverCaptor.capture()); + inOrder.verify(pcieCryptoService).statusUserKeyEcc(1, 0); + + assertEquals(2, generateCaptor.getAllValues().size()); + assertEquals(256, generateCaptor.getAllValues().get(0).getKeyBits()); + assertEquals(1, generateCaptor.getAllValues().get(0).getKeyIndex()); + 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(0, recoverCaptor.getValue().getKeyType()); + assertEquals(1, recoverCaptor.getValue().getStoreFlag()); + assertArrayEquals(new byte[] {0x01, 0x23, 0x45, 0x67}, recoverCaptor.getValue().getEncryptedKey()); + } } diff --git a/src/test/java/com/cisd/tms/modules/mk/service/LmkServiceTest.java b/src/test/java/com/cisd/tms/modules/mk/service/LmkServiceTest.java index 87e7694..f6acc06 100644 --- a/src/test/java/com/cisd/tms/modules/mk/service/LmkServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/mk/service/LmkServiceTest.java @@ -1,12 +1,14 @@ package com.cisd.tms.modules.mk.service; import com.cisd.tms.integration.crypto.pcie.Gm0018AlgorithmIds; +import com.cisd.tms.integration.crypto.pcie.PcieUserKeyType; import com.cisd.tms.integration.crypto.pcie.jna.EccRefPublicKey; import com.cisd.tms.integration.crypto.pcie.jna.SdfeIkComponent; import com.cisd.tms.integration.crypto.pcie.model.BackupDataResult; import com.cisd.tms.integration.crypto.pcie.model.DigestRequest; import com.cisd.tms.integration.crypto.pcie.model.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.IkSignRequest; import com.cisd.tms.integration.crypto.pcie.model.IkVerifyRequest; import com.cisd.tms.integration.crypto.pcie.model.RecoverUserKeyRequest; @@ -61,6 +63,21 @@ class LmkServiceTest { Assertions.assertEquals("master key initialization failed", exception.getMessage()); } + @Test + void shouldDestroyGeneratedUserKeysAndIksBeforeDestroyingLmk() { + PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); + MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE="); + LmkService service = new LmkServiceImpl(pcieCryptoService, properties); + + service.destroyMasterKey(); + + InOrder inOrder = Mockito.inOrder(pcieCryptoService); + inOrder.verify(pcieCryptoService).deleteUserKey(1); + inOrder.verify(pcieCryptoService).destroyIk(2); + inOrder.verify(pcieCryptoService).destroyIk(1); + inOrder.verify(pcieCryptoService).destroyLmk(); + } + @Test void shouldReturnStatusFalseWhenMasterKeyDoesNotExist() { PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); @@ -177,6 +194,82 @@ class LmkServiceTest { Assertions.assertEquals("75767778", packet.getUserKeys().get(1).getComponent()); } + @Test + void shouldReuseSingleBackupRoundAcrossThreePacketBuilds() { + PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); + MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE="); + + BackupDataResult lmkBackupRound1 = new BackupDataResult(); + lmkBackupRound1.setLength(20); + lmkBackupRound1.setData(new byte[] { + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x0C + }); + BackupDataResult lmkBackupRound2 = new BackupDataResult(); + lmkBackupRound2.setLength(20); + lmkBackupRound2.setData(new byte[] { + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x31, 0x32, 0x33, 0x34, + 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3A, 0x3B, 0x3C + }); + + BackupDataResult ikAuthRound1 = new BackupDataResult(); + ikAuthRound1.setLength(new SdfeIkComponent().size()); + ikAuthRound1.setData(sequentialBytes(new SdfeIkComponent().size(), 0x21)); + BackupDataResult ikAuthRound2 = new BackupDataResult(); + ikAuthRound2.setLength(new SdfeIkComponent().size()); + ikAuthRound2.setData(sequentialBytes(new SdfeIkComponent().size(), 0x51)); + + BackupDataResult ikDeviceRound1 = new BackupDataResult(); + ikDeviceRound1.setLength(new SdfeIkComponent().size()); + ikDeviceRound1.setData(sequentialBytes(new SdfeIkComponent().size(), 0x41)); + BackupDataResult ikDeviceRound2 = new BackupDataResult(); + ikDeviceRound2.setLength(new SdfeIkComponent().size()); + ikDeviceRound2.setData(sequentialBytes(new SdfeIkComponent().size(), 0x71)); + + BackupDataResult userSignRound1 = new BackupDataResult(); + userSignRound1.setLength(12); + userSignRound1.setData(new byte[] {0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C}); + BackupDataResult userSignRound2 = new BackupDataResult(); + userSignRound2.setLength(12); + userSignRound2.setData(new byte[] {0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C}); + + BackupDataResult userEncRound1 = new BackupDataResult(); + userEncRound1.setLength(12); + userEncRound1.setData(new byte[] {0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x31, 0x32}); + BackupDataResult userEncRound2 = new BackupDataResult(); + userEncRound2.setLength(12); + userEncRound2.setData(new byte[] {0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x33, 0x34}); + + Mockito.when(pcieCryptoService.backupLmkEx()).thenReturn(lmkBackupRound1, lmkBackupRound2); + Mockito.when(pcieCryptoService.backupIkComponent(2)).thenReturn(ikAuthRound1, ikAuthRound2); + Mockito.when(pcieCryptoService.backupIkComponent(1)).thenReturn(ikDeviceRound1, ikDeviceRound2); + Mockito.when(pcieCryptoService.backupUserKey(1, 0, 120)).thenReturn(userSignRound1, userSignRound2); + Mockito.when(pcieCryptoService.backupUserKey(1, 1, 120)).thenReturn(userEncRound1, userEncRound2); + + LmkService service = new LmkServiceImpl(pcieCryptoService, properties); + + MasterKeyBackupPacket packet1 = service.buildBackupPacket(1); + MasterKeyBackupPacket packet2 = service.buildBackupPacket(2); + MasterKeyBackupPacket packet3 = service.buildBackupPacket(3); + + Assertions.assertEquals("01020304", packet1.getLmk().getComponent()); + Assertions.assertEquals("05060708", packet2.getLmk().getComponent()); + Assertions.assertEquals("090a0b0c", packet3.getLmk().getComponent()); + Assertions.assertEquals("61626364", packet1.getUserKeys().get(0).getComponent()); + Assertions.assertEquals("65666768", packet2.getUserKeys().get(0).getComponent()); + Assertions.assertEquals("696a6b6c", packet3.getUserKeys().get(0).getComponent()); + + Mockito.verify(pcieCryptoService).backupLmkEx(); + Mockito.verify(pcieCryptoService).backupIkComponent(2); + Mockito.verify(pcieCryptoService).backupIkComponent(1); + Mockito.verify(pcieCryptoService).backupUserKey(1, 0, 120); + Mockito.verify(pcieCryptoService).backupUserKey(1, 1, 120); + } + @Test void shouldRecoverAllKeyPacketsInOrder() { PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); @@ -220,14 +313,20 @@ class LmkServiceTest { ArgumentCaptor lmkCaptor = ArgumentCaptor.forClass(byte[].class); ArgumentCaptor ikAuthCaptor = ArgumentCaptor.forClass(byte[].class); ArgumentCaptor ikDeviceCaptor = ArgumentCaptor.forClass(byte[].class); - ArgumentCaptor userKeyCaptor = ArgumentCaptor.forClass(RecoverUserKeyRequest.class); + ArgumentCaptor generateSignCaptor = ArgumentCaptor.forClass(GenerateKeyPairRequest.class); + ArgumentCaptor recoverSignCaptor = ArgumentCaptor.forClass(RecoverUserKeyRequest.class); + ArgumentCaptor generateEncCaptor = ArgumentCaptor.forClass(GenerateKeyPairRequest.class); + ArgumentCaptor recoverEncCaptor = ArgumentCaptor.forClass(RecoverUserKeyRequest.class); InOrder inOrder = Mockito.inOrder(pcieCryptoService); inOrder.verify(pcieCryptoService).initIdentify(oldPinCaptor.capture(), newPinCaptor.capture()); inOrder.verify(pcieCryptoService).recoverLmkEx(lmkCaptor.capture()); inOrder.verify(pcieCryptoService).loadLmk(); inOrder.verify(pcieCryptoService).recoverIkComponent(Mockito.eq(2), ikAuthCaptor.capture()); inOrder.verify(pcieCryptoService).recoverIkComponent(Mockito.eq(1), ikDeviceCaptor.capture()); - inOrder.verify(pcieCryptoService, Mockito.times(2)).recoverUserKey(userKeyCaptor.capture()); + inOrder.verify(pcieCryptoService).generateKeyPairEcc(generateSignCaptor.capture()); + inOrder.verify(pcieCryptoService).recoverUserKey(recoverSignCaptor.capture()); + inOrder.verify(pcieCryptoService).generateKeyPairEcc(generateEncCaptor.capture()); + inOrder.verify(pcieCryptoService).recoverUserKey(recoverEncCaptor.capture()); Assertions.assertArrayEquals("12345678".getBytes(StandardCharsets.UTF_8), oldPinCaptor.getValue()); Assertions.assertArrayEquals("87654321".getBytes(StandardCharsets.UTF_8), newPinCaptor.getValue()); Assertions.assertArrayEquals(new byte[] { @@ -236,9 +335,14 @@ class LmkServiceTest { }, lmkCaptor.getValue()); Assertions.assertArrayEquals(hex("1112131415162122232425262728313233344142434445464748"), ikAuthCaptor.getValue()); Assertions.assertArrayEquals(hex("2122232425263132333435363738515253546162636465666768"), ikDeviceCaptor.getValue()); - Assertions.assertEquals(2, userKeyCaptor.getAllValues().size()); - Assertions.assertArrayEquals(hex("010203040506"), userKeyCaptor.getAllValues().get(0).getEncryptedKey()); - Assertions.assertArrayEquals(hex("111213141516"), userKeyCaptor.getAllValues().get(1).getEncryptedKey()); + Assertions.assertEquals(256, generateSignCaptor.getValue().getKeyBits()); + Assertions.assertEquals(1, generateSignCaptor.getValue().getKeyIndex()); + Assertions.assertEquals(PcieUserKeyType.SIGN.getCode(), generateSignCaptor.getValue().getKeyType()); + Assertions.assertEquals(256, generateEncCaptor.getValue().getKeyBits()); + Assertions.assertEquals(1, generateEncCaptor.getValue().getKeyIndex()); + Assertions.assertEquals(PcieUserKeyType.ENC.getCode(), generateEncCaptor.getValue().getKeyType()); + Assertions.assertArrayEquals(hex("010203040506"), recoverSignCaptor.getValue().getEncryptedKey()); + Assertions.assertArrayEquals(hex("111213141516"), recoverEncCaptor.getValue().getEncryptedKey()); } @Test