@@ -239,24 +239,42 @@ def verify(model_path, sig_path, pubkey_path, output_json, quiet):
239239 click .echo (f"Error: model not found: { model_path } " , err = True )
240240 sys .exit (2 )
241241
242- # Read sig
242+ # Read sig (C2 fix: validate fingerprint matches embedded public key)
243243 try :
244- sig = read_sig (sig_file_path )
244+ sig = read_sig (sig_file_path , validate_fingerprint = True )
245+ except ValueError as e :
246+ if not quiet :
247+ click .echo (f"FAILED { model_path .name } \n Sig file corrupted: { e } " , err = True )
248+ sys .exit (1 )
245249 except Exception as e :
246250 if not quiet :
247251 click .echo (f"Error reading sig file: { e } " , err = True )
248252 sys .exit (2 )
249253
250254 # Reconstruct public key for verification
255+ # C3 fix: warn loudly when using embedded key (no independent trust verification)
256+ using_trusted_key = False
251257 try :
252258 if pubkey_path is not None :
253259 pub_key = load_public_key (Path (pubkey_path ))
260+ using_trusted_key = True
254261 else :
255- # Use embedded public key from sig file
256- raw_pub = base64 .b64decode (sig .public_key )
257- from cryptography .hazmat .primitives .asymmetric .ed25519 import Ed25519PublicKey
258- from cryptography .hazmat .primitives import serialization
259- pub_key = Ed25519PublicKey .from_public_bytes (raw_pub )
262+ # Check keyring for matching key
263+ keyring_dir = DEFAULT_KEY_DIR / "keyring"
264+ trusted_keys = keyring_list (keyring_dir )
265+ matched_trust = None
266+ for entry in trusted_keys :
267+ if entry ["fingerprint" ] == sig .key_fingerprint :
268+ pub_key = load_public_key (Path (entry ["path" ]))
269+ using_trusted_key = True
270+ matched_trust = entry ["alias" ]
271+ break
272+
273+ if not using_trusted_key :
274+ # Fall back to embedded key — but WARN the user
275+ raw_pub = base64 .b64decode (sig .public_key )
276+ from cryptography .hazmat .primitives .asymmetric .ed25519 import Ed25519PublicKey
277+ pub_key = Ed25519PublicKey .from_public_bytes (raw_pub )
260278 except Exception as e :
261279 if not quiet :
262280 click .echo (f"Error loading public key: { e } " , err = True )
@@ -273,7 +291,20 @@ def verify(model_path, sig_path, pubkey_path, output_json, quiet):
273291 click .echo (f"Error hashing model: { e } " , err = True )
274292 sys .exit (2 )
275293
276- # Rebuild message and verify
294+ # C1 fix: check file hash against sig BEFORE verifying signature
295+ # sig.sha256 is stored as "sha256:<hex>", model_hash is raw hex
296+ expected_hash = sig .sha256
297+ if expected_hash .startswith ("sha256:" ):
298+ expected_hash = expected_hash [7 :]
299+ if model_hash != expected_hash :
300+ if output_json :
301+ click .echo (json .dumps ({"verified" : False , "error" : "hash mismatch" ,
302+ "detail" : "file has been modified since signing" }))
303+ elif not quiet :
304+ click .echo (f"FAILED { model_path .name } \n Hash mismatch — file has been modified since signing." )
305+ sys .exit (1 )
306+
307+ # Rebuild message and verify signature
277308 identity_bytes = canonical_json (sig .identity )
278309 if model_path .is_dir ():
279310 message = build_dir_message (model_hash , identity_bytes )
@@ -289,33 +320,53 @@ def verify(model_path, sig_path, pubkey_path, output_json, quiet):
289320
290321 ok = verify_bytes (message , signature_bytes , pub_key )
291322
323+ if not ok :
324+ if output_json :
325+ click .echo (json .dumps ({"verified" : False , "error" : "invalid signature" ,
326+ "detail" : "signature or identity card has been tampered with" }))
327+ elif not quiet :
328+ click .echo (f"FAILED { model_path .name } \n Invalid signature — sig file may have been tampered with." )
329+ sys .exit (1 )
330+
331+ # Determine trust level for display
332+ fp = compute_fingerprint (pub_key )
333+ if pubkey_path :
334+ trust_label = "TRUSTED (--pubkey)"
335+ elif using_trusted_key :
336+ trust_label = f"TRUSTED (keyring: { matched_trust } )"
337+ else :
338+ trust_label = "UNVERIFIED — using embedded key, not independently trusted"
339+
292340 if output_json :
293341 result = {
294- "verified" : ok ,
342+ "verified" : True ,
295343 "model" : str (model_path ),
296344 "sha256" : f"sha256:{ model_hash } " ,
297- "fingerprint" : sig .key_fingerprint ,
345+ "fingerprint" : fp ,
346+ "trust" : trust_label ,
298347 "signed_at" : sig .signed_at ,
299348 "identity" : sig .identity ,
300349 }
301350 click .echo (json .dumps (result ))
302- if not ok :
303- sys .exit (1 )
304351 return
305352
306353 if quiet :
307- if not ok :
308- sys .exit (1 )
309354 return
310355
311- if ok :
312- click .echo (f"VERIFIED: { model_path } " )
313- click .echo (f" Identity: { sig .identity .get ('name' , '(unnamed)' )} " )
314- click .echo (f" Fingerprint: { sig .key_fingerprint } " )
315- click .echo (f" Signed at: { sig .signed_at } (unverified — no RFC 3161 timestamp)" )
316- else :
317- click .echo (f"FAILED: signature verification failed for { model_path } " )
318- sys .exit (1 )
356+ click .echo (f"VERIFIED { model_path .name } " )
357+ click .echo (f" Name: { sig .identity .get ('name' , '(unnamed)' )} " )
358+ if sig .identity .get ("creator" ):
359+ click .echo (f" Creator: { sig .identity ['creator' ]} " )
360+ if sig .identity .get ("architecture" ):
361+ click .echo (f" Architecture: { sig .identity ['architecture' ]} " )
362+ if sig .identity .get ("base_model" ):
363+ click .echo (f" Base model: { sig .identity ['base_model' ]} " )
364+ if sig .identity .get ("license" ):
365+ click .echo (f" License: { sig .identity ['license' ]} " )
366+ click .echo (f" Signed: { sig .signed_at } (unverified — no RFC 3161 timestamp)" )
367+ click .echo (f" Key: { fp } ({ trust_label } )" )
368+ if not using_trusted_key :
369+ click .echo (f" WARNING: key not in keyring. Run 'modelsign keyring add <pubkey> <alias>' to trust it." )
319370
320371
321372@main .command ()
0 commit comments