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, or authcrypt)
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        "ed25519" => KeyType::Ed25519,
275        "p256" => KeyType::P256,
276        "secp256k1" => KeyType::Secp256k1,
277        _ => {
278            eprintln!(
279                "Unsupported key type: {}. Using Ed25519 as default.",
280                options.key_type
281            );
282            KeyType::Ed25519
283        }
284    };
285
286    // Create DID generation options
287    let did_options = DIDGenerationOptions { key_type };
288
289    // Generate DID using the specified method
290    let generator = DIDKeyGenerator::new();
291    let generated_key = match options.method.to_lowercase().as_str() {
292        "key" => generator.generate_did(did_options)?,
293        "web" => {
294            // For did:web, domain is required
295            let domain = options.domain.ok_or_else(|| {
296                crate::error::Error::MissingConfig("Domain is required for did:web".to_string())
297            })?;
298            generator.generate_web_did(domain, did_options)?
299        }
300        _ => {
301            eprintln!(
302                "Unsupported DID method: {}. Using did:key as default.",
303                options.method
304            );
305            generator.generate_did(did_options)?
306        }
307    };
308
309    // Display DID information
310    display_generated_did(&generated_key, options.method, options.domain);
311
312    // Save DID document if output path is specified
313    if let Some(output_path) = options.output {
314        save_did_document(&generated_key, &output_path)?;
315    }
316
317    // Save private key if key output path is specified
318    if let Some(key_path) = options.key_output {
319        save_private_key(&generated_key, &key_path)?;
320    }
321
322    // Save key to default storage if requested
323    if options.save {
324        save_key_to_storage(&generated_key, options.set_default, options.label)?;
325    }
326
327    Ok(())
328}
329
330/// Display information about the generated DID
331fn display_generated_did(generated_key: &GeneratedKey, method: &str, domain: Option<&str>) {
332    println!("\n=== Generated DID ===");
333    println!("DID: {}", generated_key.did);
334    println!("Key Type: {:?}", generated_key.key_type);
335
336    // For did:web, show where to place the DID document
337    if method == "web" && domain.is_some() {
338        println!("\nTo use this did:web, place the DID document at:");
339        println!("https://{}/.well-known/did.json", domain.unwrap());
340    }
341
342    // Display the private key
343    println!("\n=== Private Key (keep this secure!) ===");
344    println!(
345        "Private Key (Base64): {}",
346        base64::engine::general_purpose::STANDARD.encode(&generated_key.private_key)
347    );
348
349    println!("\n=== Public Key ===");
350    println!(
351        "Public Key (Base64): {}",
352        base64::engine::general_purpose::STANDARD.encode(&generated_key.public_key)
353    );
354}
355
356/// Save DID document to a file
357fn save_did_document(generated_key: &GeneratedKey, output_path: &PathBuf) -> Result<()> {
358    let did_doc_json = serde_json::to_string_pretty(&generated_key.did_doc)
359        .map_err(|e| crate::error::Error::Serialization(e.to_string()))?;
360
361    fs::write(output_path, did_doc_json).map_err(crate::error::Error::Io)?;
362
363    println!("\nDID document saved to: {}", output_path.display());
364    Ok(())
365}
366
367/// Save private key to a file
368fn save_private_key(generated_key: &GeneratedKey, key_path: &PathBuf) -> Result<()> {
369    // Create a JSON object with key information
370    let key_info = serde_json::json!({
371        "did": generated_key.did,
372        "keyType": format!("{:?}", generated_key.key_type),
373        "privateKey": base64::engine::general_purpose::STANDARD.encode(&generated_key.private_key),
374        "publicKey": base64::engine::general_purpose::STANDARD.encode(&generated_key.public_key),
375    });
376
377    let key_json = serde_json::to_string_pretty(&key_info)
378        .map_err(|e| crate::error::Error::Serialization(e.to_string()))?;
379
380    fs::write(key_path, key_json).map_err(crate::error::Error::Io)?;
381
382    println!("Private key saved to: {}", key_path.display());
383    Ok(())
384}
385
386/// Save a key to the default storage location
387fn save_key_to_storage(
388    generated_key: &GeneratedKey,
389    set_as_default: bool,
390    label: Option<&str>,
391) -> Result<()> {
392    // Convert GeneratedKey to StoredKey
393    let stored_key = if let Some(label) = label {
394        KeyStorage::from_generated_key_with_label(generated_key, label)
395    } else {
396        KeyStorage::from_generated_key(generated_key)
397    };
398
399    // Load existing storage or create a new one
400    let mut storage = match KeyStorage::load_default() {
401        Ok(storage) => storage,
402        Err(_) => KeyStorage::new(),
403    };
404
405    // Add the key to storage
406    storage.add_key(stored_key);
407
408    // If requested to set as default, update the default DID
409    if set_as_default {
410        storage.default_did = Some(generated_key.did.clone());
411    }
412
413    // Save the updated storage
414    storage.save_default()?;
415
416    println!("Key saved to default storage (~/.tap/keys.json)");
417    if set_as_default {
418        println!("Key set as default agent key");
419    }
420
421    Ok(())
422}
423
424/// Import a key from a file into the key storage
425fn import_key(key_file: &PathBuf, set_as_default: bool, label: Option<&str>) -> Result<()> {
426    // Read and parse the key file
427    let key_json = fs::read_to_string(key_file)
428        .map_err(|e| Error::Storage(format!("Failed to read key file: {}", e)))?;
429
430    let key_info: serde_json::Value = serde_json::from_str(&key_json)
431        .map_err(|e| Error::Storage(format!("Failed to parse key file: {}", e)))?;
432
433    // Extract key information
434    let did = key_info["did"]
435        .as_str()
436        .ok_or_else(|| Error::Storage("Missing 'did' field in key file".to_string()))?;
437
438    let key_type_str = key_info["keyType"]
439        .as_str()
440        .ok_or_else(|| Error::Storage("Missing 'keyType' field in key file".to_string()))?;
441
442    let private_key = key_info["privateKey"]
443        .as_str()
444        .ok_or_else(|| Error::Storage("Missing 'privateKey' field in key file".to_string()))?;
445
446    let public_key = key_info["publicKey"]
447        .as_str()
448        .ok_or_else(|| Error::Storage("Missing 'publicKey' field in key file".to_string()))?;
449
450    // Parse key type
451    let key_type = match key_type_str {
452        "Ed25519" => KeyType::Ed25519,
453        "P256" => KeyType::P256,
454        "Secp256k1" => KeyType::Secp256k1,
455        _ => {
456            return Err(Error::Storage(format!(
457                "Unsupported key type: {}",
458                key_type_str
459            )))
460        }
461    };
462
463    // Create a StoredKey
464    let stored_key = StoredKey {
465        did: did.to_string(),
466        label: label.unwrap_or("").to_string(),
467        key_type,
468        private_key: private_key.to_string(),
469        public_key: public_key.to_string(),
470        metadata: std::collections::HashMap::new(),
471    };
472
473    // Load existing storage or create a new one
474    let mut storage = match KeyStorage::load_default() {
475        Ok(storage) => storage,
476        Err(_) => KeyStorage::new(),
477    };
478
479    // Add the key to storage
480    storage.add_key(stored_key);
481
482    // If requested to set as default, update the default DID
483    if set_as_default {
484        storage.default_did = Some(did.to_string());
485    }
486
487    // Save the updated storage
488    storage.save_default()?;
489
490    println!("Key '{}' imported to default storage", did);
491    if set_as_default {
492        println!("Key set as default agent key");
493    }
494
495    Ok(())
496}
497
498/// Manage stored keys
499fn manage_keys(subcommand: Option<KeysCommands>) -> Result<()> {
500    // Load key storage
501    let mut storage = match KeyStorage::load_default() {
502        Ok(storage) => storage,
503        Err(e) => {
504            eprintln!("Error loading key storage: {}", e);
505            eprintln!("Creating new key storage.");
506            KeyStorage::new()
507        }
508    };
509
510    match subcommand {
511        Some(KeysCommands::List) => {
512            list_keys(&storage)?;
513        }
514        Some(KeysCommands::View { did_or_label }) => {
515            view_key(&storage, &did_or_label)?;
516        }
517        Some(KeysCommands::SetDefault { did_or_label }) => {
518            set_default_key(&mut storage, &did_or_label)?;
519        }
520        Some(KeysCommands::Delete {
521            did_or_label,
522            force,
523        }) => {
524            delete_key(&mut storage, &did_or_label, force)?;
525        }
526        Some(KeysCommands::Relabel {
527            did_or_label,
528            new_label,
529        }) => {
530            relabel_key(&mut storage, &did_or_label, &new_label)?;
531        }
532        None => {
533            // Default to list if no subcommand is provided
534            list_keys(&storage)?;
535        }
536    }
537
538    Ok(())
539}
540
541/// Relabel a key in storage
542fn relabel_key(storage: &mut KeyStorage, did_or_label: &str, new_label: &str) -> Result<()> {
543    // Try to find by label first, then by DID
544    let did = if let Some(key) = storage.find_by_label(did_or_label) {
545        key.did.clone()
546    } else if storage.keys.contains_key(did_or_label) {
547        did_or_label.to_string()
548    } else {
549        return Err(Error::Storage(format!(
550            "Key '{}' not found in storage",
551            did_or_label
552        )));
553    };
554
555    // Update the label
556    storage.update_label(&did, new_label)?;
557
558    // Save the updated storage
559    storage.save_default()?;
560
561    println!("Key relabeled successfully to '{}'", new_label);
562
563    Ok(())
564}
565
566/// List all keys in storage
567fn list_keys(storage: &KeyStorage) -> Result<()> {
568    // Check if storage is empty
569    if storage.keys.is_empty() {
570        println!("No keys found in storage.");
571        println!("Generate a key with: tap-agent-cli generate --save");
572        return Ok(());
573    }
574
575    println!("Keys in storage:");
576    println!("{:-<60}", "");
577
578    // Get the default DID for marking
579    let default_did = storage.default_did.as_deref();
580
581    // Print header
582    println!("{:<15} {:<40} {:<10} Default", "Label", "DID", "Key Type");
583    println!("{:-<75}", "");
584
585    // Print each key
586    for (did, key) in &storage.keys {
587        let is_default = if Some(did.as_str()) == default_did {
588            "*"
589        } else {
590            ""
591        };
592        println!(
593            "{:<15} {:<40} {:<10} {}",
594            key.label,
595            did,
596            format!("{:?}", key.key_type),
597            is_default
598        );
599    }
600
601    println!("\nTotal keys: {}", storage.keys.len());
602
603    Ok(())
604}
605
606/// View details for a specific key
607fn view_key(storage: &KeyStorage, did_or_label: &str) -> Result<()> {
608    // Try to find by label first, then by DID
609    let key = storage
610        .find_by_label(did_or_label)
611        .or_else(|| storage.keys.get(did_or_label))
612        .ok_or_else(|| Error::Storage(format!("Key '{}' not found in storage", did_or_label)))?;
613
614    // Display key information
615    println!("\n=== Key Details ===");
616    println!("Label: {}", key.label);
617    println!("DID: {}", key.did);
618    println!("Key Type: {:?}", key.key_type);
619    println!("Public Key (Base64): {}", key.public_key);
620
621    // Check if this is the default key
622    if storage.default_did.as_deref() == Some(&key.did) {
623        println!("Default: Yes");
624    } else {
625        println!("Default: No");
626    }
627
628    // Print metadata if any
629    if !key.metadata.is_empty() {
630        println!("\nMetadata:");
631        for (k, v) in &key.metadata {
632            println!("  {}: {}", k, v);
633        }
634    }
635
636    Ok(())
637}
638
639/// Set a key as the default
640fn set_default_key(storage: &mut KeyStorage, did_or_label: &str) -> Result<()> {
641    // Try to find by label first, then by DID
642    let did = if let Some(key) = storage.find_by_label(did_or_label) {
643        key.did.clone()
644    } else if storage.keys.contains_key(did_or_label) {
645        did_or_label.to_string()
646    } else {
647        return Err(Error::Storage(format!(
648            "Key '{}' not found in storage",
649            did_or_label
650        )));
651    };
652
653    // Set as default
654    storage.default_did = Some(did.clone());
655
656    // Save the updated storage
657    storage.save_default()?;
658
659    println!("Key '{}' set as default", did);
660
661    Ok(())
662}
663
664/// Delete a key from storage
665fn delete_key(storage: &mut KeyStorage, did_or_label: &str, force: bool) -> Result<()> {
666    // Try to find by label first, then by DID
667    let did = if let Some(key) = storage.find_by_label(did_or_label) {
668        key.did.clone()
669    } else if storage.keys.contains_key(did_or_label) {
670        did_or_label.to_string()
671    } else {
672        return Err(Error::Storage(format!(
673            "Key '{}' not found in storage",
674            did_or_label
675        )));
676    };
677
678    // Confirm deletion if not forced
679    if !force {
680        println!("Are you sure you want to delete key '{}'? (y/N): ", did);
681        let mut input = String::new();
682        std::io::stdin().read_line(&mut input).map_err(Error::Io)?;
683
684        if !input.trim().eq_ignore_ascii_case("y") {
685            println!("Deletion cancelled.");
686            return Ok(());
687        }
688    }
689
690    // Remove the key
691    storage.keys.remove(&did);
692
693    // If this was the default key, clear the default
694    if storage.default_did.as_deref() == Some(&did) {
695        storage.default_did = storage.keys.keys().next().cloned();
696    }
697
698    // Save the updated storage
699    storage.save_default()?;
700
701    println!("Key '{}' deleted from storage", did);
702
703    Ok(())
704}
705
706/// Lookup and resolve a DID to its corresponding DID document
707fn lookup_did(did: &str, output: Option<PathBuf>) -> Result<()> {
708    println!("Looking up DID: {}", did);
709
710    // Create a resolver
711    let resolver = Arc::new(MultiResolver::default());
712
713    // Create a Tokio runtime for async resolution
714    let rt = tokio::runtime::Builder::new_current_thread()
715        .enable_all()
716        .build()
717        .map_err(|e| Error::DIDResolution(format!("Failed to create runtime: {}", e)))?;
718
719    // Resolve the DID
720    let did_doc = rt.block_on(async { resolver.resolve(did).await })?;
721
722    // Check if DID Document was found
723    match did_doc {
724        Some(doc) => {
725            println!("\n=== DID Document ===");
726
727            // Pretty print the DID Document details
728            println!("DID: {}", doc.id);
729
730            println!("\nVerification Methods:");
731            for (i, vm) in doc.verification_method.iter().enumerate() {
732                println!("  [{}] ID: {}", i + 1, vm.id);
733                println!("      Type: {:?}", vm.type_);
734                println!("      Controller: {}", vm.controller);
735
736                match &vm.verification_material {
737                    VerificationMaterial::JWK { public_key_jwk } => {
738                        println!("      Material: JWK");
739                        if let Some(kty) = public_key_jwk.get("kty") {
740                            println!("        Key Type: {}", kty);
741                        }
742                        if let Some(crv) = public_key_jwk.get("crv") {
743                            println!("        Curve: {}", crv);
744                        }
745                    }
746                    VerificationMaterial::Base58 { public_key_base58 } => {
747                        println!("      Material: Base58");
748                        println!("        Key: {}", public_key_base58);
749                    }
750                    VerificationMaterial::Multibase {
751                        public_key_multibase,
752                    } => {
753                        println!("      Material: Multibase");
754                        println!("        Key: {}", public_key_multibase);
755                    }
756                }
757                println!();
758            }
759
760            if !doc.authentication.is_empty() {
761                println!("Authentication Methods:");
762                for auth in &doc.authentication {
763                    println!("  {}", auth);
764                }
765                println!();
766            }
767
768            if !doc.key_agreement.is_empty() {
769                println!("Key Agreement Methods:");
770                for ka in &doc.key_agreement {
771                    println!("  {}", ka);
772                }
773                println!();
774            }
775
776            if !doc.service.is_empty() {
777                println!("Services:");
778                for (i, svc) in doc.service.iter().enumerate() {
779                    println!("  [{}] ID: {}", i + 1, svc.id);
780                    println!("      Endpoint: {:?}", svc.service_endpoint);
781                    println!();
782                }
783            }
784
785            // Save DID document if output path is specified
786            if let Some(output_path) = output {
787                let did_doc_json = serde_json::to_string_pretty(&doc)
788                    .map_err(|e| Error::Serialization(e.to_string()))?;
789
790                fs::write(&output_path, did_doc_json).map_err(Error::Io)?;
791                println!("DID document saved to: {}", output_path.display());
792            }
793
794            Ok(())
795        }
796        None => {
797            println!("No DID Document found for: {}", did);
798            println!("The DID may not exist or the resolver might not support this DID method.");
799
800            // Extract method to provide better feedback
801            let parts: Vec<&str> = did.split(':').collect();
802            if parts.len() >= 2 {
803                let method = parts[1];
804                println!(
805                    "DID method '{}' may not be supported by the default resolver.",
806                    method
807                );
808                println!("Currently, only the following methods are supported:");
809                println!("  - did:key");
810                println!("  - did:web");
811
812                if method == "web" {
813                    println!("\nFor did:web, ensure:");
814                    println!("  - The domain is correctly formatted");
815                    println!("  - The DID document is hosted at the expected location:");
816                    println!(
817                        "    - https://example.com/.well-known/did.json for did:web:example.com"
818                    );
819                    println!("    - https://example.com/path/to/resource/did.json for did:web:example.com:path:to:resource");
820                }
821            }
822
823            Err(Error::DIDResolution(format!("DID not found: {}", did)))
824        }
825    }
826}
827
828/// Pack a plaintext DIDComm message
829async fn pack_message_async(
830    input_file: &PathBuf,
831    output_file: Option<PathBuf>,
832    sender_did: Option<String>,
833    recipient_did: Option<String>,
834    mode: &str,
835) -> Result<()> {
836    // Read the plaintext message from the input file
837    let plaintext = fs::read_to_string(input_file).map_err(Error::Io)?;
838
839    // Parse the plaintext message
840    let plain_message: PlainMessage = serde_json::from_str(&plaintext)
841        .map_err(|e| Error::Serialization(format!("Failed to parse plaintext message: {}", e)))?;
842
843    // Load keys from storage
844    let storage = KeyStorage::load_default()?;
845
846    // Get the sender DID
847    let sender = if let Some(did_or_label) = sender_did {
848        // Try to find by label first, then by DID
849        let did = if let Some(key) = storage.find_by_label(&did_or_label) {
850            key.did.clone()
851        } else if storage.keys.contains_key(&did_or_label) {
852            did_or_label
853        } else {
854            return Err(Error::Storage(format!(
855                "Sender '{}' not found in storage",
856                did_or_label
857            )));
858        };
859        Some(did)
860    } else if let Some(default_did) = storage.default_did.clone() {
861        // Use default DID if available
862        Some(default_did)
863    } else if let Some(first_key) = storage.keys.keys().next() {
864        // Fallback to first key
865        Some(first_key.clone())
866    } else {
867        // No keys available
868        return Err(Error::Storage("No keys found in storage".to_string()));
869    };
870
871    if let Some(ref sender_did) = sender {
872        println!("Using sender DID: {}", sender_did);
873    }
874
875    // Create key manager with the loaded keys
876    let key_manager_builder =
877        crate::agent_key_manager::AgentKeyManagerBuilder::new().load_from_default_storage();
878    let key_manager = Arc::new(key_manager_builder.build()?);
879
880    // Determine security mode
881    let security_mode = match mode.to_lowercase().as_str() {
882        "plain" => SecurityMode::Plain,
883        "signed" => SecurityMode::Signed,
884        "authcrypt" | "auth" | "encrypted" => SecurityMode::AuthCrypt,
885        _ => {
886            eprintln!(
887                "Unknown security mode: {}. Using 'signed' as default.",
888                mode
889            );
890            SecurityMode::Signed
891        }
892    };
893
894    // Create pack options
895    let pack_options = PackOptions {
896        security_mode,
897        sender_kid: sender.as_ref().map(|s| format!("{}#keys-1", s)),
898        recipient_kid: recipient_did.map(|did| format!("{}#keys-1", did)),
899    };
900
901    // Pack the message directly using the PlainMessage's Packable implementation
902    let packed = plain_message.pack(&*key_manager, pack_options).await?;
903
904    // Write the packed message to the output file or display it
905    if let Some(output) = output_file {
906        fs::write(&output, &packed).map_err(Error::Io)?;
907        println!("Packed message saved to: {}", output.display());
908    } else {
909        // Try to pretty-print if it's valid JSON
910        match serde_json::from_str::<serde_json::Value>(&packed) {
911            Ok(json) => println!("{}", serde_json::to_string_pretty(&json).unwrap_or(packed)),
912            Err(_) => println!("{}", packed),
913        }
914    }
915
916    Ok(())
917}
918
919/// Pack a plaintext DIDComm message (synchronous wrapper)
920fn pack_message(
921    input_file: &PathBuf,
922    output_file: Option<PathBuf>,
923    sender_did: Option<String>,
924    recipient_did: Option<String>,
925    mode: &str,
926) -> Result<()> {
927    // Create a tokio runtime to run async function
928    let rt = tokio::runtime::Builder::new_current_thread()
929        .enable_all()
930        .build()
931        .map_err(|e| Error::Runtime(format!("Failed to create runtime: {}", e)))?;
932
933    // Run the async function in the runtime
934    rt.block_on(pack_message_async(
935        input_file,
936        output_file,
937        sender_did,
938        recipient_did,
939        mode,
940    ))
941}
942
943/// Unpack a signed or encrypted DIDComm message
944async fn unpack_message_async(
945    input_file: &PathBuf,
946    output_file: Option<PathBuf>,
947    recipient_did: Option<String>,
948) -> Result<()> {
949    // Read the packed message from the input file
950    let packed = fs::read_to_string(input_file).map_err(Error::Io)?;
951
952    // Load keys from storage
953    let storage = KeyStorage::load_default()?;
954
955    // Get the recipient DID
956    let recipient = if let Some(did_or_label) = recipient_did {
957        // Try to find by label first, then by DID
958        if let Some(key) = storage.find_by_label(&did_or_label) {
959            key.did.clone()
960        } else if storage.keys.contains_key(&did_or_label) {
961            did_or_label
962        } else {
963            return Err(Error::Storage(format!(
964                "Recipient '{}' not found in storage",
965                did_or_label
966            )));
967        }
968    } else if let Some(default_did) = storage.default_did.clone() {
969        // Use default DID if available
970        default_did
971    } else if let Some(first_key) = storage.keys.keys().next() {
972        // Otherwise use first available DID
973        first_key.clone()
974    } else {
975        // No keys found
976        return Err(Error::Storage("No keys found in storage".to_string()));
977    };
978
979    println!("Using recipient DID: {}", recipient);
980
981    // Create key manager with the loaded keys
982    let key_manager_builder =
983        crate::agent_key_manager::AgentKeyManagerBuilder::new().load_from_default_storage();
984    let key_manager = Arc::new(key_manager_builder.build()?);
985
986    // Create unpack options
987    use crate::message_packing::UnpackOptions;
988    let unpack_options = UnpackOptions {
989        expected_security_mode: SecurityMode::Any,
990        expected_recipient_kid: Some(format!("{}#keys-1", recipient)),
991        require_signature: false,
992    };
993
994    // Unpack the message using the String's Unpackable implementation
995    let unpacked: PlainMessage = String::unpack(&packed, &*key_manager, unpack_options).await?;
996
997    // Convert to pretty JSON
998    let unpacked_json = serde_json::to_string_pretty(&unpacked)
999        .map_err(|e| Error::Serialization(format!("Failed to format unpacked message: {}", e)))?;
1000
1001    // Write the unpacked message to the output file or display it
1002    if let Some(output) = output_file {
1003        fs::write(&output, &unpacked_json).map_err(Error::Io)?;
1004        println!("Unpacked message saved to: {}", output.display());
1005    } else {
1006        println!("{}", unpacked_json);
1007    }
1008
1009    Ok(())
1010}
1011
1012/// Unpack a signed or encrypted DIDComm message (synchronous wrapper)
1013fn unpack_message(
1014    input_file: &PathBuf,
1015    output_file: Option<PathBuf>,
1016    recipient_did: Option<String>,
1017) -> Result<()> {
1018    // Create a tokio runtime to run async function
1019    let rt = tokio::runtime::Builder::new_current_thread()
1020        .enable_all()
1021        .build()
1022        .map_err(|e| Error::Runtime(format!("Failed to create runtime: {}", e)))?;
1023
1024    // Run the async function in the runtime
1025    rt.block_on(unpack_message_async(input_file, output_file, recipient_did))
1026}