Skip to main content

auths_cli/commands/artifact/
mod.rs

1pub mod core;
2pub mod file;
3pub mod publish;
4pub mod sign;
5pub mod verify;
6
7use clap::{Args, Subcommand};
8use std::path::PathBuf;
9use std::sync::Arc;
10
11use anyhow::Result;
12use auths_core::config::EnvironmentConfig;
13use auths_core::signing::PassphraseProvider;
14
15#[derive(Args, Debug, Clone)]
16#[command(about = "Sign and verify arbitrary artifacts (tarballs, binaries, etc.).")]
17pub struct ArtifactCommand {
18    #[command(subcommand)]
19    pub command: ArtifactSubcommand,
20}
21
22#[derive(Subcommand, Debug, Clone)]
23pub enum ArtifactSubcommand {
24    /// Sign an artifact file with your Auths identity.
25    Sign {
26        /// Path to the artifact file to sign.
27        #[arg(help = "Path to the artifact file to sign.")]
28        file: PathBuf,
29
30        /// Output path for the signature file. Defaults to <FILE>.auths.json.
31        #[arg(long = "sig-output", value_name = "PATH")]
32        sig_output: Option<PathBuf>,
33
34        /// Local alias of the identity key (used for signing). Omit for CI device-only signing.
35        #[arg(
36            long,
37            help = "Local alias of the identity key. Omit for device-only CI signing."
38        )]
39        identity_key_alias: Option<String>,
40
41        /// Local alias of the device key (used for dual-signing).
42        #[arg(long, help = "Local alias of the device key (used for dual-signing).")]
43        device_key_alias: String,
44
45        /// Number of days until the signature expires.
46        #[arg(long, value_name = "N")]
47        expires_in_days: Option<i64>,
48
49        /// Optional note to embed in the attestation.
50        #[arg(long)]
51        note: Option<String>,
52    },
53
54    /// Publish a signed artifact attestation to a registry.
55    Publish {
56        /// Path to the .auths.json signature file created by `auths artifact sign`.
57        #[arg(long)]
58        signature: PathBuf,
59
60        /// Package identifier for registry indexing (e.g., npm:react@18.3.0).
61        #[arg(long)]
62        package: Option<String>,
63
64        /// Registry URL to publish to.
65        #[arg(long, default_value = "https://auths-registry.fly.dev")]
66        registry: String,
67    },
68
69    /// Verify an artifact's signature against an Auths identity.
70    Verify {
71        /// Path to the artifact file to verify.
72        #[arg(help = "Path to the artifact file to verify.")]
73        file: PathBuf,
74
75        /// Path to the signature file. Defaults to <FILE>.auths.json.
76        #[arg(long, value_name = "PATH")]
77        signature: Option<PathBuf>,
78
79        /// Path to identity bundle JSON (for CI/CD stateless verification).
80        #[arg(long, value_parser)]
81        identity_bundle: Option<PathBuf>,
82
83        /// Path to witness receipts JSON file.
84        #[arg(long)]
85        witness_receipts: Option<PathBuf>,
86
87        /// Witness public keys as DID:hex pairs (e.g., "did:key:z6Mk...:abcd1234...").
88        #[arg(long, num_args = 1..)]
89        witness_keys: Vec<String>,
90
91        /// Witness quorum threshold (default: 1).
92        #[arg(long, default_value = "1")]
93        witness_threshold: usize,
94    },
95}
96
97/// Handle the `artifact` command dispatch.
98pub fn handle_artifact(
99    cmd: ArtifactCommand,
100    repo_opt: Option<PathBuf>,
101    passphrase_provider: Arc<dyn PassphraseProvider + Send + Sync>,
102    env_config: &EnvironmentConfig,
103) -> Result<()> {
104    match cmd.command {
105        ArtifactSubcommand::Sign {
106            file,
107            sig_output,
108            identity_key_alias,
109            device_key_alias,
110            expires_in_days,
111            note,
112        } => sign::handle_sign(
113            &file,
114            sig_output,
115            identity_key_alias.as_deref(),
116            &device_key_alias,
117            expires_in_days,
118            note,
119            repo_opt,
120            passphrase_provider,
121            env_config,
122        ),
123        ArtifactSubcommand::Publish {
124            signature,
125            package,
126            registry,
127        } => publish::handle_publish(&signature, package.as_deref(), &registry),
128        ArtifactSubcommand::Verify {
129            file,
130            signature,
131            identity_bundle,
132            witness_receipts,
133            witness_keys,
134            witness_threshold,
135        } => {
136            let rt = tokio::runtime::Runtime::new()?;
137            rt.block_on(verify::handle_verify(
138                &file,
139                signature,
140                identity_bundle,
141                witness_receipts,
142                &witness_keys,
143                witness_threshold,
144            ))
145        }
146    }
147}
148
149impl crate::commands::executable::ExecutableCommand for ArtifactCommand {
150    fn execute(&self, ctx: &crate::config::CliConfig) -> anyhow::Result<()> {
151        handle_artifact(
152            self.clone(),
153            ctx.repo_path.clone(),
154            ctx.passphrase_provider.clone(),
155            &ctx.env_config,
156        )
157    }
158}