Skip to content

Commit ebfe79a

Browse files
committed
[update] Reject prod debug unlock request if pk hash fuse is zeroized or uinitialized (#3602)
(cherry picked from commit 15fa4d3)
1 parent 3a709fb commit ebfe79a

8 files changed

Lines changed: 337 additions & 113 deletions

File tree

common/src/debug_unlock.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ use zerocopy::IntoBytes;
4242
pub fn create_debug_unlock_challenge(
4343
trng: &mut Trng,
4444
soc_ifc: &SocIfc,
45+
dma: &mut Dma,
4546
request: &ProductionAuthDebugUnlockReq,
4647
) -> CaliptraResult<ProductionAuthDebugUnlockChallenge> {
4748
// Validate payload
@@ -57,7 +58,7 @@ pub fn create_debug_unlock_challenge(
5758

5859
// Check if the debug level is valid.
5960
let dbg_level = request.unlock_level as u32;
60-
if dbg_level > soc_ifc.debug_unlock_pk_hash_count() {
61+
if dbg_level == 0 || dbg_level > soc_ifc.debug_unlock_pk_hash_count() {
6162
crate::cprintln!(
6263
"Invalid debug level: Received level: {}, Fuse PK Hash Count: {}",
6364
dbg_level,
@@ -66,6 +67,20 @@ pub fn create_debug_unlock_challenge(
6667
Err(CaliptraError::SS_DBG_UNLOCK_PROD_INVALID_REQ)?;
6768
}
6869

70+
// Reject request if the PK hash fuse for this level is zeroized.
71+
// Check both all-0s (unprogrammed) and all-1s (zeroized).
72+
let debug_auth_pk_offset =
73+
soc_ifc.debug_unlock_pk_hash_offset(request.unlock_level as u32)? as u64;
74+
let mci_base: AxiAddr = soc_ifc.mci_base_addr().into();
75+
let debug_auth_pk_hash_base = mci_base + debug_auth_pk_offset;
76+
let mut fuse_digest: [u32; 12] = [0; 12];
77+
dma.read_buffer(debug_auth_pk_hash_base, &mut fuse_digest);
78+
let digest = Array4x12::from(fuse_digest);
79+
if digest == Array4x12::from([0xFFFF_FFFFu32; 12]) || digest == Array4x12::from([0u32; 12]) {
80+
crate::cprintln!("Prod debug unlock disabled: PK hash fuse is zeroized/unprogrammed");
81+
Err(CaliptraError::SS_DBG_UNLOCK_PROD_DISABLED)?;
82+
}
83+
6984
let length = ((size_of::<ProductionAuthDebugUnlockChallenge>()
7085
- size_of::<MailboxRespHeader>())
7186
/ size_of::<u32>()) as u32;

error/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2223,6 +2223,11 @@ impl CaliptraError {
22232223
0xa000000b,
22242224
"Debug unlock error: Req bit not set in soc_ifc"
22252225
),
2226+
(
2227+
SS_DBG_UNLOCK_PROD_DISABLED,
2228+
0xa000000c,
2229+
"Debug unlock error: Production debug unlock disabled"
2230+
),
22262231
(
22272232
RUNTIME_DRIVER_AES_READ_KEY_KV_READ,
22282233
0xa004_0001,

rom/dev/src/flow/debug_unlock.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,12 @@ fn handle_auth_debug_unlock_request(
153153
FirmwareProcessor::copy_req_verify_chksum(&mut txn, request.as_mut_bytes(), false)?;
154154

155155
// Use common function to create challenge
156-
let challenge =
157-
debug_unlock::create_debug_unlock_challenge(&mut env.trng, &env.soc_ifc, &request);
156+
let challenge = debug_unlock::create_debug_unlock_challenge(
157+
&mut env.trng,
158+
&env.soc_ifc,
159+
&mut env.dma,
160+
&request,
161+
);
158162

159163
// Send response
160164
match challenge {

rom/dev/tests/rom_integration_tests/test_debug_unlock.rs

Lines changed: 46 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -600,15 +600,21 @@ fn test_dbg_unlock_prod_invalid_token_challenge() {
600600
)))
601601
.unwrap();
602602

603+
let unlock_level = 3u8;
604+
605+
let mut prod_dbg_unlock_keypairs: Vec<(&[u8; 96], &[u8; 2592])> =
606+
vec![(&[0; 96], &[0; 2592]); 8];
607+
prod_dbg_unlock_keypairs[(unlock_level - 1) as usize] = (
608+
ecc_pub_key_bytes.try_into().unwrap(),
609+
mldsa_pub_key_bytes.try_into().unwrap(),
610+
);
611+
603612
let mut hw = caliptra_hw_model::new(
604613
caliptra_hw_model::InitParams {
605614
rom: &rom,
606615
security_state,
607616
dbg_manuf_service,
608-
prod_dbg_unlock_keypairs: vec![(
609-
ecc_pub_key_bytes.try_into().unwrap(),
610-
mldsa_pub_key_bytes.try_into().unwrap(),
611-
)],
617+
prod_dbg_unlock_keypairs,
612618
subsystem_mode: true,
613619
debug_intent: true,
614620
ss_init_params: SubsystemInitParams {
@@ -621,8 +627,6 @@ fn test_dbg_unlock_prod_invalid_token_challenge() {
621627
)
622628
.unwrap();
623629

624-
let unlock_level = 3u8;
625-
626630
let request = ProductionAuthDebugUnlockReq {
627631
length: {
628632
let req_len = size_of::<ProductionAuthDebugUnlockReq>() - size_of::<MailboxReqHeader>();
@@ -728,15 +732,21 @@ fn test_dbg_unlock_prod_invalid_signature() {
728732
)))
729733
.unwrap();
730734

735+
let unlock_level = 4u8;
736+
737+
let mut prod_dbg_unlock_keypairs: Vec<(&[u8; 96], &[u8; 2592])> =
738+
vec![(&[0; 96], &[0; 2592]); 8];
739+
prod_dbg_unlock_keypairs[(unlock_level - 1) as usize] = (
740+
ecc_pub_key_bytes.try_into().unwrap(),
741+
mldsa_pub_key_bytes.try_into().unwrap(),
742+
);
743+
731744
let mut hw = caliptra_hw_model::new(
732745
caliptra_hw_model::InitParams {
733746
rom: &rom,
734747
security_state,
735748
dbg_manuf_service,
736-
prod_dbg_unlock_keypairs: vec![(
737-
ecc_pub_key_bytes.try_into().unwrap(),
738-
mldsa_pub_key_bytes.try_into().unwrap(),
739-
)],
749+
prod_dbg_unlock_keypairs,
740750
debug_intent: true,
741751
subsystem_mode: true,
742752
..Default::default()
@@ -745,8 +755,6 @@ fn test_dbg_unlock_prod_invalid_signature() {
745755
)
746756
.unwrap();
747757

748-
let unlock_level = 4u8;
749-
750758
// [TODO][CAP2] With wrong len mbox err 0 gets returned which is not right
751759
let request = ProductionAuthDebugUnlockReq {
752760
length: {
@@ -883,15 +891,21 @@ fn test_dbg_unlock_prod_wrong_public_keys() {
883891
)))
884892
.unwrap();
885893

894+
let unlock_level = 5u8;
895+
896+
let mut prod_dbg_unlock_keypairs: Vec<(&[u8; 96], &[u8; 2592])> =
897+
vec![(&[0; 96], &[0; 2592]); 8];
898+
prod_dbg_unlock_keypairs[(unlock_level - 1) as usize] = (
899+
ecc_pub_key_bytes.try_into().unwrap(),
900+
mldsa_pub_key_bytes.try_into().unwrap(),
901+
);
902+
886903
let mut hw = caliptra_hw_model::new(
887904
caliptra_hw_model::InitParams {
888905
rom: &rom,
889906
security_state,
890907
dbg_manuf_service,
891-
prod_dbg_unlock_keypairs: vec![(
892-
ecc_pub_key_bytes.try_into().unwrap(),
893-
mldsa_pub_key_bytes.try_into().unwrap(),
894-
)],
908+
prod_dbg_unlock_keypairs,
895909
debug_intent: true,
896910
subsystem_mode: true,
897911
..Default::default()
@@ -900,8 +914,6 @@ fn test_dbg_unlock_prod_wrong_public_keys() {
900914
)
901915
.unwrap();
902916

903-
let unlock_level = 5u8;
904-
905917
let request = ProductionAuthDebugUnlockReq {
906918
length: {
907919
let req_len = size_of::<ProductionAuthDebugUnlockReq>() - size_of::<MailboxReqHeader>();
@@ -1227,13 +1239,13 @@ fn test_dbg_unlock_prod_unlock_levels_failure() {
12271239
pk[48..].copy_from_slice(ecc_key.y().unwrap());
12281240
pk
12291241
};
1230-
let ecc_pub_key = u8_to_u32_be(&ecc_pub_key_bytes);
1231-
let ecc_pub_key_bytes = ecc_pub_key.as_bytes();
1242+
let ecc_pub_key_bytes = u8_to_u32_be(&ecc_pub_key_bytes);
1243+
let ecc_pub_key_bytes = ecc_pub_key_bytes.as_bytes();
12321244

1233-
let (verifying_mldsa_key, signing_mldsa_key) = fips204::ml_dsa_87::try_keygen().unwrap();
1245+
let (verifying_mldsa_key, _signing_mldsa_key) = fips204::ml_dsa_87::try_keygen().unwrap();
12341246
let mldsa_pub_key_bytes = verifying_mldsa_key.into_bytes();
1235-
let mldsa_pub_key = u8_to_u32_le(&mldsa_pub_key_bytes);
1236-
let mldsa_pub_key_bytes = mldsa_pub_key.as_bytes();
1247+
let mldsa_pub_key_bytes = u8_to_u32_le(&mldsa_pub_key_bytes);
1248+
let mldsa_pub_key_bytes = mldsa_pub_key_bytes.as_bytes();
12371249

12381250
let security_state = *SecurityState::default()
12391251
.set_debug_locked(true)
@@ -1246,15 +1258,19 @@ fn test_dbg_unlock_prod_unlock_levels_failure() {
12461258
)))
12471259
.unwrap();
12481260

1261+
let mut prod_dbg_unlock_keypairs: Vec<(&[u8; 96], &[u8; 2592])> =
1262+
vec![(&[0; 96], &[0; 2592]); 8];
1263+
prod_dbg_unlock_keypairs[0] = (
1264+
ecc_pub_key_bytes.try_into().unwrap(),
1265+
mldsa_pub_key_bytes.try_into().unwrap(),
1266+
);
1267+
12491268
let mut hw = caliptra_hw_model::new(
12501269
caliptra_hw_model::InitParams {
12511270
rom: &rom,
12521271
security_state,
12531272
dbg_manuf_service,
1254-
prod_dbg_unlock_keypairs: vec![(
1255-
ecc_pub_key_bytes.try_into().unwrap(),
1256-
mldsa_pub_key_bytes.try_into().unwrap(),
1257-
)],
1273+
prod_dbg_unlock_keypairs,
12581274
debug_intent: true,
12591275
subsystem_mode: true,
12601276
ss_init_params: SubsystemInitParams {
@@ -1293,87 +1309,8 @@ fn test_dbg_unlock_prod_unlock_levels_failure() {
12931309
.unwrap()
12941310
.unwrap();
12951311

1296-
if unlock_level > 8 {
1297-
assert_eq!(resp.as_slice(), [0, 0, 0, 0, 0, 0, 0, 0]);
1298-
return;
1299-
}
1300-
1301-
let challenge =
1302-
ProductionAuthDebugUnlockChallenge::read_from_bytes(resp.as_slice()).unwrap();
1303-
let reserved = [0u8; 3];
1304-
1305-
let mut sha384 = sha2::Sha384::new();
1306-
sha384.update(challenge.unique_device_identifier);
1307-
sha384.update([unlock_level]);
1308-
sha384.update(reserved);
1309-
sha384.update(challenge.challenge);
1310-
let sha384_digest = sha384.finalize();
1311-
let (ecc_signature, _id) = signing_ecc_key
1312-
.sign_prehash_recoverable(sha384_digest.as_slice())
1313-
.unwrap();
1314-
let ecc_signature = ecc_signature.to_bytes();
1315-
let ecc_signature = ecc_signature.as_slice();
1316-
let ecc_signature = u8_to_u32_be(ecc_signature);
1317-
1318-
let mut sha512 = sha2::Sha512::new();
1319-
sha512.update(challenge.unique_device_identifier);
1320-
sha512.update([unlock_level]);
1321-
sha512.update(reserved);
1322-
sha512.update(challenge.challenge);
1323-
let mut sha512_digest = sha512.finalize();
1324-
let msg = {
1325-
let msg: &mut [u8] = sha512_digest.as_mut_slice();
1326-
msg
1327-
};
1328-
1329-
let mldsa_signature = signing_mldsa_key
1330-
.try_sign_with_seed(&[0; 32], msg, &[])
1331-
.unwrap();
1332-
let mldsa_signature = {
1333-
let mut sig = [0; 4628];
1334-
sig[..4627].copy_from_slice(&mldsa_signature);
1335-
u8_to_u32_le(&sig)
1336-
};
1337-
1338-
let token = ProductionAuthDebugUnlockToken {
1339-
length: {
1340-
let req_len =
1341-
size_of::<ProductionAuthDebugUnlockToken>() - size_of::<MailboxReqHeader>();
1342-
(req_len / size_of::<u32>()) as u32
1343-
},
1344-
unique_device_identifier: challenge.unique_device_identifier,
1345-
unlock_level,
1346-
challenge: challenge.challenge,
1347-
ecc_public_key: ecc_pub_key.try_into().unwrap(),
1348-
mldsa_public_key: mldsa_pub_key.try_into().unwrap(),
1349-
ecc_signature: ecc_signature.try_into().unwrap(),
1350-
mldsa_signature: mldsa_signature.try_into().unwrap(),
1351-
..Default::default()
1352-
};
1353-
let checksum = caliptra_common::checksum::calc_checksum(
1354-
u32::from(CommandId::PRODUCTION_AUTH_DEBUG_UNLOCK_TOKEN),
1355-
&token.as_bytes()[4..],
1356-
);
1357-
let token = ProductionAuthDebugUnlockToken {
1358-
hdr: MailboxReqHeader { chksum: checksum },
1359-
..token
1360-
};
1361-
1362-
let _ = hw
1363-
.mailbox_execute(
1364-
CommandId::PRODUCTION_AUTH_DEBUG_UNLOCK_TOKEN.into(),
1365-
token.as_bytes(),
1366-
)
1367-
.unwrap();
1368-
1369-
hw.step_until(|m| {
1370-
let resp = m.soc_ifc().ss_dbg_manuf_service_reg_rsp().read();
1371-
!resp.prod_dbg_unlock_in_progress()
1372-
});
1373-
assert!(hw
1374-
.soc_ifc()
1375-
.ss_dbg_manuf_service_reg_rsp()
1376-
.read()
1377-
.prod_dbg_unlock_fail());
1312+
// All levels in this test are invalid (0, 9, 16), so the ROM returns
1313+
// a default MailboxRespHeader (8 zero bytes) instead of a challenge.
1314+
assert_eq!(resp.as_slice(), [0, 0, 0, 0, 0, 0, 0, 0]);
13781315
}
13791316
}

runtime/src/debug_unlock.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ impl ProductionDebugUnlock {
5353
&mut self,
5454
trng: &mut caliptra_drivers::Trng,
5555
soc_ifc: &caliptra_drivers::SocIfc,
56+
dma: &mut caliptra_drivers::Dma,
5657
cmd_bytes: &[u8],
5758
resp: &mut [u8],
5859
) -> CaliptraResult<usize> {
@@ -74,7 +75,7 @@ impl ProductionDebugUnlock {
7475

7576
// Use common function to create challenge
7677
let challenge =
77-
caliptra_common::debug_unlock::create_debug_unlock_challenge(trng, soc_ifc, &req)?;
78+
caliptra_common::debug_unlock::create_debug_unlock_challenge(trng, soc_ifc, dma, &req)?;
7879

7980
// Store the challenge for future token validation
8081
let stored_challenge = challenge.clone();

runtime/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,7 @@ fn handle_command(drivers: &mut Drivers) -> CaliptraResult<MboxStatusE> {
456456
CommandId::PRODUCTION_AUTH_DEBUG_UNLOCK_REQ => drivers.debug_unlock.handle_request(
457457
&mut drivers.trng,
458458
&drivers.soc_ifc,
459+
&mut drivers.dma,
459460
cmd_bytes,
460461
resp,
461462
),

0 commit comments

Comments
 (0)