chie_crypto/
key_formats.rs

1//! Standard key format support (DER, JWK, PKCS#8).
2//!
3//! This module provides import/export functionality for standard cryptographic
4//! key formats used in various protocols and applications.
5//!
6//! # Supported Formats
7//!
8//! - **DER**: Distinguished Encoding Rules (ASN.1 binary format)
9//! - **JWK**: JSON Web Key (RFC 7517)
10//! - **PKCS#8**: Public-Key Cryptography Standards #8
11//!
12//! # Example
13//!
14//! ```rust
15//! use chie_crypto::key_formats::{JwkKey, DerKey};
16//! use chie_crypto::signing::KeyPair;
17//!
18//! // Generate a keypair
19//! let keypair = KeyPair::generate();
20//!
21//! // Export to JWK
22//! let jwk = JwkKey::from_ed25519_keypair(&keypair);
23//! let jwk_json = jwk.to_json().unwrap();
24//!
25//! // Import from JWK
26//! let imported_jwk = JwkKey::from_json(&jwk_json).unwrap();
27//! let imported_keypair = imported_jwk.to_ed25519_keypair().unwrap();
28//! ```
29
30use crate::signing::{KeyPair, PublicKey, SecretKey};
31use serde::{Deserialize, Serialize};
32use thiserror::Error;
33
34/// Key format errors
35#[derive(Debug, Error, Clone, PartialEq, Eq)]
36pub enum KeyFormatError {
37    /// Invalid DER encoding
38    #[error("Invalid DER encoding: {0}")]
39    InvalidDer(String),
40
41    /// Invalid JWK format
42    #[error("Invalid JWK format: {0}")]
43    InvalidJwk(String),
44
45    /// Unsupported key type
46    #[error("Unsupported key type: {0}")]
47    UnsupportedKeyType(String),
48
49    /// Invalid key length
50    #[error("Invalid key length: expected {expected}, got {actual}")]
51    InvalidKeyLength { expected: usize, actual: usize },
52
53    /// Serialization error
54    #[error("Serialization error: {0}")]
55    SerializationError(String),
56
57    /// Missing required field
58    #[error("Missing required field: {0}")]
59    MissingField(String),
60}
61
62/// Result type for key format operations
63pub type KeyFormatResult<T> = Result<T, KeyFormatError>;
64
65/// JSON Web Key (JWK) representation
66///
67/// Implements RFC 7517 for Ed25519 keys (EdDSA curve).
68#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
69pub struct JwkKey {
70    /// Key type (always "OKP" for EdDSA)
71    pub kty: String,
72
73    /// Curve (always "Ed25519")
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub crv: Option<String>,
76
77    /// Public key (base64url encoded)
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub x: Option<String>,
80
81    /// Private key (base64url encoded)
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub d: Option<String>,
84
85    /// Key usage
86    #[serde(rename = "use", skip_serializing_if = "Option::is_none")]
87    pub key_use: Option<String>,
88
89    /// Key ID
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub kid: Option<String>,
92
93    /// Algorithm
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub alg: Option<String>,
96}
97
98impl JwkKey {
99    /// Create a JWK from an Ed25519 keypair
100    pub fn from_ed25519_keypair(keypair: &KeyPair) -> Self {
101        let public_key = keypair.public_key();
102        let secret_key = keypair.secret_key();
103
104        Self {
105            kty: "OKP".to_string(),
106            crv: Some("Ed25519".to_string()),
107            x: Some(base64_url_encode(&public_key)),
108            d: Some(base64_url_encode(&secret_key)),
109            key_use: Some("sig".to_string()),
110            kid: None,
111            alg: Some("EdDSA".to_string()),
112        }
113    }
114
115    /// Create a JWK from an Ed25519 public key
116    pub fn from_ed25519_public_key(public_key: &PublicKey) -> Self {
117        Self {
118            kty: "OKP".to_string(),
119            crv: Some("Ed25519".to_string()),
120            x: Some(base64_url_encode(public_key)),
121            d: None,
122            key_use: Some("sig".to_string()),
123            kid: None,
124            alg: Some("EdDSA".to_string()),
125        }
126    }
127
128    /// Convert JWK to Ed25519 keypair
129    pub fn to_ed25519_keypair(&self) -> KeyFormatResult<KeyPair> {
130        // Validate key type
131        if self.kty != "OKP" {
132            return Err(KeyFormatError::UnsupportedKeyType(self.kty.clone()));
133        }
134
135        // Validate curve
136        if let Some(crv) = &self.crv {
137            if crv != "Ed25519" {
138                return Err(KeyFormatError::UnsupportedKeyType(crv.clone()));
139            }
140        }
141
142        // Extract public key
143        let x = self
144            .x
145            .as_ref()
146            .ok_or_else(|| KeyFormatError::MissingField("x".to_string()))?;
147        let public_bytes =
148            base64_url_decode(x).map_err(|e| KeyFormatError::InvalidJwk(e.to_string()))?;
149
150        if public_bytes.len() != 32 {
151            return Err(KeyFormatError::InvalidKeyLength {
152                expected: 32,
153                actual: public_bytes.len(),
154            });
155        }
156
157        // Extract private key
158        let d = self
159            .d
160            .as_ref()
161            .ok_or_else(|| KeyFormatError::MissingField("d".to_string()))?;
162        let secret_bytes =
163            base64_url_decode(d).map_err(|e| KeyFormatError::InvalidJwk(e.to_string()))?;
164
165        if secret_bytes.len() != 32 {
166            return Err(KeyFormatError::InvalidKeyLength {
167                expected: 32,
168                actual: secret_bytes.len(),
169            });
170        }
171
172        // Create keypair
173        let mut secret_key = [0u8; 32];
174        secret_key.copy_from_slice(&secret_bytes);
175
176        KeyPair::from_secret_key(&secret_key)
177            .map_err(|_| KeyFormatError::InvalidJwk("Invalid secret key".to_string()))
178    }
179
180    /// Convert JWK to Ed25519 public key
181    pub fn to_ed25519_public_key(&self) -> KeyFormatResult<PublicKey> {
182        // Validate key type
183        if self.kty != "OKP" {
184            return Err(KeyFormatError::UnsupportedKeyType(self.kty.clone()));
185        }
186
187        // Extract public key
188        let x = self
189            .x
190            .as_ref()
191            .ok_or_else(|| KeyFormatError::MissingField("x".to_string()))?;
192        let public_bytes =
193            base64_url_decode(x).map_err(|e| KeyFormatError::InvalidJwk(e.to_string()))?;
194
195        if public_bytes.len() != 32 {
196            return Err(KeyFormatError::InvalidKeyLength {
197                expected: 32,
198                actual: public_bytes.len(),
199            });
200        }
201
202        let mut public_key = [0u8; 32];
203        public_key.copy_from_slice(&public_bytes);
204        Ok(public_key)
205    }
206
207    /// Serialize JWK to JSON string
208    pub fn to_json(&self) -> KeyFormatResult<String> {
209        serde_json::to_string_pretty(self)
210            .map_err(|e| KeyFormatError::SerializationError(e.to_string()))
211    }
212
213    /// Deserialize JWK from JSON string
214    pub fn from_json(json: &str) -> KeyFormatResult<Self> {
215        serde_json::from_str(json).map_err(|e| KeyFormatError::SerializationError(e.to_string()))
216    }
217
218    /// Set key ID
219    pub fn with_kid(mut self, kid: impl Into<String>) -> Self {
220        self.kid = Some(kid.into());
221        self
222    }
223}
224
225/// DER (Distinguished Encoding Rules) key representation
226///
227/// Simple DER encoding for Ed25519 keys.
228/// For production use, consider using a full ASN.1 library.
229pub struct DerKey;
230
231impl DerKey {
232    /// Encode Ed25519 public key to DER format (SubjectPublicKeyInfo)
233    ///
234    /// This creates a simple DER structure for Ed25519 public keys.
235    pub fn encode_ed25519_public_key(public_key: &PublicKey) -> Vec<u8> {
236        // Simple DER encoding for Ed25519 public key
237        // This is a simplified version; production code should use proper ASN.1 library
238        let mut der = Vec::with_capacity(44);
239
240        // SEQUENCE
241        der.push(0x30);
242        der.push(42); // Length
243
244        // AlgorithmIdentifier SEQUENCE
245        der.push(0x30);
246        der.push(5);
247
248        // OID for Ed25519 (1.3.101.112)
249        der.push(0x06);
250        der.push(3);
251        der.extend_from_slice(&[0x2B, 0x65, 0x70]);
252
253        // BIT STRING for public key
254        der.push(0x03);
255        der.push(33); // Length (32 + 1 for unused bits)
256        der.push(0x00); // No unused bits
257
258        // Public key bytes
259        der.extend_from_slice(public_key);
260
261        der
262    }
263
264    /// Decode Ed25519 public key from DER format (SubjectPublicKeyInfo)
265    pub fn decode_ed25519_public_key(der: &[u8]) -> KeyFormatResult<PublicKey> {
266        // Simple DER decoder
267        // This is a simplified version; production code should use proper ASN.1 library
268
269        if der.len() < 44 {
270            return Err(KeyFormatError::InvalidDer("DER data too short".to_string()));
271        }
272
273        // Verify SEQUENCE tag
274        if der[0] != 0x30 {
275            return Err(KeyFormatError::InvalidDer(
276                "Expected SEQUENCE tag".to_string(),
277            ));
278        }
279
280        // Find BIT STRING with public key
281        // Skip to the public key part (simplified parsing)
282        let key_start = der.len() - 32;
283        if key_start >= der.len() {
284            return Err(KeyFormatError::InvalidDer(
285                "Invalid DER structure".to_string(),
286            ));
287        }
288
289        let mut public_key = [0u8; 32];
290        public_key.copy_from_slice(&der[key_start..]);
291
292        Ok(public_key)
293    }
294
295    /// Encode Ed25519 private key to DER format (PKCS#8)
296    ///
297    /// Creates a PKCS#8 PrivateKeyInfo structure for Ed25519.
298    pub fn encode_ed25519_private_key(secret_key: &SecretKey) -> Vec<u8> {
299        // Simple PKCS#8 encoding for Ed25519 private key
300        let mut der = Vec::with_capacity(48);
301
302        // SEQUENCE
303        der.push(0x30);
304        der.push(46); // Length
305
306        // Version (0)
307        der.push(0x02);
308        der.push(0x01);
309        der.push(0x00);
310
311        // AlgorithmIdentifier SEQUENCE
312        der.push(0x30);
313        der.push(5);
314
315        // OID for Ed25519
316        der.push(0x06);
317        der.push(3);
318        der.extend_from_slice(&[0x2B, 0x65, 0x70]);
319
320        // OCTET STRING containing private key
321        der.push(0x04);
322        der.push(34); // Length
323
324        // Inner OCTET STRING for the actual key
325        der.push(0x04);
326        der.push(32);
327        der.extend_from_slice(secret_key);
328
329        der
330    }
331
332    /// Decode Ed25519 private key from DER format (PKCS#8)
333    pub fn decode_ed25519_private_key(der: &[u8]) -> KeyFormatResult<SecretKey> {
334        if der.len() < 48 {
335            return Err(KeyFormatError::InvalidDer(
336                "DER data too short for private key".to_string(),
337            ));
338        }
339
340        // Find the private key octets (simplified parsing)
341        let key_start = der.len() - 32;
342        if key_start >= der.len() {
343            return Err(KeyFormatError::InvalidDer(
344                "Invalid DER structure".to_string(),
345            ));
346        }
347
348        let mut secret_key = [0u8; 32];
349        secret_key.copy_from_slice(&der[key_start..]);
350
351        Ok(secret_key)
352    }
353}
354
355/// Base64url encoding (URL-safe, no padding)
356fn base64_url_encode(data: &[u8]) -> String {
357    use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
358    URL_SAFE_NO_PAD.encode(data)
359}
360
361/// Base64url decoding
362fn base64_url_decode(data: &str) -> Result<Vec<u8>, String> {
363    use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
364    URL_SAFE_NO_PAD.decode(data).map_err(|e| e.to_string())
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370
371    #[test]
372    fn test_jwk_keypair_roundtrip() {
373        let keypair = KeyPair::generate();
374
375        // Convert to JWK and back
376        let jwk = JwkKey::from_ed25519_keypair(&keypair);
377        let restored = jwk.to_ed25519_keypair().unwrap();
378
379        // Keys should match
380        assert_eq!(keypair.public_key(), restored.public_key());
381        assert_eq!(keypair.secret_key(), restored.secret_key());
382    }
383
384    #[test]
385    fn test_jwk_public_key_roundtrip() {
386        let keypair = KeyPair::generate();
387        let public_key = keypair.public_key();
388
389        // Convert to JWK and back
390        let jwk = JwkKey::from_ed25519_public_key(&public_key);
391        let restored = jwk.to_ed25519_public_key().unwrap();
392
393        assert_eq!(public_key, restored);
394    }
395
396    #[test]
397    fn test_jwk_json_serialization() {
398        let keypair = KeyPair::generate();
399        let jwk = JwkKey::from_ed25519_keypair(&keypair);
400
401        // Serialize to JSON and back
402        let json = jwk.to_json().unwrap();
403        let restored = JwkKey::from_json(&json).unwrap();
404
405        assert_eq!(jwk, restored);
406    }
407
408    #[test]
409    fn test_jwk_with_kid() {
410        let keypair = KeyPair::generate();
411        let jwk = JwkKey::from_ed25519_keypair(&keypair).with_kid("my-key-id");
412
413        assert_eq!(jwk.kid, Some("my-key-id".to_string()));
414    }
415
416    #[test]
417    fn test_jwk_validation() {
418        // Invalid key type
419        let invalid_jwk = JwkKey {
420            kty: "RSA".to_string(),
421            crv: None,
422            x: None,
423            d: None,
424            key_use: None,
425            kid: None,
426            alg: None,
427        };
428
429        assert!(invalid_jwk.to_ed25519_keypair().is_err());
430    }
431
432    #[test]
433    fn test_der_public_key_roundtrip() {
434        let keypair = KeyPair::generate();
435        let public_key = keypair.public_key();
436
437        // Encode to DER and decode back
438        let der = DerKey::encode_ed25519_public_key(&public_key);
439        let restored = DerKey::decode_ed25519_public_key(&der).unwrap();
440
441        assert_eq!(public_key, restored);
442    }
443
444    #[test]
445    fn test_der_private_key_roundtrip() {
446        let keypair = KeyPair::generate();
447        let secret_key = keypair.secret_key();
448
449        // Encode to DER and decode back
450        let der = DerKey::encode_ed25519_private_key(&secret_key);
451        let restored = DerKey::decode_ed25519_private_key(&der).unwrap();
452
453        assert_eq!(secret_key, restored);
454    }
455
456    #[test]
457    fn test_der_public_key_structure() {
458        let keypair = KeyPair::generate();
459        let der = DerKey::encode_ed25519_public_key(&keypair.public_key());
460
461        // Verify it starts with SEQUENCE tag
462        assert_eq!(der[0], 0x30);
463
464        // Verify it contains Ed25519 OID
465        assert!(der.windows(3).any(|w| w == [0x2B, 0x65, 0x70]));
466    }
467
468    #[test]
469    fn test_base64_url_encoding() {
470        let data = b"Hello, World!";
471        let encoded = base64_url_encode(data);
472        let decoded = base64_url_decode(&encoded).unwrap();
473
474        assert_eq!(data, &decoded[..]);
475
476        // URL-safe encoding shouldn't contain +, /, or =
477        assert!(!encoded.contains('+'));
478        assert!(!encoded.contains('/'));
479        assert!(!encoded.contains('='));
480    }
481
482    #[test]
483    fn test_jwk_missing_fields() {
484        let jwk = JwkKey {
485            kty: "OKP".to_string(),
486            crv: Some("Ed25519".to_string()),
487            x: None, // Missing public key
488            d: Some("test".to_string()),
489            key_use: None,
490            kid: None,
491            alg: None,
492        };
493
494        assert!(jwk.to_ed25519_keypair().is_err());
495    }
496}