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 #[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 let did_options = DIDGenerationOptions { key_type };
301
302 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 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_generated_did(&generated_key, options.method, options.domain);
324
325 if let Some(output_path) = options.output {
327 save_did_document(&generated_key, &output_path)?;
328 }
329
330 if let Some(key_path) = options.key_output {
332 save_private_key(&generated_key, &key_path)?;
333 }
334
335 if options.save {
337 save_key_to_storage(&generated_key, options.set_default, options.label)?;
338 }
339
340 Ok(())
341}
342
343fn 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 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 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
371fn 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
382fn save_private_key(generated_key: &GeneratedKey, key_path: &PathBuf) -> Result<()> {
384 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
401fn save_key_to_storage(
403 generated_key: &GeneratedKey,
404 set_as_default: bool,
405 label: Option<&str>,
406) -> Result<()> {
407 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 let mut storage = match KeyStorage::load_default() {
416 Ok(storage) => storage,
417 Err(_) => KeyStorage::new(),
418 };
419
420 storage.add_key(stored_key);
422
423 if set_as_default {
425 storage.default_did = Some(generated_key.did.clone());
426 }
427
428 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
439fn import_key(key_file: &PathBuf, set_as_default: bool, label: Option<&str>) -> Result<()> {
441 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 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 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 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 let mut storage = match KeyStorage::load_default() {
493 Ok(storage) => storage,
494 Err(_) => KeyStorage::new(),
495 };
496
497 storage.add_key(stored_key);
499
500 if set_as_default {
502 storage.default_did = Some(did.to_string());
503 }
504
505 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
516fn manage_keys(subcommand: Option<KeysCommands>) -> Result<()> {
518 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 list_keys(&storage)?;
553 }
554 }
555
556 Ok(())
557}
558
559fn relabel_key(storage: &mut KeyStorage, did_or_label: &str, new_label: &str) -> Result<()> {
561 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 storage.update_label(&did, new_label)?;
575
576 storage.save_default()?;
578
579 println!("Key relabeled successfully to '{}'", new_label);
580
581 Ok(())
582}
583
584fn list_keys(storage: &KeyStorage) -> Result<()> {
586 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 let default_did = storage.default_did.as_deref();
598
599 println!("{:<15} {:<40} {:<10} Default", "Label", "DID", "Key Type");
601 println!("{:-<75}", "");
602
603 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
624fn view_key(storage: &KeyStorage, did_or_label: &str) -> Result<()> {
626 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 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 if storage.default_did.as_deref() == Some(&key.did) {
641 println!("Default: Yes");
642 } else {
643 println!("Default: No");
644 }
645
646 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
657fn set_default_key(storage: &mut KeyStorage, did_or_label: &str) -> Result<()> {
659 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 storage.default_did = Some(did.clone());
673
674 storage.save_default()?;
676
677 println!("Key '{}' set as default", did);
678
679 Ok(())
680}
681
682fn delete_key(storage: &mut KeyStorage, did_or_label: &str, force: bool) -> Result<()> {
684 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 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 storage.keys.remove(&did);
710
711 if storage.default_did.as_deref() == Some(&did) {
713 storage.default_did = storage.keys.keys().next().cloned();
714 }
715
716 storage.save_default()?;
718
719 println!("Key '{}' deleted from storage", did);
720
721 Ok(())
722}
723
724fn lookup_did(did: &str, output: Option<PathBuf>) -> Result<()> {
726 println!("Looking up DID: {}", did);
727
728 let resolver = Arc::new(MultiResolver::default());
730
731 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 let did_doc = rt.block_on(async { resolver.resolve(did).await })?;
739
740 match did_doc {
742 Some(doc) => {
743 println!("\n=== DID Document ===");
744
745 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 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 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
846async 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 let plaintext = fs::read_to_string(input_file).map_err(Error::Io)?;
856
857 let plain_message: PlainMessage = serde_json::from_str(&plaintext)
859 .map_err(|e| Error::Serialization(format!("Failed to parse plaintext message: {}", e)))?;
860
861 let storage = KeyStorage::load_default()?;
863
864 let sender = if let Some(did_or_label) = sender_did {
866 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 Some(default_did)
881 } else if let Some(first_key) = storage.keys.keys().next() {
882 Some(first_key.clone())
884 } else {
885 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 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 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 let sender_kid = if let Some(ref s) = sender {
915 if let Ok(key) = key_manager.get_generated_key(s) {
917 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 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 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 let pack_options = PackOptions {
949 security_mode,
950 sender_kid,
951 recipient_kid,
952 };
953
954 let packed = plain_message.pack(&*key_manager, pack_options).await?;
956
957 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 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
972fn 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 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 rt.block_on(pack_message_async(
988 input_file,
989 output_file,
990 sender_did,
991 recipient_did,
992 mode,
993 ))
994}
995
996async fn unpack_message_async(
998 input_file: &PathBuf,
999 output_file: Option<PathBuf>,
1000 recipient_did: Option<String>,
1001) -> Result<()> {
1002 let packed = fs::read_to_string(input_file).map_err(Error::Io)?;
1004
1005 let storage = KeyStorage::load_default()?;
1007
1008 let recipient = if let Some(did_or_label) = recipient_did {
1010 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 default_did
1024 } else if let Some(first_key) = storage.keys.keys().next() {
1025 first_key.clone()
1027 } else {
1028 return Err(Error::Storage("No keys found in storage".to_string()));
1030 };
1031
1032 println!("Using recipient DID: {}", recipient);
1033
1034 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 let expected_recipient_kid = if let Ok(key) = key_manager.get_generated_key(&recipient) {
1041 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 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 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 let unpacked: PlainMessage = String::unpack(&packed, &*key_manager, unpack_options).await?;
1067
1068 let unpacked_json = serde_json::to_string_pretty(&unpacked)
1070 .map_err(|e| Error::Serialization(format!("Failed to format unpacked message: {}", e)))?;
1071
1072 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
1083fn unpack_message(
1085 input_file: &PathBuf,
1086 output_file: Option<PathBuf>,
1087 recipient_did: Option<String>,
1088) -> Result<()> {
1089 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 rt.block_on(unpack_message_async(input_file, output_file, recipient_did))
1097}