chie_crypto/
keyserde.rs

1//! Key serialization and deserialization utilities.
2//!
3//! Provides import/export of keys in various formats:
4//! - PEM format for Ed25519 keys
5//! - Hex encoding
6//! - Base64 encoding
7
8use crate::signing::{KeyPair, PublicKey, SecretKey, SigningError};
9use thiserror::Error;
10
11/// Key serialization error.
12#[derive(Debug, Error)]
13pub enum KeySerdeError {
14    #[error("Invalid PEM format")]
15    InvalidPemFormat,
16
17    #[error("Invalid base64 encoding: {0}")]
18    InvalidBase64(String),
19
20    #[error("Invalid hex encoding: {0}")]
21    InvalidHex(String),
22
23    #[error("Invalid key length: expected {expected}, got {actual}")]
24    InvalidKeyLength { expected: usize, actual: usize },
25
26    #[error("Signing error: {0}")]
27    SigningError(#[from] SigningError),
28
29    #[error("Unknown key type: {0}")]
30    UnknownKeyType(String),
31}
32
33/// PEM header for Ed25519 private keys.
34const ED25519_PRIVATE_KEY_HEADER: &str = "-----BEGIN ED25519 PRIVATE KEY-----";
35const ED25519_PRIVATE_KEY_FOOTER: &str = "-----END ED25519 PRIVATE KEY-----";
36
37/// PEM header for Ed25519 public keys.
38const ED25519_PUBLIC_KEY_HEADER: &str = "-----BEGIN ED25519 PUBLIC KEY-----";
39const ED25519_PUBLIC_KEY_FOOTER: &str = "-----END ED25519 PUBLIC KEY-----";
40
41/// Key serializer for various formats.
42pub struct KeySerializer;
43
44impl KeySerializer {
45    // ========================================================================
46    // Hex encoding
47    // ========================================================================
48
49    /// Encode a secret key as hexadecimal string.
50    pub fn secret_key_to_hex(key: &SecretKey) -> String {
51        hex::encode(key)
52    }
53
54    /// Decode a secret key from hexadecimal string.
55    pub fn secret_key_from_hex(hex_str: &str) -> Result<SecretKey, KeySerdeError> {
56        let bytes = hex::decode(hex_str).map_err(|e| KeySerdeError::InvalidHex(e.to_string()))?;
57
58        if bytes.len() != 32 {
59            return Err(KeySerdeError::InvalidKeyLength {
60                expected: 32,
61                actual: bytes.len(),
62            });
63        }
64
65        let mut key = [0u8; 32];
66        key.copy_from_slice(&bytes);
67        Ok(key)
68    }
69
70    /// Encode a public key as hexadecimal string.
71    pub fn public_key_to_hex(key: &PublicKey) -> String {
72        hex::encode(key)
73    }
74
75    /// Decode a public key from hexadecimal string.
76    pub fn public_key_from_hex(hex_str: &str) -> Result<PublicKey, KeySerdeError> {
77        let bytes = hex::decode(hex_str).map_err(|e| KeySerdeError::InvalidHex(e.to_string()))?;
78
79        if bytes.len() != 32 {
80            return Err(KeySerdeError::InvalidKeyLength {
81                expected: 32,
82                actual: bytes.len(),
83            });
84        }
85
86        let mut key = [0u8; 32];
87        key.copy_from_slice(&bytes);
88        Ok(key)
89    }
90
91    // ========================================================================
92    // Base64 encoding
93    // ========================================================================
94
95    /// Encode a secret key as base64 string.
96    pub fn secret_key_to_base64(key: &SecretKey) -> String {
97        base64_encode(key)
98    }
99
100    /// Decode a secret key from base64 string.
101    pub fn secret_key_from_base64(b64_str: &str) -> Result<SecretKey, KeySerdeError> {
102        let bytes = base64_decode(b64_str)?;
103
104        if bytes.len() != 32 {
105            return Err(KeySerdeError::InvalidKeyLength {
106                expected: 32,
107                actual: bytes.len(),
108            });
109        }
110
111        let mut key = [0u8; 32];
112        key.copy_from_slice(&bytes);
113        Ok(key)
114    }
115
116    /// Encode a public key as base64 string.
117    pub fn public_key_to_base64(key: &PublicKey) -> String {
118        base64_encode(key)
119    }
120
121    /// Decode a public key from base64 string.
122    pub fn public_key_from_base64(b64_str: &str) -> Result<PublicKey, KeySerdeError> {
123        let bytes = base64_decode(b64_str)?;
124
125        if bytes.len() != 32 {
126            return Err(KeySerdeError::InvalidKeyLength {
127                expected: 32,
128                actual: bytes.len(),
129            });
130        }
131
132        let mut key = [0u8; 32];
133        key.copy_from_slice(&bytes);
134        Ok(key)
135    }
136
137    // ========================================================================
138    // PEM encoding
139    // ========================================================================
140
141    /// Export a key pair to PEM format (private key).
142    pub fn keypair_to_pem(keypair: &KeyPair) -> String {
143        let secret = keypair.secret_key();
144        Self::secret_key_to_pem(&secret)
145    }
146
147    /// Export a secret key to PEM format.
148    pub fn secret_key_to_pem(key: &SecretKey) -> String {
149        let b64 = base64_encode(key);
150        let wrapped = wrap_base64(&b64, 64);
151        format!(
152            "{}\n{}\n{}",
153            ED25519_PRIVATE_KEY_HEADER, wrapped, ED25519_PRIVATE_KEY_FOOTER
154        )
155    }
156
157    /// Import a key pair from PEM format.
158    pub fn keypair_from_pem(pem: &str) -> Result<KeyPair, KeySerdeError> {
159        let secret = Self::secret_key_from_pem(pem)?;
160        KeyPair::from_secret_key(&secret).map_err(KeySerdeError::SigningError)
161    }
162
163    /// Import a secret key from PEM format.
164    pub fn secret_key_from_pem(pem: &str) -> Result<SecretKey, KeySerdeError> {
165        let pem = pem.trim();
166
167        if !pem.starts_with(ED25519_PRIVATE_KEY_HEADER) {
168            return Err(KeySerdeError::InvalidPemFormat);
169        }
170        if !pem.ends_with(ED25519_PRIVATE_KEY_FOOTER) {
171            return Err(KeySerdeError::InvalidPemFormat);
172        }
173
174        // Extract base64 content
175        let content = pem
176            .strip_prefix(ED25519_PRIVATE_KEY_HEADER)
177            .and_then(|s| s.strip_suffix(ED25519_PRIVATE_KEY_FOOTER))
178            .ok_or(KeySerdeError::InvalidPemFormat)?;
179
180        // Remove whitespace
181        let b64: String = content.chars().filter(|c| !c.is_whitespace()).collect();
182
183        Self::secret_key_from_base64(&b64)
184    }
185
186    /// Export a public key to PEM format.
187    pub fn public_key_to_pem(key: &PublicKey) -> String {
188        let b64 = base64_encode(key);
189        let wrapped = wrap_base64(&b64, 64);
190        format!(
191            "{}\n{}\n{}",
192            ED25519_PUBLIC_KEY_HEADER, wrapped, ED25519_PUBLIC_KEY_FOOTER
193        )
194    }
195
196    /// Import a public key from PEM format.
197    pub fn public_key_from_pem(pem: &str) -> Result<PublicKey, KeySerdeError> {
198        let pem = pem.trim();
199
200        if !pem.starts_with(ED25519_PUBLIC_KEY_HEADER) {
201            return Err(KeySerdeError::InvalidPemFormat);
202        }
203        if !pem.ends_with(ED25519_PUBLIC_KEY_FOOTER) {
204            return Err(KeySerdeError::InvalidPemFormat);
205        }
206
207        // Extract base64 content
208        let content = pem
209            .strip_prefix(ED25519_PUBLIC_KEY_HEADER)
210            .and_then(|s| s.strip_suffix(ED25519_PUBLIC_KEY_FOOTER))
211            .ok_or(KeySerdeError::InvalidPemFormat)?;
212
213        // Remove whitespace
214        let b64: String = content.chars().filter(|c| !c.is_whitespace()).collect();
215
216        Self::public_key_from_base64(&b64)
217    }
218}
219
220// ============================================================================
221// Helper functions
222// ============================================================================
223
224/// Encode bytes to base64 (standard alphabet, no padding).
225fn base64_encode(data: &[u8]) -> String {
226    const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
227
228    let mut result = String::new();
229    let mut i = 0;
230
231    while i < data.len() {
232        let b0 = data[i];
233        let b1 = if i + 1 < data.len() { data[i + 1] } else { 0 };
234        let b2 = if i + 2 < data.len() { data[i + 2] } else { 0 };
235
236        result.push(ALPHABET[(b0 >> 2) as usize] as char);
237        result.push(ALPHABET[(((b0 & 0x03) << 4) | (b1 >> 4)) as usize] as char);
238
239        if i + 1 < data.len() {
240            result.push(ALPHABET[(((b1 & 0x0f) << 2) | (b2 >> 6)) as usize] as char);
241        } else {
242            result.push('=');
243        }
244
245        if i + 2 < data.len() {
246            result.push(ALPHABET[(b2 & 0x3f) as usize] as char);
247        } else {
248            result.push('=');
249        }
250
251        i += 3;
252    }
253
254    result
255}
256
257/// Decode base64 to bytes.
258fn base64_decode(data: &str) -> Result<Vec<u8>, KeySerdeError> {
259    const DECODE_TABLE: [i8; 128] = [
260        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
261        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
262        -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
263        5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1,
264        -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
265        46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
266    ];
267
268    let data = data.trim_end_matches('=');
269    let len = data.len();
270
271    if len == 0 {
272        return Ok(Vec::new());
273    }
274
275    let output_len = (len * 3) / 4;
276    let mut result = Vec::with_capacity(output_len);
277
278    let bytes = data.as_bytes();
279    let mut i = 0;
280
281    while i + 3 < len {
282        let b0 = decode_char(bytes[i], &DECODE_TABLE)?;
283        let b1 = decode_char(bytes[i + 1], &DECODE_TABLE)?;
284        let b2 = decode_char(bytes[i + 2], &DECODE_TABLE)?;
285        let b3 = decode_char(bytes[i + 3], &DECODE_TABLE)?;
286
287        result.push((b0 << 2) | (b1 >> 4));
288        result.push((b1 << 4) | (b2 >> 2));
289        result.push((b2 << 6) | b3);
290
291        i += 4;
292    }
293
294    // Handle remaining bytes
295    if i < len {
296        let remaining = len - i;
297        let b0 = decode_char(bytes[i], &DECODE_TABLE)?;
298        let b1 = if i + 1 < len {
299            decode_char(bytes[i + 1], &DECODE_TABLE)?
300        } else {
301            0
302        };
303        let b2 = if i + 2 < len {
304            decode_char(bytes[i + 2], &DECODE_TABLE)?
305        } else {
306            0
307        };
308
309        result.push((b0 << 2) | (b1 >> 4));
310        if remaining > 2 {
311            result.push((b1 << 4) | (b2 >> 2));
312        }
313        if remaining > 3 {
314            let b3 = decode_char(bytes[i + 3], &DECODE_TABLE)?;
315            result.push((b2 << 6) | b3);
316        }
317    }
318
319    Ok(result)
320}
321
322fn decode_char(c: u8, table: &[i8; 128]) -> Result<u8, KeySerdeError> {
323    if c >= 128 {
324        return Err(KeySerdeError::InvalidBase64(format!(
325            "Invalid character: {}",
326            c as char
327        )));
328    }
329    let val = table[c as usize];
330    if val < 0 {
331        return Err(KeySerdeError::InvalidBase64(format!(
332            "Invalid character: {}",
333            c as char
334        )));
335    }
336    Ok(val as u8)
337}
338
339/// Wrap base64 string at specified line length.
340fn wrap_base64(b64: &str, line_len: usize) -> String {
341    let mut result = String::new();
342    let mut i = 0;
343
344    while i < b64.len() {
345        let end = (i + line_len).min(b64.len());
346        result.push_str(&b64[i..end]);
347        if end < b64.len() {
348            result.push('\n');
349        }
350        i = end;
351    }
352
353    result
354}
355
356#[cfg(test)]
357mod tests {
358    use super::*;
359
360    #[test]
361    fn test_hex_roundtrip() {
362        let keypair = KeyPair::generate();
363        let secret = keypair.secret_key();
364        let public = keypair.public_key();
365
366        let secret_hex = KeySerializer::secret_key_to_hex(&secret);
367        let public_hex = KeySerializer::public_key_to_hex(&public);
368
369        let secret2 = KeySerializer::secret_key_from_hex(&secret_hex).unwrap();
370        let public2 = KeySerializer::public_key_from_hex(&public_hex).unwrap();
371
372        assert_eq!(secret, secret2);
373        assert_eq!(public, public2);
374    }
375
376    #[test]
377    fn test_base64_roundtrip() {
378        let keypair = KeyPair::generate();
379        let secret = keypair.secret_key();
380        let public = keypair.public_key();
381
382        let secret_b64 = KeySerializer::secret_key_to_base64(&secret);
383        let public_b64 = KeySerializer::public_key_to_base64(&public);
384
385        let secret2 = KeySerializer::secret_key_from_base64(&secret_b64).unwrap();
386        let public2 = KeySerializer::public_key_from_base64(&public_b64).unwrap();
387
388        assert_eq!(secret, secret2);
389        assert_eq!(public, public2);
390    }
391
392    #[test]
393    fn test_pem_roundtrip() {
394        let keypair = KeyPair::generate();
395        let secret = keypair.secret_key();
396        let public = keypair.public_key();
397
398        let secret_pem = KeySerializer::secret_key_to_pem(&secret);
399        let public_pem = KeySerializer::public_key_to_pem(&public);
400
401        let secret2 = KeySerializer::secret_key_from_pem(&secret_pem).unwrap();
402        let public2 = KeySerializer::public_key_from_pem(&public_pem).unwrap();
403
404        assert_eq!(secret, secret2);
405        assert_eq!(public, public2);
406    }
407
408    #[test]
409    fn test_keypair_pem_roundtrip() {
410        let keypair = KeyPair::generate();
411        let pem = KeySerializer::keypair_to_pem(&keypair);
412        let keypair2 = KeySerializer::keypair_from_pem(&pem).unwrap();
413
414        assert_eq!(keypair.public_key(), keypair2.public_key());
415    }
416
417    #[test]
418    fn test_invalid_hex_length() {
419        // Too short
420        let result = KeySerializer::secret_key_from_hex("0123456789abcdef");
421        assert!(result.is_err());
422        assert!(matches!(
423            result.unwrap_err(),
424            KeySerdeError::InvalidKeyLength { .. }
425        ));
426
427        // Too long
428        let long_hex = "0".repeat(80);
429        let result = KeySerializer::secret_key_from_hex(&long_hex);
430        assert!(result.is_err());
431    }
432
433    #[test]
434    fn test_invalid_hex_characters() {
435        let invalid_hex = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
436        let result = KeySerializer::secret_key_from_hex(invalid_hex);
437        assert!(result.is_err());
438        assert!(matches!(result.unwrap_err(), KeySerdeError::InvalidHex(_)));
439    }
440
441    #[test]
442    fn test_invalid_base64_characters() {
443        let invalid_b64 = "!!!invalid-base64!!!";
444        let result = KeySerializer::secret_key_from_base64(invalid_b64);
445        assert!(result.is_err());
446        assert!(matches!(
447            result.unwrap_err(),
448            KeySerdeError::InvalidBase64(_)
449        ));
450    }
451
452    #[test]
453    fn test_invalid_base64_length() {
454        // Valid base64 but wrong length (16 bytes instead of 32)
455        let short_b64 = base64_encode(&[0u8; 16]);
456        let result = KeySerializer::secret_key_from_base64(&short_b64);
457        assert!(result.is_err());
458        assert!(matches!(
459            result.unwrap_err(),
460            KeySerdeError::InvalidKeyLength { .. }
461        ));
462    }
463
464    #[test]
465    fn test_invalid_pem_format_missing_header() {
466        let pem = "-----END ED25519 PRIVATE KEY-----";
467        let result = KeySerializer::secret_key_from_pem(pem);
468        assert!(result.is_err());
469        assert!(matches!(
470            result.unwrap_err(),
471            KeySerdeError::InvalidPemFormat
472        ));
473    }
474
475    #[test]
476    fn test_invalid_pem_format_missing_footer() {
477        let pem = "-----BEGIN ED25519 PRIVATE KEY-----\nYWJjZGVm";
478        let result = KeySerializer::secret_key_from_pem(pem);
479        assert!(result.is_err());
480        assert!(matches!(
481            result.unwrap_err(),
482            KeySerdeError::InvalidPemFormat
483        ));
484    }
485
486    #[test]
487    fn test_invalid_pem_format_wrong_header() {
488        let pem = "-----BEGIN RSA PRIVATE KEY-----\nYWJjZGVm\n-----END RSA PRIVATE KEY-----";
489        let result = KeySerializer::secret_key_from_pem(pem);
490        assert!(result.is_err());
491        assert!(matches!(
492            result.unwrap_err(),
493            KeySerdeError::InvalidPemFormat
494        ));
495    }
496
497    #[test]
498    fn test_pem_with_extra_whitespace() {
499        let keypair = KeyPair::generate();
500        let secret = keypair.secret_key();
501
502        let pem = KeySerializer::secret_key_to_pem(&secret);
503        // Add extra whitespace
504        let pem_with_spaces = format!("  {}  \n\n", pem);
505
506        let secret2 = KeySerializer::secret_key_from_pem(&pem_with_spaces).unwrap();
507        assert_eq!(secret, secret2);
508    }
509
510    #[test]
511    fn test_hex_case_insensitive() {
512        let keypair = KeyPair::generate();
513        let secret = keypair.secret_key();
514
515        let hex_lower = KeySerializer::secret_key_to_hex(&secret);
516        let hex_upper = hex_lower.to_uppercase();
517
518        let secret_from_lower = KeySerializer::secret_key_from_hex(&hex_lower).unwrap();
519        let secret_from_upper = KeySerializer::secret_key_from_hex(&hex_upper).unwrap();
520
521        assert_eq!(secret, secret_from_lower);
522        assert_eq!(secret, secret_from_upper);
523    }
524
525    #[test]
526    fn test_public_key_hex_roundtrip() {
527        let keypair = KeyPair::generate();
528        let public = keypair.public_key();
529
530        let hex = KeySerializer::public_key_to_hex(&public);
531        let public2 = KeySerializer::public_key_from_hex(&hex).unwrap();
532
533        assert_eq!(public, public2);
534        // Hex should be 64 characters (32 bytes * 2)
535        assert_eq!(hex.len(), 64);
536    }
537
538    #[test]
539    fn test_public_key_invalid_hex_length() {
540        let result = KeySerializer::public_key_from_hex("0123");
541        assert!(result.is_err());
542        assert!(matches!(
543            result.unwrap_err(),
544            KeySerdeError::InvalidKeyLength { .. }
545        ));
546    }
547
548    #[test]
549    fn test_public_key_pem_invalid_format() {
550        let pem = "-----BEGIN ED25519 PRIVATE KEY-----\ndata\n-----END ED25519 PRIVATE KEY-----";
551        let result = KeySerializer::public_key_from_pem(pem);
552        assert!(result.is_err());
553        assert!(matches!(
554            result.unwrap_err(),
555            KeySerdeError::InvalidPemFormat
556        ));
557    }
558
559    #[test]
560    fn test_base64_empty_string() {
561        let result = KeySerializer::secret_key_from_base64("");
562        assert!(result.is_err());
563        assert!(matches!(
564            result.unwrap_err(),
565            KeySerdeError::InvalidKeyLength { .. }
566        ));
567    }
568}