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
10 changes: 10 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ members = [
"crates/auths-pairing-protocol",
"crates/auths-radicle",
"crates/auths-scim",
"crates/auths-utils",
"crates/xtask",
]

Expand Down Expand Up @@ -60,6 +61,7 @@ auths-jwt = { path = "crates/auths-jwt", version = "0.0.1-rc.7" }
auths-pairing-daemon = { path = "crates/auths-pairing-daemon", version = "0.0.1-rc.8" }
auths-pairing-protocol = { path = "crates/auths-pairing-protocol", version = "0.0.1-rc.7" }
auths-storage = { path = "crates/auths-storage", version = "0.0.1-rc.4" }
auths-utils = { path = "crates/auths-utils" }
insta = { version = "1", features = ["json"] }

# Compile crypto-heavy crates with optimizations even in dev/test builds.
Expand Down
1 change: 1 addition & 0 deletions crates/auths-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ auths-telemetry = { workspace = true, features = ["sink-http"] }
auths-verifier = { workspace = true, features = ["native"] }
auths-infra-git.workspace = true
auths-infra-http.workspace = true
auths-utils.workspace = true
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] }
ring.workspace = true
base64.workspace = true
Expand Down
18 changes: 18 additions & 0 deletions crates/auths-cli/clippy.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Duplicated from workspace clippy.toml — keep in sync
# Clippy does NOT merge per-crate configs with workspace config.
# Any changes to the workspace clippy.toml must be replicated here.

allow-unwrap-in-tests = true
allow-expect-in-tests = true

disallowed-methods = [
# === Workspace rules (duplicated from root clippy.toml) ===
{ path = "chrono::offset::Utc::now", reason = "inject ClockProvider instead of calling Utc::now() directly", allow-invalid = true },
{ path = "std::time::SystemTime::now", reason = "inject ClockProvider instead of calling SystemTime::now() directly", allow-invalid = true },
{ path = "std::env::var", reason = "use EnvironmentConfig abstraction instead of reading env vars directly", allow-invalid = true },
{ path = "uuid::Uuid::new_v4", reason = "Use UuidProvider::new_id() instead. Inject SystemUuidProvider in production and DeterministicUuidProvider in tests." },

# === DID construction: prefer parse() for user input ===
{ path = "auths_verifier::types::DeviceDID::new_unchecked", reason = "CLI should use DeviceDID::parse() for user input. Use #[allow] with INVARIANT comment for internally-derived values.", allow-invalid = true },
{ path = "auths_verifier::types::IdentityDID::new_unchecked", reason = "CLI should use IdentityDID::parse() for user input. Use #[allow] with INVARIANT comment for internally-derived values.", allow-invalid = true },
]
10 changes: 5 additions & 5 deletions crates/auths-cli/src/commands/artifact/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use std::path::{Path, PathBuf};
use auths_verifier::core::Attestation;
use auths_verifier::witness::{WitnessQuorum, WitnessReceipt, WitnessVerifyConfig};
use auths_verifier::{
Capability, IdentityBundle, VerificationReport, verify_chain, verify_chain_with_capability,
verify_chain_with_witnesses,
Capability, IdentityBundle, IdentityDID, VerificationReport, verify_chain,
verify_chain_with_capability, verify_chain_with_witnesses,
};

use super::core::{ArtifactMetadata, ArtifactSource};
Expand Down Expand Up @@ -199,7 +199,7 @@ pub async fn handle_verify(
chain_report,
capability_valid,
witness_quorum,
issuer: Some(identity_did),
issuer: Some(identity_did.to_string()),
error: None,
},
)
Expand All @@ -209,7 +209,7 @@ pub async fn handle_verify(
fn resolve_identity_key(
identity_bundle: &Option<PathBuf>,
attestation: &Attestation,
) -> Result<(Vec<u8>, String)> {
) -> Result<(Vec<u8>, IdentityDID)> {
if let Some(bundle_path) = identity_bundle {
let bundle_content = fs::read_to_string(bundle_path)
.with_context(|| format!("Failed to read identity bundle: {:?}", bundle_path))?;
Expand All @@ -222,7 +222,7 @@ fn resolve_identity_key(
let issuer = &attestation.issuer;
let pk = resolve_pk_from_did(issuer)
.with_context(|| format!("Failed to resolve public key from issuer DID '{}'. Use --identity-bundle for stateless verification.", issuer))?;
Ok((pk, issuer.to_string()))
Ok((pk, issuer.clone()))
}
}

Expand Down
3 changes: 2 additions & 1 deletion crates/auths-cli/src/commands/device/authorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,8 @@ fn handle_extend(

fn resolve_device(repo_path: &Path, device_did_str: &str) -> Result<()> {
let attestation_storage = RegistryAttestationStorage::new(repo_path.to_path_buf());
let device_did = auths_verifier::types::DeviceDID::new(device_did_str);
#[allow(clippy::disallowed_methods)] // INVARIANT: device_did_str from attestation storage
let device_did = auths_verifier::types::DeviceDID::new_unchecked(device_did_str);
let attestations = attestation_storage
.load_attestations_for_device(&device_did)
.with_context(|| format!("Failed to load attestations for device {device_did_str}"))?;
Expand Down
1 change: 1 addition & 0 deletions crates/auths-cli/src/commands/device/pair/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ pub(crate) fn handle_pairing_response(
);

let keychain = get_platform_keychain_with_config(env_config)?;
#[allow(clippy::disallowed_methods)] // INVARIANT: controller_did from managed identity
let controller_identity_did =
auths_core::storage::keychain::IdentityDID::new_unchecked(controller_did.clone());
let aliases = keychain
Expand Down
3 changes: 2 additions & 1 deletion crates/auths-cli/src/commands/emergency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,8 @@ fn handle_revoke_device(cmd: RevokeDeviceCommand) -> Result<()> {
let controller_did = managed_identity.controller_did;
let rid = managed_identity.storage_id;

let device_did_obj = DeviceDID::new(device_did.clone());
#[allow(clippy::disallowed_methods)] // INVARIANT: device_did from managed identity storage
let device_did_obj = DeviceDID::new_unchecked(device_did.clone());

// Look up the device's public key from existing attestations
let attestation_storage = RegistryAttestationStorage::new(repo_path.clone());
Expand Down
46 changes: 1 addition & 45 deletions crates/auths-cli/src/commands/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use anyhow::{Context, Result, bail};
use auths_sdk::workflows::allowed_signers::AllowedSigners;
use auths_storage::git::RegistryAttestationStorage;
use auths_utils::path::expand_tilde;
use clap::{Parser, Subcommand};
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
Expand Down Expand Up @@ -203,20 +204,6 @@ auths signers sync --repo "{}" --output "{}"
)
}

pub(crate) fn expand_tilde(path: &Path) -> Result<PathBuf> {
let path_str = path.to_string_lossy();
if path_str.starts_with("~/") || path_str == "~" {
let home = dirs::home_dir().context("Failed to determine home directory")?;
if path_str == "~" {
Ok(home)
} else {
Ok(home.join(&path_str[2..]))
}
} else {
Ok(path.to_path_buf())
}
}

use crate::commands::executable::ExecutableCommand;
use crate::config::CliConfig;

Expand All @@ -231,37 +218,6 @@ mod tests {
use super::*;
use tempfile::TempDir;

#[test]
fn test_expand_tilde() {
let path = PathBuf::from("~/.auths");
let result = expand_tilde(&path);
assert!(result.is_ok());
let expanded = result.unwrap();
assert!(!expanded.to_string_lossy().contains("~"));
assert!(expanded.ends_with(".auths"));
}

#[test]
fn test_expand_tilde_bare() {
let path = PathBuf::from("~");
let result = expand_tilde(&path).unwrap();
assert_eq!(result, dirs::home_dir().unwrap());
}

#[test]
fn test_expand_tilde_absolute_path_unchanged() {
let path = PathBuf::from("/tmp/auths");
let result = expand_tilde(&path).unwrap();
assert_eq!(result, PathBuf::from("/tmp/auths"));
}

#[test]
fn test_expand_tilde_relative_path_unchanged() {
let path = PathBuf::from("relative/path");
let result = expand_tilde(&path).unwrap();
assert_eq!(result, PathBuf::from("relative/path"));
}

#[test]
fn test_find_git_dir() {
let temp = TempDir::new().unwrap();
Expand Down
5 changes: 3 additions & 2 deletions crates/auths-cli/src/commands/id/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use auths_core::{
signing::PassphraseProvider,
storage::keychain::{KeyAlias, get_platform_keychain},
};
use auths_verifier::{IdentityBundle, Prefix};
use auths_verifier::{IdentityBundle, IdentityDID, Prefix};
use clap::ValueEnum;

use crate::commands::registry_overrides::RegistryOverrides;
Expand Down Expand Up @@ -602,7 +602,8 @@ pub fn handle_id(

// Create the bundle
let bundle = IdentityBundle {
identity_did: identity.controller_did.to_string(),
#[allow(clippy::disallowed_methods)] // INVARIANT: controller_did from storage
identity_did: IdentityDID::new_unchecked(identity.controller_did.to_string()),
public_key_hex,
attestation_chain: attestations,
bundle_timestamp: Utc::now(),
Expand Down
1 change: 1 addition & 0 deletions crates/auths-cli/src/commands/init/prompts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ fn run_github_verification(
let controller_did = auths_sdk::pairing::load_controller_did(ctx.identity_storage.as_ref())
.map_err(|e| anyhow::anyhow!("{e}"))?;

#[allow(clippy::disallowed_methods)] // INVARIANT: controller_did from identity storage
let identity_did = IdentityDID::new_unchecked(controller_did.clone());
let aliases = ctx
.key_storage
Expand Down
4 changes: 3 additions & 1 deletion crates/auths-cli/src/commands/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,9 @@ pub fn handle_key(cmd: KeyCommand) -> Result<()> {
seed_file,
controller_did,
} => {
let identity_did = IdentityDID::new(controller_did);
#[allow(clippy::disallowed_methods)]
// INVARIANT: controller_did from CLI arg validated by clap
let identity_did = IdentityDID::new_unchecked(controller_did);
key_import(&key_alias, &seed_file, &identity_did)
}
KeySubcommand::CopyBackend {
Expand Down
21 changes: 14 additions & 7 deletions crates/auths-cli/src/commands/org.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,8 @@ pub fn handle_org(cmd: OrgCommand, ctx: &crate::config::CliConfig) -> Result<()>
};

let signer = StorageSigner::new(get_platform_keychain()?);
let org_did = DeviceDID::new(controller_did.to_string());
#[allow(clippy::disallowed_methods)] // INVARIANT: controller_did from storage
let org_did = DeviceDID::new_unchecked(controller_did.to_string());

let attestation = create_signed_attestation(
now,
Expand Down Expand Up @@ -432,7 +433,9 @@ pub fn handle_org(cmd: OrgCommand, ctx: &crate::config::CliConfig) -> Result<()>
let _pkcs8_bytes = decrypt_keypair(&encrypted_key, &passphrase)
.context("Failed to decrypt signer key (invalid passphrase?)")?;

let subject_device_did = DeviceDID::new(subject_did.clone());
#[allow(clippy::disallowed_methods)]
// INVARIANT: subject_did accepts both did:key and did:keri
let subject_device_did = DeviceDID::new_unchecked(subject_did.clone());

// --- Resolve device public key using the custom resolver IF did:key ---
let device_resolved = resolver.resolve(&subject_did).with_context(|| {
Expand Down Expand Up @@ -519,8 +522,8 @@ pub fn handle_org(cmd: OrgCommand, ctx: &crate::config::CliConfig) -> Result<()>
let _pkcs8_bytes =
decrypt_keypair(&encrypted_key, &pass).context("Failed to decrypt identity key")?;

// Allow both did:key and did:keri as subject input
let subject_device_did = DeviceDID::new(subject_did.clone());
#[allow(clippy::disallowed_methods)] // INVARIANT: accepts both did:key and did:keri
let subject_device_did = DeviceDID::new_unchecked(subject_did.clone());
let now = Utc::now();

// Look up the subject's public key from existing attestations
Expand Down Expand Up @@ -568,7 +571,9 @@ pub fn handle_org(cmd: OrgCommand, ctx: &crate::config::CliConfig) -> Result<()>
let resolver = DefaultDidResolver::with_repo(&repo_path);
let group = AttestationGroup::from_list(attestation_storage.load_all_attestations()?);

let subject_device_did = DeviceDID(subject_did.clone());
#[allow(clippy::disallowed_methods)]
// INVARIANT: subject_did from CLI arg, used for lookup only
let subject_device_did = DeviceDID::new_unchecked(subject_did.clone());
if let Some(list) = group.by_device.get(subject_device_did.as_str()) {
for (i, att) in list.iter().enumerate() {
if !include_revoked
Expand Down Expand Up @@ -715,7 +720,8 @@ pub fn handle_org(cmd: OrgCommand, ctx: &crate::config::CliConfig) -> Result<()>
)
.context("Failed to add member")?;

let member_did = DeviceDID::new(member.clone());
#[allow(clippy::disallowed_methods)] // INVARIANT: member DID from org registry
let member_did = DeviceDID::new_unchecked(member.clone());
let attestation_storage = RegistryAttestationStorage::new(repo_path.clone());
attestation_storage
.export(
Expand Down Expand Up @@ -803,7 +809,8 @@ pub fn handle_org(cmd: OrgCommand, ctx: &crate::config::CliConfig) -> Result<()>
passphrase_provider: passphrase_provider.as_ref(),
};

let member_did = DeviceDID::new(member.clone());
#[allow(clippy::disallowed_methods)] // INVARIANT: member DID from org registry
let member_did = DeviceDID::new_unchecked(member.clone());
let revocation = revoke_organization_member(
&org_ctx,
RevokeMemberCommand {
Expand Down
11 changes: 2 additions & 9 deletions crates/auths-cli/src/commands/scim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use std::path::PathBuf;
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};

use auths_utils::url::mask_url;

use crate::commands::executable::ExecutableCommand;
use crate::config::CliConfig;

Expand Down Expand Up @@ -397,15 +399,6 @@ fn generate_token_b64() -> String {
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes)
}

fn mask_url(url: &str) -> String {
if let Some(at_pos) = url.find('@')
&& let Some(scheme_end) = url.find("://")
{
return format!("{}://***@{}", &url[..scheme_end], &url[at_pos + 1..]);
}
url.to_string()
}

impl ExecutableCommand for ScimCommand {
fn execute(&self, _ctx: &CliConfig) -> Result<()> {
handle_scim(self.clone())
Expand Down
4 changes: 2 additions & 2 deletions crates/auths-cli/src/commands/signers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use clap::{Parser, Subcommand};
use ssh_key::PublicKey as SshPublicKey;
use std::path::PathBuf;

use super::git::expand_tilde;
use crate::adapters::allowed_signers_store::FileAllowedSignersStore;
use auths_utils::path::expand_tilde;

#[derive(Parser, Debug, Clone)]
#[command(about = "Manage allowed signers for Git commit verification.")]
Expand Down Expand Up @@ -89,7 +89,7 @@ fn resolve_signers_path() -> Result<PathBuf> {
let path_str = String::from_utf8_lossy(&out.stdout).trim().to_string();
if !path_str.is_empty() {
let path = PathBuf::from(&path_str);
return expand_tilde(&path);
return Ok(expand_tilde(&path)?);
}
}

Expand Down
8 changes: 4 additions & 4 deletions crates/auths-cli/src/commands/verify_commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,7 @@ mod tests {
#[tokio::test]
async fn verify_bundle_chain_empty_chain() {
let bundle = IdentityBundle {
identity_did: "did:keri:test".into(),
identity_did: auths_verifier::IdentityDID::new_unchecked("did:keri:test"),
public_key_hex: "aa".repeat(32),
attestation_chain: vec![],
bundle_timestamp: Utc::now(),
Expand All @@ -929,13 +929,13 @@ mod tests {
#[tokio::test]
async fn verify_bundle_chain_invalid_hex() {
let bundle = IdentityBundle {
identity_did: "did:keri:test".into(),
identity_did: auths_verifier::IdentityDID::new_unchecked("did:keri:test"),
public_key_hex: "not_hex".into(),
attestation_chain: vec![auths_verifier::core::Attestation {
version: 1,
rid: "test".into(),
issuer: "did:keri:test".into(),
subject: auths_verifier::DeviceDID::new("did:key:test"),
issuer: auths_verifier::IdentityDID::new_unchecked("did:keri:test"),
subject: auths_verifier::DeviceDID::new_unchecked("did:key:zTest"),
device_public_key: auths_verifier::Ed25519PublicKey::from_bytes([0u8; 32]),
identity_signature: auths_verifier::core::Ed25519Signature::empty(),
device_signature: auths_verifier::core::Ed25519Signature::empty(),
Expand Down
Loading
Loading