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            visible_alias = "ika",
38            help = "Local alias of the identity key. Omit for device-only CI signing."
39        )]
40        identity_key_alias: Option<String>,
41
42        /// Local alias of the device key (used for dual-signing).
43        #[arg(
44            long,
45            visible_alias = "dka",
46            help = "Local alias of the device key (used for dual-signing)."
47        )]
48        device_key_alias: String,
49
50        /// Number of days until the signature expires.
51        #[arg(long, visible_alias = "days", value_name = "N")]
52        expires_in_days: Option<i64>,
53
54        /// Optional note to embed in the attestation.
55        #[arg(long)]
56        note: Option<String>,
57    },
58
59    /// Publish a signed artifact attestation to a registry.
60    Publish {
61        /// Path to the .auths.json signature file created by `auths artifact sign`.
62        #[arg(long)]
63        signature: PathBuf,
64
65        /// Package identifier for registry indexing (e.g., npm:react@18.3.0).
66        #[arg(long)]
67        package: Option<String>,
68
69        /// Registry URL to publish to.
70        #[arg(long, default_value = "https://auths-registry.fly.dev")]
71        registry: String,
72    },
73
74    /// Verify an artifact's signature against an Auths identity.
75    Verify {
76        /// Path to the artifact file to verify.
77        #[arg(help = "Path to the artifact file to verify.")]
78        file: PathBuf,
79
80        /// Path to the signature file. Defaults to <FILE>.auths.json.
81        #[arg(long, value_name = "PATH")]
82        signature: Option<PathBuf>,
83
84        /// Path to identity bundle JSON (for CI/CD stateless verification).
85        #[arg(long, value_parser)]
86        identity_bundle: Option<PathBuf>,
87
88        /// Path to witness receipts JSON file.
89        #[arg(long)]
90        witness_receipts: Option<PathBuf>,
91
92        /// Witness public keys as DID:hex pairs (e.g., "did:key:z6Mk...:abcd1234...").
93        #[arg(long, num_args = 1..)]
94        witness_keys: Vec<String>,
95
96        /// Witness quorum threshold (default: 1).
97        #[arg(long, default_value = "1")]
98        witness_threshold: usize,
99    },
100}
101
102/// Handle the `artifact` command dispatch.
103pub fn handle_artifact(
104    cmd: ArtifactCommand,
105    repo_opt: Option<PathBuf>,
106    passphrase_provider: Arc<dyn PassphraseProvider + Send + Sync>,
107    env_config: &EnvironmentConfig,
108) -> Result<()> {
109    match cmd.command {
110        ArtifactSubcommand::Sign {
111            file,
112            sig_output,
113            identity_key_alias,
114            device_key_alias,
115            expires_in_days,
116            note,
117        } => sign::handle_sign(
118            &file,
119            sig_output,
120            identity_key_alias.as_deref(),
121            &device_key_alias,
122            expires_in_days,
123            note,
124            repo_opt,
125            passphrase_provider,
126            env_config,
127        ),
128        ArtifactSubcommand::Publish {
129            signature,
130            package,
131            registry,
132        } => publish::handle_publish(&signature, package.as_deref(), &registry),
133        ArtifactSubcommand::Verify {
134            file,
135            signature,
136            identity_bundle,
137            witness_receipts,
138            witness_keys,
139            witness_threshold,
140        } => {
141            let rt = tokio::runtime::Runtime::new()?;
142            rt.block_on(verify::handle_verify(
143                &file,
144                signature,
145                identity_bundle,
146                witness_receipts,
147                &witness_keys,
148                witness_threshold,
149            ))
150        }
151    }
152}
153
154impl crate::commands::executable::ExecutableCommand for ArtifactCommand {
155    fn execute(&self, ctx: &crate::config::CliConfig) -> anyhow::Result<()> {
156        handle_artifact(
157            self.clone(),
158            ctx.repo_path.clone(),
159            ctx.passphrase_provider.clone(),
160            &ctx.env_config,
161        )
162    }
163}