1#![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#[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#[derive(Subcommand, Debug)]
35pub enum Commands {
36 #[command(name = "generate")]
38 Generate {
39 #[arg(short, long, default_value = "key")]
41 method: String,
42
43 #[arg(short = 't', long, default_value = "ed25519")]
45 key_type: String,
46
47 #[arg(short, long)]
49 domain: Option<String>,
50
51 #[arg(short, long)]
53 output: Option<PathBuf>,
54
55 #[arg(short = 'k', long)]
57 key_output: Option<PathBuf>,
58
59 #[arg(short = 's', long)]
61 save: bool,
62
63 #[arg(long)]
65 default: bool,
66
67 #[arg(short = 'l', long)]
69 label: Option<String>,
70 },
71
72 #[command(name = "lookup")]
74 Lookup {
75 #[arg(required = true)]
77 did: String,
78
79 #[arg(short, long)]
81 output: Option<PathBuf>,
82 },
83
84 #[command(name = "keys", about = "List, view, and manage stored keys")]
86 Keys {
87 #[command(subcommand)]
88 subcommand: Option<KeysCommands>,
89 },
90
91 #[command(name = "import", about = "Import an existing key into storage")]
93 Import {
94 #[arg(required = true)]
96 key_file: PathBuf,
97
98 #[arg(long)]
100 default: bool,
101
102 #[arg(short = 'l', long)]
104 label: Option<String>,
105 },
106
107 #[command(name = "pack", about = "Pack a plaintext DIDComm message")]
109 Pack {
110 #[arg(short, long, required = true)]
112 input: PathBuf,
113
114 #[arg(short, long)]
116 output: Option<PathBuf>,
117
118 #[arg(short, long)]
120 sender: Option<String>,
121
122 #[arg(short, long)]
124 recipient: Option<String>,
125
126 #[arg(short, long, default_value = "signed")]
128 mode: String,
129 },
130
131 #[command(
133 name = "unpack",
134 about = "Unpack a signed or encrypted DIDComm message"
135 )]
136 Unpack {
137 #[arg(short, long, required = true)]
139 input: PathBuf,
140
141 #[arg(short, long)]
143 output: Option<PathBuf>,
144
145 #[arg(short, long)]
147 recipient: Option<String>,
148 },
149}
150
151#[derive(Subcommand, Debug)]
153pub enum KeysCommands {
154 #[command(name = "list")]
156 List,
157
158 #[command(name = "view")]
160 View {
161 #[arg(required = true)]
163 did_or_label: String,
164 },
165
166 #[command(name = "set-default")]
168 SetDefault {
169 #[arg(required = true)]
171 did_or_label: String,
172 },
173
174 #[command(name = "delete")]
176 Delete {
177 #[arg(required = true)]
179 did_or_label: String,
180
181 #[arg(short, long)]
183 force: bool,
184 },
185
186 #[command(name = "relabel")]
188 Relabel {
189 #[arg(required = true)]
191 did_or_label: String,
192
193 #[arg(required = true)]
195 new_label: String,
196 },
197}
198
199pub 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
258struct 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
270fn generate_did(options: GenerateDIDOptions) -> Result<()> {
272 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 let did_options = DIDGenerationOptions { key_type };
288
289 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 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_generated_did(&generated_key, options.method, options.domain);
311
312 if let Some(output_path) = options.output {
314 save_did_document(&generated_key, &output_path)?;
315 }
316
317 if let Some(key_path) = options.key_output {
319 save_private_key(&generated_key, &key_path)?;
320 }
321
322 if options.save {
324 save_key_to_storage(&generated_key, options.set_default, options.label)?;
325 }
326
327 Ok(())
328}
329
330fn 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 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 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
356fn 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
367fn save_private_key(generated_key: &GeneratedKey, key_path: &PathBuf) -> Result<()> {
369 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
386fn save_key_to_storage(
388 generated_key: &GeneratedKey,
389 set_as_default: bool,
390 label: Option<&str>,
391) -> Result<()> {
392 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 let mut storage = match KeyStorage::load_default() {
401 Ok(storage) => storage,
402 Err(_) => KeyStorage::new(),
403 };
404
405 storage.add_key(stored_key);
407
408 if set_as_default {
410 storage.default_did = Some(generated_key.did.clone());
411 }
412
413 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
424fn import_key(key_file: &PathBuf, set_as_default: bool, label: Option<&str>) -> Result<()> {
426 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 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 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 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 let mut storage = match KeyStorage::load_default() {
475 Ok(storage) => storage,
476 Err(_) => KeyStorage::new(),
477 };
478
479 storage.add_key(stored_key);
481
482 if set_as_default {
484 storage.default_did = Some(did.to_string());
485 }
486
487 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
498fn manage_keys(subcommand: Option<KeysCommands>) -> Result<()> {
500 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 list_keys(&storage)?;
535 }
536 }
537
538 Ok(())
539}
540
541fn relabel_key(storage: &mut KeyStorage, did_or_label: &str, new_label: &str) -> Result<()> {
543 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 storage.update_label(&did, new_label)?;
557
558 storage.save_default()?;
560
561 println!("Key relabeled successfully to '{}'", new_label);
562
563 Ok(())
564}
565
566fn list_keys(storage: &KeyStorage) -> Result<()> {
568 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 let default_did = storage.default_did.as_deref();
580
581 println!("{:<15} {:<40} {:<10} Default", "Label", "DID", "Key Type");
583 println!("{:-<75}", "");
584
585 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
606fn view_key(storage: &KeyStorage, did_or_label: &str) -> Result<()> {
608 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 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 if storage.default_did.as_deref() == Some(&key.did) {
623 println!("Default: Yes");
624 } else {
625 println!("Default: No");
626 }
627
628 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
639fn set_default_key(storage: &mut KeyStorage, did_or_label: &str) -> Result<()> {
641 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 storage.default_did = Some(did.clone());
655
656 storage.save_default()?;
658
659 println!("Key '{}' set as default", did);
660
661 Ok(())
662}
663
664fn delete_key(storage: &mut KeyStorage, did_or_label: &str, force: bool) -> Result<()> {
666 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 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 storage.keys.remove(&did);
692
693 if storage.default_did.as_deref() == Some(&did) {
695 storage.default_did = storage.keys.keys().next().cloned();
696 }
697
698 storage.save_default()?;
700
701 println!("Key '{}' deleted from storage", did);
702
703 Ok(())
704}
705
706fn lookup_did(did: &str, output: Option<PathBuf>) -> Result<()> {
708 println!("Looking up DID: {}", did);
709
710 let resolver = Arc::new(MultiResolver::default());
712
713 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 let did_doc = rt.block_on(async { resolver.resolve(did).await })?;
721
722 match did_doc {
724 Some(doc) => {
725 println!("\n=== DID Document ===");
726
727 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 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 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
828async 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 let plaintext = fs::read_to_string(input_file).map_err(Error::Io)?;
838
839 let plain_message: PlainMessage = serde_json::from_str(&plaintext)
841 .map_err(|e| Error::Serialization(format!("Failed to parse plaintext message: {}", e)))?;
842
843 let storage = KeyStorage::load_default()?;
845
846 let sender = if let Some(did_or_label) = sender_did {
848 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 Some(default_did)
863 } else if let Some(first_key) = storage.keys.keys().next() {
864 Some(first_key.clone())
866 } else {
867 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 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 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 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 let packed = plain_message.pack(&*key_manager, pack_options).await?;
903
904 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 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
919fn 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 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 rt.block_on(pack_message_async(
935 input_file,
936 output_file,
937 sender_did,
938 recipient_did,
939 mode,
940 ))
941}
942
943async fn unpack_message_async(
945 input_file: &PathBuf,
946 output_file: Option<PathBuf>,
947 recipient_did: Option<String>,
948) -> Result<()> {
949 let packed = fs::read_to_string(input_file).map_err(Error::Io)?;
951
952 let storage = KeyStorage::load_default()?;
954
955 let recipient = if let Some(did_or_label) = recipient_did {
957 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 default_did
971 } else if let Some(first_key) = storage.keys.keys().next() {
972 first_key.clone()
974 } else {
975 return Err(Error::Storage("No keys found in storage".to_string()));
977 };
978
979 println!("Using recipient DID: {}", recipient);
980
981 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 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 let unpacked: PlainMessage = String::unpack(&packed, &*key_manager, unpack_options).await?;
996
997 let unpacked_json = serde_json::to_string_pretty(&unpacked)
999 .map_err(|e| Error::Serialization(format!("Failed to format unpacked message: {}", e)))?;
1000
1001 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
1012fn unpack_message(
1014 input_file: &PathBuf,
1015 output_file: Option<PathBuf>,
1016 recipient_did: Option<String>,
1017) -> Result<()> {
1018 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 rt.block_on(unpack_message_async(input_file, output_file, recipient_did))
1026}