1#![forbid(unsafe_code)]
19
20pub mod error;
21
22pub use error::TransitError;
23
24use std::path::Path;
25use std::str::FromStr;
26
27use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
28use serde::{Deserialize, Serialize};
29use tracing::{debug, info, warn};
30
31use egide_crypto::{aead, kdf, random, MasterKey};
32use egide_storage_sqlite::SqliteBackend;
33
34const SCHEMA: &str = r"
39CREATE TABLE IF NOT EXISTS transit_keys (
40 name TEXT PRIMARY KEY,
41 key_type TEXT NOT NULL,
42 latest_version INTEGER NOT NULL DEFAULT 1,
43 min_encryption_version INTEGER NOT NULL DEFAULT 1,
44 min_decryption_version INTEGER NOT NULL DEFAULT 1,
45 supports_encryption INTEGER NOT NULL DEFAULT 1,
46 supports_decryption INTEGER NOT NULL DEFAULT 1,
47 supports_derivation INTEGER NOT NULL DEFAULT 0,
48 exportable INTEGER NOT NULL DEFAULT 0,
49 deletion_allowed INTEGER NOT NULL DEFAULT 0,
50 created_at INTEGER NOT NULL,
51 updated_at INTEGER NOT NULL
52);
53
54CREATE TABLE IF NOT EXISTS transit_key_versions (
55 name TEXT NOT NULL,
56 version INTEGER NOT NULL,
57 key_material TEXT NOT NULL,
58 nonce TEXT NOT NULL,
59 created_at INTEGER NOT NULL,
60 PRIMARY KEY (name, version),
61 FOREIGN KEY (name) REFERENCES transit_keys(name) ON DELETE CASCADE
62);
63
64CREATE INDEX IF NOT EXISTS idx_transit_key_versions_name ON transit_key_versions(name);
65";
66
67#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
73#[serde(rename_all = "kebab-case")]
74pub enum KeyType {
75 #[default]
77 Aes256Gcm,
78 ChaCha20Poly1305,
80}
81
82impl std::fmt::Display for KeyType {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 match self {
85 Self::Aes256Gcm => write!(f, "aes256-gcm"),
86 Self::ChaCha20Poly1305 => write!(f, "chacha20-poly1305"),
87 }
88 }
89}
90
91impl FromStr for KeyType {
92 type Err = TransitError;
93
94 fn from_str(s: &str) -> Result<Self, Self::Err> {
95 match s {
96 "aes256-gcm" => Ok(Self::Aes256Gcm),
97 "chacha20-poly1305" => Ok(Self::ChaCha20Poly1305),
98 _ => Err(TransitError::InvalidKeyType(s.to_string())),
99 }
100 }
101}
102
103#[allow(clippy::struct_excessive_bools)]
107#[derive(Debug, Clone, Default)]
108pub struct KeyConfig {
109 pub key_type: KeyType,
111 pub supports_encryption: bool,
113 pub supports_decryption: bool,
115 pub supports_derivation: bool,
117 pub exportable: bool,
119 pub deletion_allowed: bool,
121}
122
123impl KeyConfig {
124 #[must_use]
126 pub fn new() -> Self {
127 Self {
128 key_type: KeyType::default(),
129 supports_encryption: true,
130 supports_decryption: true,
131 supports_derivation: false,
132 exportable: false,
133 deletion_allowed: false,
134 }
135 }
136}
137
138#[allow(clippy::struct_excessive_bools)]
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct TransitKey {
144 pub name: String,
146 pub key_type: KeyType,
148 pub latest_version: u32,
150 pub min_encryption_version: u32,
152 pub min_decryption_version: u32,
154 pub supports_encryption: bool,
156 pub supports_decryption: bool,
158 pub supports_derivation: bool,
160 pub exportable: bool,
162 pub deletion_allowed: bool,
164 pub created_at: u64,
166 pub updated_at: u64,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct KeyVersionInfo {
173 pub version: u32,
175 pub created_at: u64,
177}
178
179#[derive(Debug, Clone)]
181pub struct DataKey {
182 pub plaintext: Vec<u8>,
184 pub ciphertext: String,
186}
187
188fn hex_encode(data: &[u8]) -> String {
193 use std::fmt::Write as _;
194 data.iter()
195 .fold(String::with_capacity(data.len() * 2), |mut s, b| {
196 write!(s, "{b:02x}").expect("writing to String is infallible");
197 s
198 })
199}
200
201fn hex_decode(s: &str) -> Result<Vec<u8>, TransitError> {
202 if !s.len().is_multiple_of(2) {
203 return Err(TransitError::Storage("invalid hex length".into()));
204 }
205 (0..s.len())
206 .step_by(2)
207 .map(|i| {
208 u8::from_str_radix(&s[i..i + 2], 16)
209 .map_err(|_| TransitError::Storage("invalid hex".into()))
210 })
211 .collect()
212}
213
214pub struct TransitEngine {
222 storage: SqliteBackend,
223 master_key: MasterKey,
224}
225
226impl TransitEngine {
227 pub async fn new(
229 data_path: impl AsRef<Path>,
230 master_key: MasterKey,
231 ) -> Result<Self, TransitError> {
232 let storage = SqliteBackend::open(data_path.as_ref(), "transit")
233 .await
234 .map_err(|e| TransitError::Storage(e.to_string()))?;
235
236 storage
238 .execute_raw(SCHEMA)
239 .await
240 .map_err(|e| TransitError::Storage(e.to_string()))?;
241
242 info!("Transit engine initialized");
243
244 Ok(Self {
245 storage,
246 master_key,
247 })
248 }
249
250 fn derive_version_key(&self, name: &str, version: u32) -> Result<[u8; 32], TransitError> {
256 let info = format!("egide-transit-v1:{name}:{version}");
257 let key = kdf::derive_encryption_key(self.master_key.as_bytes(), info.as_bytes())?;
258 Ok(*key)
259 }
260
261 fn encrypt_key_material(
263 &self,
264 name: &str,
265 version: u32,
266 key: &[u8],
267 ) -> Result<(Vec<u8>, Vec<u8>), TransitError> {
268 let wrapping_key = self.derive_version_key(name, version)?;
269 let aad = format!("transit-key:{name}:{version}");
270 let ciphertext = aead::encrypt(&wrapping_key, key, Some(aad.as_bytes()))?;
271
272 let nonce = ciphertext[..12].to_vec();
274 let encrypted = ciphertext[12..].to_vec();
275
276 Ok((encrypted, nonce))
277 }
278
279 fn decrypt_key_material(
281 &self,
282 name: &str,
283 version: u32,
284 encrypted: &[u8],
285 nonce: &[u8],
286 ) -> Result<Vec<u8>, TransitError> {
287 let wrapping_key = self.derive_version_key(name, version)?;
288 let aad = format!("transit-key:{name}:{version}");
289
290 let mut ciphertext = Vec::with_capacity(nonce.len() + encrypted.len());
292 ciphertext.extend_from_slice(nonce);
293 ciphertext.extend_from_slice(encrypted);
294
295 let decrypted = aead::decrypt(&wrapping_key, &ciphertext, Some(aad.as_bytes()))?;
296 Ok(decrypted.to_vec())
297 }
298
299 async fn get_key_material(&self, name: &str, version: u32) -> Result<Vec<u8>, TransitError> {
301 let row = self
302 .storage
303 .query_one::<(String, String)>(
304 "SELECT key_material, nonce FROM transit_key_versions WHERE name = ? AND version = ?",
305 &[name, &version.to_string()],
306 )
307 .await
308 .map_err(|e| TransitError::Storage(e.to_string()))?
309 .ok_or_else(|| TransitError::VersionNotFound {
310 name: name.to_string(),
311 version,
312 })?;
313
314 let (key_material_hex, nonce_hex) = row;
315 let key_material = hex_decode(&key_material_hex)?;
316 let nonce = hex_decode(&nonce_hex)?;
317
318 self.decrypt_key_material(name, version, &key_material, &nonce)
319 }
320
321 fn now() -> u64 {
326 std::time::SystemTime::now()
327 .duration_since(std::time::UNIX_EPOCH)
328 .expect("system time before UNIX epoch")
329 .as_secs()
330 }
331
332 fn validate_name(name: &str) -> Result<(), TransitError> {
337 if name.is_empty() {
338 return Err(TransitError::InvalidKeyName("name cannot be empty".into()));
339 }
340 if name.len() > 128 {
341 return Err(TransitError::InvalidKeyName(
342 "name too long (max 128 chars)".into(),
343 ));
344 }
345 if !name
346 .chars()
347 .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
348 {
349 return Err(TransitError::InvalidKeyName(
350 "name can only contain alphanumeric, dash, underscore".into(),
351 ));
352 }
353 Ok(())
354 }
355
356 pub async fn create_key(
362 &self,
363 name: &str,
364 config: KeyConfig,
365 ) -> Result<TransitKey, TransitError> {
366 Self::validate_name(name)?;
367
368 let existing = self
370 .storage
371 .query_one::<(String,)>("SELECT name FROM transit_keys WHERE name = ?", &[name])
372 .await
373 .map_err(|e| TransitError::Storage(e.to_string()))?;
374
375 if existing.is_some() {
376 return Err(TransitError::KeyExists(name.to_string()));
377 }
378
379 let now = Self::now();
380
381 let raw_key = random::generate_key()?;
383 let (encrypted_key, nonce) = self.encrypt_key_material(name, 1, raw_key.as_ref())?;
384
385 self.storage
387 .execute(
388 "INSERT INTO transit_keys (name, key_type, latest_version, min_encryption_version, min_decryption_version, supports_encryption, supports_decryption, supports_derivation, exportable, deletion_allowed, created_at, updated_at) VALUES (?, ?, 1, 1, 1, ?, ?, ?, ?, ?, ?, ?)",
389 &[
390 name,
391 &config.key_type.to_string(),
392 &i32::from(config.supports_encryption).to_string(),
393 &i32::from(config.supports_decryption).to_string(),
394 &i32::from(config.supports_derivation).to_string(),
395 &i32::from(config.exportable).to_string(),
396 &i32::from(config.deletion_allowed).to_string(),
397 &now.to_string(),
398 &now.to_string(),
399 ],
400 )
401 .await
402 .map_err(|e| TransitError::Storage(e.to_string()))?;
403
404 self.storage
406 .execute(
407 "INSERT INTO transit_key_versions (name, version, key_material, nonce, created_at) VALUES (?, 1, ?, ?, ?)",
408 &[
409 name,
410 &hex_encode(&encrypted_key),
411 &hex_encode(&nonce),
412 &now.to_string(),
413 ],
414 )
415 .await
416 .map_err(|e| TransitError::Storage(e.to_string()))?;
417
418 info!(name = name, key_type = %config.key_type, "Transit key created");
419
420 Ok(TransitKey {
421 name: name.to_string(),
422 key_type: config.key_type,
423 latest_version: 1,
424 min_encryption_version: 1,
425 min_decryption_version: 1,
426 supports_encryption: config.supports_encryption,
427 supports_decryption: config.supports_decryption,
428 supports_derivation: config.supports_derivation,
429 exportable: config.exportable,
430 deletion_allowed: config.deletion_allowed,
431 created_at: now,
432 updated_at: now,
433 })
434 }
435
436 pub async fn get_key(&self, name: &str) -> Result<TransitKey, TransitError> {
438 Self::validate_name(name)?;
439
440 let row = self
441 .storage
442 .query_one::<(String, String, String, String, String, String, String, String, String, String, String, String)>(
443 "SELECT name, key_type, CAST(latest_version AS TEXT), CAST(min_encryption_version AS TEXT), CAST(min_decryption_version AS TEXT), CAST(supports_encryption AS TEXT), CAST(supports_decryption AS TEXT), CAST(supports_derivation AS TEXT), CAST(exportable AS TEXT), CAST(deletion_allowed AS TEXT), CAST(created_at AS TEXT), CAST(updated_at AS TEXT) FROM transit_keys WHERE name = ?",
444 &[name],
445 )
446 .await
447 .map_err(|e| TransitError::Storage(e.to_string()))?
448 .ok_or_else(|| TransitError::KeyNotFound(name.to_string()))?;
449
450 let (
451 name,
452 key_type,
453 latest_version,
454 min_enc,
455 min_dec,
456 enc,
457 dec,
458 deriv,
459 export,
460 del,
461 created,
462 updated,
463 ) = row;
464
465 Ok(TransitKey {
466 name,
467 key_type: key_type.parse()?,
468 latest_version: latest_version.parse().unwrap_or(1),
469 min_encryption_version: min_enc.parse().unwrap_or(1),
470 min_decryption_version: min_dec.parse().unwrap_or(1),
471 supports_encryption: enc.parse::<i32>().unwrap_or(1) != 0,
472 supports_decryption: dec.parse::<i32>().unwrap_or(1) != 0,
473 supports_derivation: deriv.parse::<i32>().unwrap_or(0) != 0,
474 exportable: export.parse::<i32>().unwrap_or(0) != 0,
475 deletion_allowed: del.parse::<i32>().unwrap_or(0) != 0,
476 created_at: created.parse().unwrap_or(0),
477 updated_at: updated.parse().unwrap_or(0),
478 })
479 }
480
481 pub async fn list_keys(&self) -> Result<Vec<String>, TransitError> {
483 let rows = self
484 .storage
485 .query_all::<(String,)>("SELECT name FROM transit_keys ORDER BY name", &[])
486 .await
487 .map_err(|e| TransitError::Storage(e.to_string()))?;
488
489 Ok(rows.into_iter().map(|(name,)| name).collect())
490 }
491
492 pub async fn list_versions(&self, name: &str) -> Result<Vec<KeyVersionInfo>, TransitError> {
494 Self::validate_name(name)?;
495
496 let _ = self.get_key(name).await?;
498
499 let rows = self
500 .storage
501 .query_all::<(String, String)>(
502 "SELECT CAST(version AS TEXT), CAST(created_at AS TEXT) FROM transit_key_versions WHERE name = ? ORDER BY version DESC",
503 &[name],
504 )
505 .await
506 .map_err(|e| TransitError::Storage(e.to_string()))?;
507
508 Ok(rows
509 .into_iter()
510 .map(|(version, created_at)| KeyVersionInfo {
511 version: version.parse().unwrap_or(0),
512 created_at: created_at.parse().unwrap_or(0),
513 })
514 .collect())
515 }
516
517 pub async fn rotate_key(&self, name: &str) -> Result<u32, TransitError> {
519 Self::validate_name(name)?;
520
521 let key = self.get_key(name).await?;
522 let new_version = key.latest_version + 1;
523 let now = Self::now();
524
525 let raw_key = random::generate_key()?;
527 let (encrypted_key, nonce) =
528 self.encrypt_key_material(name, new_version, raw_key.as_ref())?;
529
530 self.storage
532 .execute(
533 "INSERT INTO transit_key_versions (name, version, key_material, nonce, created_at) VALUES (?, ?, ?, ?, ?)",
534 &[
535 name,
536 &new_version.to_string(),
537 &hex_encode(&encrypted_key),
538 &hex_encode(&nonce),
539 &now.to_string(),
540 ],
541 )
542 .await
543 .map_err(|e| TransitError::Storage(e.to_string()))?;
544
545 self.storage
547 .execute(
548 "UPDATE transit_keys SET latest_version = ?, updated_at = ? WHERE name = ?",
549 &[&new_version.to_string(), &now.to_string(), name],
550 )
551 .await
552 .map_err(|e| TransitError::Storage(e.to_string()))?;
553
554 info!(name = name, version = new_version, "Transit key rotated");
555
556 Ok(new_version)
557 }
558
559 pub async fn delete_key(&self, name: &str) -> Result<(), TransitError> {
561 Self::validate_name(name)?;
562
563 let key = self.get_key(name).await?;
564
565 if !key.deletion_allowed {
566 return Err(TransitError::DeletionNotAllowed(name.to_string()));
567 }
568
569 self.storage
571 .execute("DELETE FROM transit_key_versions WHERE name = ?", &[name])
572 .await
573 .map_err(|e| TransitError::Storage(e.to_string()))?;
574
575 self.storage
577 .execute("DELETE FROM transit_keys WHERE name = ?", &[name])
578 .await
579 .map_err(|e| TransitError::Storage(e.to_string()))?;
580
581 warn!(name = name, "Transit key deleted");
582
583 Ok(())
584 }
585
586 pub async fn update_key_config(
588 &self,
589 name: &str,
590 min_encryption_version: Option<u32>,
591 min_decryption_version: Option<u32>,
592 deletion_allowed: Option<bool>,
593 ) -> Result<(), TransitError> {
594 Self::validate_name(name)?;
595
596 let key = self.get_key(name).await?;
597 let now = Self::now();
598
599 let min_enc = min_encryption_version.unwrap_or(key.min_encryption_version);
600 let min_dec = min_decryption_version.unwrap_or(key.min_decryption_version);
601 let del = deletion_allowed.unwrap_or(key.deletion_allowed);
602
603 if min_enc > key.latest_version {
605 return Err(TransitError::VersionNotFound {
606 name: name.to_string(),
607 version: min_enc,
608 });
609 }
610 if min_dec > key.latest_version {
611 return Err(TransitError::VersionNotFound {
612 name: name.to_string(),
613 version: min_dec,
614 });
615 }
616
617 self.storage
618 .execute(
619 "UPDATE transit_keys SET min_encryption_version = ?, min_decryption_version = ?, deletion_allowed = ?, updated_at = ? WHERE name = ?",
620 &[
621 &min_enc.to_string(),
622 &min_dec.to_string(),
623 &i32::from(del).to_string(),
624 &now.to_string(),
625 name,
626 ],
627 )
628 .await
629 .map_err(|e| TransitError::Storage(e.to_string()))?;
630
631 debug!(name = name, "Transit key config updated");
632
633 Ok(())
634 }
635
636 pub async fn encrypt(&self, name: &str, plaintext: &[u8]) -> Result<String, TransitError> {
644 let key = self.get_key(name).await?;
645
646 if !key.supports_encryption {
647 return Err(TransitError::OperationNotAllowed(
648 "encryption not allowed for this key".into(),
649 ));
650 }
651
652 self.encrypt_with_version(name, plaintext, key.latest_version)
653 .await
654 }
655
656 pub async fn encrypt_with_version(
658 &self,
659 name: &str,
660 plaintext: &[u8],
661 version: u32,
662 ) -> Result<String, TransitError> {
663 let key = self.get_key(name).await?;
664
665 if !key.supports_encryption {
666 return Err(TransitError::OperationNotAllowed(
667 "encryption not allowed for this key".into(),
668 ));
669 }
670
671 if version < key.min_encryption_version {
672 return Err(TransitError::VersionBelowMinEncryption {
673 version,
674 min: key.min_encryption_version,
675 });
676 }
677
678 if version > key.latest_version {
679 return Err(TransitError::VersionNotFound {
680 name: name.to_string(),
681 version,
682 });
683 }
684
685 let raw_key = self.get_key_material(name, version).await?;
687
688 let aad = format!("egide-transit:{name}:{version}");
690 let ciphertext = aead::encrypt(&raw_key, plaintext, Some(aad.as_bytes()))?;
691
692 let encoded = BASE64.encode(&ciphertext);
694 Ok(format!("egide:v{version}:{encoded}"))
695 }
696
697 pub async fn decrypt(&self, name: &str, ciphertext: &str) -> Result<Vec<u8>, TransitError> {
701 let key = self.get_key(name).await?;
702
703 if !key.supports_decryption {
704 return Err(TransitError::OperationNotAllowed(
705 "decryption not allowed for this key".into(),
706 ));
707 }
708
709 let (version, data) = Self::parse_ciphertext(ciphertext)?;
711
712 if version < key.min_decryption_version {
713 return Err(TransitError::VersionBelowMinDecryption {
714 version,
715 min: key.min_decryption_version,
716 });
717 }
718
719 let raw_key = self.get_key_material(name, version).await?;
721
722 let aad = format!("egide-transit:{name}:{version}");
724 let decrypted = aead::decrypt(&raw_key, &data, Some(aad.as_bytes()))
725 .map_err(|_| TransitError::DecryptionFailed)?;
726 Ok(decrypted.to_vec())
727 }
728
729 fn parse_ciphertext(ciphertext: &str) -> Result<(u32, Vec<u8>), TransitError> {
731 let parts: Vec<&str> = ciphertext.splitn(3, ':').collect();
732
733 if parts.len() != 3 || parts[0] != "egide" {
734 return Err(TransitError::InvalidCiphertext);
735 }
736
737 let version_str = parts[1]
738 .strip_prefix('v')
739 .ok_or(TransitError::InvalidCiphertext)?;
740 let version: u32 = version_str
741 .parse()
742 .map_err(|_| TransitError::InvalidCiphertext)?;
743
744 let data = BASE64
745 .decode(parts[2])
746 .map_err(|_| TransitError::InvalidCiphertext)?;
747
748 Ok((version, data))
749 }
750
751 pub async fn rewrap(&self, name: &str, ciphertext: &str) -> Result<String, TransitError> {
755 let key = self.get_key(name).await?;
756
757 let (current_version, _) = Self::parse_ciphertext(ciphertext)?;
759
760 if current_version == key.latest_version {
762 return Ok(ciphertext.to_string());
763 }
764
765 let plaintext = self.decrypt(name, ciphertext).await?;
767 self.encrypt(name, &plaintext).await
768 }
769
770 pub async fn generate_datakey(&self, name: &str) -> Result<DataKey, TransitError> {
779 let key = self.get_key(name).await?;
780
781 if !key.supports_encryption {
782 return Err(TransitError::OperationNotAllowed(
783 "datakey generation requires encryption capability".into(),
784 ));
785 }
786
787 let plaintext_key = random::generate_key()?;
789
790 let wrapped = self.encrypt(name, plaintext_key.as_ref()).await?;
792
793 Ok(DataKey {
794 plaintext: plaintext_key.to_vec(),
795 ciphertext: wrapped,
796 })
797 }
798
799 pub async fn decrypt_datakey(
801 &self,
802 name: &str,
803 wrapped: &str,
804 ) -> Result<Vec<u8>, TransitError> {
805 self.decrypt(name, wrapped).await
806 }
807}
808
809#[cfg(test)]
814#[allow(clippy::disallowed_methods)]
815mod tests {
816 use super::*;
817 use tempfile::TempDir;
818
819 async fn setup() -> (TempDir, TransitEngine) {
820 let tmp = TempDir::new().unwrap();
821 let master_key = MasterKey::generate().unwrap();
822 let engine = TransitEngine::new(tmp.path(), master_key).await.unwrap();
823 (tmp, engine)
824 }
825
826 #[tokio::test]
827 async fn test_create_and_get_key() {
828 let (_tmp, engine) = setup().await;
829
830 let key = engine.create_key("my-key", KeyConfig::new()).await.unwrap();
831 assert_eq!(key.name, "my-key");
832 assert_eq!(key.key_type, KeyType::Aes256Gcm);
833 assert_eq!(key.latest_version, 1);
834 assert!(key.supports_encryption);
835 assert!(key.supports_decryption);
836
837 let retrieved = engine.get_key("my-key").await.unwrap();
838 assert_eq!(retrieved.name, "my-key");
839 }
840
841 #[tokio::test]
842 async fn test_key_already_exists() {
843 let (_tmp, engine) = setup().await;
844
845 engine
846 .create_key("dup-key", KeyConfig::new())
847 .await
848 .unwrap();
849 let result = engine.create_key("dup-key", KeyConfig::new()).await;
850 assert!(matches!(result, Err(TransitError::KeyExists(_))));
851 }
852
853 #[tokio::test]
854 async fn test_key_not_found() {
855 let (_tmp, engine) = setup().await;
856
857 let result = engine.get_key("nonexistent").await;
858 assert!(matches!(result, Err(TransitError::KeyNotFound(_))));
859 }
860
861 #[tokio::test]
862 async fn test_invalid_key_name() {
863 let (_tmp, engine) = setup().await;
864
865 let result = engine.create_key("", KeyConfig::new()).await;
866 assert!(matches!(result, Err(TransitError::InvalidKeyName(_))));
867
868 let result = engine.create_key("key with spaces", KeyConfig::new()).await;
869 assert!(matches!(result, Err(TransitError::InvalidKeyName(_))));
870 }
871
872 #[tokio::test]
873 async fn test_list_keys() {
874 let (_tmp, engine) = setup().await;
875
876 engine.create_key("alpha", KeyConfig::new()).await.unwrap();
877 engine.create_key("beta", KeyConfig::new()).await.unwrap();
878 engine.create_key("gamma", KeyConfig::new()).await.unwrap();
879
880 let keys = engine.list_keys().await.unwrap();
881 assert_eq!(keys, vec!["alpha", "beta", "gamma"]);
882 }
883
884 #[tokio::test]
885 async fn test_encrypt_decrypt() {
886 let (_tmp, engine) = setup().await;
887
888 engine
889 .create_key("enc-key", KeyConfig::new())
890 .await
891 .unwrap();
892
893 let plaintext = b"Hello, World!";
894 let ciphertext = engine.encrypt("enc-key", plaintext).await.unwrap();
895
896 assert!(ciphertext.starts_with("egide:v1:"));
897
898 let decrypted = engine.decrypt("enc-key", &ciphertext).await.unwrap();
899 assert_eq!(decrypted, plaintext);
900 }
901
902 #[tokio::test]
903 async fn test_encrypt_decrypt_large_data() {
904 let (_tmp, engine) = setup().await;
905
906 engine
907 .create_key("large-key", KeyConfig::new())
908 .await
909 .unwrap();
910
911 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
913 let plaintext: Vec<u8> = (0..10000).map(|i| (i % 256) as u8).collect();
914 let ciphertext = engine.encrypt("large-key", &plaintext).await.unwrap();
915 let decrypted = engine.decrypt("large-key", &ciphertext).await.unwrap();
916
917 assert_eq!(decrypted, plaintext);
918 }
919
920 #[tokio::test]
921 async fn test_key_rotation() {
922 let (_tmp, engine) = setup().await;
923
924 engine
925 .create_key("rotate-key", KeyConfig::new())
926 .await
927 .unwrap();
928
929 let ciphertext_v1 = engine.encrypt("rotate-key", b"secret").await.unwrap();
931 assert!(ciphertext_v1.starts_with("egide:v1:"));
932
933 let new_version = engine.rotate_key("rotate-key").await.unwrap();
935 assert_eq!(new_version, 2);
936
937 let ciphertext_v2 = engine.encrypt("rotate-key", b"secret").await.unwrap();
939 assert!(ciphertext_v2.starts_with("egide:v2:"));
940
941 let decrypted_v1 = engine.decrypt("rotate-key", &ciphertext_v1).await.unwrap();
943 let decrypted_v2 = engine.decrypt("rotate-key", &ciphertext_v2).await.unwrap();
944 assert_eq!(decrypted_v1, b"secret");
945 assert_eq!(decrypted_v2, b"secret");
946 }
947
948 #[tokio::test]
949 async fn test_rewrap() {
950 let (_tmp, engine) = setup().await;
951
952 engine
953 .create_key("rewrap-key", KeyConfig::new())
954 .await
955 .unwrap();
956
957 let ciphertext_v1 = engine.encrypt("rewrap-key", b"data").await.unwrap();
958 assert!(ciphertext_v1.starts_with("egide:v1:"));
959
960 engine.rotate_key("rewrap-key").await.unwrap();
962
963 let ciphertext_v2 = engine.rewrap("rewrap-key", &ciphertext_v1).await.unwrap();
965 assert!(ciphertext_v2.starts_with("egide:v2:"));
966
967 let decrypted = engine.decrypt("rewrap-key", &ciphertext_v2).await.unwrap();
969 assert_eq!(decrypted, b"data");
970 }
971
972 #[tokio::test]
973 async fn test_min_decryption_version() {
974 let (_tmp, engine) = setup().await;
975
976 engine
977 .create_key("min-dec", KeyConfig::new())
978 .await
979 .unwrap();
980
981 let ciphertext_v1 = engine.encrypt("min-dec", b"old").await.unwrap();
982
983 engine.rotate_key("min-dec").await.unwrap();
985 engine
986 .update_key_config("min-dec", None, Some(2), None)
987 .await
988 .unwrap();
989
990 let result = engine.decrypt("min-dec", &ciphertext_v1).await;
992 assert!(matches!(
993 result,
994 Err(TransitError::VersionBelowMinDecryption { .. })
995 ));
996 }
997
998 #[tokio::test]
999 async fn test_delete_key() {
1000 let (_tmp, engine) = setup().await;
1001
1002 engine
1004 .create_key("no-delete", KeyConfig::new())
1005 .await
1006 .unwrap();
1007 let result = engine.delete_key("no-delete").await;
1008 assert!(matches!(result, Err(TransitError::DeletionNotAllowed(_))));
1009
1010 let mut config = KeyConfig::new();
1012 config.deletion_allowed = true;
1013 engine.create_key("can-delete", config).await.unwrap();
1014 engine.delete_key("can-delete").await.unwrap();
1015
1016 let result = engine.get_key("can-delete").await;
1017 assert!(matches!(result, Err(TransitError::KeyNotFound(_))));
1018 }
1019
1020 #[tokio::test]
1021 async fn test_generate_datakey() {
1022 let (_tmp, engine) = setup().await;
1023
1024 engine
1025 .create_key("dek-key", KeyConfig::new())
1026 .await
1027 .unwrap();
1028
1029 let datakey = engine.generate_datakey("dek-key").await.unwrap();
1030 assert_eq!(datakey.plaintext.len(), 32);
1031 assert!(datakey.ciphertext.starts_with("egide:v1:"));
1032
1033 let decrypted = engine
1035 .decrypt_datakey("dek-key", &datakey.ciphertext)
1036 .await
1037 .unwrap();
1038 assert_eq!(decrypted, datakey.plaintext);
1039 }
1040
1041 #[tokio::test]
1042 async fn test_encryption_disabled() {
1043 let (_tmp, engine) = setup().await;
1044
1045 let mut config = KeyConfig::new();
1046 config.supports_encryption = false;
1047
1048 engine.create_key("no-enc", config).await.unwrap();
1049
1050 let result = engine.encrypt("no-enc", b"data").await;
1051 assert!(matches!(result, Err(TransitError::OperationNotAllowed(_))));
1052 }
1053
1054 #[tokio::test]
1055 async fn test_decryption_disabled() {
1056 let (_tmp, engine) = setup().await;
1057
1058 engine
1060 .create_key("enc-only", KeyConfig::new())
1061 .await
1062 .unwrap();
1063 let ciphertext = engine.encrypt("enc-only", b"data").await.unwrap();
1064
1065 let (_tmp2, engine2) = setup().await;
1067 let mut config = KeyConfig::new();
1068 config.supports_decryption = false;
1069 engine2.create_key("no-dec", config).await.unwrap();
1070
1071 let result = engine2.decrypt("no-dec", &ciphertext).await;
1072 assert!(matches!(result, Err(TransitError::OperationNotAllowed(_))));
1073 }
1074
1075 #[tokio::test]
1076 async fn test_invalid_ciphertext() {
1077 let (_tmp, engine) = setup().await;
1078
1079 engine
1080 .create_key("test-key", KeyConfig::new())
1081 .await
1082 .unwrap();
1083
1084 let result = engine.decrypt("test-key", "invalid").await;
1085 assert!(matches!(result, Err(TransitError::InvalidCiphertext)));
1086
1087 let result = engine.decrypt("test-key", "not:a:valid:format").await;
1088 assert!(matches!(result, Err(TransitError::InvalidCiphertext)));
1089
1090 let result = engine
1091 .decrypt("test-key", "egide:v1:!!!invalid-base64!!!")
1092 .await;
1093 assert!(matches!(result, Err(TransitError::InvalidCiphertext)));
1094 }
1095
1096 #[tokio::test]
1097 async fn test_key_isolation() {
1098 let (_tmp, engine) = setup().await;
1099
1100 engine.create_key("key-a", KeyConfig::new()).await.unwrap();
1101 engine.create_key("key-b", KeyConfig::new()).await.unwrap();
1102
1103 let ciphertext = engine.encrypt("key-a", b"secret").await.unwrap();
1104
1105 let result = engine.decrypt("key-b", &ciphertext).await;
1107 assert!(matches!(result, Err(TransitError::DecryptionFailed)));
1108 }
1109
1110 #[tokio::test]
1111 async fn test_list_versions() {
1112 let (_tmp, engine) = setup().await;
1113
1114 engine
1115 .create_key("ver-key", KeyConfig::new())
1116 .await
1117 .unwrap();
1118 engine.rotate_key("ver-key").await.unwrap();
1119 engine.rotate_key("ver-key").await.unwrap();
1120
1121 let versions = engine.list_versions("ver-key").await.unwrap();
1122 assert_eq!(versions.len(), 3);
1123 assert_eq!(versions[0].version, 3); assert_eq!(versions[1].version, 2);
1125 assert_eq!(versions[2].version, 1);
1126 }
1127
1128 #[tokio::test]
1133 async fn test_encrypt_empty_data() {
1134 let (_tmp, engine) = setup().await;
1135 engine
1136 .create_key("empty-key", KeyConfig::new())
1137 .await
1138 .unwrap();
1139
1140 let ciphertext = engine.encrypt("empty-key", b"").await.unwrap();
1141 let decrypted = engine.decrypt("empty-key", &ciphertext).await.unwrap();
1142 assert_eq!(decrypted, b"");
1143 }
1144
1145 #[tokio::test]
1146 async fn test_encrypt_binary_data() {
1147 let (_tmp, engine) = setup().await;
1148 engine
1149 .create_key("bin-key", KeyConfig::new())
1150 .await
1151 .unwrap();
1152
1153 let binary_data: Vec<u8> = (0..=255).collect();
1155 let ciphertext = engine.encrypt("bin-key", &binary_data).await.unwrap();
1156 let decrypted = engine.decrypt("bin-key", &ciphertext).await.unwrap();
1157 assert_eq!(decrypted, binary_data);
1158 }
1159
1160 #[tokio::test]
1161 async fn test_encrypt_unicode_data() {
1162 let (_tmp, engine) = setup().await;
1163 engine
1164 .create_key("unicode-key", KeyConfig::new())
1165 .await
1166 .unwrap();
1167
1168 let unicode_data = "Hello 世界! 🔐 Ægide résiste aux attaques! 日本語テスト";
1169 let ciphertext = engine
1170 .encrypt("unicode-key", unicode_data.as_bytes())
1171 .await
1172 .unwrap();
1173 let decrypted = engine.decrypt("unicode-key", &ciphertext).await.unwrap();
1174 assert_eq!(String::from_utf8(decrypted).unwrap(), unicode_data);
1175 }
1176
1177 #[tokio::test]
1178 async fn test_key_name_max_length() {
1179 let (_tmp, engine) = setup().await;
1180
1181 let max_name: String = "a".repeat(128);
1183 engine
1184 .create_key(&max_name, KeyConfig::new())
1185 .await
1186 .unwrap();
1187
1188 let too_long: String = "a".repeat(129);
1190 let result = engine.create_key(&too_long, KeyConfig::new()).await;
1191 assert!(matches!(result, Err(TransitError::InvalidKeyName(_))));
1192 }
1193
1194 #[tokio::test]
1195 async fn test_key_name_allowed_chars() {
1196 let (_tmp, engine) = setup().await;
1197
1198 engine.create_key("my-key", KeyConfig::new()).await.unwrap();
1200 engine.create_key("my_key", KeyConfig::new()).await.unwrap();
1201 engine
1202 .create_key("MyKey123", KeyConfig::new())
1203 .await
1204 .unwrap();
1205 engine
1206 .create_key("KEY-2024_test", KeyConfig::new())
1207 .await
1208 .unwrap();
1209
1210 let result = engine.create_key("key/path", KeyConfig::new()).await;
1212 assert!(matches!(result, Err(TransitError::InvalidKeyName(_))));
1213
1214 let result = engine.create_key("key.name", KeyConfig::new()).await;
1215 assert!(matches!(result, Err(TransitError::InvalidKeyName(_))));
1216
1217 let result = engine.create_key("key name", KeyConfig::new()).await;
1218 assert!(matches!(result, Err(TransitError::InvalidKeyName(_))));
1219 }
1220
1221 #[tokio::test]
1226 async fn test_encrypt_with_specific_version() {
1227 let (_tmp, engine) = setup().await;
1228 engine
1229 .create_key("ver-enc", KeyConfig::new())
1230 .await
1231 .unwrap();
1232 engine.rotate_key("ver-enc").await.unwrap();
1233 engine.rotate_key("ver-enc").await.unwrap();
1234
1235 let ciphertext = engine
1237 .encrypt_with_version("ver-enc", b"data", 2)
1238 .await
1239 .unwrap();
1240 assert!(ciphertext.starts_with("egide:v2:"));
1241
1242 let decrypted = engine.decrypt("ver-enc", &ciphertext).await.unwrap();
1244 assert_eq!(decrypted, b"data");
1245 }
1246
1247 #[tokio::test]
1248 async fn test_min_encryption_version() {
1249 let (_tmp, engine) = setup().await;
1250 engine
1251 .create_key("min-enc", KeyConfig::new())
1252 .await
1253 .unwrap();
1254 engine.rotate_key("min-enc").await.unwrap();
1255
1256 engine
1258 .update_key_config("min-enc", Some(2), None, None)
1259 .await
1260 .unwrap();
1261
1262 let result = engine.encrypt_with_version("min-enc", b"data", 1).await;
1264 assert!(matches!(
1265 result,
1266 Err(TransitError::VersionBelowMinEncryption { .. })
1267 ));
1268
1269 let ciphertext = engine
1271 .encrypt_with_version("min-enc", b"data", 2)
1272 .await
1273 .unwrap();
1274 assert!(ciphertext.starts_with("egide:v2:"));
1275 }
1276
1277 #[tokio::test]
1278 async fn test_encrypt_with_nonexistent_version() {
1279 let (_tmp, engine) = setup().await;
1280 engine
1281 .create_key("noversion", KeyConfig::new())
1282 .await
1283 .unwrap();
1284
1285 let result = engine.encrypt_with_version("noversion", b"data", 99).await;
1287 assert!(matches!(result, Err(TransitError::VersionNotFound { .. })));
1288 }
1289
1290 #[tokio::test]
1291 async fn test_rewrap_already_latest() {
1292 let (_tmp, engine) = setup().await;
1293 engine
1294 .create_key("rewrap-latest", KeyConfig::new())
1295 .await
1296 .unwrap();
1297
1298 let ciphertext = engine.encrypt("rewrap-latest", b"data").await.unwrap();
1299
1300 let rewrapped = engine.rewrap("rewrap-latest", &ciphertext).await.unwrap();
1302 assert_eq!(rewrapped, ciphertext);
1303 }
1304
1305 #[tokio::test]
1306 async fn test_multiple_rotations() {
1307 let (_tmp, engine) = setup().await;
1308 engine
1309 .create_key("multi-rot", KeyConfig::new())
1310 .await
1311 .unwrap();
1312
1313 let ct1 = engine.encrypt("multi-rot", b"v1-data").await.unwrap();
1315
1316 engine.rotate_key("multi-rot").await.unwrap();
1317 let ct2 = engine.encrypt("multi-rot", b"v2-data").await.unwrap();
1318
1319 engine.rotate_key("multi-rot").await.unwrap();
1320 let ct3 = engine.encrypt("multi-rot", b"v3-data").await.unwrap();
1321
1322 engine.rotate_key("multi-rot").await.unwrap();
1323 let ct4 = engine.encrypt("multi-rot", b"v4-data").await.unwrap();
1324
1325 engine.rotate_key("multi-rot").await.unwrap();
1326 let ct5 = engine.encrypt("multi-rot", b"v5-data").await.unwrap();
1327
1328 assert_eq!(engine.decrypt("multi-rot", &ct1).await.unwrap(), b"v1-data");
1330 assert_eq!(engine.decrypt("multi-rot", &ct2).await.unwrap(), b"v2-data");
1331 assert_eq!(engine.decrypt("multi-rot", &ct3).await.unwrap(), b"v3-data");
1332 assert_eq!(engine.decrypt("multi-rot", &ct4).await.unwrap(), b"v4-data");
1333 assert_eq!(engine.decrypt("multi-rot", &ct5).await.unwrap(), b"v5-data");
1334
1335 assert!(ct1.starts_with("egide:v1:"));
1337 assert!(ct5.starts_with("egide:v5:"));
1338 }
1339
1340 #[tokio::test]
1345 async fn test_tampered_ciphertext_base64() {
1346 let (_tmp, engine) = setup().await;
1347 engine
1348 .create_key("tamper-key", KeyConfig::new())
1349 .await
1350 .unwrap();
1351
1352 let ciphertext = engine.encrypt("tamper-key", b"secret").await.unwrap();
1353
1354 let parts: Vec<&str> = ciphertext.splitn(3, ':').collect();
1356 let tampered = format!("{}:{}:{}TAMPERED", parts[0], parts[1], parts[2]);
1357
1358 let result = engine.decrypt("tamper-key", &tampered).await;
1359 assert!(result.is_err());
1361 }
1362
1363 #[tokio::test]
1364 async fn test_tampered_ciphertext_bytes() {
1365 let (_tmp, engine) = setup().await;
1366 engine
1367 .create_key("tamper-bytes", KeyConfig::new())
1368 .await
1369 .unwrap();
1370
1371 let ciphertext = engine.encrypt("tamper-bytes", b"secret").await.unwrap();
1372
1373 let parts: Vec<&str> = ciphertext.splitn(3, ':').collect();
1375 let mut bytes = BASE64.decode(parts[2]).unwrap();
1376 if !bytes.is_empty() {
1377 bytes[0] ^= 0xFF; }
1379 let tampered = format!("{}:{}:{}", parts[0], parts[1], BASE64.encode(&bytes));
1380
1381 let result = engine.decrypt("tamper-bytes", &tampered).await;
1382 assert!(matches!(result, Err(TransitError::DecryptionFailed)));
1383 }
1384
1385 #[tokio::test]
1386 async fn test_wrong_version_in_ciphertext() {
1387 let (_tmp, engine) = setup().await;
1388 engine
1389 .create_key("wrong-ver", KeyConfig::new())
1390 .await
1391 .unwrap();
1392
1393 let ciphertext = engine.encrypt("wrong-ver", b"data").await.unwrap();
1394
1395 let fake_v2 = ciphertext.replace("egide:v1:", "egide:v2:");
1397
1398 let result = engine.decrypt("wrong-ver", &fake_v2).await;
1400 assert!(matches!(result, Err(TransitError::VersionNotFound { .. })));
1401 }
1402
1403 #[tokio::test]
1404 async fn test_update_config_invalid_version() {
1405 let (_tmp, engine) = setup().await;
1406 engine
1407 .create_key("cfg-ver", KeyConfig::new())
1408 .await
1409 .unwrap();
1410
1411 let result = engine
1413 .update_key_config("cfg-ver", Some(99), None, None)
1414 .await;
1415 assert!(matches!(result, Err(TransitError::VersionNotFound { .. })));
1416
1417 let result = engine
1419 .update_key_config("cfg-ver", None, Some(99), None)
1420 .await;
1421 assert!(matches!(result, Err(TransitError::VersionNotFound { .. })));
1422 }
1423
1424 #[tokio::test]
1425 async fn test_list_versions_nonexistent_key() {
1426 let (_tmp, engine) = setup().await;
1427
1428 let result = engine.list_versions("nonexistent").await;
1429 assert!(matches!(result, Err(TransitError::KeyNotFound(_))));
1430 }
1431
1432 #[tokio::test]
1437 async fn test_full_lifecycle() {
1438 let (_tmp, engine) = setup().await;
1439
1440 let key = engine
1442 .create_key("lifecycle", KeyConfig::new())
1443 .await
1444 .unwrap();
1445 assert_eq!(key.latest_version, 1);
1446
1447 let ct1 = engine
1449 .encrypt("lifecycle", b"initial-secret")
1450 .await
1451 .unwrap();
1452
1453 let v2 = engine.rotate_key("lifecycle").await.unwrap();
1455 assert_eq!(v2, 2);
1456
1457 let ct2 = engine.encrypt("lifecycle", b"new-secret").await.unwrap();
1459
1460 assert_eq!(
1462 engine.decrypt("lifecycle", &ct1).await.unwrap(),
1463 b"initial-secret"
1464 );
1465 assert_eq!(
1466 engine.decrypt("lifecycle", &ct2).await.unwrap(),
1467 b"new-secret"
1468 );
1469
1470 let ct1_rewrapped = engine.rewrap("lifecycle", &ct1).await.unwrap();
1472 assert!(ct1_rewrapped.starts_with("egide:v2:"));
1473 assert_eq!(
1474 engine.decrypt("lifecycle", &ct1_rewrapped).await.unwrap(),
1475 b"initial-secret"
1476 );
1477
1478 engine
1480 .update_key_config("lifecycle", None, Some(2), None)
1481 .await
1482 .unwrap();
1483
1484 let result = engine.decrypt("lifecycle", &ct1).await;
1486 assert!(matches!(
1487 result,
1488 Err(TransitError::VersionBelowMinDecryption { .. })
1489 ));
1490
1491 assert_eq!(
1493 engine.decrypt("lifecycle", &ct1_rewrapped).await.unwrap(),
1494 b"initial-secret"
1495 );
1496 }
1497
1498 #[tokio::test]
1499 async fn test_envelope_encryption_workflow() {
1500 let (_tmp, engine) = setup().await;
1501 engine
1502 .create_key("envelope-kek", KeyConfig::new())
1503 .await
1504 .unwrap();
1505
1506 let datakey = engine.generate_datakey("envelope-kek").await.unwrap();
1509
1510 let client_data = b"sensitive application data";
1512 let client_encrypted =
1513 egide_crypto::aead::encrypt(&datakey.plaintext, client_data, Some(b"app-context"))
1514 .unwrap();
1515
1516 let stored_wrapped_key = datakey.ciphertext.clone();
1518
1519 let recovered_dek = engine
1522 .decrypt_datakey("envelope-kek", &stored_wrapped_key)
1523 .await
1524 .unwrap();
1525 assert_eq!(recovered_dek, datakey.plaintext);
1526
1527 let decrypted =
1529 egide_crypto::aead::decrypt(&recovered_dek, &client_encrypted, Some(b"app-context"))
1530 .unwrap();
1531 assert_eq!(&decrypted[..], client_data);
1532 }
1533
1534 #[tokio::test]
1535 async fn test_multi_key_workflow() {
1536 let (_tmp, engine) = setup().await;
1537
1538 engine
1540 .create_key("users-key", KeyConfig::new())
1541 .await
1542 .unwrap();
1543 engine
1544 .create_key("payments-key", KeyConfig::new())
1545 .await
1546 .unwrap();
1547 engine
1548 .create_key("logs-key", KeyConfig::new())
1549 .await
1550 .unwrap();
1551
1552 let user_ct = engine
1554 .encrypt("users-key", b"user@email.com")
1555 .await
1556 .unwrap();
1557 let payment_ct = engine
1558 .encrypt("payments-key", b"4111111111111111")
1559 .await
1560 .unwrap();
1561 let log_ct = engine
1562 .encrypt("logs-key", b"debug log entry")
1563 .await
1564 .unwrap();
1565
1566 assert!(engine.decrypt("payments-key", &user_ct).await.is_err());
1568 assert!(engine.decrypt("logs-key", &payment_ct).await.is_err());
1569 assert!(engine.decrypt("users-key", &log_ct).await.is_err());
1570
1571 assert_eq!(
1573 engine.decrypt("users-key", &user_ct).await.unwrap(),
1574 b"user@email.com"
1575 );
1576 assert_eq!(
1577 engine.decrypt("payments-key", &payment_ct).await.unwrap(),
1578 b"4111111111111111"
1579 );
1580 assert_eq!(
1581 engine.decrypt("logs-key", &log_ct).await.unwrap(),
1582 b"debug log entry"
1583 );
1584 }
1585
1586 #[tokio::test]
1591 async fn test_persistence_across_restart() {
1592 let tmp = TempDir::new().unwrap();
1593 let master_key = MasterKey::generate().unwrap();
1594 let master_key_bytes = master_key.as_bytes().to_vec();
1595
1596 let ciphertext = {
1598 let engine = TransitEngine::new(tmp.path(), master_key).await.unwrap();
1599 engine
1600 .create_key("persist-key", KeyConfig::new())
1601 .await
1602 .unwrap();
1603 engine.rotate_key("persist-key").await.unwrap();
1604 engine
1605 .encrypt("persist-key", b"persisted-data")
1606 .await
1607 .unwrap()
1608 };
1609 {
1613 let master_key2 = MasterKey::from_bytes(&master_key_bytes).unwrap();
1614 let engine2 = TransitEngine::new(tmp.path(), master_key2).await.unwrap();
1615
1616 let key = engine2.get_key("persist-key").await.unwrap();
1618 assert_eq!(key.latest_version, 2);
1619
1620 let decrypted = engine2.decrypt("persist-key", &ciphertext).await.unwrap();
1622 assert_eq!(decrypted, b"persisted-data");
1623
1624 let v3 = engine2.rotate_key("persist-key").await.unwrap();
1626 assert_eq!(v3, 3);
1627 }
1628 }
1629
1630 #[tokio::test]
1631 async fn test_wrong_master_key_fails() {
1632 let tmp = TempDir::new().unwrap();
1633
1634 let ciphertext = {
1636 let master_key1 = MasterKey::generate().unwrap();
1637 let engine = TransitEngine::new(tmp.path(), master_key1).await.unwrap();
1638 engine
1639 .create_key("wrong-mk", KeyConfig::new())
1640 .await
1641 .unwrap();
1642 engine.encrypt("wrong-mk", b"data").await.unwrap()
1643 };
1644
1645 {
1647 let master_key2 = MasterKey::generate().unwrap(); let engine2 = TransitEngine::new(tmp.path(), master_key2).await.unwrap();
1649
1650 let key = engine2.get_key("wrong-mk").await.unwrap();
1652 assert_eq!(key.name, "wrong-mk");
1653
1654 let result = engine2.decrypt("wrong-mk", &ciphertext).await;
1656 assert!(result.is_err());
1657 }
1658 }
1659
1660 #[tokio::test]
1661 async fn test_concurrent_encryptions() {
1662 let (_tmp, engine) = setup().await;
1663 engine
1664 .create_key("concurrent", KeyConfig::new())
1665 .await
1666 .unwrap();
1667
1668 let mut ciphertexts = Vec::new();
1670 for i in 0..100 {
1671 let data = format!("message-{i}");
1672 let ct = engine.encrypt("concurrent", data.as_bytes()).await.unwrap();
1673 ciphertexts.push((data, ct));
1674 }
1675
1676 for (original, ct) in ciphertexts {
1678 let decrypted = engine.decrypt("concurrent", &ct).await.unwrap();
1679 assert_eq!(String::from_utf8(decrypted).unwrap(), original);
1680 }
1681 }
1682}