From cffde8c6db7f1fcac3b8730de8e0b519e269ca24 Mon Sep 17 00:00:00 2001 From: HatemMn <19950216+HatemMn@users.noreply.github.com> Date: Wed, 24 Dec 2025 20:44:18 +0100 Subject: [PATCH 1/8] feat: add the algorithm, fix some lints --- crate/crypto/src/crypto/symmetric/mod.rs | 2 +- crate/crypto/src/crypto/symmetric/rfc3394.rs | 239 +++++++++++++++++++ crate/crypto/src/crypto/symmetric/rfc5649.rs | 35 ++- 3 files changed, 270 insertions(+), 6 deletions(-) create mode 100644 crate/crypto/src/crypto/symmetric/rfc3394.rs diff --git a/crate/crypto/src/crypto/symmetric/mod.rs b/crate/crypto/src/crypto/symmetric/mod.rs index a346b5eb71..956189ca60 100644 --- a/crate/crypto/src/crypto/symmetric/mod.rs +++ b/crate/crypto/src/crypto/symmetric/mod.rs @@ -1,6 +1,6 @@ pub mod symmetric_ciphers; -#[expect(clippy::indexing_slicing)] +pub mod rfc3394; pub mod rfc5649; #[cfg(feature = "non-fips")] diff --git a/crate/crypto/src/crypto/symmetric/rfc3394.rs b/crate/crypto/src/crypto/symmetric/rfc3394.rs new file mode 100644 index 0000000000..fd348bcd76 --- /dev/null +++ b/crate/crypto/src/crypto/symmetric/rfc3394.rs @@ -0,0 +1,239 @@ +//! AES Key Wrap (RFC 3394) without padding (KW) +//! Implements wrapping/unwrapping using raw AES-ECB via rust-openssl. +//! +//! Spec references: +//! - RFC 3394: +//! - NIST SP 800-38F: +//! +//! Notes: +//! - Input must be a multiple of 8 bytes and at least 16 bytes (n >= 2 blocks). +//! - Uses the default IV (A) = 0xA6A6A6A6A6A6A6A6. +//! - No padding is performed; for non-8-byte input lengths, use RFC 5649 (KWP). + +use openssl::symm::{Cipher, Crypter, Mode, encrypt}; +use zeroize::Zeroizing; + +use crate::error::{CryptoError, result::CryptoResult}; + +const DEFAULT_IV: u64 = 0xA6A6_A6A6_A6A6_A6A6; +const AES_WRAP_BLOCK_SIZE: usize = 0x8; // 64-bit +const AES_BLOCK_SIZE: usize = 0x10; // 128-bit + +fn select_cipher(kek: &[u8]) -> CryptoResult { + Ok(match kek.len() { + 16 => Cipher::aes_128_ecb(), + 24 => Cipher::aes_192_ecb(), + 32 => Cipher::aes_256_ecb(), + _ => { + return Err(CryptoError::InvalidSize( + "The KEK size should be 16, 24 or 32 bytes".to_owned(), + )); + } + }) +} + +/// Wrap a plaintext key using AES Key Wrap (RFC 3394). +pub fn rfc3394_wrap(plain: &[u8], kek: &[u8]) -> Result, CryptoError> { + let n_bytes = plain.len(); + + if !n_bytes.is_multiple_of(AES_WRAP_BLOCK_SIZE) || n_bytes < 2 * AES_WRAP_BLOCK_SIZE { + return Err(CryptoError::InvalidSize( + "The plaintext size should be >= 16 and a multiple of 8".to_owned(), + )); + } + + let n_blocks = n_bytes / AES_WRAP_BLOCK_SIZE; + let cipher = select_cipher(kek)?; + + // Initialize A (ICR) with default IV and load R[i] + let mut a: u64 = DEFAULT_IV; + let mut r: Vec = Vec::with_capacity(n_blocks); + for chunk in plain.chunks(AES_WRAP_BLOCK_SIZE) { + r.push(u64::from_be_bytes(chunk.try_into()?)); + } + + // 6 rounds + for j in 0..6 { + for (i, block) in r.iter_mut().enumerate() { + // B = AES(K, A | R[i]) + let plaintext_block = ((u128::from(a) << 64) | u128::from(*block)).to_be_bytes(); + let b = encrypt(cipher, kek, None, &plaintext_block)?; + + // A = MSB(64, B) ^ t, where t = (n*j)+i+1 + let t = u64::try_from((n_blocks * j) + (i + 1))?; + a = u64::from_be_bytes( + b.get(0..AES_WRAP_BLOCK_SIZE) + .ok_or_else(|| { + CryptoError::InvalidSize( + "Encryption output too short for IV extraction".to_owned(), + ) + })? + .try_into()?, + ) ^ t; + + // R[i] = LSB(64, B) + *block = u64::from_be_bytes( + b.get(AES_WRAP_BLOCK_SIZE..AES_BLOCK_SIZE) + .ok_or_else(|| { + CryptoError::InvalidSize( + "Encryption output too short for block extraction".to_owned(), + ) + })? + .try_into()?, + ); + } + } + + // Output C[0] = A, C[1..n] = R[1..n] + let mut out = Vec::with_capacity(AES_WRAP_BLOCK_SIZE * (n_blocks + 1)); + out.extend_from_slice(&a.to_be_bytes()); + for block in r { + out.extend_from_slice(&block.to_be_bytes()); + } + Ok(out) +} + +/// Unwrap a ciphertext produced by AES Key Wrap (RFC 3394). +pub fn rfc3394_unwrap(ciphertext: &[u8], kek: &[u8]) -> Result>, CryptoError> { + let n_bytes = ciphertext.len(); + + // Minimum ciphertext length: 24 bytes (16 for key + 8 for IV) and multiple of 8 + if !n_bytes.is_multiple_of(AES_WRAP_BLOCK_SIZE) || n_bytes < (AES_WRAP_BLOCK_SIZE * 3) { + return Err(CryptoError::InvalidSize( + "The ciphertext size should be >= 24 and a multiple of 8".to_owned(), + )); + } + + let n_blocks = (n_bytes / AES_WRAP_BLOCK_SIZE) - 1; // exclude C[0] + let cipher = select_cipher(kek)?; + + // Load C[0] into A, and remainder into R[i] + let mut blocks = Zeroizing::from(Vec::with_capacity(n_blocks + 1)); + for chunk in ciphertext.chunks(AES_WRAP_BLOCK_SIZE) { + blocks.push(u64::from_be_bytes(chunk.try_into()?)); + } + + // bonds are guaranteed by earlier validation, and using `get` causes ownership issues + #[expect(clippy::indexing_slicing)] + let mut a = blocks[0]; + + // Initialize AES-ECB decrypter without padding + let mut decrypt_cipher = Crypter::new(cipher, Mode::Decrypt, kek, None)?; + decrypt_cipher.pad(false); + + // 6 rounds in reverse + for j in (0..6).rev() { + #[expect(clippy::indexing_slicing)] + // bonds are garanteed by earlier validation, and using `get` causes ownership issues + for (i_rev, block) in blocks[1..].iter_mut().rev().enumerate() { + // t = (n*j) + (n - i), with i in 1..=n; here i_rev enumerates 0..n-1 from end + let t = u64::try_from((n_blocks * j) + (n_blocks - i_rev))?; + + // B = AES-1(K, (A ^ t) | R[i]) + let big_i = ((u128::from(a ^ t) << 64) | u128::from(*block)).to_be_bytes(); + let mut plaintext = Zeroizing::from(vec![0; AES_BLOCK_SIZE * 2]); + let mut dec_len = decrypt_cipher.update(&big_i, &mut plaintext)?; + dec_len += decrypt_cipher.finalize(&mut plaintext)?; + plaintext.truncate(dec_len); + + // A = MSB(64, B) + a = u64::from_be_bytes( + plaintext + .get(0..AES_WRAP_BLOCK_SIZE) + .ok_or_else(|| { + CryptoError::InvalidSize( + "Decryption output too short for IV extraction".to_owned(), + ) + })? + .try_into()?, + ); + + // R[i] = LSB(64, B) + *block = u64::from_be_bytes( + plaintext + .get(AES_WRAP_BLOCK_SIZE..AES_WRAP_BLOCK_SIZE * 2) + .ok_or_else(|| { + CryptoError::InvalidSize( + "Decryption output too short for block extraction".to_owned(), + ) + })? + .try_into()?, + ); + } + } + + // Validate A equals default IV + if a != DEFAULT_IV { + return Err(CryptoError::InvalidSize( + "The ciphertext is invalid. Unwrapped IV does not match RFC 3394".to_owned(), + )); + } + + // Collect R[1..n] + let mut unwrapped = Zeroizing::from(Vec::with_capacity(n_blocks * AES_WRAP_BLOCK_SIZE)); + for block in blocks + .get(1..) + .ok_or_else(|| CryptoError::IndexingSlicing("Block index issue".to_owned()))? + { + unwrapped.extend_from_slice(&block.to_be_bytes()); + } + + Ok(unwrapped) +} + +#[allow(clippy::unwrap_used, clippy::expect_used)] +#[cfg(test)] +mod tests { + use zeroize::Zeroizing; + + use crate::crypto::symmetric::rfc3394::{rfc3394_unwrap, rfc3394_wrap}; + + // Test vectors from RFC 3394 (AES-128 KEK, 128-bit key) + // KEK = 000102030405060708090A0B0C0D0E0F + // P = 00112233445566778899AABBCCDDEEFF + // C = 1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5 + #[test] + fn test_rfc3394_vector_aes128() { + #[cfg(not(feature = "non-fips"))] + openssl::provider::Provider::load(None, "fips").unwrap(); + + let kek = hex::decode("000102030405060708090A0B0C0D0E0F").unwrap(); + let p = hex::decode("00112233445566778899AABBCCDDEEFF").unwrap(); + let c_expected = hex::decode("1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5").unwrap(); + + let c = rfc3394_wrap(&p, &kek).unwrap(); + assert_eq!(c, c_expected); + + let p_unwrapped = rfc3394_unwrap(&c, &kek).unwrap(); + assert_eq!(p_unwrapped, Zeroizing::from(p)); + } + + // Additional sanity: error cases + #[test] + fn test_errors() { + #[cfg(not(feature = "non-fips"))] + openssl::provider::Provider::load(None, "fips").unwrap(); + + // KEK length invalid + let kek_bad = [0x00_u8; 1]; + let p16 = [0x11_u8; 16]; + rfc3394_wrap(&p16, &kek_bad).unwrap_err(); + let c24 = [0x22_u8; 24]; + rfc3394_unwrap(&c24, &kek_bad).unwrap_err(); + + // Plaintext not multiple of 8 or too small + let kek16 = [0x01_u8; 16]; + let p15 = [0x33_u8; 15]; + rfc3394_wrap(&p15, &kek16).unwrap_err(); + let p8 = [0x44_u8; 8]; + rfc3394_wrap(&p8, &kek16).unwrap_err(); + + // Ciphertext too small + let c16 = [0x55_u8; 16]; + rfc3394_unwrap(&c16, &kek16).unwrap_err(); + + // Ciphertext not multiple of 8 + let c23 = [0x66_u8; 23]; + rfc3394_unwrap(&c23, &kek16).unwrap_err(); + } +} diff --git a/crate/crypto/src/crypto/symmetric/rfc5649.rs b/crate/crypto/src/crypto/symmetric/rfc5649.rs index ab52754245..ed574a3f92 100644 --- a/crate/crypto/src/crypto/symmetric/rfc5649.rs +++ b/crate/crypto/src/crypto/symmetric/rfc5649.rs @@ -47,7 +47,11 @@ fn check_iv(iv: u64, data: &[u8]) -> CryptoResult { return Ok(false); } - Ok(data[real_data_size..].iter().all(|&x| x == 0)) + Ok(data + .get(real_data_size..) + .ok_or_else(|| CryptoError::IndexingSlicing("Block index issue".to_owned()))? + .iter() + .all(|&x| x == 0)) } /// Wrap a plain text of variable length following RFC 5649. @@ -71,6 +75,7 @@ pub fn rfc5649_wrap(plain: &[u8], kek: &[u8]) -> Result, CryptoError> { // (C[0] | C[1]) = ENC(K, A | P[1]). let iv_and_key = [ &build_iv(n)?.to_be_bytes(), + #[expect(clippy::indexing_slicing)] // complicated to do otherwise &padded_plain[0..AES_WRAP_PAD_BLOCK_SIZE], ] .concat(); @@ -88,7 +93,10 @@ pub fn rfc5649_wrap(plain: &[u8], kek: &[u8]) -> Result, CryptoError> { } }; - Ok(ciphertext[..AES_BLOCK_SIZE].to_vec()) + Ok(ciphertext + .get(..AES_BLOCK_SIZE) + .ok_or_else(|| CryptoError::IndexingSlicing("Block index issue".to_owned()))? + .to_vec()) } else { wrap_64(&padded_plain, kek, Some(build_iv(n)?)) } @@ -98,6 +106,7 @@ pub fn rfc5649_wrap(plain: &[u8], kek: &[u8]) -> Result, CryptoError> { /// /// The function name matches the one used in the RFC and has no link to the /// unwrap function in Rust. +#[expect(clippy::indexing_slicing)] // safe indexing, and the .get() syntax makes the code unreadable here pub fn rfc5649_unwrap(ciphertext: &[u8], kek: &[u8]) -> Result>, CryptoError> { let n = ciphertext.len(); @@ -196,6 +205,8 @@ fn wrap_64(plain: &[u8], kek: &[u8], iv: Option) -> Result, CryptoE }; for j in 0..6 { + #[expect(clippy::indexing_slicing)] + // safe indexing, and the .get() syntax makes the code unreadable here for (i, block) in blocks.iter_mut().enumerate().take(n) { // B = AES(K, A | R[i]) let plaintext_block = ((u128::from(icr) << 64) | u128::from(*block)).to_be_bytes(); @@ -239,6 +250,7 @@ fn unwrap_64(ciphertext: &[u8], kek: &[u8]) -> Result<(u64, Zeroizing>), } // ICR stands for Integrity Check Register initially containing the IV. + #[expect(clippy::indexing_slicing)] let mut icr = blocks[0]; // Encrypt block using AES with ECB mode i.e. raw AES as specified in @@ -257,6 +269,7 @@ fn unwrap_64(ciphertext: &[u8], kek: &[u8]) -> Result<(u64, Zeroizing>), }; decrypt_cipher.pad(false); + #[expect(clippy::indexing_slicing)] for j in (0..6).rev() { for (i, block) in blocks[1..].iter_mut().rev().enumerate().take(n) { let t = u64::try_from((n * j) + (n - i))?; @@ -271,7 +284,16 @@ fn unwrap_64(ciphertext: &[u8], kek: &[u8]) -> Result<(u64, Zeroizing>), plaintext.truncate(dec_len); // A = MSB(64, B) - icr = u64::from_be_bytes(plaintext[0..AES_WRAP_PAD_BLOCK_SIZE].try_into()?); + icr = u64::from_be_bytes( + plaintext + .get(0..AES_WRAP_PAD_BLOCK_SIZE) + .ok_or_else(|| { + CryptoError::InvalidSize( + "Decryption output too short for IV extraction".to_owned(), + ) + })? + .try_into()?, + ); // R[i] = LSB(64, B) *block = u64::from_be_bytes( @@ -281,14 +303,17 @@ fn unwrap_64(ciphertext: &[u8], kek: &[u8]) -> Result<(u64, Zeroizing>), } let mut unwrapped_key = Zeroizing::from(Vec::with_capacity((blocks.len() - 1) * 8)); - for block in &blocks[1..] { + for block in blocks + .get(1..) + .ok_or_else(|| CryptoError::IndexingSlicing("Block index issue".to_owned()))? + { unwrapped_key.extend(block.to_be_bytes()); } Ok((icr, unwrapped_key)) } -#[expect(clippy::unwrap_used, clippy::expect_used)] +#[expect(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)] #[cfg(test)] mod tests { use aes_kw::KeyInit; From adccb57b14ddab9e26f15fff3d07f971c27d0531 Mon Sep 17 00:00:00 2001 From: HatemMn <19950216+HatemMn@users.noreply.github.com> Date: Wed, 24 Dec 2025 20:44:47 +0100 Subject: [PATCH 2/8] chore: temporary disable nist key wrap --- .../src/crypto/symmetric/symmetric_ciphers.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crate/crypto/src/crypto/symmetric/symmetric_ciphers.rs b/crate/crypto/src/crypto/symmetric/symmetric_ciphers.rs index 82a7171515..0ab102c898 100644 --- a/crate/crypto/src/crypto/symmetric/symmetric_ciphers.rs +++ b/crate/crypto/src/crypto/symmetric/symmetric_ciphers.rs @@ -344,13 +344,13 @@ impl SymCipher { {key_size} bytes", ))), }, - BlockCipherMode::NISTKeyWrap => match key_size { - RFC5649_16_KEY_LENGTH => Ok(Self::Rfc5649_16), - RFC5649_32_KEY_LENGTH => Ok(Self::Rfc5649_32), - _ => crypto_bail!(CryptoError::NotSupported(format!( - "RFC5649 key must be 16 or 32 bytes long. Found {key_size} bytes", - ))), - }, + // BlockCipherMode::NISTKeyWrap => match key_size { + // RFC5649_16_KEY_LENGTH => Ok(Self::Rfc5649_16), + // RFC5649_32_KEY_LENGTH => Ok(Self::Rfc5649_32), + // _ => crypto_bail!(CryptoError::NotSupported(format!( + // "RFC5649 key must be 16 or 32 bytes long. Found {key_size} bytes", + // ))), + // }, mode => { crypto_bail!(CryptoError::NotSupported(format!( "AES is not supported with mode: {mode:?}" From db5ed79f98e1a8077d12147df5ed16c07039553f Mon Sep 17 00:00:00 2001 From: HatemMn <19950216+HatemMn@users.noreply.github.com> Date: Fri, 26 Dec 2025 19:28:27 +0100 Subject: [PATCH 3/8] feat: rfc3394 algo --- crate/crypto/src/crypto/symmetric/rfc3394.rs | 270 +++++++++---------- 1 file changed, 124 insertions(+), 146 deletions(-) diff --git a/crate/crypto/src/crypto/symmetric/rfc3394.rs b/crate/crypto/src/crypto/symmetric/rfc3394.rs index fd348bcd76..41c51f7f30 100644 --- a/crate/crypto/src/crypto/symmetric/rfc3394.rs +++ b/crate/crypto/src/crypto/symmetric/rfc3394.rs @@ -1,5 +1,5 @@ //! AES Key Wrap (RFC 3394) without padding (KW) -//! Implements wrapping/unwrapping using raw AES-ECB via rust-openssl. +//! Implements wrapping/unwrapping via rust-openssl. //! //! Spec references: //! - RFC 3394: @@ -7,23 +7,22 @@ //! //! Notes: //! - Input must be a multiple of 8 bytes and at least 16 bytes (n >= 2 blocks). -//! - Uses the default IV (A) = 0xA6A6A6A6A6A6A6A6. //! - No padding is performed; for non-8-byte input lengths, use RFC 5649 (KWP). - -use openssl::symm::{Cipher, Crypter, Mode, encrypt}; +use openssl::cipher::{Cipher, CipherRef}; +use openssl::cipher_ctx::{CipherCtx, CipherCtxFlags}; use zeroize::Zeroizing; -use crate::error::{CryptoError, result::CryptoResult}; +use crate::error::CryptoError; +use crate::error::result::CryptoResult; -const DEFAULT_IV: u64 = 0xA6A6_A6A6_A6A6_A6A6; -const AES_WRAP_BLOCK_SIZE: usize = 0x8; // 64-bit -const AES_BLOCK_SIZE: usize = 0x10; // 128-bit +const AES_WRAP_BLOCK_SIZE: usize = 8; // 64-bit +const AES_BLOCK_SIZE: usize = 16; // 128-bit -fn select_cipher(kek: &[u8]) -> CryptoResult { +fn select_cipher(kek: &[u8]) -> CryptoResult<&CipherRef> { Ok(match kek.len() { - 16 => Cipher::aes_128_ecb(), - 24 => Cipher::aes_192_ecb(), - 32 => Cipher::aes_256_ecb(), + 16 => Cipher::aes_128_wrap(), + 24 => Cipher::aes_192_wrap(), + 32 => Cipher::aes_256_wrap(), _ => { return Err(CryptoError::InvalidSize( "The KEK size should be 16, 24 or 32 bytes".to_owned(), @@ -32,180 +31,159 @@ fn select_cipher(kek: &[u8]) -> CryptoResult { }) } -/// Wrap a plaintext key using AES Key Wrap (RFC 3394). -pub fn rfc3394_wrap(plain: &[u8], kek: &[u8]) -> Result, CryptoError> { - let n_bytes = plain.len(); +pub fn rfc3394_wrap(plaintext: &[u8], kek: &[u8]) -> CryptoResult> { + let n_bytes = plaintext.len(); + // RFC 3394 requires plaintext to be at least 16 bytes and a multiple of 8 bytes if !n_bytes.is_multiple_of(AES_WRAP_BLOCK_SIZE) || n_bytes < 2 * AES_WRAP_BLOCK_SIZE { return Err(CryptoError::InvalidSize( "The plaintext size should be >= 16 and a multiple of 8".to_owned(), )); } - let n_blocks = n_bytes / AES_WRAP_BLOCK_SIZE; let cipher = select_cipher(kek)?; - // Initialize A (ICR) with default IV and load R[i] - let mut a: u64 = DEFAULT_IV; - let mut r: Vec = Vec::with_capacity(n_blocks); - for chunk in plain.chunks(AES_WRAP_BLOCK_SIZE) { - r.push(u64::from_be_bytes(chunk.try_into()?)); - } + // Initialize cipher context for encryption + let mut ctx = CipherCtx::new()?; + ctx.set_flags(CipherCtxFlags::FLAG_WRAP_ALLOW); // For some reason the code works without this, but it should should anyway + ctx.encrypt_init(Some(cipher), Some(kek), None)?; - // 6 rounds - for j in 0..6 { - for (i, block) in r.iter_mut().enumerate() { - // B = AES(K, A | R[i]) - let plaintext_block = ((u128::from(a) << 64) | u128::from(*block)).to_be_bytes(); - let b = encrypt(cipher, kek, None, &plaintext_block)?; - - // A = MSB(64, B) ^ t, where t = (n*j)+i+1 - let t = u64::try_from((n_blocks * j) + (i + 1))?; - a = u64::from_be_bytes( - b.get(0..AES_WRAP_BLOCK_SIZE) - .ok_or_else(|| { - CryptoError::InvalidSize( - "Encryption output too short for IV extraction".to_owned(), - ) - })? - .try_into()?, - ) ^ t; - - // R[i] = LSB(64, B) - *block = u64::from_be_bytes( - b.get(AES_WRAP_BLOCK_SIZE..AES_BLOCK_SIZE) - .ok_or_else(|| { - CryptoError::InvalidSize( - "Encryption output too short for block extraction".to_owned(), - ) - })? - .try_into()?, - ); - } - } + // Allocate output buffer: wrapped size is plaintext + 8 bytes (IV) + 2 extra blocks for cipher_final + // The extra blocs will not propagate to the result as its truncated to the actual size. Due to how the openssl library is programmed, + // not adding at least 1 extra bloc results in a panic. We chose to add two because that's how openssl library operates when using this cypher + let mut ciphertext = vec![0_u8; n_bytes + AES_WRAP_BLOCK_SIZE + cipher.block_size() * 2]; - // Output C[0] = A, C[1..n] = R[1..n] - let mut out = Vec::with_capacity(AES_WRAP_BLOCK_SIZE * (n_blocks + 1)); - out.extend_from_slice(&a.to_be_bytes()); - for block in r { - out.extend_from_slice(&block.to_be_bytes()); - } - Ok(out) + // Perform the key wrap operation + let mut written = ctx.cipher_update(plaintext, Some(&mut ciphertext))?; + written += ctx.cipher_final(ciphertext.get_mut(written..).ok_or_else(|| { + CryptoError::IndexingSlicing("Buffer too small for cipher_final".to_owned()) + })?)?; + + // Truncate to actual output size + ciphertext.truncate(written); + + Ok(ciphertext) } -/// Unwrap a ciphertext produced by AES Key Wrap (RFC 3394). -pub fn rfc3394_unwrap(ciphertext: &[u8], kek: &[u8]) -> Result>, CryptoError> { +pub fn rfc3394_unwrap(ciphertext: &[u8], kek: &[u8]) -> CryptoResult>> { let n_bytes = ciphertext.len(); - // Minimum ciphertext length: 24 bytes (16 for key + 8 for IV) and multiple of 8 - if !n_bytes.is_multiple_of(AES_WRAP_BLOCK_SIZE) || n_bytes < (AES_WRAP_BLOCK_SIZE * 3) { + // RFC 3394 requires ciphertext to be at least 24 bytes (16 bytes plaintext + 8 bytes IV) and a multiple of 8 + if !n_bytes.is_multiple_of(AES_WRAP_BLOCK_SIZE) || n_bytes < 3 * AES_WRAP_BLOCK_SIZE { return Err(CryptoError::InvalidSize( "The ciphertext size should be >= 24 and a multiple of 8".to_owned(), )); } - let n_blocks = (n_bytes / AES_WRAP_BLOCK_SIZE) - 1; // exclude C[0] let cipher = select_cipher(kek)?; - // Load C[0] into A, and remainder into R[i] - let mut blocks = Zeroizing::from(Vec::with_capacity(n_blocks + 1)); - for chunk in ciphertext.chunks(AES_WRAP_BLOCK_SIZE) { - blocks.push(u64::from_be_bytes(chunk.try_into()?)); - } + // Initialize cipher context for decryption + let mut ctx = CipherCtx::new()?; + ctx.set_flags(CipherCtxFlags::FLAG_WRAP_ALLOW); // For some reason the code works without this flag, but let's set it anyway + ctx.decrypt_init(Some(cipher), Some(kek), None)?; - // bonds are guaranteed by earlier validation, and using `get` causes ownership issues - #[expect(clippy::indexing_slicing)] - let mut a = blocks[0]; - - // Initialize AES-ECB decrypter without padding - let mut decrypt_cipher = Crypter::new(cipher, Mode::Decrypt, kek, None)?; - decrypt_cipher.pad(false); - - // 6 rounds in reverse - for j in (0..6).rev() { - #[expect(clippy::indexing_slicing)] - // bonds are garanteed by earlier validation, and using `get` causes ownership issues - for (i_rev, block) in blocks[1..].iter_mut().rev().enumerate() { - // t = (n*j) + (n - i), with i in 1..=n; here i_rev enumerates 0..n-1 from end - let t = u64::try_from((n_blocks * j) + (n_blocks - i_rev))?; - - // B = AES-1(K, (A ^ t) | R[i]) - let big_i = ((u128::from(a ^ t) << 64) | u128::from(*block)).to_be_bytes(); - let mut plaintext = Zeroizing::from(vec![0; AES_BLOCK_SIZE * 2]); - let mut dec_len = decrypt_cipher.update(&big_i, &mut plaintext)?; - dec_len += decrypt_cipher.finalize(&mut plaintext)?; - plaintext.truncate(dec_len); - - // A = MSB(64, B) - a = u64::from_be_bytes( - plaintext - .get(0..AES_WRAP_BLOCK_SIZE) - .ok_or_else(|| { - CryptoError::InvalidSize( - "Decryption output too short for IV extraction".to_owned(), - ) - })? - .try_into()?, - ); - - // R[i] = LSB(64, B) - *block = u64::from_be_bytes( - plaintext - .get(AES_WRAP_BLOCK_SIZE..AES_WRAP_BLOCK_SIZE * 2) - .ok_or_else(|| { - CryptoError::InvalidSize( - "Decryption output too short for block extraction".to_owned(), - ) - })? - .try_into()?, - ); - } - } + // Allocate output buffer: unwrapped size is ciphertext - 8 bytes (IV) + extra blocks for cipher_final. Same comments as above. + let mut plaintext = Zeroizing::new(vec![ + 0_u8; + n_bytes - AES_WRAP_BLOCK_SIZE + (AES_BLOCK_SIZE * 2) + ]); - // Validate A equals default IV - if a != DEFAULT_IV { - return Err(CryptoError::InvalidSize( - "The ciphertext is invalid. Unwrapped IV does not match RFC 3394".to_owned(), - )); - } + // Perform the key unwrap operation + let mut written = ctx.cipher_update(ciphertext, Some(&mut plaintext))?; + written += ctx.cipher_final(plaintext.get_mut(written..).ok_or_else(|| { + CryptoError::IndexingSlicing("Buffer too small for cipher_final".to_owned()) + })?)?; - // Collect R[1..n] - let mut unwrapped = Zeroizing::from(Vec::with_capacity(n_blocks * AES_WRAP_BLOCK_SIZE)); - for block in blocks - .get(1..) - .ok_or_else(|| CryptoError::IndexingSlicing("Block index issue".to_owned()))? - { - unwrapped.extend_from_slice(&block.to_be_bytes()); - } + // Truncate to actual output size + plaintext.truncate(written); - Ok(unwrapped) + Ok(plaintext) } #[allow(clippy::unwrap_used, clippy::expect_used)] #[cfg(test)] mod tests { + use super::*; use zeroize::Zeroizing; - use crate::crypto::symmetric::rfc3394::{rfc3394_unwrap, rfc3394_wrap}; + /// Helper to run wrap/unwrap roundtrip test + fn test_wrap_unwrap(kek_hex: &str, plaintext_hex: &str, expected_ciphertext_hex: &str) { + let kek = hex::decode(kek_hex).unwrap(); + let p = hex::decode(plaintext_hex).unwrap(); + let c_expected = hex::decode(expected_ciphertext_hex).unwrap(); + + let c = rfc3394_wrap(&p, &kek).unwrap(); + assert_eq!(c, c_expected, "Wrap output mismatch"); - // Test vectors from RFC 3394 (AES-128 KEK, 128-bit key) - // KEK = 000102030405060708090A0B0C0D0E0F - // P = 00112233445566778899AABBCCDDEEFF - // C = 1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5 + let p_unwrapped = rfc3394_unwrap(&c, &kek).unwrap(); + assert_eq!(p_unwrapped, Zeroizing::from(p), "Unwrap output mismatch"); + } + + // RFC 3394 test vectors with AES-128 KEK #[test] - fn test_rfc3394_vector_aes128() { + fn test_rfc3394_aes128_kek() { #[cfg(not(feature = "non-fips"))] openssl::provider::Provider::load(None, "fips").unwrap(); - let kek = hex::decode("000102030405060708090A0B0C0D0E0F").unwrap(); - let p = hex::decode("00112233445566778899AABBCCDDEEFF").unwrap(); - let c_expected = hex::decode("1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5").unwrap(); + // Section 4.1: 128-bit plaintext + test_wrap_unwrap( + "000102030405060708090A0B0C0D0E0F", + "00112233445566778899AABBCCDDEEFF", + "1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5", + ); + } - let c = rfc3394_wrap(&p, &kek).unwrap(); - assert_eq!(c, c_expected); + // RFC 3394 test vectors with AES-192 KEK + #[test] + fn test_rfc3394_aes192_kek() { + #[cfg(not(feature = "non-fips"))] + openssl::provider::Provider::load(None, "fips").unwrap(); - let p_unwrapped = rfc3394_unwrap(&c, &kek).unwrap(); - assert_eq!(p_unwrapped, Zeroizing::from(p)); + let kek = "000102030405060708090A0B0C0D0E0F1011121314151617"; + + // Section 4.2: 128-bit plaintext + test_wrap_unwrap( + kek, + "00112233445566778899AABBCCDDEEFF", + "96778B25AE6CA435F92B5B97C050AED2468AB8A17AD84E5D", + ); + + // Section 4.4: 192-bit plaintext + test_wrap_unwrap( + kek, + "00112233445566778899AABBCCDDEEFF0001020304050607", + "031D33264E15D33268F24EC260743EDCE1C6C7DDEE725A936BA814915C6762D2", + ); + } + + // RFC 3394 test vectors with AES-256 KEK + #[test] + fn test_rfc3394_aes256_kek() { + #[cfg(not(feature = "non-fips"))] + openssl::provider::Provider::load(None, "fips").unwrap(); + + let kek = "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"; + + // Section 4.3: 128-bit plaintext + test_wrap_unwrap( + kek, + "00112233445566778899AABBCCDDEEFF", + "64E8C3F9CE0F5BA263E9777905818A2A93C8191E7D6E8AE7", + ); + + // Section 4.5: 192-bit plaintext + test_wrap_unwrap( + kek, + "00112233445566778899AABBCCDDEEFF0001020304050607", + "A8F9BC1612C68B3FF6E6F4FBE30E71E4769C8B80A32CB8958CD5D17D6B254DA1", + ); + + // Section 4.6: 256-bit plaintext + test_wrap_unwrap( + kek, + "00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F", + "28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD21", + ); } // Additional sanity: error cases From 4463c90c8538e3191f5b4f81249b1ccebb3065ac Mon Sep 17 00:00:00 2001 From: HatemMn <19950216+HatemMn@users.noreply.github.com> Date: Fri, 26 Dec 2025 20:06:44 +0100 Subject: [PATCH 4/8] feat: update the rfc5649 --- crate/crypto/src/crypto/symmetric/rfc3394.rs | 10 +- crate/crypto/src/crypto/symmetric/rfc5649.rs | 646 +++++++++++-------- 2 files changed, 378 insertions(+), 278 deletions(-) diff --git a/crate/crypto/src/crypto/symmetric/rfc3394.rs b/crate/crypto/src/crypto/symmetric/rfc3394.rs index 41c51f7f30..83585a700c 100644 --- a/crate/crypto/src/crypto/symmetric/rfc3394.rs +++ b/crate/crypto/src/crypto/symmetric/rfc3394.rs @@ -1,5 +1,5 @@ -//! AES Key Wrap (RFC 3394) without padding (KW) -//! Implements wrapping/unwrapping via rust-openssl. +//! AES Key Wrap (RFC 3394) without padding (KW) via rust-openssl. +//! Please prefer using the RFC 5649, as it's the current standard. This implementation is only made available to comply with API that still support legacy encryption standards. //! //! Spec references: //! - RFC 3394: @@ -20,9 +20,9 @@ const AES_BLOCK_SIZE: usize = 16; // 128-bit fn select_cipher(kek: &[u8]) -> CryptoResult<&CipherRef> { Ok(match kek.len() { - 16 => Cipher::aes_128_wrap(), - 24 => Cipher::aes_192_wrap(), - 32 => Cipher::aes_256_wrap(), + 16 => Cipher::aes_128_wrap_pad(), + 24 => Cipher::aes_192_wrap_pad(), + 32 => Cipher::aes_256_wrap_pad(), _ => { return Err(CryptoError::InvalidSize( "The KEK size should be 16, 24 or 32 bytes".to_owned(), diff --git a/crate/crypto/src/crypto/symmetric/rfc5649.rs b/crate/crypto/src/crypto/symmetric/rfc5649.rs index ed574a3f92..bc3fde1963 100644 --- a/crate/crypto/src/crypto/symmetric/rfc5649.rs +++ b/crate/crypto/src/crypto/symmetric/rfc5649.rs @@ -1,318 +1,418 @@ -//! Symmetrically wrap keys using RFC 5649 available at: -//! -> +#![allow( + clippy::manual_div_ceil, + clippy::cast_possible_truncation, + clippy::as_conversions +)] +//! AES Key Wrap (RFC 5649) with padding (KWP) via rust-openssl. +//! This is the current standard for AES key wrapping according to the NIST SP 800-38F. +//! +//! Spec references: +//! - RFC 5649: +//! - NIST SP 800-38F: //! //! This RFC is an improvement of RFC 3394 and allows to wrap keys of any size. //! This is done by introducing an Integrity Check Register (ICR) of 64 bits. The //! encryption algorithm is fed blocks of 64 bits concatenated to the ICR for a -//! total of 128 bits blocks. AES in ECB mode is used since padding and integrity -//! check are done manually following the RFC. -//! -//! OpenSSL unfortunately does not provide a way to use AES-KWP directly. -//! See: -//! Google provides a patch : -//! and so does AWS: - -use openssl::symm::{Cipher, Crypter, Mode, encrypt}; +//! total of 128 bits blocks. +use openssl::cipher::{Cipher, CipherRef}; +use openssl::cipher_ctx::{CipherCtx, CipherCtxFlags}; use zeroize::Zeroizing; use crate::error::{CryptoError, result::CryptoResult}; -const DEFAULT_RFC5649_CONST: u32 = 0xA659_59A6_u32; -const DEFAULT_IV: u64 = 0xA6A6_A6A6_A6A6_A6A6; -const AES_WRAP_PAD_BLOCK_SIZE: usize = 0x8; -const AES_BLOCK_SIZE: usize = 0x10; - -/// Build the iv according to the RFC 5649. -/// -/// `wrapped_key_size` is the size of the key to wrap. -fn build_iv(wrapped_key_size: usize) -> CryptoResult { - // RFC 5649 AIV: 0xA65959A6 || MLI (MLI is 32-bit big-endian in the low 32 bits) - let mli_u32 = u32::try_from(wrapped_key_size)?; - Ok((u64::from(DEFAULT_RFC5649_CONST) << 32) | u64::from(mli_u32)) -} - -/// Check if the `iv` value obtained after decryption is appropriate according -/// to the RFC 5649. -fn check_iv(iv: u64, data: &[u8]) -> CryptoResult { - let data_size: usize = data.len(); - // High 32 bits must match the RFC 5649 constant - if u32::try_from(iv >> 32)? != DEFAULT_RFC5649_CONST { - return Ok(false); - } - - // Low 32 bits contain MLI (big-endian value placed in low 32 bits) - let low32 = u32::try_from(iv & 0xFFFF_FFFF)?; - let real_data_size = usize::try_from(low32)?; - if real_data_size > data_size || real_data_size <= (data_size - 8) { - return Ok(false); - } +const AES_BLOCK_SIZE: usize = 128; // 128-bit +const AES_WRAP_BLOCK_SIZE: usize = 8; // 64-bit - Ok(data - .get(real_data_size..) - .ok_or_else(|| CryptoError::IndexingSlicing("Block index issue".to_owned()))? - .iter() - .all(|&x| x == 0)) -} - -/// Wrap a plain text of variable length following RFC 5649. -/// -/// KEK stands for `Key-Encryption Key` -/// The function name matches the one used in the RFC and has no link to the -/// unwrap function in Rust. -pub fn rfc5649_wrap(plain: &[u8], kek: &[u8]) -> Result, CryptoError> { - let n = plain.len(); - let n_bytes_key = n % 8; - - // Pad the plaintext with null bytes. - let padded_plain = if n_bytes_key != 0 { - let missing_bytes = 8 - n_bytes_key; - [plain.to_vec(), vec![0_u8; missing_bytes]].concat() - } else { - plain.to_vec() - }; - - if n <= 8 { - // (C[0] | C[1]) = ENC(K, A | P[1]). - let iv_and_key = [ - &build_iv(n)?.to_be_bytes(), - #[expect(clippy::indexing_slicing)] // complicated to do otherwise - &padded_plain[0..AES_WRAP_PAD_BLOCK_SIZE], - ] - .concat(); - - // Encrypt block using AES with ECB mode i.e. raw AES as specified in - // RFC5649. - let ciphertext = match kek.len() { - 16 => encrypt(Cipher::aes_128_ecb(), kek, None, &iv_and_key)?, - 24 => encrypt(Cipher::aes_192_ecb(), kek, None, &iv_and_key)?, - 32 => encrypt(Cipher::aes_256_ecb(), kek, None, &iv_and_key)?, - _ => { - return Err(CryptoError::InvalidSize( - "The kek size should be 16, 24 or 32".to_owned(), - )); - } - }; - - Ok(ciphertext - .get(..AES_BLOCK_SIZE) - .ok_or_else(|| CryptoError::IndexingSlicing("Block index issue".to_owned()))? - .to_vec()) - } else { - wrap_64(&padded_plain, kek, Some(build_iv(n)?)) - } -} - -/// Unwrap to a plain text of variable length according to RFC 5649. -/// -/// The function name matches the one used in the RFC and has no link to the -/// unwrap function in Rust. -#[expect(clippy::indexing_slicing)] // safe indexing, and the .get() syntax makes the code unreadable here -pub fn rfc5649_unwrap(ciphertext: &[u8], kek: &[u8]) -> Result>, CryptoError> { - let n = ciphertext.len(); - - if !n.is_multiple_of(AES_WRAP_PAD_BLOCK_SIZE) || n < AES_BLOCK_SIZE { - return Err(CryptoError::InvalidSize( - "The ciphertext size should be >= 16 and a multiple of 16.".to_owned(), - )); - } - - if n > 16 { - let (iv, padded_plain) = unwrap_64(ciphertext, kek)?; - - // Verify integrity check register as described in RFC 5649. - if !check_iv(iv, &padded_plain)? { - return Err(CryptoError::InvalidSize( - "The ciphertext is invalid. Decrypted IV is not appropriate".to_owned(), - )); - } - - // Extract MLI (low 32 bits, big-endian numeric value) - let unpadded_size = usize::try_from(u32::try_from(iv & 0xFFFF_FFFF)?)?; - Ok(Zeroizing::from(padded_plain[0..unpadded_size].to_vec())) - } else { - // Encrypt block using AES with ECB mode i.e. raw AES as specified in - // RFC5649. - // Make use of OpenSSL Crypter interface to decrypt blocks incrementally - // without padding since RFC5649 has special padding methods. - let mut decrypt_cipher = match kek.len() { - 16 => Crypter::new(Cipher::aes_128_ecb(), Mode::Decrypt, kek, None)?, - 24 => Crypter::new(Cipher::aes_192_ecb(), Mode::Decrypt, kek, None)?, - 32 => Crypter::new(Cipher::aes_256_ecb(), Mode::Decrypt, kek, None)?, - _ => { - return Err(CryptoError::InvalidSize( - "The kek size should be 16, 24 or 32 bytes".to_owned(), - )); - } - }; - decrypt_cipher.pad(false); - - //// A | P[1] = DEC(K, C[0] | C[1]) - let mut plaintext = Zeroizing::from(vec![0; ciphertext.len() + AES_BLOCK_SIZE]); - let mut dec_len = decrypt_cipher.update(ciphertext, &mut plaintext)?; - dec_len += decrypt_cipher.finalize(&mut plaintext)?; - plaintext.truncate(dec_len); - - // Verify integrity check register as described in RFC 5649. - if !check_iv( - u64::from_be_bytes(plaintext[0..AES_WRAP_PAD_BLOCK_SIZE].try_into()?), - &plaintext[AES_WRAP_PAD_BLOCK_SIZE..16], - )? { +fn select_cipher_pad(kek: &[u8]) -> CryptoResult<&CipherRef> { + Ok(match kek.len() { + 16 => Cipher::aes_128_wrap_pad(), + 24 => Cipher::aes_192_wrap_pad(), + 32 => Cipher::aes_256_wrap_pad(), + _ => { return Err(CryptoError::InvalidSize( - "The ciphertext is invalid. Decrypted IV is not appropriate".to_owned(), + "The KEK size should be 16, 24 or 32 bytes".to_owned(), )); } + }) +} - let unpadded_size = usize::try_from(u32::from_be_bytes(plaintext[4..8].try_into()?))?; +pub fn rfc5649_wrap(plaintext: &[u8], kek: &[u8]) -> CryptoResult> { + let n_bytes = plaintext.len(); - Ok(Zeroizing::from( - plaintext[AES_WRAP_PAD_BLOCK_SIZE..(AES_WRAP_PAD_BLOCK_SIZE + unpadded_size)].to_vec(), - )) + // RFC 5649 requires plaintext to be at least 1 byte and less than 2^32 bytes + if n_bytes == 0 { + return Err(CryptoError::InvalidSize( + "The plaintext size should be at least 1 byte".to_owned(), + )); } -} -/// Wrap a plain text of a 64-bits modulo size according to RFC 3394. -/// -/// The function name matches the one used in the RFC and has no link to the -/// unwrap function in Rust. -fn wrap_64(plain: &[u8], kek: &[u8], iv: Option) -> Result, CryptoError> { - let n = plain.len(); - - if !n.is_multiple_of(AES_WRAP_PAD_BLOCK_SIZE) { + // Check maximum length (2^32 - 1 bytes as per NIST SP 800-38F Section 5.3.1) + if n_bytes >= (1_u64 << 32) as usize { return Err(CryptoError::InvalidSize( - "The plaintext size should be a multiple of 8".to_owned(), + "The plaintext size should be less than 2^32 bytes".to_owned(), )); } - // Number of 64-bit blocks (block size for RFC 5649). - let n = n / AES_WRAP_PAD_BLOCK_SIZE; + let cipher = select_cipher_pad(kek)?; - // ICR stands for Integrity Check Register initially containing the IV. - let mut icr = iv.unwrap_or(DEFAULT_IV); - let mut blocks = Vec::with_capacity(n); + // Initialize cipher context for encryption + let mut ctx = CipherCtx::new()?; + ctx.set_flags(CipherCtxFlags::FLAG_WRAP_ALLOW); // For some reason the code works without this, but it should should anyway + ctx.encrypt_init(Some(cipher), Some(kek), None)?; - for chunk in plain.chunks(AES_WRAP_PAD_BLOCK_SIZE) { - blocks.push(u64::from_be_bytes(chunk.try_into()?)); - } - let cipher = match kek.len() { - 16 => Cipher::aes_128_ecb(), - 24 => Cipher::aes_192_ecb(), - 32 => Cipher::aes_256_ecb(), - _ => { - return Err(CryptoError::InvalidSize( - "The kek size should be 16, 24 or 32".to_owned(), - )); - } + // Calculate output size: for KWP, output is always a multiple of 8 bytes + // Minimum output is 16 bytes (2 semiblocks) + // The wrapped size includes the AIV (8 bytes) plus padded plaintext + let padded_len = if n_bytes <= AES_WRAP_BLOCK_SIZE { + AES_BLOCK_SIZE // Special case: single block encryption + } else { + // Calculate padding: round up to next multiple of 8, then add 8 for AIV + let padded_plaintext_len = + ((n_bytes + AES_WRAP_BLOCK_SIZE - 1) / AES_WRAP_BLOCK_SIZE) * AES_WRAP_BLOCK_SIZE; + padded_plaintext_len + AES_WRAP_BLOCK_SIZE }; - for j in 0..6 { - #[expect(clippy::indexing_slicing)] - // safe indexing, and the .get() syntax makes the code unreadable here - for (i, block) in blocks.iter_mut().enumerate().take(n) { - // B = AES(K, A | R[i]) - let plaintext_block = ((u128::from(icr) << 64) | u128::from(*block)).to_be_bytes(); + // Allocate output buffer with extra space for cipher_final + let mut ciphertext = vec![0_u8; padded_len + (cipher.block_size() * 2)]; - // Encrypt block using AES with ECB mode i.e. raw AES as specified in - // RFC5649. - let ciphertext = encrypt(cipher, kek, None, &plaintext_block)?; - - // A = MSB(64, B) ^ t where t = (n*j)+i - let t = u64::try_from((n * j) + (i + 1))?; - - icr = u64::from_be_bytes(ciphertext[0..8].try_into()?) ^ t; - *block = u64::from_be_bytes(ciphertext[8..16].try_into()?); - } - } + // Perform the key wrap operation + let mut written = ctx.cipher_update(plaintext, Some(&mut ciphertext))?; + written += ctx.cipher_final(ciphertext.get_mut(written..).ok_or_else(|| { + CryptoError::IndexingSlicing("Buffer too small for cipher_final".to_owned()) + })?)?; - let mut wrapped_key = Vec::with_capacity(8 * (blocks.len() + 1)); - wrapped_key.extend(icr.to_be_bytes()); - for block in blocks { - wrapped_key.extend(block.to_be_bytes()); - } + // Truncate to actual output size + ciphertext.truncate(written); - Ok(wrapped_key) + Ok(ciphertext) } -fn unwrap_64(ciphertext: &[u8], kek: &[u8]) -> Result<(u64, Zeroizing>), CryptoError> { - let n = ciphertext.len(); +pub fn rfc5649_unwrap(ciphertext: &[u8], kek: &[u8]) -> CryptoResult>> { + let n_bytes = ciphertext.len(); - if !n.is_multiple_of(AES_WRAP_PAD_BLOCK_SIZE) || n < AES_BLOCK_SIZE { + // RFC 5649 requires ciphertext to be at least 16 bytes (minimum 2 semiblocks) and a multiple of 8 + if !n_bytes.is_multiple_of(AES_WRAP_BLOCK_SIZE) || n_bytes < 2 * AES_WRAP_BLOCK_SIZE { return Err(CryptoError::InvalidSize( "The ciphertext size should be >= 16 and a multiple of 8".to_owned(), )); } - // Number of 64-bit blocks minus 1 - let n = n / 8 - 1; + let cipher = select_cipher_pad(kek)?; - let mut blocks = Zeroizing::from(Vec::with_capacity(n + 1)); - for chunk in ciphertext.chunks(AES_WRAP_PAD_BLOCK_SIZE) { - blocks.push(u64::from_be_bytes(chunk.try_into()?)); - } + // Initialize cipher context for decryption + let mut ctx = CipherCtx::new()?; + ctx.decrypt_init(Some(cipher), Some(kek), None)?; - // ICR stands for Integrity Check Register initially containing the IV. - #[expect(clippy::indexing_slicing)] - let mut icr = blocks[0]; - - // Encrypt block using AES with ECB mode i.e. raw AES as specified in - // RFC5649. - // Make use of OpenSSL Crypter interface to decrypt blocks incrementally - // without padding since RFC5649 has special padding methods. - let mut decrypt_cipher = match kek.len() { - 16 => Crypter::new(Cipher::aes_128_ecb(), Mode::Decrypt, kek, None)?, - 24 => Crypter::new(Cipher::aes_192_ecb(), Mode::Decrypt, kek, None)?, - 32 => Crypter::new(Cipher::aes_256_ecb(), Mode::Decrypt, kek, None)?, - _ => { - return Err(CryptoError::InvalidSize( - "The kek size should be 16, 24 or 32".to_owned(), - )); - } - }; - decrypt_cipher.pad(false); - - #[expect(clippy::indexing_slicing)] - for j in (0..6).rev() { - for (i, block) in blocks[1..].iter_mut().rev().enumerate().take(n) { - let t = u64::try_from((n * j) + (n - i))?; - - // B = AES-1(K, (A ^ t) | R[i]) where t = n*j+i - let big_i = ((u128::from(icr ^ t) << 64) | u128::from(*block)).to_be_bytes(); - let big_b = big_i.as_slice(); - - let mut plaintext = Zeroizing::from(vec![0; big_b.len() + AES_BLOCK_SIZE]); - let mut dec_len = decrypt_cipher.update(big_b, &mut plaintext)?; - dec_len += decrypt_cipher.finalize(&mut plaintext)?; - plaintext.truncate(dec_len); - - // A = MSB(64, B) - icr = u64::from_be_bytes( - plaintext - .get(0..AES_WRAP_PAD_BLOCK_SIZE) - .ok_or_else(|| { - CryptoError::InvalidSize( - "Decryption output too short for IV extraction".to_owned(), - ) - })? - .try_into()?, - ); + // Allocate output buffer: maximum plaintext size is ciphertext - 8 bytes (AIV) + // Add extra space for cipher_final + let mut plaintext = Zeroizing::new(vec![ + 0_u8; + n_bytes - AES_WRAP_BLOCK_SIZE + (AES_BLOCK_SIZE * 2) + ]); - // R[i] = LSB(64, B) - *block = u64::from_be_bytes( - plaintext[AES_WRAP_PAD_BLOCK_SIZE..AES_WRAP_PAD_BLOCK_SIZE * 2].try_into()?, - ); - } - } + // Perform the key unwrap operation + let mut written = ctx.cipher_update(ciphertext, Some(&mut plaintext))?; + written += ctx.cipher_final(plaintext.get_mut(written..).ok_or_else(|| { + CryptoError::IndexingSlicing("Buffer too small for cipher_final".to_owned()) + })?)?; - let mut unwrapped_key = Zeroizing::from(Vec::with_capacity((blocks.len() - 1) * 8)); - for block in blocks - .get(1..) - .ok_or_else(|| CryptoError::IndexingSlicing("Block index issue".to_owned()))? - { - unwrapped_key.extend(block.to_be_bytes()); - } + // Truncate to actual output size (OpenSSL's wrap_pad removes padding automatically) + plaintext.truncate(written); - Ok((icr, unwrapped_key)) + Ok(plaintext) } +// /// Build the iv according to the RFC 5649. +// /// +// /// `wrapped_key_size` is the size of the key to wrap. +// fn build_iv(wrapped_key_size: usize) -> CryptoResult { +// // RFC 5649 AIV: 0xA65959A6 || MLI (MLI is 32-bit big-endian in the low 32 bits) +// let mli_u32 = u32::try_from(wrapped_key_size)?; +// Ok((u64::from(DEFAULT_RFC5649_CONST) << 32) | u64::from(mli_u32)) +// } + +// /// Check if the `iv` value obtained after decryption is appropriate according +// /// to the RFC 5649. +// fn check_iv(iv: u64, data: &[u8]) -> CryptoResult { +// let data_size: usize = data.len(); +// // High 32 bits must match the RFC 5649 constant +// if u32::try_from(iv >> 32)? != DEFAULT_RFC5649_CONST { +// return Ok(false); +// } + +// // Low 32 bits contain MLI (big-endian value placed in low 32 bits) +// let low32 = u32::try_from(iv & 0xFFFF_FFFF)?; +// let real_data_size = usize::try_from(low32)?; +// if real_data_size > data_size || real_data_size <= (data_size - 8) { +// return Ok(false); +// } + +// Ok(data +// .get(real_data_size..) +// .ok_or_else(|| CryptoError::IndexingSlicing("Block index issue".to_owned()))? +// .iter() +// .all(|&x| x == 0)) +// } + +// /// Wrap a plain text of variable length following RFC 5649. +// /// +// /// KEK stands for `Key-Encryption Key` +// /// The function name matches the one used in the RFC and has no link to the +// /// unwrap function in Rust. +// pub fn rfc5649_wrap(plain: &[u8], kek: &[u8]) -> Result, CryptoError> { +// let n = plain.len(); +// let n_bytes_key = n % 8; + +// // Pad the plaintext with null bytes. +// let padded_plain = if n_bytes_key != 0 { +// let missing_bytes = 8 - n_bytes_key; +// [plain.to_vec(), vec![0_u8; missing_bytes]].concat() +// } else { +// plain.to_vec() +// }; + +// if n <= 8 { +// // (C[0] | C[1]) = ENC(K, A | P[1]). +// let iv_and_key = [ +// &build_iv(n)?.to_be_bytes(), +// #[expect(clippy::indexing_slicing)] // complicated to do otherwise +// &padded_plain[0..AES_WRAP_PAD_BLOCK_SIZE], +// ] +// .concat(); + +// // Encrypt block using AES with ECB mode i.e. raw AES as specified in +// // RFC5649. +// let ciphertext = match kek.len() { +// 16 => encrypt(Cipher::aes_128_ecb(), kek, None, &iv_and_key)?, +// 24 => encrypt(Cipher::aes_192_ecb(), kek, None, &iv_and_key)?, +// 32 => encrypt(Cipher::aes_256_ecb(), kek, None, &iv_and_key)?, +// _ => { +// return Err(CryptoError::InvalidSize( +// "The kek size should be 16, 24 or 32".to_owned(), +// )); +// } +// }; + +// Ok(ciphertext +// .get(..AES_BLOCK_SIZE) +// .ok_or_else(|| CryptoError::IndexingSlicing("Block index issue".to_owned()))? +// .to_vec()) +// } else { +// wrap_64(&padded_plain, kek, Some(build_iv(n)?)) +// } +// } + +// /// Unwrap to a plain text of variable length according to RFC 5649. +// /// +// /// The function name matches the one used in the RFC and has no link to the +// /// unwrap function in Rust. +// #[expect(clippy::indexing_slicing)] // safe indexing, and the .get() syntax makes the code unreadable here +// pub fn rfc5649_unwrap(ciphertext: &[u8], kek: &[u8]) -> Result>, CryptoError> { +// let n = ciphertext.len(); + +// if !n.is_multiple_of(AES_WRAP_PAD_BLOCK_SIZE) || n < AES_BLOCK_SIZE { +// return Err(CryptoError::InvalidSize( +// "The ciphertext size should be >= 16 and a multiple of 16.".to_owned(), +// )); +// } + +// if n > 16 { +// let (iv, padded_plain) = unwrap_64(ciphertext, kek)?; + +// // Verify integrity check register as described in RFC 5649. +// if !check_iv(iv, &padded_plain)? { +// return Err(CryptoError::InvalidSize( +// "The ciphertext is invalid. Decrypted IV is not appropriate".to_owned(), +// )); +// } + +// // Extract MLI (low 32 bits, big-endian numeric value) +// let unpadded_size = usize::try_from(u32::try_from(iv & 0xFFFF_FFFF)?)?; +// Ok(Zeroizing::from(padded_plain[0..unpadded_size].to_vec())) +// } else { +// // Encrypt block using AES with ECB mode i.e. raw AES as specified in +// // RFC5649. +// // Make use of OpenSSL Crypter interface to decrypt blocks incrementally +// // without padding since RFC5649 has special padding methods. +// let mut decrypt_cipher = match kek.len() { +// 16 => Crypter::new(Cipher::aes_128_ecb(), Mode::Decrypt, kek, None)?, +// 24 => Crypter::new(Cipher::aes_192_ecb(), Mode::Decrypt, kek, None)?, +// 32 => Crypter::new(Cipher::aes_256_ecb(), Mode::Decrypt, kek, None)?, +// _ => { +// return Err(CryptoError::InvalidSize( +// "The kek size should be 16, 24 or 32 bytes".to_owned(), +// )); +// } +// }; +// decrypt_cipher.pad(false); + +// //// A | P[1] = DEC(K, C[0] | C[1]) +// let mut plaintext = Zeroizing::from(vec![0; ciphertext.len() + AES_BLOCK_SIZE]); +// let mut dec_len = decrypt_cipher.update(ciphertext, &mut plaintext)?; +// dec_len += decrypt_cipher.finalize(&mut plaintext)?; +// plaintext.truncate(dec_len); + +// // Verify integrity check register as described in RFC 5649. +// if !check_iv( +// u64::from_be_bytes(plaintext[0..AES_WRAP_PAD_BLOCK_SIZE].try_into()?), +// &plaintext[AES_WRAP_PAD_BLOCK_SIZE..16], +// )? { +// return Err(CryptoError::InvalidSize( +// "The ciphertext is invalid. Decrypted IV is not appropriate".to_owned(), +// )); +// } + +// let unpadded_size = usize::try_from(u32::from_be_bytes(plaintext[4..8].try_into()?))?; + +// Ok(Zeroizing::from( +// plaintext[AES_WRAP_PAD_BLOCK_SIZE..(AES_WRAP_PAD_BLOCK_SIZE + unpadded_size)].to_vec(), +// )) +// } +// } + +// /// Wrap a plain text of a 64-bits modulo size according to RFC 3394. +// /// +// /// The function name matches the one used in the RFC and has no link to the +// /// unwrap function in Rust. +// fn wrap_64(plain: &[u8], kek: &[u8], iv: Option) -> Result, CryptoError> { +// let n = plain.len(); + +// if !n.is_multiple_of(AES_WRAP_PAD_BLOCK_SIZE) { +// return Err(CryptoError::InvalidSize( +// "The plaintext size should be a multiple of 8".to_owned(), +// )); +// } + +// // Number of 64-bit blocks (block size for RFC 5649). +// let n = n / AES_WRAP_PAD_BLOCK_SIZE; + +// // ICR stands for Integrity Check Register initially containing the IV. +// let mut icr = iv.unwrap_or(DEFAULT_IV); +// let mut blocks = Vec::with_capacity(n); + +// for chunk in plain.chunks(AES_WRAP_PAD_BLOCK_SIZE) { +// blocks.push(u64::from_be_bytes(chunk.try_into()?)); +// } +// let cipher = match kek.len() { +// 16 => Cipher::aes_128_ecb(), +// 24 => Cipher::aes_192_ecb(), +// 32 => Cipher::aes_256_ecb(), +// _ => { +// return Err(CryptoError::InvalidSize( +// "The kek size should be 16, 24 or 32".to_owned(), +// )); +// } +// }; + +// for j in 0..6 { +// #[expect(clippy::indexing_slicing)] +// // safe indexing, and the .get() syntax makes the code unreadable here +// for (i, block) in blocks.iter_mut().enumerate().take(n) { +// // B = AES(K, A | R[i]) +// let plaintext_block = ((u128::from(icr) << 64) | u128::from(*block)).to_be_bytes(); + +// // Encrypt block using AES with ECB mode i.e. raw AES as specified in +// // RFC5649. +// let ciphertext = encrypt(cipher, kek, None, &plaintext_block)?; + +// // A = MSB(64, B) ^ t where t = (n*j)+i +// let t = u64::try_from((n * j) + (i + 1))?; + +// icr = u64::from_be_bytes(ciphertext[0..8].try_into()?) ^ t; +// *block = u64::from_be_bytes(ciphertext[8..16].try_into()?); +// } +// } + +// let mut wrapped_key = Vec::with_capacity(8 * (blocks.len() + 1)); +// wrapped_key.extend(icr.to_be_bytes()); +// for block in blocks { +// wrapped_key.extend(block.to_be_bytes()); +// } + +// Ok(wrapped_key) +// } + +// fn unwrap_64(ciphertext: &[u8], kek: &[u8]) -> Result<(u64, Zeroizing>), CryptoError> { +// let n = ciphertext.len(); + +// if !n.is_multiple_of(AES_WRAP_PAD_BLOCK_SIZE) || n < AES_BLOCK_SIZE { +// return Err(CryptoError::InvalidSize( +// "The ciphertext size should be >= 16 and a multiple of 8".to_owned(), +// )); +// } + +// // Number of 64-bit blocks minus 1 +// let n = n / 8 - 1; + +// let mut blocks = Zeroizing::from(Vec::with_capacity(n + 1)); +// for chunk in ciphertext.chunks(AES_WRAP_PAD_BLOCK_SIZE) { +// blocks.push(u64::from_be_bytes(chunk.try_into()?)); +// } + +// // ICR stands for Integrity Check Register initially containing the IV. +// #[expect(clippy::indexing_slicing)] +// let mut icr = blocks[0]; + +// // Encrypt block using AES with ECB mode i.e. raw AES as specified in +// // RFC5649. +// // Make use of OpenSSL Crypter interface to decrypt blocks incrementally +// // without padding since RFC5649 has special padding methods. +// let mut decrypt_cipher = match kek.len() { +// 16 => Crypter::new(Cipher::aes_128_ecb(), Mode::Decrypt, kek, None)?, +// 24 => Crypter::new(Cipher::aes_192_ecb(), Mode::Decrypt, kek, None)?, +// 32 => Crypter::new(Cipher::aes_256_ecb(), Mode::Decrypt, kek, None)?, +// _ => { +// return Err(CryptoError::InvalidSize( +// "The kek size should be 16, 24 or 32".to_owned(), +// )); +// } +// }; +// decrypt_cipher.pad(false); + +// #[expect(clippy::indexing_slicing)] +// for j in (0..6).rev() { +// for (i, block) in blocks[1..].iter_mut().rev().enumerate().take(n) { +// let t = u64::try_from((n * j) + (n - i))?; + +// // B = AES-1(K, (A ^ t) | R[i]) where t = n*j+i +// let big_i = ((u128::from(icr ^ t) << 64) | u128::from(*block)).to_be_bytes(); +// let big_b = big_i.as_slice(); + +// let mut plaintext = Zeroizing::from(vec![0; big_b.len() + AES_BLOCK_SIZE]); +// let mut dec_len = decrypt_cipher.update(big_b, &mut plaintext)?; +// dec_len += decrypt_cipher.finalize(&mut plaintext)?; +// plaintext.truncate(dec_len); + +// // A = MSB(64, B) +// icr = u64::from_be_bytes( +// plaintext +// .get(0..AES_WRAP_PAD_BLOCK_SIZE) +// .ok_or_else(|| { +// CryptoError::InvalidSize( +// "Decryption output too short for IV extraction".to_owned(), +// ) +// })? +// .try_into()?, +// ); + +// // R[i] = LSB(64, B) +// *block = u64::from_be_bytes( +// plaintext[AES_WRAP_PAD_BLOCK_SIZE..AES_WRAP_PAD_BLOCK_SIZE * 2].try_into()?, +// ); +// } +// } + +// let mut unwrapped_key = Zeroizing::from(Vec::with_capacity((blocks.len() - 1) * 8)); +// for block in blocks +// .get(1..) +// .ok_or_else(|| CryptoError::IndexingSlicing("Block index issue".to_owned()))? +// { +// unwrapped_key.extend(block.to_be_bytes()); +// } + +// Ok((icr, unwrapped_key)) +// } + #[expect(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)] #[cfg(test)] mod tests { From 8dc67aec456b5d06387b6bf90376c1de3fb80be8 Mon Sep 17 00:00:00 2001 From: HatemMn <19950216+HatemMn@users.noreply.github.com> Date: Fri, 26 Dec 2025 21:08:39 +0100 Subject: [PATCH 5/8] feat: DONE rfc5649 --- crate/crypto/src/crypto/symmetric/rfc3394.rs | 2 +- crate/crypto/src/crypto/symmetric/rfc5649.rs | 354 ++----------------- 2 files changed, 28 insertions(+), 328 deletions(-) diff --git a/crate/crypto/src/crypto/symmetric/rfc3394.rs b/crate/crypto/src/crypto/symmetric/rfc3394.rs index 83585a700c..47610f9fd0 100644 --- a/crate/crypto/src/crypto/symmetric/rfc3394.rs +++ b/crate/crypto/src/crypto/symmetric/rfc3394.rs @@ -45,7 +45,7 @@ pub fn rfc3394_wrap(plaintext: &[u8], kek: &[u8]) -> CryptoResult> { // Initialize cipher context for encryption let mut ctx = CipherCtx::new()?; - ctx.set_flags(CipherCtxFlags::FLAG_WRAP_ALLOW); // For some reason the code works without this, but it should should anyway + ctx.set_flags(CipherCtxFlags::FLAG_WRAP_ALLOW); // For some reason the code works without this, but it should be set anyway ctx.encrypt_init(Some(cipher), Some(kek), None)?; // Allocate output buffer: wrapped size is plaintext + 8 bytes (IV) + 2 extra blocks for cipher_final diff --git a/crate/crypto/src/crypto/symmetric/rfc5649.rs b/crate/crypto/src/crypto/symmetric/rfc5649.rs index bc3fde1963..a92a1e4430 100644 --- a/crate/crypto/src/crypto/symmetric/rfc5649.rs +++ b/crate/crypto/src/crypto/symmetric/rfc5649.rs @@ -20,9 +20,6 @@ use zeroize::Zeroizing; use crate::error::{CryptoError, result::CryptoResult}; -const AES_BLOCK_SIZE: usize = 128; // 128-bit -const AES_WRAP_BLOCK_SIZE: usize = 8; // 64-bit - fn select_cipher_pad(kek: &[u8]) -> CryptoResult<&CipherRef> { Ok(match kek.len() { 16 => Cipher::aes_128_wrap_pad(), @@ -54,22 +51,21 @@ pub fn rfc5649_wrap(plaintext: &[u8], kek: &[u8]) -> CryptoResult> { } let cipher = select_cipher_pad(kek)?; + let bloc_size = cipher.block_size(); - // Initialize cipher context for encryption let mut ctx = CipherCtx::new()?; ctx.set_flags(CipherCtxFlags::FLAG_WRAP_ALLOW); // For some reason the code works without this, but it should should anyway ctx.encrypt_init(Some(cipher), Some(kek), None)?; // Calculate output size: for KWP, output is always a multiple of 8 bytes - // Minimum output is 16 bytes (2 semiblocks) + // Minimum output is 16 bytes (2 semi-blocks) // The wrapped size includes the AIV (8 bytes) plus padded plaintext - let padded_len = if n_bytes <= AES_WRAP_BLOCK_SIZE { - AES_BLOCK_SIZE // Special case: single block encryption + let padded_len = if n_bytes <= bloc_size { + cipher.block_size() // Special case: single block encryption } else { // Calculate padding: round up to next multiple of 8, then add 8 for AIV - let padded_plaintext_len = - ((n_bytes + AES_WRAP_BLOCK_SIZE - 1) / AES_WRAP_BLOCK_SIZE) * AES_WRAP_BLOCK_SIZE; - padded_plaintext_len + AES_WRAP_BLOCK_SIZE + let padded_plaintext_len = ((n_bytes + bloc_size - 1) / bloc_size) * bloc_size; + padded_plaintext_len + bloc_size }; // Allocate output buffer with extra space for cipher_final @@ -88,27 +84,26 @@ pub fn rfc5649_wrap(plaintext: &[u8], kek: &[u8]) -> CryptoResult> { } pub fn rfc5649_unwrap(ciphertext: &[u8], kek: &[u8]) -> CryptoResult>> { + let cipher = select_cipher_pad(kek)?; + let bloc_size = cipher.block_size(); + let n_bytes = ciphertext.len(); - // RFC 5649 requires ciphertext to be at least 16 bytes (minimum 2 semiblocks) and a multiple of 8 - if !n_bytes.is_multiple_of(AES_WRAP_BLOCK_SIZE) || n_bytes < 2 * AES_WRAP_BLOCK_SIZE { + // RFC 5649 requires ciphertext to be at least 16 bytes and a multiple of 8 bytes (complete blocs) + if !n_bytes.is_multiple_of(bloc_size) || n_bytes < 2 * (bloc_size * 2) { return Err(CryptoError::InvalidSize( "The ciphertext size should be >= 16 and a multiple of 8".to_owned(), )); } - let cipher = select_cipher_pad(kek)?; - // Initialize cipher context for decryption let mut ctx = CipherCtx::new()?; + ctx.set_flags(CipherCtxFlags::FLAG_WRAP_ALLOW); // For some reason the code works without this, but it should should anyway ctx.decrypt_init(Some(cipher), Some(kek), None)?; // Allocate output buffer: maximum plaintext size is ciphertext - 8 bytes (AIV) // Add extra space for cipher_final - let mut plaintext = Zeroizing::new(vec![ - 0_u8; - n_bytes - AES_WRAP_BLOCK_SIZE + (AES_BLOCK_SIZE * 2) - ]); + let mut plaintext = Zeroizing::new(vec![0_u8; n_bytes - bloc_size + (bloc_size * 2)]); // Perform the key unwrap operation let mut written = ctx.cipher_update(ciphertext, Some(&mut plaintext))?; @@ -122,297 +117,6 @@ pub fn rfc5649_unwrap(ciphertext: &[u8], kek: &[u8]) -> CryptoResult CryptoResult { -// // RFC 5649 AIV: 0xA65959A6 || MLI (MLI is 32-bit big-endian in the low 32 bits) -// let mli_u32 = u32::try_from(wrapped_key_size)?; -// Ok((u64::from(DEFAULT_RFC5649_CONST) << 32) | u64::from(mli_u32)) -// } - -// /// Check if the `iv` value obtained after decryption is appropriate according -// /// to the RFC 5649. -// fn check_iv(iv: u64, data: &[u8]) -> CryptoResult { -// let data_size: usize = data.len(); -// // High 32 bits must match the RFC 5649 constant -// if u32::try_from(iv >> 32)? != DEFAULT_RFC5649_CONST { -// return Ok(false); -// } - -// // Low 32 bits contain MLI (big-endian value placed in low 32 bits) -// let low32 = u32::try_from(iv & 0xFFFF_FFFF)?; -// let real_data_size = usize::try_from(low32)?; -// if real_data_size > data_size || real_data_size <= (data_size - 8) { -// return Ok(false); -// } - -// Ok(data -// .get(real_data_size..) -// .ok_or_else(|| CryptoError::IndexingSlicing("Block index issue".to_owned()))? -// .iter() -// .all(|&x| x == 0)) -// } - -// /// Wrap a plain text of variable length following RFC 5649. -// /// -// /// KEK stands for `Key-Encryption Key` -// /// The function name matches the one used in the RFC and has no link to the -// /// unwrap function in Rust. -// pub fn rfc5649_wrap(plain: &[u8], kek: &[u8]) -> Result, CryptoError> { -// let n = plain.len(); -// let n_bytes_key = n % 8; - -// // Pad the plaintext with null bytes. -// let padded_plain = if n_bytes_key != 0 { -// let missing_bytes = 8 - n_bytes_key; -// [plain.to_vec(), vec![0_u8; missing_bytes]].concat() -// } else { -// plain.to_vec() -// }; - -// if n <= 8 { -// // (C[0] | C[1]) = ENC(K, A | P[1]). -// let iv_and_key = [ -// &build_iv(n)?.to_be_bytes(), -// #[expect(clippy::indexing_slicing)] // complicated to do otherwise -// &padded_plain[0..AES_WRAP_PAD_BLOCK_SIZE], -// ] -// .concat(); - -// // Encrypt block using AES with ECB mode i.e. raw AES as specified in -// // RFC5649. -// let ciphertext = match kek.len() { -// 16 => encrypt(Cipher::aes_128_ecb(), kek, None, &iv_and_key)?, -// 24 => encrypt(Cipher::aes_192_ecb(), kek, None, &iv_and_key)?, -// 32 => encrypt(Cipher::aes_256_ecb(), kek, None, &iv_and_key)?, -// _ => { -// return Err(CryptoError::InvalidSize( -// "The kek size should be 16, 24 or 32".to_owned(), -// )); -// } -// }; - -// Ok(ciphertext -// .get(..AES_BLOCK_SIZE) -// .ok_or_else(|| CryptoError::IndexingSlicing("Block index issue".to_owned()))? -// .to_vec()) -// } else { -// wrap_64(&padded_plain, kek, Some(build_iv(n)?)) -// } -// } - -// /// Unwrap to a plain text of variable length according to RFC 5649. -// /// -// /// The function name matches the one used in the RFC and has no link to the -// /// unwrap function in Rust. -// #[expect(clippy::indexing_slicing)] // safe indexing, and the .get() syntax makes the code unreadable here -// pub fn rfc5649_unwrap(ciphertext: &[u8], kek: &[u8]) -> Result>, CryptoError> { -// let n = ciphertext.len(); - -// if !n.is_multiple_of(AES_WRAP_PAD_BLOCK_SIZE) || n < AES_BLOCK_SIZE { -// return Err(CryptoError::InvalidSize( -// "The ciphertext size should be >= 16 and a multiple of 16.".to_owned(), -// )); -// } - -// if n > 16 { -// let (iv, padded_plain) = unwrap_64(ciphertext, kek)?; - -// // Verify integrity check register as described in RFC 5649. -// if !check_iv(iv, &padded_plain)? { -// return Err(CryptoError::InvalidSize( -// "The ciphertext is invalid. Decrypted IV is not appropriate".to_owned(), -// )); -// } - -// // Extract MLI (low 32 bits, big-endian numeric value) -// let unpadded_size = usize::try_from(u32::try_from(iv & 0xFFFF_FFFF)?)?; -// Ok(Zeroizing::from(padded_plain[0..unpadded_size].to_vec())) -// } else { -// // Encrypt block using AES with ECB mode i.e. raw AES as specified in -// // RFC5649. -// // Make use of OpenSSL Crypter interface to decrypt blocks incrementally -// // without padding since RFC5649 has special padding methods. -// let mut decrypt_cipher = match kek.len() { -// 16 => Crypter::new(Cipher::aes_128_ecb(), Mode::Decrypt, kek, None)?, -// 24 => Crypter::new(Cipher::aes_192_ecb(), Mode::Decrypt, kek, None)?, -// 32 => Crypter::new(Cipher::aes_256_ecb(), Mode::Decrypt, kek, None)?, -// _ => { -// return Err(CryptoError::InvalidSize( -// "The kek size should be 16, 24 or 32 bytes".to_owned(), -// )); -// } -// }; -// decrypt_cipher.pad(false); - -// //// A | P[1] = DEC(K, C[0] | C[1]) -// let mut plaintext = Zeroizing::from(vec![0; ciphertext.len() + AES_BLOCK_SIZE]); -// let mut dec_len = decrypt_cipher.update(ciphertext, &mut plaintext)?; -// dec_len += decrypt_cipher.finalize(&mut plaintext)?; -// plaintext.truncate(dec_len); - -// // Verify integrity check register as described in RFC 5649. -// if !check_iv( -// u64::from_be_bytes(plaintext[0..AES_WRAP_PAD_BLOCK_SIZE].try_into()?), -// &plaintext[AES_WRAP_PAD_BLOCK_SIZE..16], -// )? { -// return Err(CryptoError::InvalidSize( -// "The ciphertext is invalid. Decrypted IV is not appropriate".to_owned(), -// )); -// } - -// let unpadded_size = usize::try_from(u32::from_be_bytes(plaintext[4..8].try_into()?))?; - -// Ok(Zeroizing::from( -// plaintext[AES_WRAP_PAD_BLOCK_SIZE..(AES_WRAP_PAD_BLOCK_SIZE + unpadded_size)].to_vec(), -// )) -// } -// } - -// /// Wrap a plain text of a 64-bits modulo size according to RFC 3394. -// /// -// /// The function name matches the one used in the RFC and has no link to the -// /// unwrap function in Rust. -// fn wrap_64(plain: &[u8], kek: &[u8], iv: Option) -> Result, CryptoError> { -// let n = plain.len(); - -// if !n.is_multiple_of(AES_WRAP_PAD_BLOCK_SIZE) { -// return Err(CryptoError::InvalidSize( -// "The plaintext size should be a multiple of 8".to_owned(), -// )); -// } - -// // Number of 64-bit blocks (block size for RFC 5649). -// let n = n / AES_WRAP_PAD_BLOCK_SIZE; - -// // ICR stands for Integrity Check Register initially containing the IV. -// let mut icr = iv.unwrap_or(DEFAULT_IV); -// let mut blocks = Vec::with_capacity(n); - -// for chunk in plain.chunks(AES_WRAP_PAD_BLOCK_SIZE) { -// blocks.push(u64::from_be_bytes(chunk.try_into()?)); -// } -// let cipher = match kek.len() { -// 16 => Cipher::aes_128_ecb(), -// 24 => Cipher::aes_192_ecb(), -// 32 => Cipher::aes_256_ecb(), -// _ => { -// return Err(CryptoError::InvalidSize( -// "The kek size should be 16, 24 or 32".to_owned(), -// )); -// } -// }; - -// for j in 0..6 { -// #[expect(clippy::indexing_slicing)] -// // safe indexing, and the .get() syntax makes the code unreadable here -// for (i, block) in blocks.iter_mut().enumerate().take(n) { -// // B = AES(K, A | R[i]) -// let plaintext_block = ((u128::from(icr) << 64) | u128::from(*block)).to_be_bytes(); - -// // Encrypt block using AES with ECB mode i.e. raw AES as specified in -// // RFC5649. -// let ciphertext = encrypt(cipher, kek, None, &plaintext_block)?; - -// // A = MSB(64, B) ^ t where t = (n*j)+i -// let t = u64::try_from((n * j) + (i + 1))?; - -// icr = u64::from_be_bytes(ciphertext[0..8].try_into()?) ^ t; -// *block = u64::from_be_bytes(ciphertext[8..16].try_into()?); -// } -// } - -// let mut wrapped_key = Vec::with_capacity(8 * (blocks.len() + 1)); -// wrapped_key.extend(icr.to_be_bytes()); -// for block in blocks { -// wrapped_key.extend(block.to_be_bytes()); -// } - -// Ok(wrapped_key) -// } - -// fn unwrap_64(ciphertext: &[u8], kek: &[u8]) -> Result<(u64, Zeroizing>), CryptoError> { -// let n = ciphertext.len(); - -// if !n.is_multiple_of(AES_WRAP_PAD_BLOCK_SIZE) || n < AES_BLOCK_SIZE { -// return Err(CryptoError::InvalidSize( -// "The ciphertext size should be >= 16 and a multiple of 8".to_owned(), -// )); -// } - -// // Number of 64-bit blocks minus 1 -// let n = n / 8 - 1; - -// let mut blocks = Zeroizing::from(Vec::with_capacity(n + 1)); -// for chunk in ciphertext.chunks(AES_WRAP_PAD_BLOCK_SIZE) { -// blocks.push(u64::from_be_bytes(chunk.try_into()?)); -// } - -// // ICR stands for Integrity Check Register initially containing the IV. -// #[expect(clippy::indexing_slicing)] -// let mut icr = blocks[0]; - -// // Encrypt block using AES with ECB mode i.e. raw AES as specified in -// // RFC5649. -// // Make use of OpenSSL Crypter interface to decrypt blocks incrementally -// // without padding since RFC5649 has special padding methods. -// let mut decrypt_cipher = match kek.len() { -// 16 => Crypter::new(Cipher::aes_128_ecb(), Mode::Decrypt, kek, None)?, -// 24 => Crypter::new(Cipher::aes_192_ecb(), Mode::Decrypt, kek, None)?, -// 32 => Crypter::new(Cipher::aes_256_ecb(), Mode::Decrypt, kek, None)?, -// _ => { -// return Err(CryptoError::InvalidSize( -// "The kek size should be 16, 24 or 32".to_owned(), -// )); -// } -// }; -// decrypt_cipher.pad(false); - -// #[expect(clippy::indexing_slicing)] -// for j in (0..6).rev() { -// for (i, block) in blocks[1..].iter_mut().rev().enumerate().take(n) { -// let t = u64::try_from((n * j) + (n - i))?; - -// // B = AES-1(K, (A ^ t) | R[i]) where t = n*j+i -// let big_i = ((u128::from(icr ^ t) << 64) | u128::from(*block)).to_be_bytes(); -// let big_b = big_i.as_slice(); - -// let mut plaintext = Zeroizing::from(vec![0; big_b.len() + AES_BLOCK_SIZE]); -// let mut dec_len = decrypt_cipher.update(big_b, &mut plaintext)?; -// dec_len += decrypt_cipher.finalize(&mut plaintext)?; -// plaintext.truncate(dec_len); - -// // A = MSB(64, B) -// icr = u64::from_be_bytes( -// plaintext -// .get(0..AES_WRAP_PAD_BLOCK_SIZE) -// .ok_or_else(|| { -// CryptoError::InvalidSize( -// "Decryption output too short for IV extraction".to_owned(), -// ) -// })? -// .try_into()?, -// ); - -// // R[i] = LSB(64, B) -// *block = u64::from_be_bytes( -// plaintext[AES_WRAP_PAD_BLOCK_SIZE..AES_WRAP_PAD_BLOCK_SIZE * 2].try_into()?, -// ); -// } -// } - -// let mut unwrapped_key = Zeroizing::from(Vec::with_capacity((blocks.len() - 1) * 8)); -// for block in blocks -// .get(1..) -// .ok_or_else(|| CryptoError::IndexingSlicing("Block index issue".to_owned()))? -// { -// unwrapped_key.extend(block.to_be_bytes()); -// } - -// Ok((icr, unwrapped_key)) -// } - #[expect(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)] #[cfg(test)] mod tests { @@ -422,7 +126,7 @@ mod tests { use crate::crypto::symmetric::rfc5649::{rfc5649_unwrap, rfc5649_wrap}; #[test] - pub(super) fn test_wrap1() { + pub(super) fn test_wrap_unwrap() { const TEST_SIZE_LIMIT: usize = 100; #[cfg(not(feature = "non-fips"))] // Load FIPS provider module from OpenSSL. @@ -456,19 +160,20 @@ mod tests { } } + // This test uses the vectors provided by the official RFC paper #[test] - pub(super) fn test_wrap_large_length() { + pub(super) fn test_rfc_test_vectors() { #[cfg(not(feature = "non-fips"))] // Load FIPS provider module from OpenSSL. openssl::provider::Provider::load(None, "fips").unwrap(); let kek = b"\x58\x40\xdf\x6e\x29\xb0\x2a\xf1\xab\x49\x3b\x70\x5b\xf1\x6e\xa1\xae\x83\x38\xf4\xdc\xc1\x76\xa8"; - let key_to_wrap = - b"\xc3\x7b\x7e\x64\x92\x58\x43\x40\xbe\xd1\x22\x07\x80\x89\x41\x15\x50\x68\xf7\x38"; + + // 7 bytes of data + let key_to_wrap = b"\x46\x6f\x72\x50\x61\x73\x69"; let wrapped_key = [ - 0x13, 0x8b, 0xde, 0xaa, 0x9b, 0x8f, 0xa7, 0xfc, 0x61, 0xf9, 0x77, 0x42, 0xe7, 0x22, - 0x48, 0xee, 0x5a, 0xe6, 0xae, 0x53, 0x60, 0xd1, 0xae, 0x6a, 0x5f, 0x54, 0xf3, 0x73, - 0xfa, 0x54, 0x3b, 0x6a, + 0xaf, 0xbe, 0xb0, 0xf0, 0x7d, 0xfb, 0xf5, 0x41, 0x92, 0x0, 0xf2, 0xcc, 0xb5, 0xb, 0xb2, + 0x4f, ]; assert_eq!( @@ -479,19 +184,14 @@ mod tests { rfc5649_unwrap(&wrapped_key, kek).expect("Fail to unwrap"), Zeroizing::from(key_to_wrap.to_vec()) ); - } - - #[test] - pub(super) fn test_wrap_small_length() { - #[cfg(not(feature = "non-fips"))] - // Load FIPS provider module from OpenSSL. - openssl::provider::Provider::load(None, "fips").unwrap(); - let kek = b"\x58\x40\xdf\x6e\x29\xb0\x2a\xf1\xab\x49\x3b\x70\x5b\xf1\x6e\xa1\xae\x83\x38\xf4\xdc\xc1\x76\xa8"; - let key_to_wrap = b"\x46\x6f\x72\x50\x61\x73\x69"; + // 20 bytes of data + let key_to_wrap = + b"\xc3\x7b\x7e\x64\x92\x58\x43\x40\xbe\xd1\x22\x07\x80\x89\x41\x15\x50\x68\xf7\x38"; let wrapped_key = [ - 0xaf, 0xbe, 0xb0, 0xf0, 0x7d, 0xfb, 0xf5, 0x41, 0x92, 0x0, 0xf2, 0xcc, 0xb5, 0xb, 0xb2, - 0x4f, + 0x13, 0x8b, 0xde, 0xaa, 0x9b, 0x8f, 0xa7, 0xfc, 0x61, 0xf9, 0x77, 0x42, 0xe7, 0x22, + 0x48, 0xee, 0x5a, 0xe6, 0xae, 0x53, 0x60, 0xd1, 0xae, 0x6a, 0x5f, 0x54, 0xf3, 0x73, + 0xfa, 0x54, 0x3b, 0x6a, ]; assert_eq!( From 1ea862631c175a801747b8c032da1939fee7fdbb Mon Sep 17 00:00:00 2001 From: HatemMn <19950216+HatemMn@users.noreply.github.com> Date: Mon, 29 Dec 2025 19:32:38 +0100 Subject: [PATCH 6/8] fix: fix problematic test --- crate/crypto/src/crypto/symmetric/rfc3394.rs | 10 ++- crate/crypto/src/crypto/symmetric/rfc5649.rs | 76 ++++++-------------- 2 files changed, 29 insertions(+), 57 deletions(-) diff --git a/crate/crypto/src/crypto/symmetric/rfc3394.rs b/crate/crypto/src/crypto/symmetric/rfc3394.rs index 47610f9fd0..ac75d5ab60 100644 --- a/crate/crypto/src/crypto/symmetric/rfc3394.rs +++ b/crate/crypto/src/crypto/symmetric/rfc3394.rs @@ -15,8 +15,8 @@ use zeroize::Zeroizing; use crate::error::CryptoError; use crate::error::result::CryptoResult; -const AES_WRAP_BLOCK_SIZE: usize = 8; // 64-bit const AES_BLOCK_SIZE: usize = 16; // 128-bit +const AES_WRAP_BLOCK_SIZE: usize = 8; // 64-bit fn select_cipher(kek: &[u8]) -> CryptoResult<&CipherRef> { Ok(match kek.len() { @@ -31,6 +31,9 @@ fn select_cipher(kek: &[u8]) -> CryptoResult<&CipherRef> { }) } +#[deprecated( + note = "Use `rfc5649::rfc5649_wrap` instead. RFC 3394 is provided only for legacy compatibility." +)] pub fn rfc3394_wrap(plaintext: &[u8], kek: &[u8]) -> CryptoResult> { let n_bytes = plaintext.len(); @@ -51,7 +54,7 @@ pub fn rfc3394_wrap(plaintext: &[u8], kek: &[u8]) -> CryptoResult> { // Allocate output buffer: wrapped size is plaintext + 8 bytes (IV) + 2 extra blocks for cipher_final // The extra blocs will not propagate to the result as its truncated to the actual size. Due to how the openssl library is programmed, // not adding at least 1 extra bloc results in a panic. We chose to add two because that's how openssl library operates when using this cypher - let mut ciphertext = vec![0_u8; n_bytes + AES_WRAP_BLOCK_SIZE + cipher.block_size() * 2]; + let mut ciphertext = vec![0_u8; n_bytes + AES_WRAP_BLOCK_SIZE + (AES_BLOCK_SIZE * 2)]; // Perform the key wrap operation let mut written = ctx.cipher_update(plaintext, Some(&mut ciphertext))?; @@ -65,6 +68,9 @@ pub fn rfc3394_wrap(plaintext: &[u8], kek: &[u8]) -> CryptoResult> { Ok(ciphertext) } +#[deprecated( + note = "Use `rfc5649::rfc5649_wrap` instead. RFC 3394 is provided only for legacy compatibility." +)] pub fn rfc3394_unwrap(ciphertext: &[u8], kek: &[u8]) -> CryptoResult>> { let n_bytes = ciphertext.len(); diff --git a/crate/crypto/src/crypto/symmetric/rfc5649.rs b/crate/crypto/src/crypto/symmetric/rfc5649.rs index a92a1e4430..4db1df377d 100644 --- a/crate/crypto/src/crypto/symmetric/rfc5649.rs +++ b/crate/crypto/src/crypto/symmetric/rfc5649.rs @@ -1,8 +1,3 @@ -#![allow( - clippy::manual_div_ceil, - clippy::cast_possible_truncation, - clippy::as_conversions -)] //! AES Key Wrap (RFC 5649) with padding (KWP) via rust-openssl. //! This is the current standard for AES key wrapping according to the NIST SP 800-38F. //! @@ -20,6 +15,9 @@ use zeroize::Zeroizing; use crate::error::{CryptoError, result::CryptoResult}; +const AES_BLOCK_SIZE: usize = 16; // 128-bit +const AES_WRAP_BLOCK_SIZE: usize = 8; // 64-bit + fn select_cipher_pad(kek: &[u8]) -> CryptoResult<&CipherRef> { Ok(match kek.len() { 16 => Cipher::aes_128_wrap_pad(), @@ -44,14 +42,13 @@ pub fn rfc5649_wrap(plaintext: &[u8], kek: &[u8]) -> CryptoResult> { } // Check maximum length (2^32 - 1 bytes as per NIST SP 800-38F Section 5.3.1) - if n_bytes >= (1_u64 << 32) as usize { + if n_bytes >= (1_usize << 32) { return Err(CryptoError::InvalidSize( "The plaintext size should be less than 2^32 bytes".to_owned(), )); } let cipher = select_cipher_pad(kek)?; - let bloc_size = cipher.block_size(); let mut ctx = CipherCtx::new()?; ctx.set_flags(CipherCtxFlags::FLAG_WRAP_ALLOW); // For some reason the code works without this, but it should should anyway @@ -60,16 +57,16 @@ pub fn rfc5649_wrap(plaintext: &[u8], kek: &[u8]) -> CryptoResult> { // Calculate output size: for KWP, output is always a multiple of 8 bytes // Minimum output is 16 bytes (2 semi-blocks) // The wrapped size includes the AIV (8 bytes) plus padded plaintext - let padded_len = if n_bytes <= bloc_size { - cipher.block_size() // Special case: single block encryption + let padded_len = if n_bytes <= AES_WRAP_BLOCK_SIZE { + AES_WRAP_BLOCK_SIZE // Special case: single block encryption } else { // Calculate padding: round up to next multiple of 8, then add 8 for AIV - let padded_plaintext_len = ((n_bytes + bloc_size - 1) / bloc_size) * bloc_size; - padded_plaintext_len + bloc_size + let padded_plaintext_len = ((n_bytes + 8 - 1) / 8) * 8; + padded_plaintext_len + 8 }; // Allocate output buffer with extra space for cipher_final - let mut ciphertext = vec![0_u8; padded_len + (cipher.block_size() * 2)]; + let mut ciphertext = vec![0_u8; padded_len + (AES_BLOCK_SIZE * 2)]; // Perform the key wrap operation let mut written = ctx.cipher_update(plaintext, Some(&mut ciphertext))?; @@ -85,12 +82,10 @@ pub fn rfc5649_wrap(plaintext: &[u8], kek: &[u8]) -> CryptoResult> { pub fn rfc5649_unwrap(ciphertext: &[u8], kek: &[u8]) -> CryptoResult>> { let cipher = select_cipher_pad(kek)?; - let bloc_size = cipher.block_size(); - let n_bytes = ciphertext.len(); - // RFC 5649 requires ciphertext to be at least 16 bytes and a multiple of 8 bytes (complete blocs) - if !n_bytes.is_multiple_of(bloc_size) || n_bytes < 2 * (bloc_size * 2) { + // RFC 5649 requires ciphertext to be at least 2 semi-blocks (so 1 bloc) and a multiple of 8 bytes (complete blocs) + if !n_bytes.is_multiple_of(AES_WRAP_BLOCK_SIZE) || n_bytes < 2 * AES_WRAP_BLOCK_SIZE { return Err(CryptoError::InvalidSize( "The ciphertext size should be >= 16 and a multiple of 8".to_owned(), )); @@ -103,7 +98,10 @@ pub fn rfc5649_unwrap(ciphertext: &[u8], kek: &[u8]) -> CryptoResult Date: Mon, 29 Dec 2025 19:39:26 +0100 Subject: [PATCH 7/8] feat: finish up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: major bugé feat: add a lot of things fis: add back provider for ci tests fix:clip fix: a lot more stuff fix: a lot more stuff2 fix: some fixes fix: finish up --- .vscode/settings.json | 2 +- .../cli/src/actions/kms/symmetric/decrypt.rs | 2 - crate/cli/src/actions/kms/symmetric/mod.rs | 6 ++ .../tests/kms/secret_data/create_secret.rs | 6 +- crate/cli/src/tests/kms/shared/export.rs | 1 - .../cli/src/tests/kms/shared/export_import.rs | 6 +- crate/client_utils/src/export_utils.rs | 6 ++ crate/client_utils/src/symmetric_utils.rs | 20 ++-- .../src/crypto/rsa/ckm_rsa_aes_key_wrap.rs | 2 +- crate/crypto/src/crypto/symmetric/rfc3394.rs | 34 ++----- crate/crypto/src/crypto/symmetric/rfc5649.rs | 36 ++++--- .../src/crypto/symmetric/symmetric_ciphers.rs | 93 ++++++++++++++----- crate/crypto/src/crypto/wrap/wrap_key.rs | 2 +- crate/kmip/src/kmip_0/kmip_types.rs | 2 +- crate/kmip/src/ttlv/xml/deserializer.rs | 2 +- crate/server_database/README.md | 2 +- .../src/stores/redis/objects_db.rs | 2 +- 17 files changed, 140 insertions(+), 84 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5bd0649e26..0c4f878ad5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -108,5 +108,5 @@ "sqltools.useNodeRuntime": true, "rust-analyzer.cargo.features": [ "non-fips" - ] + ], } diff --git a/crate/cli/src/actions/kms/symmetric/decrypt.rs b/crate/cli/src/actions/kms/symmetric/decrypt.rs index 10392330ea..4075a2d7db 100644 --- a/crate/cli/src/actions/kms/symmetric/decrypt.rs +++ b/crate/cli/src/actions/kms/symmetric/decrypt.rs @@ -386,11 +386,9 @@ impl DecryptAction { // recover the DEK unwrap_key_block(dek_object.key_block_mut()?, &unwrapping_key)?; let dek = dek_object.key_block()?.key_bytes()?; - // determine the DEM parameters let dem_cryptographic_parameters: CryptographicParameters = data_encryption_algorithm.into(); - trace!("dek length {}", dek.len()); let sym_cipher = SymCipher::from_algorithm_and_key_size( dem_cryptographic_parameters diff --git a/crate/cli/src/actions/kms/symmetric/mod.rs b/crate/cli/src/actions/kms/symmetric/mod.rs index 00b6b4079e..fdb2a68bd4 100644 --- a/crate/cli/src/actions/kms/symmetric/mod.rs +++ b/crate/cli/src/actions/kms/symmetric/mod.rs @@ -51,6 +51,7 @@ pub enum KeyEncryptionAlgorithm { AesXts, #[cfg(feature = "non-fips")] AesGcmSiv, + RFC3394, RFC5649, } @@ -79,6 +80,11 @@ impl From for CryptographicParameters { ..Self::default() }, KeyEncryptionAlgorithm::RFC5649 => Self { + cryptographic_algorithm: Some(CryptographicAlgorithm::AES), + block_cipher_mode: Some(BlockCipherMode::AESKeyWrapPadding), + ..Self::default() + }, + KeyEncryptionAlgorithm::RFC3394 => Self { cryptographic_algorithm: Some(CryptographicAlgorithm::AES), block_cipher_mode: Some(BlockCipherMode::NISTKeyWrap), ..Self::default() diff --git a/crate/cli/src/tests/kms/secret_data/create_secret.rs b/crate/cli/src/tests/kms/secret_data/create_secret.rs index dcf24a6203..e43919eada 100644 --- a/crate/cli/src/tests/kms/secret_data/create_secret.rs +++ b/crate/cli/src/tests/kms/secret_data/create_secret.rs @@ -145,7 +145,11 @@ pub(crate) async fn test_secret_data_export_with_different_wrapping_algorithms() .await?; // Test different wrapping algorithms - let wrapping_algorithms = [WrappingAlgorithm::NistKeyWrap, WrappingAlgorithm::AesGCM]; + let wrapping_algorithms = [ + WrappingAlgorithm::AESKeyWrapPadding, // RFC 5649 + WrappingAlgorithm::NistKeyWrap, // RFC 3394 + WrappingAlgorithm::AesGCM, + ]; for (i, algorithm) in wrapping_algorithms.iter().enumerate() { let wrapped_file = tmp_path.join(format!("wrapped_secret_{i}.json")); diff --git a/crate/cli/src/tests/kms/shared/export.rs b/crate/cli/src/tests/kms/shared/export.rs index 113e8547ea..a9183a2fd8 100644 --- a/crate/cli/src/tests/kms/shared/export.rs +++ b/crate/cli/src/tests/kms/shared/export.rs @@ -193,7 +193,6 @@ pub(crate) async fn test_export_wrapped() -> KmsCliResult<()> { key_id: Some(private_key_id.to_string()), key_file: tmp_path.join("output.export"), wrap_key_id: Some(sym_key_id.to_string()), - wrapping_algorithm: Some(WrappingAlgorithm::NistKeyWrap), ..Default::default() } .run(ctx.get_owner_client()) diff --git a/crate/cli/src/tests/kms/shared/export_import.rs b/crate/cli/src/tests/kms/shared/export_import.rs index d54cdcd1b1..6451ef78c9 100644 --- a/crate/cli/src/tests/kms/shared/export_import.rs +++ b/crate/cli/src/tests/kms/shared/export_import.rs @@ -48,7 +48,11 @@ pub(crate) async fn test_wrap_on_export_unwrap_on_import() -> KmsCliResult<()> { .to_string(); // Export and import the key with different block cipher modes - for wrapping_algorithm in [WrappingAlgorithm::AesGCM, WrappingAlgorithm::NistKeyWrap] { + for wrapping_algorithm in [ + WrappingAlgorithm::AesGCM, + WrappingAlgorithm::NistKeyWrap, + WrappingAlgorithm::AESKeyWrapPadding, + ] { debug!("wrapping algorithm: {wrapping_algorithm:?}",); ExportSecretDataOrKeyAction { key_id: Some(dek_id.clone()), diff --git a/crate/client_utils/src/export_utils.rs b/crate/client_utils/src/export_utils.rs index 7f1fa3f878..2937d9e975 100644 --- a/crate/client_utils/src/export_utils.rs +++ b/crate/client_utils/src/export_utils.rs @@ -35,6 +35,7 @@ pub enum ExportKeyFormat { #[derive(Debug, Clone, PartialEq, Eq, EnumString, ValueEnum)] #[strum(serialize_all = "kebab-case")] pub enum WrappingAlgorithm { + AESKeyWrapPadding, NistKeyWrap, AesGCM, RsaPkcsV15Sha1, @@ -155,6 +156,11 @@ pub fn prepare_key_export_elements( wrapping_algorithm .as_ref() .map(|wrapping_algorithm| match wrapping_algorithm { + WrappingAlgorithm::AESKeyWrapPadding => CryptographicParameters { + cryptographic_algorithm: Some(CryptographicAlgorithm::AES), + block_cipher_mode: Some(BlockCipherMode::AESKeyWrapPadding), + ..CryptographicParameters::default() + }, WrappingAlgorithm::NistKeyWrap => CryptographicParameters { cryptographic_algorithm: Some(CryptographicAlgorithm::AES), block_cipher_mode: Some(BlockCipherMode::NISTKeyWrap), diff --git a/crate/client_utils/src/symmetric_utils.rs b/crate/client_utils/src/symmetric_utils.rs index a144cdd0ab..acdb879d40 100644 --- a/crate/client_utils/src/symmetric_utils.rs +++ b/crate/client_utils/src/symmetric_utils.rs @@ -114,18 +114,15 @@ pub const AES_256_GCM_SIV_IV_LENGTH: usize = 12; #[cfg(feature = "non-fips")] pub const AES_256_GCM_SIV_MAC_LENGTH: usize = 16; -/// RFC 5649 with a 16-byte KEK. -pub const RFC5649_16_KEY_LENGTH: usize = 16; -// RFC 5649 IV is actually a fixed overhead -pub const RFC5649_16_IV_LENGTH: usize = 0; -/// RFC5649 has no authentication. -pub const RFC5649_16_MAC_LENGTH: usize = 0; -/// RFC 5649 with a 32-byte KEK. -pub const RFC5649_32_KEY_LENGTH: usize = 32; +// RFC 3394 IV is actually a fixed overhead +pub const RFC3394_IV_LENGTH: usize = 0; +/// RFC3394 has no authentication. +pub const RFC3394_MAC_LENGTH: usize = 0; + // RFC 5649 IV is actually a fixed overhead -pub const RFC5649_32_IV_LENGTH: usize = 0; +pub const RFC5649_IV_LENGTH: usize = 0; /// RFC5649 has no authentication. -pub const RFC5649_32_MAC_LENGTH: usize = 0; +pub const RFC5649_MAC_LENGTH: usize = 0; #[cfg(feature = "non-fips")] /// Chacha20-Poly1305 key length in bytes. @@ -156,7 +153,8 @@ pub fn parse_decrypt_elements( } BlockCipherMode::CBC => (AES_128_CBC_IV_LENGTH, AES_128_CBC_MAC_LENGTH), BlockCipherMode::XTS => (AES_128_XTS_TWEAK_LENGTH, AES_128_XTS_MAC_LENGTH), - BlockCipherMode::NISTKeyWrap => (RFC5649_16_IV_LENGTH, RFC5649_16_MAC_LENGTH), + BlockCipherMode::AESKeyWrapPadding => (RFC5649_IV_LENGTH, RFC5649_MAC_LENGTH), + BlockCipherMode::NISTKeyWrap => (RFC3394_IV_LENGTH, RFC3394_MAC_LENGTH), _ => { return Err(UtilsError::Default( "Unsupported block cipher mode".to_owned(), diff --git a/crate/crypto/src/crypto/rsa/ckm_rsa_aes_key_wrap.rs b/crate/crypto/src/crypto/rsa/ckm_rsa_aes_key_wrap.rs index c6235a971e..9d65beeceb 100644 --- a/crate/crypto/src/crypto/rsa/ckm_rsa_aes_key_wrap.rs +++ b/crate/crypto/src/crypto/rsa/ckm_rsa_aes_key_wrap.rs @@ -582,7 +582,7 @@ FQIDAQAB crypto_bail!("test_for_byok: RFC5649 pkeyutl failed: {output:?}"); } let rfc5649_encapsulation = fs::read(&rfc5649_encapsulation_file)?; - // Check against our implementation of NistKeyWrap + // Check against our implementation of AESKeyWrapPadding let rec_secret_bytes = rfc5649_unwrap( &rfc5649_encapsulation, &hex::decode(ephemeral) diff --git a/crate/crypto/src/crypto/symmetric/rfc3394.rs b/crate/crypto/src/crypto/symmetric/rfc3394.rs index ac75d5ab60..0fc41ea4ef 100644 --- a/crate/crypto/src/crypto/symmetric/rfc3394.rs +++ b/crate/crypto/src/crypto/symmetric/rfc3394.rs @@ -9,7 +9,7 @@ //! - Input must be a multiple of 8 bytes and at least 16 bytes (n >= 2 blocks). //! - No padding is performed; for non-8-byte input lengths, use RFC 5649 (KWP). use openssl::cipher::{Cipher, CipherRef}; -use openssl::cipher_ctx::{CipherCtx, CipherCtxFlags}; +use openssl::cipher_ctx::CipherCtx; use zeroize::Zeroizing; use crate::error::CryptoError; @@ -20,9 +20,9 @@ const AES_WRAP_BLOCK_SIZE: usize = 8; // 64-bit fn select_cipher(kek: &[u8]) -> CryptoResult<&CipherRef> { Ok(match kek.len() { - 16 => Cipher::aes_128_wrap_pad(), - 24 => Cipher::aes_192_wrap_pad(), - 32 => Cipher::aes_256_wrap_pad(), + 16 => Cipher::aes_128_wrap(), + 24 => Cipher::aes_192_wrap(), + 32 => Cipher::aes_256_wrap(), _ => { return Err(CryptoError::InvalidSize( "The KEK size should be 16, 24 or 32 bytes".to_owned(), @@ -31,9 +31,6 @@ fn select_cipher(kek: &[u8]) -> CryptoResult<&CipherRef> { }) } -#[deprecated( - note = "Use `rfc5649::rfc5649_wrap` instead. RFC 3394 is provided only for legacy compatibility." -)] pub fn rfc3394_wrap(plaintext: &[u8], kek: &[u8]) -> CryptoResult> { let n_bytes = plaintext.len(); @@ -48,13 +45,12 @@ pub fn rfc3394_wrap(plaintext: &[u8], kek: &[u8]) -> CryptoResult> { // Initialize cipher context for encryption let mut ctx = CipherCtx::new()?; - ctx.set_flags(CipherCtxFlags::FLAG_WRAP_ALLOW); // For some reason the code works without this, but it should be set anyway ctx.encrypt_init(Some(cipher), Some(kek), None)?; // Allocate output buffer: wrapped size is plaintext + 8 bytes (IV) + 2 extra blocks for cipher_final // The extra blocs will not propagate to the result as its truncated to the actual size. Due to how the openssl library is programmed, // not adding at least 1 extra bloc results in a panic. We chose to add two because that's how openssl library operates when using this cypher - let mut ciphertext = vec![0_u8; n_bytes + AES_WRAP_BLOCK_SIZE + (AES_BLOCK_SIZE * 2)]; + let mut ciphertext = vec![0_u8; n_bytes + AES_WRAP_BLOCK_SIZE + AES_BLOCK_SIZE]; // Perform the key wrap operation let mut written = ctx.cipher_update(plaintext, Some(&mut ciphertext))?; @@ -68,9 +64,6 @@ pub fn rfc3394_wrap(plaintext: &[u8], kek: &[u8]) -> CryptoResult> { Ok(ciphertext) } -#[deprecated( - note = "Use `rfc5649::rfc5649_wrap` instead. RFC 3394 is provided only for legacy compatibility." -)] pub fn rfc3394_unwrap(ciphertext: &[u8], kek: &[u8]) -> CryptoResult>> { let n_bytes = ciphertext.len(); @@ -85,14 +78,10 @@ pub fn rfc3394_unwrap(ciphertext: &[u8], kek: &[u8]) -> CryptoResult CryptoResult<&CipherRef> { } pub fn rfc5649_wrap(plaintext: &[u8], kek: &[u8]) -> CryptoResult> { + const MAX_PLAINTEXT_LEN: u64 = 1 << 32; let n_bytes = plaintext.len(); // RFC 5649 requires plaintext to be at least 1 byte and less than 2^32 bytes @@ -42,7 +43,7 @@ pub fn rfc5649_wrap(plaintext: &[u8], kek: &[u8]) -> CryptoResult> { } // Check maximum length (2^32 - 1 bytes as per NIST SP 800-38F Section 5.3.1) - if n_bytes >= (1_usize << 32) { + if u64::try_from(n_bytes).is_ok_and(|n_bytes| n_bytes >= MAX_PLAINTEXT_LEN) { return Err(CryptoError::InvalidSize( "The plaintext size should be less than 2^32 bytes".to_owned(), )); @@ -51,22 +52,21 @@ pub fn rfc5649_wrap(plaintext: &[u8], kek: &[u8]) -> CryptoResult> { let cipher = select_cipher_pad(kek)?; let mut ctx = CipherCtx::new()?; - ctx.set_flags(CipherCtxFlags::FLAG_WRAP_ALLOW); // For some reason the code works without this, but it should should anyway ctx.encrypt_init(Some(cipher), Some(kek), None)?; // Calculate output size: for KWP, output is always a multiple of 8 bytes - // Minimum output is 16 bytes (2 semi-blocks) + // Minimum output is 16 bytes (2 "semi-blocks") // The wrapped size includes the AIV (8 bytes) plus padded plaintext let padded_len = if n_bytes <= AES_WRAP_BLOCK_SIZE { AES_WRAP_BLOCK_SIZE // Special case: single block encryption } else { // Calculate padding: round up to next multiple of 8, then add 8 for AIV - let padded_plaintext_len = ((n_bytes + 8 - 1) / 8) * 8; + let padded_plaintext_len = n_bytes.div_ceil(8) * 8; padded_plaintext_len + 8 }; // Allocate output buffer with extra space for cipher_final - let mut ciphertext = vec![0_u8; padded_len + (AES_BLOCK_SIZE * 2)]; + let mut ciphertext = vec![0_u8; padded_len + AES_BLOCK_SIZE]; // Perform the key wrap operation let mut written = ctx.cipher_update(plaintext, Some(&mut ciphertext))?; @@ -93,15 +93,11 @@ pub fn rfc5649_unwrap(ciphertext: &[u8], kek: &[u8]) -> CryptoResult Ok(Cipher::aes_256_gcm()), Self::Aes128Xts => Ok(Cipher::aes_128_xts()), Self::Aes256Xts => Ok(Cipher::aes_256_xts()), - Self::Rfc5649_16 | Self::Rfc5649_32 => { + Self::Rfc3394_16 | Self::Rfc3394_24 | Self::Rfc3394_32 => { + crypto_bail!(CryptoError::NotSupported( + "RFC3394 is not supported in this version of openssl".to_owned() + )) + } + Self::Rfc5649_16 | Self::Rfc5649_24 | Self::Rfc5649_32 => { crypto_bail!(CryptoError::NotSupported( "RFC5649 is not supported in this version of openssl".to_owned() )) @@ -219,8 +238,8 @@ impl SymCipher { Self::Aes256Gcm => AES_256_GCM_MAC_LENGTH, Self::Aes128Xts => AES_128_XTS_MAC_LENGTH, Self::Aes256Xts => AES_256_XTS_MAC_LENGTH, - Self::Rfc5649_16 => RFC5649_16_MAC_LENGTH, - Self::Rfc5649_32 => RFC5649_32_MAC_LENGTH, + Self::Rfc3394_16 | Self::Rfc3394_24 | Self::Rfc3394_32 => RFC3394_MAC_LENGTH, + Self::Rfc5649_16 | Self::Rfc5649_24 | Self::Rfc5649_32 => RFC5649_MAC_LENGTH, #[cfg(feature = "non-fips")] Self::Chacha20Poly1305 => CHACHA20_POLY1305_MAC_LENGTH, #[cfg(feature = "non-fips")] @@ -247,8 +266,8 @@ impl SymCipher { Self::Aes256Gcm => AES_256_GCM_IV_LENGTH, Self::Aes128Xts => AES_128_XTS_TWEAK_LENGTH, Self::Aes256Xts => AES_256_XTS_TWEAK_LENGTH, - Self::Rfc5649_16 => RFC5649_16_IV_LENGTH, - Self::Rfc5649_32 => RFC5649_32_IV_LENGTH, + Self::Rfc3394_16 | Self::Rfc3394_24 | Self::Rfc3394_32 => RFC3394_IV_LENGTH, + Self::Rfc5649_16 | Self::Rfc5649_24 | Self::Rfc5649_32 => RFC5649_IV_LENGTH, #[cfg(feature = "non-fips")] Self::Chacha20Poly1305 => CHACHA20_POLY1305_IV_LENGTH, #[cfg(feature = "non-fips")] @@ -275,7 +294,11 @@ impl SymCipher { Self::Aes256Gcm => AES_256_GCM_KEY_LENGTH, Self::Aes128Xts => AES_128_XTS_KEY_LENGTH, Self::Aes256Xts => AES_256_XTS_KEY_LENGTH, + Self::Rfc3394_16 => RFC3394_16_KEY_LENGTH, + Self::Rfc3394_24 => RFC3394_24_KEY_LENGTH, + Self::Rfc3394_32 => RFC3394_32_KEY_LENGTH, Self::Rfc5649_16 => RFC5649_16_KEY_LENGTH, + Self::Rfc5649_24 => RFC5649_24_KEY_LENGTH, Self::Rfc5649_32 => RFC5649_32_KEY_LENGTH, #[cfg(feature = "non-fips")] Self::Chacha20Poly1305 => CHACHA20_POLY1305_KEY_LENGTH, @@ -344,13 +367,23 @@ impl SymCipher { {key_size} bytes", ))), }, - // BlockCipherMode::NISTKeyWrap => match key_size { - // RFC5649_16_KEY_LENGTH => Ok(Self::Rfc5649_16), - // RFC5649_32_KEY_LENGTH => Ok(Self::Rfc5649_32), - // _ => crypto_bail!(CryptoError::NotSupported(format!( - // "RFC5649 key must be 16 or 32 bytes long. Found {key_size} bytes", - // ))), - // }, + BlockCipherMode::AESKeyWrapPadding => match key_size { + RFC5649_16_KEY_LENGTH => Ok(Self::Rfc5649_16), + RFC5649_24_KEY_LENGTH => Ok(Self::Rfc5649_24), + RFC5649_32_KEY_LENGTH => Ok(Self::Rfc5649_32), + _ => crypto_bail!(CryptoError::NotSupported(format!( + "RFC5649 key must be 16, 24 or 32 bytes long. Found {key_size} \ + bytes", + ))), + }, + BlockCipherMode::NISTKeyWrap => match key_size { + RFC3394_16_KEY_LENGTH => Ok(Self::Rfc3394_16), + RFC3394_24_KEY_LENGTH => Ok(Self::Rfc3394_24), + RFC3394_32_KEY_LENGTH => Ok(Self::Rfc3394_32), + _ => crypto_bail!(CryptoError::NotSupported(format!( + "RFC3394 key must be 16, 24 or 32 bytes long. Found {key_size} bytes", + ))), + }, mode => { crypto_bail!(CryptoError::NotSupported(format!( "AES is not supported with mode: {mode:?}" @@ -511,9 +544,15 @@ pub fn encrypt( SymCipher::Aes128GcmSiv | SymCipher::Aes256GcmSiv => { aes_gcm_siv_not_openssl::encrypt(key, nonce, aad, plaintext) } - SymCipher::Rfc5649_16 | SymCipher::Rfc5649_32 => { + SymCipher::Rfc5649_16 | SymCipher::Rfc5649_24 | SymCipher::Rfc5649_32 => { Ok((rfc5649_wrap(plaintext, key)?, vec![])) } + SymCipher::Rfc3394_16 | SymCipher::Rfc3394_24 | SymCipher::Rfc3394_32 => { + info!( + "RFC 3394 is deprecated in favor of RFC 5549 and is supported only for legacy compatibility. Please consider using `BlockCipherMode::AESKeyWrapPadding` (RFC 5649) for new applications instead of `BlockCipherMode::NISTKeyWrap `." + ); + Ok((rfc3394_wrap(plaintext, key)?, vec![])) + } #[cfg(feature = "non-fips")] SymCipher::Chacha20 => { trace!( @@ -657,7 +696,15 @@ pub fn decrypt( SymCipher::Aes128GcmSiv | SymCipher::Aes256GcmSiv => { aes_gcm_siv_not_openssl::decrypt(key, nonce, aad, ciphertext, tag)? } - SymCipher::Rfc5649_16 | SymCipher::Rfc5649_32 => rfc5649_unwrap(ciphertext, key)?, + SymCipher::Rfc3394_16 | SymCipher::Rfc3394_24 | SymCipher::Rfc3394_32 => { + info!( + "RFC 3394 is deprecated in favor of RFC 5549 and is supported only for legacy compatibility. Please consider using `BlockCipherMode::AESKeyWrapPadding` (RFC 5649) for new applications instead of `BlockCipherMode::NISTKeyWrap `." + ); + rfc3394_unwrap(ciphertext, key)? + } + SymCipher::Rfc5649_16 | SymCipher::Rfc5649_24 | SymCipher::Rfc5649_32 => { + rfc5649_unwrap(ciphertext, key)? + } #[cfg(feature = "non-fips")] SymCipher::Chacha20 => { if key.len() != CHACHA20_KEY_LENGTH { diff --git a/crate/crypto/src/crypto/wrap/wrap_key.rs b/crate/crypto/src/crypto/wrap/wrap_key.rs index 899e4d505f..351d5ca09c 100644 --- a/crate/crypto/src/crypto/wrap/wrap_key.rs +++ b/crate/crypto/src/crypto/wrap/wrap_key.rs @@ -262,7 +262,7 @@ pub(super) fn wrap( let block_cipher_mode = cryptographic_parameters .as_ref() .and_then(|params| params.block_cipher_mode) - .unwrap_or(BlockCipherMode::NISTKeyWrap); + .unwrap_or(BlockCipherMode::AESKeyWrapPadding); let padding_method = cryptographic_parameters .as_ref() .and_then(|params| params.padding_method) diff --git a/crate/kmip/src/kmip_0/kmip_types.rs b/crate/kmip/src/kmip_0/kmip_types.rs index 1061e33f0a..e5d153cec5 100644 --- a/crate/kmip/src/kmip_0/kmip_types.rs +++ b/crate/kmip/src/kmip_0/kmip_types.rs @@ -780,7 +780,7 @@ pub enum BlockCipherMode { CBCMAC = 0x0000_000A, XTS = 0x0000_000B, AESKeyWrapPadding = 0x0000_000C, - NISTKeyWrap = 0x8000_000D, + NISTKeyWrap = 0x8000_000D, // FIXME X9102AESKW = 0x0000_000E, X9102TDKW = 0x0000_000F, X9102AKW1 = 0x0000_0010, diff --git a/crate/kmip/src/ttlv/xml/deserializer.rs b/crate/kmip/src/ttlv/xml/deserializer.rs index 3f1597aad1..8442299d19 100644 --- a/crate/kmip/src/ttlv/xml/deserializer.rs +++ b/crate/kmip/src/ttlv/xml/deserializer.rs @@ -541,7 +541,7 @@ impl TTLVXMLDeserializer { "ECDSA" => Some((0x0000_0006, "ECDSA")), // Vendor / extension algorithms present in mandatory vectors // NISTKeyWrap appears as an algorithm enumeration in interop vectors; assign vendor range code - "NISTKeyWrap" => Some((0x8000_000D, "NISTKeyWrap")), + "NISTKeyWrap" => Some((0x8000_000D, "NISTKeyWrap")), // FIXME "HMAC_SHA1" => Some((0x0000_0007, "HMACSHA1")), "HMAC_SHA224" => Some((0x0000_0008, "HMACSHA224")), "HMAC_SHA256" => Some((0x0000_0009, "HMACSHA256")), diff --git a/crate/server_database/README.md b/crate/server_database/README.md index b22322daa7..d5ff0390c8 100644 --- a/crate/server_database/README.md +++ b/crate/server_database/README.md @@ -77,7 +77,7 @@ ENC_Findex v8(u:userid) → ENC_Findex v8(permission_triplet) ENC_Findex v8(object_uid) → ENC_Findex v8(metadata) ``` -A more colorful and clear description of how the Redis backend operates with Findex can be red on the its original PR description : [github.com/Cosmian/kms/pull/542](github.com/Cosmian/kms/pull/542). +A more colorful and clear description of how the Redis backend operates with Findex can be red on the its original PR description : [github.com/Cosmian/kms/pull/542](https://github.com/Cosmian/kms/pull/542). ### Environment Variables diff --git a/crate/server_database/src/stores/redis/objects_db.rs b/crate/server_database/src/stores/redis/objects_db.rs index a9f2159374..698ff02ed3 100644 --- a/crate/server_database/src/stores/redis/objects_db.rs +++ b/crate/server_database/src/stores/redis/objects_db.rs @@ -152,7 +152,7 @@ impl ObjectsDB { })?; Nonce::new(&mut *rng) }; - let ct = self.dem.encrypt( + let ct: Vec = self.dem.encrypt( &nonce, &serde_json::to_vec(redis_db_object)?, Some(uid.as_bytes()), From b05d2e53bff869f9136676a01a44f06b4f9a2ddd Mon Sep 17 00:00:00 2001 From: HatemMn <19950216+HatemMn@users.noreply.github.com> Date: Fri, 2 Jan 2026 18:26:49 +0100 Subject: [PATCH 8/8] fix: method --- crate/server_database/src/stores/redis/objects_db.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crate/server_database/src/stores/redis/objects_db.rs b/crate/server_database/src/stores/redis/objects_db.rs index 698ff02ed3..fdb471f096 100644 --- a/crate/server_database/src/stores/redis/objects_db.rs +++ b/crate/server_database/src/stores/redis/objects_db.rs @@ -182,6 +182,8 @@ impl ObjectsDB { Some(uid.as_bytes()), ) .with_context(|| format!("decrypt_object uid: {uid}"))?; + // TODO: load the old version of the cosmian_kmip to support migration from old serialized objects* + // then, write using the new one let redis_db_object: RedisDbObject = serde_json::from_slice(&plaintext) .with_context(|| format!("decrypt_object uid: {uid}"))?; Ok(redis_db_object)