fix:主密钥拆分成两个

This commit is contained in:
waner 2026-04-14 14:55:38 +08:00
parent 05546fa213
commit f60bef7142
15 changed files with 462 additions and 198 deletions

View File

@ -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);

View File

@ -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()

View File

@ -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;
}

View File

@ -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<Void> 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<MasterKeyBackupPacket> 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");
}
}
}

View File

@ -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();

View File

@ -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<MasterKeyPacketUserKeyPart> 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() {

View File

@ -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<Integer, String> 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<Integer, String> 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<Integer, String> getComponents() {
return components;
}
public void setComponents(Map<Integer, String> 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<Integer, String> singleComponent = new LinkedHashMap<>();
if (component != null) {
singleComponent.put(1, component);
}
this.components = singleComponent;
}
}

View File

@ -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<Integer, String> components = new LinkedHashMap<>();
public MasterKeyPacketLmkPart() {
}
public MasterKeyPacketLmkPart(String lmkMac, String component) {
this.lmkMac = lmkMac;
this.component = component;
setComponent(component);
}
public MasterKeyPacketLmkPart(String lmkMac, Map<Integer, String> 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<Integer, String> getComponents() {
return components;
}
public void setComponents(Map<Integer, String> 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<Integer, String> singleComponent = new LinkedHashMap<>();
if (component != null) {
singleComponent.put(1, component);
}
this.components = singleComponent;
}
}

View File

@ -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<MasterKeyBackupPacket> getPackets() {

View File

@ -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() {

View File

@ -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<MasterKeyBackupPacket> packets) {
// 进入恢复流程后旧的备份轮次缓存不再可信避免和恢复后的新密钥体系混用
clearCachedBackupRound();
// 调用方可以任意顺序传入两份包这里统一按 packetIndex 排序并校验必须是 12 两包
List<MasterKeyBackupPacket> 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<MasterKeyBackupPacket> 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<MasterKeyBackupPacket> 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<MasterKeyBackupPacket> 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<MasterKeyBackupPacket> 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 1packet 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<Integer, String> 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<Integer, String> 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 1packet 2
String[] components = DivisionUtils.equalDivision(Hex.toHexString(normalized), LMKConstant.COMPONENT_NUM);
Map<Integer, String> componentMap = new HashMap<>();
Map<Integer, String> 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<Integer, String> 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<Integer, String> 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<Integer, String> 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<Integer, String> packet1Components, Map<Integer, String> 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<Integer, String> 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<MasterKeyBackupPacket> 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<MasterKeyBackupPacket> sortPackets(List<MasterKeyBackupPacket> packets) {
@ -454,11 +532,12 @@ public class LmkServiceImpl implements LmkService {
throw new IllegalArgumentException("packets size must be " + LMKConstant.COMPONENT_NUM);
}
List<MasterKeyBackupPacket> sorted = new ArrayList<>(packets);
sorted.sort(java.util.Comparator.comparingInt(MasterKeyBackupPacket::getComponentIndex));
sorted.sort(Comparator.comparingInt(MasterKeyBackupPacket::getPacketIndex));
// 当前外部契约固定就是两包并且必须严格是 packet 1packet 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<Integer, String> packetComponentMap(Map<Integer, String> nativeComponents, int packetIndex, String name) {
LinkedHashMap<Integer, String> packetComponents = new LinkedHashMap<>();
if (packetIndex == PACKET_INDEX_ONE) {
// 第一包取原生分量 12
packetComponents.put(NATIVE_COMPONENT_ONE, requiredComponent(nativeComponents, NATIVE_COMPONENT_ONE, name));
packetComponents.put(NATIVE_COMPONENT_TWO, requiredComponent(nativeComponents, NATIVE_COMPONENT_TWO, name));
return packetComponents;
}
// 第二包取原生分量 23其中 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);
}

View File

@ -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")));
}
}

View File

@ -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());
}

View File

@ -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(

View File

@ -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<byte[]> oldPinCaptor = ArgumentCaptor.forClass(byte[].class);
ArgumentCaptor<byte[]> newPinCaptor = ArgumentCaptor.forClass(byte[].class);
ArgumentCaptor<byte[]> lmkCaptor = ArgumentCaptor.forClass(byte[].class);
ArgumentCaptor<byte[]> ikAuthCaptor = ArgumentCaptor.forClass(byte[].class);
ArgumentCaptor<byte[]> ikDeviceCaptor = ArgumentCaptor.forClass(byte[].class);
ArgumentCaptor<GenerateKeyPairRequest> generateSignCaptor = ArgumentCaptor.forClass(GenerateKeyPairRequest.class);
ArgumentCaptor<RecoverUserKeyRequest> recoverSignCaptor = ArgumentCaptor.forClass(RecoverUserKeyRequest.class);
ArgumentCaptor<GenerateKeyPairRequest> generateEncCaptor = ArgumentCaptor.forClass(GenerateKeyPairRequest.class);
ArgumentCaptor<RecoverUserKeyRequest> recoverEncCaptor = ArgumentCaptor.forClass(RecoverUserKeyRequest.class);
ArgumentCaptor<RecoverUserKeyRequest> 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<String, String> lmkComponents,
String ikDeviceMac,
String ikDevicePublicKey,
String ikDevicePublicKeyMac,
Map<String, String> ikAuthComponents,
Map<String, String> 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<String, String> packetComponents(String firstIndex, String firstValue, String secondIndex, String secondValue) {
Map<String, String> components = new LinkedHashMap<>();
components.put(firstIndex, firstValue);
components.put(secondIndex, secondValue);
return components;
}
}