@@ -5,22 +5,25 @@ use clap::Parser;
55use std:: path:: { Path , PathBuf } ;
66
77use super :: verify_commit:: { VerifyCommitCommand , handle_verify_commit} ;
8+ use crate :: commands:: artifact:: verify:: handle_verify as handle_artifact_verify;
89use crate :: commands:: device:: verify_attestation:: { VerifyCommand , handle_verify} ;
910
1011/// What kind of target the user provided.
1112pub enum VerifyTarget {
1213 GitRef ( String ) ,
1314 Attestation ( String ) ,
15+ ArtifactFile ( PathBuf ) , // binary artifact, will look up .auths.json sidecar
1416}
1517
1618/// Determine whether `raw_target` is a Git reference or an attestation path.
1719///
1820/// Rules (evaluated in order):
1921/// 1. "-" → stdin attestation
20- /// 2. Path exists on disk → attestation file
21- /// 3. Contains ".." (range notation) → git ref
22- /// 4. Is "HEAD" or matches ^[0-9a-f]{4,40}$ → git ref
23- /// 5. Otherwise → git ref (assume the user knows what they're typing)
22+ /// 2. Path exists on disk and is JSON → attestation file
23+ /// 3. Path exists on disk and is not JSON → artifact file (sidecar lookup)
24+ /// 4. Contains ".." (range notation) → git ref
25+ /// 5. Is "HEAD" or matches ^[0-9a-f]{4,40}$ → git ref
26+ /// 6. Otherwise → git ref (assume the user knows what they're typing)
2427///
2528/// Args:
2629/// * `raw_target` - Raw CLI input string.
@@ -36,7 +39,11 @@ pub fn parse_verify_target(raw_target: &str) -> VerifyTarget {
3639 }
3740 let path = Path :: new ( raw_target) ;
3841 if path. exists ( ) {
39- return VerifyTarget :: Attestation ( raw_target. to_string ( ) ) ;
42+ if is_attestation_path ( raw_target) {
43+ return VerifyTarget :: Attestation ( raw_target. to_string ( ) ) ;
44+ } else {
45+ return VerifyTarget :: ArtifactFile ( path. to_path_buf ( ) ) ;
46+ }
4047 }
4148 if raw_target. contains ( ".." ) {
4249 return VerifyTarget :: GitRef ( raw_target. to_string ( ) ) ;
@@ -57,28 +64,31 @@ pub fn parse_verify_target(raw_target: &str) -> VerifyTarget {
5764 VerifyTarget :: GitRef ( raw_target. to_string ( ) )
5865}
5966
67+ /// Returns true if the path looks like an attestation/JSON file rather than a binary artifact.
68+ fn is_attestation_path ( path : & str ) -> bool {
69+ let lower = path. to_lowercase ( ) ;
70+ lower. ends_with ( ".json" )
71+ }
72+
6073/// Unified verify command: verifies a signed commit or an attestation.
6174#[ derive( Parser , Debug , Clone ) ]
6275#[ command(
6376 about = "Verify a signed commit or attestation." ,
6477 after_help = "Examples:
6578 auths verify HEAD # Verify current commit signature
6679 auths verify main..HEAD # Verify range of commits
67- auths verify artifact.json # Verify signed artifact
80+ auths verify release.tar.gz # Verify artifact (finds .auths.json sidecar)
81+ auths verify release.tar.gz.auths.json # Verify attestation file directly
6882 auths verify - < artifact.json # Verify from stdin
6983
70- Trust Policies:
71- Defaults to TOFU (Trust-On-First-Use) on interactive terminals.
72- Use --trust explicit in CI/CD to reject unknown identities.
73-
7484Artifact Verification:
75- File signatures are stored as <file>.auths.json.
76- JSON attestations can be verified directly .
85+ Pass the artifact file directly — auths finds <file>.auths.json automatically .
86+ Pass --signature to override the default sidecar path .
7787
7888Related:
79- auths trust add <did> — Add an identity to your trust store
80- auths sign — Create signatures
81- auths --help-all — See all commands "
89+ auths sign — Create signatures
90+ auths publish — Sign and publish to registry
91+ auths trust — Manage trusted identities "
8292) ]
8393pub struct UnifiedVerifyCommand {
8494 /// Git ref, commit hash, range (e.g. HEAD, abc1234, main..HEAD),
@@ -113,6 +123,11 @@ pub struct UnifiedVerifyCommand {
113123 /// Witness public keys as DID:hex pairs.
114124 #[ arg( long, num_args = 1 ..) ]
115125 pub witness_keys : Vec < String > ,
126+
127+ /// Path to signature file. Only used when verifying an artifact file (not a commit).
128+ /// Defaults to <FILE>.auths.json.
129+ #[ arg( long, value_name = "PATH" ) ]
130+ pub signature : Option < PathBuf > ,
116131}
117132
118133/// Handle the unified verify command.
@@ -148,6 +163,18 @@ pub async fn handle_verify_unified(cmd: UnifiedVerifyCommand) -> Result<()> {
148163 } ;
149164 handle_verify ( verify_cmd) . await
150165 }
166+ VerifyTarget :: ArtifactFile ( artifact_path) => {
167+ handle_artifact_verify (
168+ & artifact_path,
169+ cmd. signature ,
170+ cmd. identity_bundle ,
171+ cmd. witness_receipts ,
172+ & cmd. witness_keys ,
173+ cmd. witness_threshold ,
174+ false ,
175+ )
176+ . await
177+ }
151178 }
152179}
153180
@@ -202,4 +229,37 @@ mod tests {
202229 let target = parse_verify_target ( f. to_str ( ) . unwrap ( ) ) ;
203230 assert ! ( matches!( target, VerifyTarget :: Attestation ( _) ) ) ;
204231 }
232+
233+ #[ test]
234+ fn test_parse_verify_target_binary_file_routes_to_artifact ( ) {
235+ use std:: fs:: File ;
236+ use tempfile:: tempdir;
237+ let dir = tempdir ( ) . unwrap ( ) ;
238+ let artifact = dir. path ( ) . join ( "release.tar.gz" ) ;
239+ File :: create ( & artifact) . unwrap ( ) ;
240+ let target = parse_verify_target ( artifact. to_str ( ) . unwrap ( ) ) ;
241+ assert ! ( matches!( target, VerifyTarget :: ArtifactFile ( _) ) ) ;
242+ }
243+
244+ #[ test]
245+ fn test_parse_verify_target_json_file_routes_to_attestation ( ) {
246+ use std:: fs:: File ;
247+ use tempfile:: tempdir;
248+ let dir = tempdir ( ) . unwrap ( ) ;
249+ let attest = dir. path ( ) . join ( "release.auths.json" ) ;
250+ File :: create ( & attest) . unwrap ( ) ;
251+ let target = parse_verify_target ( attest. to_str ( ) . unwrap ( ) ) ;
252+ assert ! ( matches!( target, VerifyTarget :: Attestation ( _) ) ) ;
253+ }
254+
255+ #[ test]
256+ fn test_parse_verify_target_plain_json_routes_to_attestation ( ) {
257+ use std:: fs:: File ;
258+ use tempfile:: tempdir;
259+ let dir = tempdir ( ) . unwrap ( ) ;
260+ let f = dir. path ( ) . join ( "attestation.json" ) ;
261+ File :: create ( & f) . unwrap ( ) ;
262+ let target = parse_verify_target ( f. to_str ( ) . unwrap ( ) ) ;
263+ assert ! ( matches!( target, VerifyTarget :: Attestation ( _) ) ) ;
264+ }
205265}
0 commit comments