55A unified utility for managing wolfBoot images, boot status, and keystores.
66
77Usage:
8- boot_status.py status get <partition> --file <file> --config <config>
9- boot_status.py status set <partition> <value> --file <file> --config <config>
8+ boot_status.py status --file <file> --config <config> get <partition >
9+ boot_status.py status --file <file> --config <config> set <partition> <value >
1010 boot_status.py image inspect <image> [--header-size SIZE]
1111 boot_status.py image verify <image> --pubkey <key> [--alg ALG] [--verify-hash]
1212 boot_status.py image dump <image> <output> [--header-size SIZE]
1313 boot_status.py keystore convert <input> [--curve CURVE]
1414
1515Examples:
1616 # Boot status management
17- boot_status.py status get BOOT --file internal_flash.dd --config .config
18- boot_status.py status set UPDATE SUCCESS --file internal_flash.dd --config .config
17+ boot_status.py status --file internal_flash.dd --config .config get BOOT
18+ boot_status.py status --file internal_flash.dd --config .config set UPDATE SUCCESS
1919
2020 # Image inspection and verification
2121 boot_status.py image inspect test_v1_signed.bin
@@ -122,6 +122,7 @@ def read_config(config_path: str) -> dict[str, str]:
122122# IMAGE INSPECTION (from image-peek.py)
123123# ============================================================================
124124
125+ # TLV type constants for wolfBoot image headers (for documentation/reference)
125126TYPE_NAMES = {
126127 0x0001 : "version" ,
127128 0x0002 : "timestamp" ,
@@ -135,6 +136,8 @@ def parse_header(data: bytes, header_size: int = 0x100):
135136 """Parse wolfBoot image header and TLVs."""
136137 if len (data ) < 8 :
137138 raise ValueError ("Input too small to contain header" )
139+ if len (data ) < header_size :
140+ raise ValueError (f"Input size ({ len (data )} bytes) is smaller than specified header_size ({ header_size } bytes)" )
138141 magic = data [0 :4 ]
139142 size_le = struct .unpack ("<I" , data [4 :8 ])[0 ]
140143 off = 8
@@ -166,6 +169,8 @@ def find_tlv(data: bytes, header_size: int, ttype: int):
166169 Scan the header TLV area and return (value_offset, value_len, tlv_start_offset)
167170 for the first TLV matching 'ttype'. Returns None if not found.
168171 """
172+ if len (data ) < header_size :
173+ return None
169174 off = 8 # skip magic(4) + size(4)
170175 while off + 4 <= header_size :
171176 # skip padding bytes 0xFF
@@ -188,7 +193,7 @@ def decode_timestamp(v: bytes):
188193 """Decode wolfBoot timestamp TLV."""
189194 ts = struct .unpack ("<Q" , v )[0 ]
190195 try :
191- utc = datetime .datetime .utcfromtimestamp (ts ).strftime ("%Y-%m-%d %H:%M:%S UTC" )
196+ utc = datetime .datetime .fromtimestamp (ts , tz = datetime . timezone . utc ).strftime ("%Y-%m-%d %H:%M:%S UTC" )
192197 except Exception :
193198 utc = "out-of-range"
194199 return ts , utc
@@ -244,11 +249,17 @@ def verify_signature(pubkey, alg: str, firmware_hash: bytes, signature: bytes):
244249 return False , f"ECDSA verify error: { e } "
245250
246251 if alg == "ed25519" :
252+ # NOTE: wolfBoot uses Ed25519 in a non-standard way - it signs the SHA hash digest,
253+ # not the full message (see image.c:142). This matches wolfBoot's implementation where
254+ # wc_ed25519_verify_msg is called with img->sha_hash (the precomputed digest) as the
255+ # "message" parameter. While this differs from standard Ed25519 practice (which hashes
256+ # the full message internally), we implement it this way to match wolfBoot's behavior.
247257 try :
248258 if not hasattr (pubkey , "verify" ):
249259 return False , "Public key object is not Ed25519-capable"
260+ # Verify the signature over the digest (matching wolfBoot's approach)
250261 pubkey .verify (signature , firmware_hash )
251- return True , "Signature OK (Ed25519 over stored digest)"
262+ return True , "Signature OK (Ed25519 over stored digest - non-standard wolfBoot approach )"
252263 except Exception as e :
253264 return False , f"Ed25519 verify error: { e } "
254265
@@ -272,11 +283,17 @@ def cmd_image_inspect(args):
272283
273284 version = d .get (0x0001 , [(None , None )])[0 ][1 ]
274285 if version is not None :
275- print (f"Version: { struct .unpack ('<I' , version )[0 ]} " )
286+ if len (version ) >= 4 :
287+ print (f"Version: { struct .unpack ('<I' , version [:4 ])[0 ]} " )
288+ else :
289+ print (f"Version TLV too short ({ len (version )} bytes), expected at least 4 bytes" )
276290 if 0x0002 in d :
277291 ts_val = d [0x0002 ][0 ][1 ]
278- ts , utc = decode_timestamp (ts_val )
279- print (f"Timestamp: { ts } ({ utc } )" )
292+ if ts_val is not None and len (ts_val ) == 8 :
293+ ts , utc = decode_timestamp (ts_val )
294+ print (f"Timestamp: { ts } ({ utc } )" )
295+ else :
296+ print (f"Timestamp TLV has invalid length: expected 8 bytes, got { 0 if ts_val is None else len (ts_val )} " )
280297 hash_bytes = d .get (0x0003 , [(None , None )])[0 ][1 ]
281298 if hash_bytes is not None :
282299 print (f"Hash ({ len (hash_bytes )} bytes): { hash_bytes .hex ()} " )
@@ -285,7 +302,16 @@ def cmd_image_inspect(args):
285302 print (f"Pubkey hint: { hint } " )
286303 sig = d .get (0x0020 , [(None , None )])[0 ][1 ]
287304 if sig is not None :
288- print (f"Signature ({ len (sig )} bytes): { sig [:8 ].hex ()} ...{ sig [- 8 :].hex ()} " )
305+ sig_len = len (sig )
306+ if sig_len >= 16 :
307+ # Show first and last 8 bytes for typical full-length signatures
308+ print (f"Signature ({ sig_len } bytes): { sig [:8 ].hex ()} ...{ sig [- 8 :].hex ()} " )
309+ elif sig_len > 0 :
310+ # For short signatures, show the entire value without an ellipsis
311+ print (f"Signature ({ sig_len } bytes): { sig .hex ()} " )
312+ else :
313+ # Explicitly handle empty signatures
314+ print ("Signature (0 bytes): <empty>" )
289315
290316 if len (data ) < header_size + size :
291317 print (f"[WARN] File shorter ({ len (data )} bytes) than header+payload ({ header_size + size } ). Hash/signature verification may fail." )
@@ -353,7 +379,7 @@ def cmd_image_verify(args):
353379 if len (sig ) == 64 and len (hash_bytes ) in (32 ,48 ,64 ):
354380 alg = "ecdsa-p256"
355381 else :
356- print (f"[SIG] Cannot infer algorithm (sig={ len (sig )} bytes, hash={ len (hash_bytes ) if hash_bytes else 0 } )" )
382+ print (f"[SIG] Cannot infer algorithm (sig={ len (sig )} bytes, hash={ len (hash_bytes ) if hash_bytes else 0 } ); defaulting to ecdsa-p256. Specify --alg explicitly for best results. " )
357383 alg = "ecdsa-p256"
358384 ok , msg = verify_signature (pubkey , alg , hash_bytes , sig )
359385 print (f"[SIG] { msg } (alg={ alg } )" )
@@ -406,13 +432,10 @@ def cmd_keystore_convert(args):
406432 # 1) raw X||Y (64/96/132)
407433 # 2) SEC1 0x04||X||Y (65/97/133)
408434 # 3) wolfBoot 16+X||Y (80/112/148)
409- data = raw
410- is_sec1 = False
411435
412436 # Case 2: SEC1 uncompressed (leading 0x04, lengths 65/97/133)
413437 if ln in (65 , 97 , 133 ) and raw [0 ] == 0x04 :
414438 sec1 = raw
415- is_sec1 = True
416439 xy_len = ln - 1
417440 # Case 3: wolfBoot container 16+X||Y
418441 elif ln in (80 , 112 , 148 ):
@@ -422,12 +445,10 @@ def cmd_keystore_convert(args):
422445 print ("ERROR: Unexpected container size after stripping 16 bytes:" , len (data ), file = sys .stderr )
423446 sys .exit (3 )
424447 sec1 = b"\x04 " + data
425- is_sec1 = True
426448 xy_len = len (data )
427449 # Case 1: raw X||Y
428450 elif ln in (64 , 96 , 132 ):
429451 sec1 = b"\x04 " + raw
430- is_sec1 = True
431452 xy_len = ln
432453 else :
433454 print ("ERROR: Unrecognized input size:" , ln , file = sys .stderr )
@@ -452,8 +473,11 @@ def cmd_keystore_convert(args):
452473 crv = ec .SECP256R1 ()
453474 elif curve == "p384" :
454475 crv = ec .SECP384R1 ()
455- else :
476+ elif curve == "p521" :
456477 crv = ec .SECP521R1 ()
478+ else :
479+ print ("ERROR: Unsupported or unknown curve:" , curve , file = sys .stderr )
480+ sys .exit (4 )
457481
458482 try :
459483 key_obj = ec .EllipticCurvePublicKey .from_encoded_point (crv , sec1 )
@@ -479,11 +503,10 @@ def cmd_keystore_convert(args):
479503
480504 # Print SPKI SHA-256 for pubkey-hint comparison
481505 try :
482- import binascii
483506 h = hashlib .sha256 (der ).digest ()
484507 print ("Wrote:" , out_der )
485508 print ("Wrote:" , out_pem )
486- print ("SPKI SHA-256 (hex):" , binascii . hexlify ( h ). decode ( "ascii" ))
509+ print ("SPKI SHA-256 (hex):" , h . hex ( ))
487510 except Exception :
488511 print ("Wrote:" , out_der )
489512 print ("Wrote:" , out_pem )
0 commit comments