1717from pathlib import Path
1818from enum import IntEnum
1919from cryptography .hazmat .primitives import serialization
20+ from typing import BinaryIO
21+
2022
2123class 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
4349class PsaCracenUsageSceme (IntEnum ):
44- NONE = 0xff
50+ NONE = 0xFF
4551 PROTECTED = 0
4652 SEED = 1
4753 ENCRYPTED = 2
4854 RAW = 3
4955
56+
5057class 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
5773class PsaAlgorithm (IntEnum ):
@@ -63,28 +79,33 @@ class PsaAlgorithm(IntEnum):
6379
6480
6581class 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+
114135def 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+ )
0 commit comments