Skip to main content

tap_agent/
storage.rs

1//! Key storage functionality for TAP Agent
2//!
3//! This module provides utilities for persisting agent keys to disk
4//! and loading them later. This allows for persistent agent identities
5//! across multiple runs.
6
7use 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
18/// Default directory for TAP configuration and keys
19pub const DEFAULT_TAP_DIR: &str = ".tap";
20/// Default filename for the keys file
21pub const DEFAULT_KEYS_FILE: &str = "keys.json";
22
23/// A structure representing a stored key
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct StoredKey {
26    /// The DID for this key
27    pub did: String,
28    /// Human-friendly label for this key
29    #[serde(default)]
30    pub label: String,
31    /// The key type (e.g., Ed25519, P256)
32    #[serde(with = "key_type_serde")]
33    pub key_type: KeyType,
34    /// Base64-encoded private key
35    pub private_key: String,
36    /// Base64-encoded public key
37    pub public_key: String,
38    /// Optional metadata for this key
39    #[serde(default)]
40    pub metadata: HashMap<String, String>,
41}
42
43/// Serialization helper for KeyType
44mod 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        // Save current env vars
94        let old_home = env::var("TAP_HOME").ok();
95        let old_test = env::var("TAP_TEST_DIR").ok();
96
97        // Clear env vars
98        env::remove_var("TAP_HOME");
99        env::remove_var("TAP_TEST_DIR");
100
101        // Create a temporary directory
102        let temp_dir = TempDir::new().unwrap();
103        let temp_path = temp_dir.path().to_path_buf();
104
105        // Set TAP_HOME
106        env::set_var("TAP_HOME", &temp_path);
107
108        // Get the default key path
109        let key_path = KeyStorage::default_key_path().unwrap();
110
111        // Verify it uses TAP_HOME
112        assert_eq!(key_path, temp_path.join(DEFAULT_KEYS_FILE));
113
114        // Restore env vars
115        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        // Save current env vars
128        let old_home = env::var("TAP_HOME").ok();
129        let old_test = env::var("TAP_TEST_DIR").ok();
130
131        // Clear env vars
132        env::remove_var("TAP_HOME");
133        env::remove_var("TAP_TEST_DIR");
134
135        // Create a temporary directory and keep it alive
136        let temp_dir = TempDir::new().unwrap();
137        let temp_path = temp_dir.path().to_path_buf();
138
139        // Set TAP_TEST_DIR
140        env::set_var("TAP_TEST_DIR", &temp_path);
141
142        // Get the default key path
143        let key_path = KeyStorage::default_key_path().unwrap();
144
145        // Verify it uses TAP_TEST_DIR/.tap
146        let expected_path = temp_path.join(DEFAULT_TAP_DIR).join(DEFAULT_KEYS_FILE);
147        assert_eq!(key_path, expected_path);
148
149        // Restore env vars
150        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        // Keep temp_dir alive until the end of the test
159        drop(temp_dir);
160    }
161
162    #[test]
163    #[serial]
164    fn test_environment_variable_priority() {
165        // Save current env vars
166        let old_home = env::var("TAP_HOME").ok();
167        let old_test = env::var("TAP_TEST_DIR").ok();
168
169        // Create temporary directories
170        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        // Set both TAP_HOME and TAP_TEST_DIR
177        env::set_var("TAP_HOME", &home_path);
178        env::set_var("TAP_TEST_DIR", &test_path);
179
180        // Get the default key path
181        let key_path = KeyStorage::default_key_path().unwrap();
182
183        // Verify TAP_HOME takes priority
184        assert_eq!(key_path, home_path.join(DEFAULT_KEYS_FILE));
185
186        // Restore env vars
187        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        // Save current env vars
201        let old_home = env::var("TAP_HOME").ok();
202        let old_test = env::var("TAP_TEST_DIR").ok();
203
204        // Clear env vars
205        env::remove_var("TAP_HOME");
206        env::remove_var("TAP_TEST_DIR");
207
208        // Create a temporary directory
209        let temp_dir = TempDir::new().unwrap();
210        let temp_path = temp_dir.path().to_path_buf();
211
212        // Set TAP_HOME
213        env::set_var("TAP_HOME", &temp_path);
214
215        // Create a storage instance
216        let storage = KeyStorage::new();
217
218        // Get agent directory (function expects pre-sanitized DID)
219        let sanitized = sanitize_did("did:key:test123");
220        let agent_dir = storage.get_agent_directory(&sanitized).unwrap();
221
222        // Verify it uses TAP_HOME with sanitized DID
223        assert_eq!(agent_dir, temp_path.join("did_key_test123"));
224
225        // Restore env vars
226        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        // Use TestStorage for complete isolation - no environment variable manipulation needed
241        let test_storage = TestStorage::new().unwrap();
242
243        // Create and save storage using the isolated test environment
244        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        // Save using the test storage's path (isolated from global state)
255        test_storage.save(&storage).unwrap();
256
257        // Verify file was created
258        assert!(
259            test_storage.path().exists(),
260            "Keys file should exist at: {:?}",
261            test_storage.path()
262        );
263
264        // Load it back using the same isolated storage
265        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        // Use TestStorage for complete isolation
285        let test_storage = TestStorage::new().unwrap();
286
287        // Create and save storage
288        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        // Save to create the file
299        test_storage.save(&storage).unwrap();
300
301        // Verify file permissions are 0o600 (owner read/write only)
302        let metadata = fs::metadata(test_storage.path()).unwrap();
303        let permissions = metadata.permissions();
304        let mode = permissions.mode() & 0o777; // Mask to get just permission bits
305
306        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/// A collection of stored keys
315#[derive(Debug, Clone, Serialize, Deserialize, Default)]
316pub struct KeyStorage {
317    /// A map of DIDs to their stored keys
318    pub keys: HashMap<String, StoredKey>,
319    /// The default DID to use when not specified
320    pub default_did: Option<String>,
321    /// Creation timestamp
322    #[serde(default = "chrono::Utc::now")]
323    pub created_at: chrono::DateTime<chrono::Utc>,
324    /// Last update timestamp
325    #[serde(default = "chrono::Utc::now")]
326    pub updated_at: chrono::DateTime<chrono::Utc>,
327    /// Base directory for agent subdirectories (not serialized, runtime only)
328    #[serde(skip)]
329    base_directory: Option<PathBuf>,
330}
331
332impl KeyStorage {
333    /// Create a new empty key storage
334    pub fn new() -> Self {
335        Default::default()
336    }
337
338    /// Add a key to the storage
339    pub fn add_key(&mut self, mut key: StoredKey) {
340        // Generate default label if not provided
341        if key.label.is_empty() {
342            key.label = self.generate_default_label();
343        }
344
345        // Ensure label is unique
346        let final_label = self.ensure_unique_label(&key.label, Some(&key.did));
347        key.label = final_label.clone();
348
349        // If this is the first key, make it the default
350        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    /// Generate a default label in the format agent-{n}
359    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    /// Ensure a label is unique, modifying it if necessary
371    fn ensure_unique_label(&self, desired_label: &str, exclude_did: Option<&str>) -> String {
372        // Check if the label exists in any key
373        if let Some(existing_key) = self.keys.values().find(|key| key.label == desired_label) {
374            // If it belongs to the same DID we're updating, it's fine
375            if exclude_did.is_some() && existing_key.did == exclude_did.unwrap() {
376                return desired_label.to_string();
377            }
378        } else {
379            // Label doesn't exist, so it's available
380            return desired_label.to_string();
381        }
382
383        // Generate a unique label by appending a number
384        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    /// Find a key by label
395    pub fn find_by_label(&self, label: &str) -> Option<&StoredKey> {
396        self.keys.values().find(|key| key.label == label)
397    }
398
399    /// Update a key's label
400    pub fn update_label(&mut self, did: &str, new_label: &str) -> Result<()> {
401        // First ensure the key exists
402        if !self.keys.contains_key(did) {
403            return Err(Error::Storage(format!("Key with DID '{}' not found", did)));
404        }
405
406        // Ensure new label is unique
407        let final_label = self.ensure_unique_label(new_label, Some(did));
408
409        // Update the key's label
410        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    /// Get the default key path
419    pub fn default_key_path() -> Option<PathBuf> {
420        // Check for TAP_HOME environment variable first (useful for tests)
421        if let Ok(tap_home) = env::var("TAP_HOME") {
422            return Some(PathBuf::from(tap_home).join(DEFAULT_KEYS_FILE));
423        }
424
425        // Check for TAP_TEST_DIR environment variable (for tests/examples)
426        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        // Default to home directory
435        home_dir().map(|home| home.join(DEFAULT_TAP_DIR).join(DEFAULT_KEYS_FILE))
436    }
437
438    /// Load keys from the default location
439    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    /// Load keys from a specific path
447    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            // Ensure all keys have labels (for backward compatibility)
458            storage.ensure_all_keys_have_labels();
459
460            storage
461        };
462
463        // Set base directory from the path for agent subdirectories
464        if let Some(parent) = path.parent() {
465            storage.base_directory = Some(parent.to_path_buf());
466        }
467
468        Ok(storage)
469    }
470
471    /// Ensure all keys have labels (for backward compatibility)
472    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    /// Save keys to the default location
490    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        // Ensure directory exists
496        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    /// Save keys to a specific path
506    ///
507    /// On Unix systems, the file is created with restrictive permissions (0o600)
508    /// to protect private key material from unauthorized access.
509    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 restrictive permissions on Unix systems (owner read/write only)
517        set_secure_file_permissions(path)?;
518
519        Ok(())
520    }
521
522    /// Convert a GeneratedKey to a StoredKey
523    pub fn from_generated_key(key: &GeneratedKey) -> StoredKey {
524        StoredKey {
525            did: key.did.clone(),
526            label: String::new(), // Will be set when added to storage
527            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    /// Convert a GeneratedKey to a StoredKey with a specific label
535    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    /// Convert a StoredKey to a Secret
547    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    /// Create agent directory and save policies/metadata files
557    ///
558    /// On Unix systems, files are created with restrictive permissions (0o600)
559    /// to protect sensitive agent configuration from unauthorized access.
560    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        // Create the agent directory
570        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        // Set restrictive permissions on the agent directory (owner rwx only)
579        #[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        // Save policies.json
589        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        // Save metadata.json
597        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    /// Load policies from agent directory
608    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    /// Load metadata from agent directory
625    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    /// Get the agent directory path for a given DID
642    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            // Check for TAP_HOME environment variable first
647            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                // For tests, use TAP_TEST_DIR/.tap
651                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
663/// Sanitize a DID for use as a directory name (same as TAP Node)
664fn sanitize_did(did: &str) -> String {
665    did.replace(':', "_")
666}
667
668/// Set restrictive file permissions (owner read/write only) on Unix systems
669///
670/// This is a no-op on non-Unix systems.
671#[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
688/// Generate a JWK for a stored key
689fn generate_jwk_for_key(key: &StoredKey) -> serde_json::Value {
690    // Generate the proper key ID based on DID type
691    let kid = if key.did.starts_with("did:key:") {
692        // For did:key, extract the multibase key and use it as fragment
693        // did:key:z6Mk... -> did:key:z6Mk...#z6Mk...
694        let key_part = &key.did[8..]; // Skip "did:key:"
695        format!("{}#{}", key.did, key_part)
696    } else {
697        // For other DID methods, use #keys-1 as default
698        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}