1use crate::did::{GeneratedKey, KeyType};
8use crate::error::{Error, Result};
9use crate::key_manager::{Secret, SecretMaterial, SecretType};
10use base64::Engine;
11use dirs::home_dir;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::env;
15use std::fs;
16use std::path::{Path, PathBuf};
17
18pub const DEFAULT_TAP_DIR: &str = ".tap";
20pub const DEFAULT_KEYS_FILE: &str = "keys.json";
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct StoredKey {
26 pub did: String,
28 #[serde(default)]
30 pub label: String,
31 #[serde(with = "key_type_serde")]
33 pub key_type: KeyType,
34 pub private_key: String,
36 pub public_key: String,
38 #[serde(default)]
40 pub metadata: HashMap<String, String>,
41}
42
43mod key_type_serde {
45 use super::KeyType;
46 use serde::{Deserialize, Deserializer, Serializer};
47
48 pub fn serialize<S>(key_type: &KeyType, serializer: S) -> Result<S::Ok, S::Error>
49 where
50 S: Serializer,
51 {
52 let s = match key_type {
53 #[cfg(feature = "crypto-ed25519")]
54 KeyType::Ed25519 => "Ed25519",
55 #[cfg(feature = "crypto-p256")]
56 KeyType::P256 => "P256",
57 #[cfg(feature = "crypto-secp256k1")]
58 KeyType::Secp256k1 => "Secp256k1",
59 };
60 serializer.serialize_str(s)
61 }
62
63 pub fn deserialize<'de, D>(deserializer: D) -> Result<KeyType, D::Error>
64 where
65 D: Deserializer<'de>,
66 {
67 let s = String::deserialize(deserializer)?;
68 match s.as_str() {
69 #[cfg(feature = "crypto-ed25519")]
70 "Ed25519" => Ok(KeyType::Ed25519),
71 #[cfg(feature = "crypto-p256")]
72 "P256" => Ok(KeyType::P256),
73 #[cfg(feature = "crypto-secp256k1")]
74 "Secp256k1" => Ok(KeyType::Secp256k1),
75 _ => Err(serde::de::Error::custom(format!(
76 "Unknown or disabled key type: {}",
77 s
78 ))),
79 }
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use serial_test::serial;
87 use std::env;
88 use tempfile::TempDir;
89
90 #[test]
91 #[serial]
92 fn test_tap_home_environment_variable() {
93 let old_home = env::var("TAP_HOME").ok();
95 let old_test = env::var("TAP_TEST_DIR").ok();
96
97 env::remove_var("TAP_HOME");
99 env::remove_var("TAP_TEST_DIR");
100
101 let temp_dir = TempDir::new().unwrap();
103 let temp_path = temp_dir.path().to_path_buf();
104
105 env::set_var("TAP_HOME", &temp_path);
107
108 let key_path = KeyStorage::default_key_path().unwrap();
110
111 assert_eq!(key_path, temp_path.join(DEFAULT_KEYS_FILE));
113
114 env::remove_var("TAP_HOME");
116 if let Some(val) = old_home {
117 env::set_var("TAP_HOME", val);
118 }
119 if let Some(val) = old_test {
120 env::set_var("TAP_TEST_DIR", val);
121 }
122 }
123
124 #[test]
125 #[serial]
126 fn test_tap_test_dir_environment_variable() {
127 let old_home = env::var("TAP_HOME").ok();
129 let old_test = env::var("TAP_TEST_DIR").ok();
130
131 env::remove_var("TAP_HOME");
133 env::remove_var("TAP_TEST_DIR");
134
135 let temp_dir = TempDir::new().unwrap();
137 let temp_path = temp_dir.path().to_path_buf();
138
139 env::set_var("TAP_TEST_DIR", &temp_path);
141
142 let key_path = KeyStorage::default_key_path().unwrap();
144
145 let expected_path = temp_path.join(DEFAULT_TAP_DIR).join(DEFAULT_KEYS_FILE);
147 assert_eq!(key_path, expected_path);
148
149 env::remove_var("TAP_TEST_DIR");
151 if let Some(val) = old_home {
152 env::set_var("TAP_HOME", val);
153 }
154 if let Some(val) = old_test {
155 env::set_var("TAP_TEST_DIR", val);
156 }
157
158 drop(temp_dir);
160 }
161
162 #[test]
163 #[serial]
164 fn test_environment_variable_priority() {
165 let old_home = env::var("TAP_HOME").ok();
167 let old_test = env::var("TAP_TEST_DIR").ok();
168
169 let home_dir = TempDir::new().unwrap();
171 let test_dir = TempDir::new().unwrap();
172
173 let home_path = home_dir.path().to_path_buf();
174 let test_path = test_dir.path().to_path_buf();
175
176 env::set_var("TAP_HOME", &home_path);
178 env::set_var("TAP_TEST_DIR", &test_path);
179
180 let key_path = KeyStorage::default_key_path().unwrap();
182
183 assert_eq!(key_path, home_path.join(DEFAULT_KEYS_FILE));
185
186 env::remove_var("TAP_HOME");
188 env::remove_var("TAP_TEST_DIR");
189 if let Some(val) = old_home {
190 env::set_var("TAP_HOME", val);
191 }
192 if let Some(val) = old_test {
193 env::set_var("TAP_TEST_DIR", val);
194 }
195 }
196
197 #[test]
198 #[serial]
199 fn test_agent_directory_with_tap_home() {
200 let old_home = env::var("TAP_HOME").ok();
202 let old_test = env::var("TAP_TEST_DIR").ok();
203
204 env::remove_var("TAP_HOME");
206 env::remove_var("TAP_TEST_DIR");
207
208 let temp_dir = TempDir::new().unwrap();
210 let temp_path = temp_dir.path().to_path_buf();
211
212 env::set_var("TAP_HOME", &temp_path);
214
215 let storage = KeyStorage::new();
217
218 let sanitized = sanitize_did("did:key:test123");
220 let agent_dir = storage.get_agent_directory(&sanitized).unwrap();
221
222 assert_eq!(agent_dir, temp_path.join("did_key_test123"));
224
225 env::remove_var("TAP_HOME");
227 if let Some(val) = old_home {
228 env::set_var("TAP_HOME", val);
229 }
230 if let Some(val) = old_test {
231 env::set_var("TAP_TEST_DIR", val);
232 }
233 }
234
235 #[test]
236 #[serial]
237 fn test_storage_persistence_with_temp_dir() {
238 use crate::test_utils::TestStorage;
239
240 let test_storage = TestStorage::new().unwrap();
242
243 let mut storage = KeyStorage::new();
245 storage.add_key(StoredKey {
246 did: "did:key:test".to_string(),
247 label: "test-key".to_string(),
248 key_type: KeyType::Ed25519,
249 private_key: "test-private".to_string(),
250 public_key: "test-public".to_string(),
251 metadata: HashMap::new(),
252 });
253
254 test_storage.save(&storage).unwrap();
256
257 assert!(
259 test_storage.path().exists(),
260 "Keys file should exist at: {:?}",
261 test_storage.path()
262 );
263
264 let loaded = test_storage.load().unwrap();
266 assert_eq!(
267 loaded.keys.len(),
268 1,
269 "Should have exactly 1 key in loaded storage"
270 );
271 assert!(
272 loaded.keys.contains_key("did:key:test"),
273 "Should contain the test key"
274 );
275 }
276
277 #[cfg(unix)]
278 #[test]
279 #[serial]
280 fn test_key_storage_file_permissions() {
281 use crate::test_utils::TestStorage;
282 use std::os::unix::fs::PermissionsExt;
283
284 let test_storage = TestStorage::new().unwrap();
286
287 let mut storage = KeyStorage::new();
289 storage.add_key(StoredKey {
290 did: "did:key:test".to_string(),
291 label: "test-key".to_string(),
292 key_type: KeyType::Ed25519,
293 private_key: "test-private-key-material".to_string(),
294 public_key: "test-public".to_string(),
295 metadata: HashMap::new(),
296 });
297
298 test_storage.save(&storage).unwrap();
300
301 let metadata = fs::metadata(test_storage.path()).unwrap();
303 let permissions = metadata.permissions();
304 let mode = permissions.mode() & 0o777; assert_eq!(
307 mode, 0o600,
308 "Key storage file should have permissions 0o600 (owner read/write only), got {:o}",
309 mode
310 );
311 }
312}
313
314#[derive(Debug, Clone, Serialize, Deserialize, Default)]
316pub struct KeyStorage {
317 pub keys: HashMap<String, StoredKey>,
319 pub default_did: Option<String>,
321 #[serde(default = "chrono::Utc::now")]
323 pub created_at: chrono::DateTime<chrono::Utc>,
324 #[serde(default = "chrono::Utc::now")]
326 pub updated_at: chrono::DateTime<chrono::Utc>,
327 #[serde(skip)]
329 base_directory: Option<PathBuf>,
330}
331
332impl KeyStorage {
333 pub fn new() -> Self {
335 Default::default()
336 }
337
338 pub fn add_key(&mut self, mut key: StoredKey) {
340 if key.label.is_empty() {
342 key.label = self.generate_default_label();
343 }
344
345 let final_label = self.ensure_unique_label(&key.label, Some(&key.did));
347 key.label = final_label.clone();
348
349 if self.keys.is_empty() {
351 self.default_did = Some(key.did.clone());
352 }
353
354 self.keys.insert(key.did.clone(), key);
355 self.updated_at = chrono::Utc::now();
356 }
357
358 fn generate_default_label(&self) -> String {
360 let mut counter = 1;
361 loop {
362 let label = format!("agent-{}", counter);
363 if !self.keys.values().any(|key| key.label == label) {
364 return label;
365 }
366 counter += 1;
367 }
368 }
369
370 fn ensure_unique_label(&self, desired_label: &str, exclude_did: Option<&str>) -> String {
372 if let Some(existing_key) = self.keys.values().find(|key| key.label == desired_label) {
374 if exclude_did.is_some() && existing_key.did == exclude_did.unwrap() {
376 return desired_label.to_string();
377 }
378 } else {
379 return desired_label.to_string();
381 }
382
383 let mut counter = 2;
385 loop {
386 let new_label = format!("{}-{}", desired_label, counter);
387 if !self.keys.values().any(|key| key.label == new_label) {
388 return new_label;
389 }
390 counter += 1;
391 }
392 }
393
394 pub fn find_by_label(&self, label: &str) -> Option<&StoredKey> {
396 self.keys.values().find(|key| key.label == label)
397 }
398
399 pub fn update_label(&mut self, did: &str, new_label: &str) -> Result<()> {
401 if !self.keys.contains_key(did) {
403 return Err(Error::Storage(format!("Key with DID '{}' not found", did)));
404 }
405
406 let final_label = self.ensure_unique_label(new_label, Some(did));
408
409 if let Some(key) = self.keys.get_mut(did) {
411 key.label = final_label;
412 }
413
414 self.updated_at = chrono::Utc::now();
415 Ok(())
416 }
417
418 pub fn default_key_path() -> Option<PathBuf> {
420 if let Ok(tap_home) = env::var("TAP_HOME") {
422 return Some(PathBuf::from(tap_home).join(DEFAULT_KEYS_FILE));
423 }
424
425 if let Ok(test_dir) = env::var("TAP_TEST_DIR") {
427 return Some(
428 PathBuf::from(test_dir)
429 .join(DEFAULT_TAP_DIR)
430 .join(DEFAULT_KEYS_FILE),
431 );
432 }
433
434 home_dir().map(|home| home.join(DEFAULT_TAP_DIR).join(DEFAULT_KEYS_FILE))
436 }
437
438 pub fn load_default() -> Result<Self> {
440 let path = Self::default_key_path().ok_or_else(|| {
441 Error::Storage("Could not determine home directory for default key path".to_string())
442 })?;
443 Self::load_from_path(&path)
444 }
445
446 pub fn load_from_path(path: &Path) -> Result<Self> {
448 let mut storage = if !path.exists() {
449 Self::new()
450 } else {
451 let contents = fs::read_to_string(path)
452 .map_err(|e| Error::Storage(format!("Failed to read key storage file: {}", e)))?;
453
454 let mut storage: KeyStorage = serde_json::from_str(&contents)
455 .map_err(|e| Error::Storage(format!("Failed to parse key storage file: {}", e)))?;
456
457 storage.ensure_all_keys_have_labels();
459
460 storage
461 };
462
463 if let Some(parent) = path.parent() {
465 storage.base_directory = Some(parent.to_path_buf());
466 }
467
468 Ok(storage)
469 }
470
471 fn ensure_all_keys_have_labels(&mut self) {
473 let mut keys_to_update = Vec::new();
474
475 for (did, key) in &self.keys {
476 if key.label.is_empty() {
477 keys_to_update.push(did.clone());
478 }
479 }
480
481 for did in keys_to_update {
482 let new_label = self.generate_default_label();
483 if let Some(key) = self.keys.get_mut(&did) {
484 key.label = new_label;
485 }
486 }
487 }
488
489 pub fn save_default(&self) -> Result<()> {
491 let path = Self::default_key_path().ok_or_else(|| {
492 Error::Storage("Could not determine home directory for default key path".to_string())
493 })?;
494
495 if let Some(parent) = path.parent() {
497 fs::create_dir_all(parent).map_err(|e| {
498 Error::Storage(format!("Failed to create key storage directory: {}", e))
499 })?;
500 }
501
502 self.save_to_path(&path)
503 }
504
505 pub fn save_to_path(&self, path: &Path) -> Result<()> {
510 let contents = serde_json::to_string_pretty(self)
511 .map_err(|e| Error::Storage(format!("Failed to serialize key storage: {}", e)))?;
512
513 fs::write(path, &contents)
514 .map_err(|e| Error::Storage(format!("Failed to write key storage file: {}", e)))?;
515
516 set_secure_file_permissions(path)?;
518
519 Ok(())
520 }
521
522 pub fn from_generated_key(key: &GeneratedKey) -> StoredKey {
524 StoredKey {
525 did: key.did.clone(),
526 label: String::new(), key_type: key.key_type,
528 private_key: base64::engine::general_purpose::STANDARD.encode(&key.private_key),
529 public_key: base64::engine::general_purpose::STANDARD.encode(&key.public_key),
530 metadata: HashMap::new(),
531 }
532 }
533
534 pub fn from_generated_key_with_label(key: &GeneratedKey, label: &str) -> StoredKey {
536 StoredKey {
537 did: key.did.clone(),
538 label: label.to_string(),
539 key_type: key.key_type,
540 private_key: base64::engine::general_purpose::STANDARD.encode(&key.private_key),
541 public_key: base64::engine::general_purpose::STANDARD.encode(&key.public_key),
542 metadata: HashMap::new(),
543 }
544 }
545
546 pub fn to_secret(key: &StoredKey) -> Secret {
548 Secret {
549 id: key.did.clone(),
550 type_: SecretType::JsonWebKey2020,
551 secret_material: SecretMaterial::JWK {
552 private_key_jwk: generate_jwk_for_key(key),
553 },
554 }
555 }
556 pub fn create_agent_directory(
561 &self,
562 did: &str,
563 policies: &[String],
564 metadata: &HashMap<String, String>,
565 ) -> Result<()> {
566 let sanitized_did = sanitize_did(did);
567 let agent_dir = self.get_agent_directory(&sanitized_did)?;
568
569 fs::create_dir_all(&agent_dir).map_err(|e| {
571 Error::Storage(format!(
572 "Failed to create agent directory {}: {}",
573 agent_dir.display(),
574 e
575 ))
576 })?;
577
578 #[cfg(unix)]
580 {
581 use std::os::unix::fs::PermissionsExt;
582 let dir_permissions = fs::Permissions::from_mode(0o700);
583 fs::set_permissions(&agent_dir, dir_permissions).map_err(|e| {
584 Error::Storage(format!("Failed to set agent directory permissions: {}", e))
585 })?;
586 }
587
588 let policies_file = agent_dir.join("policies.json");
590 let policies_json = serde_json::to_string_pretty(policies)
591 .map_err(|e| Error::Storage(format!("Failed to serialize policies: {}", e)))?;
592 fs::write(&policies_file, &policies_json)
593 .map_err(|e| Error::Storage(format!("Failed to write policies file: {}", e)))?;
594 set_secure_file_permissions(&policies_file)?;
595
596 let metadata_file = agent_dir.join("metadata.json");
598 let metadata_json = serde_json::to_string_pretty(metadata)
599 .map_err(|e| Error::Storage(format!("Failed to serialize metadata: {}", e)))?;
600 fs::write(&metadata_file, &metadata_json)
601 .map_err(|e| Error::Storage(format!("Failed to write metadata file: {}", e)))?;
602 set_secure_file_permissions(&metadata_file)?;
603
604 Ok(())
605 }
606
607 pub fn load_agent_policies(&self, did: &str) -> Result<Vec<String>> {
609 let sanitized_did = sanitize_did(did);
610 let agent_dir = self.get_agent_directory(&sanitized_did)?;
611 let policies_file = agent_dir.join("policies.json");
612
613 if !policies_file.exists() {
614 return Ok(vec![]);
615 }
616
617 let content = fs::read_to_string(&policies_file)
618 .map_err(|e| Error::Storage(format!("Failed to read policies file: {}", e)))?;
619
620 serde_json::from_str(&content)
621 .map_err(|e| Error::Storage(format!("Failed to parse policies file: {}", e)))
622 }
623
624 pub fn load_agent_metadata(&self, did: &str) -> Result<HashMap<String, String>> {
626 let sanitized_did = sanitize_did(did);
627 let agent_dir = self.get_agent_directory(&sanitized_did)?;
628 let metadata_file = agent_dir.join("metadata.json");
629
630 if !metadata_file.exists() {
631 return Ok(HashMap::new());
632 }
633
634 let content = fs::read_to_string(&metadata_file)
635 .map_err(|e| Error::Storage(format!("Failed to read metadata file: {}", e)))?;
636
637 serde_json::from_str(&content)
638 .map_err(|e| Error::Storage(format!("Failed to parse metadata file: {}", e)))
639 }
640
641 fn get_agent_directory(&self, sanitized_did: &str) -> Result<PathBuf> {
643 let base_dir = if let Some(ref base) = self.base_directory {
644 base.clone()
645 } else {
646 if let Ok(tap_home) = env::var("TAP_HOME") {
648 PathBuf::from(tap_home)
649 } else if let Ok(test_dir) = env::var("TAP_TEST_DIR") {
650 PathBuf::from(test_dir).join(DEFAULT_TAP_DIR)
652 } else {
653 let home = home_dir().ok_or_else(|| {
654 Error::Storage("Could not determine home directory".to_string())
655 })?;
656 home.join(DEFAULT_TAP_DIR)
657 }
658 };
659 Ok(base_dir.join(sanitized_did))
660 }
661}
662
663fn sanitize_did(did: &str) -> String {
665 did.replace(':', "_")
666}
667
668#[allow(unused_variables)]
672fn set_secure_file_permissions(path: &Path) -> Result<()> {
673 #[cfg(unix)]
674 {
675 use std::os::unix::fs::PermissionsExt;
676 let permissions = fs::Permissions::from_mode(0o600);
677 fs::set_permissions(path, permissions).map_err(|e| {
678 Error::Storage(format!(
679 "Failed to set secure permissions on {}: {}",
680 path.display(),
681 e
682 ))
683 })?;
684 }
685 Ok(())
686}
687
688fn generate_jwk_for_key(key: &StoredKey) -> serde_json::Value {
690 let kid = if key.did.starts_with("did:key:") {
692 let key_part = &key.did[8..]; format!("{}#{}", key.did, key_part)
696 } else {
697 format!("{}#keys-1", key.did)
699 };
700
701 match key.key_type {
702 #[cfg(feature = "crypto-ed25519")]
703 KeyType::Ed25519 => {
704 serde_json::json!({
705 "kty": "OKP",
706 "crv": "Ed25519",
707 "x": key.public_key,
708 "d": key.private_key,
709 "kid": kid
710 })
711 }
712 #[cfg(feature = "crypto-p256")]
713 KeyType::P256 => {
714 serde_json::json!({
715 "kty": "EC",
716 "crv": "P-256",
717 "x": key.public_key,
718 "d": key.private_key,
719 "kid": kid
720 })
721 }
722 #[cfg(feature = "crypto-secp256k1")]
723 KeyType::Secp256k1 => {
724 serde_json::json!({
725 "kty": "EC",
726 "crv": "secp256k1",
727 "x": key.public_key,
728 "d": key.private_key,
729 "kid": kid
730 })
731 }
732 }
733}