auths_cli/commands/
unified_verify.rs1use anyhow::Result;
4use clap::Parser;
5use std::path::{Path, PathBuf};
6
7use super::verify_commit::{VerifyCommitCommand, handle_verify_commit};
8use crate::commands::device::verify_attestation::{VerifyCommand, handle_verify};
9
10pub enum VerifyTarget {
12 GitRef(String),
13 Attestation(String),
14}
15
16pub fn parse_verify_target(raw_target: &str) -> VerifyTarget {
34 if raw_target == "-" {
35 return VerifyTarget::Attestation(raw_target.to_string());
36 }
37 let path = Path::new(raw_target);
38 if path.exists() {
39 return VerifyTarget::Attestation(raw_target.to_string());
40 }
41 if raw_target.contains("..") {
42 return VerifyTarget::GitRef(raw_target.to_string());
43 }
44 if raw_target.eq_ignore_ascii_case("HEAD") {
45 return VerifyTarget::GitRef(raw_target.to_string());
46 }
47 let is_hex = raw_target.len() >= 4
49 && raw_target.len() <= 40
50 && raw_target.chars().all(|c| c.is_ascii_hexdigit());
51 if is_hex {
52 return VerifyTarget::GitRef(raw_target.to_string());
53 }
54 VerifyTarget::GitRef(raw_target.to_string())
58}
59
60#[derive(Parser, Debug, Clone)]
62#[command(about = "Verify a signed commit or attestation.")]
63pub struct UnifiedVerifyCommand {
64 #[arg(default_value = "HEAD")]
67 pub target: String,
68
69 #[arg(long, default_value = ".auths/allowed_signers")]
71 pub allowed_signers: PathBuf,
72
73 #[arg(long, value_parser)]
75 pub identity_bundle: Option<PathBuf>,
76
77 #[arg(long = "issuer-pk")]
79 pub issuer_pk: Option<String>,
80
81 #[arg(long = "issuer-did")]
83 pub issuer_did: Option<String>,
84
85 #[arg(long)]
87 pub witness_receipts: Option<PathBuf>,
88
89 #[arg(long, default_value = "1")]
91 pub witness_threshold: usize,
92
93 #[arg(long, num_args = 1..)]
95 pub witness_keys: Vec<String>,
96}
97
98pub async fn handle_verify_unified(cmd: UnifiedVerifyCommand) -> Result<()> {
105 match parse_verify_target(&cmd.target) {
106 VerifyTarget::GitRef(ref_str) => {
107 let commit_cmd = VerifyCommitCommand {
108 commit: ref_str,
109 allowed_signers: cmd.allowed_signers,
110 identity_bundle: cmd.identity_bundle,
111 witness_receipts: cmd.witness_receipts,
112 witness_threshold: cmd.witness_threshold,
113 witness_keys: cmd.witness_keys,
114 };
115 handle_verify_commit(commit_cmd).await
116 }
117 VerifyTarget::Attestation(path_str) => {
118 let verify_cmd = VerifyCommand {
119 attestation: path_str,
120 issuer_pk: cmd.issuer_pk,
121 issuer_did: cmd.issuer_did,
122 trust: None,
123 roots_file: None,
124 require_capability: None,
125 witness_receipts: cmd.witness_receipts,
126 witness_threshold: cmd.witness_threshold,
127 witness_keys: cmd.witness_keys,
128 };
129 handle_verify(verify_cmd).await
130 }
131 }
132}
133
134impl crate::commands::executable::ExecutableCommand for UnifiedVerifyCommand {
135 fn execute(&self, _ctx: &crate::config::CliConfig) -> anyhow::Result<()> {
136 let rt = tokio::runtime::Runtime::new()?;
137 rt.block_on(handle_verify_unified(self.clone()))
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_parse_verify_target_git_ref() {
147 assert!(matches!(
148 parse_verify_target("HEAD"),
149 VerifyTarget::GitRef(_)
150 ));
151 assert!(matches!(
152 parse_verify_target("abc1234"),
153 VerifyTarget::GitRef(_)
154 ));
155 assert!(matches!(
156 parse_verify_target("main..HEAD"),
157 VerifyTarget::GitRef(_)
158 ));
159 }
160
161 #[test]
162 fn test_parse_verify_target_stdin() {
163 assert!(matches!(
164 parse_verify_target("-"),
165 VerifyTarget::Attestation(_)
166 ));
167 }
168
169 #[test]
170 fn test_parse_verify_target_nonexistent_defaults_to_git_ref() {
171 let target = parse_verify_target("/nonexistent/attestation.json");
172 assert!(matches!(target, VerifyTarget::GitRef(_)));
173 }
174
175 #[test]
176 fn test_parse_verify_target_file() {
177 use std::fs::File;
178 use tempfile::tempdir;
179 let dir = tempdir().unwrap();
180 let f = dir.path().join("attestation.json");
181 File::create(&f).unwrap();
182 let target = parse_verify_target(f.to_str().unwrap());
183 assert!(matches!(target, VerifyTarget::Attestation(_)));
184 }
185}