1use crate::encryption::{decrypt, encrypt, generate_nonce};
39use crate::hash::hash;
40use crate::kdf::hkdf_extract_expand;
41use crate::shamir::{Share, reconstruct, split};
42use crate::signing::KeyPair;
43use rand::RngCore;
44use serde::{Deserialize, Serialize};
45use std::time::{SystemTime, UNIX_EPOCH};
46
47#[derive(Debug)]
49pub enum BackupError {
50 InvalidThreshold(String),
52 InsufficientShares(String),
54 InvalidShare(String),
56 CryptoError(String),
58 SerializationError(String),
60 InvalidPassword,
62 VersionMismatch(String),
64}
65
66impl std::fmt::Display for BackupError {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 match self {
69 BackupError::InvalidThreshold(msg) => write!(f, "Invalid threshold: {}", msg),
70 BackupError::InsufficientShares(msg) => write!(f, "Insufficient shares: {}", msg),
71 BackupError::InvalidShare(msg) => write!(f, "Invalid share: {}", msg),
72 BackupError::CryptoError(msg) => write!(f, "Crypto error: {}", msg),
73 BackupError::SerializationError(msg) => write!(f, "Serialization error: {}", msg),
74 BackupError::InvalidPassword => write!(f, "Invalid password"),
75 BackupError::VersionMismatch(msg) => write!(f, "Version mismatch: {}", msg),
76 }
77 }
78}
79
80impl std::error::Error for BackupError {}
81
82pub type BackupResult<T> = Result<T, BackupError>;
83
84#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
86pub enum KeyType {
87 SigningKey,
89 EncryptionKey,
91 GenericSecret,
93}
94
95#[derive(Clone, Debug, Serialize, Deserialize)]
97pub struct BackupConfig {
98 pub threshold: usize,
100 pub total_shares: usize,
102 pub label: Option<String>,
104 pub description: Option<String>,
106 pub key_type: KeyType,
108 pub version: u32,
110}
111
112impl BackupConfig {
113 pub fn new(threshold: usize, total_shares: usize) -> Self {
115 Self {
116 threshold,
117 total_shares,
118 label: None,
119 description: None,
120 key_type: KeyType::GenericSecret,
121 version: 1,
122 }
123 }
124
125 pub fn with_label(mut self, label: &str) -> Self {
127 self.label = Some(label.to_string());
128 self
129 }
130
131 pub fn with_description(mut self, description: &str) -> Self {
133 self.description = Some(description.to_string());
134 self
135 }
136
137 pub fn with_key_type(mut self, key_type: KeyType) -> Self {
139 self.key_type = key_type;
140 self
141 }
142
143 pub fn with_version(mut self, version: u32) -> Self {
145 self.version = version;
146 self
147 }
148
149 pub fn validate(&self) -> BackupResult<()> {
151 if self.threshold == 0 {
152 return Err(BackupError::InvalidThreshold(
153 "Threshold must be at least 1".to_string(),
154 ));
155 }
156 if self.threshold > self.total_shares {
157 return Err(BackupError::InvalidThreshold(format!(
158 "Threshold ({}) cannot exceed total shares ({})",
159 self.threshold, self.total_shares
160 )));
161 }
162 if self.total_shares > 255 {
163 return Err(BackupError::InvalidThreshold(
164 "Total shares cannot exceed 255".to_string(),
165 ));
166 }
167 Ok(())
168 }
169}
170
171#[derive(Clone, Debug, Serialize, Deserialize)]
173pub struct BackupShare {
174 pub index: u8,
176 pub share_data: Vec<u8>,
178 pub config: BackupConfig,
180 pub created_at: u64,
182 pub checksum: [u8; 32],
184}
185
186impl BackupShare {
187 fn new(index: u8, share: Share, config: BackupConfig) -> Self {
189 let timestamp = SystemTime::now()
190 .duration_since(UNIX_EPOCH)
191 .unwrap()
192 .as_secs();
193
194 let share_data = share.data.clone();
195
196 let mut data = Vec::new();
197 data.push(index);
198 data.push(share.index);
199 data.extend_from_slice(&share_data);
200 data.extend_from_slice(×tamp.to_le_bytes());
201
202 let checksum = hash(&data);
203
204 Self {
205 index,
206 share_data,
207 config,
208 created_at: timestamp,
209 checksum,
210 }
211 }
212
213 pub fn verify_integrity(&self) -> bool {
215 let mut data = Vec::new();
216 data.push(self.index);
217 data.push(self.index); data.extend_from_slice(&self.share_data);
219 data.extend_from_slice(&self.created_at.to_le_bytes());
220
221 let expected_checksum = hash(&data);
222 expected_checksum == self.checksum
223 }
224
225 fn to_share(&self) -> BackupResult<Share> {
227 Share::new(self.index, self.share_data.clone())
228 .map_err(|e| BackupError::InvalidShare(e.to_string()))
229 }
230
231 pub fn to_bytes(&self) -> BackupResult<Vec<u8>> {
233 crate::codec::encode(self).map_err(|e| BackupError::SerializationError(e.to_string()))
234 }
235
236 pub fn from_bytes(bytes: &[u8]) -> BackupResult<Self> {
238 crate::codec::decode(bytes).map_err(|e| BackupError::SerializationError(e.to_string()))
239 }
240}
241
242#[derive(Clone, Debug, Serialize, Deserialize)]
244pub struct EncryptedBackup {
245 pub ciphertext: Vec<u8>,
247 pub nonce: [u8; 12],
249 pub salt: [u8; 32],
251 pub config: BackupConfig,
253 pub created_at: u64,
255}
256
257impl EncryptedBackup {
258 pub fn to_bytes(&self) -> BackupResult<Vec<u8>> {
260 crate::codec::encode(self).map_err(|e| BackupError::SerializationError(e.to_string()))
261 }
262
263 pub fn from_bytes(bytes: &[u8]) -> BackupResult<Self> {
265 crate::codec::decode(bytes).map_err(|e| BackupError::SerializationError(e.to_string()))
266 }
267}
268
269pub fn backup_key_shamir(
271 keypair: &KeyPair,
272 config: &BackupConfig,
273) -> BackupResult<Vec<BackupShare>> {
274 config.validate()?;
275
276 let secret = keypair.secret_key();
278
279 let shares = split(&secret, config.threshold, config.total_shares)
281 .map_err(|e| BackupError::CryptoError(e.to_string()))?;
282
283 let backup_shares: Vec<BackupShare> = shares
285 .into_iter()
286 .enumerate()
287 .map(|(i, share)| BackupShare::new((i + 1) as u8, share, config.clone()))
288 .collect();
289
290 Ok(backup_shares)
291}
292
293pub fn recover_key_shamir(shares: &[BackupShare]) -> BackupResult<KeyPair> {
295 if shares.is_empty() {
296 return Err(BackupError::InsufficientShares(
297 "No shares provided".to_string(),
298 ));
299 }
300
301 let config = &shares[0].config;
303 if shares.len() < config.threshold {
304 return Err(BackupError::InsufficientShares(format!(
305 "Need {} shares but only {} provided",
306 config.threshold,
307 shares.len()
308 )));
309 }
310
311 for share in shares {
313 if !share.verify_integrity() {
314 return Err(BackupError::InvalidShare(format!(
315 "Share {} failed integrity check",
316 share.index
317 )));
318 }
319
320 if share.config.threshold != config.threshold {
322 return Err(BackupError::InvalidShare(
323 "Incompatible share thresholds".to_string(),
324 ));
325 }
326 }
327
328 let raw_shares: Vec<Share> = shares
330 .iter()
331 .map(|bs| bs.to_share())
332 .collect::<Result<Vec<_>, _>>()?;
333
334 let secret = reconstruct(&raw_shares).map_err(|e| BackupError::CryptoError(e.to_string()))?;
336
337 if secret.len() != 32 {
339 return Err(BackupError::CryptoError(
340 "Invalid secret length".to_string(),
341 ));
342 }
343 let mut secret_array = [0u8; 32];
344 secret_array.copy_from_slice(&secret);
345 KeyPair::from_secret_key(&secret_array).map_err(|e| BackupError::CryptoError(e.to_string()))
346}
347
348pub fn backup_secret_shamir(
350 secret: &[u8],
351 config: &BackupConfig,
352) -> BackupResult<Vec<BackupShare>> {
353 config.validate()?;
354
355 let shares = split(secret, config.threshold, config.total_shares)
357 .map_err(|e| BackupError::CryptoError(e.to_string()))?;
358
359 let backup_shares: Vec<BackupShare> = shares
361 .into_iter()
362 .enumerate()
363 .map(|(i, share)| BackupShare::new((i + 1) as u8, share, config.clone()))
364 .collect();
365
366 Ok(backup_shares)
367}
368
369pub fn recover_secret_shamir(shares: &[BackupShare]) -> BackupResult<Vec<u8>> {
371 if shares.is_empty() {
372 return Err(BackupError::InsufficientShares(
373 "No shares provided".to_string(),
374 ));
375 }
376
377 let config = &shares[0].config;
379 if shares.len() < config.threshold {
380 return Err(BackupError::InsufficientShares(format!(
381 "Need {} shares but only {} provided",
382 config.threshold,
383 shares.len()
384 )));
385 }
386
387 for share in shares {
389 if !share.verify_integrity() {
390 return Err(BackupError::InvalidShare(format!(
391 "Share {} failed integrity check",
392 share.index
393 )));
394 }
395 }
396
397 let raw_shares: Vec<Share> = shares
399 .iter()
400 .map(|bs| bs.to_share())
401 .collect::<Result<Vec<_>, _>>()?;
402
403 reconstruct(&raw_shares).map_err(|e| BackupError::CryptoError(e.to_string()))
405}
406
407pub fn backup_key_encrypted(
409 keypair: &KeyPair,
410 password: &str,
411 config: &BackupConfig,
412) -> BackupResult<EncryptedBackup> {
413 config.validate()?;
414
415 let mut salt = [0u8; 32];
417 rand::thread_rng().fill_bytes(&mut salt);
418
419 let key_bytes = hkdf_extract_expand(password.as_bytes(), &salt, b"chie-backup-encryption-v1");
421
422 let secret = keypair.secret_key();
424
425 let nonce = generate_nonce();
427
428 let ciphertext = encrypt(&secret, &key_bytes, &nonce)
430 .map_err(|e| BackupError::CryptoError(e.to_string()))?;
431
432 let timestamp = SystemTime::now()
433 .duration_since(UNIX_EPOCH)
434 .unwrap()
435 .as_secs();
436
437 let nonce_bytes: [u8; 12] = nonce.as_slice().try_into().unwrap();
439
440 Ok(EncryptedBackup {
441 ciphertext,
442 nonce: nonce_bytes,
443 salt,
444 config: config.clone(),
445 created_at: timestamp,
446 })
447}
448
449pub fn recover_key_encrypted(backup: &EncryptedBackup, password: &str) -> BackupResult<KeyPair> {
451 let key_bytes = hkdf_extract_expand(
453 password.as_bytes(),
454 &backup.salt,
455 b"chie-backup-encryption-v1",
456 );
457
458 let nonce = &backup.nonce;
460
461 let secret =
463 decrypt(&backup.ciphertext, &key_bytes, nonce).map_err(|_| BackupError::InvalidPassword)?;
464
465 if secret.len() != 32 {
467 return Err(BackupError::CryptoError(
468 "Invalid secret length".to_string(),
469 ));
470 }
471 let mut secret_array = [0u8; 32];
472 secret_array.copy_from_slice(&secret);
473 KeyPair::from_secret_key(&secret_array).map_err(|e| BackupError::CryptoError(e.to_string()))
474}
475
476pub fn backup_secret_encrypted(
478 secret: &[u8],
479 password: &str,
480 config: &BackupConfig,
481) -> BackupResult<EncryptedBackup> {
482 config.validate()?;
483
484 let mut salt = [0u8; 32];
486 rand::thread_rng().fill_bytes(&mut salt);
487
488 let key_bytes = hkdf_extract_expand(password.as_bytes(), &salt, b"chie-backup-encryption-v1");
490
491 let nonce = generate_nonce();
493
494 let ciphertext =
496 encrypt(secret, &key_bytes, &nonce).map_err(|e| BackupError::CryptoError(e.to_string()))?;
497
498 let timestamp = SystemTime::now()
499 .duration_since(UNIX_EPOCH)
500 .unwrap()
501 .as_secs();
502
503 let nonce_bytes: [u8; 12] = nonce.as_slice().try_into().unwrap();
505
506 Ok(EncryptedBackup {
507 ciphertext,
508 nonce: nonce_bytes,
509 salt,
510 config: config.clone(),
511 created_at: timestamp,
512 })
513}
514
515pub fn recover_secret_encrypted(backup: &EncryptedBackup, password: &str) -> BackupResult<Vec<u8>> {
517 let key_bytes = hkdf_extract_expand(
519 password.as_bytes(),
520 &backup.salt,
521 b"chie-backup-encryption-v1",
522 );
523
524 let nonce = &backup.nonce;
526
527 decrypt(&backup.ciphertext, &key_bytes, nonce).map_err(|_| BackupError::InvalidPassword)
529}
530
531#[cfg(test)]
532mod tests {
533 use super::*;
534
535 #[test]
536 fn test_shamir_backup_recovery() {
537 let keypair = KeyPair::generate();
538 let config = BackupConfig::new(3, 5).with_label("test-key");
539
540 let shares = backup_key_shamir(&keypair, &config).unwrap();
542 assert_eq!(shares.len(), 5);
543
544 for (i, share) in shares.iter().enumerate() {
546 assert_eq!(share.index, (i + 1) as u8);
547 assert!(share.verify_integrity());
548 }
549
550 let recovered = recover_key_shamir(&shares[0..3]).unwrap();
552 assert_eq!(keypair.public_key(), recovered.public_key());
553
554 let recovered = recover_key_shamir(&shares[1..5]).unwrap();
556 assert_eq!(keypair.public_key(), recovered.public_key());
557 }
558
559 #[test]
560 fn test_shamir_insufficient_shares() {
561 let keypair = KeyPair::generate();
562 let config = BackupConfig::new(3, 5);
563 let shares = backup_key_shamir(&keypair, &config).unwrap();
564
565 let result = recover_key_shamir(&shares[0..2]);
567 assert!(result.is_err());
568 }
569
570 #[test]
571 fn test_encrypted_backup_recovery() {
572 let keypair = KeyPair::generate();
573 let password = "secure_password_123";
574 let config = BackupConfig::new(1, 1).with_key_type(KeyType::SigningKey);
575
576 let backup = backup_key_encrypted(&keypair, password, &config).unwrap();
578
579 let recovered = recover_key_encrypted(&backup, password).unwrap();
581 assert_eq!(keypair.public_key(), recovered.public_key());
582 }
583
584 #[test]
585 fn test_encrypted_backup_wrong_password() {
586 let keypair = KeyPair::generate();
587 let password = "correct_password";
588 let wrong_password = "wrong_password";
589 let config = BackupConfig::new(1, 1);
590
591 let backup = backup_key_encrypted(&keypair, password, &config).unwrap();
592
593 let result = recover_key_encrypted(&backup, wrong_password);
595 assert!(result.is_err());
596 }
597
598 #[test]
599 fn test_backup_share_serialization() {
600 let keypair = KeyPair::generate();
601 let config = BackupConfig::new(2, 3);
602 let shares = backup_key_shamir(&keypair, &config).unwrap();
603
604 let bytes = shares[0].to_bytes().unwrap();
606 let recovered_share = BackupShare::from_bytes(&bytes).unwrap();
607
608 assert_eq!(shares[0].index, recovered_share.index);
609 assert!(recovered_share.verify_integrity());
610 }
611
612 #[test]
613 fn test_encrypted_backup_serialization() {
614 let keypair = KeyPair::generate();
615 let password = "test_password";
616 let config = BackupConfig::new(1, 1);
617
618 let backup = backup_key_encrypted(&keypair, password, &config).unwrap();
619
620 let bytes = backup.to_bytes().unwrap();
622 let recovered_backup = EncryptedBackup::from_bytes(&bytes).unwrap();
623
624 let recovered_key = recover_key_encrypted(&recovered_backup, password).unwrap();
626 assert_eq!(keypair.public_key(), recovered_key.public_key());
627 }
628
629 #[test]
630 fn test_generic_secret_shamir_backup() {
631 let secret = b"my secret data that needs backup";
632 let config = BackupConfig::new(2, 4).with_key_type(KeyType::GenericSecret);
633
634 let shares = backup_secret_shamir(secret, &config).unwrap();
635 assert_eq!(shares.len(), 4);
636
637 let recovered = recover_secret_shamir(&shares[0..2]).unwrap();
639 assert_eq!(secret.as_slice(), recovered.as_slice());
640
641 let recovered = recover_secret_shamir(&shares[1..4]).unwrap();
643 assert_eq!(secret.as_slice(), recovered.as_slice());
644 }
645
646 #[test]
647 fn test_generic_secret_encrypted_backup() {
648 let secret = b"confidential data";
649 let password = "strong_password";
650 let config = BackupConfig::new(1, 1);
651
652 let backup = backup_secret_encrypted(secret, password, &config).unwrap();
653 let recovered = recover_secret_encrypted(&backup, password).unwrap();
654
655 assert_eq!(secret.as_slice(), recovered.as_slice());
656 }
657
658 #[test]
659 fn test_invalid_threshold_config() {
660 let config = BackupConfig::new(0, 5);
662 assert!(config.validate().is_err());
663
664 let config = BackupConfig::new(6, 5);
666 assert!(config.validate().is_err());
667
668 let config = BackupConfig::new(128, 256);
670 assert!(config.validate().is_err());
671 }
672
673 #[test]
674 fn test_backup_config_builder() {
675 let config = BackupConfig::new(3, 5)
676 .with_label("main-key")
677 .with_description("Primary signing key")
678 .with_key_type(KeyType::SigningKey)
679 .with_version(2);
680
681 assert_eq!(config.label, Some("main-key".to_string()));
682 assert_eq!(config.description, Some("Primary signing key".to_string()));
683 assert_eq!(config.key_type, KeyType::SigningKey);
684 assert_eq!(config.version, 2);
685 }
686
687 #[test]
688 fn test_share_integrity_verification() {
689 let keypair = KeyPair::generate();
690 let config = BackupConfig::new(2, 3);
691 let shares = backup_key_shamir(&keypair, &config).unwrap();
692
693 for share in &shares {
695 assert!(share.verify_integrity());
696 }
697
698 let mut corrupted = shares[0].clone();
700 corrupted.share_data[0] ^= 0xFF; assert!(!corrupted.verify_integrity());
704 }
705
706 #[test]
707 fn test_different_passwords_different_ciphertexts() {
708 let keypair = KeyPair::generate();
709 let config = BackupConfig::new(1, 1);
710
711 let backup1 = backup_key_encrypted(&keypair, "password1", &config).unwrap();
712 let backup2 = backup_key_encrypted(&keypair, "password2", &config).unwrap();
713
714 assert_ne!(backup1.ciphertext, backup2.ciphertext);
716 assert_ne!(backup1.salt, backup2.salt);
717 }
718
719 #[test]
720 fn test_empty_shares_recovery() {
721 let shares: Vec<BackupShare> = vec![];
722 let result = recover_key_shamir(&shares);
723 assert!(result.is_err());
724
725 let result = recover_secret_shamir(&shares);
726 assert!(result.is_err());
727 }
728}