Skip to content

Commit 20fc7c0

Browse files
evenlnordicjm
authored andcommitted
scripts: west_commands: update ncs-provision to work with new key format
The .json format used to pass key information to nrfutil device has been changed, this is an update will make ncs-provision use the new format. Signed-off-by: Even Falch-Larsen <[email protected]> Signed-off-by: Grzegorz Chwierut <[email protected]>
1 parent 49c2635 commit 20fc7c0

File tree

4 files changed

+186
-259
lines changed

4 files changed

+186
-259
lines changed

scripts/generate_psa_key_attributes.py

Lines changed: 125 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
from pathlib import Path
1818
from enum import IntEnum
1919
from cryptography.hazmat.primitives import serialization
20+
from typing import BinaryIO
21+
2022

2123
class PsaKeyType(IntEnum):
2224
"""The type of the key"""
@@ -39,19 +41,33 @@ class PsaUsage(IntEnum):
3941
VERIFY_MESSAGE_EXPORT = 0x0801
4042
ENCRYPT_DECRYPT = 0x0300
4143
USAGE_DERIVE = 0x4000
44+
ENCRYPT_DECRYPT_EXPORT_COPY = 0x0303
45+
ENCRYPT_DECRYPT_EXPORT = 0x0301
46+
SIGN_VERIFY_EXPORT = 0x3C01
47+
4248

4349
class PsaCracenUsageSceme(IntEnum):
44-
NONE = 0xff
50+
NONE = 0xFF
4551
PROTECTED = 0
4652
SEED = 1
4753
ENCRYPTED = 2
4854
RAW = 3
4955

56+
5057
class PsaKeyLifetime(IntEnum):
51-
"""Lifetime and location for storing key"""
58+
"""Lifetime for key"""
59+
60+
PERSISTENCE_VOLATILE = 0x00
61+
PERSISTENCE_DEFAULT = 0x01
62+
PERSISTENCE_REVOKABLE = 0x02
63+
PERSISTENCE_READ_ONLY = 0x03
5264

53-
PERSISTENT_CRACEN = 0x804E0001
54-
PERSISTENT_CRACEN_KMU = 0x804E4B01
65+
66+
class PsaKeyLocation(IntEnum):
67+
"""Location for storing key"""
68+
69+
LOCATION_CRACEN = 0x804E0000
70+
LOCATION_CRACEN_KMU = 0x804E4B00
5571

5672

5773
class PsaAlgorithm(IntEnum):
@@ -63,28 +79,33 @@ class PsaAlgorithm(IntEnum):
6379

6480

6581
class PlatformKeyAttributes:
66-
def __init__(self,
67-
key_type: PsaKeyType,
68-
identifier: int,
69-
location: PsaKeyLifetime,
70-
usage: PsaUsage,
71-
algorithm: PsaAlgorithm,
72-
size: int,
73-
cracen_usage: PsaCracenUsageSceme = PsaCracenUsageSceme.NONE):
74-
82+
def __init__(
83+
self,
84+
key_type: PsaKeyType,
85+
identifier: int,
86+
location: PsaKeyLocation,
87+
lifetime: PsaKeyLifetime,
88+
usage: PsaUsage,
89+
algorithm: PsaAlgorithm,
90+
size: int,
91+
cracen_usage: PsaCracenUsageSceme = PsaCracenUsageSceme.NONE,
92+
):
7593
self.key_type = key_type
76-
self.lifetime = location
94+
self.location = location
95+
self.lifetime = lifetime
7796
self.usage = usage
7897
self.alg0 = algorithm
7998
self.alg1 = PsaAlgorithm.NONE
80-
self.bits = size
99+
self.size = size
81100
self.identifier = identifier
82101

83-
if location == PsaKeyLifetime.PERSISTENT_CRACEN_KMU:
102+
if location == PsaKeyLocation.LOCATION_CRACEN_KMU:
84103
if cracen_usage == PsaCracenUsageSceme.NONE:
85-
print("--cracen_usage must be set if location target is PERSISTENT_CRACEN_KMU")
104+
print(
105+
"--cracen_usage must be set if location target is PERSISTENT_CRACEN_KMU"
106+
)
86107
return
87-
self.identifier = 0x7fff0000 | (cracen_usage << 12) | (identifier & 0xff)
108+
self.identifier = 0x7FFF0000 | (cracen_usage << 12) | (identifier & 0xFF)
88109

89110
if self.key_type == PsaKeyType.AES:
90111
self.alg1 = PsaAlgorithm.NONE
@@ -97,12 +118,11 @@ def __init__(self,
97118

98119
def pack(self):
99120
"""Builds a binary blob compatible with the psa_key_attributes_s C struct"""
100-
101121
return struct.pack(
102122
"<hhIIIIII",
103123
self.key_type,
104-
self.bits,
105-
self.lifetime,
124+
self.size,
125+
self.location | (self.lifetime & 0xFF),
106126
# Policy
107127
self.usage,
108128
self.alg0,
@@ -111,22 +131,73 @@ def pack(self):
111131
0, # Reserved, only used if key id encodes owner id
112132
)
113133

134+
114135
def is_valid_hexa_code(string):
115136
try:
116137
int(string, 16)
117138
return True
118139
except ValueError:
119140
return False
120141

121-
def main() -> None:
142+
143+
def generate_attr_file(
144+
attributes: PlatformKeyAttributes,
145+
key_value: str | None = None,
146+
key_file: str | None = None,
147+
trng_key: bool = False,
148+
key_from_file: BinaryIO | None = None,
149+
bin_out: bool = False,
150+
) -> None:
151+
metadata_str = binascii.hexlify(attributes.pack()).decode("utf-8").upper()
152+
153+
if key_file and Path(key_file).is_file():
154+
with open(key_file, encoding="utf-8") as file:
155+
json_data = json.load(file)
156+
else:
157+
json_data = json.loads('{ "version": 0, "keyslots": [ ]}')
158+
159+
if trng_key:
160+
value = f"TRNG:{int(math.ceil(attributes.size / 8))}"
161+
elif key_value:
162+
key = key_value.removeprefix("0x")
163+
if not is_valid_hexa_code(key):
164+
print("Invalid KEY value")
165+
return
166+
value = f"0x{key}"
167+
elif key_from_file:
168+
key_data = key_from_file.read()
169+
try:
170+
public_key = serialization.load_pem_public_key(key_data)
171+
except ValueError:
172+
# it seems it is not public key, so lets try with private
173+
private_key = serialization.load_pem_private_key(key_data, password=None)
174+
public_key = private_key.public_key()
175+
value = f"0x{public_key.public_bytes_raw().hex()}"
176+
else:
177+
print("Expecting either --key, --trng-key or --key-from-file")
178+
return
179+
180+
json_data["keyslots"].append({"metadata": f"0x{metadata_str}", "value": f"{value}"})
181+
output = json.dumps(json_data, indent=4)
182+
183+
if key_file is not None:
184+
with open(key_file, "w", encoding="utf-8") as file:
185+
file.write(output)
186+
elif bin_out:
187+
sys.stdout.buffer.write(attributes.pack())
188+
else:
189+
print(output)
190+
191+
192+
if __name__ == "__main__":
122193
parser = argparse.ArgumentParser(
123194
description="Generate PSA key attributes and write to stdout or"
124-
"create or append the information including the key to a"
125-
"nrfutil compatible json file. Also supports reading key"
126-
"from a PEM file in some cases. Key source can either be"
127-
"a RAW key using the --key argument, a randomly generated"
128-
"key using the --trng-key argument or a public key can be"
129-
"read from a .PEM file. These are mutual exclusive.",
195+
"create or append the information including the key to a"
196+
"nrfutil compatible json file. Also supports reading key"
197+
"from a PEM file in some cases. Key source can either be"
198+
"a RAW key using the --key argument, a randomly generated"
199+
"key using the --trng-key argument or a public key can be"
200+
"read from a .PEM file. These are mutual exclusive.",
130201
allow_abbrev=False,
131202
)
132203

@@ -174,6 +245,14 @@ def main() -> None:
174245
help="Storage location",
175246
type=str,
176247
required=True,
248+
choices=[x.name for x in PsaKeyLocation],
249+
)
250+
251+
parser.add_argument(
252+
"--lifetime",
253+
help="Persistence level",
254+
type=str,
255+
required=True,
177256
choices=[x.name for x in PsaKeyLifetime],
178257
)
179258

@@ -223,55 +302,22 @@ def main() -> None:
223302

224303
args = parser.parse_args()
225304

226-
metadata = PlatformKeyAttributes(key_type=PsaKeyType[args.type],
227-
identifier=args.id,
228-
location=PsaKeyLifetime[args.location],
229-
usage=PsaUsage[args.usage],
230-
algorithm=PsaAlgorithm[args.algorithm],
231-
size=args.size,
232-
cracen_usage=PsaCracenUsageSceme[args.cracen_usage]).pack()
233-
234-
metadata_str = binascii.hexlify(metadata).decode('utf-8').upper()
235-
236-
if args.file and Path(args.file).is_file():
237-
with open(args.file, encoding="utf-8") as file:
238-
json_data= json.load(file)
239-
else:
240-
json_data= json.loads('{ "version": 0, "keyslots": [ ]}')
241-
242-
if args.trng_key:
243-
value = f'TRNG:{int(math.ceil(args.size / 8))}'
244-
elif args.key:
245-
key = args.key
246-
while key.startswith("0x"):
247-
key = key.removeprefix("0x")
248-
if not is_valid_hexa_code(key):
249-
print("Invalid KEY value")
250-
return
251-
value = f'0x{key}'
252-
elif args.key_from_file:
253-
key_data = args.key_from_file.read()
254-
key = serialization.load_pem_public_key(key_data)
255-
key = key.public_bytes(
256-
encoding=serialization.Encoding.Raw,
257-
format=serialization.PublicFormat.Raw
258-
)
259-
value = f'0x{key.hex()}'
260-
else:
261-
print("Expecting either --key, --trng-key or --key-from-file")
262-
return
263-
264-
json_data["keyslots"].append({"metadata": f'0x{metadata_str}', "value": f'{value}'})
265-
output = json.dumps(json_data, indent=4)
266-
267-
if args.file:
268-
with open(args.file, "w", encoding="utf-8") as file:
269-
file.write(output)
270-
elif args.bin_output:
271-
sys.stdout.buffer.write(metadata)
272-
else:
273-
print(output)
274-
305+
attr = PlatformKeyAttributes(
306+
key_type=PsaKeyType[args.type],
307+
identifier=args.id,
308+
location=PsaKeyLocation[args.location],
309+
lifetime=PsaKeyLifetime[args.lifetime],
310+
usage=PsaUsage[args.usage],
311+
algorithm=PsaAlgorithm[args.algorithm],
312+
size=args.size,
313+
cracen_usage=PsaCracenUsageSceme[args.cracen_usage],
314+
)
275315

276-
if __name__ == "__main__":
277-
main()
316+
generate_attr_file(
317+
attributes=attr,
318+
key_value=args.key,
319+
bin_out=args.bin_output,
320+
trng_key=args.trng_key,
321+
key_from_file=args.key_from_file,
322+
key_file=args.file,
323+
)

scripts/tests/conftest.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,3 @@
88

99
# make all scripts importable in tests
1010
sys.path.insert(0, str(Path(__file__).parents[1]))
11-
sys.path.insert(0, str(Path(__file__).parents[1].joinpath('west_commands')))

scripts/tests/west_commands/ncs_provision_test.py

Lines changed: 0 additions & 44 deletions
This file was deleted.

0 commit comments

Comments
 (0)