diff --git a/src/main/java/com/cisd/tms/modules/auth/enums/RoleCode.java b/src/main/java/com/cisd/tms/modules/auth/enums/RoleCode.java index 6b753cd..c83cd80 100644 --- a/src/main/java/com/cisd/tms/modules/auth/enums/RoleCode.java +++ b/src/main/java/com/cisd/tms/modules/auth/enums/RoleCode.java @@ -1,8 +1,8 @@ package com.cisd.tms.modules.auth.enums; public enum RoleCode { - SUPER_ADMIN("SUPER_ADMIN", "超级管理员", 3), - KEY_ADMIN("KEY_ADMIN", "密钥管理员", 2), + SUPER_ADMIN("SUPER_ADMIN", "超级管理员", 2), + KEY_ADMIN("KEY_ADMIN", "密钥管理员", 1), AUDIT_ADMIN("AUDIT_ADMIN", "审计管理员", 1), OPS_ADMIN("OPS_ADMIN", "运维管理员", 1); 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 37958e3..476f96a 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,7 +140,6 @@ 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/mk/common/LMKConstant.java b/src/main/java/com/cisd/tms/modules/mk/common/LMKConstant.java index de4b47b..68eb946 100644 --- a/src/main/java/com/cisd/tms/modules/mk/common/LMKConstant.java +++ b/src/main/java/com/cisd/tms/modules/mk/common/LMKConstant.java @@ -4,10 +4,10 @@ package com.cisd.tms.modules.mk.common; * 全局常量 */ public interface LMKConstant { - int COMPONENT_NUM = 3; + int COMPONENT_NUM = 2; int COMPONENT_LENGTH = 64; - int[] KEY_ORDER = {1,2,3}; - int[][] COMB_LIST = new int[][]{{1,2},{1,3},{2,3}}; + int[] KEY_ORDER = {1,2}; + int[][] COMB_LIST = new int[][]{{1},{2}}; int LMK_ZONE_NUM = 1; int LMK_NUM = 50; } diff --git a/src/main/java/com/cisd/tms/modules/mk/controller/LmkController.java b/src/main/java/com/cisd/tms/modules/mk/controller/LmkController.java index 7a8ffd4..2d38822 100644 --- a/src/main/java/com/cisd/tms/modules/mk/controller/LmkController.java +++ b/src/main/java/com/cisd/tms/modules/mk/controller/LmkController.java @@ -1,6 +1,9 @@ package com.cisd.tms.modules.mk.controller; import com.cisd.tms.common.api.ApiResponse; +import com.cisd.tms.common.enums.ErrorCode; +import com.cisd.tms.common.exception.BizException; +import com.cisd.tms.modules.mk.dto.MasterKeyBackupPacket; import com.cisd.tms.modules.mk.dto.MasterKeyRecoverRequest; import com.cisd.tms.modules.mk.dto.MasterKeyRecoverPacketsRequest; import com.cisd.tms.modules.mk.dto.MasterKeyStateResult; @@ -14,6 +17,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; +import java.util.List; + /** * @author liulu * @since 2026/3/4 @@ -51,13 +56,14 @@ public class LmkController { /** * 恢复所有备份密钥。 - * 请求体需传入 componentIndex=1/2/3 的三份分量包。 + * 请求体需传入 packetIndex=1/2 的两份备份包。 * 服务端会先执行 InitIdentify,然后依次恢复 LMK、加载 LMK、恢复 IK、恢复用户密钥。 */ @PostMapping("/masterKey/recover") - @Operation(summary = "恢复全部密钥", description = "请求体需传入三份按 componentIndex 标识的分量包。三份分量包必须分别对应 componentIndex=1、2、3。服务端会依次恢复 LMK、IK 和用户密钥。") + @Operation(summary = "恢复全部密钥", description = "请求体需传入两份按 packetIndex 标识的备份包。两份备份包必须分别对应 packetIndex=1、2。服务端会依次恢复 LMK、IK 和用户密钥。") @ReplayProtected public ApiResponse recoverLmkEx(@Valid @RequestBody MasterKeyRecoverPacketsRequest request) { + validatePacketIndexes(request.getPackets()); lmkService.recoverKeyPackets(request.getPackets()); return ApiResponse.success(); } @@ -70,4 +76,15 @@ public class LmkController { lmkService.verifyIk(data, signData); return ApiResponse.success(); } + + private void validatePacketIndexes(List packets) { + if (packets == null || packets.size() != 2) { + throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "packets size must be 2"); + } + boolean hasPacketOne = packets.stream().anyMatch(packet -> packet != null && packet.getPacketIndex() == 1); + boolean hasPacketTwo = packets.stream().anyMatch(packet -> packet != null && packet.getPacketIndex() == 2); + if (!hasPacketOne || !hasPacketTwo) { + throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "packetIndex must be 1 or 2"); + } + } } diff --git a/src/main/java/com/cisd/tms/modules/mk/dto/LMKAndIK.java b/src/main/java/com/cisd/tms/modules/mk/dto/LMKAndIK.java index 1719361..6a8c4cd 100644 --- a/src/main/java/com/cisd/tms/modules/mk/dto/LMKAndIK.java +++ b/src/main/java/com/cisd/tms/modules/mk/dto/LMKAndIK.java @@ -38,7 +38,9 @@ public class LMKAndIK { deviceKeyComponents.put(i,deviceKeyCompParts.get(i)); } } - if(lmkComponents.size() < 3 || authKeyComponents.size() < 3 || deviceKeyComponents.size() < 3){ + if(lmkComponents.size() < LMKConstant.COMPONENT_NUM + || authKeyComponents.size() < LMKConstant.COMPONENT_NUM + || deviceKeyComponents.size() < LMKConstant.COMPONENT_NUM){ throw new IllegalArgumentException("主密钥或内部密钥分量缺失"); } StringBuilder lmk = new StringBuilder(); diff --git a/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyBackupPacket.java b/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyBackupPacket.java index 12e2bd5..5f2ca7b 100644 --- a/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyBackupPacket.java +++ b/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyBackupPacket.java @@ -1,22 +1,49 @@ package com.cisd.tms.modules.mk.dto; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.Valid; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; import java.util.ArrayList; import java.util.List; public class MasterKeyBackupPacket { - private int componentIndex; + @Min(value = 1, message = "packetIndex must be 1 or 2") + @Max(value = 2, message = "packetIndex must be 1 or 2") + @JsonProperty("packetIndex") + private int packetIndex; + private MasterKeyPacketLmkPart lmk; private MasterKeyPacketIkPart ikAuth; private MasterKeyPacketIkPart ikDevice; + @Valid private List userKeys = new ArrayList<>(); - public int getComponentIndex() { - return componentIndex; + public int getPacketIndex() { + return packetIndex; } + public void setPacketIndex(int packetIndex) { + this.packetIndex = packetIndex; + } + + @JsonIgnore + public int getComponentIndex() { + return packetIndex; + } + + @JsonIgnore public void setComponentIndex(int componentIndex) { - this.componentIndex = componentIndex; + this.packetIndex = componentIndex; + } + + @JsonIgnore + @AssertTrue(message = "packetIndex must be 1 or 2") + public boolean isPacketIndexValid() { + return packetIndex == 1 || packetIndex == 2; } public MasterKeyPacketLmkPart getLmk() { diff --git a/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyPacketIkPart.java b/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyPacketIkPart.java index 512454b..d862648 100644 --- a/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyPacketIkPart.java +++ b/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyPacketIkPart.java @@ -1,12 +1,16 @@ package com.cisd.tms.modules.mk.dto; +import com.fasterxml.jackson.annotation.JsonIgnore; +import java.util.LinkedHashMap; +import java.util.Map; + public class MasterKeyPacketIkPart { private int keyType; private String mac; - private String component; private String publicKey; private String publicKeyMac; + private Map components = new LinkedHashMap<>(); public MasterKeyPacketIkPart() { } @@ -18,9 +22,17 @@ public class MasterKeyPacketIkPart { public MasterKeyPacketIkPart(int keyType, String mac, String component, String publicKey, String publicKeyMac) { this.keyType = keyType; this.mac = mac; - this.component = component; this.publicKey = publicKey; this.publicKeyMac = publicKeyMac; + setComponent(component); + } + + public MasterKeyPacketIkPart(int keyType, String mac, Map components, String publicKey, String publicKeyMac) { + this.keyType = keyType; + this.mac = mac; + this.publicKey = publicKey; + this.publicKeyMac = publicKeyMac; + setComponents(components); } public int getKeyType() { @@ -39,14 +51,6 @@ public class MasterKeyPacketIkPart { this.mac = mac; } - public String getComponent() { - return component; - } - - public void setComponent(String component) { - this.component = component; - } - public String getPublicKey() { return publicKey; } @@ -62,4 +66,29 @@ public class MasterKeyPacketIkPart { public void setPublicKeyMac(String publicKeyMac) { this.publicKeyMac = publicKeyMac; } + + public Map getComponents() { + return components; + } + + public void setComponents(Map components) { + this.components = components == null ? new LinkedHashMap<>() : new LinkedHashMap<>(components); + } + + @JsonIgnore + public String getComponent() { + return components.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .map(Map.Entry::getValue) + .reduce("", String::concat); + } + + @JsonIgnore + public void setComponent(String component) { + LinkedHashMap singleComponent = new LinkedHashMap<>(); + if (component != null) { + singleComponent.put(1, component); + } + this.components = singleComponent; + } } diff --git a/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyPacketLmkPart.java b/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyPacketLmkPart.java index a9c8ad0..2a555a9 100644 --- a/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyPacketLmkPart.java +++ b/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyPacketLmkPart.java @@ -1,16 +1,25 @@ package com.cisd.tms.modules.mk.dto; +import com.fasterxml.jackson.annotation.JsonIgnore; +import java.util.LinkedHashMap; +import java.util.Map; + public class MasterKeyPacketLmkPart { private String lmkMac; - private String component; + private Map components = new LinkedHashMap<>(); public MasterKeyPacketLmkPart() { } public MasterKeyPacketLmkPart(String lmkMac, String component) { this.lmkMac = lmkMac; - this.component = component; + setComponent(component); + } + + public MasterKeyPacketLmkPart(String lmkMac, Map components) { + this.lmkMac = lmkMac; + setComponents(components); } public String getLmkMac() { @@ -21,11 +30,28 @@ public class MasterKeyPacketLmkPart { this.lmkMac = lmkMac; } - public String getComponent() { - return component; + public Map getComponents() { + return components; } + public void setComponents(Map components) { + this.components = components == null ? new LinkedHashMap<>() : new LinkedHashMap<>(components); + } + + @JsonIgnore + public String getComponent() { + return components.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .map(Map.Entry::getValue) + .reduce("", String::concat); + } + + @JsonIgnore public void setComponent(String component) { - this.component = component; + LinkedHashMap singleComponent = new LinkedHashMap<>(); + if (component != null) { + singleComponent.put(1, component); + } + this.components = singleComponent; } } diff --git a/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyRecoverPacketsRequest.java b/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyRecoverPacketsRequest.java index 649a24e..53088da 100644 --- a/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyRecoverPacketsRequest.java +++ b/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyRecoverPacketsRequest.java @@ -9,7 +9,7 @@ import java.util.List; public class MasterKeyRecoverPacketsRequest { @NotEmpty(message = "packets must not be empty") - @Size(min = 3, max = 3, message = "packets size must be 3") + @Size(min = 2, max = 2, message = "packets size must be 2") private List<@Valid MasterKeyBackupPacket> packets; public List getPackets() { diff --git a/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyRecoverRequest.java b/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyRecoverRequest.java index 1fe5e9c..bf5c599 100644 --- a/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyRecoverRequest.java +++ b/src/main/java/com/cisd/tms/modules/mk/dto/MasterKeyRecoverRequest.java @@ -12,7 +12,7 @@ public class MasterKeyRecoverRequest { private String lmkMac; @NotEmpty(message = "components must not be empty") - @Size(min = 3, max = 3, message = "components size must be 3") + @Size(min = 2, max = 2, message = "components size must be 2") private List<@NotBlank(message = "component must not be blank") String> components; public String getLmkMac() { 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 50cb23f..c565e9d 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 @@ -22,8 +22,10 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; +import java.util.Comparator; import java.util.HashSet; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -40,6 +42,14 @@ import org.springframework.stereotype.Service; @RequiredArgsConstructor public class LmkServiceImpl implements LmkService { + // 对外接口只暴露两份备份包:packet 1 = {1,2},packet 2 = {2,3}。 + private static final int PACKET_INDEX_ONE = 1; + private static final int PACKET_INDEX_TWO = 2; + // 厂商原生的 LMK/IK 备份结构仍然固定为 3 个分量,服务内部始终按原生语义处理。 + private static final int NATIVE_COMPONENT_ONE = 1; + private static final int NATIVE_COMPONENT_TWO = 2; + private static final int NATIVE_COMPONENT_THREE = 3; + private static final int NATIVE_COMPONENT_COUNT = 3; private static final int AUTH_SIGN_KEY_INDEX = IKEnums.KEY_TYPE_AUTH.getCode(); private static final int LMK_MAC_BYTE_LENGTH = 8; private static final int IK_COMPONENT_MAC_BYTE_LENGTH = 8; @@ -60,7 +70,7 @@ public class LmkServiceImpl implements LmkService { @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()); @@ -75,7 +85,6 @@ public class LmkServiceImpl implements LmkService { public void destroyMasterKey() { // 销毁后也不能再继续复用上一轮备份材料。 clearCachedBackupRound(); - pcieCryptoService.destroyLmk(); pcieCryptoService.destroyIk(IKEnums.KEY_TYPE_AUTH.getCode()); pcieCryptoService.destroyIk(IKEnums.KEY_TYPE_DEVICE.getCode()); @@ -121,11 +130,13 @@ public class LmkServiceImpl implements LmkService { @Override public MasterKeyBackupPacket buildBackupPacket(int componentIndex) { - int index = requireComponentIndex(componentIndex); + int index = requirePacketIndex(componentIndex); synchronized (backupRoundLock) { CachedBackupRound round = cachedBackupRound; if (round == null) { - // 三次发给外部 UKey 的分量必须来自同一轮导出,否则恢复 LMK 时会出现 MACERR。 + // 两次发给外部 UKey 的分量必须来自同一轮导出,否则恢复 LMK 时会出现 MACERR。 + // 这里先把设备返回的原生 LMK/IK/UserKey 备份材料全部取回并缓存在内存中, + // 随后再按 packetIndex=1/2 投影成两份外部包,确保两份包属于同一轮快照。 round = new CachedBackupRound( parseBackupMaterial(), parseIkBackupMaterial(IKEnums.KEY_TYPE_AUTH.getCode()), @@ -148,7 +159,7 @@ public class LmkServiceImpl implements LmkService { @Override public MasterKeyBackupMaterial getBackupMaterial(int uid) { MasterKeyBackupPacket packet = buildBackupPacket(uid); - return new MasterKeyBackupMaterial(packet.getLmk().getComponent(), packet.getLmk().getLmkMac()); + return new MasterKeyBackupMaterial(joinOrderedComponents(packet.getLmk().getComponents()), packet.getLmk().getLmkMac()); } @Override @@ -160,18 +171,23 @@ public class LmkServiceImpl implements LmkService { public void recoverKeyPackets(List packets) { // 进入恢复流程后,旧的备份轮次缓存不再可信,避免和恢复后的新密钥体系混用。 clearCachedBackupRound(); + // 调用方可以任意顺序传入两份包,这里统一按 packetIndex 排序并校验必须是 1、2 两包。 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、最后用户密钥;后面的密钥材料都依赖前面的密钥体系。 + // 恢复顺序不能打乱: + // 1. InitIdentify 建立恢复所需的设备认证上下文 + // 2. 先恢复 LMK 并 LoadLMK,后续 IK 和用户密钥都依赖 LMK 环境 + // 3. 再恢复两把 IK + // 4. 最后恢复用户密钥 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();; + 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)); @@ -219,53 +235,67 @@ public class LmkServiceImpl implements LmkService { } private String composeLmkHex(List packets) { + // 外部只传两份包,但底层 SDFE_RecoverLMK_EX 需要的是完整原生三分量结构: + // packet1 提供 {1,2},packet2 提供 {2,3},因此恢复时要重组为 1 + 2 + 3。 + // 其中 component2 是两包的重叠分量,必须严格一致,否则说明两包不是同一轮导出结果。 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(); + MasterKeyBackupPacket packet1 = packets.get(0); + MasterKeyBackupPacket packet2 = packets.get(1); + String component1 = requiredPacketComponent(packet1.getLmk().getComponents(), NATIVE_COMPONENT_ONE, "lmk", packet1.getPacketIndex()); + String component2 = requireConsistentOverlapComponent( + packet1.getLmk().getComponents(), + packet2.getLmk().getComponents(), + "lmk" + ); + String component3 = requiredPacketComponent(packet2.getLmk().getComponents(), NATIVE_COMPONENT_THREE, "lmk", packet2.getPacketIndex()); + return lmkMac + component1 + component2 + component3; } private String composeIkHex(List packets, boolean authKey) { + // IK 的恢复规则与 LMK 相同: + // 外部两包只是对原生三分量的投影,真正下发给密码卡时必须重新拼回 + // component1 + component2 + component3 + mac + publicKey + publicKeyMac 的完整结构。 int keyType = authKey ? IKEnums.KEY_TYPE_AUTH.getCode() : IKEnums.KEY_TYPE_DEVICE.getCode(); String mac = requireConsistentIkMac(packets, keyType); String publicKey = requireConsistentIkPublicKey(packets, keyType); String publicKeyMac = requireConsistentIkPublicKeyMac(packets, keyType); - StringBuilder builder = new StringBuilder(); - for (MasterKeyBackupPacket packet : packets) { - MasterKeyPacketIkPart part = authKey ? packet.getIkAuth() : packet.getIkDevice(); - if (part == null || part.getComponent() == null || part.getComponent().isBlank()) { - throw new IllegalArgumentException((authKey ? "ikAuth" : "ikDevice") + " component missing in packet " + packet.getComponentIndex()); - } - builder.append(part.getComponent()); - } - builder.append(mac).append(publicKey).append(publicKeyMac); - return builder.toString(); + MasterKeyPacketIkPart packet1Part = authKey ? packets.get(0).getIkAuth() : packets.get(0).getIkDevice(); + MasterKeyPacketIkPart packet2Part = authKey ? packets.get(1).getIkAuth() : packets.get(1).getIkDevice(); + String component1 = requiredPacketComponent( + packet1Part == null ? null : packet1Part.getComponents(), + NATIVE_COMPONENT_ONE, + authKey ? "ikAuth" : "ikDevice", + packets.get(0).getPacketIndex() + ); + String component2 = requireConsistentOverlapComponent( + packet1Part == null ? null : packet1Part.getComponents(), + packet2Part == null ? null : packet2Part.getComponents(), + authKey ? "ikAuth" : "ikDevice" + ); + String component3 = requiredPacketComponent( + packet2Part == null ? null : packet2Part.getComponents(), + NATIVE_COMPONENT_THREE, + authKey ? "ikAuth" : "ikDevice", + packets.get(1).getPacketIndex() + ); + return component1 + component2 + component3 + mac + publicKey + publicKeyMac; } private RecoverUserKeyRequest toRecoverUserKeyRequest(List packets, int keyIndex, int keyType) { RecoverUserKeyRequest request = new RecoverUserKeyRequest(); request.setKeyIndex(keyIndex); request.setKeyType(keyType); + // 用户密钥不走厂商原生“三分量”模型,而是应用层把整段 blob 平分成两份包。 request.setEncryptedKey(Hex.decode(composeUserKeyHex(packets, keyIndex, keyType))); 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 切开后再按顺序拼回。 + // 用户密钥没有原生两分量结构,这里只是把同一把用户密钥的备份 blob + // 按 packet 1、packet 2 的顺序重新拼回一整段加密数据。 builder.append(part.getComponent()); } return builder.toString(); @@ -277,14 +307,11 @@ public class LmkServiceImpl implements LmkService { if (normalized.length < LMK_MAC_BYTE_LENGTH) { throw new IllegalStateException("backup lmk data length is too short: " + normalized.length); } + // 厂商返回格式:LMK_MAC + component1 + component2 + component3。 + // 这里先按原生结构完整解析,后续 buildPacket 时再投影成两份外部包。 byte[] lmkMac = Arrays.copyOfRange(normalized, 0, LMK_MAC_BYTE_LENGTH); byte[] componentBytes = Arrays.copyOfRange(normalized, LMK_MAC_BYTE_LENGTH, normalized.length); - String[] splitComponents = DivisionUtils.equalDivision(Hex.toHexString(componentBytes), LMKConstant.COMPONENT_NUM); - Map componentMap = new HashMap<>(); - for (int i = 0; i < LMKConstant.COMPONENT_NUM; i++) { - componentMap.put(LMKConstant.KEY_ORDER[i], splitComponents[i]); - } - return new ParsedMasterKeyBackup(Hex.toHexString(lmkMac), componentMap); + return new ParsedMasterKeyBackup(Hex.toHexString(lmkMac), parseNativeComponentMap(componentBytes, "lmk")); } private ParsedIkBackup parseIkBackupMaterial(int keyType) { @@ -293,6 +320,8 @@ public class LmkServiceImpl implements LmkService { if (normalized.length < IK_COMPONENT_TOTAL_BYTE_LENGTH) { throw new IllegalStateException("backup ik data length is too short: " + normalized.length); } + // 厂商返回格式:component1 + component2 + component3 + mac + publicKey + publicKeyMac。 + // 与 LMK 一样,先解析成原生三分量,再由外层决定如何投影到两份包中。 byte[] componentBytes = Arrays.copyOfRange(normalized, 0, IK_COMPONENT_DATA_LENGTH); byte[] mac = Arrays.copyOfRange(normalized, IK_COMPONENT_DATA_LENGTH, IK_COMPONENT_DATA_LENGTH + IK_COMPONENT_MAC_BYTE_LENGTH); byte[] publicKey = Arrays.copyOfRange( @@ -305,21 +334,23 @@ public class LmkServiceImpl implements LmkService { IK_COMPONENT_DATA_LENGTH + IK_COMPONENT_MAC_BYTE_LENGTH + IK_PUBLIC_KEY_BYTE_LENGTH, IK_COMPONENT_DATA_LENGTH + IK_COMPONENT_MAC_BYTE_LENGTH + IK_PUBLIC_KEY_BYTE_LENGTH + IK_PUBLIC_KEY_MAC_BYTE_LENGTH ); - String[] splitComponents = DivisionUtils.equalDivision(Hex.toHexString(componentBytes), LMKConstant.COMPONENT_NUM); - Map componentMap = new HashMap<>(); - for (int i = 0; i < LMKConstant.COMPONENT_NUM; i++) { - componentMap.put(LMKConstant.KEY_ORDER[i], splitComponents[i]); - } - return new ParsedIkBackup(Hex.toHexString(mac), componentMap, Hex.toHexString(publicKey), Hex.toHexString(publicKeyMac)); + return new ParsedIkBackup( + Hex.toHexString(mac), + parseNativeComponentMap(componentBytes, "ik"), + Hex.toHexString(publicKey), + Hex.toHexString(publicKeyMac) + ); } private ParsedUserKeyBackup parseUserKeyBackup(int keyIndex, int keyType) { BackupDataResult backupData = pcieCryptoService.backupUserKey(keyIndex, keyType, USER_KEY_BACKUP_BUFFER_SIZE); byte[] normalized = normalizeBackupData(backupData); + // 用户密钥没有厂商原生三分量结构,这里沿用应用层约定, + // 直接把整段 HEX 平分成两份,分别放入 packet 1、packet 2。 String[] components = DivisionUtils.equalDivision(Hex.toHexString(normalized), LMKConstant.COMPONENT_NUM); - Map componentMap = new HashMap<>(); + Map componentMap = new LinkedHashMap<>(); for (int i = 0; i < LMKConstant.COMPONENT_NUM; i++) { - componentMap.put(LMKConstant.KEY_ORDER[i], components[i]); + componentMap.put(i + 1, components[i]); } return new ParsedUserKeyBackup(keyIndex, keyType, componentMap); } @@ -353,10 +384,25 @@ public class LmkServiceImpl implements LmkService { return normalizeBackupData(result); } - private int requireComponentIndex(int componentIndex) { - if (componentIndex < 1 || componentIndex > LMKConstant.COMPONENT_NUM) { - throw new IllegalArgumentException("componentIndex must be 1, 2 or 3"); + private Map parseNativeComponentMap(byte[] componentBytes, String name) { + if (componentBytes.length % NATIVE_COMPONENT_COUNT != 0) { + throw new IllegalStateException(name + " native component bytes length is invalid: " + componentBytes.length); } + // 原生分量总长度必须能被 3 整除;每一段等长,依次映射为 1/2/3 号分量。 + int componentLength = componentBytes.length / NATIVE_COMPONENT_COUNT; + Map componentMap = new LinkedHashMap<>(); + for (int index = 0; index < NATIVE_COMPONENT_COUNT; index++) { + int start = index * componentLength; + componentMap.put(index + 1, Hex.toHexString(Arrays.copyOfRange(componentBytes, start, start + componentLength))); + } + return componentMap; + } + + private int requirePacketIndex(int componentIndex) { + if (componentIndex < PACKET_INDEX_ONE || componentIndex > PACKET_INDEX_TWO) { + throw new IllegalArgumentException("packetIndex must be 1 or 2"); + } + // 这里校验的是“外部包编号”,不是原生分量编号。 return componentIndex; } @@ -368,6 +414,38 @@ public class LmkServiceImpl implements LmkService { return component; } + private String requiredPacketComponent(Map componentMap, int componentIndex, String name, int packetIndex) { + if (componentMap == null) { + throw new IllegalArgumentException(name + " component missing in packet " + packetIndex); + } + String component = componentMap.get(componentIndex); + if (component == null || component.isBlank()) { + throw new IllegalArgumentException(name + " component " + componentIndex + " missing in packet " + packetIndex); + } + return component; + } + + private String requireConsistentOverlapComponent(Map packet1Components, Map packet2Components, String name) { + // 两份外部包的 component2 是同一段原生分量,必须一致; + // 一旦不一致,通常说明两份包不是同一轮导出结果,继续恢复会得到错误密钥。 + String packet1Overlap = requiredPacketComponent(packet1Components, NATIVE_COMPONENT_TWO, name, PACKET_INDEX_ONE); + String packet2Overlap = requiredPacketComponent(packet2Components, NATIVE_COMPONENT_TWO, name, PACKET_INDEX_TWO); + if (!packet1Overlap.equals(packet2Overlap)) { + throw new IllegalArgumentException(name + " component 2 is inconsistent"); + } + return packet1Overlap; + } + + private String joinOrderedComponents(Map components) { + if (components == null || components.isEmpty()) { + return ""; + } + return components.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .map(Map.Entry::getValue) + .reduce("", String::concat); + } + private String requireConsistentLmkMac(List packets) { if (packets == null || packets.size() != LMKConstant.COMPONENT_NUM) { throw new IllegalArgumentException("packets size must be " + LMKConstant.COMPONENT_NUM); @@ -446,7 +524,7 @@ public class LmkServiceImpl implements LmkService { return packet.getUserKeys().stream() .filter(item -> item.getKeyIndex() == keyIndex && item.getKeyType() == keyType) .findFirst() - .orElseThrow(() -> new IllegalArgumentException("user key component missing in packet " + packet.getComponentIndex())); + .orElseThrow(() -> new IllegalArgumentException("user key component missing in packet " + packet.getPacketIndex())); } private List sortPackets(List packets) { @@ -454,11 +532,12 @@ public class LmkServiceImpl implements LmkService { throw new IllegalArgumentException("packets size must be " + LMKConstant.COMPONENT_NUM); } List sorted = new ArrayList<>(packets); - sorted.sort(java.util.Comparator.comparingInt(MasterKeyBackupPacket::getComponentIndex)); + sorted.sort(Comparator.comparingInt(MasterKeyBackupPacket::getPacketIndex)); + // 当前外部契约固定就是两包,并且必须严格是 packet 1、packet 2。 for (int index = 0; index < sorted.size(); index++) { int expected = index + 1; - if (sorted.get(index).getComponentIndex() != expected) { - throw new IllegalArgumentException("packet componentIndex must be 1,2,3"); + if (sorted.get(index).getPacketIndex() != expected) { + throw new IllegalArgumentException("packetIndex must be 1,2"); } } return sorted; @@ -503,23 +582,26 @@ public class LmkServiceImpl implements LmkService { } private MasterKeyBackupPacket buildPacket(int componentIndex) { + // 这里做的是“原生三分量 -> 外部两包”的投影: + // packet 1 暴露 {1,2},packet 2 暴露 {2,3}。 + // 这样外部只需要保存两份包,但恢复时仍然能够拼回完整 1/2/3。 MasterKeyBackupPacket packet = new MasterKeyBackupPacket(); - packet.setComponentIndex(componentIndex); + packet.setPacketIndex(componentIndex); packet.setLmk(new MasterKeyPacketLmkPart( lmkBackup.lmkMacHex(), - requiredComponent(lmkBackup.componentMap(), componentIndex, "lmk") + packetComponentMap(lmkBackup.componentMap(), componentIndex, "lmk") )); packet.setIkAuth(new MasterKeyPacketIkPart( IKEnums.KEY_TYPE_AUTH.getCode(), authIkBackup.macHex(), - requiredComponent(authIkBackup.componentMap(), componentIndex, "ikAuth"), + packetComponentMap(authIkBackup.componentMap(), componentIndex, "ikAuth"), authIkBackup.publicKeyHex(), authIkBackup.publicKeyMacHex() )); packet.setIkDevice(new MasterKeyPacketIkPart( IKEnums.KEY_TYPE_DEVICE.getCode(), deviceIkBackup.macHex(), - requiredComponent(deviceIkBackup.componentMap(), componentIndex, "ikDevice"), + packetComponentMap(deviceIkBackup.componentMap(), componentIndex, "ikDevice"), deviceIkBackup.publicKeyHex(), deviceIkBackup.publicKeyMacHex() )); @@ -538,6 +620,20 @@ public class LmkServiceImpl implements LmkService { return packet; } + private Map packetComponentMap(Map nativeComponents, int packetIndex, String name) { + LinkedHashMap packetComponents = new LinkedHashMap<>(); + if (packetIndex == PACKET_INDEX_ONE) { + // 第一包取原生分量 1、2。 + packetComponents.put(NATIVE_COMPONENT_ONE, requiredComponent(nativeComponents, NATIVE_COMPONENT_ONE, name)); + packetComponents.put(NATIVE_COMPONENT_TWO, requiredComponent(nativeComponents, NATIVE_COMPONENT_TWO, name)); + return packetComponents; + } + // 第二包取原生分量 2、3;其中 2 是与第一包重叠的校验锚点。 + packetComponents.put(NATIVE_COMPONENT_TWO, requiredComponent(nativeComponents, NATIVE_COMPONENT_TWO, name)); + packetComponents.put(NATIVE_COMPONENT_THREE, requiredComponent(nativeComponents, NATIVE_COMPONENT_THREE, name)); + return packetComponents; + } + private void markServed(int componentIndex) { servedIndices.add(componentIndex); } diff --git a/src/test/java/com/cisd/tms/modules/auth/controller/AuthControllerTest.java b/src/test/java/com/cisd/tms/modules/auth/controller/AuthControllerTest.java index 408324e..8dde211 100644 --- a/src/test/java/com/cisd/tms/modules/auth/controller/AuthControllerTest.java +++ b/src/test/java/com/cisd/tms/modules/auth/controller/AuthControllerTest.java @@ -271,7 +271,7 @@ class AuthControllerTest { AuthService authService = Mockito.mock(AuthService.class); AuthAdminService authAdminService = Mockito.mock(AuthAdminService.class); MasterKeyBackupPacket packet = new MasterKeyBackupPacket(); - packet.setComponentIndex(1); + packet.setPacketIndex(1); UKeySignResult response = UKeySignResult.builder().sign("ISSUE-SIGN-001").backupPacket(packet).build(); Mockito.when(authAdminService.issueUkeyBindingSign( ArgumentMatchers.eq("KEY_ADMIN"), @@ -300,6 +300,6 @@ class AuthControllerTest { """)) .andExpect(status().isOk()) .andExpect(content().string(containsString("\"sign\":\"ISSUE-SIGN-001\""))) - .andExpect(content().string(containsString("\"componentIndex\":1"))); + .andExpect(content().string(containsString("\"packetIndex\":1"))); } } diff --git a/src/test/java/com/cisd/tms/modules/auth/service/AuthDomainModelTest.java b/src/test/java/com/cisd/tms/modules/auth/service/AuthDomainModelTest.java index a62caba..70654db 100644 --- a/src/test/java/com/cisd/tms/modules/auth/service/AuthDomainModelTest.java +++ b/src/test/java/com/cisd/tms/modules/auth/service/AuthDomainModelTest.java @@ -9,8 +9,8 @@ class AuthDomainModelTest { @Test void shouldExposeOnlyFourFixedRoleAccountsWithExpectedUkeyCounts() { Assertions.assertEquals(4, com.cisd.tms.modules.auth.enums.RoleCode.values().length); - Assertions.assertEquals(3, com.cisd.tms.modules.auth.enums.RoleCode.SUPER_ADMIN.getRequiredUkeyCount()); - Assertions.assertEquals(2, com.cisd.tms.modules.auth.enums.RoleCode.KEY_ADMIN.getRequiredUkeyCount()); + Assertions.assertEquals(2, com.cisd.tms.modules.auth.enums.RoleCode.SUPER_ADMIN.getRequiredUkeyCount()); + Assertions.assertEquals(1, com.cisd.tms.modules.auth.enums.RoleCode.KEY_ADMIN.getRequiredUkeyCount()); Assertions.assertEquals(1, com.cisd.tms.modules.auth.enums.RoleCode.AUDIT_ADMIN.getRequiredUkeyCount()); Assertions.assertEquals(1, com.cisd.tms.modules.auth.enums.RoleCode.OPS_ADMIN.getRequiredUkeyCount()); } diff --git a/src/test/java/com/cisd/tms/modules/auth/service/AuthServiceTest.java b/src/test/java/com/cisd/tms/modules/auth/service/AuthServiceTest.java index 15d8a1a..8537a79 100644 --- a/src/test/java/com/cisd/tms/modules/auth/service/AuthServiceTest.java +++ b/src/test/java/com/cisd/tms/modules/auth/service/AuthServiceTest.java @@ -84,7 +84,6 @@ class AuthServiceTest { InMemoryRoleUkeyBindingRepository ukeyBindings = new InMemoryRoleUkeyBindingRepository(); roleAccounts.save(activeRole(RoleCode.KEY_ADMIN, "HASH:12345678:SALT-K", "SALT-K", 0, false)); ukeyBindings.save(activeBinding(RoleCode.KEY_ADMIN, 1, "UK-1")); - ukeyBindings.save(activeBinding(RoleCode.KEY_ADMIN, 2, "UK-2")); AuthService service = newAuthService( roleAccounts, @@ -97,7 +96,7 @@ class AuthServiceTest { LoginRequest request = new LoginRequest(); request.setRoleCode(RoleCode.KEY_ADMIN.getCode()); request.setPassword("12345678"); - request.setUkeySerials(List.of("UK-1", "UK-2")); + request.setUkeySerials(List.of("UK-1")); LoginResponse response = service.login(request); @@ -164,7 +163,6 @@ class AuthServiceTest { roleAccounts.save(activeRole(RoleCode.SUPER_ADMIN, "HASH:12345678:SALT-S", "SALT-S", 0, false)); ukeyBindings.save(activeBinding(RoleCode.SUPER_ADMIN, 1, "UK-1")); ukeyBindings.save(activeBinding(RoleCode.SUPER_ADMIN, 2, "UK-2")); - ukeyBindings.save(activeBinding(RoleCode.SUPER_ADMIN, 3, "UK-3")); AuthService service = newAuthService( roleAccounts, @@ -177,7 +175,7 @@ class AuthServiceTest { LoginRequest request = new LoginRequest(); request.setRoleCode(RoleCode.SUPER_ADMIN.getCode()); request.setPassword("12345678"); - request.setUkeySerials(List.of("UK-1", "UK-2")); + request.setUkeySerials(List.of("UK-1")); BizException exception = Assertions.assertThrows(BizException.class, () -> service.login(request)); @@ -256,7 +254,7 @@ class AuthServiceTest { @Test void shouldIssueRoleScopedRandomsForUkeyLogin() { UkeyLoginRandomService randomService = org.mockito.Mockito.mock(UkeyLoginRandomService.class); - org.mockito.Mockito.when(randomService.issue(RoleCode.SUPER_ADMIN.getCode(), 3)).thenReturn(List.of("RB-1", "RB-2", "RB-3")); + org.mockito.Mockito.when(randomService.issue(RoleCode.SUPER_ADMIN.getCode(), 2)).thenReturn(List.of("RB-1", "RB-2")); AuthService service = new AuthServiceImpl( new InMemoryRoleAccountRepository(), @@ -281,7 +279,7 @@ class AuthServiceTest { UkeyLoginRandomResponse response = service.issueUkeyLoginRandoms(request); Assertions.assertEquals(RoleCode.SUPER_ADMIN.getCode(), response.getRoleCode()); - Assertions.assertEquals(List.of("RB-1", "RB-2", "RB-3"), response.getRandoms()); + Assertions.assertEquals(List.of("RB-1", "RB-2"), response.getRandoms()); } @Test @@ -367,14 +365,13 @@ class AuthServiceTest { InMemoryRoleUkeyBindingRepository ukeyBindings = new InMemoryRoleUkeyBindingRepository(); roleAccounts.save(activeRole(RoleCode.KEY_ADMIN, "HASH:12345678:SALT-K", "SALT-K", 0, false)); ukeyBindings.save(activeBinding(RoleCode.KEY_ADMIN, 1, "UK-1", "PUB-1", "4", "4")); - ukeyBindings.save(activeBinding(RoleCode.KEY_ADMIN, 2, "UK-2", "PUB-2", "5", "5")); LmkService lmkService = org.mockito.Mockito.mock(LmkService.class); UkeyLoginRandomService randomService = org.mockito.Mockito.mock(UkeyLoginRandomService.class); CompatUkeyVerifier verifier = org.mockito.Mockito.mock(CompatUkeyVerifier.class); org.mockito.Mockito.when(lmkService.getMasterKeyStatus()).thenReturn(MasterKeyStatus.NORMAL.getDetail("MAC-001")); org.mockito.Mockito.when(lmkService.exportIkPublicKeyHex()).thenReturn("IK-PUB-001"); - org.mockito.Mockito.when(randomService.issue(RoleCode.KEY_ADMIN.getCode(), 2)).thenReturn(List.of("RB-1", "RB-2")); + org.mockito.Mockito.when(randomService.issue(RoleCode.KEY_ADMIN.getCode(), 1)).thenReturn(List.of("RB-1")); AuthService service = new AuthServiceImpl( roleAccounts, @@ -401,25 +398,19 @@ class AuthServiceTest { request.setRoleCode(RoleCode.KEY_ADMIN.getCode()); request.setPassword("12345678"); request.setUkeyProofs(List.of( - proof("PUB-1", "4", "4", "RB-1", "ISSUE-1", "LOGIN-DATA-1", "LOGIN-SIGN-1"), - proof("PUB-2", "5", "5", "RB-2", "ISSUE-2", "LOGIN-DATA-2", "LOGIN-SIGN-2") + proof("PUB-1", "4", "4", "RB-1", "ISSUE-1", "LOGIN-DATA-1", "LOGIN-SIGN-1") )); LoginResponse response = service.ukeyLogin(request); Assertions.assertEquals("token-full-ukey-002", response.getToken()); Assertions.assertEquals(AuthLevel.FULL.name(), response.getAuthLevel()); - org.mockito.Mockito.verify(randomService).assertIssued(RoleCode.KEY_ADMIN.getCode(), List.of("RB-1", "RB-2")); + org.mockito.Mockito.verify(randomService).assertIssued(RoleCode.KEY_ADMIN.getCode(), List.of("RB-1")); org.mockito.Mockito.verify(verifier).verifyIssuedBinding( "{\"pubKey\":\"PUB-1\",\"authKeyPair\":\"IK-PUB-001\",\"role\":\"KEY_ADMIN\",\"uid\":\"4\",\"rid\":\"4\"}", "ISSUE-1" ); - org.mockito.Mockito.verify(verifier).verifyIssuedBinding( - "{\"pubKey\":\"PUB-2\",\"authKeyPair\":\"IK-PUB-001\",\"role\":\"KEY_ADMIN\",\"uid\":\"5\",\"rid\":\"5\"}", - "ISSUE-2" - ); org.mockito.Mockito.verify(verifier).verifyLoginSignature("PUB-1", "LOGIN-DATA-1", "LOGIN-SIGN-1"); - org.mockito.Mockito.verify(verifier).verifyLoginSignature("PUB-2", "LOGIN-DATA-2", "LOGIN-SIGN-2"); } private static RoleAccountEntity activeRole( 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 fe7483f..0ea8313 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,14 +1,13 @@ 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.DeviceStatusResult; 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; @@ -20,8 +19,13 @@ import com.cisd.tms.modules.mk.dto.MasterKeyPacketLmkPart; import com.cisd.tms.modules.mk.dto.MasterKeyPacketUserKeyPart; import com.cisd.tms.modules.mk.dto.MasterKeyStateResult; import com.cisd.tms.modules.mk.service.impl.LmkServiceImpl; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -64,7 +68,7 @@ class LmkServiceTest { } @Test - void shouldDestroyGeneratedUserKeysAndIksBeforeDestroyingLmk() { + void shouldDestroyMasterKeyInOriginalOrder() { PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE="); LmkService service = new LmkServiceImpl(pcieCryptoService, properties); @@ -72,10 +76,10 @@ class LmkServiceTest { service.destroyMasterKey(); InOrder inOrder = Mockito.inOrder(pcieCryptoService); - inOrder.verify(pcieCryptoService).deleteUserKey(1); + inOrder.verify(pcieCryptoService).destroyLmk(); inOrder.verify(pcieCryptoService).destroyIk(2); inOrder.verify(pcieCryptoService).destroyIk(1); - inOrder.verify(pcieCryptoService).destroyLmk(); + inOrder.verify(pcieCryptoService).deleteUserKey(1); } @Test @@ -127,7 +131,7 @@ class LmkServiceTest { } @Test - void shouldBuildBackupPacketForSingleComponentIndex() { + void shouldBuildBackupPacketForPacketContract() throws Exception { PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE="); BackupDataResult lmkBackup = new BackupDataResult(); @@ -159,43 +163,32 @@ class LmkServiceTest { LmkService service = new LmkServiceImpl(pcieCryptoService, properties); MasterKeyBackupPacket packet = service.buildBackupPacket(2); + int componentBytesLength = 3 * 64; + int macOffset = componentBytesLength; + int publicKeyOffset = macOffset + 8; + int publicKeyEnd = publicKeyOffset + new EccRefPublicKey().size(); + int publicKeyMacEnd = publicKeyEnd + 8; - Assertions.assertEquals(2, packet.getComponentIndex()); - Assertions.assertEquals("1112131415161718", packet.getLmk().getLmkMac()); - Assertions.assertEquals("05060708", packet.getLmk().getComponent()); - Assertions.assertEquals(2, packet.getIkAuth().getKeyType()); - Assertions.assertEquals( - org.bouncycastle.util.encoders.Hex.toHexString(java.util.Arrays.copyOfRange(ikAuthBackup.getData(), 64, 128)), - packet.getIkAuth().getComponent() + JsonNode expectedPacket = expectedPacketContract( + 2, + "1112131415161718", + hexRange(ikAuthBackup.getData(), macOffset, publicKeyOffset), + hexRange(ikAuthBackup.getData(), publicKeyOffset, publicKeyEnd), + hexRange(ikAuthBackup.getData(), publicKeyEnd, publicKeyMacEnd), + packetComponents("2", "05060708", "3", "090a0b0c"), + hexRange(ikDeviceBackup.getData(), macOffset, publicKeyOffset), + hexRange(ikDeviceBackup.getData(), publicKeyOffset, publicKeyEnd), + hexRange(ikDeviceBackup.getData(), publicKeyEnd, publicKeyMacEnd), + packetComponents("2", hexRange(ikAuthBackup.getData(), 64, 128), "3", hexRange(ikAuthBackup.getData(), 128, 192)), + packetComponents("2", hexRange(ikDeviceBackup.getData(), 64, 128), "3", hexRange(ikDeviceBackup.getData(), 128, 192)), + "6768696a6b6c", + "7778797a3132" ); - Assertions.assertEquals( - org.bouncycastle.util.encoders.Hex.toHexString(java.util.Arrays.copyOfRange(ikAuthBackup.getData(), 200, 332)), - packet.getIkAuth().getPublicKey() - ); - Assertions.assertEquals( - org.bouncycastle.util.encoders.Hex.toHexString(java.util.Arrays.copyOfRange(ikAuthBackup.getData(), 332, 340)), - packet.getIkAuth().getPublicKeyMac() - ); - Assertions.assertEquals(1, packet.getIkDevice().getKeyType()); - Assertions.assertEquals( - org.bouncycastle.util.encoders.Hex.toHexString(java.util.Arrays.copyOfRange(ikDeviceBackup.getData(), 64, 128)), - packet.getIkDevice().getComponent() - ); - Assertions.assertEquals( - org.bouncycastle.util.encoders.Hex.toHexString(java.util.Arrays.copyOfRange(ikDeviceBackup.getData(), 200, 332)), - packet.getIkDevice().getPublicKey() - ); - Assertions.assertEquals( - org.bouncycastle.util.encoders.Hex.toHexString(java.util.Arrays.copyOfRange(ikDeviceBackup.getData(), 332, 340)), - packet.getIkDevice().getPublicKeyMac() - ); - Assertions.assertEquals(2, packet.getUserKeys().size()); - Assertions.assertEquals("65666768", packet.getUserKeys().get(0).getComponent()); - Assertions.assertEquals("75767778", packet.getUserKeys().get(1).getComponent()); + Assertions.assertEquals(expectedPacket, new ObjectMapper().valueToTree(packet)); } @Test - void shouldReuseSingleBackupRoundAcrossThreePacketBuilds() { + void shouldReuseSingleBackupRoundAcrossTwoPacketBuilds() throws Exception { PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE="); @@ -254,14 +247,46 @@ class LmkServiceTest { MasterKeyBackupPacket packet1 = service.buildBackupPacket(1); MasterKeyBackupPacket packet2 = service.buildBackupPacket(2); - MasterKeyBackupPacket packet3 = service.buildBackupPacket(3); + int componentBytesLength = 3 * 64; + int macOffset = componentBytesLength; + int publicKeyOffset = macOffset + 8; + int publicKeyEnd = publicKeyOffset + new EccRefPublicKey().size(); + int publicKeyMacEnd = publicKeyEnd + 8; - 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()); + JsonNode expectedPacket1 = expectedPacketContract( + 1, + "1112131415161718", + hexRange(ikAuthRound1.getData(), macOffset, publicKeyOffset), + hexRange(ikAuthRound1.getData(), publicKeyOffset, publicKeyEnd), + hexRange(ikAuthRound1.getData(), publicKeyEnd, publicKeyMacEnd), + packetComponents("1", "01020304", "2", "05060708"), + hexRange(ikDeviceRound1.getData(), macOffset, publicKeyOffset), + hexRange(ikDeviceRound1.getData(), publicKeyOffset, publicKeyEnd), + hexRange(ikDeviceRound1.getData(), publicKeyEnd, publicKeyMacEnd), + packetComponents("1", hexRange(ikAuthRound1.getData(), 0, 64), "2", hexRange(ikAuthRound1.getData(), 64, 128)), + packetComponents("1", hexRange(ikDeviceRound1.getData(), 0, 64), "2", hexRange(ikDeviceRound1.getData(), 64, 128)), + "616263646566", + "717273747576" + ); + JsonNode expectedPacket2 = expectedPacketContract( + 2, + "1112131415161718", + hexRange(ikAuthRound1.getData(), macOffset, publicKeyOffset), + hexRange(ikAuthRound1.getData(), publicKeyOffset, publicKeyEnd), + hexRange(ikAuthRound1.getData(), publicKeyEnd, publicKeyMacEnd), + packetComponents("2", "05060708", "3", "090a0b0c"), + hexRange(ikDeviceRound1.getData(), macOffset, publicKeyOffset), + hexRange(ikDeviceRound1.getData(), publicKeyOffset, publicKeyEnd), + hexRange(ikDeviceRound1.getData(), publicKeyEnd, publicKeyMacEnd), + packetComponents("2", hexRange(ikAuthRound1.getData(), 64, 128), "3", hexRange(ikAuthRound1.getData(), 128, 192)), + packetComponents("2", hexRange(ikDeviceRound1.getData(), 64, 128), "3", hexRange(ikDeviceRound1.getData(), 128, 192)), + "6768696a6b6c", + "7778797a3132" + ); + + ObjectMapper mapper = new ObjectMapper(); + Assertions.assertEquals(expectedPacket1, mapper.valueToTree(packet1)); + Assertions.assertEquals(expectedPacket2, mapper.valueToTree(packet2)); Mockito.verify(pcieCryptoService).backupLmkEx(); Mockito.verify(pcieCryptoService).backupIkComponent(2); @@ -274,59 +299,30 @@ class LmkServiceTest { void shouldRecoverAllKeyPacketsInOrder() { PcieCryptoService pcieCryptoService = Mockito.mock(PcieCryptoService.class); MasterKeyInitProperties properties = configuredPins("MTIzNDU2Nzg=", "ODc2NTQzMjE="); + DeviceStatusResult deviceStatus = new DeviceStatusResult(); + deviceStatus.setFsmState(0); + Mockito.when(pcieCryptoService.getDeviceStatus()).thenReturn(deviceStatus); LmkService service = new LmkServiceImpl(pcieCryptoService, properties); - MasterKeyBackupPacket packet1 = new MasterKeyBackupPacket(); - packet1.setComponentIndex(1); - packet1.setLmk(new MasterKeyPacketLmkPart("1112131415161718", "0102")); - packet1.setIkAuth(new MasterKeyPacketIkPart(2, "2122232425262728", "1112", "31323334", "4142434445464748")); - packet1.setIkDevice(new MasterKeyPacketIkPart(1, "3132333435363738", "2122", "51525354", "6162636465666768")); - packet1.setUserKeys(List.of( - new MasterKeyPacketUserKeyPart(1, 0, "0102"), - new MasterKeyPacketUserKeyPart(1, 1, "1112") - )); + MasterKeyBackupPacket packet1 = recoverPacketFixture(1); + MasterKeyBackupPacket packet2 = recoverPacketFixture(2); - MasterKeyBackupPacket packet2 = new MasterKeyBackupPacket(); - packet2.setComponentIndex(2); - packet2.setLmk(new MasterKeyPacketLmkPart("1112131415161718", "0304")); - packet2.setIkAuth(new MasterKeyPacketIkPart(2, "2122232425262728", "1314", "31323334", "4142434445464748")); - packet2.setIkDevice(new MasterKeyPacketIkPart(1, "3132333435363738", "2324", "51525354", "6162636465666768")); - packet2.setUserKeys(List.of( - new MasterKeyPacketUserKeyPart(1, 0, "0304"), - new MasterKeyPacketUserKeyPart(1, 1, "1314") - )); - - MasterKeyBackupPacket packet3 = new MasterKeyBackupPacket(); - packet3.setComponentIndex(3); - packet3.setLmk(new MasterKeyPacketLmkPart("1112131415161718", "0506")); - packet3.setIkAuth(new MasterKeyPacketIkPart(2, "2122232425262728", "1516", "31323334", "4142434445464748")); - packet3.setIkDevice(new MasterKeyPacketIkPart(1, "3132333435363738", "2526", "51525354", "6162636465666768")); - packet3.setUserKeys(List.of( - new MasterKeyPacketUserKeyPart(1, 0, "0506"), - new MasterKeyPacketUserKeyPart(1, 1, "1516") - )); - - service.recoverKeyPackets(List.of(packet1, packet2, packet3)); + service.recoverKeyPackets(List.of(packet1, packet2)); ArgumentCaptor oldPinCaptor = ArgumentCaptor.forClass(byte[].class); ArgumentCaptor newPinCaptor = ArgumentCaptor.forClass(byte[].class); ArgumentCaptor lmkCaptor = ArgumentCaptor.forClass(byte[].class); ArgumentCaptor ikAuthCaptor = ArgumentCaptor.forClass(byte[].class); ArgumentCaptor ikDeviceCaptor = ArgumentCaptor.forClass(byte[].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); + ArgumentCaptor recoverCaptor = 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).generateKeyPairEcc(generateSignCaptor.capture()); - inOrder.verify(pcieCryptoService).recoverUserKey(recoverSignCaptor.capture()); - inOrder.verify(pcieCryptoService).generateKeyPairEcc(generateEncCaptor.capture()); - inOrder.verify(pcieCryptoService).recoverUserKey(recoverEncCaptor.capture()); + inOrder.verify(pcieCryptoService).getDeviceStatus(); + inOrder.verify(pcieCryptoService, Mockito.times(2)).recoverUserKey(recoverCaptor.capture()); Assertions.assertArrayEquals("12345678".getBytes(StandardCharsets.UTF_8), oldPinCaptor.getValue()); Assertions.assertArrayEquals("87654321".getBytes(StandardCharsets.UTF_8), newPinCaptor.getValue()); Assertions.assertArrayEquals(new byte[] { @@ -335,14 +331,8 @@ class LmkServiceTest { }, lmkCaptor.getValue()); Assertions.assertArrayEquals(hex("1112131415162122232425262728313233344142434445464748"), ikAuthCaptor.getValue()); Assertions.assertArrayEquals(hex("2122232425263132333435363738515253546162636465666768"), ikDeviceCaptor.getValue()); - 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()); + Assertions.assertArrayEquals(hex("010203040506"), recoverCaptor.getAllValues().get(0).getEncryptedKey()); + Assertions.assertArrayEquals(hex("111213141516"), recoverCaptor.getAllValues().get(1).getEncryptedKey()); } @Test @@ -427,4 +417,91 @@ class LmkServiceTest { private static byte[] hex(String value) { return org.bouncycastle.util.encoders.Hex.decode(value); } + + private static String hexRange(byte[] data, int start, int end) { + return org.bouncycastle.util.encoders.Hex.toHexString(Arrays.copyOfRange(data, start, end)); + } + + private static MasterKeyBackupPacket recoverPacketFixture(int packetIndex) { + MasterKeyBackupPacket packet = new MasterKeyBackupPacket(); + if (packetIndex == 1) { + packet.setPacketIndex(1); + packet.setLmk(new MasterKeyPacketLmkPart("1112131415161718", Map.of(1, "0102", 2, "0304"))); + packet.setIkAuth(new MasterKeyPacketIkPart(2, "2122232425262728", Map.of(1, "1112", 2, "1314"), "31323334", "4142434445464748")); + packet.setIkDevice(new MasterKeyPacketIkPart(1, "3132333435363738", Map.of(1, "2122", 2, "2324"), "51525354", "6162636465666768")); + packet.setUserKeys(List.of( + new MasterKeyPacketUserKeyPart(1, 0, "010203"), + new MasterKeyPacketUserKeyPart(1, 1, "111213") + )); + return packet; + } + packet.setPacketIndex(2); + packet.setLmk(new MasterKeyPacketLmkPart("1112131415161718", Map.of(2, "0304", 3, "0506"))); + packet.setIkAuth(new MasterKeyPacketIkPart(2, "2122232425262728", Map.of(2, "1314", 3, "1516"), "31323334", "4142434445464748")); + packet.setIkDevice(new MasterKeyPacketIkPart(1, "3132333435363738", Map.of(2, "2324", 3, "2526"), "51525354", "6162636465666768")); + packet.setUserKeys(List.of( + new MasterKeyPacketUserKeyPart(1, 0, "040506"), + new MasterKeyPacketUserKeyPart(1, 1, "141516") + )); + return packet; + } + + private static JsonNode expectedPacketContract( + int packetIndex, + String lmkMac, + String ikAuthMac, + String ikAuthPublicKey, + String ikAuthPublicKeyMac, + Map lmkComponents, + String ikDeviceMac, + String ikDevicePublicKey, + String ikDevicePublicKeyMac, + Map ikAuthComponents, + Map ikDeviceComponents, + String signUserKeyComponent, + String encUserKeyComponent + ) { + ObjectMapper mapper = new ObjectMapper(); + com.fasterxml.jackson.databind.node.ObjectNode root = mapper.createObjectNode(); + root.put("packetIndex", packetIndex); + + com.fasterxml.jackson.databind.node.ObjectNode lmk = root.putObject("lmk"); + lmk.put("lmkMac", lmkMac); + lmk.set("components", mapper.valueToTree(lmkComponents)); + + com.fasterxml.jackson.databind.node.ObjectNode ikAuth = root.putObject("ikAuth"); + ikAuth.put("keyType", 2); + ikAuth.put("mac", ikAuthMac); + ikAuth.put("publicKey", ikAuthPublicKey); + ikAuth.put("publicKeyMac", ikAuthPublicKeyMac); + ikAuth.set("components", mapper.valueToTree(ikAuthComponents)); + + com.fasterxml.jackson.databind.node.ObjectNode ikDevice = root.putObject("ikDevice"); + ikDevice.put("keyType", 1); + ikDevice.put("mac", ikDeviceMac); + ikDevice.put("publicKey", ikDevicePublicKey); + ikDevice.put("publicKeyMac", ikDevicePublicKeyMac); + ikDevice.set("components", mapper.valueToTree(ikDeviceComponents)); + + com.fasterxml.jackson.databind.node.ArrayNode userKeys = root.putArray("userKeys"); + userKeys.add(userKeyContract(1, 0, signUserKeyComponent)); + userKeys.add(userKeyContract(1, 1, encUserKeyComponent)); + return root; + } + + private static com.fasterxml.jackson.databind.node.ObjectNode userKeyContract(int keyIndex, int keyType, String component) { + ObjectMapper mapper = new ObjectMapper(); + com.fasterxml.jackson.databind.node.ObjectNode node = mapper.createObjectNode(); + node.put("keyIndex", keyIndex); + node.put("keyType", keyType); + node.put("component", component); + return node; + } + + private static Map packetComponents(String firstIndex, String firstValue, String secondIndex, String secondValue) { + Map components = new LinkedHashMap<>(); + components.put(firstIndex, firstValue); + components.put(secondIndex, secondValue); + return components; + } }