diff --git a/crates/auths-cli/src/bin/sign.rs b/crates/auths-cli/src/bin/sign.rs index 437b6da1..ea0cf464 100644 --- a/crates/auths-cli/src/bin/sign.rs +++ b/crates/auths-cli/src/bin/sign.rs @@ -281,7 +281,7 @@ fn run_sign(args: &Args) -> Result<()> { mod tests { use super::*; use auths_core::crypto::ssh::construct_sshsig_signed_data; - use zeroize::Zeroizing; + use auths_crypto::Pkcs8Der; #[test] fn test_args_accepts_o_flag() { @@ -458,9 +458,9 @@ mod tests { let rng = SystemRandom::new(); let pkcs8_doc = Ed25519KeyPair::generate_pkcs8(&rng) .expect("ring must generate a valid PKCS#8 document"); - let pkcs8_bytes = Zeroizing::new(pkcs8_doc.as_ref().to_vec()); + let pkcs8 = Pkcs8Der::new(pkcs8_doc.as_ref()); - let result = extract_seed_from_pkcs8(&pkcs8_bytes); + let result = extract_seed_from_pkcs8(&pkcs8); assert!( result.is_ok(), "extract_seed_from_pkcs8 must succeed on a ring-generated key, got: {:?}", @@ -472,8 +472,7 @@ mod tests { let derived = Ed25519KeyPair::from_seed_unchecked(seed.as_bytes()) .expect("extracted seed must be valid"); - let original = - Ed25519KeyPair::from_pkcs8(pkcs8_bytes.as_ref()).expect("original key must parse"); + let original = Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()).expect("original key must parse"); assert_eq!( derived.public_key().as_ref(), original.public_key().as_ref(), @@ -485,7 +484,7 @@ mod tests { fn test_extract_seed_from_pkcs8_rejects_invalid_input() { use auths_core::crypto::ssh::extract_seed_from_pkcs8; - let bad_input = Zeroizing::new(vec![0u8; 50]); + let bad_input = Pkcs8Der::new(vec![0u8; 50]); let result = extract_seed_from_pkcs8(&bad_input); assert!(result.is_err(), "must reject non-PKCS#8 input"); } diff --git a/crates/auths-cli/src/cli.rs b/crates/auths-cli/src/cli.rs index 4a26d127..8ad93656 100644 --- a/crates/auths-cli/src/cli.rs +++ b/crates/auths-cli/src/cli.rs @@ -26,6 +26,7 @@ use crate::commands::sign::SignCommand; use crate::commands::status::StatusCommand; use crate::commands::trust::TrustCommand; use crate::commands::unified_verify::UnifiedVerifyCommand; +use crate::commands::whoami::WhoamiCommand; use crate::commands::witness::WitnessCommand; use crate::config::OutputFormat; @@ -44,7 +45,7 @@ fn cli_styles() -> Styles { #[command( name = "auths", about = "auths \u{2014} cryptographic identity for developers", - long_about = "Commands:\n init Set up your cryptographic identity and Git signing\n sign Sign a Git commit or artifact\n verify Verify a signed commit or attestation\n status Show identity and signing status\n\nMore commands (run with --help for details):\n auths device, auths id, auths key, auths policy, auths emergency, ...", + long_about = "auths \u{2014} cryptographic identity for developers\n\nCore commands:\n init Set up your cryptographic identity and Git signing\n sign Sign a Git commit or artifact\n verify Verify a signed commit or attestation\n status Show identity and signing status\n\nMore commands:\n id, device, key, approval, artifact, policy, git, trust, org,\n audit, agent, witness, scim, config, emergency\n\nRun `auths --help` for details on any command.", version, styles = cli_styles() )] @@ -97,24 +98,40 @@ pub enum RootCommand { Sign(SignCommand), Verify(UnifiedVerifyCommand), Status(StatusCommand), + Whoami(WhoamiCommand), Tutorial(LearnCommand), Doctor(DoctorCommand), Completions(CompletionsCommand), + #[command(hide = true)] Emergency(EmergencyCommand), + #[command(hide = true)] Id(IdCommand), + #[command(hide = true)] Device(DeviceCommand), + #[command(hide = true)] Key(KeyCommand), + #[command(hide = true)] Approval(ApprovalCommand), + #[command(hide = true)] Artifact(ArtifactCommand), + #[command(hide = true)] Policy(PolicyCommand), + #[command(hide = true)] Git(GitCommand), + #[command(hide = true)] Trust(TrustCommand), + #[command(hide = true)] Org(OrgCommand), + #[command(hide = true)] Audit(AuditCommand), + #[command(hide = true)] Agent(AgentCommand), + #[command(hide = true)] Witness(WitnessCommand), + #[command(hide = true)] Scim(ScimCommand), + #[command(hide = true)] Config(ConfigCommand), #[command(hide = true)] diff --git a/crates/auths-cli/src/commands/device/authorization.rs b/crates/auths-cli/src/commands/device/authorization.rs index 4d3adb1c..09ee7915 100644 --- a/crates/auths-cli/src/commands/device/authorization.rs +++ b/crates/auths-cli/src/commands/device/authorization.rs @@ -136,6 +136,9 @@ pub enum DeviceSubcommand { #[arg(long, help = "Optional note explaining the revocation.")] note: Option, + + #[arg(long, help = "Preview actions without making changes.")] + dry_run: bool, }, /// Resolve a device DID to its controller identity DID. @@ -276,7 +279,12 @@ pub fn handle_device( device_did, identity_key_alias, note, + dry_run, } => { + if dry_run { + return display_dry_run_revoke(&device_did, &identity_key_alias); + } + let ctx = build_auths_context( &repo_path, env_config, @@ -325,6 +333,38 @@ fn display_link_result( Ok(()) } +fn display_dry_run_revoke(device_did: &str, identity_key_alias: &str) -> Result<()> { + if is_json_mode() { + JsonResponse::success( + "device revoke", + &serde_json::json!({ + "dry_run": true, + "device_did": device_did, + "identity_key_alias": identity_key_alias, + "actions": [ + "Revoke device authorization", + "Create signed revocation attestation", + "Store revocation in Git repository" + ] + }), + ) + .print() + .map_err(|e| anyhow!("{e}")) + } else { + let out = crate::ux::format::Output::new(); + out.print_info("Dry run mode — no changes will be made"); + out.newline(); + out.println("Would perform the following actions:"); + out.println(&format!( + " 1. Revoke device authorization for {}", + device_did + )); + out.println(" 2. Create signed revocation attestation"); + out.println(" 3. Store revocation in Git repository"); + Ok(()) + } +} + fn display_revoke_result(device_did: &str, repo_path: &Path) -> Result<()> { let identity_storage = RegistryIdentityStorage::new(repo_path.to_path_buf()); let identity: ManagedIdentity = identity_storage diff --git a/crates/auths-cli/src/commands/device/pair/mod.rs b/crates/auths-cli/src/commands/device/pair/mod.rs index 0915ca9c..b7a6dde4 100644 --- a/crates/auths-cli/src/commands/device/pair/mod.rs +++ b/crates/auths-cli/src/commands/device/pair/mod.rs @@ -34,7 +34,7 @@ pub struct PairCommand { pub registry: Option, /// Don't display QR code (only show short code) - #[clap(long)] + #[clap(long, hide_short_help = true)] pub no_qr: bool, /// Custom timeout in seconds for the pairing session (default: 300 = 5 minutes) @@ -47,16 +47,21 @@ pub struct PairCommand { pub timeout: u64, /// Skip registry server (offline mode, for testing) - #[clap(long)] + #[clap(long, hide_short_help = true)] pub offline: bool, /// Capabilities to grant the paired device (comma-separated) - #[clap(long, value_delimiter = ',', default_value = "sign_commit")] + #[clap( + long, + value_delimiter = ',', + default_value = "sign_commit", + hide_short_help = true + )] pub capabilities: Vec, /// Disable mDNS advertisement/discovery in LAN mode #[cfg(feature = "lan-pairing")] - #[clap(long)] + #[clap(long, hide_short_help = true)] pub no_mdns: bool, } diff --git a/crates/auths-cli/src/commands/id/identity.rs b/crates/auths-cli/src/commands/id/identity.rs index 8797edda..50e82043 100644 --- a/crates/auths-cli/src/commands/id/identity.rs +++ b/crates/auths-cli/src/commands/id/identity.rs @@ -158,6 +158,10 @@ pub enum IdSubcommand { help = "New simple verification threshold count (e.g., 1 for 1-of-N)." )] witness_threshold: Option, + + /// Preview actions without making changes. + #[arg(long)] + dry_run: bool, }, /// Export an identity bundle for stateless CI/CD verification. @@ -196,6 +200,50 @@ pub enum IdSubcommand { Migrate(super::migrate::MigrateCommand), } +fn display_dry_run_rotate( + repo_path: &std::path::Path, + current_alias: Option<&str>, + next_alias: Option<&str>, +) -> Result<()> { + if is_json_mode() { + JsonResponse::success( + "id rotate", + &serde_json::json!({ + "dry_run": true, + "repo_path": repo_path.display().to_string(), + "current_key_alias": current_alias, + "next_key_alias": next_alias, + "actions": [ + "Generate new Ed25519 keypair", + "Create rotation event in KERI event log", + "Update key alias mappings", + "All devices will need to re-authorize" + ] + }), + ) + .print() + .map_err(|e| anyhow!("{e}")) + } else { + let out = crate::ux::format::Output::new(); + out.print_info("Dry run mode — no changes will be made"); + out.newline(); + out.println(&format!(" Repository: {:?}", repo_path)); + if let Some(alias) = current_alias { + out.println(&format!(" Current Key Alias: {}", alias)); + } + if let Some(alias) = next_alias { + out.println(&format!(" New Key Alias: {}", alias)); + } + out.newline(); + out.println("Would perform the following actions:"); + out.println(" 1. Generate new Ed25519 keypair"); + out.println(" 2. Create rotation event in KERI event log"); + out.println(" 3. Update key alias mappings"); + out.println(" 4. All devices will need to re-authorize"); + Ok(()) + } +} + // --- Command Handler --- /// Handles the `id` subcommand, accepting the specific subcommand details @@ -425,9 +473,18 @@ pub fn handle_id( add_witness, remove_witness, witness_threshold, + dry_run, } => { let identity_key_alias = alias.or(current_key_alias); + if dry_run { + return display_dry_run_rotate( + &repo_path, + identity_key_alias.as_deref(), + next_key_alias.as_deref(), + ); + } + println!("🔄 Rotating KERI identity keys..."); println!(" Using Repository: {:?}", repo_path); if let Some(ref a) = identity_key_alias { diff --git a/crates/auths-cli/src/commands/init.rs b/crates/auths-cli/src/commands/init.rs index db741390..1738e576 100644 --- a/crates/auths-cli/src/commands/init.rs +++ b/crates/auths-cli/src/commands/init.rs @@ -33,7 +33,7 @@ use crate::factories::storage::build_auths_context; use super::init_helpers::{ check_git_version, detect_ci_environment, get_auths_repo_path, offer_shell_completions, - select_agent_capabilities, short_did, write_allowed_signers, + select_agent_capabilities, write_allowed_signers, }; use crate::config::CliConfig; use crate::ux::format::Output; @@ -179,14 +179,8 @@ pub fn handle_init(cmd: InitCommand, ctx: &CliConfig) -> Result<()> { _ => unreachable!(), }; - out.print_success(&format!( - "Identity ready: {}", - short_did(&result.identity_did) - )); - out.print_success(&format!( - "Device linked: {}", - short_did(result.device_did.as_str()) - )); + out.print_success(&format!("Identity ready: {}", &result.identity_did)); + out.print_success(&format!("Device linked: {}", result.device_did.as_str())); out.newline(); // Post-execute: platform verification (interactive CLI concern) @@ -452,7 +446,7 @@ fn prompt_for_conflict_policy( if let Ok(existing) = identity_storage.load_identity() { out.println(&format!( " Found existing identity: {}", - out.info(&short_did(existing.controller_did.as_str())) + out.info(existing.controller_did.as_str()) )); if !interactive { @@ -627,10 +621,7 @@ fn display_developer_result( out.newline(); out.print_heading("You are on the Web of Trust!"); out.newline(); - out.println(&format!( - " Identity: {}", - out.info(&short_did(&result.identity_did)) - )); + out.println(&format!(" Identity: {}", out.info(&result.identity_did))); out.println(&format!(" Key alias: {}", out.info(&result.key_alias))); if let Some(registry) = registered { out.println(&format!(" Registry: {}", out.info(registry))); @@ -653,7 +644,7 @@ fn display_ci_result( result: &auths_sdk::result::CiIdentityResult, ci_vendor: Option<&str>, ) { - out.print_success(&format!("CI identity: {}", short_did(&result.identity_did))); + out.print_success(&format!("CI identity: {}", &result.identity_did)); out.newline(); out.print_heading("Add these to your CI secrets:"); @@ -676,10 +667,7 @@ fn display_ci_result( fn display_agent_result(out: &Output, result: &auths_sdk::result::AgentIdentityResult) { out.print_heading("Agent Setup Complete!"); out.newline(); - out.println(&format!( - " Identity: {}", - out.info(&short_did(&result.agent_did)) - )); + out.println(&format!(" Identity: {}", out.info(&result.agent_did))); let cap_display: Vec = result.capabilities.iter().map(|c| c.to_string()).collect(); out.println(&format!(" Capabilities: {}", cap_display.join(", "))); out.newline(); diff --git a/crates/auths-cli/src/commands/init_helpers.rs b/crates/auths-cli/src/commands/init_helpers.rs index 38a71c1d..14b7280d 100644 --- a/crates/auths-cli/src/commands/init_helpers.rs +++ b/crates/auths-cli/src/commands/init_helpers.rs @@ -17,14 +17,6 @@ pub(crate) fn get_auths_repo_path() -> Result { auths_core::paths::auths_home().map_err(|e| anyhow!(e)) } -pub(crate) fn short_did(did: &str) -> String { - if did.len() <= 24 { - did.to_string() - } else { - format!("{}...{}", &did[..16], &did[did.len() - 8..]) - } -} - pub(crate) fn check_git_version(out: &Output) -> Result<()> { let output = Command::new("git") .arg("--version") @@ -340,15 +332,6 @@ mod tests { assert_eq!(parse_git_version("git version 2.30").unwrap(), (2, 30, 0)); } - #[test] - fn test_short_did() { - assert_eq!(short_did("did:key:z123"), "did:key:z123"); - assert_eq!( - short_did("did:keri:EAbcdefghijklmnopqrstuvwxyz123456789"), - "did:keri:EAbcdef...23456789" - ); - } - #[test] fn test_min_git_version() { assert!(MIN_GIT_VERSION <= (2, 34, 0)); diff --git a/crates/auths-cli/src/commands/mod.rs b/crates/auths-cli/src/commands/mod.rs index 0d5bb01a..d5a4fb4b 100644 --- a/crates/auths-cli/src/commands/mod.rs +++ b/crates/auths-cli/src/commands/mod.rs @@ -31,4 +31,5 @@ pub mod unified_verify; pub mod utils; pub mod verify_commit; pub mod verify_helpers; +pub mod whoami; pub mod witness; diff --git a/crates/auths-cli/src/commands/org.rs b/crates/auths-cli/src/commands/org.rs index 00a0fec0..86951d70 100644 --- a/crates/auths-cli/src/commands/org.rs +++ b/crates/auths-cli/src/commands/org.rs @@ -155,6 +155,10 @@ pub enum OrgSubcommand { /// Alias of the signing key in keychain #[arg(long)] signer_alias: Option, + + /// Preview actions without making changes. + #[arg(long)] + dry_run: bool, }, /// List members of an organization @@ -797,6 +801,7 @@ pub fn handle_org( member_did: member, note, signer_alias, + dry_run, } => { println!("🛑 Revoking member from organization..."); println!(" Org: {}", org); @@ -865,6 +870,10 @@ pub fn handle_org( Some(_) => {} // Member exists and is active, proceed } + if dry_run { + return display_dry_run_revoke_member(&org, &member, invoker_did.as_ref()); + } + // Load signer key and verify passphrase let key_storage = get_platform_keychain()?; let (stored_did, _role, encrypted_key) = key_storage @@ -1036,6 +1045,44 @@ pub fn handle_org( } } +fn display_dry_run_revoke_member(org: &str, member: &str, invoker_did: &str) -> Result<()> { + use crate::ux::format::{JsonResponse, is_json_mode}; + + if is_json_mode() { + JsonResponse::success( + "org revoke-member", + &serde_json::json!({ + "dry_run": true, + "org": org, + "member_did": member, + "invoker_did": invoker_did, + "actions": [ + "Create signed revocation for member", + "Store revocation in Git repository", + "Member will lose all org capabilities" + ] + }), + ) + .print() + .map_err(|e| anyhow!("{e}")) + } else { + let out = crate::ux::format::Output::new(); + out.print_info("Dry run mode — no changes will be made"); + out.newline(); + out.println(&format!(" Org: {}", org)); + out.println(&format!(" Member: {}", member)); + out.newline(); + out.println("Would perform the following actions:"); + out.println(&format!( + " 1. Create signed revocation for member {}", + member + )); + out.println(" 2. Store revocation in Git repository"); + out.println(" 3. Member will lose all org capabilities"); + Ok(()) + } +} + use crate::commands::executable::ExecutableCommand; use crate::config::CliConfig; diff --git a/crates/auths-cli/src/commands/sign.rs b/crates/auths-cli/src/commands/sign.rs index 16680e6a..3e1360dd 100644 --- a/crates/auths-cli/src/commands/sign.rs +++ b/crates/auths-cli/src/commands/sign.rs @@ -7,6 +7,9 @@ use std::sync::Arc; use auths_core::config::EnvironmentConfig; use auths_core::signing::PassphraseProvider; +use auths_core::storage::keychain::KeyStorage; +use auths_id::storage::identity::IdentityStorage; +use auths_storage::git::RegistryIdentityStorage; use super::artifact::sign::handle_sign as handle_artifact_sign; @@ -75,7 +78,15 @@ fn sign_commit_range(range: &str) -> Result<()> { return Err(anyhow!("git commit --amend failed: {}", stderr.trim())); } } - println!("✔ Signed: {}", range); + if crate::ux::format::is_json_mode() { + crate::ux::format::JsonResponse::success( + "sign", + &serde_json::json!({ "target": range, "type": "commit" }), + ) + .print()?; + } else { + println!("✔ Signed: {}", range); + } Ok(()) } @@ -122,16 +133,15 @@ pub fn handle_sign_unified( ) -> Result<()> { match parse_sign_target(&cmd.target) { SignTarget::Artifact(path) => { - let device_key_alias = cmd.device_key_alias.as_deref().ok_or_else(|| { - anyhow!( - "artifact signing requires --device-key-alias\n\nRun: auths sign --device-key-alias " - ) - })?; + let device_key_alias = match cmd.device_key_alias.as_deref() { + Some(alias) => alias.to_string(), + None => auto_detect_device_key(repo_opt.as_deref(), env_config)?, + }; handle_artifact_sign( &path, cmd.sig_output, cmd.identity_key_alias.as_deref(), - device_key_alias, + &device_key_alias, cmd.expires_in_days, cmd.note, repo_opt, @@ -143,6 +153,44 @@ pub fn handle_sign_unified( } } +/// Auto-detect the device key alias when not explicitly provided. +/// +/// Loads the identity from the registry, then lists all key aliases associated +/// with that identity. If exactly one alias exists, it is returned. Otherwise, +/// an error with actionable guidance is returned. +fn auto_detect_device_key( + repo_opt: Option<&Path>, + env_config: &EnvironmentConfig, +) -> Result { + let repo_path = + auths_id::storage::layout::resolve_repo_path(repo_opt.map(|p| p.to_path_buf()))?; + let identity_storage = RegistryIdentityStorage::new(repo_path.clone()); + let identity = identity_storage + .load_identity() + .map_err(|_| anyhow!("No identity found. Run `auths init` to get started."))?; + + let keychain = auths_core::storage::keychain::get_platform_keychain_with_config(env_config) + .context("Failed to access keychain")?; + let aliases = keychain + .list_aliases_for_identity(&identity.controller_did) + .map_err(|e| anyhow!("Failed to list key aliases: {e}"))?; + + match aliases.len() { + 0 => Err(anyhow!( + "No device keys found for identity {}.\n\nRun `auths device link` to authorize a device.", + identity.controller_did + )), + 1 => Ok(aliases[0].as_str().to_string()), + _ => { + let alias_list: Vec<&str> = aliases.iter().map(|a| a.as_str()).collect(); + Err(anyhow!( + "Multiple device keys found. Specify with --device-key-alias.\n\nAvailable aliases: {}", + alias_list.join(", ") + )) + } + } +} + impl crate::commands::executable::ExecutableCommand for SignCommand { fn execute(&self, ctx: &crate::config::CliConfig) -> anyhow::Result<()> { handle_sign_unified( diff --git a/crates/auths-cli/src/commands/status.rs b/crates/auths-cli/src/commands/status.rs index b4b8eab2..274f9db0 100644 --- a/crates/auths-cli/src/commands/status.rs +++ b/crates/auths-cli/src/commands/status.rs @@ -107,8 +107,7 @@ fn print_status(report: &StatusReport) { // Identity if let Some(ref id) = report.identity { - let did_display = truncate_did(&id.controller_did, 40); - out.println(&format!("Identity: {}", out.info(&did_display))); + out.println(&format!("Identity: {}", out.info(&id.controller_did))); if let Some(ref alias) = id.alias { out.println(&format!("Alias: {}", alias)); } @@ -179,8 +178,7 @@ fn print_status(report: &StatusReport) { if device.revoked_at.is_some() { continue; } - let did_display = truncate_did(&device.device_did, 40); - out.println(&format!(" {}", out.dim(&did_display))); + out.println(&format!(" {}", out.dim(&device.device_did))); display_device_expiry(device.expires_at, &out, now); } } @@ -375,15 +373,6 @@ fn is_process_running(_pid: u32) -> bool { false } -/// Truncate a DID for display. -fn truncate_did(did: &str, max_len: usize) -> String { - if did.len() <= max_len { - did.to_string() - } else { - format!("{}...", &did[..max_len - 3]) - } -} - impl crate::commands::executable::ExecutableCommand for StatusCommand { fn execute(&self, ctx: &crate::config::CliConfig) -> anyhow::Result<()> { handle_status(self.clone(), ctx.repo_path.clone()) @@ -394,20 +383,6 @@ impl crate::commands::executable::ExecutableCommand for StatusCommand { mod tests { use super::*; - #[test] - fn test_truncate_did_short() { - let did = "did:key:z6Mk"; - assert_eq!(truncate_did(did, 20), did); - } - - #[test] - fn test_truncate_did_long() { - let did = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"; - let truncated = truncate_did(did, 24); - assert!(truncated.ends_with("...")); - assert_eq!(truncated.len(), 24); - } - #[test] fn test_get_auths_dir() { let dir = get_auths_dir().unwrap(); diff --git a/crates/auths-cli/src/commands/trust.rs b/crates/auths-cli/src/commands/trust.rs index bc46c665..1a7efdd9 100644 --- a/crates/auths-cli/src/commands/trust.rs +++ b/crates/auths-cli/src/commands/trust.rs @@ -151,8 +151,7 @@ fn handle_list(_cmd: TrustListCommand) -> Result<()> { TrustLevel::Manual => out.info("Manual"), TrustLevel::OrgPolicy => out.success("OrgPolicy"), }; - let did_short = truncate_did(&pin.did, 50); - out.println(&format!(" {} [{}]", did_short, level)); + out.println(&format!(" {} [{}]", pin.did, level)); } } } @@ -207,7 +206,7 @@ fn handle_pin(cmd: TrustPinCommand) -> Result<()> { out.println(&format!( "{} Pinned identity: {}", out.success("OK"), - truncate_did(&cmd.did, 50) + &cmd.did )); } @@ -237,7 +236,7 @@ fn handle_remove(cmd: TrustRemoveCommand) -> Result<()> { out.println(&format!( "{} Removed pin for: {}", out.success("OK"), - truncate_did(&cmd.did, 50) + &cmd.did )); } @@ -286,15 +285,6 @@ fn handle_show(cmd: TrustShowCommand) -> Result<()> { Ok(()) } -/// Truncate a DID for display. -fn truncate_did(did: &str, max_len: usize) -> String { - if did.len() <= max_len { - did.to_string() - } else { - format!("{}...", &did[..max_len - 3]) - } -} - use crate::commands::executable::ExecutableCommand; use crate::config::CliConfig; @@ -303,22 +293,3 @@ impl ExecutableCommand for TrustCommand { handle_trust(self.clone()) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_truncate_did_short() { - let did = "did:keri:E123"; - assert_eq!(truncate_did(did, 20), did); - } - - #[test] - fn test_truncate_did_long() { - let did = "did:keri:EXq5YqaL6L48pf0fu7IUhL0JRaU2_RxFP0AL43wYn148"; - let truncated = truncate_did(did, 24); - assert!(truncated.ends_with("...")); - assert_eq!(truncated.len(), 24); - } -} diff --git a/crates/auths-cli/src/commands/utils.rs b/crates/auths-cli/src/commands/utils.rs index 26aa86bd..8254e080 100644 --- a/crates/auths-cli/src/commands/utils.rs +++ b/crates/auths-cli/src/commands/utils.rs @@ -80,10 +80,16 @@ pub fn handle_util(cmd: UtilCommand) -> Result<()> { .try_into() .context("Failed to convert public key to fixed array")?; // Should not fail - // Derive the identity ID string let did = ed25519_pubkey_to_did_key(&pubkey_fixed); - // Print the result clearly - println!("✅ Identity ID: {}", did); + if crate::ux::format::is_json_mode() { + crate::ux::format::JsonResponse::success( + "derive-did", + &serde_json::json!({ "did": did }), + ) + .print()?; + } else { + println!("✅ Identity ID: {}", did); + } Ok(()) } @@ -92,7 +98,15 @@ pub fn handle_util(cmd: UtilCommand) -> Result<()> { .map_err(anyhow::Error::from) .context("Failed to parse OpenSSH public key")?; let did = ed25519_pubkey_to_did_key(&raw); - println!("{}", did); + if crate::ux::format::is_json_mode() { + crate::ux::format::JsonResponse::success( + "pubkey-to-did", + &serde_json::json!({ "did": did }), + ) + .print()?; + } else { + println!("{}", did); + } Ok(()) } diff --git a/crates/auths-cli/src/commands/whoami.rs b/crates/auths-cli/src/commands/whoami.rs new file mode 100644 index 00000000..7bf7911e --- /dev/null +++ b/crates/auths-cli/src/commands/whoami.rs @@ -0,0 +1,76 @@ +use anyhow::{Result, anyhow}; +use auths_id::storage::identity::IdentityStorage; +use auths_id::storage::layout; +use auths_storage::git::RegistryIdentityStorage; +use clap::Parser; +use serde::Serialize; + +use crate::config::CliConfig; +use crate::ux::format::{JsonResponse, Output, is_json_mode}; + +/// Show the current identity on this machine. +#[derive(Parser, Debug, Clone)] +#[command(name = "whoami", about = "Show the current identity on this machine")] +pub struct WhoamiCommand {} + +#[derive(Debug, Serialize)] +struct WhoamiResponse { + identity_did: String, + #[serde(skip_serializing_if = "Option::is_none")] + label: Option, +} + +pub fn handle_whoami(_cmd: WhoamiCommand, repo: Option) -> Result<()> { + let repo_path = layout::resolve_repo_path(repo).map_err(|e| anyhow!(e))?; + + if crate::factories::storage::open_git_repo(&repo_path).is_err() { + if is_json_mode() { + JsonResponse::<()>::error( + "whoami", + "No identity found. Run `auths init` to get started.", + ) + .print()?; + } else { + let out = Output::new(); + out.print_error("No identity found. Run `auths init` to get started."); + } + return Ok(()); + } + + let storage = RegistryIdentityStorage::new(&repo_path); + match storage.load_identity() { + Ok(identity) => { + let response = WhoamiResponse { + identity_did: identity.controller_did.to_string(), + label: None, + }; + + if is_json_mode() { + JsonResponse::success("whoami", &response).print()?; + } else { + let out = Output::new(); + out.println(&format!("Identity: {}", out.info(&response.identity_did))); + } + } + Err(_) => { + if is_json_mode() { + JsonResponse::<()>::error( + "whoami", + "No identity found. Run `auths init` to get started.", + ) + .print()?; + } else { + let out = Output::new(); + out.print_error("No identity found. Run `auths init` to get started."); + } + } + } + + Ok(()) +} + +impl crate::commands::executable::ExecutableCommand for WhoamiCommand { + fn execute(&self, ctx: &CliConfig) -> Result<()> { + handle_whoami(self.clone(), ctx.repo_path.clone()) + } +} diff --git a/crates/auths-cli/src/main.rs b/crates/auths-cli/src/main.rs index f057ccc7..7a0eaba3 100644 --- a/crates/auths-cli/src/main.rs +++ b/crates/auths-cli/src/main.rs @@ -40,6 +40,7 @@ fn run() -> Result<()> { RootCommand::Sign(cmd) => cmd.execute(&ctx), RootCommand::Verify(cmd) => cmd.execute(&ctx), RootCommand::Status(cmd) => cmd.execute(&ctx), + RootCommand::Whoami(cmd) => cmd.execute(&ctx), RootCommand::Tutorial(cmd) => cmd.execute(&ctx), RootCommand::Doctor(cmd) => cmd.execute(&ctx), RootCommand::Completions(cmd) => cmd.execute(&ctx), diff --git a/crates/auths-core/src/api/runtime.rs b/crates/auths-core/src/api/runtime.rs index 114d004b..bbc4edef 100644 --- a/crates/auths-core/src/api/runtime.rs +++ b/crates/auths-core/src/api/runtime.rs @@ -396,7 +396,8 @@ pub fn export_key_openssh_pem( let pkcs8_bytes = decrypt_keypair(&encrypted_pkcs8, passphrase)?; // 3. Extract seed via the consolidated SSH crypto module - let secure_seed = crate::crypto::ssh::extract_seed_from_pkcs8(&pkcs8_bytes).map_err(|e| { + let pkcs8 = auths_crypto::Pkcs8Der::new(&pkcs8_bytes[..]); + let secure_seed = crate::crypto::ssh::extract_seed_from_pkcs8(&pkcs8).map_err(|e| { AgentError::KeyDeserializationError(format!( "Failed to extract Ed25519 seed for alias '{}': {}", alias, e diff --git a/crates/auths-core/src/crypto/ssh/keys.rs b/crates/auths-core/src/crypto/ssh/keys.rs index 62e74ec9..48c8abb3 100644 --- a/crates/auths-core/src/crypto/ssh/keys.rs +++ b/crates/auths-core/src/crypto/ssh/keys.rs @@ -1,9 +1,8 @@ //! SSH key parsing, seed extraction, and public key derivation. pub use auths_crypto::SecureSeed; -use auths_crypto::{build_ed25519_pkcs8_v2, parse_ed25519_key_material}; +use auths_crypto::{Pkcs8Der, build_ed25519_pkcs8_v2, parse_ed25519_key_material}; use ssh_key::private::Ed25519Keypair as SshEd25519Keypair; -use zeroize::Zeroizing; use super::CryptoError; @@ -13,17 +12,15 @@ use super::CryptoError; /// in the SSH-specific `CryptoError`. /// /// Args: -/// * `pkcs8_bytes`: PKCS#8 encoded key material (v1 or v2). +/// * `pkcs8`: PKCS#8 encoded key material (v1 or v2). /// /// Usage: /// ```ignore -/// let seed = extract_seed_from_pkcs8(&Zeroizing::new(raw_bytes))?; +/// let seed = extract_seed_from_pkcs8(&pkcs8)?; /// let sshsig = create_sshsig(&seed, data, "git")?; /// ``` -pub fn extract_seed_from_pkcs8( - pkcs8_bytes: &Zeroizing>, -) -> Result { - auths_crypto::parse_ed25519_seed(pkcs8_bytes).map_err(CryptoError::from) +pub fn extract_seed_from_pkcs8(pkcs8: &Pkcs8Der) -> Result { + auths_crypto::parse_ed25519_seed(pkcs8.as_ref()).map_err(CryptoError::from) } /// Build a PKCS#8 v2 DER document from a seed, deriving the public key internally. @@ -35,16 +32,14 @@ pub fn extract_seed_from_pkcs8( /// ```ignore /// let pkcs8 = build_ed25519_pkcs8_v2_from_seed(&seed)?; /// ``` -pub fn build_ed25519_pkcs8_v2_from_seed( - seed: &SecureSeed, -) -> Result>, CryptoError> { +pub fn build_ed25519_pkcs8_v2_from_seed(seed: &SecureSeed) -> Result { let ssh_kp = SshEd25519Keypair::from_seed(seed.as_bytes()); let pubkey: [u8; 32] = ssh_kp.public.0; let pkcs8 = build_ed25519_pkcs8_v2(seed.as_bytes(), &pubkey); - Ok(Zeroizing::new(pkcs8)) + Ok(Pkcs8Der::new(pkcs8)) } -/// Extract the 32-byte Ed25519 public key from PKCS#8 key bytes. +/// Extract the 32-byte Ed25519 public key from key bytes. /// /// Parses key material to find the embedded public key (PKCS#8 v2), or /// derives it from the seed when only PKCS#8 v1 or raw bytes are provided. @@ -54,11 +49,9 @@ pub fn build_ed25519_pkcs8_v2_from_seed( /// /// Usage: /// ```ignore -/// let pubkey = extract_pubkey_from_key_bytes(&Zeroizing::new(raw_bytes))?; +/// let pubkey = extract_pubkey_from_key_bytes(pkcs8.as_ref())?; /// ``` -pub fn extract_pubkey_from_key_bytes( - key_bytes: &Zeroizing>, -) -> Result, CryptoError> { +pub fn extract_pubkey_from_key_bytes(key_bytes: &[u8]) -> Result, CryptoError> { let (seed, maybe_pubkey) = parse_ed25519_key_material(key_bytes).map_err(CryptoError::from)?; match maybe_pubkey { diff --git a/crates/auths-core/tests/cases/ssh_crypto.rs b/crates/auths-core/tests/cases/ssh_crypto.rs index 8bbb15db..f65a1957 100644 --- a/crates/auths-core/tests/cases/ssh_crypto.rs +++ b/crates/auths-core/tests/cases/ssh_crypto.rs @@ -3,7 +3,7 @@ use auths_core::crypto::ssh::{ encode_ssh_pubkey, encode_ssh_signature, extract_pubkey_from_key_bytes, extract_seed_from_pkcs8, }; -use zeroize::Zeroizing; +use auths_crypto::Pkcs8Der; #[test] fn test_secure_seed_zeroes_on_drop() { @@ -66,7 +66,7 @@ fn test_extract_pubkey_from_pkcs8_v2() { let seed = SecureSeed::new([5u8; 32]); let pkcs8 = build_ed25519_pkcs8_v2_from_seed(&seed).unwrap(); - let pubkey = extract_pubkey_from_key_bytes(&pkcs8).unwrap(); + let pubkey = extract_pubkey_from_key_bytes(pkcs8.as_ref()).unwrap(); assert_eq!(pubkey.len(), 32); } @@ -96,15 +96,15 @@ fn test_construct_sshsig_pem_format() { #[test] fn test_extract_seed_rejects_invalid_length() { - let bad = Zeroizing::new(vec![0u8; 50]); + let bad = Pkcs8Der::new(vec![0u8; 50]); assert!(extract_seed_from_pkcs8(&bad).is_err()); } #[test] -fn test_build_pkcs8_v2_returns_zeroizing() { +fn test_build_pkcs8_v2_returns_pkcs8der() { use auths_core::crypto::ssh::build_ed25519_pkcs8_v2_from_seed; let seed = SecureSeed::new([7u8; 32]); let pkcs8 = build_ed25519_pkcs8_v2_from_seed(&seed).unwrap(); - assert_eq!(pkcs8.len(), 85); + assert_eq!(pkcs8.as_ref().len(), 85); } diff --git a/crates/auths-sdk/src/keys.rs b/crates/auths-sdk/src/keys.rs index e0de661f..9b6a6997 100644 --- a/crates/auths-sdk/src/keys.rs +++ b/crates/auths-sdk/src/keys.rs @@ -80,10 +80,10 @@ pub fn import_seed( let secure_seed = SecureSeed::new(**seed); - let pkcs8_bytes = build_ed25519_pkcs8_v2_from_seed(&secure_seed) + let pkcs8 = build_ed25519_pkcs8_v2_from_seed(&secure_seed) .map_err(|e| KeyImportError::Pkcs8Generation(e.to_string()))?; - let encrypted = encrypt_keypair(&pkcs8_bytes, passphrase) + let encrypted = encrypt_keypair(pkcs8.as_ref(), passphrase) .map_err(|e| KeyImportError::Encryption(e.to_string()))?; keychain diff --git a/crates/auths-sdk/src/workflows/signing.rs b/crates/auths-sdk/src/workflows/signing.rs index f6917020..aec4e47f 100644 --- a/crates/auths-sdk/src/workflows/signing.rs +++ b/crates/auths-sdk/src/workflows/signing.rs @@ -8,13 +8,13 @@ use std::path::PathBuf; use std::sync::Arc; use chrono::{DateTime, Utc}; -use zeroize::Zeroizing; use auths_core::AgentError; use auths_core::crypto::signer::decrypt_keypair; use auths_core::crypto::ssh::{SecureSeed, extract_seed_from_pkcs8}; use auths_core::signing::PassphraseProvider; use auths_core::storage::keychain::{KeyAlias, KeyStorage}; +use auths_crypto::Pkcs8Der; use crate::ports::agent::{AgentSigningError, AgentSigningPort}; use crate::signing::{self, SigningError}; @@ -162,14 +162,14 @@ impl CommitSigningWorkflow { // Tier 2: auto-start agent + decrypt key + load into agent + direct sign let _ = ctx.agent_signing.ensure_running(); - let pkcs8_der = load_key_with_passphrase_retry(ctx, ¶ms)?; - let seed = extract_seed_from_pkcs8(&pkcs8_der) + let pkcs8 = load_key_with_passphrase_retry(ctx, ¶ms)?; + let seed = extract_seed_from_pkcs8(&pkcs8) .map_err(|e| SigningError::KeyDecryptionFailed(e.to_string()))?; // Best-effort: load identity into agent for future Tier 1 hits let _ = ctx .agent_signing - .add_identity(¶ms.namespace, &pkcs8_der); + .add_identity(¶ms.namespace, pkcs8.as_ref()); // Tier 3: direct sign direct_sign(¶ms, &seed, now) @@ -193,7 +193,7 @@ fn try_agent_sign( fn load_key_with_passphrase_retry( ctx: &CommitSigningContext, params: &CommitSigningParams, -) -> Result>, SigningError> { +) -> Result { let alias = KeyAlias::new_unchecked(¶ms.key_alias); let (_identity_did, _role, encrypted_data) = ctx .key_storage @@ -209,7 +209,7 @@ fn load_key_with_passphrase_retry( .map_err(|e| SigningError::KeyDecryptionFailed(e.to_string()))?; match decrypt_keypair(&encrypted_data, &passphrase) { - Ok(decrypted) => return Ok(decrypted), + Ok(decrypted) => return Ok(Pkcs8Der::new(&decrypted[..])), Err(AgentError::IncorrectPassphrase) => { if attempt < params.max_passphrase_attempts { ctx.passphrase_provider.on_incorrect_passphrase(&prompt); diff --git a/packages/auths-python/python/auths/_client.py b/packages/auths-python/python/auths/_client.py index 72bf07be..f0c193b2 100644 --- a/packages/auths-python/python/auths/_client.py +++ b/packages/auths-python/python/auths/_client.py @@ -18,7 +18,14 @@ verify_chain_with_witnesses as _verify_chain_with_witnesses, verify_device_authorization as _verify_device_authorization, ) -from auths._errors import CryptoError, NetworkError, StorageError, VerificationError +from auths._errors import ( + CryptoError, + IdentityError, + KeychainError, + NetworkError, + StorageError, + VerificationError, +) if TYPE_CHECKING: from auths._native import VerificationReport, VerificationResult @@ -48,6 +55,13 @@ "AUTHS_ORG_VERIFICATION_FAILED": ("invalid_signature", VerificationError), "AUTHS_ORG_ATTESTATION_EXPIRED": ("expired_attestation", VerificationError), "AUTHS_ORG_DID_RESOLUTION_FAILED": ("invalid_key", CryptoError), + "AUTHS_REGISTRY_ERROR": ("repo_not_found", StorageError), + "AUTHS_KEYCHAIN_ERROR": ("keychain_locked", KeychainError), + "AUTHS_IDENTITY_ERROR": ("identity_not_found", IdentityError), + "AUTHS_DEVICE_ERROR": ("unknown", IdentityError), + "AUTHS_ROTATION_ERROR": ("unknown", IdentityError), + "AUTHS_NETWORK_ERROR": ("server_error", NetworkError), + "AUTHS_VERIFICATION_FAILED": ("invalid_signature", VerificationError), } @@ -299,7 +313,7 @@ def sign_as_agent( Usage: agent = auths.identities.delegate_agent(identity.did, "bot", ["sign"]) - sig = auths.sign_as_agent(b"hello", key_alias=agent.key_alias) + sig = auths.sign_as_agent(b"hello", key_alias=agent._key_alias) """ from auths._native import sign_as_agent as _sign_as_agent @@ -328,7 +342,7 @@ def sign_action_as_agent( Usage: agent = auths.identities.delegate_agent(identity.did, "bot", ["deploy"]) - envelope = auths.sign_action_as_agent("deploy", payload, agent.key_alias, agent.did) + envelope = auths.sign_action_as_agent("deploy", payload, agent._key_alias, agent.did) """ from auths._native import sign_action_as_agent as _sign_action_as_agent diff --git a/packages/auths-python/python/auths/identity.py b/packages/auths-python/python/auths/identity.py index 3723fa4e..712a03d8 100644 --- a/packages/auths-python/python/auths/identity.py +++ b/packages/auths-python/python/auths/identity.py @@ -2,7 +2,7 @@ from __future__ import annotations -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import TYPE_CHECKING from auths._native import ( @@ -22,7 +22,7 @@ class Identity: """An Auths identity (represents a did:keri: identifier).""" did: str - key_alias: str + _key_alias: str = field(repr=False) label: str repo_path: str public_key: str @@ -33,7 +33,7 @@ class AgentIdentity: """Standalone agent identity (did:keri:). Created via identities.create_agent().""" did: str - key_alias: str + _key_alias: str = field(repr=False) attestation: str public_key: str @@ -43,7 +43,7 @@ class DelegatedAgent: """Agent delegated under a parent identity (did:key:). Created via identities.delegate_agent().""" did: str - key_alias: str + _key_alias: str = field(repr=False) attestation: str public_key: str @@ -79,7 +79,7 @@ def create( rp = repo_path or self._client.repo_path pp = passphrase or self._client._passphrase did, key_alias, public_key_hex = _create_identity(label, rp, pp) - return Identity(did=did, key_alias=key_alias, label=label, repo_path=rp, public_key=public_key_hex) + return Identity(did=did, _key_alias=key_alias, label=label, repo_path=rp, public_key=public_key_hex) def rotate( self, @@ -135,7 +135,7 @@ def create_agent( name, capabilities, self._client.repo_path, pp, ) return AgentIdentity( - did=bundle.agent_did, key_alias=bundle.key_alias, + did=bundle.agent_did, _key_alias=bundle.key_alias, attestation=bundle.attestation_json, public_key=bundle.public_key_hex, ) @@ -165,6 +165,6 @@ def delegate_agent( identity_did, ) return DelegatedAgent( - did=bundle.agent_did, key_alias=bundle.key_alias, + did=bundle.agent_did, _key_alias=bundle.key_alias, attestation=bundle.attestation_json, public_key=bundle.public_key_hex, ) diff --git a/packages/auths-python/src/artifact_publish.rs b/packages/auths-python/src/artifact_publish.rs index 82cae85e..e72dec08 100644 --- a/packages/auths-python/src/artifact_publish.rs +++ b/packages/auths-python/src/artifact_publish.rs @@ -100,7 +100,7 @@ pub fn publish_artifact( signer_did: String, } let resp: PublishResponse = response.json().await.map_err(|e| { - PyRuntimeError::new_err(format!("invalid registry response: {e}")) + PyRuntimeError::new_err(format!("[AUTHS_NETWORK_ERROR] Invalid registry response: {e}")) })?; Ok(PyArtifactPublishResult { attestation_rid: resp.attestation_rid, @@ -109,18 +109,18 @@ pub fn publish_artifact( }) } 409 => Err(PyRuntimeError::new_err( - "duplicate_attestation: artifact attestation already published (duplicate RID)", + "[AUTHS_REGISTRY_ERROR] Duplicate attestation: artifact attestation already published (duplicate RID)", )), 422 => { let body = response.text().await.unwrap_or_default(); Err(PyRuntimeError::new_err(format!( - "verification_failed: {body}" + "[AUTHS_VERIFICATION_FAILED] Verification failed: {body}" ))) } status => { let body = response.text().await.unwrap_or_default(); Err(PyRuntimeError::new_err(format!( - "registry_error ({status}): {body}" + "[AUTHS_NETWORK_ERROR] Registry error ({status}): {body}" ))) } } diff --git a/packages/auths-python/src/artifact_sign.rs b/packages/auths-python/src/artifact_sign.rs index cbb7f6bf..8c7a71fa 100644 --- a/packages/auths-python/src/artifact_sign.rs +++ b/packages/auths-python/src/artifact_sign.rs @@ -140,18 +140,18 @@ fn build_context_and_sign( let config = RegistryConfig::single_tenant(&repo); let backend = Arc::new( GitRegistryBackend::open_existing(config) - .map_err(|e| PyRuntimeError::new_err(format!("Failed to open registry: {e}")))?, + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_REGISTRY_ERROR] Failed to open registry: {e}")))?, ); let keychain = get_platform_keychain_with_config(&env_config) - .map_err(|e| PyRuntimeError::new_err(format!("Keychain error: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_KEYCHAIN_ERROR] Keychain error: {e}")))?; let keychain = Arc::from(keychain); let identity_storage = Arc::new(RegistryIdentityStorage::new(&repo)); let attestation_storage = Arc::new(RegistryAttestationStorage::new(&repo)); let alias = KeyAlias::new(identity_key_alias) - .map_err(|e| PyRuntimeError::new_err(format!("Invalid key alias: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_KEY_NOT_FOUND] Invalid key alias: {e}")))?; let ctx = AuthsContext::builder() .registry(backend) @@ -177,7 +177,7 @@ fn build_context_and_sign( }; let result = sdk_sign_artifact(params, &ctx) - .map_err(|e| PyRuntimeError::new_err(format!("Artifact signing failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_SIGNING_FAILED] Artifact signing failed: {e}")))?; Ok(PyArtifactResult { attestation_json: result.attestation_json, diff --git a/packages/auths-python/src/attestation_query.rs b/packages/auths-python/src/attestation_query.rs index 3507c1ce..2f614cc0 100644 --- a/packages/auths-python/src/attestation_query.rs +++ b/packages/auths-python/src/attestation_query.rs @@ -77,7 +77,7 @@ fn open_attestation_storage(repo_path: &str) -> PyResult, repo_path: &str) -> PyResult, repo_path: &str) -> PyResul let storage = RegistryAttestationStorage::new(&repo); let entries = generate_allowed_signers(&storage).map_err( |e: auths_sdk::workflows::git_integration::GitIntegrationError| { - PyRuntimeError::new_err(e.to_string()) + PyRuntimeError::new_err(format!("[AUTHS_REGISTRY_ERROR] {e}")) }, )?; Ok(format_allowed_signers_file(&entries)) diff --git a/packages/auths-python/src/identity.rs b/packages/auths-python/src/identity.rs index 9e5ab7f9..24141b41 100644 --- a/packages/auths-python/src/identity.rs +++ b/packages/auths-python/src/identity.rs @@ -55,18 +55,18 @@ pub(crate) fn resolve_key_alias( let did = IdentityDID::new_unchecked(identity_ref.to_string()); let aliases = keychain .list_aliases_for_identity_with_role(&did, KeyRole::Primary) - .map_err(|e| PyRuntimeError::new_err(format!("Key lookup failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_KEY_NOT_FOUND] Key lookup failed: {e}")))?; aliases .into_iter() .next() .ok_or_else(|| { PyRuntimeError::new_err(format!( - "No primary key found for identity '{identity_ref}'" + "[AUTHS_KEY_NOT_FOUND] No primary key found for identity '{identity_ref}'" )) }) } else { KeyAlias::new(identity_ref) - .map_err(|e| PyRuntimeError::new_err(format!("Invalid key alias: {e}"))) + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_KEY_NOT_FOUND] Invalid key alias: {e}"))) } } @@ -135,7 +135,7 @@ pub fn create_identity( let passphrase_str = resolve_passphrase(passphrase); let env_config = make_keychain_config(&passphrase_str); let alias = KeyAlias::new(key_alias) - .map_err(|e| PyRuntimeError::new_err(format!("Invalid key alias: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_KEY_NOT_FOUND] Invalid key alias: {e}")))?; let provider = PrefilledPassphraseProvider::new(&passphrase_str); let repo = PathBuf::from(shellexpand::tilde(repo_path).as_ref()); @@ -143,16 +143,16 @@ pub fn create_identity( let backend = GitRegistryBackend::from_config_unchecked(config); backend .init_if_needed() - .map_err(|e| PyRuntimeError::new_err(format!("Failed to initialize registry: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_REGISTRY_ERROR] Failed to initialize registry: {e}")))?; let backend = Arc::new(backend); let keychain = get_platform_keychain_with_config(&env_config) - .map_err(|e| PyRuntimeError::new_err(format!("Keychain error: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_KEYCHAIN_ERROR] Keychain error: {e}")))?; py.allow_threads(|| { let (identity_did, result_alias) = initialize_registry_identity(backend, &alias, &provider, keychain.as_ref(), None) - .map_err(|e| PyRuntimeError::new_err(format!("Identity creation failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_IDENTITY_ERROR] Identity creation failed: {e}")))?; // Extract public key so callers can verify signatures immediately let pub_bytes = auths_core::storage::keychain::extract_public_key_bytes( @@ -160,7 +160,7 @@ pub fn create_identity( &result_alias, &provider, ) - .map_err(|e| PyRuntimeError::new_err(format!("Public key extraction failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_CRYPTO_ERROR] Public key extraction failed: {e}")))?; Ok((identity_did.to_string(), result_alias.to_string(), hex::encode(pub_bytes))) }) @@ -197,18 +197,18 @@ pub fn create_agent_identity( let backend = GitRegistryBackend::from_config_unchecked(config); backend .init_if_needed() - .map_err(|e| PyRuntimeError::new_err(format!("Failed to initialize registry: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_REGISTRY_ERROR] Failed to initialize registry: {e}")))?; let backend = Arc::new(backend); let keychain = get_platform_keychain_with_config(&env_config) - .map_err(|e| PyRuntimeError::new_err(format!("Keychain error: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_KEYCHAIN_ERROR] Keychain error: {e}")))?; // Validate capabilities let _parsed_caps: Vec = capabilities .iter() .map(|c| { Capability::parse(c) - .map_err(|e| PyRuntimeError::new_err(format!("Invalid capability '{c}': {e}"))) + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_INVALID_INPUT] Invalid capability '{c}': {e}"))) }) .collect::>>()?; @@ -218,7 +218,7 @@ pub fn create_agent_identity( let (identity_did, result_alias) = initialize_registry_identity(backend.clone(), &alias, &provider, keychain.as_ref(), None) .map_err(|e| { - PyRuntimeError::new_err(format!("Agent identity creation failed: {e}")) + PyRuntimeError::new_err(format!("[AUTHS_IDENTITY_ERROR] Agent identity creation failed: {e}")) })?; // Extract public key @@ -227,13 +227,13 @@ pub fn create_agent_identity( &result_alias, &provider, ) - .map_err(|e| PyRuntimeError::new_err(format!("Public key extraction failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_CRYPTO_ERROR] Public key extraction failed: {e}")))?; // Build a self-attestation for the standalone agent let attestation_json = { let device_did = DeviceDID::from_ed25519( pub_bytes.as_slice().try_into().map_err(|_| { - PyRuntimeError::new_err("Invalid public key length") + PyRuntimeError::new_err("[AUTHS_CRYPTO_ERROR] Invalid public key length") })?, ); let att = serde_json::json!({ @@ -247,7 +247,7 @@ pub fn create_agent_identity( "note": format!("Agent: {}", alias), }); serde_json::to_string(&att) - .map_err(|e| PyRuntimeError::new_err(format!("Serialization failed: {e}")))? + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_SERIALIZATION_ERROR] Serialization failed: {e}")))? }; Ok(AgentIdentityBundle { @@ -298,11 +298,11 @@ pub fn delegate_agent( let config = RegistryConfig::single_tenant(&repo); let backend = Arc::new( GitRegistryBackend::open_existing(config) - .map_err(|e| PyRuntimeError::new_err(format!("Failed to open registry: {e}")))?, + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_REGISTRY_ERROR] Failed to open registry: {e}")))?, ); let keychain = get_platform_keychain_with_config(&env_config) - .map_err(|e| PyRuntimeError::new_err(format!("Keychain error: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_KEYCHAIN_ERROR] Keychain error: {e}")))?; // Resolve parent identity key alias let parent_alias = if let Some(ref did) = identity_did { @@ -310,44 +310,44 @@ pub fn delegate_agent( } else { let aliases = keychain .list_aliases() - .map_err(|e| PyRuntimeError::new_err(format!("Keychain error: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_KEYCHAIN_ERROR] Keychain error: {e}")))?; aliases .into_iter() .find(|a| !a.as_str().contains("--next-")) - .ok_or_else(|| PyRuntimeError::new_err("No identity key found in keychain"))? + .ok_or_else(|| PyRuntimeError::new_err("[AUTHS_KEY_NOT_FOUND] No identity key found in keychain"))? }; // Generate a new Ed25519 keypair for the agent let agent_alias = KeyAlias::new_unchecked(format!("{}-agent", agent_name)); let rng = SystemRandom::new(); let pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng) - .map_err(|e| PyRuntimeError::new_err(format!("Key generation failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_CRYPTO_ERROR] Key generation failed: {e}")))?; let keypair = Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()) - .map_err(|e| PyRuntimeError::new_err(format!("Key parsing failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_CRYPTO_ERROR] Key parsing failed: {e}")))?; let agent_pubkey = keypair.public_key().as_ref().to_vec(); // Get parent identity DID for key storage association let (parent_did, _, _) = keychain .load_key(&parent_alias) - .map_err(|e| PyRuntimeError::new_err(format!("Key load failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_KEY_NOT_FOUND] Key load failed: {e}")))?; // Encrypt and store the agent key let seed = extract_seed_bytes(pkcs8.as_ref()) - .map_err(|e| PyRuntimeError::new_err(format!("Seed extraction failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_CRYPTO_ERROR] Seed extraction failed: {e}")))?; let seed_pkcs8 = encode_seed_as_pkcs8(seed) - .map_err(|e| PyRuntimeError::new_err(format!("PKCS8 encoding failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_CRYPTO_ERROR] PKCS8 encoding failed: {e}")))?; let encrypted = encrypt_keypair(&seed_pkcs8, &passphrase_str) - .map_err(|e| PyRuntimeError::new_err(format!("Key encryption failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_CRYPTO_ERROR] Key encryption failed: {e}")))?; keychain .store_key(&agent_alias, &parent_did, KeyRole::DelegatedAgent, &encrypted) - .map_err(|e| PyRuntimeError::new_err(format!("Key storage failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_KEYCHAIN_ERROR] Key storage failed: {e}")))?; // Parse capabilities let parsed_caps: Vec = capabilities .iter() .map(|c| { Capability::parse(c) - .map_err(|e| PyRuntimeError::new_err(format!("Invalid capability '{c}': {e}"))) + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_INVALID_INPUT] Invalid capability '{c}': {e}"))) }) .collect::>>()?; @@ -377,19 +377,19 @@ pub fn delegate_agent( py.allow_threads(|| { let result = link_device(link_config, &ctx, clock.as_ref()) - .map_err(|e| PyRuntimeError::new_err(format!("Agent provisioning failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_IDENTITY_ERROR] Agent provisioning failed: {e}")))?; let device_did = DeviceDID(result.device_did.to_string()); let attestations = attestation_storage .load_attestations_for_device(&device_did) - .map_err(|e| PyRuntimeError::new_err(format!("Failed to load attestation: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_REGISTRY_ERROR] Failed to load attestation: {e}")))?; let attestation = attestations .last() - .ok_or_else(|| PyRuntimeError::new_err("No attestation found after provisioning"))?; + .ok_or_else(|| PyRuntimeError::new_err("[AUTHS_REGISTRY_ERROR] No attestation found after provisioning"))?; let attestation_json = serde_json::to_string(attestation) - .map_err(|e| PyRuntimeError::new_err(format!("Serialization failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_SERIALIZATION_ERROR] Serialization failed: {e}")))?; Ok(DelegatedAgentBundle { agent_did: result.device_did.to_string(), @@ -433,11 +433,11 @@ pub fn link_device_to_identity( let config = RegistryConfig::single_tenant(&repo); let backend = Arc::new( GitRegistryBackend::open_existing(config) - .map_err(|e| PyRuntimeError::new_err(format!("Failed to open registry: {e}")))?, + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_REGISTRY_ERROR] Failed to open registry: {e}")))?, ); let keychain = get_platform_keychain_with_config(&env_config) - .map_err(|e| PyRuntimeError::new_err(format!("Keychain error: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_KEYCHAIN_ERROR] Keychain error: {e}")))?; let alias = resolve_key_alias(identity_key_alias, keychain.as_ref())?; @@ -449,7 +449,7 @@ pub fn link_device_to_identity( .iter() .map(|c| { Capability::parse(c) - .map_err(|e| PyRuntimeError::new_err(format!("Invalid capability '{c}': {e}"))) + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_INVALID_INPUT] Invalid capability '{c}': {e}"))) }) .collect::>>()?; @@ -475,7 +475,7 @@ pub fn link_device_to_identity( py.allow_threads(|| { let result = link_device(link_config, &ctx, clock.as_ref()) - .map_err(|e| PyRuntimeError::new_err(format!("Device linking failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_DEVICE_ERROR] Device linking failed: {e}")))?; Ok(( result.device_did.to_string(), result.attestation_id.to_string(), @@ -515,11 +515,11 @@ pub fn revoke_device_from_identity( let config = RegistryConfig::single_tenant(&repo); let backend = Arc::new( GitRegistryBackend::open_existing(config) - .map_err(|e| PyRuntimeError::new_err(format!("Failed to open registry: {e}")))?, + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_REGISTRY_ERROR] Failed to open registry: {e}")))?, ); let keychain = get_platform_keychain_with_config(&env_config) - .map_err(|e| PyRuntimeError::new_err(format!("Keychain error: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_KEYCHAIN_ERROR] Keychain error: {e}")))?; let alias = resolve_key_alias(identity_key_alias, keychain.as_ref())?; @@ -539,7 +539,7 @@ pub fn revoke_device_from_identity( py.allow_threads(|| { revoke_device(device_did, &alias, &ctx, note, clock.as_ref()) - .map_err(|e| PyRuntimeError::new_err(format!("Device revocation failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_DEVICE_ERROR] Device revocation failed: {e}")))?; Ok(()) }) } diff --git a/packages/auths-python/src/identity_sign.rs b/packages/auths-python/src/identity_sign.rs index 0ec89d08..a3f14f0f 100644 --- a/packages/auths-python/src/identity_sign.rs +++ b/packages/auths-python/src/identity_sign.rs @@ -26,7 +26,7 @@ fn make_signer( }; let keychain = get_platform_keychain_with_config(&env_config) - .map_err(|e| PyRuntimeError::new_err(format!("Keychain error: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_KEYCHAIN_ERROR] Keychain error: {e}")))?; let signer = StorageSigner::new(keychain); let provider = PrefilledPassphraseProvider::new(&passphrase_str); @@ -62,7 +62,7 @@ pub fn sign_as_identity( py.allow_threads(move || { let sig_bytes = signer .sign_for_identity(&did, &provider, &msg) - .map_err(|e| PyRuntimeError::new_err(format!("Signing failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_SIGNING_FAILED] Signing failed: {e}")))?; Ok(hex::encode(sig_bytes)) }) } @@ -115,7 +115,7 @@ pub fn sign_action_as_identity( }); let canonical = json_canon::to_string(&signing_data) - .map_err(|e| PyRuntimeError::new_err(format!("Canonicalization failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_SERIALIZATION_ERROR] Canonicalization failed: {e}")))?; let (signer, provider) = make_signer(passphrase)?; let did = IdentityDID::new(identity_did); @@ -126,7 +126,7 @@ pub fn sign_action_as_identity( let sig_hex = py.allow_threads(move || { let sig_bytes = signer .sign_for_identity(&did, &provider, canonical.as_bytes()) - .map_err(|e| PyRuntimeError::new_err(format!("Signing failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_SIGNING_FAILED] Signing failed: {e}")))?; Ok::(hex::encode(sig_bytes)) })?; @@ -140,7 +140,7 @@ pub fn sign_action_as_identity( }); serde_json::to_string(&envelope) - .map_err(|e| PyRuntimeError::new_err(format!("Failed to serialize envelope: {e}"))) + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_SERIALIZATION_ERROR] Failed to serialize envelope: {e}"))) } /// Retrieve the Ed25519 public key (hex) for an identity DID. @@ -165,15 +165,15 @@ pub fn get_identity_public_key( py.allow_threads(move || { let aliases = signer.inner().list_aliases_for_identity(&did) - .map_err(|e| PyRuntimeError::new_err(format!("Key lookup failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_KEY_NOT_FOUND] Key lookup failed: {e}")))?; let alias = aliases.first() - .ok_or_else(|| PyRuntimeError::new_err(format!("No key found for identity '{identity_did}'")))?; + .ok_or_else(|| PyRuntimeError::new_err(format!("[AUTHS_KEY_NOT_FOUND] No key found for identity '{identity_did}'")))?; let pub_bytes = auths_core::storage::keychain::extract_public_key_bytes( signer.inner().as_ref(), alias, &provider, ) - .map_err(|e| PyRuntimeError::new_err(format!("Public key extraction failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_CRYPTO_ERROR] Public key extraction failed: {e}")))?; Ok(hex::encode(pub_bytes)) }) } @@ -202,13 +202,13 @@ pub fn sign_as_agent( ) -> PyResult { let (signer, provider) = make_signer(passphrase)?; let alias = KeyAlias::new(key_alias) - .map_err(|e| PyRuntimeError::new_err(format!("Invalid key alias: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_KEY_NOT_FOUND] Invalid key alias: {e}")))?; let msg = message.to_vec(); py.allow_threads(move || { let sig_bytes = signer .sign_with_alias(&alias, &provider, &msg) - .map_err(|e| PyRuntimeError::new_err(format!("Signing failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_SIGNING_FAILED] Signing failed: {e}")))?; Ok(hex::encode(sig_bytes)) }) } @@ -259,11 +259,11 @@ pub fn sign_action_as_agent( }); let canonical = json_canon::to_string(&signing_data) - .map_err(|e| PyRuntimeError::new_err(format!("Canonicalization failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_SERIALIZATION_ERROR] Canonicalization failed: {e}")))?; let (signer, provider) = make_signer(passphrase)?; let alias = KeyAlias::new(key_alias) - .map_err(|e| PyRuntimeError::new_err(format!("Invalid key alias: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_KEY_NOT_FOUND] Invalid key alias: {e}")))?; let action_type_owned = action_type.to_string(); let agent_did_owned = agent_did.to_string(); @@ -271,7 +271,7 @@ pub fn sign_action_as_agent( let sig_hex = py.allow_threads(move || { let sig_bytes = signer .sign_with_alias(&alias, &provider, canonical.as_bytes()) - .map_err(|e| PyRuntimeError::new_err(format!("Signing failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_SIGNING_FAILED] Signing failed: {e}")))?; Ok::(hex::encode(sig_bytes)) })?; @@ -285,5 +285,5 @@ pub fn sign_action_as_agent( }); serde_json::to_string(&envelope) - .map_err(|e| PyRuntimeError::new_err(format!("Failed to serialize envelope: {e}"))) + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_SERIALIZATION_ERROR] Failed to serialize envelope: {e}"))) } diff --git a/packages/auths-python/src/rotation.rs b/packages/auths-python/src/rotation.rs index 63801340..78f65fed 100644 --- a/packages/auths-python/src/rotation.rs +++ b/packages/auths-python/src/rotation.rs @@ -70,11 +70,11 @@ pub fn rotate_identity_ffi( let config = RegistryConfig::single_tenant(&repo); let backend = Arc::new( GitRegistryBackend::open_existing(config) - .map_err(|e| PyRuntimeError::new_err(format!("Failed to open registry: {e}")))?, + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_REGISTRY_ERROR] Failed to open registry: {e}")))?, ); let keychain = get_platform_keychain_with_config(&env_config) - .map_err(|e| PyRuntimeError::new_err(format!("Keychain error: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_KEYCHAIN_ERROR] Keychain error: {e}")))?; let keychain: Arc = Arc::from(keychain); let identity_storage = Arc::new(RegistryIdentityStorage::new(&repo)); @@ -97,7 +97,7 @@ pub fn rotate_identity_ffi( let next_alias = next_key_alias .map(|a| { auths_core::storage::keychain::KeyAlias::new(a) - .map_err(|e| PyRuntimeError::new_err(format!("Invalid next key alias: {e}"))) + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_KEY_NOT_FOUND] Invalid next key alias: {e}"))) }) .transpose()?; @@ -109,7 +109,7 @@ pub fn rotate_identity_ffi( py.allow_threads(|| { let result = rotate_identity(rotation_config, &ctx, clock.as_ref()) - .map_err(|e| PyRuntimeError::new_err(format!("Key rotation failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_ROTATION_ERROR] Key rotation failed: {e}")))?; Ok(PyIdentityRotationResult { controller_did: result.controller_did.to_string(), diff --git a/packages/auths-python/src/sign.rs b/packages/auths-python/src/sign.rs index 73b13f53..180dee71 100644 --- a/packages/auths-python/src/sign.rs +++ b/packages/auths-python/src/sign.rs @@ -27,7 +27,7 @@ pub fn sign_bytes(private_key_hex: &str, message: &[u8]) -> PyResult { } let keypair = ring::signature::Ed25519KeyPair::from_seed_unchecked(&seed) - .map_err(|e| PyRuntimeError::new_err(format!("Failed to create keypair: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_CRYPTO_ERROR] Failed to create keypair: {e}")))?; let sig = keypair.sign(message); Ok(hex::encode(sig.as_ref())) @@ -84,10 +84,10 @@ pub fn sign_action( }); let canonical = json_canon::to_string(&signing_data) - .map_err(|e| PyRuntimeError::new_err(format!("Canonicalization failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_SERIALIZATION_ERROR] Canonicalization failed: {e}")))?; let keypair = ring::signature::Ed25519KeyPair::from_seed_unchecked(&seed) - .map_err(|e| PyRuntimeError::new_err(format!("Failed to create keypair: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_CRYPTO_ERROR] Failed to create keypair: {e}")))?; let sig = keypair.sign(canonical.as_bytes()); let sig_hex = hex::encode(sig.as_ref()); @@ -102,7 +102,7 @@ pub fn sign_action( }); serde_json::to_string(&envelope) - .map_err(|e| PyRuntimeError::new_err(format!("Failed to serialize envelope: {e}"))) + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_SERIALIZATION_ERROR] Failed to serialize envelope: {e}"))) } /// Verify an action envelope's Ed25519 signature. @@ -170,7 +170,7 @@ pub fn verify_action_envelope( }); let canonical = json_canon::to_string(&signing_data) - .map_err(|e| PyRuntimeError::new_err(format!("Canonicalization failed: {e}")))?; + .map_err(|e| PyRuntimeError::new_err(format!("[AUTHS_SERIALIZATION_ERROR] Canonicalization failed: {e}")))?; let key = ring::signature::UnparsedPublicKey::new(&ring::signature::ED25519, &pk_bytes); match key.verify(canonical.as_bytes(), &sig_bytes) { diff --git a/packages/auths-python/tests/test_identity.py b/packages/auths-python/tests/test_identity.py index 8eb86990..aed89d80 100644 --- a/packages/auths-python/tests/test_identity.py +++ b/packages/auths-python/tests/test_identity.py @@ -29,7 +29,7 @@ def test_delegate_agent(auths): ) assert isinstance(agent, DelegatedAgent) assert agent.did.startswith("did:key:") - assert agent.key_alias == "ci-bot-agent" + assert agent._key_alias == "ci-bot-agent" assert agent.attestation @@ -38,7 +38,7 @@ def test_create_agent_identity(auths): agent = auths.identities.create_agent(name="standalone-bot", capabilities=["sign"]) assert isinstance(agent, AgentIdentity) assert agent.did.startswith("did:keri:") - assert agent.key_alias == "standalone-bot-agent" + assert agent._key_alias == "standalone-bot-agent" def test_device_lifecycle(auths):