Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions crates/auths-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,8 @@ chrono = "0.4.40"
jsonschema = { version = "0.42.2", default-features = false }
rpassword = "7.3.1"
log = "0.4.27"
json-canon = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.140"
ssh-key = "0.6"
pkcs8 = "0.10"
der = { version = "0.7", features = ["oid"] }
env_logger = "0.11.8"
Expand All @@ -69,8 +67,6 @@ reqwest = { version = "0.13.2", features = ["json", "form"] }
url = "2.5"
which = "8.0.0"
open = "5"
tokio-tungstenite = { version = "0.28.0", features = ["native-tls"] }
futures-util = "0.3"
indicatif = "0.18.4"

# LAN pairing (optional, default-on)
Expand Down
135 changes: 54 additions & 81 deletions crates/auths-cli/src/commands/artifact/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ use std::path::Path;
use std::time::Duration;

use anyhow::{Context, Result, bail};
use serde::{Deserialize, Serialize};
use auths_infra_http::HttpRegistryClient;
use auths_sdk::workflows::artifact::{
ArtifactPublishConfig, ArtifactPublishError, publish_artifact,
};
use serde::Serialize;

use crate::ux::format::{JsonResponse, Output, is_json_mode};

Expand All @@ -14,13 +18,6 @@ struct PublishJsonResponse {
signer_did: String,
}

#[derive(Deserialize)]
struct ArtifactPublishResponse {
attestation_rid: String,
package_name: Option<String>,
signer_did: String,
}

/// Publishes a signed artifact attestation to a registry.
///
/// Args:
Expand Down Expand Up @@ -99,84 +96,60 @@ async fn handle_publish_async(
None
};

let registry_url = registry.trim_end_matches('/');
let response = transmit_publish(registry_url, &attestation, package_name.as_deref()).await?;
let status = response.status();

match status.as_u16() {
201 => {
let body: ArtifactPublishResponse = response
.json()
.await
.context("Failed to parse publish response")?;
let registry_url = registry.trim_end_matches('/').to_string();
let registry_client =
HttpRegistryClient::new_with_timeouts(Duration::from_secs(30), Duration::from_secs(60));
let config = ArtifactPublishConfig {
attestation,
package_name,
registry_url: registry_url.clone(),
};

if is_json_mode() {
let json_resp = JsonResponse::success(
"artifact publish",
PublishJsonResponse {
attestation_rid: body.attestation_rid.clone(),
registry: registry_url.to_string(),
package_name: body.package_name.clone(),
signer_did: body.signer_did.clone(),
},
);
json_resp.print()?;
} else {
let out = Output::stdout();
if let Some(ref pkg) = body.package_name {
println!("Anchoring signature for {}...", out.info(pkg));
}
println!(
"{} Cryptographic attestation anchored at {}",
out.success("Success!"),
out.bold(registry_url)
);
println!("Attestation RID: {}", out.info(&body.attestation_rid));
println!();
if let Some(ref pkg) = body.package_name {
println!(
"View your trust graph online: {}/registry?q={}",
registry_url, pkg
);
}
let body = publish_artifact(&config, &registry_client)
.await
.map_err(|e| match e {
ArtifactPublishError::DuplicateAttestation => {
anyhow::anyhow!("Artifact attestation already published (duplicate RID).")
}
ArtifactPublishError::VerificationFailed(msg) => {
anyhow::anyhow!("Signature verification failed at registry: {}", msg)
}
ArtifactPublishError::RegistryError { status, body } => {
anyhow::anyhow!("Registry error ({}): {}", status, body)
}
other => anyhow::anyhow!("{}", other),
})?;

if is_json_mode() {
let json_resp = JsonResponse::success(
"artifact publish",
PublishJsonResponse {
attestation_rid: body.attestation_rid.clone(),
registry: registry_url.clone(),
package_name: body.package_name.clone(),
signer_did: body.signer_did.clone(),
},
);
json_resp.print()?;
} else {
let out = Output::stdout();
if let Some(ref pkg) = body.package_name {
println!("Anchoring signature for {}...", out.info(pkg));
}
409 => {
bail!("Artifact attestation already published (duplicate RID).");
}
422 => {
let body = response.text().await.unwrap_or_default();
bail!("Signature verification failed at registry: {}", body);
}
_ => {
let body = response.text().await.unwrap_or_default();
bail!("Registry error ({}): {}", status, body);
println!(
"{} Cryptographic attestation anchored at {}",
out.success("Success!"),
out.bold(&registry_url)
);
println!("Attestation RID: {}", out.info(&body.attestation_rid));
println!();
if let Some(ref pkg) = body.package_name {
println!(
"View your trust graph online: {}/registry?q={}",
registry_url, pkg
);
}
}

Ok(())
}

async fn transmit_publish(
registry: &str,
attestation: &serde_json::Value,
package_name: Option<&str>,
) -> Result<reqwest::Response> {
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_secs(30))
.timeout(Duration::from_secs(60))
.build()
.context("Failed to create HTTP client")?;

let endpoint = format!("{}/v1/artifacts/publish", registry);
let mut body = serde_json::json!({ "attestation": attestation });
if let Some(name) = package_name {
body["package_name"] = serde_json::Value::String(name.to_string());
}
client
.post(&endpoint)
.json(&body)
.send()
.await
.context("Failed to connect to registry server")
}
35 changes: 3 additions & 32 deletions crates/auths-cli/src/commands/artifact/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,12 @@ use std::path::{Path, PathBuf};
use std::sync::Arc;

use auths_core::config::EnvironmentConfig;
use auths_core::ports::clock::SystemClock;
use auths_core::signing::PassphraseProvider;
use auths_core::storage::keychain::{KeyAlias, get_platform_keychain_with_config};
use auths_id::attestation::export::AttestationSink;
use auths_id::ports::registry::RegistryBackend;
use auths_id::storage::attestation::AttestationSource;
use auths_id::storage::identity::IdentityStorage;
use auths_sdk::context::AuthsContext;
use auths_core::storage::keychain::KeyAlias;
use auths_sdk::signing::{ArtifactSigningParams, SigningKeyMaterial, sign_artifact_attestation};
use auths_storage::git::{
GitRegistryBackend, RegistryAttestationStorage, RegistryConfig, RegistryIdentityStorage,
};

use super::file::FileArtifact;
use crate::factories::storage::build_auths_context;

/// Execute the `artifact sign` command.
#[allow(clippy::too_many_arguments)]
Expand All @@ -33,28 +25,7 @@ pub fn handle_sign(
) -> Result<()> {
let repo_path = auths_id::storage::layout::resolve_repo_path(repo_opt)?;

let backend: Arc<dyn RegistryBackend + Send + Sync> = Arc::new(
GitRegistryBackend::from_config_unchecked(RegistryConfig::single_tenant(&repo_path)),
);
let identity_storage: Arc<dyn IdentityStorage + Send + Sync> =
Arc::new(RegistryIdentityStorage::new(repo_path.to_path_buf()));
let attestation_store = Arc::new(RegistryAttestationStorage::new(&repo_path));
let attestation_sink: Arc<dyn AttestationSink + Send + Sync> =
Arc::clone(&attestation_store) as Arc<dyn AttestationSink + Send + Sync>;
let attestation_source: Arc<dyn AttestationSource + Send + Sync> =
attestation_store as Arc<dyn AttestationSource + Send + Sync>;
let key_storage =
get_platform_keychain_with_config(env_config).context("Failed to initialize keychain")?;

let ctx = AuthsContext::builder()
.registry(backend)
.key_storage(Arc::from(key_storage))
.clock(Arc::new(SystemClock))
.identity_storage(identity_storage)
.attestation_sink(attestation_sink)
.attestation_source(attestation_source)
.passphrase_provider(passphrase_provider)
.build()?;
let ctx = build_auths_context(&repo_path, env_config, Some(passphrase_provider))?;

let params = ArtifactSigningParams {
artifact: Arc::new(FileArtifact::new(file)),
Expand Down
48 changes: 6 additions & 42 deletions crates/auths-cli/src/commands/device/authorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,53 +7,20 @@ use std::path::{Path, PathBuf};
use std::sync::Arc;

use auths_core::config::EnvironmentConfig;
use auths_core::ports::clock::SystemClock;
use auths_core::signing::{PassphraseProvider, UnifiedPassphraseProvider};
use auths_core::storage::keychain::{KeyAlias, get_platform_keychain_with_config};
use auths_id::attestation::export::AttestationSink;
use auths_core::storage::keychain::KeyAlias;
use auths_id::attestation::group::AttestationGroup;
use auths_id::identity::helpers::ManagedIdentity;
use auths_id::ports::registry::RegistryBackend;
use auths_id::storage::attestation::AttestationSource;
use auths_id::storage::identity::IdentityStorage;
use auths_id::storage::layout::{self, StorageLayoutConfig};
use auths_sdk::context::AuthsContext;
use auths_storage::git::{
GitRegistryBackend, RegistryAttestationStorage, RegistryConfig, RegistryIdentityStorage,
};
use chrono::Utc;

use crate::commands::registry_overrides::RegistryOverrides;

fn build_device_context(
repo_path: &Path,
env_config: &EnvironmentConfig,
passphrase_provider: Option<Arc<dyn PassphraseProvider + Send + Sync>>,
) -> Result<AuthsContext> {
let backend: Arc<dyn RegistryBackend + Send + Sync> = Arc::new(
GitRegistryBackend::from_config_unchecked(RegistryConfig::single_tenant(repo_path)),
);
let identity_storage: Arc<dyn IdentityStorage + Send + Sync> =
Arc::new(RegistryIdentityStorage::new(repo_path.to_path_buf()));
let attestation_store = Arc::new(RegistryAttestationStorage::new(repo_path));
let attestation_sink: Arc<dyn AttestationSink + Send + Sync> =
Arc::clone(&attestation_store) as Arc<dyn AttestationSink + Send + Sync>;
let attestation_source: Arc<dyn AttestationSource + Send + Sync> =
attestation_store as Arc<dyn AttestationSource + Send + Sync>;
let key_storage =
get_platform_keychain_with_config(env_config).context("Failed to initialize keychain")?;
let mut builder = AuthsContext::builder()
.registry(backend)
.key_storage(Arc::from(key_storage))
.clock(Arc::new(SystemClock))
.identity_storage(identity_storage)
.attestation_sink(attestation_sink)
.attestation_source(attestation_source);
if let Some(pp) = passphrase_provider {
builder = builder.passphrase_provider(pp);
}
Ok(builder.build()?)
}
use crate::factories::storage::build_auths_context;

#[derive(Args, Debug, Clone)]
#[command(about = "Manage device authorizations within an identity repository.")]
Expand Down Expand Up @@ -215,7 +182,6 @@ pub fn handle_device(
attestation_prefix_override: Option<String>,
attestation_blob_name_override: Option<String>,
passphrase_provider: Arc<dyn PassphraseProvider + Send + Sync>,
http_client: &reqwest::Client,
env_config: &EnvironmentConfig,
) -> Result<()> {
let repo_path = layout::resolve_repo_path(repo_opt)?;
Expand All @@ -239,9 +205,7 @@ pub fn handle_device(
list_devices(&repo_path, &config, include_revoked)
}
DeviceSubcommand::Resolve { device_did } => resolve_device(&repo_path, &device_did),
DeviceSubcommand::Pair(pair_cmd) => {
super::pair::handle_pair(pair_cmd, http_client, env_config)
}
DeviceSubcommand::Pair(pair_cmd) => super::pair::handle_pair(pair_cmd, env_config),
DeviceSubcommand::VerifyAttestation(verify_cmd) => {
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(super::verify_attestation::handle_verify(verify_cmd))
Expand Down Expand Up @@ -277,7 +241,7 @@ pub fn handle_device(

let passphrase_provider: Arc<dyn PassphraseProvider + Send + Sync> =
Arc::new(UnifiedPassphraseProvider::new(passphrase_provider));
let ctx = build_device_context(
let ctx = build_auths_context(
&repo_path,
env_config,
Some(Arc::clone(&passphrase_provider)),
Expand All @@ -298,7 +262,7 @@ pub fn handle_device(
identity_key_alias,
note,
} => {
let ctx = build_device_context(
let ctx = build_auths_context(
&repo_path,
env_config,
Some(Arc::clone(&passphrase_provider)),
Expand Down Expand Up @@ -419,7 +383,7 @@ fn handle_extend(
identity_key_alias: KeyAlias::new_unchecked(identity_key_alias),
device_key_alias: Some(KeyAlias::new_unchecked(device_key_alias)),
};
let ctx = build_device_context(repo_path, env_config, Some(passphrase_provider))?;
let ctx = build_auths_context(repo_path, env_config, Some(passphrase_provider))?;

let result = auths_sdk::device::extend_device_authorization(
config,
Expand Down
1 change: 0 additions & 1 deletion crates/auths-cli/src/commands/device/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ impl ExecutableCommand for DeviceCommand {
self.overrides.attestation_prefix.clone(),
self.overrides.attestation_blob.clone(),
ctx.passphrase_provider.clone(),
&ctx.http_client,
&ctx.env_config,
)
}
Expand Down
Loading
Loading