fix:主密钥拆分成两个
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 排序并校验必须是 1、2 两包。
|
||||
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 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<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 1、packet 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 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<Integer, String> packetComponentMap(Map<Integer, String> nativeComponents, int packetIndex, String name) {
|
||||
LinkedHashMap<Integer, String> 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user