Skip to main content

tap_agent/
cli.rs

1//! CLI tool for managing DIDs and keys
2//!
3//! This module provides command-line utilities for creating and managing
4//! Decentralized Identifiers (DIDs) and associated cryptographic keys.
5//!
6//! This module is only available when the `native` feature is enabled.
7#![cfg(feature = "native")]
8
9use crate::did::{
10    DIDGenerationOptions, DIDKeyGenerator, GeneratedKey, KeyType, MultiResolver, SyncDIDResolver,
11    VerificationMaterial,
12};
13use crate::error::{Error, Result};
14use crate::message::SecurityMode;
15use crate::message_packing::{PackOptions, Packable, Unpackable};
16use crate::storage::{KeyStorage, StoredKey};
17use base64::Engine;
18use clap::{Parser, Subcommand};
19use std::fs;
20use std::path::PathBuf;
21use std::sync::Arc;
22use tap_msg::didcomm::PlainMessage;
23
24/// TAP Agent CLI Tool for DID and Key Management
25#[derive(Parser, Debug)]
26#[command(name = "tap-agent-cli")]
27#[command(about = "CLI tool for managing DIDs and keys for TAP protocol", long_about = None)]
28pub struct Cli {
29    #[command(subcommand)]
30    pub command: Commands,
31}
32
33/// Available CLI commands
34#[derive(Subcommand, Debug)]
35pub enum Commands {
36    /// Generate a new DID
37    #[command(name = "generate")]
38    Generate {
39        /// The DID method to use (key or web)
40        #[arg(short, long, default_value = "key")]
41        method: String,
42
43        /// The key type to use
44        #[arg(short = 't', long, default_value = "ed25519")]
45        key_type: String,
46
47        /// Domain for did:web (required if method is 'web')
48        #[arg(short, long)]
49        domain: Option<String>,
50
51        /// Output file path for the DID document
52        #[arg(short, long)]
53        output: Option<PathBuf>,
54
55        /// Output file for private key (if not specified, key is shown only in console)
56        #[arg(short = 'k', long)]
57        key_output: Option<PathBuf>,
58
59        /// Save key to default location (~/.tap/keys.json)
60        #[arg(short = 's', long)]
61        save: bool,
62
63        /// Set as default key
64        #[arg(long)]
65        default: bool,
66
67        /// Label for the key (defaults to agent-{n})
68        #[arg(short = 'l', long)]
69        label: Option<String>,
70    },
71
72    /// Lookup and resolve a DID to its DID Document
73    #[command(name = "lookup")]
74    Lookup {
75        /// The DID to resolve
76        #[arg(required = true)]
77        did: String,
78
79        /// Output file path for the resolved DID document
80        #[arg(short, long)]
81        output: Option<PathBuf>,
82    },
83
84    /// List all stored keys
85    #[command(name = "keys", about = "List, view, and manage stored keys")]
86    Keys {
87        #[command(subcommand)]
88        subcommand: Option<KeysCommands>,
89    },
90
91    /// Import an existing key into storage
92    #[command(name = "import", about = "Import an existing key into storage")]
93    Import {
94        /// The JSON file containing the key to import
95        #[arg(required = true)]
96        key_file: PathBuf,
97
98        /// Set as default key
99        #[arg(long)]
100        default: bool,
101
102        /// Label for the imported key (defaults to agent-{n})
103        #[arg(short = 'l', long)]
104        label: Option<String>,
105    },
106
107    /// Pack a plaintext DIDComm message
108    #[command(name = "pack", about = "Pack a plaintext DIDComm message")]
109    Pack {
110        /// The input file containing the plaintext message
111        #[arg(short, long, required = true)]
112        input: PathBuf,
113
114        /// The output file for the packed message
115        #[arg(short, long)]
116        output: Option<PathBuf>,
117
118        /// The DID of the sender (uses default if not specified)
119        #[arg(short, long)]
120        sender: Option<String>,
121
122        /// The DID of the recipient
123        #[arg(short, long)]
124        recipient: Option<String>,
125
126        /// The security mode to use (plain, signed, authcrypt, or anoncrypt)
127        #[arg(short, long, default_value = "signed")]
128        mode: String,
129    },
130
131    /// Unpack a signed or encrypted DIDComm message
132    #[command(
133        name = "unpack",
134        about = "Unpack a signed or encrypted DIDComm message"
135    )]
136    Unpack {
137        /// The input file containing the packed message
138        #[arg(short, long, required = true)]
139        input: PathBuf,
140
141        /// The output file for the unpacked message
142        #[arg(short, long)]
143        output: Option<PathBuf>,
144
145        /// The DID of the recipient (uses default if not specified)
146        #[arg(short, long)]
147        recipient: Option<String>,
148    },
149}
150
151/// Subcommands for key management
152#[derive(Subcommand, Debug)]
153pub enum KeysCommands {
154    /// List all stored keys
155    #[command(name = "list")]
156    List,
157
158    /// View details of a specific key
159    #[command(name = "view")]
160    View {
161        /// The DID or label of the key to view
162        #[arg(required = true)]
163        did_or_label: String,
164    },
165
166    /// Set a key as the default
167    #[command(name = "set-default")]
168    SetDefault {
169        /// The DID or label of the key to set as default
170        #[arg(required = true)]
171        did_or_label: String,
172    },
173
174    /// Delete a key from storage
175    #[command(name = "delete")]
176    Delete {
177        /// The DID or label of the key to delete
178        #[arg(required = true)]
179        did_or_label: String,
180
181        /// Force deletion without confirmation
182        #[arg(short, long)]
183        force: bool,
184    },
185
186    /// Update the label of a key
187    #[command(name = "relabel")]
188    Relabel {
189        /// The DID or label of the key to relabel
190        #[arg(required = true)]
191        did_or_label: String,
192
193        /// The new label for the key
194        #[arg(required = true)]
195        new_label: String,
196    },
197}
198
199/// Run the CLI with the given arguments
200pub fn run() -> Result<()> {
201    let cli = Cli::parse();
202    match cli.command {
203        Commands::Generate {
204            method,
205            key_type,
206            domain,
207            output,
208            key_output,
209            save,
210            default,
211            label,
212        } => {
213            generate_did(GenerateDIDOptions {
214                method: &method,
215                key_type: &key_type,
216                domain: domain.as_deref(),
217                output,
218                key_output,
219                save,
220                set_default: default,
221                label: label.as_deref(),
222            })?;
223        }
224        Commands::Lookup { did, output } => {
225            lookup_did(&did, output)?;
226        }
227        Commands::Keys { subcommand } => {
228            manage_keys(subcommand)?;
229        }
230        Commands::Import {
231            key_file,
232            default,
233            label,
234        } => {
235            import_key(&key_file, default, label.as_deref())?;
236        }
237        Commands::Pack {
238            input,
239            output,
240            sender,
241            recipient,
242            mode,
243        } => {
244            pack_message(&input, output, sender, recipient, &mode)?;
245        }
246        Commands::Unpack {
247            input,
248            output,
249            recipient,
250        } => {
251            unpack_message(&input, output, recipient)?;
252        }
253    }
254
255    Ok(())
256}
257
258/// Options for DID generation
259struct GenerateDIDOptions<'a> {
260    method: &'a str,
261    key_type: &'a str,
262    domain: Option<&'a str>,
263    output: Option<PathBuf>,
264    key_output: Option<PathBuf>,
265    save: bool,
266    set_default: bool,
267    label: Option<&'a str>,
268}
269
270/// Generate a DID of the specified method and key type
271fn generate_did(options: GenerateDIDOptions) -> Result<()> {
272    // Parse key type
273    let key_type = match options.key_type.to_lowercase().as_str() {
274        #[cfg(feature = "crypto-ed25519")]
275        "ed25519" => KeyType::Ed25519,
276        #[cfg(feature = "crypto-p256")]
277        "p256" => KeyType::P256,
278        #[cfg(feature = "crypto-secp256k1")]
279        "secp256k1" => KeyType::Secp256k1,
280        _ => {
281            eprintln!(
282                "Unsupported key type: {}. Using Ed25519 as default.",
283                options.key_type
284            );
285            #[cfg(feature = "crypto-ed25519")]
286            {
287                KeyType::Ed25519
288            }
289            #[cfg(not(feature = "crypto-ed25519"))]
290            {
291                return Err(Error::Cryptography(format!(
292                    "Unsupported key type: {}",
293                    options.key_type
294                )));
295            }
296        }
297    };
298
299    // Create DID generation options
300    let did_options = DIDGenerationOptions { key_type };
301
302    // Generate DID using the specified method
303    let generator = DIDKeyGenerator::new();
304    let generated_key = match options.method.to_lowercase().as_str() {
305        "key" => generator.generate_did(did_options)?,
306        "web" => {
307            // For did:web, domain is required
308            let domain = options.domain.ok_or_else(|| {
309                crate::error::Error::MissingConfig("Domain is required for did:web".to_string())
310            })?;
311            generator.generate_web_did(domain, did_options)?
312        }
313        _ => {
314            eprintln!(
315                "Unsupported DID method: {}. Using did:key as default.",
316                options.method
317            );
318            generator.generate_did(did_options)?
319        }
320    };
321
322    // Display DID information
323    display_generated_did(&generated_key, options.method, options.domain);
324
325    // Save DID document if output path is specified
326    if let Some(output_path) = options.output {
327        save_did_document(&generated_key, &output_path)?;
328    }
329
330    // Save private key if key output path is specified
331    if let Some(key_path) = options.key_output {
332        save_private_key(&generated_key, &key_path)?;
333    }
334
335    // Save key to default storage if requested
336    if options.save {
337        save_key_to_storage(&generated_key, options.set_default, options.label)?;
338    }
339
340    Ok(())
341}
342
343/// Display information about the generated DID
344fn display_generated_did(generated_key: &GeneratedKey, method: &str, domain: Option<&str>) {
345    println!("\n=== Generated DID ===");
346    println!("DID: {}", generated_key.did);
347    println!("Key Type: {:?}", generated_key.key_type);
348
349    // For did:web, show where to place the DID document
350    if method == "web" {
351        if let Some(d) = domain {
352            println!("\nTo use this did:web, place the DID document at:");
353            println!("https://{}/.well-known/did.json", d);
354        }
355    }
356
357    // Display the private key
358    println!("\n=== Private Key (keep this secure!) ===");
359    println!(
360        "Private Key (Base64): {}",
361        base64::engine::general_purpose::STANDARD.encode(&generated_key.private_key)
362    );
363
364    println!("\n=== Public Key ===");
365    println!(
366        "Public Key (Base64): {}",
367        base64::engine::general_purpose::STANDARD.encode(&generated_key.public_key)
368    );
369}
370
371/// Save DID document to a file
372fn save_did_document(generated_key: &GeneratedKey, output_path: &PathBuf) -> Result<()> {
373    let did_doc_json = serde_json::to_string_pretty(&generated_key.did_doc)
374        .map_err(|e| crate::error::Error::Serialization(e.to_string()))?;
375
376    fs::write(output_path, did_doc_json).map_err(crate::error::Error::Io)?;
377
378    println!("\nDID document saved to: {}", output_path.display());
379    Ok(())
380}
381
382/// Save private key to a file
383fn save_private_key(generated_key: &GeneratedKey, key_path: &PathBuf) -> Result<()> {
384    // Create a JSON object with key information
385    let key_info = serde_json::json!({
386        "did": generated_key.did,
387        "keyType": format!("{:?}", generated_key.key_type),
388        "privateKey": base64::engine::general_purpose::STANDARD.encode(&generated_key.private_key),
389        "publicKey": base64::engine::general_purpose::STANDARD.encode(&generated_key.public_key),
390    });
391
392    let key_json = serde_json::to_string_pretty(&key_info)
393        .map_err(|e| crate::error::Error::Serialization(e.to_string()))?;
394
395    fs::write(key_path, key_json).map_err(crate::error::Error::Io)?;
396
397    println!("Private key saved to: {}", key_path.display());
398    Ok(())
399}
400
401/// Save a key to the default storage location
402fn save_key_to_storage(
403    generated_key: &GeneratedKey,
404    set_as_default: bool,
405    label: Option<&str>,
406) -> Result<()> {
407    // Convert GeneratedKey to StoredKey
408    let stored_key = if let Some(label) = label {
409        KeyStorage::from_generated_key_with_label(generated_key, label)
410    } else {
411        KeyStorage::from_generated_key(generated_key)
412    };
413
414    // Load existing storage or create a new one
415    let mut storage = match KeyStorage::load_default() {
416        Ok(storage) => storage,
417        Err(_) => KeyStorage::new(),
418    };
419
420    // Add the key to storage
421    storage.add_key(stored_key);
422
423    // If requested to set as default, update the default DID
424    if set_as_default {
425        storage.default_did = Some(generated_key.did.clone());
426    }
427
428    // Save the updated storage
429    storage.save_default()?;
430
431    println!("Key saved to default storage (~/.tap/keys.json)");
432    if set_as_default {
433        println!("Key set as default agent key");
434    }
435
436    Ok(())
437}
438
439/// Import a key from a file into the key storage
440fn import_key(key_file: &PathBuf, set_as_default: bool, label: Option<&str>) -> Result<()> {
441    // Read and parse the key file
442    let key_json = fs::read_to_string(key_file)
443        .map_err(|e| Error::Storage(format!("Failed to read key file: {}", e)))?;
444
445    let key_info: serde_json::Value = serde_json::from_str(&key_json)
446        .map_err(|e| Error::Storage(format!("Failed to parse key file: {}", e)))?;
447
448    // Extract key information
449    let did = key_info["did"]
450        .as_str()
451        .ok_or_else(|| Error::Storage("Missing 'did' field in key file".to_string()))?;
452
453    let key_type_str = key_info["keyType"]
454        .as_str()
455        .ok_or_else(|| Error::Storage("Missing 'keyType' field in key file".to_string()))?;
456
457    let private_key = key_info["privateKey"]
458        .as_str()
459        .ok_or_else(|| Error::Storage("Missing 'privateKey' field in key file".to_string()))?;
460
461    let public_key = key_info["publicKey"]
462        .as_str()
463        .ok_or_else(|| Error::Storage("Missing 'publicKey' field in key file".to_string()))?;
464
465    // Parse key type
466    let key_type = match key_type_str {
467        #[cfg(feature = "crypto-ed25519")]
468        "Ed25519" => KeyType::Ed25519,
469        #[cfg(feature = "crypto-p256")]
470        "P256" => KeyType::P256,
471        #[cfg(feature = "crypto-secp256k1")]
472        "Secp256k1" => KeyType::Secp256k1,
473        _ => {
474            return Err(Error::Storage(format!(
475                "Unsupported key type: {}",
476                key_type_str
477            )))
478        }
479    };
480
481    // Create a StoredKey
482    let stored_key = StoredKey {
483        did: did.to_string(),
484        label: label.unwrap_or("").to_string(),
485        key_type,
486        private_key: private_key.to_string(),
487        public_key: public_key.to_string(),
488        metadata: std::collections::HashMap::new(),
489    };
490
491    // Load existing storage or create a new one
492    let mut storage = match KeyStorage::load_default() {
493        Ok(storage) => storage,
494        Err(_) => KeyStorage::new(),
495    };
496
497    // Add the key to storage
498    storage.add_key(stored_key);
499
500    // If requested to set as default, update the default DID
501    if set_as_default {
502        storage.default_did = Some(did.to_string());
503    }
504
505    // Save the updated storage
506    storage.save_default()?;
507
508    println!("Key '{}' imported to default storage", did);
509    if set_as_default {
510        println!("Key set as default agent key");
511    }
512
513    Ok(())
514}
515
516/// Manage stored keys
517fn manage_keys(subcommand: Option<KeysCommands>) -> Result<()> {
518    // Load key storage
519    let mut storage = match KeyStorage::load_default() {
520        Ok(storage) => storage,
521        Err(e) => {
522            eprintln!("Error loading key storage: {}", e);
523            eprintln!("Creating new key storage.");
524            KeyStorage::new()
525        }
526    };
527
528    match subcommand {
529        Some(KeysCommands::List) => {
530            list_keys(&storage)?;
531        }
532        Some(KeysCommands::View { did_or_label }) => {
533            view_key(&storage, &did_or_label)?;
534        }
535        Some(KeysCommands::SetDefault { did_or_label }) => {
536            set_default_key(&mut storage, &did_or_label)?;
537        }
538        Some(KeysCommands::Delete {
539            did_or_label,
540            force,
541        }) => {
542            delete_key(&mut storage, &did_or_label, force)?;
543        }
544        Some(KeysCommands::Relabel {
545            did_or_label,
546            new_label,
547        }) => {
548            relabel_key(&mut storage, &did_or_label, &new_label)?;
549        }
550        None => {
551            // Default to list if no subcommand is provided
552            list_keys(&storage)?;
553        }
554    }
555
556    Ok(())
557}
558
559/// Relabel a key in storage
560fn relabel_key(storage: &mut KeyStorage, did_or_label: &str, new_label: &str) -> Result<()> {
561    // Try to find by label first, then by DID
562    let did = if let Some(key) = storage.find_by_label(did_or_label) {
563        key.did.clone()
564    } else if storage.keys.contains_key(did_or_label) {
565        did_or_label.to_string()
566    } else {
567        return Err(Error::Storage(format!(
568            "Key '{}' not found in storage",
569            did_or_label
570        )));
571    };
572
573    // Update the label
574    storage.update_label(&did, new_label)?;
575
576    // Save the updated storage
577    storage.save_default()?;
578
579    println!("Key relabeled successfully to '{}'", new_label);
580
581    Ok(())
582}
583
584/// List all keys in storage
585fn list_keys(storage: &KeyStorage) -> Result<()> {
586    // Check if storage is empty
587    if storage.keys.is_empty() {
588        println!("No keys found in storage.");
589        println!("Generate a key with: tap-agent-cli generate --save");
590        return Ok(());
591    }
592
593    println!("Keys in storage:");
594    println!("{:-<60}", "");
595
596    // Get the default DID for marking
597    let default_did = storage.default_did.as_deref();
598
599    // Print header
600    println!("{:<15} {:<40} {:<10} Default", "Label", "DID", "Key Type");
601    println!("{:-<75}", "");
602
603    // Print each key
604    for (did, key) in &storage.keys {
605        let is_default = if Some(did.as_str()) == default_did {
606            "*"
607        } else {
608            ""
609        };
610        println!(
611            "{:<15} {:<40} {:<10} {}",
612            key.label,
613            did,
614            format!("{:?}", key.key_type),
615            is_default
616        );
617    }
618
619    println!("\nTotal keys: {}", storage.keys.len());
620
621    Ok(())
622}
623
624/// View details for a specific key
625fn view_key(storage: &KeyStorage, did_or_label: &str) -> Result<()> {
626    // Try to find by label first, then by DID
627    let key = storage
628        .find_by_label(did_or_label)
629        .or_else(|| storage.keys.get(did_or_label))
630        .ok_or_else(|| Error::Storage(format!("Key '{}' not found in storage", did_or_label)))?;
631
632    // Display key information
633    println!("\n=== Key Details ===");
634    println!("Label: {}", key.label);
635    println!("DID: {}", key.did);
636    println!("Key Type: {:?}", key.key_type);
637    println!("Public Key (Base64): {}", key.public_key);
638
639    // Check if this is the default key
640    if storage.default_did.as_deref() == Some(&key.did) {
641        println!("Default: Yes");
642    } else {
643        println!("Default: No");
644    }
645
646    // Print metadata if any
647    if !key.metadata.is_empty() {
648        println!("\nMetadata:");
649        for (k, v) in &key.metadata {
650            println!("  {}: {}", k, v);
651        }
652    }
653
654    Ok(())
655}
656
657/// Set a key as the default
658fn set_default_key(storage: &mut KeyStorage, did_or_label: &str) -> Result<()> {
659    // Try to find by label first, then by DID
660    let did = if let Some(key) = storage.find_by_label(did_or_label) {
661        key.did.clone()
662    } else if storage.keys.contains_key(did_or_label) {
663        did_or_label.to_string()
664    } else {
665        return Err(Error::Storage(format!(
666            "Key '{}' not found in storage",
667            did_or_label
668        )));
669    };
670
671    // Set as default
672    storage.default_did = Some(did.clone());
673
674    // Save the updated storage
675    storage.save_default()?;
676
677    println!("Key '{}' set as default", did);
678
679    Ok(())
680}
681
682/// Delete a key from storage
683fn delete_key(storage: &mut KeyStorage, did_or_label: &str, force: bool) -> Result<()> {
684    // Try to find by label first, then by DID
685    let did = if let Some(key) = storage.find_by_label(did_or_label) {
686        key.did.clone()
687    } else if storage.keys.contains_key(did_or_label) {
688        did_or_label.to_string()
689    } else {
690        return Err(Error::Storage(format!(
691            "Key '{}' not found in storage",
692            did_or_label
693        )));
694    };
695
696    // Confirm deletion if not forced
697    if !force {
698        println!("Are you sure you want to delete key '{}'? (y/N): ", did);
699        let mut input = String::new();
700        std::io::stdin().read_line(&mut input).map_err(Error::Io)?;
701
702        if !input.trim().eq_ignore_ascii_case("y") {
703            println!("Deletion cancelled.");
704            return Ok(());
705        }
706    }
707
708    // Remove the key
709    storage.keys.remove(&did);
710
711    // If this was the default key, clear the default
712    if storage.default_did.as_deref() == Some(&did) {
713        storage.default_did = storage.keys.keys().next().cloned();
714    }
715
716    // Save the updated storage
717    storage.save_default()?;
718
719    println!("Key '{}' deleted from storage", did);
720
721    Ok(())
722}
723
724/// Lookup and resolve a DID to its corresponding DID document
725fn lookup_did(did: &str, output: Option<PathBuf>) -> Result<()> {
726    println!("Looking up DID: {}", did);
727
728    // Create a resolver
729    let resolver = Arc::new(MultiResolver::default());
730
731    // Create a Tokio runtime for async resolution
732    let rt = tokio::runtime::Builder::new_current_thread()
733        .enable_all()
734        .build()
735        .map_err(|e| Error::DIDResolution(format!("Failed to create runtime: {}", e)))?;
736
737    // Resolve the DID
738    let did_doc = rt.block_on(async { resolver.resolve(did).await })?;
739
740    // Check if DID Document was found
741    match did_doc {
742        Some(doc) => {
743            println!("\n=== DID Document ===");
744
745            // Pretty print the DID Document details
746            println!("DID: {}", doc.id);
747
748            println!("\nVerification Methods:");
749            for (i, vm) in doc.verification_method.iter().enumerate() {
750                println!("  [{}] ID: {}", i + 1, vm.id);
751                println!("      Type: {:?}", vm.type_);
752                println!("      Controller: {}", vm.controller);
753
754                match &vm.verification_material {
755                    VerificationMaterial::JWK { public_key_jwk } => {
756                        println!("      Material: JWK");
757                        if let Some(kty) = public_key_jwk.get("kty") {
758                            println!("        Key Type: {}", kty);
759                        }
760                        if let Some(crv) = public_key_jwk.get("crv") {
761                            println!("        Curve: {}", crv);
762                        }
763                    }
764                    VerificationMaterial::Base58 { public_key_base58 } => {
765                        println!("      Material: Base58");
766                        println!("        Key: {}", public_key_base58);
767                    }
768                    VerificationMaterial::Multibase {
769                        public_key_multibase,
770                    } => {
771                        println!("      Material: Multibase");
772                        println!("        Key: {}", public_key_multibase);
773                    }
774                }
775                println!();
776            }
777
778            if !doc.authentication.is_empty() {
779                println!("Authentication Methods:");
780                for auth in &doc.authentication {
781                    println!("  {}", auth);
782                }
783                println!();
784            }
785
786            if !doc.key_agreement.is_empty() {
787                println!("Key Agreement Methods:");
788                for ka in &doc.key_agreement {
789                    println!("  {}", ka);
790                }
791                println!();
792            }
793
794            if !doc.service.is_empty() {
795                println!("Services:");
796                for (i, svc) in doc.service.iter().enumerate() {
797                    println!("  [{}] ID: {}", i + 1, svc.id);
798                    println!("      Endpoint: {:?}", svc.service_endpoint);
799                    println!();
800                }
801            }
802
803            // Save DID document if output path is specified
804            if let Some(output_path) = output {
805                let did_doc_json = serde_json::to_string_pretty(&doc)
806                    .map_err(|e| Error::Serialization(e.to_string()))?;
807
808                fs::write(&output_path, did_doc_json).map_err(Error::Io)?;
809                println!("DID document saved to: {}", output_path.display());
810            }
811
812            Ok(())
813        }
814        None => {
815            println!("No DID Document found for: {}", did);
816            println!("The DID may not exist or the resolver might not support this DID method.");
817
818            // Extract method to provide better feedback
819            let parts: Vec<&str> = did.split(':').collect();
820            if parts.len() >= 2 {
821                let method = parts[1];
822                println!(
823                    "DID method '{}' may not be supported by the default resolver.",
824                    method
825                );
826                println!("Currently, only the following methods are supported:");
827                println!("  - did:key");
828                println!("  - did:web");
829
830                if method == "web" {
831                    println!("\nFor did:web, ensure:");
832                    println!("  - The domain is correctly formatted");
833                    println!("  - The DID document is hosted at the expected location:");
834                    println!(
835                        "    - https://example.com/.well-known/did.json for did:web:example.com"
836                    );
837                    println!("    - https://example.com/path/to/resource/did.json for did:web:example.com:path:to:resource");
838                }
839            }
840
841            Err(Error::DIDResolution(format!("DID not found: {}", did)))
842        }
843    }
844}
845
846/// Pack a plaintext DIDComm message
847async fn pack_message_async(
848    input_file: &PathBuf,
849    output_file: Option<PathBuf>,
850    sender_did: Option<String>,
851    recipient_did: Option<String>,
852    mode: &str,
853) -> Result<()> {
854    // Read the plaintext message from the input file
855    let plaintext = fs::read_to_string(input_file).map_err(Error::Io)?;
856
857    // Parse the plaintext message
858    let plain_message: PlainMessage = serde_json::from_str(&plaintext)
859        .map_err(|e| Error::Serialization(format!("Failed to parse plaintext message: {}", e)))?;
860
861    // Load keys from storage
862    let storage = KeyStorage::load_default()?;
863
864    // Get the sender DID
865    let sender = if let Some(did_or_label) = sender_did {
866        // Try to find by label first, then by DID
867        let did = if let Some(key) = storage.find_by_label(&did_or_label) {
868            key.did.clone()
869        } else if storage.keys.contains_key(&did_or_label) {
870            did_or_label
871        } else {
872            return Err(Error::Storage(format!(
873                "Sender '{}' not found in storage",
874                did_or_label
875            )));
876        };
877        Some(did)
878    } else if let Some(default_did) = storage.default_did.clone() {
879        // Use default DID if available
880        Some(default_did)
881    } else if let Some(first_key) = storage.keys.keys().next() {
882        // Fallback to first key
883        Some(first_key.clone())
884    } else {
885        // No keys available
886        return Err(Error::Storage("No keys found in storage".to_string()));
887    };
888
889    if let Some(ref sender_did) = sender {
890        println!("Using sender DID: {}", sender_did);
891    }
892
893    // Create key manager with the loaded keys
894    let key_manager_builder =
895        crate::agent_key_manager::AgentKeyManagerBuilder::new().load_from_default_storage();
896    let key_manager = Arc::new(key_manager_builder.build()?);
897
898    // Determine security mode
899    let security_mode = match mode.to_lowercase().as_str() {
900        "plain" => SecurityMode::Plain,
901        "signed" => SecurityMode::Signed,
902        "authcrypt" | "auth" | "encrypted" => SecurityMode::AuthCrypt,
903        "anoncrypt" | "anon" => SecurityMode::AnonCrypt,
904        _ => {
905            eprintln!(
906                "Unknown security mode: {}. Using 'signed' as default.",
907                mode
908            );
909            SecurityMode::Signed
910        }
911    };
912
913    // Get the actual key IDs from the key manager
914    let sender_kid = if let Some(ref s) = sender {
915        // Try to get the actual key ID from the key manager
916        if let Ok(key) = key_manager.get_generated_key(s) {
917            // Get the first authentication method ID from the DID document
918            key.did_doc.authentication.first().cloned().or_else(|| {
919                key.did_doc
920                    .verification_method
921                    .first()
922                    .map(|vm| vm.id.clone())
923            })
924        } else {
925            // Fallback to the DID-based format for did:key
926            if let Some(key_part) = s.strip_prefix("did:key:") {
927                Some(format!("{}#{}", s, key_part))
928            } else {
929                Some(format!("{}#keys-1", s))
930            }
931        }
932    } else {
933        None
934    };
935
936    let recipient_kid = if let Some(did) = recipient_did {
937        // For recipients, we don't have their keys, so use the standard format
938        if let Some(key_part) = did.strip_prefix("did:key:") {
939            Some(format!("{}#{}", did, key_part))
940        } else {
941            Some(format!("{}#keys-1", did))
942        }
943    } else {
944        None
945    };
946
947    // Create pack options
948    let pack_options = PackOptions {
949        security_mode,
950        sender_kid,
951        recipient_kid,
952    };
953
954    // Pack the message directly using the PlainMessage's Packable implementation
955    let packed = plain_message.pack(&*key_manager, pack_options).await?;
956
957    // Write the packed message to the output file or display it
958    if let Some(output) = output_file {
959        fs::write(&output, &packed).map_err(Error::Io)?;
960        println!("Packed message saved to: {}", output.display());
961    } else {
962        // Try to pretty-print if it's valid JSON
963        match serde_json::from_str::<serde_json::Value>(&packed) {
964            Ok(json) => println!("{}", serde_json::to_string_pretty(&json).unwrap_or(packed)),
965            Err(_) => println!("{}", packed),
966        }
967    }
968
969    Ok(())
970}
971
972/// Pack a plaintext DIDComm message (synchronous wrapper)
973fn pack_message(
974    input_file: &PathBuf,
975    output_file: Option<PathBuf>,
976    sender_did: Option<String>,
977    recipient_did: Option<String>,
978    mode: &str,
979) -> Result<()> {
980    // Create a tokio runtime to run async function
981    let rt = tokio::runtime::Builder::new_current_thread()
982        .enable_all()
983        .build()
984        .map_err(|e| Error::Runtime(format!("Failed to create runtime: {}", e)))?;
985
986    // Run the async function in the runtime
987    rt.block_on(pack_message_async(
988        input_file,
989        output_file,
990        sender_did,
991        recipient_did,
992        mode,
993    ))
994}
995
996/// Unpack a signed or encrypted DIDComm message
997async fn unpack_message_async(
998    input_file: &PathBuf,
999    output_file: Option<PathBuf>,
1000    recipient_did: Option<String>,
1001) -> Result<()> {
1002    // Read the packed message from the input file
1003    let packed = fs::read_to_string(input_file).map_err(Error::Io)?;
1004
1005    // Load keys from storage
1006    let storage = KeyStorage::load_default()?;
1007
1008    // Get the recipient DID
1009    let recipient = if let Some(did_or_label) = recipient_did {
1010        // Try to find by label first, then by DID
1011        if let Some(key) = storage.find_by_label(&did_or_label) {
1012            key.did.clone()
1013        } else if storage.keys.contains_key(&did_or_label) {
1014            did_or_label
1015        } else {
1016            return Err(Error::Storage(format!(
1017                "Recipient '{}' not found in storage",
1018                did_or_label
1019            )));
1020        }
1021    } else if let Some(default_did) = storage.default_did.clone() {
1022        // Use default DID if available
1023        default_did
1024    } else if let Some(first_key) = storage.keys.keys().next() {
1025        // Otherwise use first available DID
1026        first_key.clone()
1027    } else {
1028        // No keys found
1029        return Err(Error::Storage("No keys found in storage".to_string()));
1030    };
1031
1032    println!("Using recipient DID: {}", recipient);
1033
1034    // Create key manager with the loaded keys
1035    let key_manager_builder =
1036        crate::agent_key_manager::AgentKeyManagerBuilder::new().load_from_default_storage();
1037    let key_manager = Arc::new(key_manager_builder.build()?);
1038
1039    // Get the actual key ID for the recipient
1040    let expected_recipient_kid = if let Ok(key) = key_manager.get_generated_key(&recipient) {
1041        // Get the first authentication method ID from the DID document
1042        key.did_doc.authentication.first().cloned().or_else(|| {
1043            key.did_doc
1044                .verification_method
1045                .first()
1046                .map(|vm| vm.id.clone())
1047        })
1048    } else {
1049        // Fallback to the DID-based format for did:key
1050        if let Some(key_part) = recipient.strip_prefix("did:key:") {
1051            Some(format!("{}#{}", recipient, key_part))
1052        } else {
1053            Some(format!("{}#keys-1", recipient))
1054        }
1055    };
1056
1057    // Create unpack options
1058    use crate::message_packing::UnpackOptions;
1059    let unpack_options = UnpackOptions {
1060        expected_security_mode: SecurityMode::Any,
1061        expected_recipient_kid,
1062        require_signature: false,
1063    };
1064
1065    // Unpack the message using the String's Unpackable implementation
1066    let unpacked: PlainMessage = String::unpack(&packed, &*key_manager, unpack_options).await?;
1067
1068    // Convert to pretty JSON
1069    let unpacked_json = serde_json::to_string_pretty(&unpacked)
1070        .map_err(|e| Error::Serialization(format!("Failed to format unpacked message: {}", e)))?;
1071
1072    // Write the unpacked message to the output file or display it
1073    if let Some(output) = output_file {
1074        fs::write(&output, &unpacked_json).map_err(Error::Io)?;
1075        println!("Unpacked message saved to: {}", output.display());
1076    } else {
1077        println!("{}", unpacked_json);
1078    }
1079
1080    Ok(())
1081}
1082
1083/// Unpack a signed or encrypted DIDComm message (synchronous wrapper)
1084fn unpack_message(
1085    input_file: &PathBuf,
1086    output_file: Option<PathBuf>,
1087    recipient_did: Option<String>,
1088) -> Result<()> {
1089    // Create a tokio runtime to run async function
1090    let rt = tokio::runtime::Builder::new_current_thread()
1091        .enable_all()
1092        .build()
1093        .map_err(|e| Error::Runtime(format!("Failed to create runtime: {}", e)))?;
1094
1095    // Run the async function in the runtime
1096    rt.block_on(unpack_message_async(input_file, output_file, recipient_did))
1097}