aead.rs
1 //! Authenticated Encryption with Associated Data (AEAD). 2 //! 3 //! This module provides ChaCha20-Poly1305 encryption and decryption, the same 4 //! algorithm used by TLS 1.3, WireGuard, and many modern protocols. 5 //! 6 //! # What is AEAD? 7 //! 8 //! AEAD ciphers provide two guarantees in a single operation: 9 //! 10 //! - **Confidentiality**: Encrypted data cannot be read without the key 11 //! - **Authenticity**: Any tampering with ciphertext or AAD is detected 12 //! 13 //! This is strictly better than encrypting and MACing separately, as AEAD 14 //! avoids subtle composition errors that have broken many protocols. 15 //! 16 //! # Why ChaCha20-Poly1305? 17 //! 18 //! - **No timing side-channels**: Pure arithmetic operations, no table lookups 19 //! - **Software-friendly**: Fast on platforms without AES hardware (mobile, etc.) 20 //! - **Widely audited**: Used by TLS 1.3, WireGuard, Signal, etc. 21 //! - **Simple API**: Hard to misuse compared to block cipher modes 22 //! 23 //! # Security Properties 24 //! 25 //! | Property | Provided? | Notes | 26 //! |----------|-----------|-------| 27 //! | Confidentiality | ✓ | Data is encrypted with 256-bit key | 28 //! | Authenticity | ✓ | 128-bit Poly1305 tag detects tampering | 29 //! | Integrity of AAD | ✓ | Associated data is authenticated | 30 //! | Nonce misuse resistance | ✗ | **Never reuse a nonce with the same key** | 31 //! 32 //! # Nonce Safety 33 //! 34 //! **CRITICAL**: Reusing a nonce with the same key completely breaks security. 35 //! This module provides two safe patterns: 36 //! 37 //! 1. **Random nonces**: Use [`generate_nonce()`] or [`seal()`] which generate 38 //! random 96-bit nonces. Safe for up to 2^32 messages per key. 39 //! 40 //! 2. **Counter nonces**: If you need more messages, use a counter stored 41 //! persistently. Must never be reused, even after crash/restart. 42 //! 43 //! # Usage 44 //! 45 //! ``` 46 //! use dead_drop_core::crypto::aead::{encrypt, decrypt, generate_nonce}; 47 //! 48 //! let key = [0x42u8; 32]; 49 //! let nonce = generate_nonce(); 50 //! let plaintext = b"secret message"; 51 //! let aad = b"message-id-123"; // Authenticated but not encrypted 52 //! 53 //! let ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap(); 54 //! let decrypted = decrypt(&key, &nonce, &ciphertext, aad).unwrap(); 55 //! 56 //! assert_eq!(plaintext.as_slice(), decrypted.as_slice()); 57 //! ``` 58 //! 59 //! # High-Level vs Low-Level API 60 //! 61 //! | Function | Nonce Handling | Best For | 62 //! |----------|----------------|----------| 63 //! | [`seal()`] / [`open()`] | Automatic (random, prepended) | Most use cases | 64 //! | [`encrypt()`] / [`decrypt()`] | Manual (caller provides) | Custom protocols | 65 66 use chacha20poly1305::{ 67 aead::{Aead, KeyInit, Payload}, 68 ChaCha20Poly1305, Key, Nonce, 69 }; 70 use rand::rngs::OsRng; 71 use rand::RngCore; 72 use zeroize::Zeroize; 73 74 use crate::error::{DeadDropError, Result}; 75 76 // ============================================================================= 77 // CONSTANTS 78 // ============================================================================= 79 80 /// Size of ChaCha20-Poly1305 key in bytes (256 bits). 81 /// 82 /// This provides 256-bit security against brute-force attacks. 83 pub const KEY_SIZE: usize = 32; 84 85 /// Size of ChaCha20-Poly1305 nonce in bytes (96 bits). 86 /// 87 /// With random nonces, you can safely encrypt up to 2^32 messages 88 /// before the birthday bound becomes concerning. 89 pub const NONCE_SIZE: usize = 12; 90 91 /// Size of Poly1305 authentication tag in bytes (128 bits). 92 /// 93 /// The tag is appended to the ciphertext and verified during decryption. 94 /// Any modification to the ciphertext or AAD will cause verification to fail. 95 pub const TAG_SIZE: usize = 16; 96 97 /// Maximum plaintext size (to prevent memory exhaustion). 98 /// 99 /// Set to 64 MB. Adjust based on your application's requirements. 100 /// Messages larger than this will return `MessageTooLarge` error. 101 pub const MAX_PLAINTEXT_SIZE: usize = 64 * 1024 * 1024; // 64 MB 102 103 /// Encrypt plaintext using ChaCha20-Poly1305. 104 /// 105 /// # Arguments 106 /// 107 /// * `key` - 32-byte encryption key 108 /// * `nonce` - 12-byte nonce (must be unique for each encryption with the same key) 109 /// * `plaintext` - Data to encrypt 110 /// * `associated_data` - Additional data to authenticate but not encrypt 111 /// 112 /// # Returns 113 /// 114 /// Ciphertext with appended authentication tag (plaintext.len() + 16 bytes). 115 /// 116 /// # Errors 117 /// 118 /// - `Encryption` if the plaintext is too large 119 /// - `Encryption` if the encryption operation fails (should not happen with valid inputs) 120 /// 121 /// # Security 122 /// 123 /// **CRITICAL**: Never reuse a nonce with the same key. Use `generate_nonce()` 124 /// to create random nonces, or use a counter-based approach if you need 125 /// deterministic nonces. 126 pub fn encrypt( 127 key: &[u8; KEY_SIZE], 128 nonce: &[u8; NONCE_SIZE], 129 plaintext: &[u8], 130 associated_data: &[u8], 131 ) -> Result<Vec<u8>> { 132 // Check plaintext size 133 if plaintext.len() > MAX_PLAINTEXT_SIZE { 134 return Err(DeadDropError::MessageTooLarge { 135 size: plaintext.len(), 136 max: MAX_PLAINTEXT_SIZE, 137 }); 138 } 139 140 let cipher = ChaCha20Poly1305::new(Key::from_slice(key)); 141 let nonce = Nonce::from_slice(nonce); 142 143 let payload = Payload { 144 msg: plaintext, 145 aad: associated_data, 146 }; 147 148 cipher 149 .encrypt(nonce, payload) 150 .map_err(|_| DeadDropError::Encryption("ChaCha20-Poly1305 encryption failed".to_string())) 151 } 152 153 /// Decrypt ciphertext using ChaCha20-Poly1305. 154 /// 155 /// # Arguments 156 /// 157 /// * `key` - 32-byte decryption key 158 /// * `nonce` - 12-byte nonce (same nonce used for encryption) 159 /// * `ciphertext` - Data to decrypt (includes authentication tag) 160 /// * `associated_data` - Additional authenticated data (must match encryption) 161 /// 162 /// # Returns 163 /// 164 /// Decrypted plaintext. 165 /// 166 /// # Errors 167 /// 168 /// - `Decryption` if authentication fails (ciphertext or AAD was tampered with) 169 /// - `Decryption` if the ciphertext is too short (< 16 bytes for tag) 170 pub fn decrypt( 171 key: &[u8; KEY_SIZE], 172 nonce: &[u8; NONCE_SIZE], 173 ciphertext: &[u8], 174 associated_data: &[u8], 175 ) -> Result<Vec<u8>> { 176 // Ciphertext must be at least TAG_SIZE bytes (for the auth tag) 177 if ciphertext.len() < TAG_SIZE { 178 return Err(DeadDropError::Decryption( 179 "Ciphertext too short".to_string(), 180 )); 181 } 182 183 let cipher = ChaCha20Poly1305::new(Key::from_slice(key)); 184 let nonce = Nonce::from_slice(nonce); 185 186 let payload = Payload { 187 msg: ciphertext, 188 aad: associated_data, 189 }; 190 191 cipher 192 .decrypt(nonce, payload) 193 .map_err(|_| DeadDropError::Decryption("Authentication failed".to_string())) 194 } 195 196 /// Generate a random 12-byte nonce. 197 /// 198 /// Uses the operating system's secure random number generator. 199 /// 200 /// # Security 201 /// 202 /// Random nonces are safe for up to 2^32 messages with the same key 203 /// before collision probability becomes concerning. For higher message 204 /// volumes, consider using a counter-based nonce scheme. 205 pub fn generate_nonce() -> [u8; NONCE_SIZE] { 206 let mut nonce = [0u8; NONCE_SIZE]; 207 OsRng.fill_bytes(&mut nonce); 208 nonce 209 } 210 211 /// Encrypt and return both nonce and ciphertext. 212 /// 213 /// This is a convenience function that generates a random nonce 214 /// and returns it along with the ciphertext. 215 /// 216 /// # Arguments 217 /// 218 /// * `key` - 32-byte encryption key 219 /// * `plaintext` - Data to encrypt 220 /// * `associated_data` - Additional data to authenticate but not encrypt 221 /// 222 /// # Returns 223 /// 224 /// A tuple of (nonce, ciphertext). 225 pub fn encrypt_with_random_nonce( 226 key: &[u8; KEY_SIZE], 227 plaintext: &[u8], 228 associated_data: &[u8], 229 ) -> Result<([u8; NONCE_SIZE], Vec<u8>)> { 230 let nonce = generate_nonce(); 231 let ciphertext = encrypt(key, &nonce, plaintext, associated_data)?; 232 Ok((nonce, ciphertext)) 233 } 234 235 /// Sealed box: nonce prepended to ciphertext. 236 /// 237 /// This format is convenient for storage and transmission as it 238 /// keeps the nonce with the ciphertext. 239 /// 240 /// Format: nonce (12 bytes) || ciphertext (N + 16 bytes) 241 /// 242 /// # Arguments 243 /// 244 /// * `key` - 32-byte encryption key 245 /// * `plaintext` - Data to encrypt 246 /// * `associated_data` - Additional data to authenticate but not encrypt 247 /// 248 /// # Returns 249 /// 250 /// Combined nonce and ciphertext (12 + plaintext.len() + 16 bytes). 251 pub fn seal(key: &[u8; KEY_SIZE], plaintext: &[u8], associated_data: &[u8]) -> Result<Vec<u8>> { 252 let nonce = generate_nonce(); 253 let ciphertext = encrypt(key, &nonce, plaintext, associated_data)?; 254 255 let mut sealed = Vec::with_capacity(NONCE_SIZE + ciphertext.len()); 256 sealed.extend_from_slice(&nonce); 257 sealed.extend(ciphertext); 258 259 Ok(sealed) 260 } 261 262 /// Open a sealed box. 263 /// 264 /// Extracts the nonce from the beginning of the sealed data and decrypts. 265 /// 266 /// # Arguments 267 /// 268 /// * `key` - 32-byte decryption key 269 /// * `sealed` - Combined nonce and ciphertext 270 /// * `associated_data` - Additional authenticated data 271 /// 272 /// # Returns 273 /// 274 /// Decrypted plaintext. 275 /// 276 /// # Errors 277 /// 278 /// - `Decryption` if the sealed data is too short 279 /// - `Decryption` if authentication fails 280 pub fn open(key: &[u8; KEY_SIZE], sealed: &[u8], associated_data: &[u8]) -> Result<Vec<u8>> { 281 // Need at least nonce + tag 282 if sealed.len() < NONCE_SIZE + TAG_SIZE { 283 return Err(DeadDropError::Decryption("Sealed data too short".to_string())); 284 } 285 286 let nonce: [u8; NONCE_SIZE] = sealed[..NONCE_SIZE] 287 .try_into() 288 .map_err(|_| DeadDropError::Decryption("Invalid nonce".to_string()))?; 289 290 let ciphertext = &sealed[NONCE_SIZE..]; 291 292 decrypt(key, &nonce, ciphertext, associated_data) 293 } 294 295 /// Securely encrypt sensitive data with automatic key zeroization. 296 /// 297 /// This function takes ownership of the key and zeroizes it after use. 298 /// 299 /// # Arguments 300 /// 301 /// * `key` - 32-byte encryption key (will be zeroized) 302 /// * `plaintext` - Data to encrypt 303 /// * `associated_data` - Additional data to authenticate 304 /// 305 /// # Returns 306 /// 307 /// Sealed ciphertext (nonce prepended). 308 pub fn seal_and_zeroize_key( 309 mut key: [u8; KEY_SIZE], 310 plaintext: &[u8], 311 associated_data: &[u8], 312 ) -> Result<Vec<u8>> { 313 let result = seal(&key, plaintext, associated_data); 314 key.zeroize(); 315 result 316 } 317 318 /// Calculate the ciphertext length for a given plaintext length. 319 /// 320 /// Useful for pre-allocating buffers. 321 pub const fn ciphertext_len(plaintext_len: usize) -> usize { 322 plaintext_len + TAG_SIZE 323 } 324 325 /// Calculate the sealed length for a given plaintext length. 326 /// 327 /// Includes nonce and authentication tag. 328 pub const fn sealed_len(plaintext_len: usize) -> usize { 329 NONCE_SIZE + plaintext_len + TAG_SIZE 330 } 331 332 #[cfg(test)] 333 mod tests { 334 use super::*; 335 336 // ==================== Basic Encryption/Decryption Tests ==================== 337 338 #[test] 339 fn test_encrypt_decrypt_basic() { 340 let key = [0x42u8; KEY_SIZE]; 341 let nonce = generate_nonce(); 342 let plaintext = b"Hello, World!"; 343 let aad = b""; 344 345 let ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap(); 346 let decrypted = decrypt(&key, &nonce, &ciphertext, aad).unwrap(); 347 348 assert_eq!(plaintext.as_slice(), decrypted.as_slice()); 349 } 350 351 #[test] 352 fn test_encrypt_decrypt_with_aad() { 353 let key = [0x42u8; KEY_SIZE]; 354 let nonce = generate_nonce(); 355 let plaintext = b"Secret message"; 356 let aad = b"message-id-12345"; 357 358 let ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap(); 359 let decrypted = decrypt(&key, &nonce, &ciphertext, aad).unwrap(); 360 361 assert_eq!(plaintext.as_slice(), decrypted.as_slice()); 362 } 363 364 #[test] 365 fn test_encrypt_decrypt_empty_plaintext() { 366 let key = [0x42u8; KEY_SIZE]; 367 let nonce = generate_nonce(); 368 let plaintext = b""; 369 let aad = b"some-aad"; 370 371 let ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap(); 372 let decrypted = decrypt(&key, &nonce, &ciphertext, aad).unwrap(); 373 374 assert_eq!(plaintext.as_slice(), decrypted.as_slice()); 375 // Ciphertext should just be the tag 376 assert_eq!(ciphertext.len(), TAG_SIZE); 377 } 378 379 #[test] 380 fn test_encrypt_decrypt_large_plaintext() { 381 let key = [0x42u8; KEY_SIZE]; 382 let nonce = generate_nonce(); 383 let plaintext = vec![0xABu8; 1024 * 1024]; // 1 MB 384 let aad = b""; 385 386 let ciphertext = encrypt(&key, &nonce, &plaintext, aad).unwrap(); 387 let decrypted = decrypt(&key, &nonce, &ciphertext, aad).unwrap(); 388 389 assert_eq!(plaintext, decrypted); 390 } 391 392 #[test] 393 fn test_ciphertext_length() { 394 let key = [0x42u8; KEY_SIZE]; 395 let nonce = generate_nonce(); 396 let plaintext = b"test message"; 397 let aad = b""; 398 399 let ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap(); 400 401 // Ciphertext should be plaintext + tag 402 assert_eq!(ciphertext.len(), plaintext.len() + TAG_SIZE); 403 assert_eq!(ciphertext.len(), ciphertext_len(plaintext.len())); 404 } 405 406 // ==================== Authentication Tests ==================== 407 408 #[test] 409 fn test_decrypt_wrong_key_fails() { 410 let key1 = [0x42u8; KEY_SIZE]; 411 let key2 = [0x43u8; KEY_SIZE]; 412 let nonce = generate_nonce(); 413 let plaintext = b"Secret"; 414 let aad = b""; 415 416 let ciphertext = encrypt(&key1, &nonce, plaintext, aad).unwrap(); 417 let result = decrypt(&key2, &nonce, &ciphertext, aad); 418 419 assert!(result.is_err()); 420 assert!(matches!(result.unwrap_err(), DeadDropError::Decryption(_))); 421 } 422 423 #[test] 424 fn test_decrypt_wrong_nonce_fails() { 425 let key = [0x42u8; KEY_SIZE]; 426 let nonce1 = [0x01u8; NONCE_SIZE]; 427 let nonce2 = [0x02u8; NONCE_SIZE]; 428 let plaintext = b"Secret"; 429 let aad = b""; 430 431 let ciphertext = encrypt(&key, &nonce1, plaintext, aad).unwrap(); 432 let result = decrypt(&key, &nonce2, &ciphertext, aad); 433 434 assert!(result.is_err()); 435 } 436 437 #[test] 438 fn test_decrypt_wrong_aad_fails() { 439 let key = [0x42u8; KEY_SIZE]; 440 let nonce = generate_nonce(); 441 let plaintext = b"Secret"; 442 let aad1 = b"correct-aad"; 443 let aad2 = b"wrong-aad"; 444 445 let ciphertext = encrypt(&key, &nonce, plaintext, aad1).unwrap(); 446 let result = decrypt(&key, &nonce, &ciphertext, aad2); 447 448 assert!(result.is_err()); 449 } 450 451 #[test] 452 fn test_decrypt_tampered_ciphertext_fails() { 453 let key = [0x42u8; KEY_SIZE]; 454 let nonce = generate_nonce(); 455 let plaintext = b"Secret message"; 456 let aad = b""; 457 458 let mut ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap(); 459 460 // Tamper with the ciphertext 461 if !ciphertext.is_empty() { 462 ciphertext[0] ^= 0xFF; 463 } 464 465 let result = decrypt(&key, &nonce, &ciphertext, aad); 466 assert!(result.is_err()); 467 } 468 469 #[test] 470 fn test_decrypt_tampered_tag_fails() { 471 let key = [0x42u8; KEY_SIZE]; 472 let nonce = generate_nonce(); 473 let plaintext = b"Secret message"; 474 let aad = b""; 475 476 let mut ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap(); 477 478 // Tamper with the authentication tag (last 16 bytes) 479 let len = ciphertext.len(); 480 ciphertext[len - 1] ^= 0xFF; 481 482 let result = decrypt(&key, &nonce, &ciphertext, aad); 483 assert!(result.is_err()); 484 } 485 486 #[test] 487 fn test_decrypt_truncated_ciphertext_fails() { 488 let key = [0x42u8; KEY_SIZE]; 489 let nonce = generate_nonce(); 490 let plaintext = b"Secret message"; 491 let aad = b""; 492 493 let ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap(); 494 495 // Truncate to less than tag size 496 let truncated = &ciphertext[..TAG_SIZE - 1]; 497 let result = decrypt(&key, &nonce, truncated, aad); 498 499 assert!(result.is_err()); 500 } 501 502 // ==================== Nonce Generation Tests ==================== 503 504 #[test] 505 fn test_generate_nonce_correct_size() { 506 let nonce = generate_nonce(); 507 assert_eq!(nonce.len(), NONCE_SIZE); 508 } 509 510 #[test] 511 fn test_generate_nonce_unique() { 512 let nonce1 = generate_nonce(); 513 let nonce2 = generate_nonce(); 514 515 // Random nonces should be different 516 assert_ne!(nonce1, nonce2); 517 } 518 519 #[test] 520 fn test_generate_nonce_not_zero() { 521 // Generate multiple nonces and ensure they're not all zeros 522 for _ in 0..10 { 523 let nonce = generate_nonce(); 524 // Very unlikely to be all zeros 525 assert_ne!(nonce, [0u8; NONCE_SIZE]); 526 } 527 } 528 529 // ==================== Convenience Function Tests ==================== 530 531 #[test] 532 fn test_encrypt_with_random_nonce() { 533 let key = [0x42u8; KEY_SIZE]; 534 let plaintext = b"Hello"; 535 let aad = b"test"; 536 537 let (nonce, ciphertext) = encrypt_with_random_nonce(&key, plaintext, aad).unwrap(); 538 539 // Should be able to decrypt with returned nonce 540 let decrypted = decrypt(&key, &nonce, &ciphertext, aad).unwrap(); 541 assert_eq!(plaintext.as_slice(), decrypted.as_slice()); 542 } 543 544 #[test] 545 fn test_encrypt_with_random_nonce_unique_nonces() { 546 let key = [0x42u8; KEY_SIZE]; 547 let plaintext = b"Hello"; 548 let aad = b"test"; 549 550 let (nonce1, _) = encrypt_with_random_nonce(&key, plaintext, aad).unwrap(); 551 let (nonce2, _) = encrypt_with_random_nonce(&key, plaintext, aad).unwrap(); 552 553 // Each call should generate a different nonce 554 assert_ne!(nonce1, nonce2); 555 } 556 557 // ==================== Seal/Open Tests ==================== 558 559 #[test] 560 fn test_seal_open_basic() { 561 let key = [0x42u8; KEY_SIZE]; 562 let plaintext = b"Secret message"; 563 let aad = b"metadata"; 564 565 let sealed = seal(&key, plaintext, aad).unwrap(); 566 let opened = open(&key, &sealed, aad).unwrap(); 567 568 assert_eq!(plaintext.as_slice(), opened.as_slice()); 569 } 570 571 #[test] 572 fn test_seal_length() { 573 let key = [0x42u8; KEY_SIZE]; 574 let plaintext = b"Hello, World!"; 575 let aad = b""; 576 577 let sealed = seal(&key, plaintext, aad).unwrap(); 578 579 assert_eq!(sealed.len(), sealed_len(plaintext.len())); 580 assert_eq!(sealed.len(), NONCE_SIZE + plaintext.len() + TAG_SIZE); 581 } 582 583 #[test] 584 fn test_seal_open_empty_plaintext() { 585 let key = [0x42u8; KEY_SIZE]; 586 let plaintext = b""; 587 let aad = b"some-aad"; 588 589 let sealed = seal(&key, plaintext, aad).unwrap(); 590 let opened = open(&key, &sealed, aad).unwrap(); 591 592 assert_eq!(plaintext.as_slice(), opened.as_slice()); 593 } 594 595 #[test] 596 fn test_open_wrong_key_fails() { 597 let key1 = [0x42u8; KEY_SIZE]; 598 let key2 = [0x43u8; KEY_SIZE]; 599 let plaintext = b"Secret"; 600 let aad = b""; 601 602 let sealed = seal(&key1, plaintext, aad).unwrap(); 603 let result = open(&key2, &sealed, aad); 604 605 assert!(result.is_err()); 606 } 607 608 #[test] 609 fn test_open_wrong_aad_fails() { 610 let key = [0x42u8; KEY_SIZE]; 611 let plaintext = b"Secret"; 612 let aad1 = b"correct"; 613 let aad2 = b"wrong"; 614 615 let sealed = seal(&key, plaintext, aad1).unwrap(); 616 let result = open(&key, &sealed, aad2); 617 618 assert!(result.is_err()); 619 } 620 621 #[test] 622 fn test_open_too_short_fails() { 623 let key = [0x42u8; KEY_SIZE]; 624 let short_data = [0u8; NONCE_SIZE + TAG_SIZE - 1]; // Just under minimum 625 626 let result = open(&key, &short_data, b""); 627 assert!(result.is_err()); 628 } 629 630 #[test] 631 fn test_open_tampered_nonce_fails() { 632 let key = [0x42u8; KEY_SIZE]; 633 let plaintext = b"Secret"; 634 let aad = b""; 635 636 let mut sealed = seal(&key, plaintext, aad).unwrap(); 637 638 // Tamper with the nonce (first 12 bytes) 639 sealed[0] ^= 0xFF; 640 641 let result = open(&key, &sealed, aad); 642 assert!(result.is_err()); 643 } 644 645 #[test] 646 fn test_seal_unique_each_time() { 647 let key = [0x42u8; KEY_SIZE]; 648 let plaintext = b"Same message"; 649 let aad = b""; 650 651 let sealed1 = seal(&key, plaintext, aad).unwrap(); 652 let sealed2 = seal(&key, plaintext, aad).unwrap(); 653 654 // Different random nonces should produce different sealed data 655 assert_ne!(sealed1, sealed2); 656 657 // But both should decrypt to the same plaintext 658 let opened1 = open(&key, &sealed1, aad).unwrap(); 659 let opened2 = open(&key, &sealed2, aad).unwrap(); 660 661 assert_eq!(opened1, opened2); 662 } 663 664 // ==================== seal_and_zeroize_key Tests ==================== 665 666 #[test] 667 fn test_seal_and_zeroize_key() { 668 let key = [0x42u8; KEY_SIZE]; 669 let plaintext = b"Secret"; 670 let aad = b""; 671 672 let sealed = seal_and_zeroize_key(key, plaintext, aad).unwrap(); 673 let opened = open(&[0x42u8; KEY_SIZE], &sealed, aad).unwrap(); 674 675 assert_eq!(plaintext.as_slice(), opened.as_slice()); 676 } 677 678 // ==================== Length Calculation Tests ==================== 679 680 #[test] 681 fn test_ciphertext_len_calculation() { 682 assert_eq!(ciphertext_len(0), TAG_SIZE); 683 assert_eq!(ciphertext_len(10), 10 + TAG_SIZE); 684 assert_eq!(ciphertext_len(1000), 1000 + TAG_SIZE); 685 } 686 687 #[test] 688 fn test_sealed_len_calculation() { 689 assert_eq!(sealed_len(0), NONCE_SIZE + TAG_SIZE); 690 assert_eq!(sealed_len(10), NONCE_SIZE + 10 + TAG_SIZE); 691 assert_eq!(sealed_len(1000), NONCE_SIZE + 1000 + TAG_SIZE); 692 } 693 694 // ==================== Error Case Tests ==================== 695 696 #[test] 697 fn test_encrypt_too_large_plaintext() { 698 let key = [0x42u8; KEY_SIZE]; 699 let nonce = generate_nonce(); 700 let plaintext = vec![0u8; MAX_PLAINTEXT_SIZE + 1]; 701 let aad = b""; 702 703 let result = encrypt(&key, &nonce, &plaintext, aad); 704 705 assert!(result.is_err()); 706 match result.unwrap_err() { 707 DeadDropError::MessageTooLarge { size, max } => { 708 assert_eq!(size, MAX_PLAINTEXT_SIZE + 1); 709 assert_eq!(max, MAX_PLAINTEXT_SIZE); 710 } 711 _ => panic!("Expected MessageTooLarge error"), 712 } 713 } 714 715 #[test] 716 fn test_decrypt_empty_ciphertext() { 717 let key = [0x42u8; KEY_SIZE]; 718 let nonce = generate_nonce(); 719 let ciphertext = b""; 720 let aad = b""; 721 722 let result = decrypt(&key, &nonce, ciphertext, aad); 723 assert!(result.is_err()); 724 } 725 726 // ==================== Determinism Tests ==================== 727 728 #[test] 729 fn test_encrypt_deterministic_with_same_nonce() { 730 let key = [0x42u8; KEY_SIZE]; 731 let nonce = [0x01u8; NONCE_SIZE]; 732 let plaintext = b"Hello"; 733 let aad = b"test"; 734 735 let ciphertext1 = encrypt(&key, &nonce, plaintext, aad).unwrap(); 736 let ciphertext2 = encrypt(&key, &nonce, plaintext, aad).unwrap(); 737 738 // Same key, nonce, plaintext, AAD should produce same ciphertext 739 assert_eq!(ciphertext1, ciphertext2); 740 } 741 742 // ==================== Binary Data Tests ==================== 743 744 #[test] 745 fn test_encrypt_decrypt_binary_data() { 746 let key = [0x42u8; KEY_SIZE]; 747 let nonce = generate_nonce(); 748 749 // Binary data with all byte values 750 let plaintext: Vec<u8> = (0..=255).collect(); 751 let aad = b"binary-test"; 752 753 let ciphertext = encrypt(&key, &nonce, &plaintext, aad).unwrap(); 754 let decrypted = decrypt(&key, &nonce, &ciphertext, aad).unwrap(); 755 756 assert_eq!(plaintext, decrypted); 757 } 758 759 #[test] 760 fn test_encrypt_decrypt_with_null_bytes() { 761 let key = [0x42u8; KEY_SIZE]; 762 let nonce = generate_nonce(); 763 let plaintext = b"hello\x00world\x00\x00"; 764 let aad = b"\x00\x00\x00"; 765 766 let ciphertext = encrypt(&key, &nonce, plaintext, aad).unwrap(); 767 let decrypted = decrypt(&key, &nonce, &ciphertext, aad).unwrap(); 768 769 assert_eq!(plaintext.as_slice(), decrypted.as_slice()); 770 } 771 }