Skip to content

Commit 0a82417

Browse files
fundakolnordicjm
authored andcommitted
scripts: ncs-provision: handle metadata for KMU
Metadata must be properly set for uploaded keys to KMU. Signed-off-by: Lukasz Fundakowski <[email protected]>
1 parent 44d8597 commit 0a82417

File tree

4 files changed

+116
-5
lines changed

4 files changed

+116
-5
lines changed

scripts/requirements-test.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ cryptography
33
intelhex
44
pytest
55
pyyaml
6+
west

scripts/tests/conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
from pathlib import Path
88

99
# make all scripts importable in tests
10-
sys.path.insert(0, str(Path(__file__).parent.parent))
10+
sys.path.insert(0, str(Path(__file__).parents[1]))
11+
sys.path.insert(0, str(Path(__file__).parents[1].joinpath('west_commands')))
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor ASA
3+
#
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
6+
import pytest
7+
from ncs_provision import KmuMetadata
8+
9+
10+
@pytest.mark.parametrize(
11+
'rpolicy,expected_metadata',
12+
[
13+
(1, 0x10BA0030),
14+
(2, 0x113A0030),
15+
(3, 0x11BA0030)
16+
],
17+
ids=['ROTATING', 'LOCKED', 'REVOKED']
18+
)
19+
def test_if_kmu_metadata_are_properly_generated_for_rpolicy(rpolicy, expected_metadata):
20+
metadata = KmuMetadata(
21+
metadata_version=0,
22+
key_usage_scheme=3,
23+
reserved=0,
24+
algorithm=10,
25+
size=3,
26+
rpolicy=rpolicy,
27+
usage_flags=8
28+
)
29+
assert metadata.value == expected_metadata
30+
assert str(metadata) == hex(expected_metadata)
31+
32+
33+
@pytest.mark.parametrize(
34+
'metadata,rpolicy',
35+
[
36+
(0x10BA0030, 1),
37+
(0x113A0030, 2),
38+
(0x11bA0030, 3)
39+
],
40+
ids=['ROTATING', 'LOCKED', 'REVOKED']
41+
)
42+
def test_if_kmu_metadata_are_properly_generated_from_value(rpolicy, metadata):
43+
actual_metadata = KmuMetadata.from_value(metadata)
44+
assert actual_metadata.rpolicy == rpolicy

scripts/west_commands/ncs_provision.py

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,74 @@
2525
"BL_PUBKEY": [242, 244, 246],
2626
"APP_PUBKEY": [202, 204, 206],
2727
}
28-
KEY_SLOT_METADATA: str = "0x10ba0030"
2928
KMU_KEY_SLOT_DEST_ADDR: str = "0x20000000"
3029
ALGORITHM: str = "ED25519"
3130
POLICIES = ["revokable", "lock", "lock-last"]
3231
NRF54L15_KEY_POLICIES: dict[str, str] = {"revokable": "REVOKED", "lock": "LOCKED"}
3332

33+
METADATA_ALGORITHM_MAPPING: dict[str, int] = {
34+
"ED25519": 10,
35+
}
36+
METADATA_RPOLICY_MAPPING: dict[str, int] = {
37+
'ROTATING': 1,
38+
'LOCKED': 2,
39+
'REVOKED': 3
40+
}
41+
42+
43+
# Implemented based on
44+
# https://github.com/nrfconnect/sdk-nrf/blob/f3ac9cfb784d163f4c1af11209976fcd5c403d5a/subsys/nrf_security/src/drivers/cracen/cracenpsa/src/kmu.c#L41
45+
@dataclass
46+
class KmuMetadata:
47+
metadata_version: int = 0
48+
key_usage_scheme: int = 0
49+
reserved: int = 0
50+
algorithm: int = 0
51+
size: int = 0
52+
rpolicy: int = 0
53+
usage_flags: int = 0
54+
55+
@property
56+
def value(self) -> int:
57+
# Combine all fields into a single 32-bit integer
58+
value = 0
59+
value |= (self.metadata_version & 0xF) << 0 # (4 bits)
60+
value |= (self.key_usage_scheme & 0x3) << 4 # (2 bits)
61+
value |= (self.reserved & 0x3FF) << 6 # (10 bits)
62+
value |= (self.algorithm & 0xF) << 16 # (4 bits)
63+
value |= (self.size & 0x7) << 20 # (3 bits)
64+
value |= (self.rpolicy & 0x3) << 23 # (2 bits)
65+
value |= (self.usage_flags & 0x7F) << 25 # (7 bits)
66+
67+
return value
68+
69+
@classmethod
70+
def from_value(cls, value: int):
71+
metadata_version = (value >> 0) & 0xF
72+
key_usage_scheme = (value >> 4) & 0x3
73+
reserved = (value >> 6) & 0x3FF
74+
algorithm = (value >> 16) & 0xF
75+
size = (value >> 20) & 0x7
76+
rpolicy = (value >> 23) & 0x3
77+
usage_flags = (value >> 25) & 0x7F
78+
79+
return cls(
80+
metadata_version, key_usage_scheme, reserved,
81+
algorithm, size, rpolicy, usage_flags
82+
)
83+
84+
def __str__(self) -> str:
85+
return f'0x{self.value:08x}'
86+
3487

3588
@dataclass
3689
class SlotParams:
3790
id: int
3891
value: str
3992
rpolicy: str
93+
metadata: str
4094
algorithm: str = ALGORITHM
4195
dest: str = KMU_KEY_SLOT_DEST_ADDR
42-
metadata: str = KEY_SLOT_METADATA
4396

4497
def asdict(self) -> dict[str, str]:
4598
return asdict(self)
@@ -232,14 +285,26 @@ def _generate_slots(self, keyname: str, keys: str, policy: str) -> list[SlotPara
232285
else:
233286
key_policy = NRF54L15_KEY_POLICIES[policy]
234287
slot_id = KEY_SLOTS[keyname][slot_idx]
235-
slot = SlotParams(id=slot_id, value=pub_key_hex, rpolicy=key_policy)
288+
289+
metadata = KmuMetadata(
290+
metadata_version=0,
291+
key_usage_scheme=3,
292+
reserved=0,
293+
algorithm=METADATA_ALGORITHM_MAPPING[ALGORITHM],
294+
size=3,
295+
rpolicy=METADATA_RPOLICY_MAPPING[key_policy],
296+
usage_flags=8
297+
)
298+
slot = SlotParams(
299+
id=slot_id, value=pub_key_hex, rpolicy=key_policy, metadata=str(metadata)
300+
)
236301

237302
slots.append(slot)
238303

239304
return slots
240305

241306
@staticmethod
242-
def _get_public_key_hex(keyfile: str) -> str:
307+
def _get_public_key_hex(keyfile: str | Path) -> str:
243308
"""Return the public key hex from the given keyfile."""
244309
with open(keyfile, "rb") as f:
245310
data = f.read()

0 commit comments

Comments
 (0)