use anyhow::Result;
use clap::Parser;
use std::path::{Path, PathBuf};
use super::verify_commit::{VerifyCommitCommand, handle_verify_commit};
use crate::commands::artifact::verify::handle_verify as handle_artifact_verify;
use crate::commands::device::verify_attestation::{VerifyCommand, handle_verify};
pub enum VerifyTarget {
GitRef(String),
Attestation(String),
ArtifactFile(PathBuf), }
pub fn parse_verify_target(raw_target: &str) -> VerifyTarget {
if raw_target == "-" {
return VerifyTarget::Attestation(raw_target.to_string());
}
let path = Path::new(raw_target);
if path.exists() {
if is_attestation_path(raw_target) {
return VerifyTarget::Attestation(raw_target.to_string());
} else {
return VerifyTarget::ArtifactFile(path.to_path_buf());
}
}
if raw_target.contains("..") {
return VerifyTarget::GitRef(raw_target.to_string());
}
if raw_target.eq_ignore_ascii_case("HEAD") {
return VerifyTarget::GitRef(raw_target.to_string());
}
let is_hex = raw_target.len() >= 4
&& raw_target.len() <= 40
&& raw_target.chars().all(|c| c.is_ascii_hexdigit());
if is_hex {
return VerifyTarget::GitRef(raw_target.to_string());
}
VerifyTarget::GitRef(raw_target.to_string())
}
fn is_attestation_path(path: &str) -> bool {
let lower = path.to_lowercase();
lower.ends_with(".json")
}
#[derive(Parser, Debug, Clone)]
#[command(
about = "Verify a signed commit or attestation.",
after_help = "Examples:
auths verify HEAD # Verify current commit signature
auths verify main..HEAD # Verify range of commits
auths verify release.tar.gz # Verify artifact (finds .auths.json sidecar)
auths verify release.tar.gz.auths.json # Verify attestation file directly
auths verify - < artifact.json # Verify from stdin
Artifact Verification:
Pass the artifact file directly — auths finds <file>.auths.json automatically.
Pass --signature to override the default sidecar path.
Exit codes:
0 verified
1 verification failed (signature, trust, or untrusted/unresolvable signer)
2 could not attempt (I/O error, malformed input, missing repository)
Related:
auths sign — Create signatures
auths publish — Sign and publish to registry
auths trust — Manage trusted identities"
)]
pub struct UnifiedVerifyCommand {
#[arg(default_value = "HEAD")]
pub target: String,
#[arg(long, value_parser)]
pub identity_bundle: Option<PathBuf>,
#[arg(long = "signer-key")]
pub issuer_pk: Option<String>,
#[arg(long = "signer", visible_alias = "issuer-did")]
pub issuer_did: Option<String>,
#[arg(long = "witness-signatures")]
pub witness_receipts: Option<PathBuf>,
#[arg(long = "witnesses-required", default_value = "1")]
pub witness_threshold: usize,
#[arg(long, num_args = 1..)]
pub witness_keys: Vec<String>,
#[arg(long, value_name = "PATH")]
pub signature: Option<PathBuf>,
#[arg(long)]
pub remote: Option<String>,
#[arg(long)]
pub oobi: Option<String>,
#[arg(long = "require-witnesses")]
pub require_witnesses: bool,
}
pub async fn handle_verify_unified(
cmd: UnifiedVerifyCommand,
env_config: &auths_sdk::core_config::EnvironmentConfig,
) -> Result<()> {
match parse_verify_target(&cmd.target) {
VerifyTarget::GitRef(ref_str) => {
let commit_cmd = VerifyCommitCommand {
commit: ref_str,
witness_receipts: cmd.witness_receipts,
witness_threshold: cmd.witness_threshold,
witness_keys: cmd.witness_keys,
remote: cmd.remote,
oobi: cmd.oobi,
require_witnesses: cmd.require_witnesses,
identity_bundle: cmd.identity_bundle,
};
handle_verify_commit(commit_cmd, env_config).await
}
VerifyTarget::Attestation(path_str) => {
let verify_cmd = VerifyCommand {
attestation: path_str,
issuer_pk: cmd.issuer_pk,
issuer_did: cmd.issuer_did,
trust: None,
roots_file: None,
witness_receipts: cmd.witness_receipts,
witness_threshold: cmd.witness_threshold,
witness_keys: cmd.witness_keys,
};
handle_verify(verify_cmd).await
}
VerifyTarget::ArtifactFile(artifact_path) => {
handle_artifact_verify(
&artifact_path,
cmd.signature,
cmd.identity_bundle,
cmd.witness_receipts,
&cmd.witness_keys,
cmd.witness_threshold,
false,
)
.await
}
}
}
impl crate::commands::executable::ExecutableCommand for UnifiedVerifyCommand {
fn execute(&self, ctx: &crate::config::CliConfig) -> anyhow::Result<()> {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(handle_verify_unified(self.clone(), &ctx.env_config))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_verify_target_git_ref() {
assert!(matches!(
parse_verify_target("HEAD"),
VerifyTarget::GitRef(_)
));
assert!(matches!(
parse_verify_target("abc1234"),
VerifyTarget::GitRef(_)
));
assert!(matches!(
parse_verify_target("main..HEAD"),
VerifyTarget::GitRef(_)
));
}
#[test]
fn test_parse_verify_target_stdin() {
assert!(matches!(
parse_verify_target("-"),
VerifyTarget::Attestation(_)
));
}
#[test]
fn test_parse_verify_target_nonexistent_defaults_to_git_ref() {
let target = parse_verify_target("/nonexistent/attestation.json");
assert!(matches!(target, VerifyTarget::GitRef(_)));
}
#[test]
fn test_parse_verify_target_file() {
use std::fs::File;
use tempfile::tempdir;
let dir = tempdir().unwrap();
let f = dir.path().join("attestation.json");
File::create(&f).unwrap();
let target = parse_verify_target(f.to_str().unwrap());
assert!(matches!(target, VerifyTarget::Attestation(_)));
}
#[test]
fn test_parse_verify_target_binary_file_routes_to_artifact() {
use std::fs::File;
use tempfile::tempdir;
let dir = tempdir().unwrap();
let artifact = dir.path().join("release.tar.gz");
File::create(&artifact).unwrap();
let target = parse_verify_target(artifact.to_str().unwrap());
assert!(matches!(target, VerifyTarget::ArtifactFile(_)));
}
#[test]
fn test_parse_verify_target_json_file_routes_to_attestation() {
use std::fs::File;
use tempfile::tempdir;
let dir = tempdir().unwrap();
let attest = dir.path().join("release.auths.json");
File::create(&attest).unwrap();
let target = parse_verify_target(attest.to_str().unwrap());
assert!(matches!(target, VerifyTarget::Attestation(_)));
}
#[test]
fn test_parse_verify_target_plain_json_routes_to_attestation() {
use std::fs::File;
use tempfile::tempdir;
let dir = tempdir().unwrap();
let f = dir.path().join("attestation.json");
File::create(&f).unwrap();
let target = parse_verify_target(f.to_str().unwrap());
assert!(matches!(target, VerifyTarget::Attestation(_)));
}
}