From 6fd67c1ffa0765a591661ae51b6e14b0f2bd6079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Dugr=C3=A9?= Date: Mon, 2 Dec 2024 16:30:05 -0500 Subject: [PATCH 01/21] [STREAM] WIP commit --- Cargo.lock | 18 ++ Cargo.toml | 3 +- src/enums.rs | 20 ++ src/error.rs | 4 + src/header.rs | 14 +- src/lib.rs | 3 +- src/online_ciphertext/mod.rs | 84 ++++++ src/online_ciphertext/online_ciphertext_v1.rs | 279 ++++++++++++++++++ src/utils.rs | 3 + 9 files changed, 419 insertions(+), 9 deletions(-) create mode 100644 src/online_ciphertext/mod.rs create mode 100644 src/online_ciphertext/online_ciphertext_v1.rs diff --git a/Cargo.lock b/Cargo.lock index 29640f56e..1a09da85e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,6 +104,9 @@ name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "zeroize", +] [[package]] name = "askama" @@ -216,6 +219,20 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "blake3" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "zeroize", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -494,6 +511,7 @@ dependencies = [ "arbitrary", "base64 0.22.1", "blahaj", + "blake3", "byteorder", "cbc", "cfg-if", diff --git a/Cargo.toml b/Cargo.toml index 1067791b6..57ae156c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ name = "devolutions_crypto" crate-type = ["cdylib", "rlib"] [dependencies] -aead = "0.5" +aead = { version = "0.5", features = ["stream"] } aes = "0.8" base64 = "0.22" cbc = { version = "0.1.2", features = ["block-padding", "alloc"] } @@ -58,6 +58,7 @@ x25519-dalek = { version = "2", features = [ "static_secrets" ] } # Version is pinned because newer version requires lifetime annotations # that isn't compatible with the header trait arbitrary = { version = "0.4.7", features = ["derive"], optional = true } +blake3 = { version = "1.5.5", features = ["zeroize"] } [target.'cfg(target_arch="wasm32")'.dependencies] rust-argon2 = { version = "1.0", default-features = false } diff --git a/src/enums.rs b/src/enums.rs index 202132ccd..11efcee0a 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -27,6 +27,8 @@ pub enum DataType { SigningKey = 5, /// A wrapped signature. Signature = 6, + /// A wrapped online ciphertextr that can be encrypted/decrypted chunk by chunk + OnlineCiphertext = 7, } impl Default for DataType { @@ -55,6 +57,24 @@ impl Default for CiphertextVersion { } } +/// The versions of the online encryption scheme to use. +#[cfg_attr(feature = "wbindgen", wasm_bindgen())] +#[cfg_attr(feature = "fuzz", derive(Arbitrary))] +#[derive(Clone, Copy, PartialEq, Eq, Zeroize, IntoPrimitive, TryFromPrimitive, Debug)] +#[repr(u16)] +pub enum OnlineCiphertextVersion { + /// Uses the latest version. + Latest = 0, + /// Uses version 1: XChaCha20-Poly1305 wrapped in a STREAM construction. + V1 = 1, +} + +impl Default for OnlineCiphertextVersion { + fn default() -> Self { + Self::Latest + } +} + /// The versions of the password hashing scheme to use. #[cfg_attr(feature = "wbindgen", wasm_bindgen())] #[cfg_attr(feature = "fuzz", derive(Arbitrary))] diff --git a/src/error.rs b/src/error.rs index aa8e34dcb..e9e570319 100644 --- a/src/error.rs +++ b/src/error.rs @@ -60,6 +60,9 @@ pub enum Error { /// The version of the multiple data is inconsistent: -42 #[error("The version is not the same for all the data.")] InconsistentVersion, + /// The length of the data to encrypt/decrypt during online encryption is not the same as the chunk size: -43 + #[error("The length of the data to encrypt/decrypt during online encryption is not the same as the chunk size")] + InvalidChunkLength, } impl Error { @@ -83,6 +86,7 @@ impl Error { Error::IoError(_) => -34, Error::NotEnoughShares => -41, Error::InconsistentVersion => -42, + Error::InvalidChunkLength => -43, } } } diff --git a/src/header.rs b/src/header.rs index bf2406214..d8850a797 100644 --- a/src/header.rs +++ b/src/header.rs @@ -18,12 +18,12 @@ const SIGNATURE: u16 = 0x0C0D; pub trait HeaderType { cfg_if! { if #[cfg(feature = "fuzz")] { - type Version: Into + TryFrom + Clone + Default + Zeroize + std::fmt::Debug + Arbitrary; - type Subtype: Into + TryFrom + Clone + Default + Zeroize + std::fmt::Debug + Arbitrary; + type Version: Into + TryFrom + Clone + Copy + Default + Zeroize + std::fmt::Debug + Arbitrary; + type Subtype: Into + TryFrom + Clone + Copy + Default + Zeroize + std::fmt::Debug + Arbitrary; } else { - type Version: Into + TryFrom + Clone + Default + Zeroize + std::fmt::Debug; - type Subtype: Into + TryFrom + Clone + Default + Zeroize + std::fmt::Debug; + type Version: Into + TryFrom + Clone + Copy + Default + Zeroize + std::fmt::Debug; + type Subtype: Into + TryFrom + Clone + Copy + Default + Zeroize + std::fmt::Debug; } } @@ -50,7 +50,7 @@ impl HeaderType for () { #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz", derive(Arbitrary))] -pub struct Header +pub struct Header where M: HeaderType, { @@ -104,11 +104,11 @@ where } } -impl From> for Vec +impl From<&Header> for Vec where M: HeaderType, { - fn from(header: Header) -> Vec { + fn from(header: &Header) -> Vec { let mut data = Vec::with_capacity(8); data.write_u16::(header.signature).unwrap(); data.write_u16::(header.data_type.into()) diff --git a/src/lib.rs b/src/lib.rs index cda22c4bb..8ce3f75e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -212,6 +212,7 @@ mod error; mod header; pub mod ciphertext; +pub mod online_ciphertext; pub mod key; pub mod password_hash; pub mod secret_sharing; @@ -224,7 +225,7 @@ pub use header::{Header, HeaderType}; pub use enums::{ CiphertextVersion, DataType, KeyVersion, PasswordHashVersion, SecretSharingVersion, - SignatureVersion, SigningKeyVersion, + SignatureVersion, SigningKeyVersion, OnlineCiphertextVersion, }; pub use argon2::Variant as Argon2Variant; diff --git a/src/online_ciphertext/mod.rs b/src/online_ciphertext/mod.rs new file mode 100644 index 000000000..8084f72a9 --- /dev/null +++ b/src/online_ciphertext/mod.rs @@ -0,0 +1,84 @@ +//! Module for symmetric/asymmetric encryption/decryption. +//! +//! This module contains everything related to encryption. You can use it to encrypt and decrypt data using either a shared key of a keypair. +//! Either way, the encryption will give you a `Ciphertext`, which has a method to decrypt it. +//! +//! ### Symmetric +//! +//! ```rust +//! use devolutions_crypto::utils::generate_key; +//! use devolutions_crypto::ciphertext::{ encrypt, CiphertextVersion, Ciphertext }; +//! +//! let key: Vec = generate_key(32); +//! +//! let data = b"somesecretdata"; +//! +//! let encrypted_data: Ciphertext = encrypt(data, &key, CiphertextVersion::Latest).expect("encryption shouldn't fail"); +//! +//! let decrypted_data = encrypted_data.decrypt(&key).expect("The decryption shouldn't fail"); +//! +//! assert_eq!(decrypted_data, data); +//! ``` +//! +//! ### Asymmetric +//! Here, you will need a `PublicKey` to encrypt data and the corresponding +//! `PrivateKey` to decrypt it. You can generate them by using `generate_keypair` +//! in the [Key module](#key). +//! +//! ```rust +//! use devolutions_crypto::key::{generate_keypair, KeyVersion, KeyPair}; +//! use devolutions_crypto::ciphertext::{ encrypt_asymmetric, CiphertextVersion, Ciphertext }; +//! +//! let keypair: KeyPair = generate_keypair(KeyVersion::Latest); +//! +//! let data = b"somesecretdata"; +//! +//! let encrypted_data: Ciphertext = encrypt_asymmetric(data, &keypair.public_key, CiphertextVersion::Latest).expect("encryption shouldn't fail"); +//! +//! let decrypted_data = encrypted_data.decrypt_asymmetric(&keypair.private_key).expect("The decryption shouldn't fail"); +//! +//! assert_eq!(decrypted_data, data); +//! ``` + +mod online_ciphertext_v1; + +use super::CiphertextSubtype; +pub use super::OnlineCiphertextVersion; +use super::DataType; +use super::Error; +use super::Header; +use super::HeaderType; +use super::Result; + +use super::key::{PrivateKey, PublicKey}; + +use online_ciphertext_v1::{OnlineCiphertextV1Engine, OnlineCiphertextV1Asymmetric, OnlineCiphertextV1Symmetric}; + +use std::convert::TryFrom; + +#[cfg(feature = "fuzz")] +use arbitrary::Arbitrary; + +/// A versionned online ciphertext. Can be either symmetric or asymmetric. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "fuzz", derive(Arbitrary))] +pub struct OnlineCiphertext { + pub(crate) header: Header, + payload: OnlineCiphertextPayload, +} + +impl HeaderType for OnlineCiphertext { + type Version = OnlineCiphertextVersion; + type Subtype = CiphertextSubtype; + + fn data_type() -> DataType { + DataType::OnlineCiphertext + } +} + +#[derive(Clone, Debug)] +#[cfg_attr(feature = "fuzz", derive(Arbitrary))] +enum OnlineCiphertextPayload { + V1Symmetric(OnlineCiphertextV1Symmetric), + V1Asymmetric(OnlineCiphertextV1Asymmetric), +} diff --git a/src/online_ciphertext/online_ciphertext_v1.rs b/src/online_ciphertext/online_ciphertext_v1.rs new file mode 100644 index 000000000..09b47c9a3 --- /dev/null +++ b/src/online_ciphertext/online_ciphertext_v1.rs @@ -0,0 +1,279 @@ +//! Online Ciphertext V1: STREAM-XChaCha20Poly1305 +use super::{PrivateKey, PublicKey}; + +use super::Error; +use super::Header; +use super::Result; + +use super::OnlineCiphertext; + +use std::convert::TryFrom; +use std::ops::Sub; + +use aead::generic_array::GenericArray; +use aead::stream::{StreamLE31, StreamPrimitive}; +use aead::AeadCore; +use chacha20poly1305::aead::{Aead, Payload, stream::{ EncryptorLE31, DecryptorLE31 }}; +use chacha20poly1305::{Key, KeyInit, XChaCha20Poly1305, XNonce}; + +use rand::{rngs::OsRng, RngCore}; +use x25519_dalek::StaticSecret; +use zeroize::{Zeroize, Zeroizing}; + +#[cfg(feature = "fuzz")] +use arbitrary::Arbitrary; + +const CONTEXT: &'static str = "devolutions_crypto online_ciphertext_v1"; + +pub struct OnlineCiphertextV1Encryptor { + chunk_size: u64, + nonce: [u8; 20], + cipher: EncryptorLE31, +} + +pub struct OnlineCiphertextV1Decryptor { + chunk_size: u64, + nonce: [u8; 20], + cipher: DecryptorLE31, +} + +impl OnlineCiphertextV1Encryptor { + pub fn new(key: &[u8], chunk_size: u64) -> Self { + // Generate a new nonce + let mut nonce = [0u8; 20]; + OsRng.fill_bytes(&mut nonce); + + // Derive the key and zeroize it after key schedule + let mut key = blake3::derive_key(CONTEXT, key); + let cipher = XChaCha20Poly1305::new(&key.into()); + key.zeroize(); + + // Create the STREAM encryptor + let cipher = EncryptorLE31::from_aead(cipher, &nonce.into()); + + Self { + chunk_size, + nonce, + cipher, + } + } + + pub fn encrypt_chunk(&mut self, data: &[u8], header: &Header) -> Result> { + if (data.len() as u64) != self.chunk_size { + return Err(Error::InvalidChunkLength) + }; + + let header: Vec = header.into(); + + let payload = Payload { + msg: data, + aad: header.as_slice() + }; + + Ok(self.cipher.encrypt_next(payload)?) + } + + pub fn encrypt_last_chunk(self, data: &[u8], header: &Header) -> Result> { + if (data.len() as u64) >= self.chunk_size { + return Err(Error::InvalidChunkLength) + }; + + let header: Vec = header.into(); + + let payload = Payload { + msg: data, + aad: header.as_slice() + }; + + Ok(self.cipher.encrypt_last(payload)?) + } +} + +#[derive(Zeroize, Clone, Debug)] +#[cfg_attr(feature = "fuzz", derive(Arbitrary))] +#[zeroize(drop)] +pub struct CiphertextV2Symmetric { + nonce: [u8; 24], + ciphertext: Vec, +} + +#[derive(Clone, Debug)] +pub struct CiphertextV2Asymmetric { + public_key: x25519_dalek::PublicKey, + ciphertext: CiphertextV2Symmetric, +} + +#[cfg(feature = "fuzz")] +impl Arbitrary for CiphertextV2Asymmetric { + fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { + let public_key: [u8; 32] = Arbitrary::arbitrary(u)?; + let public_key = x25519_dalek::PublicKey::from(public_key); + let ciphertext = CiphertextV2Symmetric::arbitrary(u)?; + Ok(Self { + public_key, + ciphertext, + }) + } +} + +impl From for Vec { + fn from(mut cipher: CiphertextV2Symmetric) -> Vec { + let mut data = Vec::new(); + data.extend_from_slice(&cipher.nonce); + data.append(&mut cipher.ciphertext); + data + } +} + +impl TryFrom<&[u8]> for CiphertextV2Symmetric { + type Error = Error; + fn try_from(data: &[u8]) -> Result { + if data.len() <= 24 { + return Err(Error::InvalidLength); + }; + + let mut nonce = [0u8; 24]; + let mut ciphertext = vec![0u8; data.len() - 24]; + + nonce.copy_from_slice(&data[0..24]); + ciphertext.copy_from_slice(&data[24..]); + + Ok(CiphertextV2Symmetric { nonce, ciphertext }) + } +} + +impl CiphertextV2Symmetric { + fn derive_key(secret: &[u8]) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(secret); + hasher.finalize().into() + } + + pub fn encrypt( + data: &[u8], + key: &[u8], + aad: &[u8], + header: &Header, + ) -> Result { + // Derive key + let key = Zeroizing::new(CiphertextV2Symmetric::derive_key(key)); + + // Generate nonce + let mut nonce_bytes = [0u8; 24]; + OsRng.fill_bytes(&mut nonce_bytes); + + let nonce = XNonce::from_slice(&nonce_bytes); + + // Authenticate the header + let mut mac_data: Vec = (*header).clone().into(); + mac_data.extend_from_slice(aad); + + let payload = Payload { + msg: data, + aad: &mac_data, + }; + + // Encrypt + let ciphertext = { + let key = Key::from_slice(key.as_slice()); + let cipher = XChaCha20Poly1305::new(key); + cipher.encrypt(nonce, payload)? + }; + + Ok(CiphertextV2Symmetric { + nonce: nonce_bytes, + ciphertext, + }) + } + + pub fn decrypt(&self, key: &[u8], aad: &[u8], header: &Header) -> Result> { + // Derive key + let key = Zeroizing::new(CiphertextV2Symmetric::derive_key(key)); + + // Authenticate the header + let mut mac_data: Vec = (*header).clone().into(); + mac_data.extend_from_slice(aad); + + let payload = Payload { + msg: self.ciphertext.as_slice(), + aad: &mac_data, + }; + + let result = { + // Decrypt + let key = Key::from_slice(key.as_slice()); + let nonce = XNonce::from_slice(&self.nonce); + + let cipher = XChaCha20Poly1305::new(key); + cipher.decrypt(nonce, payload)? + }; + + Ok(result) + } +} + +impl From for Vec { + fn from(cipher: CiphertextV2Asymmetric) -> Self { + let mut data = Vec::new(); + let mut public_key = cipher.public_key.as_bytes().to_vec(); + let mut ciphertext = cipher.ciphertext.into(); + data.append(&mut public_key); + data.append(&mut ciphertext); + data + } +} + +impl TryFrom<&[u8]> for CiphertextV2Asymmetric { + type Error = Error; + fn try_from(data: &[u8]) -> Result { + if data.len() <= 32 { + return Err(Error::InvalidLength); + }; + + let mut public_key = [0u8; 32]; + + public_key.copy_from_slice(&data[0..32]); + let ciphertext = CiphertextV2Symmetric::try_from(&data[32..])?; + + Ok(CiphertextV2Asymmetric { + public_key: x25519_dalek::PublicKey::from(public_key), + ciphertext, + }) + } +} + +impl CiphertextV2Asymmetric { + pub fn encrypt( + data: &[u8], + public_key: &PublicKey, + aad: &[u8], + header: &Header, + ) -> Result { + let public_key = x25519_dalek::PublicKey::from(public_key); + + let ephemeral_private_key = StaticSecret::random_from_rng(rand_core::OsRng); + let ephemeral_public_key = x25519_dalek::PublicKey::from(&ephemeral_private_key); + + let key = ephemeral_private_key.diffie_hellman(&public_key); + + let ciphertext = CiphertextV2Symmetric::encrypt(data, key.as_bytes(), aad, header)?; + + Ok(Self { + public_key: ephemeral_public_key, + ciphertext, + }) + } + + pub fn decrypt( + &self, + private_key: &PrivateKey, + aad: &[u8], + header: &Header, + ) -> Result> { + let private_key = StaticSecret::from(private_key); + + let key = private_key.diffie_hellman(&self.public_key); + + self.ciphertext.decrypt(key.as_bytes(), aad, header) + } +} diff --git a/src/utils.rs b/src/utils.rs index 76f85cc52..cd15e262a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -7,6 +7,8 @@ use rand::{rngs::OsRng, RngCore}; use sha2::Sha256; use subtle::ConstantTimeEq as _; +use crate::online_ciphertext::OnlineCiphertext; + use super::Argon2Parameters; use super::DataType; use super::Error; @@ -117,6 +119,7 @@ pub fn validate_header(data: &[u8], data_type: DataType) -> bool { } DataType::Share => Header::::try_from(&data[0..Header::len()]).is_ok(), DataType::Signature => Header::::try_from(&data[0..Header::len()]).is_ok(), + DataType::OnlineCiphertext => Header::::try_from(&data[0..Header::len()]).is_ok(), } } From fbc5599ade404e9490bd3025dc92cc4a258afbe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Dugr=C3=A9?= Date: Fri, 6 Dec 2024 10:56:11 -0500 Subject: [PATCH 02/21] [STREAM] WIP commit --- src/ciphertext/ciphertext_v1.rs | 4 +- src/ciphertext/ciphertext_v2.rs | 4 +- src/ciphertext/mod.rs | 3 +- src/error.rs | 14 +- src/header.rs | 27 +- src/key/mod.rs | 5 +- src/lib.rs | 10 +- src/online_ciphertext/mod.rs | 6 +- src/online_ciphertext/online_ciphertext_v1.rs | 269 +++++------------- src/password_hash/mod.rs | 3 +- src/secret_sharing/mod.rs | 3 +- src/signature/mod.rs | 3 +- src/signature/signature_v1.rs | 8 +- src/signing_key/mod.rs | 5 +- src/signing_key/signing_key_v1.rs | 18 +- src/utils.rs | 4 +- 16 files changed, 143 insertions(+), 243 deletions(-) diff --git a/src/ciphertext/ciphertext_v1.rs b/src/ciphertext/ciphertext_v1.rs index 0f4181563..8c619523f 100644 --- a/src/ciphertext/ciphertext_v1.rs +++ b/src/ciphertext/ciphertext_v1.rs @@ -91,7 +91,7 @@ impl CiphertextV1 { let ciphertext = cipher.encrypt_padded_vec_mut::(data); // Append MAC data - let mut mac_data: Vec = (*header).clone().into(); + let mut mac_data: Zeroizing> = Zeroizing::new(header.into()); mac_data.extend_from_slice(aad); mac_data.extend_from_slice(&iv); mac_data.extend_from_slice(&ciphertext); @@ -116,7 +116,7 @@ impl CiphertextV1 { CiphertextV1::split_key(key, &mut encryption_key, &mut signature_key)?; // Verify HMAC - let mut mac_data: Zeroizing> = Zeroizing::new((*header).clone().into()); + let mut mac_data: Zeroizing> = Zeroizing::new(header.into()); mac_data.extend_from_slice(aad); mac_data.extend_from_slice(&self.iv); mac_data.extend_from_slice(&self.ciphertext); diff --git a/src/ciphertext/ciphertext_v2.rs b/src/ciphertext/ciphertext_v2.rs index 2f067e279..a92fc1022 100644 --- a/src/ciphertext/ciphertext_v2.rs +++ b/src/ciphertext/ciphertext_v2.rs @@ -96,7 +96,7 @@ impl CiphertextV2Symmetric { let nonce = XNonce::from_slice(&nonce_bytes); // Authenticate the header - let mut mac_data: Vec = (*header).clone().into(); + let mut mac_data: Zeroizing> = Zeroizing::new(header.into()); mac_data.extend_from_slice(aad); let payload = Payload { @@ -122,7 +122,7 @@ impl CiphertextV2Symmetric { let key = Zeroizing::new(CiphertextV2Symmetric::derive_key(key)); // Authenticate the header - let mut mac_data: Vec = (*header).clone().into(); + let mut mac_data: Zeroizing> = Zeroizing::new(header.into()); mac_data.extend_from_slice(aad); let payload = Payload { diff --git a/src/ciphertext/mod.rs b/src/ciphertext/mod.rs index 5655a9027..06ef35f57 100644 --- a/src/ciphertext/mod.rs +++ b/src/ciphertext/mod.rs @@ -56,6 +56,7 @@ use super::key::{PrivateKey, PublicKey}; use ciphertext_v1::CiphertextV1; use ciphertext_v2::{CiphertextV2Asymmetric, CiphertextV2Symmetric}; +use std::borrow::Borrow; use std::convert::TryFrom; #[cfg(feature = "fuzz")] @@ -324,7 +325,7 @@ impl Ciphertext { impl From for Vec { /// Serialize the structure into a `Vec`, for storage, transmission or use in another language. fn from(data: Ciphertext) -> Self { - let mut header: Self = data.header.into(); + let mut header: Self = data.header.borrow().into(); let mut payload: Self = data.payload.into(); header.append(&mut payload); header diff --git a/src/error.rs b/src/error.rs index e9e570319..9798f9b3c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,4 @@ //! Possible errors in the library. - use cbc::cipher::block_padding::UnpadError; #[cfg(feature = "wbindgen")] @@ -9,6 +8,19 @@ use strum::IntoStaticStr; use hmac::digest::MacError; +pub type Result = std::result::Result; + +// Doesn't work because Result is a type alias, keeping the commented code just in case we revisit someday +// impl From> for Result +// where E: Into { +// fn from(value: std::result::Result) -> Self { +// match value { +// Ok(t) => Ok(t), +// Err(e) => Err(e.into()), +// } +// } +// } + /// This crate's error type. #[derive(Debug, IntoStaticStr, thiserror::Error)] pub enum Error { diff --git a/src/header.rs b/src/header.rs index d8850a797..6cb6ecb7f 100644 --- a/src/header.rs +++ b/src/header.rs @@ -50,7 +50,7 @@ impl HeaderType for () { #[derive(Clone, Debug)] #[cfg_attr(feature = "fuzz", derive(Arbitrary))] -pub struct Header +pub struct Header where M: HeaderType, { @@ -108,14 +108,27 @@ impl From<&Header> for Vec where M: HeaderType, { - fn from(header: &Header) -> Vec { - let mut data = Vec::with_capacity(8); - data.write_u16::(header.signature).unwrap(); - data.write_u16::(header.data_type.into()) + fn from(header: &Header) -> Self { + <&Header as Into<[u8; 8]>>::into(header).to_vec() + } +} + +impl From<&Header> for [u8; 8] +where + M: HeaderType, +{ + fn from(header: &Header) -> Self { + let data = [0u8; 8]; + let mut cursor = Cursor::new(data); + cursor.write_u16::(header.signature).unwrap(); + cursor + .write_u16::(header.data_type.into()) .unwrap(); - data.write_u16::(header.data_subtype.into()) + cursor + .write_u16::(header.data_subtype.into()) .unwrap(); - data.write_u16::(header.version.into()) + cursor + .write_u16::(header.version.into()) .unwrap(); data } diff --git a/src/key/mod.rs b/src/key/mod.rs index 1614be696..da5a7c0a3 100644 --- a/src/key/mod.rs +++ b/src/key/mod.rs @@ -52,6 +52,7 @@ use super::Result; use key_v1::{KeyV1Private, KeyV1Public}; +use std::borrow::Borrow; use std::convert::TryFrom; #[cfg(feature = "fuzz")] @@ -231,7 +232,7 @@ fn keypair_headers(version: KeyVersion) -> (Header, Header for Vec { /// Serialize the structure into a `Vec`, for storage, transmission or use in another language. fn from(data: PublicKey) -> Self { - let mut header: Self = data.header.into(); + let mut header: Self = data.header.borrow().into(); let mut payload: Self = data.payload.into(); header.append(&mut payload); header @@ -265,7 +266,7 @@ impl TryFrom<&[u8]> for PublicKey { impl From for Vec { /// Serialize the structure into a `Vec`, for storage, transmission or use in another language. fn from(data: PrivateKey) -> Self { - let mut header: Self = data.header.into(); + let mut header: Self = data.header.borrow().into(); let mut payload: Self = data.payload.into(); header.append(&mut payload); header diff --git a/src/lib.rs b/src/lib.rs index 8ce3f75e1..e8569b4e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -212,8 +212,8 @@ mod error; mod header; pub mod ciphertext; -pub mod online_ciphertext; pub mod key; +pub mod online_ciphertext; pub mod password_hash; pub mod secret_sharing; pub mod signature; @@ -224,8 +224,8 @@ use enums::{CiphertextSubtype, KeySubtype, PasswordHashSubtype, ShareSubtype, Si pub use header::{Header, HeaderType}; pub use enums::{ - CiphertextVersion, DataType, KeyVersion, PasswordHashVersion, SecretSharingVersion, - SignatureVersion, SigningKeyVersion, OnlineCiphertextVersion, + CiphertextVersion, DataType, KeyVersion, OnlineCiphertextVersion, PasswordHashVersion, + SecretSharingVersion, SignatureVersion, SigningKeyVersion, }; pub use argon2::Variant as Argon2Variant; @@ -233,9 +233,7 @@ pub use argon2::Version as Argon2Version; pub use argon2parameters::defaults as argon2parameters_defaults; pub use argon2parameters::Argon2Parameters; pub use argon2parameters::Argon2ParametersBuilder; -pub use error::Error; - -pub type Result = std::result::Result; +pub use error::{Error, Result}; pub const DEFAULT_KEY_SIZE: usize = 32; pub const DEFAULT_PBKDF2_ITERATIONS: u32 = 10000; diff --git a/src/online_ciphertext/mod.rs b/src/online_ciphertext/mod.rs index 8084f72a9..cbf83f2e6 100644 --- a/src/online_ciphertext/mod.rs +++ b/src/online_ciphertext/mod.rs @@ -43,16 +43,18 @@ mod online_ciphertext_v1; use super::CiphertextSubtype; -pub use super::OnlineCiphertextVersion; use super::DataType; use super::Error; use super::Header; use super::HeaderType; +pub use super::OnlineCiphertextVersion; use super::Result; use super::key::{PrivateKey, PublicKey}; -use online_ciphertext_v1::{OnlineCiphertextV1Engine, OnlineCiphertextV1Asymmetric, OnlineCiphertextV1Symmetric}; +use online_ciphertext_v1::{ + OnlineCiphertextV1Asymmetric, OnlineCiphertextV1Engine, OnlineCiphertextV1Symmetric, +}; use std::convert::TryFrom; diff --git a/src/online_ciphertext/online_ciphertext_v1.rs b/src/online_ciphertext/online_ciphertext_v1.rs index 09b47c9a3..68958d2a1 100644 --- a/src/online_ciphertext/online_ciphertext_v1.rs +++ b/src/online_ciphertext/online_ciphertext_v1.rs @@ -1,4 +1,4 @@ -//! Online Ciphertext V1: STREAM-XChaCha20Poly1305 +///! Online Ciphertext V1: STREAM-LE31-XChaCha20Poly1305 use super::{PrivateKey, PublicKey}; use super::Error; @@ -11,9 +11,12 @@ use std::convert::TryFrom; use std::ops::Sub; use aead::generic_array::GenericArray; -use aead::stream::{StreamLE31, StreamPrimitive}; +use aead::stream::{NewStream, StreamLE31, StreamPrimitive}; use aead::AeadCore; -use chacha20poly1305::aead::{Aead, Payload, stream::{ EncryptorLE31, DecryptorLE31 }}; +use chacha20poly1305::aead::{ + stream::{DecryptorLE31, EncryptorLE31}, + Aead, Payload, +}; use chacha20poly1305::{Key, KeyInit, XChaCha20Poly1305, XNonce}; use rand::{rngs::OsRng, RngCore}; @@ -27,253 +30,109 @@ const CONTEXT: &'static str = "devolutions_crypto online_ciphertext_v1"; pub struct OnlineCiphertextV1Encryptor { chunk_size: u64, - nonce: [u8; 20], cipher: EncryptorLE31, } pub struct OnlineCiphertextV1Decryptor { chunk_size: u64, - nonce: [u8; 20], cipher: DecryptorLE31, } -impl OnlineCiphertextV1Encryptor { - pub fn new(key: &[u8], chunk_size: u64) -> Self { +trait NewOnlineCiphertextV1 { + type Ciphertext: NewStream>; + + fn new(key: &[u8], chunk_size: u64) -> (Self, [u8; 20]) { // Generate a new nonce let mut nonce = [0u8; 20]; OsRng.fill_bytes(&mut nonce); - // Derive the key and zeroize it after key schedule - let mut key = blake3::derive_key(CONTEXT, key); - let cipher = XChaCha20Poly1305::new(&key.into()); - key.zeroize(); + // Derive the key + let key = Zeroizing::new(blake3::derive_key(CONTEXT, key)); + let cipher = XChaCha20Poly1305::new(key.as_ref().into()); // Create the STREAM encryptor let cipher = EncryptorLE31::from_aead(cipher, &nonce.into()); - Self { - chunk_size, - nonce, - cipher, - } - } - - pub fn encrypt_chunk(&mut self, data: &[u8], header: &Header) -> Result> { - if (data.len() as u64) != self.chunk_size { - return Err(Error::InvalidChunkLength) - }; - - let header: Vec = header.into(); - - let payload = Payload { - msg: data, - aad: header.as_slice() - }; - - Ok(self.cipher.encrypt_next(payload)?) + (Self { chunk_size, cipher }, nonce) } +} - pub fn encrypt_last_chunk(self, data: &[u8], header: &Header) -> Result> { - if (data.len() as u64) >= self.chunk_size { - return Err(Error::InvalidChunkLength) - }; +impl OnlineCiphertextV1Encryptor { + pub fn new(key: &[u8], chunk_size: u64) -> (Self, [u8; 20]) { + // Generate a new nonce + let mut nonce = [0u8; 20]; + OsRng.fill_bytes(&mut nonce); - let header: Vec = header.into(); + // Derive the key + let key = Zeroizing::new(blake3::derive_key(CONTEXT, key)); + let cipher = XChaCha20Poly1305::new(key.as_ref().into()); - let payload = Payload { - msg: data, - aad: header.as_slice() - }; + // Create the STREAM encryptor + let cipher = EncryptorLE31::from_aead(cipher, &nonce.into()); - Ok(self.cipher.encrypt_last(payload)?) + (Self { chunk_size, cipher }, nonce) } -} -#[derive(Zeroize, Clone, Debug)] -#[cfg_attr(feature = "fuzz", derive(Arbitrary))] -#[zeroize(drop)] -pub struct CiphertextV2Symmetric { - nonce: [u8; 24], - ciphertext: Vec, -} - -#[derive(Clone, Debug)] -pub struct CiphertextV2Asymmetric { - public_key: x25519_dalek::PublicKey, - ciphertext: CiphertextV2Symmetric, -} + pub fn encrypt_chunk( + &mut self, + data: &[u8], + header: &Header, + ) -> Result> { + let mut data = data.to_vec(); -#[cfg(feature = "fuzz")] -impl Arbitrary for CiphertextV2Asymmetric { - fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { - let public_key: [u8; 32] = Arbitrary::arbitrary(u)?; - let public_key = x25519_dalek::PublicKey::from(public_key); - let ciphertext = CiphertextV2Symmetric::arbitrary(u)?; - Ok(Self { - public_key, - ciphertext, - }) - } -} + self.encrypt_in_place(&mut data, header)?; -impl From for Vec { - fn from(mut cipher: CiphertextV2Symmetric) -> Vec { - let mut data = Vec::new(); - data.extend_from_slice(&cipher.nonce); - data.append(&mut cipher.ciphertext); - data + Ok(data) } -} -impl TryFrom<&[u8]> for CiphertextV2Symmetric { - type Error = Error; - fn try_from(data: &[u8]) -> Result { - if data.len() <= 24 { - return Err(Error::InvalidLength); + pub fn encrypt_in_place( + &mut self, + data: &mut Vec, + header: &Header, + ) -> Result<()> { + if (data.len() as u64) != self.chunk_size { + return Err(Error::InvalidChunkLength); }; - let mut nonce = [0u8; 24]; - let mut ciphertext = vec![0u8; data.len() - 24]; + let header: [u8; 8] = header.into(); - nonce.copy_from_slice(&data[0..24]); - ciphertext.copy_from_slice(&data[24..]); + self.cipher.encrypt_next_in_place(&header, data)?; - Ok(CiphertextV2Symmetric { nonce, ciphertext }) + Ok(()) } -} -impl CiphertextV2Symmetric { - fn derive_key(secret: &[u8]) -> [u8; 32] { - let mut hasher = Sha256::new(); - hasher.update(secret); - hasher.finalize().into() - } - - pub fn encrypt( + pub fn encrypt_last_chunk( + self, data: &[u8], - key: &[u8], - aad: &[u8], - header: &Header, - ) -> Result { - // Derive key - let key = Zeroizing::new(CiphertextV2Symmetric::derive_key(key)); - - // Generate nonce - let mut nonce_bytes = [0u8; 24]; - OsRng.fill_bytes(&mut nonce_bytes); - - let nonce = XNonce::from_slice(&nonce_bytes); - - // Authenticate the header - let mut mac_data: Vec = (*header).clone().into(); - mac_data.extend_from_slice(aad); - - let payload = Payload { - msg: data, - aad: &mac_data, - }; - - // Encrypt - let ciphertext = { - let key = Key::from_slice(key.as_slice()); - let cipher = XChaCha20Poly1305::new(key); - cipher.encrypt(nonce, payload)? + header: &Header, + ) -> Result> { + if (data.len() as u64) >= self.chunk_size { + return Err(Error::InvalidChunkLength); }; - Ok(CiphertextV2Symmetric { - nonce: nonce_bytes, - ciphertext, - }) - } - - pub fn decrypt(&self, key: &[u8], aad: &[u8], header: &Header) -> Result> { - // Derive key - let key = Zeroizing::new(CiphertextV2Symmetric::derive_key(key)); - - // Authenticate the header - let mut mac_data: Vec = (*header).clone().into(); - mac_data.extend_from_slice(aad); + let header: [u8; 8] = header.into(); let payload = Payload { - msg: self.ciphertext.as_slice(), - aad: &mac_data, - }; - - let result = { - // Decrypt - let key = Key::from_slice(key.as_slice()); - let nonce = XNonce::from_slice(&self.nonce); - - let cipher = XChaCha20Poly1305::new(key); - cipher.decrypt(nonce, payload)? + msg: data, + aad: header.as_slice(), }; - Ok(result) - } -} - -impl From for Vec { - fn from(cipher: CiphertextV2Asymmetric) -> Self { - let mut data = Vec::new(); - let mut public_key = cipher.public_key.as_bytes().to_vec(); - let mut ciphertext = cipher.ciphertext.into(); - data.append(&mut public_key); - data.append(&mut ciphertext); - data + Ok(self.cipher.encrypt_last(payload)?) } -} -impl TryFrom<&[u8]> for CiphertextV2Asymmetric { - type Error = Error; - fn try_from(data: &[u8]) -> Result { - if data.len() <= 32 { - return Err(Error::InvalidLength); + pub fn encrypt_last_in_place( + self, + data: &mut Vec, + header: &Header, + ) -> Result<()> { + if (data.len() as u64) != self.chunk_size { + return Err(Error::InvalidChunkLength); }; - let mut public_key = [0u8; 32]; - - public_key.copy_from_slice(&data[0..32]); - let ciphertext = CiphertextV2Symmetric::try_from(&data[32..])?; - - Ok(CiphertextV2Asymmetric { - public_key: x25519_dalek::PublicKey::from(public_key), - ciphertext, - }) - } -} - -impl CiphertextV2Asymmetric { - pub fn encrypt( - data: &[u8], - public_key: &PublicKey, - aad: &[u8], - header: &Header, - ) -> Result { - let public_key = x25519_dalek::PublicKey::from(public_key); - - let ephemeral_private_key = StaticSecret::random_from_rng(rand_core::OsRng); - let ephemeral_public_key = x25519_dalek::PublicKey::from(&ephemeral_private_key); - - let key = ephemeral_private_key.diffie_hellman(&public_key); - - let ciphertext = CiphertextV2Symmetric::encrypt(data, key.as_bytes(), aad, header)?; - - Ok(Self { - public_key: ephemeral_public_key, - ciphertext, - }) - } - - pub fn decrypt( - &self, - private_key: &PrivateKey, - aad: &[u8], - header: &Header, - ) -> Result> { - let private_key = StaticSecret::from(private_key); + let header: [u8; 8] = header.into(); - let key = private_key.diffie_hellman(&self.public_key); + self.cipher.encrypt_last_in_place(&header, data)?; - self.ciphertext.decrypt(key.as_bytes(), aad, header) + Ok(()) } } diff --git a/src/password_hash/mod.rs b/src/password_hash/mod.rs index 0bef56caf..0640236f9 100644 --- a/src/password_hash/mod.rs +++ b/src/password_hash/mod.rs @@ -24,6 +24,7 @@ use super::Result; use password_hash_v1::PasswordHashV1; +use std::borrow::Borrow; use std::convert::TryFrom; #[cfg(feature = "fuzz")] @@ -112,7 +113,7 @@ impl PasswordHash { impl From for Vec { /// Serialize the structure into a `Vec`, for storage, transmission or use in another language. fn from(data: PasswordHash) -> Self { - let mut header: Self = data.header.into(); + let mut header: Self = data.header.borrow().into(); let mut payload: Self = data.payload.into(); header.append(&mut payload); header diff --git a/src/secret_sharing/mod.rs b/src/secret_sharing/mod.rs index 359ba59e9..dc1b44861 100644 --- a/src/secret_sharing/mod.rs +++ b/src/secret_sharing/mod.rs @@ -32,6 +32,7 @@ use super::ShareSubtype; use secret_sharing_v1::ShareV1; +use std::borrow::Borrow; use std::convert::TryFrom; #[cfg(feature = "fuzz")] @@ -150,7 +151,7 @@ where impl From for Vec { /// Serialize the structure into a `Vec`, for storage, transmission or use in another language. fn from(data: Share) -> Self { - let mut header: Self = data.header.into(); + let mut header: Self = data.header.borrow().into(); let mut payload: Self = data.payload.into(); header.append(&mut payload); header diff --git a/src/signature/mod.rs b/src/signature/mod.rs index af852b8ca..85ce2109c 100644 --- a/src/signature/mod.rs +++ b/src/signature/mod.rs @@ -38,6 +38,7 @@ use super::signing_key::{SigningKeyPair, SigningPublicKey}; use signature_v1::SignatureV1; +use std::borrow::Borrow; use std::convert::TryFrom; #[cfg(feature = "fuzz")] @@ -121,7 +122,7 @@ impl Signature { impl From for Vec { /// Serialize the structure into a `Vec`, for storage, transmission or use in another language. fn from(data: Signature) -> Self { - let mut header: Self = data.header.into(); + let mut header: Self = data.header.borrow().into(); let mut payload: Self = data.payload.into(); header.append(&mut payload); header diff --git a/src/signature/signature_v1.rs b/src/signature/signature_v1.rs index c442faadb..79e063765 100644 --- a/src/signature/signature_v1.rs +++ b/src/signature/signature_v1.rs @@ -19,8 +19,12 @@ pub struct SignatureV1 { #[cfg(feature = "fuzz")] impl Arbitrary for SignatureV1 { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { - let signature: [u8; 32] = Arbitrary::arbitrary(u)?; - Ok(Self { signature }) + let mut signature = [0u8; 64]; + u.fill_buffer(&mut signature)?; + + Ok(Self { + signature: Signature::from_bytes(&signature), + }) } } diff --git a/src/signing_key/mod.rs b/src/signing_key/mod.rs index 924cc8b29..d926fecb3 100644 --- a/src/signing_key/mod.rs +++ b/src/signing_key/mod.rs @@ -33,6 +33,7 @@ pub use super::SigningKeyVersion; use signing_key_v1::{SigningKeyV1Pair, SigningKeyV1Public}; +use std::borrow::Borrow; use std::convert::TryFrom; #[cfg(feature = "fuzz")] @@ -153,7 +154,7 @@ impl SigningKeyPair { impl From for Vec { /// Serialize the structure into a `Vec`, for storage, transmission or use in another language. fn from(data: SigningPublicKey) -> Self { - let mut header: Self = data.header.into(); + let mut header: Self = data.header.borrow().into(); let mut payload: Self = data.payload.into(); header.append(&mut payload); header @@ -189,7 +190,7 @@ impl TryFrom<&[u8]> for SigningPublicKey { impl From for Vec { /// Serialize the structure into a `Vec`, for storage, transmission or use in another language. fn from(data: SigningKeyPair) -> Self { - let mut header: Self = data.header.into(); + let mut header: Self = data.header.borrow().into(); let mut payload: Self = data.payload.into(); header.append(&mut payload); header diff --git a/src/signing_key/signing_key_v1.rs b/src/signing_key/signing_key_v1.rs index b032850ed..95008a1d4 100644 --- a/src/signing_key/signing_key_v1.rs +++ b/src/signing_key/signing_key_v1.rs @@ -31,10 +31,13 @@ impl core::fmt::Debug for SigningKeyV1Pair { #[cfg(feature = "fuzz")] impl Arbitrary for SigningKeyV1Pair { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { - let keypair: [u8; 64] = Arbitrary::arbitrary(u)?; - Ok(Self { - keypair: Keypair::from(private_key), - }) + let mut keypair = [0u8; 64]; + u.fill_buffer(&mut keypair)?; + + match SigningKey::from_keypair_bytes(&keypair) { + Ok(keypair) => Ok(Self { keypair }), + Err(_) => Err(arbitrary::Error::IncorrectFormat), + } } } @@ -47,9 +50,10 @@ pub struct SigningKeyV1Public { impl Arbitrary for SigningKeyV1Public { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let public_key: [u8; 32] = Arbitrary::arbitrary(u)?; - Ok(Self { - key: PublicKey::from(public_key), - }) + match VerifyingKey::from_bytes(&public_key) { + Ok(key) => Ok(Self { key }), + Err(_) => Err(arbitrary::Error::IncorrectFormat), + } } } diff --git a/src/utils.rs b/src/utils.rs index cd15e262a..5d420f7c9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -119,7 +119,9 @@ pub fn validate_header(data: &[u8], data_type: DataType) -> bool { } DataType::Share => Header::::try_from(&data[0..Header::len()]).is_ok(), DataType::Signature => Header::::try_from(&data[0..Header::len()]).is_ok(), - DataType::OnlineCiphertext => Header::::try_from(&data[0..Header::len()]).is_ok(), + DataType::OnlineCiphertext => { + Header::::try_from(&data[0..Header::len()]).is_ok() + } } } From 59e4c5aa07a9e477565181120a76ee747da48dd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Dugr=C3=A9?= Date: Mon, 9 Dec 2024 14:31:40 -0500 Subject: [PATCH 03/21] [STREAM] Initial V1 implementation --- Cargo.lock | 1 + Cargo.toml | 1 + src/online_ciphertext/online_ciphertext_v1.rs | 291 +++++++++++++----- 3 files changed, 219 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a09da85e..a11b479f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -521,6 +521,7 @@ dependencies = [ "hmac", "js-sys", "num_enum", + "paste", "pbkdf2", "rand", "rand_core", diff --git a/Cargo.toml b/Cargo.toml index 57ae156c3..b031c24a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ x25519-dalek = { version = "2", features = [ "static_secrets" ] } # that isn't compatible with the header trait arbitrary = { version = "0.4.7", features = ["derive"], optional = true } blake3 = { version = "1.5.5", features = ["zeroize"] } +paste = "1.0.15" [target.'cfg(target_arch="wasm32")'.dependencies] rust-argon2 = { version = "1.0", default-features = false } diff --git a/src/online_ciphertext/online_ciphertext_v1.rs b/src/online_ciphertext/online_ciphertext_v1.rs index 68958d2a1..a14b2e6de 100644 --- a/src/online_ciphertext/online_ciphertext_v1.rs +++ b/src/online_ciphertext/online_ciphertext_v1.rs @@ -2,46 +2,164 @@ use super::{PrivateKey, PublicKey}; use super::Error; -use super::Header; use super::Result; -use super::OnlineCiphertext; +use std::borrow::Borrow; -use std::convert::TryFrom; -use std::ops::Sub; - -use aead::generic_array::GenericArray; -use aead::stream::{NewStream, StreamLE31, StreamPrimitive}; -use aead::AeadCore; use chacha20poly1305::aead::{ stream::{DecryptorLE31, EncryptorLE31}, - Aead, Payload, + Payload, }; -use chacha20poly1305::{Key, KeyInit, XChaCha20Poly1305, XNonce}; +use chacha20poly1305::{KeyInit, XChaCha20Poly1305}; use rand::{rngs::OsRng, RngCore}; use x25519_dalek::StaticSecret; -use zeroize::{Zeroize, Zeroizing}; +use zeroize::Zeroizing; -#[cfg(feature = "fuzz")] -use arbitrary::Arbitrary; +use paste::paste; const CONTEXT: &'static str = "devolutions_crypto online_ciphertext_v1"; -pub struct OnlineCiphertextV1Encryptor { +pub struct OnlineCiphertextV1HeaderSymmetric { chunk_size: u64, - cipher: EncryptorLE31, + nonce: [u8; 20], +} + +impl From<&OnlineCiphertextV1HeaderSymmetric> for Vec { + fn from(value: &OnlineCiphertextV1HeaderSymmetric) -> Self { + let mut buf = value.chunk_size.to_le_bytes().to_vec(); + buf.extend(value.nonce); + + buf + } } -pub struct OnlineCiphertextV1Decryptor { +pub struct OnlineCiphertextV1HeaderAsymmetric { chunk_size: u64, - cipher: DecryptorLE31, + nonce: [u8; 20], + public_key: x25519_dalek::PublicKey, +} + +impl From<&OnlineCiphertextV1HeaderAsymmetric> for Vec { + fn from(value: &OnlineCiphertextV1HeaderAsymmetric) -> Self { + let mut buf = value.chunk_size.to_le_bytes().to_vec(); + buf.extend(value.nonce); + buf.extend_from_slice(value.public_key.as_bytes()); + + buf + } } -trait NewOnlineCiphertextV1 { - type Ciphertext: NewStream>; +macro_rules! online_ciphertext_impl { + ($struct_name:ident, $cipher_name:ident, $func:ident) => { + pub struct $struct_name { + chunk_size: u64, + aad: Vec, + cipher: $cipher_name, + } + + impl $struct_name { + pub fn get_chunk_size(&self) -> u64 { + self.chunk_size + } + + paste! { + pub fn [<$func _chunk>]( + &mut self, + data: &[u8], + aad: &[u8], + ) -> Result> { + if (data.len() as u64) != self.chunk_size { + return Err(Error::InvalidChunkLength); + }; + + let mut full_aad = self.aad.to_vec(); + + if !aad.is_empty() { + full_aad.extend_from_slice(aad); + }; + + let payload = Payload { + msg: &data, + aad: &full_aad, + }; + + Ok(self.cipher.[<$func _next>](payload)?) + } + + pub fn [<$func _in_place>]( + &mut self, + data: &mut Vec, + aad: &[u8], + ) -> Result<()> { + if (data.len() as u64) != self.chunk_size { + return Err(Error::InvalidChunkLength); + }; + + let mut full_aad = self.aad.to_vec(); + + if !aad.is_empty() { + full_aad.extend_from_slice(aad); + }; + + self.cipher.[<$func _next_in_place>](&full_aad, data)?; + + Ok(()) + } + + pub fn [<$func _last_chunk>]( + self, + data: &[u8], + aad: &[u8], + ) -> Result> { + if (data.len() as u64) != self.chunk_size { + return Err(Error::InvalidChunkLength); + }; + + let mut full_aad = self.aad.to_vec(); + + if !aad.is_empty() { + full_aad.extend_from_slice(aad); + }; + + let payload = Payload { + msg: &data, + aad: &full_aad, + }; + + Ok(self.cipher.[<$func _last>](payload)?) + } + + pub fn [<$func _last_in_place>]( + self, + data: &mut Vec, + aad: &[u8], + ) -> Result<()> { + if (data.len() as u64) != self.chunk_size { + return Err(Error::InvalidChunkLength); + }; + + let mut full_aad = self.aad.to_vec(); + + if !aad.is_empty() { + full_aad.extend_from_slice(aad); + }; + + self.cipher.[<$func _last_in_place>](&full_aad, data)?; + + Ok(()) + } + } + } + }; +} - fn new(key: &[u8], chunk_size: u64) -> (Self, [u8; 20]) { +impl OnlineCiphertextV1Encryptor { + pub fn new( + key: &[u8], + mut aad: Vec, + chunk_size: u64, + ) -> (Self, OnlineCiphertextV1HeaderSymmetric) { // Generate a new nonce let mut nonce = [0u8; 20]; OsRng.fill_bytes(&mut nonce); @@ -53,86 +171,111 @@ trait NewOnlineCiphertextV1 { // Create the STREAM encryptor let cipher = EncryptorLE31::from_aead(cipher, &nonce.into()); - (Self { chunk_size, cipher }, nonce) + // Create aad + let header = OnlineCiphertextV1HeaderSymmetric { chunk_size, nonce }; + + let mut header_bytes: Vec = header.borrow().into(); + aad.append(&mut header_bytes); + + ( + Self { + chunk_size, + aad, + cipher, + }, + header, + ) } -} -impl OnlineCiphertextV1Encryptor { - pub fn new(key: &[u8], chunk_size: u64) -> (Self, [u8; 20]) { + pub fn new_asymmetric( + public_key: &PublicKey, + mut aad: Vec, + chunk_size: u64, + ) -> (Self, OnlineCiphertextV1HeaderAsymmetric) { + // Perform a ECDH exchange as per ECIES + let public_key = x25519_dalek::PublicKey::from(public_key); + + let ephemeral_private_key = StaticSecret::random_from_rng(rand_core::OsRng); + let ephemeral_public_key = x25519_dalek::PublicKey::from(&ephemeral_private_key); + + let key = ephemeral_private_key.diffie_hellman(&public_key); + // Generate a new nonce let mut nonce = [0u8; 20]; OsRng.fill_bytes(&mut nonce); // Derive the key - let key = Zeroizing::new(blake3::derive_key(CONTEXT, key)); + let key = Zeroizing::new(blake3::derive_key(CONTEXT, key.as_bytes())); let cipher = XChaCha20Poly1305::new(key.as_ref().into()); // Create the STREAM encryptor let cipher = EncryptorLE31::from_aead(cipher, &nonce.into()); - (Self { chunk_size, cipher }, nonce) - } + let header = OnlineCiphertextV1HeaderAsymmetric { + chunk_size, + nonce, + public_key: ephemeral_public_key, + }; - pub fn encrypt_chunk( - &mut self, - data: &[u8], - header: &Header, - ) -> Result> { - let mut data = data.to_vec(); + let mut header_bytes: Vec = header.borrow().into(); + aad.append(&mut header_bytes); - self.encrypt_in_place(&mut data, header)?; + let encryptor = Self { + chunk_size, + cipher, + aad, + }; - Ok(data) + (encryptor, header) } +} - pub fn encrypt_in_place( - &mut self, - data: &mut Vec, - header: &Header, - ) -> Result<()> { - if (data.len() as u64) != self.chunk_size { - return Err(Error::InvalidChunkLength); - }; +impl OnlineCiphertextV1Decryptor { + pub fn new(key: &[u8], mut aad: Vec, header: &OnlineCiphertextV1HeaderSymmetric) -> Self { + // Derive the key + let key = Zeroizing::new(blake3::derive_key(CONTEXT, key)); + let cipher = XChaCha20Poly1305::new(key.as_ref().into()); - let header: [u8; 8] = header.into(); + // Create the STREAM decryptor + let cipher = DecryptorLE31::from_aead(cipher, &header.nonce.into()); - self.cipher.encrypt_next_in_place(&header, data)?; + let mut header_bytes: Vec = header.borrow().into(); + aad.append(&mut header_bytes); - Ok(()) + Self { + chunk_size: header.chunk_size, + aad, + cipher, + } } - pub fn encrypt_last_chunk( - self, - data: &[u8], - header: &Header, - ) -> Result> { - if (data.len() as u64) >= self.chunk_size { - return Err(Error::InvalidChunkLength); - }; - - let header: [u8; 8] = header.into(); + pub fn new_asymmetric( + private_key: &PrivateKey, + mut aad: Vec, + header: &OnlineCiphertextV1HeaderAsymmetric, + ) -> Self { + // Perform a ECDH exchange as per ECIES + let private_key = x25519_dalek::StaticSecret::from(private_key); - let payload = Payload { - msg: data, - aad: header.as_slice(), - }; - - Ok(self.cipher.encrypt_last(payload)?) - } + let key = private_key.diffie_hellman(&header.public_key); - pub fn encrypt_last_in_place( - self, - data: &mut Vec, - header: &Header, - ) -> Result<()> { - if (data.len() as u64) != self.chunk_size { - return Err(Error::InvalidChunkLength); - }; + // Derive the key + let key = Zeroizing::new(blake3::derive_key(CONTEXT, key.as_bytes())); + let cipher = XChaCha20Poly1305::new(key.as_ref().into()); - let header: [u8; 8] = header.into(); + // Create the STREAM decryptor + let cipher = DecryptorLE31::from_aead(cipher, &header.nonce.into()); - self.cipher.encrypt_last_in_place(&header, data)?; + let mut header_bytes: Vec = header.borrow().into(); + aad.append(&mut header_bytes); - Ok(()) + Self { + chunk_size: header.chunk_size, + aad, + cipher, + } } } + +online_ciphertext_impl!(OnlineCiphertextV1Encryptor, EncryptorLE31, encrypt); +online_ciphertext_impl!(OnlineCiphertextV1Decryptor, DecryptorLE31, decrypt); From 9d45af2e534ee422f79ef5ee0eac731e59bc393f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Dugr=C3=A9?= Date: Mon, 9 Dec 2024 15:54:12 -0500 Subject: [PATCH 04/21] [STREAM] Generic bindings --- src/online_ciphertext/mod.rs | 213 ++++++++++++++++-- src/online_ciphertext/online_ciphertext_v1.rs | 36 +-- src/utils.rs | 4 +- 3 files changed, 220 insertions(+), 33 deletions(-) diff --git a/src/online_ciphertext/mod.rs b/src/online_ciphertext/mod.rs index cbf83f2e6..3d877b158 100644 --- a/src/online_ciphertext/mod.rs +++ b/src/online_ciphertext/mod.rs @@ -42,6 +42,8 @@ mod online_ciphertext_v1; +use std::borrow::Borrow; + use super::CiphertextSubtype; use super::DataType; use super::Error; @@ -53,23 +55,20 @@ use super::Result; use super::key::{PrivateKey, PublicKey}; use online_ciphertext_v1::{ - OnlineCiphertextV1Asymmetric, OnlineCiphertextV1Engine, OnlineCiphertextV1Symmetric, + OnlineCiphertextV1Decryptor, OnlineCiphertextV1Encryptor, OnlineCiphertextV1HeaderAsymmetric, + OnlineCiphertextV1HeaderSymmetric, }; -use std::convert::TryFrom; - -#[cfg(feature = "fuzz")] -use arbitrary::Arbitrary; +use paste::paste; /// A versionned online ciphertext. Can be either symmetric or asymmetric. #[derive(Clone, Debug)] -#[cfg_attr(feature = "fuzz", derive(Arbitrary))] -pub struct OnlineCiphertext { - pub(crate) header: Header, - payload: OnlineCiphertextPayload, +pub struct OnlineCiphertextHeader { + pub(crate) header: Header, + payload: OnlineCiphertextHeaderPayload, } -impl HeaderType for OnlineCiphertext { +impl HeaderType for OnlineCiphertextHeader { type Version = OnlineCiphertextVersion; type Subtype = CiphertextSubtype; @@ -78,9 +77,195 @@ impl HeaderType for OnlineCiphertext { } } +pub fn new_encryptor( + key: &[u8], + aad: &[u8], + chunk_size: u32, + version: OnlineCiphertextVersion, +) -> (OnlineCiphertextEncryptor, OnlineCiphertextHeader) { + let mut header = Header { + data_subtype: CiphertextSubtype::Symmetric, + ..Default::default() + }; + + let mut full_aad: Vec = header.borrow().into(); + full_aad.extend_from_slice(aad); + + match version { + OnlineCiphertextVersion::V1 | OnlineCiphertextVersion::Latest => { + header.version = OnlineCiphertextVersion::V1; + + let (encryptor, inner_header) = + OnlineCiphertextV1Encryptor::new(key, full_aad, chunk_size); + + ( + OnlineCiphertextEncryptor { + header: header.clone(), + cipher: OnlineCiphertextEncryptorPayload::V1(encryptor), + }, + OnlineCiphertextHeader { + header, + payload: OnlineCiphertextHeaderPayload::V1Symmetric(inner_header), + }, + ) + } + } +} + +pub fn new_encryptor_asymmetric( + public_key: &PublicKey, + aad: &[u8], + chunk_size: u32, + version: OnlineCiphertextVersion, +) -> (OnlineCiphertextEncryptor, OnlineCiphertextHeader) { + let mut header = Header { + data_subtype: CiphertextSubtype::Asymmetric, + ..Default::default() + }; + + let mut full_aad: Vec = header.borrow().into(); + full_aad.extend_from_slice(aad); + + match version { + OnlineCiphertextVersion::V1 | OnlineCiphertextVersion::Latest => { + header.version = OnlineCiphertextVersion::V1; + + let (encryptor, inner_header) = + OnlineCiphertextV1Encryptor::new_asymmetric(public_key, full_aad, chunk_size); + + ( + OnlineCiphertextEncryptor { + header: header.clone(), + cipher: OnlineCiphertextEncryptorPayload::V1(encryptor), + }, + OnlineCiphertextHeader { + header, + payload: OnlineCiphertextHeaderPayload::V1Asymmetric(inner_header), + }, + ) + } + } +} + +impl OnlineCiphertextHeader { + pub fn get_decryptor(&self, key: &[u8], aad: &[u8]) -> Result { + let mut full_aad: Vec = self.header.borrow().into(); + full_aad.extend_from_slice(aad); + + match &self.payload { + OnlineCiphertextHeaderPayload::V1Symmetric(header) => { + let cipher = OnlineCiphertextV1Decryptor::new(key, full_aad, &header); + + Ok(OnlineCiphertextDecryptor { + header: self.header.clone(), + cipher: OnlineCiphertextDecryptorPayload::V1(cipher), + }) + } + OnlineCiphertextHeaderPayload::V1Asymmetric(_) => Err(Error::InvalidDataType), + } + } + + pub fn get_decryptor_asymmetric( + &self, + private_key: &PrivateKey, + aad: &[u8], + ) -> Result { + let mut full_aad: Vec = self.header.borrow().into(); + full_aad.extend_from_slice(aad); + + match &self.payload { + OnlineCiphertextHeaderPayload::V1Symmetric(_) => Err(Error::InvalidDataType), + OnlineCiphertextHeaderPayload::V1Asymmetric(header) => { + let cipher = + OnlineCiphertextV1Decryptor::new_asymmetric(private_key, full_aad, &header); + + Ok(OnlineCiphertextDecryptor { + header: self.header.clone(), + cipher: OnlineCiphertextDecryptorPayload::V1(cipher), + }) + } + } + } +} + +macro_rules! online_ciphertext_impl { + ($name:ident, $v1_name:ident, $func:ident) => { + paste! { + enum [<$name Payload>] { + V1($v1_name), + } + + pub struct $name { + pub(crate) header: Header, + cipher: [<$name Payload>], + } + + impl $name { + pub fn [<$func _chunk>]( + &mut self, + data: &[u8], + aad: &[u8], + ) -> Result> { + match &mut self.cipher { + [<$name Payload>]::V1(cipher) => { + cipher.[<$func _chunk>](data, aad) + } + } + } + + pub fn [<$func _chunk_in_place>]( + &mut self, + data: &mut Vec, + aad: &[u8], + ) -> Result<()> { + match &mut self.cipher { + [<$name Payload>]::V1(cipher) => { + cipher.[<$func _chunk_in_place>](data, aad) + } + } + } + + pub fn [<$func _last>]( + self, + data: &[u8], + aad: &[u8], + ) -> Result> { + match self.cipher { + [<$name Payload>]::V1(cipher) => { + cipher.[<$func _last>](data, aad) + } + } + } + + pub fn [<$func _last_in_place>]( + self, + data: &mut Vec, + aad: &[u8], + ) -> Result<()> { + match self.cipher { + [<$name Payload>]::V1(cipher) => { + cipher.[<$func _last_in_place>](data, aad) + } + } + } + } + } + }; +} + +online_ciphertext_impl!( + OnlineCiphertextEncryptor, + OnlineCiphertextV1Encryptor, + encrypt +); +online_ciphertext_impl!( + OnlineCiphertextDecryptor, + OnlineCiphertextV1Decryptor, + decrypt +); + #[derive(Clone, Debug)] -#[cfg_attr(feature = "fuzz", derive(Arbitrary))] -enum OnlineCiphertextPayload { - V1Symmetric(OnlineCiphertextV1Symmetric), - V1Asymmetric(OnlineCiphertextV1Asymmetric), +enum OnlineCiphertextHeaderPayload { + V1Symmetric(OnlineCiphertextV1HeaderSymmetric), + V1Asymmetric(OnlineCiphertextV1HeaderAsymmetric), } diff --git a/src/online_ciphertext/online_ciphertext_v1.rs b/src/online_ciphertext/online_ciphertext_v1.rs index a14b2e6de..9314c4924 100644 --- a/src/online_ciphertext/online_ciphertext_v1.rs +++ b/src/online_ciphertext/online_ciphertext_v1.rs @@ -20,11 +20,19 @@ use paste::paste; const CONTEXT: &'static str = "devolutions_crypto online_ciphertext_v1"; +#[derive(Clone, Debug)] pub struct OnlineCiphertextV1HeaderSymmetric { - chunk_size: u64, + chunk_size: u32, nonce: [u8; 20], } +#[derive(Clone, Debug)] +pub struct OnlineCiphertextV1HeaderAsymmetric { + chunk_size: u32, + nonce: [u8; 20], + public_key: x25519_dalek::PublicKey, +} + impl From<&OnlineCiphertextV1HeaderSymmetric> for Vec { fn from(value: &OnlineCiphertextV1HeaderSymmetric) -> Self { let mut buf = value.chunk_size.to_le_bytes().to_vec(); @@ -34,12 +42,6 @@ impl From<&OnlineCiphertextV1HeaderSymmetric> for Vec { } } -pub struct OnlineCiphertextV1HeaderAsymmetric { - chunk_size: u64, - nonce: [u8; 20], - public_key: x25519_dalek::PublicKey, -} - impl From<&OnlineCiphertextV1HeaderAsymmetric> for Vec { fn from(value: &OnlineCiphertextV1HeaderAsymmetric) -> Self { let mut buf = value.chunk_size.to_le_bytes().to_vec(); @@ -53,13 +55,13 @@ impl From<&OnlineCiphertextV1HeaderAsymmetric> for Vec { macro_rules! online_ciphertext_impl { ($struct_name:ident, $cipher_name:ident, $func:ident) => { pub struct $struct_name { - chunk_size: u64, + chunk_size: u32, aad: Vec, cipher: $cipher_name, } impl $struct_name { - pub fn get_chunk_size(&self) -> u64 { + pub fn get_chunk_size(&self) -> u32 { self.chunk_size } @@ -69,7 +71,7 @@ macro_rules! online_ciphertext_impl { data: &[u8], aad: &[u8], ) -> Result> { - if (data.len() as u64) != self.chunk_size { + if (data.len() as u32) != self.chunk_size { return Err(Error::InvalidChunkLength); }; @@ -87,12 +89,12 @@ macro_rules! online_ciphertext_impl { Ok(self.cipher.[<$func _next>](payload)?) } - pub fn [<$func _in_place>]( + pub fn [<$func _chunk_in_place>]( &mut self, data: &mut Vec, aad: &[u8], ) -> Result<()> { - if (data.len() as u64) != self.chunk_size { + if (data.len() as u32) != self.chunk_size { return Err(Error::InvalidChunkLength); }; @@ -107,12 +109,12 @@ macro_rules! online_ciphertext_impl { Ok(()) } - pub fn [<$func _last_chunk>]( + pub fn [<$func _last>]( self, data: &[u8], aad: &[u8], ) -> Result> { - if (data.len() as u64) != self.chunk_size { + if (data.len() as u32) != self.chunk_size { return Err(Error::InvalidChunkLength); }; @@ -135,7 +137,7 @@ macro_rules! online_ciphertext_impl { data: &mut Vec, aad: &[u8], ) -> Result<()> { - if (data.len() as u64) != self.chunk_size { + if (data.len() as u32) != self.chunk_size { return Err(Error::InvalidChunkLength); }; @@ -158,7 +160,7 @@ impl OnlineCiphertextV1Encryptor { pub fn new( key: &[u8], mut aad: Vec, - chunk_size: u64, + chunk_size: u32, ) -> (Self, OnlineCiphertextV1HeaderSymmetric) { // Generate a new nonce let mut nonce = [0u8; 20]; @@ -190,7 +192,7 @@ impl OnlineCiphertextV1Encryptor { pub fn new_asymmetric( public_key: &PublicKey, mut aad: Vec, - chunk_size: u64, + chunk_size: u32, ) -> (Self, OnlineCiphertextV1HeaderAsymmetric) { // Perform a ECDH exchange as per ECIES let public_key = x25519_dalek::PublicKey::from(public_key); diff --git a/src/utils.rs b/src/utils.rs index 5d420f7c9..3d1584d26 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -7,7 +7,7 @@ use rand::{rngs::OsRng, RngCore}; use sha2::Sha256; use subtle::ConstantTimeEq as _; -use crate::online_ciphertext::OnlineCiphertext; +use crate::online_ciphertext::OnlineCiphertextHeader; use super::Argon2Parameters; use super::DataType; @@ -120,7 +120,7 @@ pub fn validate_header(data: &[u8], data_type: DataType) -> bool { DataType::Share => Header::::try_from(&data[0..Header::len()]).is_ok(), DataType::Signature => Header::::try_from(&data[0..Header::len()]).is_ok(), DataType::OnlineCiphertext => { - Header::::try_from(&data[0..Header::len()]).is_ok() + Header::::try_from(&data[0..Header::len()]).is_ok() } } } From b72d16bc5bb4f0249530ae8ca21430480e8af2db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Dugr=C3=A9?= Date: Mon, 9 Dec 2024 16:02:22 -0500 Subject: [PATCH 05/21] [STREAM] Fix warnings --- cli/src/main.rs | 4 ++-- fuzz/fuzz_targets/utils/derive_key.rs | 4 ++-- src/online_ciphertext/mod.rs | 6 ++++++ src/online_ciphertext/online_ciphertext_v1.rs | 4 ++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 73361cd9a..47fe9128d 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,5 +1,5 @@ use clap::{Parser, Subcommand}; -use std::convert::TryFrom; +use std::{borrow::Borrow, convert::TryFrom}; /// Gives a CLI interface to Devolutions Crypto Library #[derive(Debug, Parser)] @@ -243,7 +243,7 @@ fn generate_argon2parameters( parameters.length = length; }; - let parameters: Vec = parameters.into(); + let parameters: Vec = parameters.borrow().into(); println!("{}", base64::encode(¶meters)); } diff --git a/fuzz/fuzz_targets/utils/derive_key.rs b/fuzz/fuzz_targets/utils/derive_key.rs index fb6d9415e..167344460 100644 --- a/fuzz/fuzz_targets/utils/derive_key.rs +++ b/fuzz/fuzz_targets/utils/derive_key.rs @@ -2,7 +2,7 @@ use arbitrary::Arbitrary; use libfuzzer_sys::fuzz_target; -use devolutions_crypto::utils::derive_key; +use devolutions_crypto::utils::derive_key_pbkdf2; #[derive(Arbitrary, Clone, Debug)] struct Input { @@ -12,5 +12,5 @@ struct Input { } fuzz_target!(|data: Input| { - let _ = derive_key(&data.input, &data.salt, 10, data.length.into()); + let _ = derive_key_pbkdf2(&data.input, &data.salt, 10, data.length.into()); }); diff --git a/src/online_ciphertext/mod.rs b/src/online_ciphertext/mod.rs index 3d877b158..d54175d2c 100644 --- a/src/online_ciphertext/mod.rs +++ b/src/online_ciphertext/mod.rs @@ -201,6 +201,12 @@ macro_rules! online_ciphertext_impl { } impl $name { + pub fn get_chunk_size(&self) -> u32 { + match &self.cipher { + [<$name Payload>]::V1(cipher) => cipher.get_chunk_size() + } + } + pub fn [<$func _chunk>]( &mut self, data: &[u8], diff --git a/src/online_ciphertext/online_ciphertext_v1.rs b/src/online_ciphertext/online_ciphertext_v1.rs index 9314c4924..03c4a339d 100644 --- a/src/online_ciphertext/online_ciphertext_v1.rs +++ b/src/online_ciphertext/online_ciphertext_v1.rs @@ -241,7 +241,7 @@ impl OnlineCiphertextV1Decryptor { // Create the STREAM decryptor let cipher = DecryptorLE31::from_aead(cipher, &header.nonce.into()); - let mut header_bytes: Vec = header.borrow().into(); + let mut header_bytes: Vec = header.into(); aad.append(&mut header_bytes); Self { @@ -268,7 +268,7 @@ impl OnlineCiphertextV1Decryptor { // Create the STREAM decryptor let cipher = DecryptorLE31::from_aead(cipher, &header.nonce.into()); - let mut header_bytes: Vec = header.borrow().into(); + let mut header_bytes: Vec = header.into(); aad.append(&mut header_bytes); Self { From 7b77316472e9564e2ac655c1ddf93486307fec1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Dugr=C3=A9?= Date: Mon, 9 Dec 2024 16:13:16 -0500 Subject: [PATCH 06/21] [STREAM] Remove useless info in encryptor/decryptor structs --- src/online_ciphertext/mod.rs | 51 ++++++++++++------------------------ 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/online_ciphertext/mod.rs b/src/online_ciphertext/mod.rs index d54175d2c..f59167d46 100644 --- a/src/online_ciphertext/mod.rs +++ b/src/online_ciphertext/mod.rs @@ -95,14 +95,11 @@ pub fn new_encryptor( OnlineCiphertextVersion::V1 | OnlineCiphertextVersion::Latest => { header.version = OnlineCiphertextVersion::V1; - let (encryptor, inner_header) = + let (cipher, inner_header) = OnlineCiphertextV1Encryptor::new(key, full_aad, chunk_size); ( - OnlineCiphertextEncryptor { - header: header.clone(), - cipher: OnlineCiphertextEncryptorPayload::V1(encryptor), - }, + OnlineCiphertextEncryptor::V1(cipher), OnlineCiphertextHeader { header, payload: OnlineCiphertextHeaderPayload::V1Symmetric(inner_header), @@ -130,14 +127,11 @@ pub fn new_encryptor_asymmetric( OnlineCiphertextVersion::V1 | OnlineCiphertextVersion::Latest => { header.version = OnlineCiphertextVersion::V1; - let (encryptor, inner_header) = + let (cipher, inner_header) = OnlineCiphertextV1Encryptor::new_asymmetric(public_key, full_aad, chunk_size); ( - OnlineCiphertextEncryptor { - header: header.clone(), - cipher: OnlineCiphertextEncryptorPayload::V1(encryptor), - }, + OnlineCiphertextEncryptor::V1(cipher), OnlineCiphertextHeader { header, payload: OnlineCiphertextHeaderPayload::V1Asymmetric(inner_header), @@ -156,10 +150,7 @@ impl OnlineCiphertextHeader { OnlineCiphertextHeaderPayload::V1Symmetric(header) => { let cipher = OnlineCiphertextV1Decryptor::new(key, full_aad, &header); - Ok(OnlineCiphertextDecryptor { - header: self.header.clone(), - cipher: OnlineCiphertextDecryptorPayload::V1(cipher), - }) + Ok(OnlineCiphertextDecryptor::V1(cipher)) } OnlineCiphertextHeaderPayload::V1Asymmetric(_) => Err(Error::InvalidDataType), } @@ -179,10 +170,7 @@ impl OnlineCiphertextHeader { let cipher = OnlineCiphertextV1Decryptor::new_asymmetric(private_key, full_aad, &header); - Ok(OnlineCiphertextDecryptor { - header: self.header.clone(), - cipher: OnlineCiphertextDecryptorPayload::V1(cipher), - }) + Ok(OnlineCiphertextDecryptor::V1(cipher)) } } } @@ -191,19 +179,14 @@ impl OnlineCiphertextHeader { macro_rules! online_ciphertext_impl { ($name:ident, $v1_name:ident, $func:ident) => { paste! { - enum [<$name Payload>] { + pub enum $name { V1($v1_name), } - pub struct $name { - pub(crate) header: Header, - cipher: [<$name Payload>], - } - impl $name { pub fn get_chunk_size(&self) -> u32 { - match &self.cipher { - [<$name Payload>]::V1(cipher) => cipher.get_chunk_size() + match &self { + $name::V1(cipher) => cipher.get_chunk_size() } } @@ -212,8 +195,8 @@ macro_rules! online_ciphertext_impl { data: &[u8], aad: &[u8], ) -> Result> { - match &mut self.cipher { - [<$name Payload>]::V1(cipher) => { + match self { + $name::V1(cipher) => { cipher.[<$func _chunk>](data, aad) } } @@ -224,8 +207,8 @@ macro_rules! online_ciphertext_impl { data: &mut Vec, aad: &[u8], ) -> Result<()> { - match &mut self.cipher { - [<$name Payload>]::V1(cipher) => { + match self { + $name::V1(cipher) => { cipher.[<$func _chunk_in_place>](data, aad) } } @@ -236,8 +219,8 @@ macro_rules! online_ciphertext_impl { data: &[u8], aad: &[u8], ) -> Result> { - match self.cipher { - [<$name Payload>]::V1(cipher) => { + match self { + $name::V1(cipher) => { cipher.[<$func _last>](data, aad) } } @@ -248,8 +231,8 @@ macro_rules! online_ciphertext_impl { data: &mut Vec, aad: &[u8], ) -> Result<()> { - match self.cipher { - [<$name Payload>]::V1(cipher) => { + match self { + $name::V1(cipher) => { cipher.[<$func _last_in_place>](data, aad) } } From 98b8774da55ef335a8f1bb6a6b098330de567430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Dugr=C3=A9?= Date: Mon, 9 Dec 2024 16:31:03 -0500 Subject: [PATCH 07/21] [STREAM] Write macro to remove code duplication --- src/online_ciphertext/mod.rs | 44 ++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/online_ciphertext/mod.rs b/src/online_ciphertext/mod.rs index f59167d46..e6bb25d6b 100644 --- a/src/online_ciphertext/mod.rs +++ b/src/online_ciphertext/mod.rs @@ -177,16 +177,22 @@ impl OnlineCiphertextHeader { } macro_rules! online_ciphertext_impl { - ($name:ident, $v1_name:ident, $func:ident) => { + ($name:ident, $func:ident, $($version_name:ident),+) => { paste! { - pub enum $name { - V1($v1_name), + pub enum [] { + $( + $version_name([]), + ),+ } - impl $name { + impl [] { pub fn get_chunk_size(&self) -> u32 { match &self { - $name::V1(cipher) => cipher.get_chunk_size() + $( + []::$version_name(cipher) => { + cipher.get_chunk_size() + } + ),+ } } @@ -196,9 +202,11 @@ macro_rules! online_ciphertext_impl { aad: &[u8], ) -> Result> { match self { - $name::V1(cipher) => { + $( + []::$version_name(cipher) => { cipher.[<$func _chunk>](data, aad) } + ),+ } } @@ -208,9 +216,11 @@ macro_rules! online_ciphertext_impl { aad: &[u8], ) -> Result<()> { match self { - $name::V1(cipher) => { + $( + []::$version_name(cipher) => { cipher.[<$func _chunk_in_place>](data, aad) } + ),+ } } @@ -220,9 +230,11 @@ macro_rules! online_ciphertext_impl { aad: &[u8], ) -> Result> { match self { - $name::V1(cipher) => { + $( + []::$version_name(cipher) => { cipher.[<$func _last>](data, aad) } + ),+ } } @@ -232,9 +244,11 @@ macro_rules! online_ciphertext_impl { aad: &[u8], ) -> Result<()> { match self { - $name::V1(cipher) => { + $( + []::$version_name(cipher) => { cipher.[<$func _last_in_place>](data, aad) } + ),+ } } } @@ -242,16 +256,8 @@ macro_rules! online_ciphertext_impl { }; } -online_ciphertext_impl!( - OnlineCiphertextEncryptor, - OnlineCiphertextV1Encryptor, - encrypt -); -online_ciphertext_impl!( - OnlineCiphertextDecryptor, - OnlineCiphertextV1Decryptor, - decrypt -); +online_ciphertext_impl!(Encryptor, encrypt, V1); +online_ciphertext_impl!(Decryptor, decrypt, V1); #[derive(Clone, Debug)] enum OnlineCiphertextHeaderPayload { From 3eaf116be5bc4149c0b221acbed664719edc41a0 Mon Sep 17 00:00:00 2001 From: pdugre Date: Wed, 11 Dec 2024 08:12:21 -0500 Subject: [PATCH 08/21] [STREAM] WIP make use of generics --- src/online_ciphertext/mod.rs | 45 ++--- src/online_ciphertext/online_ciphertext_v1.rs | 176 ++++++++++++++---- 2 files changed, 154 insertions(+), 67 deletions(-) diff --git a/src/online_ciphertext/mod.rs b/src/online_ciphertext/mod.rs index e6bb25d6b..3ba22453f 100644 --- a/src/online_ciphertext/mod.rs +++ b/src/online_ciphertext/mod.rs @@ -54,6 +54,7 @@ use super::Result; use super::key::{PrivateKey, PublicKey}; +use online_ciphertext_v1::OnlineCiphertextV1Header; use online_ciphertext_v1::{ OnlineCiphertextV1Decryptor, OnlineCiphertextV1Encryptor, OnlineCiphertextV1HeaderAsymmetric, OnlineCiphertextV1HeaderSymmetric, @@ -82,7 +83,7 @@ pub fn new_encryptor( aad: &[u8], chunk_size: u32, version: OnlineCiphertextVersion, -) -> (OnlineCiphertextEncryptor, OnlineCiphertextHeader) { +) -> OnlineCiphertextEncryptor { let mut header = Header { data_subtype: CiphertextSubtype::Symmetric, ..Default::default() @@ -95,16 +96,9 @@ pub fn new_encryptor( OnlineCiphertextVersion::V1 | OnlineCiphertextVersion::Latest => { header.version = OnlineCiphertextVersion::V1; - let (cipher, inner_header) = - OnlineCiphertextV1Encryptor::new(key, full_aad, chunk_size); + let cipher = OnlineCiphertextV1Encryptor::new(key, full_aad, chunk_size); - ( - OnlineCiphertextEncryptor::V1(cipher), - OnlineCiphertextHeader { - header, - payload: OnlineCiphertextHeaderPayload::V1Symmetric(inner_header), - }, - ) + OnlineCiphertextEncryptor::V1(cipher) } } } @@ -114,7 +108,7 @@ pub fn new_encryptor_asymmetric( aad: &[u8], chunk_size: u32, version: OnlineCiphertextVersion, -) -> (OnlineCiphertextEncryptor, OnlineCiphertextHeader) { +) -> OnlineCiphertextEncryptor { let mut header = Header { data_subtype: CiphertextSubtype::Asymmetric, ..Default::default() @@ -127,28 +121,22 @@ pub fn new_encryptor_asymmetric( OnlineCiphertextVersion::V1 | OnlineCiphertextVersion::Latest => { header.version = OnlineCiphertextVersion::V1; - let (cipher, inner_header) = + let cipher = OnlineCiphertextV1Encryptor::new_asymmetric(public_key, full_aad, chunk_size); - ( - OnlineCiphertextEncryptor::V1(cipher), - OnlineCiphertextHeader { - header, - payload: OnlineCiphertextHeaderPayload::V1Asymmetric(inner_header), - }, - ) + OnlineCiphertextEncryptor::V1(cipher) } } } impl OnlineCiphertextHeader { - pub fn get_decryptor(&self, key: &[u8], aad: &[u8]) -> Result { + pub fn into_decryptor(self, key: &[u8], aad: &[u8]) -> Result { let mut full_aad: Vec = self.header.borrow().into(); full_aad.extend_from_slice(aad); - match &self.payload { + match self.payload { OnlineCiphertextHeaderPayload::V1Symmetric(header) => { - let cipher = OnlineCiphertextV1Decryptor::new(key, full_aad, &header); + let cipher = OnlineCiphertextV1Decryptor::new(key, full_aad, header); Ok(OnlineCiphertextDecryptor::V1(cipher)) } @@ -157,18 +145,18 @@ impl OnlineCiphertextHeader { } pub fn get_decryptor_asymmetric( - &self, + self, private_key: &PrivateKey, aad: &[u8], ) -> Result { let mut full_aad: Vec = self.header.borrow().into(); full_aad.extend_from_slice(aad); - match &self.payload { + match self.payload { OnlineCiphertextHeaderPayload::V1Symmetric(_) => Err(Error::InvalidDataType), OnlineCiphertextHeaderPayload::V1Asymmetric(header) => { let cipher = - OnlineCiphertextV1Decryptor::new_asymmetric(private_key, full_aad, &header); + OnlineCiphertextV1Decryptor::new_asymmetric(private_key, full_aad, header); Ok(OnlineCiphertextDecryptor::V1(cipher)) } @@ -181,7 +169,7 @@ macro_rules! online_ciphertext_impl { paste! { pub enum [] { $( - $version_name([]), + $version_name([]), ),+ } @@ -260,7 +248,6 @@ online_ciphertext_impl!(Encryptor, encrypt, V1); online_ciphertext_impl!(Decryptor, decrypt, V1); #[derive(Clone, Debug)] -enum OnlineCiphertextHeaderPayload { - V1Symmetric(OnlineCiphertextV1HeaderSymmetric), - V1Asymmetric(OnlineCiphertextV1HeaderAsymmetric), +enum OnlineCiphertextHeaderPayload<'a, 'b> { + V1(OnlineCiphertextV1Header<'a, 'b>), } diff --git a/src/online_ciphertext/online_ciphertext_v1.rs b/src/online_ciphertext/online_ciphertext_v1.rs index 03c4a339d..8a9e7c481 100644 --- a/src/online_ciphertext/online_ciphertext_v1.rs +++ b/src/online_ciphertext/online_ciphertext_v1.rs @@ -18,8 +18,18 @@ use zeroize::Zeroizing; use paste::paste; +/// Context string for the Blake3 KDF function. +/// This is used to normalize the key length and domain separation const CONTEXT: &'static str = "devolutions_crypto online_ciphertext_v1"; +pub trait OnlineCiphertextV1Header<'a, 'b>: + 'b + Sized + Clone + std::fmt::Debug + TryFrom<&'a [u8]> +where + &'b Self: Into>, +{ + fn get_chunk_size(&self) -> u32; +} + #[derive(Clone, Debug)] pub struct OnlineCiphertextV1HeaderSymmetric { chunk_size: u32, @@ -34,6 +44,7 @@ pub struct OnlineCiphertextV1HeaderAsymmetric { } impl From<&OnlineCiphertextV1HeaderSymmetric> for Vec { + /// Serialize the header into bytes fn from(value: &OnlineCiphertextV1HeaderSymmetric) -> Self { let mut buf = value.chunk_size.to_le_bytes().to_vec(); buf.extend(value.nonce); @@ -43,6 +54,7 @@ impl From<&OnlineCiphertextV1HeaderSymmetric> for Vec { } impl From<&OnlineCiphertextV1HeaderAsymmetric> for Vec { + /// Serialize the header into bytes fn from(value: &OnlineCiphertextV1HeaderAsymmetric) -> Self { let mut buf = value.chunk_size.to_le_bytes().to_vec(); buf.extend(value.nonce); @@ -52,26 +64,117 @@ impl From<&OnlineCiphertextV1HeaderAsymmetric> for Vec { } } +impl TryFrom<&[u8]> for OnlineCiphertextV1HeaderSymmetric { + type Error = Error; + + /// Parse a header from a byte array + fn try_from(value: &[u8]) -> Result { + if value.len() != 24 { + return Err(Error::InvalidLength); + } + + let (chunk_size, nonce) = value.split_at(4); + + let chunk_size: [u8; 4] = chunk_size + .try_into() + .expect("size is hardcoded and should always be right"); + let chunk_size = u32::from_le_bytes(chunk_size); + + let nonce = nonce + .try_into() + .expect("Length is checked at the start of the function"); + + Ok(Self { chunk_size, nonce }) + } +} + +impl TryFrom<&[u8]> for OnlineCiphertextV1HeaderAsymmetric { + type Error = Error; + + /// Parse a header from a byte array + fn try_from(value: &[u8]) -> Result { + if value.len() != 24 + 32 { + return Err(Error::InvalidLength); + } + + let (chunk_size, value) = value.split_at(4); + + let chunk_size: [u8; 4] = chunk_size + .try_into() + .expect("size is hardcoded and should always be right"); + let chunk_size = u32::from_le_bytes(chunk_size); + + let (nonce, public_key) = value.split_at(20); + + let nonce = nonce + .try_into() + .expect("size is hardcoded and should always be right"); + + let public_key: [u8; 32] = public_key + .try_into() + .expect("size is checked at the start of the function"); + let public_key = + x25519_dalek::PublicKey::try_from(public_key).map_err(|_| Error::InvalidData)?; + + Ok(Self { + chunk_size, + nonce, + public_key, + }) + } +} + +impl OnlineCiphertextV1Header<'_, '_> for OnlineCiphertextV1HeaderSymmetric { + fn get_chunk_size(&self) -> u32 { + self.chunk_size + } +} + +impl OnlineCiphertextV1Header<'_, '_> for OnlineCiphertextV1HeaderAsymmetric { + fn get_chunk_size(&self) -> u32 { + self.chunk_size + } +} + +/// Implements the encryptor/decryptor structure macro_rules! online_ciphertext_impl { ($struct_name:ident, $cipher_name:ident, $func:ident) => { - pub struct $struct_name { - chunk_size: u32, + pub struct $struct_name + where + for<'a, 'b> H: OnlineCiphertextV1Header<'a, 'b>, + { + header: H, aad: Vec, cipher: $cipher_name, } - impl $struct_name { + impl $struct_name + where + for<'a, 'b> H: OnlineCiphertextV1Header<'a, 'b> + + std::fmt::Debug + + Sized + + Clone + + std::fmt::Debug + + TryFrom<&'a [u8]>, + for<'b> &'b H: Into>, + { + /// Gets the number of bytes to process in each chunk pub fn get_chunk_size(&self) -> u32 { - self.chunk_size + self.header.get_chunk_size() + } + + pub fn get_header(&self) -> &H { + &self.header } paste! { + /// Process a single chunk pub fn [<$func _chunk>]( &mut self, data: &[u8], aad: &[u8], ) -> Result> { - if (data.len() as u32) != self.chunk_size { + if (data.len() as u32) != self.get_chunk_size() { return Err(Error::InvalidChunkLength); }; @@ -89,12 +192,14 @@ macro_rules! online_ciphertext_impl { Ok(self.cipher.[<$func _next>](payload)?) } + /// Process a single chunk in place. + /// Requires a Vec because it needs to be expandable to accomodate the tag. pub fn [<$func _chunk_in_place>]( &mut self, data: &mut Vec, aad: &[u8], ) -> Result<()> { - if (data.len() as u32) != self.chunk_size { + if (data.len() as u32) != self.get_chunk_size() { return Err(Error::InvalidChunkLength); }; @@ -109,12 +214,13 @@ macro_rules! online_ciphertext_impl { Ok(()) } + /// Process the last chunk. pub fn [<$func _last>]( self, data: &[u8], aad: &[u8], ) -> Result> { - if (data.len() as u32) != self.chunk_size { + if (data.len() as u32) != self.get_chunk_size() { return Err(Error::InvalidChunkLength); }; @@ -132,12 +238,14 @@ macro_rules! online_ciphertext_impl { Ok(self.cipher.[<$func _last>](payload)?) } + /// Process a single chunk in place. + /// Requires a Vec because it needs to be expandable to accomodate the tag. pub fn [<$func _last_in_place>]( self, data: &mut Vec, aad: &[u8], ) -> Result<()> { - if (data.len() as u32) != self.chunk_size { + if (data.len() as u32) != self.get_chunk_size() { return Err(Error::InvalidChunkLength); }; @@ -156,12 +264,9 @@ macro_rules! online_ciphertext_impl { }; } -impl OnlineCiphertextV1Encryptor { - pub fn new( - key: &[u8], - mut aad: Vec, - chunk_size: u32, - ) -> (Self, OnlineCiphertextV1HeaderSymmetric) { +impl OnlineCiphertextV1Encryptor { + /// Creates a new encryptor and the corresponding header + pub fn new(key: &[u8], mut aad: Vec, chunk_size: u32) -> Self { // Generate a new nonce let mut nonce = [0u8; 20]; OsRng.fill_bytes(&mut nonce); @@ -179,21 +284,16 @@ impl OnlineCiphertextV1Encryptor { let mut header_bytes: Vec = header.borrow().into(); aad.append(&mut header_bytes); - ( - Self { - chunk_size, - aad, - cipher, - }, + Self { header, - ) + aad, + cipher, + } } +} - pub fn new_asymmetric( - public_key: &PublicKey, - mut aad: Vec, - chunk_size: u32, - ) -> (Self, OnlineCiphertextV1HeaderAsymmetric) { +impl OnlineCiphertextV1Encryptor { + pub fn new_asymmetric(public_key: &PublicKey, mut aad: Vec, chunk_size: u32) -> Self { // Perform a ECDH exchange as per ECIES let public_key = x25519_dalek::PublicKey::from(public_key); @@ -222,18 +322,16 @@ impl OnlineCiphertextV1Encryptor { let mut header_bytes: Vec = header.borrow().into(); aad.append(&mut header_bytes); - let encryptor = Self { - chunk_size, + Self { + header, cipher, aad, - }; - - (encryptor, header) + } } } -impl OnlineCiphertextV1Decryptor { - pub fn new(key: &[u8], mut aad: Vec, header: &OnlineCiphertextV1HeaderSymmetric) -> Self { +impl OnlineCiphertextV1Decryptor { + pub fn new(key: &[u8], mut aad: Vec, header: OnlineCiphertextV1HeaderSymmetric) -> Self { // Derive the key let key = Zeroizing::new(blake3::derive_key(CONTEXT, key)); let cipher = XChaCha20Poly1305::new(key.as_ref().into()); @@ -241,20 +339,22 @@ impl OnlineCiphertextV1Decryptor { // Create the STREAM decryptor let cipher = DecryptorLE31::from_aead(cipher, &header.nonce.into()); - let mut header_bytes: Vec = header.into(); + let mut header_bytes: Vec = header.borrow().into(); aad.append(&mut header_bytes); Self { - chunk_size: header.chunk_size, + header, aad, cipher, } } +} +impl OnlineCiphertextV1Decryptor { pub fn new_asymmetric( private_key: &PrivateKey, mut aad: Vec, - header: &OnlineCiphertextV1HeaderAsymmetric, + header: OnlineCiphertextV1HeaderAsymmetric, ) -> Self { // Perform a ECDH exchange as per ECIES let private_key = x25519_dalek::StaticSecret::from(private_key); @@ -268,11 +368,11 @@ impl OnlineCiphertextV1Decryptor { // Create the STREAM decryptor let cipher = DecryptorLE31::from_aead(cipher, &header.nonce.into()); - let mut header_bytes: Vec = header.into(); + let mut header_bytes: Vec = header.borrow().into(); aad.append(&mut header_bytes); Self { - chunk_size: header.chunk_size, + header, aad, cipher, } From f27933bd16a5e018a8596b43e0f785ef39baec53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Dugr=C3=A9?= Date: Thu, 12 Dec 2024 12:13:08 -0500 Subject: [PATCH 09/21] [STREAM] temporarly fix the trait system with a downcast --- Cargo.lock | 7 ++ Cargo.toml | 1 + src/online_ciphertext/mod.rs | 45 ++++++---- src/online_ciphertext/online_ciphertext_v1.rs | 86 ++++++++++--------- 4 files changed, 83 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a11b479f4..62854506d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -516,6 +516,7 @@ dependencies = [ "cbc", "cfg-if", "chacha20poly1305", + "dyn-clone", "ed25519-dalek", "getrandom", "hmac", @@ -596,6 +597,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + [[package]] name = "ed25519" version = "2.2.3" diff --git a/Cargo.toml b/Cargo.toml index b031c24a9..d782ba1b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ x25519-dalek = { version = "2", features = [ "static_secrets" ] } arbitrary = { version = "0.4.7", features = ["derive"], optional = true } blake3 = { version = "1.5.5", features = ["zeroize"] } paste = "1.0.15" +dyn-clone = "1.0.17" [target.'cfg(target_arch="wasm32")'.dependencies] rust-argon2 = { version = "1.0", default-features = false } diff --git a/src/online_ciphertext/mod.rs b/src/online_ciphertext/mod.rs index 3ba22453f..245ed22a2 100644 --- a/src/online_ciphertext/mod.rs +++ b/src/online_ciphertext/mod.rs @@ -55,10 +55,7 @@ use super::Result; use super::key::{PrivateKey, PublicKey}; use online_ciphertext_v1::OnlineCiphertextV1Header; -use online_ciphertext_v1::{ - OnlineCiphertextV1Decryptor, OnlineCiphertextV1Encryptor, OnlineCiphertextV1HeaderAsymmetric, - OnlineCiphertextV1HeaderSymmetric, -}; +use online_ciphertext_v1::{OnlineCiphertextV1Decryptor, OnlineCiphertextV1Encryptor}; use paste::paste; @@ -84,7 +81,7 @@ pub fn new_encryptor( chunk_size: u32, version: OnlineCiphertextVersion, ) -> OnlineCiphertextEncryptor { - let mut header = Header { + let mut header = Header:: { data_subtype: CiphertextSubtype::Symmetric, ..Default::default() }; @@ -109,7 +106,7 @@ pub fn new_encryptor_asymmetric( chunk_size: u32, version: OnlineCiphertextVersion, ) -> OnlineCiphertextEncryptor { - let mut header = Header { + let mut header = Header:: { data_subtype: CiphertextSubtype::Asymmetric, ..Default::default() }; @@ -135,12 +132,13 @@ impl OnlineCiphertextHeader { full_aad.extend_from_slice(aad); match self.payload { - OnlineCiphertextHeaderPayload::V1Symmetric(header) => { - let cipher = OnlineCiphertextV1Decryptor::new(key, full_aad, header); + OnlineCiphertextHeaderPayload::V1(header) => { + // TODO: Remove downcasting black magic + let header = header.downcast_symmetric()?; + let cipher = OnlineCiphertextV1Decryptor::new(key, full_aad, header.clone()); Ok(OnlineCiphertextDecryptor::V1(cipher)) } - OnlineCiphertextHeaderPayload::V1Asymmetric(_) => Err(Error::InvalidDataType), } } @@ -153,10 +151,15 @@ impl OnlineCiphertextHeader { full_aad.extend_from_slice(aad); match self.payload { - OnlineCiphertextHeaderPayload::V1Symmetric(_) => Err(Error::InvalidDataType), - OnlineCiphertextHeaderPayload::V1Asymmetric(header) => { - let cipher = - OnlineCiphertextV1Decryptor::new_asymmetric(private_key, full_aad, header); + OnlineCiphertextHeaderPayload::V1(header) => { + // TODO: Remove downcasting black magic + let header = header.downcast_asymmetric()?; + + let cipher = OnlineCiphertextV1Decryptor::new_asymmetric( + private_key, + full_aad, + header.clone(), + ); Ok(OnlineCiphertextDecryptor::V1(cipher)) } @@ -169,7 +172,7 @@ macro_rules! online_ciphertext_impl { paste! { pub enum [] { $( - $version_name([]), + $version_name([]), ),+ } @@ -247,7 +250,15 @@ macro_rules! online_ciphertext_impl { online_ciphertext_impl!(Encryptor, encrypt, V1); online_ciphertext_impl!(Decryptor, decrypt, V1); -#[derive(Clone, Debug)] -enum OnlineCiphertextHeaderPayload<'a, 'b> { - V1(OnlineCiphertextV1Header<'a, 'b>), +#[derive(Debug)] +enum OnlineCiphertextHeaderPayload { + V1(Box), +} + +impl Clone for OnlineCiphertextHeaderPayload { + fn clone(&self) -> Self { + match self { + OnlineCiphertextHeaderPayload::V1(x) => Self::V1(dyn_clone::clone_box(x.as_ref())), + } + } } diff --git a/src/online_ciphertext/online_ciphertext_v1.rs b/src/online_ciphertext/online_ciphertext_v1.rs index 8a9e7c481..0b52e63eb 100644 --- a/src/online_ciphertext/online_ciphertext_v1.rs +++ b/src/online_ciphertext/online_ciphertext_v1.rs @@ -12,6 +12,7 @@ use chacha20poly1305::aead::{ }; use chacha20poly1305::{KeyInit, XChaCha20Poly1305}; +use dyn_clone::DynClone; use rand::{rngs::OsRng, RngCore}; use x25519_dalek::StaticSecret; use zeroize::Zeroizing; @@ -22,12 +23,13 @@ use paste::paste; /// This is used to normalize the key length and domain separation const CONTEXT: &'static str = "devolutions_crypto online_ciphertext_v1"; -pub trait OnlineCiphertextV1Header<'a, 'b>: - 'b + Sized + Clone + std::fmt::Debug + TryFrom<&'a [u8]> -where - &'b Self: Into>, -{ +pub trait OnlineCiphertextV1Header: std::fmt::Debug + DynClone { fn get_chunk_size(&self) -> u32; + + // TODO: Remove downcasting black magic + fn downcast_symmetric(&self) -> Result<&OnlineCiphertextV1HeaderSymmetric>; + + fn downcast_asymmetric(&self) -> Result<&OnlineCiphertextV1HeaderAsymmetric>; } #[derive(Clone, Debug)] @@ -124,46 +126,52 @@ impl TryFrom<&[u8]> for OnlineCiphertextV1HeaderAsymmetric { } } -impl OnlineCiphertextV1Header<'_, '_> for OnlineCiphertextV1HeaderSymmetric { +impl OnlineCiphertextV1Header for OnlineCiphertextV1HeaderSymmetric { fn get_chunk_size(&self) -> u32 { self.chunk_size } + + // TODO: Remove downcasting black magic + fn downcast_symmetric(&self) -> Result<&OnlineCiphertextV1HeaderSymmetric> { + Ok(&self) + } + + fn downcast_asymmetric(&self) -> Result<&OnlineCiphertextV1HeaderAsymmetric> { + Err(Error::InvalidDataType) + } } -impl OnlineCiphertextV1Header<'_, '_> for OnlineCiphertextV1HeaderAsymmetric { +impl OnlineCiphertextV1Header for OnlineCiphertextV1HeaderAsymmetric { fn get_chunk_size(&self) -> u32 { self.chunk_size } + + // TODO: Remove downcasting black magic + fn downcast_symmetric(&self) -> Result<&OnlineCiphertextV1HeaderSymmetric> { + Err(Error::InvalidDataType) + } + + fn downcast_asymmetric(&self) -> Result<&OnlineCiphertextV1HeaderAsymmetric> { + Ok(&self) + } } /// Implements the encryptor/decryptor structure macro_rules! online_ciphertext_impl { ($struct_name:ident, $cipher_name:ident, $func:ident) => { - pub struct $struct_name - where - for<'a, 'b> H: OnlineCiphertextV1Header<'a, 'b>, - { - header: H, + pub struct $struct_name { + header: Box, aad: Vec, cipher: $cipher_name, } - impl $struct_name - where - for<'a, 'b> H: OnlineCiphertextV1Header<'a, 'b> - + std::fmt::Debug - + Sized - + Clone - + std::fmt::Debug - + TryFrom<&'a [u8]>, - for<'b> &'b H: Into>, - { + impl $struct_name { /// Gets the number of bytes to process in each chunk pub fn get_chunk_size(&self) -> u32 { self.header.get_chunk_size() } - pub fn get_header(&self) -> &H { + pub fn get_header(&self) -> &Box { &self.header } @@ -174,7 +182,7 @@ macro_rules! online_ciphertext_impl { data: &[u8], aad: &[u8], ) -> Result> { - if (data.len() as u32) != self.get_chunk_size() { + if (data.len() as u32) != self.header.get_chunk_size() { return Err(Error::InvalidChunkLength); }; @@ -199,7 +207,7 @@ macro_rules! online_ciphertext_impl { data: &mut Vec, aad: &[u8], ) -> Result<()> { - if (data.len() as u32) != self.get_chunk_size() { + if (data.len() as u32) != self.header.get_chunk_size() { return Err(Error::InvalidChunkLength); }; @@ -220,7 +228,7 @@ macro_rules! online_ciphertext_impl { data: &[u8], aad: &[u8], ) -> Result> { - if (data.len() as u32) != self.get_chunk_size() { + if (data.len() as u32) != self.header.get_chunk_size() { return Err(Error::InvalidChunkLength); }; @@ -245,7 +253,7 @@ macro_rules! online_ciphertext_impl { data: &mut Vec, aad: &[u8], ) -> Result<()> { - if (data.len() as u32) != self.get_chunk_size() { + if (data.len() as u32) != self.header.get_chunk_size() { return Err(Error::InvalidChunkLength); }; @@ -264,7 +272,7 @@ macro_rules! online_ciphertext_impl { }; } -impl OnlineCiphertextV1Encryptor { +impl OnlineCiphertextV1Encryptor { /// Creates a new encryptor and the corresponding header pub fn new(key: &[u8], mut aad: Vec, chunk_size: u32) -> Self { // Generate a new nonce @@ -279,9 +287,10 @@ impl OnlineCiphertextV1Encryptor { let cipher = EncryptorLE31::from_aead(cipher, &nonce.into()); // Create aad - let header = OnlineCiphertextV1HeaderSymmetric { chunk_size, nonce }; + let header = Box::new(OnlineCiphertextV1HeaderSymmetric { chunk_size, nonce }); - let mut header_bytes: Vec = header.borrow().into(); + let mut header_bytes: Vec = + Borrow::::borrow(&header).into(); aad.append(&mut header_bytes); Self { @@ -290,9 +299,7 @@ impl OnlineCiphertextV1Encryptor { cipher, } } -} -impl OnlineCiphertextV1Encryptor { pub fn new_asymmetric(public_key: &PublicKey, mut aad: Vec, chunk_size: u32) -> Self { // Perform a ECDH exchange as per ECIES let public_key = x25519_dalek::PublicKey::from(public_key); @@ -313,13 +320,14 @@ impl OnlineCiphertextV1Encryptor { // Create the STREAM encryptor let cipher = EncryptorLE31::from_aead(cipher, &nonce.into()); - let header = OnlineCiphertextV1HeaderAsymmetric { + let header = Box::new(OnlineCiphertextV1HeaderAsymmetric { chunk_size, nonce, public_key: ephemeral_public_key, - }; + }); - let mut header_bytes: Vec = header.borrow().into(); + let mut header_bytes: Vec = + Borrow::::borrow(&header).into(); aad.append(&mut header_bytes); Self { @@ -330,7 +338,7 @@ impl OnlineCiphertextV1Encryptor { } } -impl OnlineCiphertextV1Decryptor { +impl OnlineCiphertextV1Decryptor { pub fn new(key: &[u8], mut aad: Vec, header: OnlineCiphertextV1HeaderSymmetric) -> Self { // Derive the key let key = Zeroizing::new(blake3::derive_key(CONTEXT, key)); @@ -343,14 +351,14 @@ impl OnlineCiphertextV1Decryptor { aad.append(&mut header_bytes); Self { - header, + header: Box::new(header), aad, cipher, } } } -impl OnlineCiphertextV1Decryptor { +impl OnlineCiphertextV1Decryptor { pub fn new_asymmetric( private_key: &PrivateKey, mut aad: Vec, @@ -372,7 +380,7 @@ impl OnlineCiphertextV1Decryptor { aad.append(&mut header_bytes); Self { - header, + header: Box::new(header), aad, cipher, } From 428692d5324eb5682a47df32cade4fdf69306404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Dugr=C3=A9?= Date: Mon, 16 Dec 2024 16:33:28 -0500 Subject: [PATCH 10/21] [STREAM] revert symmetric/asymmetric trait to be dynamic --- src/online_ciphertext/mod.rs | 166 +++++++++++++----- src/online_ciphertext/online_ciphertext_v1.rs | 95 +++++----- 2 files changed, 161 insertions(+), 100 deletions(-) diff --git a/src/online_ciphertext/mod.rs b/src/online_ciphertext/mod.rs index 245ed22a2..660ee5a29 100644 --- a/src/online_ciphertext/mod.rs +++ b/src/online_ciphertext/mod.rs @@ -54,27 +54,13 @@ use super::Result; use super::key::{PrivateKey, PublicKey}; -use online_ciphertext_v1::OnlineCiphertextV1Header; use online_ciphertext_v1::{OnlineCiphertextV1Decryptor, OnlineCiphertextV1Encryptor}; +use online_ciphertext_v1::{ + OnlineCiphertextV1Header, OnlineCiphertextV1HeaderAsymmetric, OnlineCiphertextV1HeaderSymmetric, +}; use paste::paste; -/// A versionned online ciphertext. Can be either symmetric or asymmetric. -#[derive(Clone, Debug)] -pub struct OnlineCiphertextHeader { - pub(crate) header: Header, - payload: OnlineCiphertextHeaderPayload, -} - -impl HeaderType for OnlineCiphertextHeader { - type Version = OnlineCiphertextVersion; - type Subtype = CiphertextSubtype; - - fn data_type() -> DataType { - DataType::OnlineCiphertext - } -} - pub fn new_encryptor( key: &[u8], aad: &[u8], @@ -132,39 +118,119 @@ impl OnlineCiphertextHeader { full_aad.extend_from_slice(aad); match self.payload { - OnlineCiphertextHeaderPayload::V1(header) => { - // TODO: Remove downcasting black magic - let header = header.downcast_symmetric()?; - let cipher = OnlineCiphertextV1Decryptor::new(key, full_aad, header.clone()); + OnlineCiphertextHeaderPayload::V1(header) => match header { + OnlineCiphertextV1Header::Symmetric(header) => { + let cipher = OnlineCiphertextV1Decryptor::new(key, full_aad, header); - Ok(OnlineCiphertextDecryptor::V1(cipher)) - } + Ok(OnlineCiphertextDecryptor::V1(cipher)) + } + _ => Err(Error::InvalidDataType), + }, } } - pub fn get_decryptor_asymmetric( + pub fn into_decryptor_asymmetric( self, - private_key: &PrivateKey, + key: &PrivateKey, aad: &[u8], ) -> Result { let mut full_aad: Vec = self.header.borrow().into(); full_aad.extend_from_slice(aad); match self.payload { - OnlineCiphertextHeaderPayload::V1(header) => { - // TODO: Remove downcasting black magic - let header = header.downcast_asymmetric()?; + OnlineCiphertextHeaderPayload::V1(header) => match header { + OnlineCiphertextV1Header::Asymmetric(header) => { + let cipher = OnlineCiphertextV1Decryptor::new_asymmetric(key, full_aad, header); + + Ok(OnlineCiphertextDecryptor::V1(cipher)) + } + _ => Err(Error::InvalidDataType), + }, + } + } +} + +macro_rules! online_ciphertext_header_impl { + ($($version_name:ident),+) => { + paste! { + /// A versionned online ciphertext. + #[derive(Clone, Debug)] + pub struct OnlineCiphertextHeader { + pub(crate) header: Header, + payload: OnlineCiphertextHeaderPayload, + } + + impl HeaderType for OnlineCiphertextHeader { + type Version = OnlineCiphertextVersion; + type Subtype = CiphertextSubtype; + + fn data_type() -> DataType { + DataType::OnlineCiphertext + } + } - let cipher = OnlineCiphertextV1Decryptor::new_asymmetric( - private_key, - full_aad, - header.clone(), - ); + #[derive(Clone, Debug)] + enum OnlineCiphertextHeaderPayload { + $( + $version_name([]), + ),+ + } - Ok(OnlineCiphertextDecryptor::V1(cipher)) + impl From<&OnlineCiphertextHeader> for Vec { + fn from(value: &OnlineCiphertextHeader) -> Self { + let mut output: Vec = value.header.borrow().into(); + let mut payload: Vec = match &value.payload { + $( + OnlineCiphertextHeaderPayload::$version_name(p) => { + p.into() + }, + ),+ + }; + + output.append(&mut payload); + output + } + } + + impl TryFrom<&[u8]> for OnlineCiphertextHeader { + type Error = Error; + + fn try_from(value: &[u8]) -> Result { + let header = Header::::try_from(&value[..8])?; + + let version = if header.version == OnlineCiphertextVersion::Latest { OnlineCiphertextVersion::V1 } else { header.version }; + match version { + $( + OnlineCiphertextVersion::$version_name => { + match header.data_subtype { + CiphertextSubtype::Symmetric => { + Ok(Self { + header, + payload: + OnlineCiphertextHeaderPayload::$version_name + ([]::Symmetric + ([]::try_from(&value[8..])?)) + }) + } + CiphertextSubtype::Asymmetric => { + Ok(Self { + header, + payload: + OnlineCiphertextHeaderPayload::$version_name + ([]::Asymmetric + ([]::try_from(&value[8..])?)) + }) + } + CiphertextSubtype::None => Err(Error::UnknownSubtype) + } + } + ),+ + OnlineCiphertextVersion::Latest => unreachable!("Latest is checked before the match arm") + } + } } } - } + }; } macro_rules! online_ciphertext_impl { @@ -187,6 +253,23 @@ macro_rules! online_ciphertext_impl { } } + pub fn get_header(&self) -> OnlineCiphertextHeader { + match self { + $( + Self::$version_name(encryptor) => { + let data_subtype = encryptor.get_header().get_subtype(); + OnlineCiphertextHeader { + header: Header:: { + data_subtype, + ..Default::default() + }, + payload: OnlineCiphertextHeaderPayload::$version_name(encryptor.get_header().clone()), + } + } + ),+ + } + } + pub fn [<$func _chunk>]( &mut self, data: &[u8], @@ -250,15 +333,4 @@ macro_rules! online_ciphertext_impl { online_ciphertext_impl!(Encryptor, encrypt, V1); online_ciphertext_impl!(Decryptor, decrypt, V1); -#[derive(Debug)] -enum OnlineCiphertextHeaderPayload { - V1(Box), -} - -impl Clone for OnlineCiphertextHeaderPayload { - fn clone(&self) -> Self { - match self { - OnlineCiphertextHeaderPayload::V1(x) => Self::V1(dyn_clone::clone_box(x.as_ref())), - } - } -} +online_ciphertext_header_impl!(V1); diff --git a/src/online_ciphertext/online_ciphertext_v1.rs b/src/online_ciphertext/online_ciphertext_v1.rs index 0b52e63eb..fe0dadb1e 100644 --- a/src/online_ciphertext/online_ciphertext_v1.rs +++ b/src/online_ciphertext/online_ciphertext_v1.rs @@ -1,3 +1,5 @@ +use crate::enums::CiphertextSubtype; + ///! Online Ciphertext V1: STREAM-LE31-XChaCha20Poly1305 use super::{PrivateKey, PublicKey}; @@ -12,7 +14,6 @@ use chacha20poly1305::aead::{ }; use chacha20poly1305::{KeyInit, XChaCha20Poly1305}; -use dyn_clone::DynClone; use rand::{rngs::OsRng, RngCore}; use x25519_dalek::StaticSecret; use zeroize::Zeroizing; @@ -23,13 +24,10 @@ use paste::paste; /// This is used to normalize the key length and domain separation const CONTEXT: &'static str = "devolutions_crypto online_ciphertext_v1"; -pub trait OnlineCiphertextV1Header: std::fmt::Debug + DynClone { - fn get_chunk_size(&self) -> u32; - - // TODO: Remove downcasting black magic - fn downcast_symmetric(&self) -> Result<&OnlineCiphertextV1HeaderSymmetric>; - - fn downcast_asymmetric(&self) -> Result<&OnlineCiphertextV1HeaderAsymmetric>; +#[derive(Clone, Debug)] +pub enum OnlineCiphertextV1Header { + Symmetric(OnlineCiphertextV1HeaderSymmetric), + Asymmetric(OnlineCiphertextV1HeaderAsymmetric), } #[derive(Clone, Debug)] @@ -45,6 +43,22 @@ pub struct OnlineCiphertextV1HeaderAsymmetric { public_key: x25519_dalek::PublicKey, } +impl OnlineCiphertextV1Header { + pub fn get_chunk_size(&self) -> u32 { + match self { + Self::Symmetric(x) => x.chunk_size, + Self::Asymmetric(x) => x.chunk_size, + } + } + + pub fn get_subtype(&self) -> CiphertextSubtype { + match self { + Self::Symmetric(_) => CiphertextSubtype::Symmetric, + Self::Asymmetric(_) => CiphertextSubtype::Asymmetric, + } + } +} + impl From<&OnlineCiphertextV1HeaderSymmetric> for Vec { /// Serialize the header into bytes fn from(value: &OnlineCiphertextV1HeaderSymmetric) -> Self { @@ -66,6 +80,15 @@ impl From<&OnlineCiphertextV1HeaderAsymmetric> for Vec { } } +impl From<&OnlineCiphertextV1Header> for Vec { + fn from(value: &OnlineCiphertextV1Header) -> Self { + match value { + OnlineCiphertextV1Header::Symmetric(x) => x.into(), + OnlineCiphertextV1Header::Asymmetric(x) => x.into(), + } + } +} + impl TryFrom<&[u8]> for OnlineCiphertextV1HeaderSymmetric { type Error = Error; @@ -126,41 +149,11 @@ impl TryFrom<&[u8]> for OnlineCiphertextV1HeaderAsymmetric { } } -impl OnlineCiphertextV1Header for OnlineCiphertextV1HeaderSymmetric { - fn get_chunk_size(&self) -> u32 { - self.chunk_size - } - - // TODO: Remove downcasting black magic - fn downcast_symmetric(&self) -> Result<&OnlineCiphertextV1HeaderSymmetric> { - Ok(&self) - } - - fn downcast_asymmetric(&self) -> Result<&OnlineCiphertextV1HeaderAsymmetric> { - Err(Error::InvalidDataType) - } -} - -impl OnlineCiphertextV1Header for OnlineCiphertextV1HeaderAsymmetric { - fn get_chunk_size(&self) -> u32 { - self.chunk_size - } - - // TODO: Remove downcasting black magic - fn downcast_symmetric(&self) -> Result<&OnlineCiphertextV1HeaderSymmetric> { - Err(Error::InvalidDataType) - } - - fn downcast_asymmetric(&self) -> Result<&OnlineCiphertextV1HeaderAsymmetric> { - Ok(&self) - } -} - /// Implements the encryptor/decryptor structure macro_rules! online_ciphertext_impl { ($struct_name:ident, $cipher_name:ident, $func:ident) => { pub struct $struct_name { - header: Box, + header: OnlineCiphertextV1Header, aad: Vec, cipher: $cipher_name, } @@ -171,7 +164,7 @@ macro_rules! online_ciphertext_impl { self.header.get_chunk_size() } - pub fn get_header(&self) -> &Box { + pub fn get_header(&self) -> &OnlineCiphertextV1Header { &self.header } @@ -287,14 +280,13 @@ impl OnlineCiphertextV1Encryptor { let cipher = EncryptorLE31::from_aead(cipher, &nonce.into()); // Create aad - let header = Box::new(OnlineCiphertextV1HeaderSymmetric { chunk_size, nonce }); + let header = OnlineCiphertextV1HeaderSymmetric { chunk_size, nonce }; - let mut header_bytes: Vec = - Borrow::::borrow(&header).into(); + let mut header_bytes: Vec = header.borrow().into(); aad.append(&mut header_bytes); Self { - header, + header: OnlineCiphertextV1Header::Symmetric(header), aad, cipher, } @@ -320,18 +312,17 @@ impl OnlineCiphertextV1Encryptor { // Create the STREAM encryptor let cipher = EncryptorLE31::from_aead(cipher, &nonce.into()); - let header = Box::new(OnlineCiphertextV1HeaderAsymmetric { + let header = OnlineCiphertextV1HeaderAsymmetric { chunk_size, nonce, public_key: ephemeral_public_key, - }); + }; - let mut header_bytes: Vec = - Borrow::::borrow(&header).into(); + let mut header_bytes: Vec = header.borrow().into(); aad.append(&mut header_bytes); Self { - header, + header: OnlineCiphertextV1Header::Asymmetric(header), cipher, aad, } @@ -351,14 +342,12 @@ impl OnlineCiphertextV1Decryptor { aad.append(&mut header_bytes); Self { - header: Box::new(header), + header: OnlineCiphertextV1Header::Symmetric(header), aad, cipher, } } -} -impl OnlineCiphertextV1Decryptor { pub fn new_asymmetric( private_key: &PrivateKey, mut aad: Vec, @@ -380,7 +369,7 @@ impl OnlineCiphertextV1Decryptor { aad.append(&mut header_bytes); Self { - header: Box::new(header), + header: OnlineCiphertextV1Header::Asymmetric(header), aad, cipher, } From e0bf39ddbe52ed70982c821339f83877cbeb5f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Dugr=C3=A9?= Date: Wed, 18 Dec 2024 13:26:53 -0500 Subject: [PATCH 11/21] [STREAM] FFI bindings --- ffi/devolutions-crypto.h | 92 +++++ ffi/src/lib.rs | 361 ++++++++++++++++++ src/argon2parameters.rs | 1 + src/error.rs | 4 + src/online_ciphertext/mod.rs | 156 +++++--- src/online_ciphertext/online_ciphertext_v1.rs | 35 +- src/utils.rs | 1 + 7 files changed, 581 insertions(+), 69 deletions(-) diff --git a/ffi/devolutions-crypto.h b/ffi/devolutions-crypto.h index 24bb294ee..f582791c9 100644 --- a/ffi/devolutions-crypto.h +++ b/ffi/devolutions-crypto.h @@ -64,6 +64,9 @@ int64_t DecodeUrl(const uint8_t *input, * * `data_length` - Length of the data to decrypt. * * `key` - Pointer to the key to use to decrypt. * * `key_length` - Length of the key to use to decrypt. + * * `aad` - Pointer to additionnal data to authenticate alongside the ciphertext. + * Pass null if there is not additionnal data to authenticate. + * * `aad_length` - Length of the additionnal data to authenticate. Pass 0 if there is no data. * * `result` - Pointer to the buffer to write the plaintext to. * * `result_length` - Length of the buffer to write the plaintext to. * The safest size is the same size as the ciphertext. @@ -77,6 +80,8 @@ int64_t Decrypt(const uint8_t *data, size_t data_length, const uint8_t *key, size_t key_length, + const uint8_t *aad, + size_t aad_length, uint8_t *result, size_t result_length); @@ -87,6 +92,9 @@ int64_t Decrypt(const uint8_t *data, * * `data_length` - Length of the data to decrypt. * * `private_key` - Pointer to the private key to use to decrypt. * * `private_key_length` - Length of the private key to use to decrypt. + * * `aad` - Pointer to additionnal data to authenticate alongside the ciphertext. + * Pass null if there is not additionnal data to authenticate. + * * `aad_length` - Length of the additionnal data to authenticate. Pass 0 if there is no data. * * `result` - Pointer to the buffer to write the plaintext to. * * `result_length` - Length of the buffer to write the plaintext to. * The safest size is the same size as the ciphertext. @@ -100,6 +108,8 @@ int64_t DecryptAsymmetric(const uint8_t *data, size_t data_length, const uint8_t *private_key, size_t private_key_length, + const uint8_t *aad, + size_t aad_length, uint8_t *result, size_t result_length); @@ -189,6 +199,9 @@ int64_t EncodeUrl(const uint8_t *input, * * `data_length` - Length of the data to encrypt. * * `key` - Pointer to the key to use to encrypt. * * `key_length` - Length of the key to use to encrypt. + * * `aad` - Pointer to additionnal data to authenticate alongside the ciphertext. + * Pass null if there is not additionnal data to authenticate. + * * `aad_length` - Length of the additionnal data to authenticate. Pass 0 if there is no data. * * `result` - Pointer to the buffer to write the ciphertext to. * * `result_length` - Length of the buffer to write the ciphertext to. You can get the value by * calling EncryptSize() beforehand. @@ -203,6 +216,8 @@ int64_t Encrypt(const uint8_t *data, size_t data_length, const uint8_t *key, size_t key_length, + const uint8_t *aad, + size_t aad_length, uint8_t *result, size_t result_length, uint16_t version); @@ -214,6 +229,9 @@ int64_t Encrypt(const uint8_t *data, * * `data_length` - Length of the data to encrypt. * * `public_key` - Pointer to the public key to use to encrypt. * * `public_key_length` - Length of the public key to use to encrypt. + * * `aad` - Pointer to additionnal data to authenticate alongside the ciphertext. + * Pass null if there is not additionnal data to authenticate. + * * `aad_length` - Length of the additionnal data to authenticate. Pass 0 if there is no data. * * `result` - Pointer to the buffer to write the ciphertext to. * * `result_length` - Length of the buffer to write the ciphertext to. You can get the value by * calling EncryptAsymmetricSize() beforehand. @@ -228,6 +246,8 @@ int64_t EncryptAsymmetric(const uint8_t *data, size_t data_length, const uint8_t *public_key, size_t public_key_length, + const uint8_t *aad, + size_t aad_length, uint8_t *result, size_t result_length, uint16_t version); @@ -251,6 +271,10 @@ int64_t EncryptAsymmetricSize(size_t data_length, */ int64_t EncryptSize(size_t data_length, uint16_t version); +void FreeOnlineDecryptor(void *ptr); + +void FreeOnlineEncryptor(void *ptr); + /** * Generate a key using a CSPRNG. * # Arguments @@ -373,6 +397,8 @@ int64_t GetDefaultArgon2ParametersSize(void); * * `public` - Pointer to the buffer to write the public key to. * * `public_length` - Length of the buffer to write the public key to. * You can get the value by calling `GetSigningPublicKeySize()` beforehand. + * # Safety + * This method is made to be called by C, so it is therefore unsafe. The caller should make sure it passes the right pointers and sizes. */ int64_t GetSigningPublicKey(const uint8_t *keypair, size_t keypair_length, @@ -482,6 +508,72 @@ int64_t MixKeyExchange(const uint8_t *private_, */ int64_t MixKeyExchangeSize(void); +int64_t NewOnlineDecryptor(const uint8_t *key, + size_t key_size, + const uint8_t *aad, + size_t aad_size, + const uint8_t *header, + size_t header_size, + bool asymmetric, + void **output); + +int64_t NewOnlineEncryptor(const uint8_t *key, + size_t key_size, + const uint8_t *aad, + size_t aad_size, + uint32_t chunk_size, + bool asymmetric, + uint16_t version, + void **output); + +int64_t OnlineDecryptorGetChunkSize(const void *ptr); + +int64_t OnlineDecryptorGetHeader(const void *ptr, uint8_t *result, size_t result_size); + +int64_t OnlineDecryptorGetHeaderSize(const void *ptr); + +int64_t OnlineDecryptorGetTagSize(const void *ptr); + +int64_t OnlineDecryptorLastChunk(void *ptr, + const uint8_t *data, + size_t data_size, + const uint8_t *aad, + size_t aad_size, + uint8_t *result, + size_t result_size); + +int64_t OnlineDecryptorNextChunk(void *ptr, + const uint8_t *data, + size_t data_size, + const uint8_t *aad, + size_t aad_size, + uint8_t *result, + size_t result_size); + +int64_t OnlineEncryptorGetChunkSize(const void *ptr); + +int64_t OnlineEncryptorGetHeader(const void *ptr, uint8_t *result, size_t result_size); + +int64_t OnlineEncryptorGetHeaderSize(const void *ptr); + +int64_t OnlineEncryptorGetTagSize(const void *ptr); + +int64_t OnlineEncryptorLastChunk(void *ptr, + const uint8_t *data, + size_t data_size, + const uint8_t *aad, + size_t aad_size, + uint8_t *result, + size_t result_size); + +int64_t OnlineEncryptorNextChunk(void *ptr, + const uint8_t *data, + size_t data_size, + const uint8_t *aad, + size_t aad_size, + uint8_t *result, + size_t result_size); + /** * This is binded here for one specific use case, do not use it if you don't know what you're doing. * # Safety diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 5157231b8..4c801f327 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -8,6 +8,9 @@ //! The Size functions must be called to get the required length of the returned array before //! calling it. +use devolutions_crypto::online_ciphertext::OnlineCiphertextDecryptor; +use devolutions_crypto::online_ciphertext::OnlineCiphertextEncryptor; +use devolutions_crypto::online_ciphertext::OnlineCiphertextHeader; use devolutions_crypto::utils; use devolutions_crypto::Argon2Parameters; use devolutions_crypto::DataType; @@ -25,6 +28,7 @@ use devolutions_crypto::password_hash::{hash_password, PasswordHash, PasswordHas use devolutions_crypto::secret_sharing::{ generate_shared_key, join_shares, SecretSharingVersion, Share, }; +use devolutions_crypto::OnlineCiphertextVersion; use devolutions_crypto::{ signature, signature::{Signature, SignatureVersion}, @@ -37,7 +41,10 @@ use devolutions_crypto::{ use devolutions_crypto::Result; +use std::borrow::Borrow; +use std::ffi::c_void; use std::slice; +use std::sync::Mutex; use zeroize::Zeroizing; @@ -835,6 +842,360 @@ pub unsafe extern "C" fn JoinShares( } } +#[no_mangle] +pub unsafe extern "C" fn NewOnlineEncryptor( + key: *const u8, + key_size: usize, + aad: *const u8, + aad_size: usize, + chunk_size: u32, + asymmetric: bool, + version: u16, + output: *mut *mut c_void, +) -> i64 { + if key.is_null() || aad.is_null() { + return Error::NullPointer.error_code(); + }; + + let key = slice::from_raw_parts(key, key_size); + let aad = slice::from_raw_parts(aad, aad_size); + + let version = match OnlineCiphertextVersion::try_from(version) { + Ok(v) => v, + Err(_) => return Error::UnknownVersion.error_code(), + }; + + let encryptor = if asymmetric { + let public_key = match PublicKey::try_from(key) { + Ok(pk) => pk, + Err(e) => return e.error_code(), + }; + + OnlineCiphertextEncryptor::new_asymmetric(&public_key, aad, chunk_size, version) + } else { + OnlineCiphertextEncryptor::new(key, aad, chunk_size, version) + }; + + let encryptor = Box::new(Mutex::new(encryptor)); + + *output = Box::into_raw(encryptor) as *mut c_void; + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn NewOnlineDecryptor( + key: *const u8, + key_size: usize, + aad: *const u8, + aad_size: usize, + header: *const u8, + header_size: usize, + asymmetric: bool, + output: *mut *mut c_void, +) -> i64 { + if key.is_null() | aad.is_null() | header.is_null() { + return Error::NullPointer.error_code(); + }; + + let key = slice::from_raw_parts(key, key_size); + let aad = slice::from_raw_parts(aad, aad_size); + let header = slice::from_raw_parts(header, header_size); + + let header = match OnlineCiphertextHeader::try_from(header) { + Ok(h) => h, + Err(e) => return e.error_code(), + }; + + let decryptor = if asymmetric { + let private_key = match PrivateKey::try_from(key) { + Ok(pk) => pk, + Err(e) => return e.error_code(), + }; + + header.into_decryptor_asymmetric(&private_key, aad) + } else { + header.into_decryptor(key, aad) + }; + + let decryptor = match decryptor { + Ok(d) => d, + Err(e) => return e.error_code(), + }; + + let decryptor = Box::new(Mutex::new(decryptor)); + + *output = Box::into_raw(decryptor) as *mut c_void; + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineEncryptorGetHeader( + ptr: *const c_void, + result: *mut u8, + result_size: usize, +) -> i64 { + if result.is_null() { + return Error::NullPointer.error_code(); + }; + + let encryptor = &*(ptr as *const Mutex); + let header: Vec = match encryptor.lock() { + Ok(c) => c.get_header().borrow().into(), + Err(_) => return Error::PoisonedMutex.error_code(), + }; + + if header.len() != result_size { + return Error::InvalidOutputLength.error_code(); + } + + result.copy_from(header.as_slice().as_ptr() as *const u8, result_size); + + result_size as i64 +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineDecryptorGetHeader( + ptr: *const c_void, + result: *mut u8, + result_size: usize, +) -> i64 { + if result.is_null() { + return Error::NullPointer.error_code(); + }; + + let decryptor = &*(ptr as *const Mutex); + let header: Vec = match decryptor.lock() { + Ok(c) => c.get_header().borrow().into(), + Err(_) => return Error::PoisonedMutex.error_code(), + }; + + if header.len() != result_size { + return Error::InvalidOutputLength.error_code(); + } + + result.copy_from(header.as_slice().as_ptr() as *const u8, result_size); + + result_size as i64 +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineEncryptorNextChunk( + ptr: *mut c_void, + data: *const u8, + data_size: usize, + aad: *const u8, + aad_size: usize, + result: *mut u8, + result_size: usize, +) -> i64 { + if aad.is_null() | data.is_null() | result.is_null() { + return Error::NullPointer.error_code(); + }; + + let encryptor = &mut *(ptr as *mut Mutex); + let mut encryptor = match encryptor.lock() { + Ok(c) => c, + Err(_) => return Error::PoisonedMutex.error_code(), + }; + + let data = slice::from_raw_parts(data, data_size); + let aad = slice::from_raw_parts(aad, aad_size); + + let encrypted = match encryptor.encrypt_next_chunk(data, aad) { + Ok(e) => e, + Err(e) => return e.error_code(), + }; + + if encrypted.len() != result_size { + return Error::InvalidOutputLength.error_code(); + } + + result.copy_from(encrypted.as_slice().as_ptr() as *const u8, result_size); + + result_size as i64 +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineDecryptorNextChunk( + ptr: *mut c_void, + data: *const u8, + data_size: usize, + aad: *const u8, + aad_size: usize, + result: *mut u8, + result_size: usize, +) -> i64 { + if aad.is_null() | data.is_null() | result.is_null() { + return Error::NullPointer.error_code(); + }; + + let decryptor = &mut *(ptr as *mut Mutex); + let mut decryptor = match decryptor.lock() { + Ok(c) => c, + Err(_) => return Error::PoisonedMutex.error_code(), + }; + + let data = slice::from_raw_parts(data, data_size); + let aad = slice::from_raw_parts(aad, aad_size); + + let decrypted = match decryptor.decrypt_next_chunk(data, aad) { + Ok(e) => e, + Err(e) => return e.error_code(), + }; + + if decrypted.len() != result_size { + return Error::InvalidOutputLength.error_code(); + } + + result.copy_from(decrypted.as_slice().as_ptr() as *const u8, result_size); + + result_size as i64 +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineEncryptorLastChunk( + ptr: *mut c_void, + data: *const u8, + data_size: usize, + aad: *const u8, + aad_size: usize, + result: *mut u8, + result_size: usize, +) -> i64 { + if aad.is_null() | data.is_null() | result.is_null() { + return Error::NullPointer.error_code(); + }; + + let encryptor = Box::from_raw(ptr as *mut Mutex); + let encryptor = match encryptor.into_inner() { + Ok(c) => c, + Err(_) => return Error::PoisonedMutex.error_code(), + }; + + let data = slice::from_raw_parts(data, data_size); + let aad = slice::from_raw_parts(aad, aad_size); + + let encrypted = match encryptor.encrypt_last_chunk(data, aad) { + Ok(e) => e, + Err(e) => return e.error_code(), + }; + + if result_size < encrypted.len() { + return Error::InvalidOutputLength.error_code(); + } + + result.copy_from(encrypted.as_slice().as_ptr() as *const u8, encrypted.len()); + + encrypted.len() as i64 +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineDecryptorLastChunk( + ptr: *mut c_void, + data: *const u8, + data_size: usize, + aad: *const u8, + aad_size: usize, + result: *mut u8, + result_size: usize, +) -> i64 { + if aad.is_null() | data.is_null() | result.is_null() { + return Error::NullPointer.error_code(); + }; + + let decryptor = Box::from_raw(ptr as *mut Mutex); + let decryptor = match decryptor.into_inner() { + Ok(c) => c, + Err(_) => return Error::PoisonedMutex.error_code(), + }; + + let data = slice::from_raw_parts(data, data_size); + let aad = slice::from_raw_parts(aad, aad_size); + + let decrypted = match decryptor.decrypt_last_chunk(data, aad) { + Ok(e) => e, + Err(e) => return e.error_code(), + }; + + if result_size < decrypted.len() { + return Error::InvalidOutputLength.error_code(); + } + + result.copy_from(decrypted.as_slice().as_ptr() as *const u8, decrypted.len()); + + decrypted.len() as i64 +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineEncryptorGetHeaderSize(ptr: *const c_void) -> i64 { + let encryptor = &*(ptr as *const Mutex); + let header = match encryptor.lock() { + Ok(c) => c.get_header(), + Err(_) => return Error::PoisonedMutex.error_code(), + }; + + header.get_serialized_size() as i64 +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineDecryptorGetHeaderSize(ptr: *const c_void) -> i64 { + let decryptor = &*(ptr as *const Mutex); + let header = match decryptor.lock() { + Ok(c) => c.get_header(), + Err(_) => return Error::PoisonedMutex.error_code(), + }; + + header.get_serialized_size() as i64 +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineEncryptorGetChunkSize(ptr: *const c_void) -> i64 { + let encryptor = &*(ptr as *const Mutex); + match encryptor.lock() { + Ok(c) => c.get_chunk_size() as i64, + Err(_) => Error::PoisonedMutex.error_code(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineDecryptorGetChunkSize(ptr: *const c_void) -> i64 { + let decryptor = &*(ptr as *const Mutex); + match decryptor.lock() { + Ok(c) => c.get_chunk_size() as i64, + Err(_) => Error::PoisonedMutex.error_code(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineEncryptorGetTagSize(ptr: *const c_void) -> i64 { + let encryptor = &*(ptr as *const Mutex); + match encryptor.lock() { + Ok(c) => c.get_tag_size() as i64, + Err(_) => return Error::PoisonedMutex.error_code(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn OnlineDecryptorGetTagSize(ptr: *const c_void) -> i64 { + let decryptor = &*(ptr as *const Mutex); + match decryptor.lock() { + Ok(c) => c.get_tag_size() as i64, + Err(_) => return Error::PoisonedMutex.error_code(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn FreeOnlineEncryptor(ptr: *mut c_void) { + drop(Box::from_raw(ptr as *mut Mutex)); +} + +#[no_mangle] +pub unsafe extern "C" fn FreeOnlineDecryptor(ptr: *mut c_void) { + drop(Box::from_raw(ptr as *mut Mutex)); +} + /// The size, in bytes, of the resulting secret /// # Arguments /// * share_length - The length of a share diff --git a/src/argon2parameters.rs b/src/argon2parameters.rs index 6a5a54143..5bebbd754 100644 --- a/src/argon2parameters.rs +++ b/src/argon2parameters.rs @@ -35,6 +35,7 @@ pub mod defaults { } /// Parameters used to derive the password into an Argon2 hash. +/// /// It is used to derive a password into a keypair. /// You should use the default, although this may be tweakable by the user in some cases. /// Once serialized, you can save it along the user information as it is not sensitive data. diff --git a/src/error.rs b/src/error.rs index 9798f9b3c..056eda6cf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -75,6 +75,9 @@ pub enum Error { /// The length of the data to encrypt/decrypt during online encryption is not the same as the chunk size: -43 #[error("The length of the data to encrypt/decrypt during online encryption is not the same as the chunk size")] InvalidChunkLength, + /// The mutex is poisoned and cannot be locked: -44 + #[error("The mutex is poisoned and cannot be locked")] + PoisonedMutex, } impl Error { @@ -99,6 +102,7 @@ impl Error { Error::NotEnoughShares => -41, Error::InconsistentVersion => -42, Error::InvalidChunkLength => -43, + Error::PoisonedMutex => -44, } } } diff --git a/src/online_ciphertext/mod.rs b/src/online_ciphertext/mod.rs index 660ee5a29..20c00c2bb 100644 --- a/src/online_ciphertext/mod.rs +++ b/src/online_ciphertext/mod.rs @@ -61,57 +61,6 @@ use online_ciphertext_v1::{ use paste::paste; -pub fn new_encryptor( - key: &[u8], - aad: &[u8], - chunk_size: u32, - version: OnlineCiphertextVersion, -) -> OnlineCiphertextEncryptor { - let mut header = Header:: { - data_subtype: CiphertextSubtype::Symmetric, - ..Default::default() - }; - - let mut full_aad: Vec = header.borrow().into(); - full_aad.extend_from_slice(aad); - - match version { - OnlineCiphertextVersion::V1 | OnlineCiphertextVersion::Latest => { - header.version = OnlineCiphertextVersion::V1; - - let cipher = OnlineCiphertextV1Encryptor::new(key, full_aad, chunk_size); - - OnlineCiphertextEncryptor::V1(cipher) - } - } -} - -pub fn new_encryptor_asymmetric( - public_key: &PublicKey, - aad: &[u8], - chunk_size: u32, - version: OnlineCiphertextVersion, -) -> OnlineCiphertextEncryptor { - let mut header = Header:: { - data_subtype: CiphertextSubtype::Asymmetric, - ..Default::default() - }; - - let mut full_aad: Vec = header.borrow().into(); - full_aad.extend_from_slice(aad); - - match version { - OnlineCiphertextVersion::V1 | OnlineCiphertextVersion::Latest => { - header.version = OnlineCiphertextVersion::V1; - - let cipher = - OnlineCiphertextV1Encryptor::new_asymmetric(public_key, full_aad, chunk_size); - - OnlineCiphertextEncryptor::V1(cipher) - } - } -} - impl OnlineCiphertextHeader { pub fn into_decryptor(self, key: &[u8], aad: &[u8]) -> Result { let mut full_aad: Vec = self.header.borrow().into(); @@ -148,6 +97,67 @@ impl OnlineCiphertextHeader { }, } } + + pub fn get_serialized_size(&self) -> usize { + self.payload.get_serialized_size() + 8 + } + + pub fn get_chunk_size(&self) -> u32 { + self.payload.get_chunk_size() + } +} + +impl OnlineCiphertextEncryptor { + pub fn new( + key: &[u8], + aad: &[u8], + chunk_size: u32, + version: OnlineCiphertextVersion, + ) -> OnlineCiphertextEncryptor { + let mut header = Header:: { + data_subtype: CiphertextSubtype::Symmetric, + ..Default::default() + }; + + let mut full_aad: Vec = header.borrow().into(); + full_aad.extend_from_slice(aad); + + match version { + OnlineCiphertextVersion::V1 | OnlineCiphertextVersion::Latest => { + header.version = OnlineCiphertextVersion::V1; + + let cipher = OnlineCiphertextV1Encryptor::new(key, full_aad, chunk_size); + + OnlineCiphertextEncryptor::V1(cipher) + } + } + } + + pub fn new_asymmetric( + public_key: &PublicKey, + aad: &[u8], + chunk_size: u32, + version: OnlineCiphertextVersion, + ) -> OnlineCiphertextEncryptor { + let mut header = Header:: { + data_subtype: CiphertextSubtype::Asymmetric, + ..Default::default() + }; + + let mut full_aad: Vec = header.borrow().into(); + full_aad.extend_from_slice(aad); + + match version { + OnlineCiphertextVersion::V1 | OnlineCiphertextVersion::Latest => { + header.version = OnlineCiphertextVersion::V1; + + let cipher = + OnlineCiphertextV1Encryptor::new_asymmetric(public_key, full_aad, chunk_size); + + OnlineCiphertextEncryptor::V1(cipher) + } + } + } } macro_rules! online_ciphertext_header_impl { @@ -176,6 +186,24 @@ macro_rules! online_ciphertext_header_impl { ),+ } + impl OnlineCiphertextHeaderPayload { + pub fn get_serialized_size(&self) -> usize { + match &self { + $( + Self::$version_name(p) => p.get_serialized_size(), + ),+ + } + } + + pub fn get_chunk_size(&self) -> u32 { + match &self { + $( + Self::$version_name(p) => p.get_chunk_size(), + ),+ + } + } + } + impl From<&OnlineCiphertextHeader> for Vec { fn from(value: &OnlineCiphertextHeader) -> Self { let mut output: Vec = value.header.borrow().into(); @@ -253,6 +281,16 @@ macro_rules! online_ciphertext_impl { } } + pub fn get_tag_size(&self) -> usize { + match &self { + $( + []::$version_name(cipher) => { + cipher.get_tag_size() + } + ),+ + } + } + pub fn get_header(&self) -> OnlineCiphertextHeader { match self { $( @@ -270,7 +308,7 @@ macro_rules! online_ciphertext_impl { } } - pub fn [<$func _chunk>]( + pub fn [<$func _next_chunk>]( &mut self, data: &[u8], aad: &[u8], @@ -278,13 +316,13 @@ macro_rules! online_ciphertext_impl { match self { $( []::$version_name(cipher) => { - cipher.[<$func _chunk>](data, aad) + cipher.[<$func _next_chunk>](data, aad) } ),+ } } - pub fn [<$func _chunk_in_place>]( + pub fn [<$func _next_chunk_in_place>]( &mut self, data: &mut Vec, aad: &[u8], @@ -292,13 +330,13 @@ macro_rules! online_ciphertext_impl { match self { $( []::$version_name(cipher) => { - cipher.[<$func _chunk_in_place>](data, aad) + cipher.[<$func _next_chunk_in_place>](data, aad) } ),+ } } - pub fn [<$func _last>]( + pub fn [<$func _last_chunk>]( self, data: &[u8], aad: &[u8], @@ -306,13 +344,13 @@ macro_rules! online_ciphertext_impl { match self { $( []::$version_name(cipher) => { - cipher.[<$func _last>](data, aad) + cipher.[<$func _last_chunk>](data, aad) } ),+ } } - pub fn [<$func _last_in_place>]( + pub fn [<$func _last_chunk_in_place>]( self, data: &mut Vec, aad: &[u8], @@ -320,7 +358,7 @@ macro_rules! online_ciphertext_impl { match self { $( []::$version_name(cipher) => { - cipher.[<$func _last_in_place>](data, aad) + cipher.[<$func _last_chunk_in_place>](data, aad) } ),+ } diff --git a/src/online_ciphertext/online_ciphertext_v1.rs b/src/online_ciphertext/online_ciphertext_v1.rs index fe0dadb1e..242fd66fb 100644 --- a/src/online_ciphertext/online_ciphertext_v1.rs +++ b/src/online_ciphertext/online_ciphertext_v1.rs @@ -1,7 +1,6 @@ -use crate::enums::CiphertextSubtype; - -///! Online Ciphertext V1: STREAM-LE31-XChaCha20Poly1305 +//! Online Ciphertext V1: STREAM-LE31-XChaCha20Poly1305 use super::{PrivateKey, PublicKey}; +use crate::enums::CiphertextSubtype; use super::Error; use super::Result; @@ -22,7 +21,7 @@ use paste::paste; /// Context string for the Blake3 KDF function. /// This is used to normalize the key length and domain separation -const CONTEXT: &'static str = "devolutions_crypto online_ciphertext_v1"; +const CONTEXT: &str = "devolutions_crypto online_ciphertext_v1"; #[derive(Clone, Debug)] pub enum OnlineCiphertextV1Header { @@ -44,6 +43,19 @@ pub struct OnlineCiphertextV1HeaderAsymmetric { } impl OnlineCiphertextV1Header { + pub fn get_serialized_size(&self) -> usize { + match self { + Self::Symmetric(_) => { + // chunk_size (u32, so 4 bytes) + 20 bytes nonce + 20 + 4 + } + Self::Asymmetric(_) => { + // chunk_size (u32, so 4 bytes) + 20 bytes nonce + 32 bytes public key + 20 + 4 + 32 + } + } + } + pub fn get_chunk_size(&self) -> u32 { match self { Self::Symmetric(x) => x.chunk_size, @@ -138,8 +150,7 @@ impl TryFrom<&[u8]> for OnlineCiphertextV1HeaderAsymmetric { let public_key: [u8; 32] = public_key .try_into() .expect("size is checked at the start of the function"); - let public_key = - x25519_dalek::PublicKey::try_from(public_key).map_err(|_| Error::InvalidData)?; + let public_key = x25519_dalek::PublicKey::from(public_key); Ok(Self { chunk_size, @@ -164,13 +175,17 @@ macro_rules! online_ciphertext_impl { self.header.get_chunk_size() } + pub fn get_tag_size(&self) -> usize { + 16 + } + pub fn get_header(&self) -> &OnlineCiphertextV1Header { &self.header } paste! { /// Process a single chunk - pub fn [<$func _chunk>]( + pub fn [<$func _next_chunk>]( &mut self, data: &[u8], aad: &[u8], @@ -195,7 +210,7 @@ macro_rules! online_ciphertext_impl { /// Process a single chunk in place. /// Requires a Vec because it needs to be expandable to accomodate the tag. - pub fn [<$func _chunk_in_place>]( + pub fn [<$func _next_chunk_in_place>]( &mut self, data: &mut Vec, aad: &[u8], @@ -216,7 +231,7 @@ macro_rules! online_ciphertext_impl { } /// Process the last chunk. - pub fn [<$func _last>]( + pub fn [<$func _last_chunk>]( self, data: &[u8], aad: &[u8], @@ -241,7 +256,7 @@ macro_rules! online_ciphertext_impl { /// Process a single chunk in place. /// Requires a Vec because it needs to be expandable to accomodate the tag. - pub fn [<$func _last_in_place>]( + pub fn [<$func _last_chunk_in_place>]( self, data: &mut Vec, aad: &[u8], diff --git a/src/utils.rs b/src/utils.rs index 3d1584d26..9c7d2fc00 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -126,6 +126,7 @@ pub fn validate_header(data: &[u8], data_type: DataType) -> bool { } /// Temporarly binded here for a specific use case, don't rely on this. +/// /// Copied and modified from: /// https://github.com/RustCrypto/password-hashing/blob/master/scrypt/src/simple.rs /// Because rand is outdated, I cannot use the crate directly From de7326d8fc436be97c2e2601c3bbff1b39c5a449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Dugr=C3=A9?= Date: Wed, 18 Dec 2024 16:43:33 -0500 Subject: [PATCH 12/21] [STREAM] C# EncryptionStream --- ffi/src/lib.rs | 52 +++++- wrappers/csharp/src/EncryptionStream.cs | 227 ++++++++++++++++++++++++ wrappers/csharp/src/Native.Core.cs | 48 +++++ wrappers/csharp/src/NativeError.cs | 12 +- 4 files changed, 330 insertions(+), 9 deletions(-) create mode 100644 wrappers/csharp/src/EncryptionStream.cs diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index 4c801f327..ca407ffe3 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -936,7 +936,7 @@ pub unsafe extern "C" fn OnlineEncryptorGetHeader( result: *mut u8, result_size: usize, ) -> i64 { - if result.is_null() { + if ptr.is_null() | result.is_null() { return Error::NullPointer.error_code(); }; @@ -961,7 +961,7 @@ pub unsafe extern "C" fn OnlineDecryptorGetHeader( result: *mut u8, result_size: usize, ) -> i64 { - if result.is_null() { + if ptr.is_null() | result.is_null() { return Error::NullPointer.error_code(); }; @@ -990,7 +990,7 @@ pub unsafe extern "C" fn OnlineEncryptorNextChunk( result: *mut u8, result_size: usize, ) -> i64 { - if aad.is_null() | data.is_null() | result.is_null() { + if ptr.is_null() | aad.is_null() | data.is_null() | result.is_null() { return Error::NullPointer.error_code(); }; @@ -1027,7 +1027,7 @@ pub unsafe extern "C" fn OnlineDecryptorNextChunk( result: *mut u8, result_size: usize, ) -> i64 { - if aad.is_null() | data.is_null() | result.is_null() { + if ptr.is_null() | aad.is_null() | data.is_null() | result.is_null() { return Error::NullPointer.error_code(); }; @@ -1064,7 +1064,7 @@ pub unsafe extern "C" fn OnlineEncryptorLastChunk( result: *mut u8, result_size: usize, ) -> i64 { - if aad.is_null() | data.is_null() | result.is_null() { + if ptr.is_null() | aad.is_null() | data.is_null() | result.is_null() { return Error::NullPointer.error_code(); }; @@ -1101,7 +1101,7 @@ pub unsafe extern "C" fn OnlineDecryptorLastChunk( result: *mut u8, result_size: usize, ) -> i64 { - if aad.is_null() | data.is_null() | result.is_null() { + if ptr.is_null() | aad.is_null() | data.is_null() | result.is_null() { return Error::NullPointer.error_code(); }; @@ -1130,6 +1130,10 @@ pub unsafe extern "C" fn OnlineDecryptorLastChunk( #[no_mangle] pub unsafe extern "C" fn OnlineEncryptorGetHeaderSize(ptr: *const c_void) -> i64 { + if ptr.is_null() { + return Error::NullPointer.error_code(); + }; + let encryptor = &*(ptr as *const Mutex); let header = match encryptor.lock() { Ok(c) => c.get_header(), @@ -1141,6 +1145,10 @@ pub unsafe extern "C" fn OnlineEncryptorGetHeaderSize(ptr: *const c_void) -> i64 #[no_mangle] pub unsafe extern "C" fn OnlineDecryptorGetHeaderSize(ptr: *const c_void) -> i64 { + if ptr.is_null() { + return Error::NullPointer.error_code(); + }; + let decryptor = &*(ptr as *const Mutex); let header = match decryptor.lock() { Ok(c) => c.get_header(), @@ -1152,6 +1160,10 @@ pub unsafe extern "C" fn OnlineDecryptorGetHeaderSize(ptr: *const c_void) -> i64 #[no_mangle] pub unsafe extern "C" fn OnlineEncryptorGetChunkSize(ptr: *const c_void) -> i64 { + if ptr.is_null() { + return Error::NullPointer.error_code(); + }; + let encryptor = &*(ptr as *const Mutex); match encryptor.lock() { Ok(c) => c.get_chunk_size() as i64, @@ -1161,6 +1173,10 @@ pub unsafe extern "C" fn OnlineEncryptorGetChunkSize(ptr: *const c_void) -> i64 #[no_mangle] pub unsafe extern "C" fn OnlineDecryptorGetChunkSize(ptr: *const c_void) -> i64 { + if ptr.is_null() { + return Error::NullPointer.error_code(); + }; + let decryptor = &*(ptr as *const Mutex); match decryptor.lock() { Ok(c) => c.get_chunk_size() as i64, @@ -1170,6 +1186,10 @@ pub unsafe extern "C" fn OnlineDecryptorGetChunkSize(ptr: *const c_void) -> i64 #[no_mangle] pub unsafe extern "C" fn OnlineEncryptorGetTagSize(ptr: *const c_void) -> i64 { + if ptr.is_null() { + return Error::NullPointer.error_code(); + }; + let encryptor = &*(ptr as *const Mutex); match encryptor.lock() { Ok(c) => c.get_tag_size() as i64, @@ -1179,6 +1199,10 @@ pub unsafe extern "C" fn OnlineEncryptorGetTagSize(ptr: *const c_void) -> i64 { #[no_mangle] pub unsafe extern "C" fn OnlineDecryptorGetTagSize(ptr: *const c_void) -> i64 { + if ptr.is_null() { + return Error::NullPointer.error_code(); + }; + let decryptor = &*(ptr as *const Mutex); match decryptor.lock() { Ok(c) => c.get_tag_size() as i64, @@ -1187,13 +1211,25 @@ pub unsafe extern "C" fn OnlineDecryptorGetTagSize(ptr: *const c_void) -> i64 { } #[no_mangle] -pub unsafe extern "C" fn FreeOnlineEncryptor(ptr: *mut c_void) { +pub unsafe extern "C" fn FreeOnlineEncryptor(ptr: *mut c_void) -> i64 { + if ptr.is_null() { + return Error::NullPointer.error_code(); + }; + drop(Box::from_raw(ptr as *mut Mutex)); + + 0 } #[no_mangle] -pub unsafe extern "C" fn FreeOnlineDecryptor(ptr: *mut c_void) { +pub unsafe extern "C" fn FreeOnlineDecryptor(ptr: *mut c_void) -> i64 { + if ptr.is_null() { + return Error::NullPointer.error_code(); + }; + drop(Box::from_raw(ptr as *mut Mutex)); + + 0 } /// The size, in bytes, of the resulting secret diff --git a/wrappers/csharp/src/EncryptionStream.cs b/wrappers/csharp/src/EncryptionStream.cs new file mode 100644 index 000000000..1f14fa791 --- /dev/null +++ b/wrappers/csharp/src/EncryptionStream.cs @@ -0,0 +1,227 @@ +using System; +using System.IO; + +namespace Devolutions.Cryptography +{ + public class EncryptionStream + : Stream, IDisposable + { + private UIntPtr native_ptr = UIntPtr.Zero; + private readonly int _chunkLength; + private readonly int _tagLength; + private bool _finalBlockTransformed = false; + private bool _leaveOpen; + + public int ChunkLength { get { return _chunkLength; } } + + public int TagLength { get { return _tagLength; } } + + public bool HasFlushedFinalBlock + { + get { return _finalBlockTransformed; } + } + + private readonly byte[] inputBuffer; + + private int inputBufferOffset = 0; + private bool disposed = false; + + private readonly Stream outputStream; + + public EncryptionStream(byte[] key, byte[] aad, int chunkLength, bool asymmetric, int version, Stream outputStream) + : this(key, aad, chunkLength, asymmetric, version, outputStream, false) { } + + public EncryptionStream(byte[] key, byte[] aad, int chunkLength, bool asymmetric, int version, Stream outputStream, bool leaveOpen) { + _chunkLength = chunkLength; + this.outputStream = outputStream; + this._leaveOpen = leaveOpen; + + long result = Native.NewOnlineEncryptor(key, (UIntPtr)key.Length, aad, (UIntPtr)aad.Length, (uint)chunkLength, asymmetric, (ushort)version, out native_ptr); + + if (result < 0) + { + Utils.HandleError(result); + } + + long tagSize = Native.OnlineEncryptorGetTagSize(native_ptr); + + if (tagSize < 0) + { + Utils.HandleError(tagSize); + } + + _tagLength = (int) tagSize; + + inputBuffer = new byte[chunkLength]; + } + + public byte[] GetHeader() + { + long headerSize = Native.OnlineEncryptorGetHeaderSize(native_ptr); + + if (headerSize < 0) + { + Utils.HandleError(headerSize); + } + + byte[] header = new byte[headerSize]; + + long result = Native.OnlineEncryptorGetHeader(native_ptr, header, (UIntPtr)headerSize); + + if (result < 0) + { + Utils.HandleError(result); + } + + return header; + } + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => true; + + public override long Length => throw new NotSupportedException(); + + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + + public void FlushFinalBlock() + { + if(HasFlushedFinalBlock) + { + throw new NotSupportedException(); + } + + if (inputBufferOffset > 0) + { + byte[] outputBuffer = EncryptLastChunk(); + + outputStream.Write(outputBuffer, 0, outputBuffer.Length); + } + + _finalBlockTransformed = true; + } + + public override void Flush() + { + return; + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + if(HasFlushedFinalBlock) + { + // Cannot write as stream is already closed + return; + }; + + while (count > ChunkLength - inputBufferOffset) + { + // Here we write every finished blocks + int countToAdd = ChunkLength - inputBufferOffset; + Buffer.BlockCopy(buffer, offset, inputBuffer, inputBufferOffset, countToAdd); + + // Encrypt the buffer + byte[] outputBuffer = EncryptChunk(); + + // Write the output to the stream + outputStream.Write(outputBuffer, 0, outputBuffer.Length); + + count -= countToAdd; + offset += countToAdd; + inputBufferOffset = 0; + } + + if (count > 0) { + Buffer.BlockCopy(buffer, offset, inputBuffer, inputBufferOffset, count); + + inputBufferOffset += count; + } + } + + private byte[] EncryptChunk() + { + byte[] aad = new byte[0]; + byte[] outputBuffer = new byte[ChunkLength + TagLength]; + + long result = Native.OnlineEncryptorNextChunk(native_ptr, inputBuffer, (UIntPtr) ChunkLength, aad, UIntPtr.Zero, outputBuffer, (UIntPtr) outputBuffer.Length); + + if (result < 0) { + Utils.HandleError(result); + } + + return outputBuffer; + } + + private byte[] EncryptLastChunk() + { + byte[] aad = new byte[0]; + byte[] outputBuffer = new byte[inputBufferOffset + TagLength]; + + long result = Native.OnlineEncryptorLastChunk(native_ptr, inputBuffer, (UIntPtr) inputBufferOffset, aad, UIntPtr.Zero, outputBuffer, (UIntPtr)outputBuffer.Length); + + if (result < 0) + { + Utils.HandleError(result); + } + + // Here, the pointer is freed, so let's set it to 0 + native_ptr = UIntPtr.Zero; + + return outputBuffer; + } + + public new void Dispose() + { + base.Dispose(); + + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected override void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + if(!HasFlushedFinalBlock) + { + FlushFinalBlock(); + }; + + if(!_leaveOpen) + { + outputStream.Close(); + } + } + base.Dispose(disposing: disposing); + + // If the ptr has not been freed yet, do it now + if (native_ptr != UIntPtr.Zero) + { + Native.FreeOnlineEncryptor(native_ptr); + native_ptr = UIntPtr.Zero; + } + + disposed = true; + } + } + } +} \ No newline at end of file diff --git a/wrappers/csharp/src/Native.Core.cs b/wrappers/csharp/src/Native.Core.cs index 903e8e631..5778b1893 100644 --- a/wrappers/csharp/src/Native.Core.cs +++ b/wrappers/csharp/src/Native.Core.cs @@ -101,6 +101,54 @@ public static partial class Native [DllImport(LibName, EntryPoint = "MixKeyExchangeSize", CallingConvention = CallingConvention.Cdecl)] internal static extern long MixKeyExchangeSizeNative(); + [DllImport(LibName, EntryPoint = "NewOnlineEncryptor", CallingConvention = CallingConvention.Cdecl)] + internal static extern long NewOnlineEncryptor(byte[] key, UIntPtr keyLength, byte[] aad, UIntPtr aadLength, UInt32 chunkLength, bool asymmetric, UInt16 version, out UIntPtr output); + + [DllImport(LibName, EntryPoint = "NewOnlineDecryptor", CallingConvention = CallingConvention.Cdecl)] + internal static extern long NewOnlineDecryptor(byte[] key, UIntPtr keyLength, byte[] aad, UIntPtr aadLength, byte[] header, UIntPtr headerLength, bool asymmetric, out UIntPtr output); + + [DllImport(LibName, EntryPoint = "FreeOnlineEncryptor", CallingConvention = CallingConvention.Cdecl)] + internal static extern long FreeOnlineEncryptor(UIntPtr ptr); + + [DllImport(LibName, EntryPoint = "FreeOnlineDecryptor", CallingConvention = CallingConvention.Cdecl)] + internal static extern long FreeOnlineDecryptor(UIntPtr ptr); + + [DllImport(LibName, EntryPoint = "OnlineEncryptorGetChunkSize", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineEncryptorGetChunkSize(UIntPtr ptr); + + [DllImport(LibName, EntryPoint = "OnlineDecryptorGetChunkSize", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineDecryptorGetChunkSize(UIntPtr ptr); + + [DllImport(LibName, EntryPoint = "OnlineEncryptorGetHeader", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineEncryptorGetHeader(UIntPtr ptr, byte[] result, UIntPtr resultLength); + + [DllImport(LibName, EntryPoint = "OnlineDecryptorGetHeader", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineDecryptorGetHeader(UIntPtr ptr, byte[] result, UIntPtr resultLength); + + [DllImport(LibName, EntryPoint = "OnlineEncryptorGetHeaderSize", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineEncryptorGetHeaderSize(UIntPtr ptr); + + [DllImport(LibName, EntryPoint = "OnlineDecryptorGetHeaderSize", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineDecryptorGetHeaderSize(UIntPtr ptr); + + [DllImport(LibName, EntryPoint = "OnlineEncryptorGetTagSize", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineEncryptorGetTagSize(UIntPtr ptr); + + [DllImport(LibName, EntryPoint = "OnlineDecryptorGetTagSize", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineDecryptorGetTagSize(UIntPtr ptr); + + [DllImport(LibName, EntryPoint = "OnlineEncryptorNextChunk", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineEncryptorNextChunk(UIntPtr ptr, byte[] data, UIntPtr dataLength, byte[] aad, UIntPtr aadLength, byte[] result, UIntPtr resultSize); + + [DllImport(LibName, EntryPoint = "OnlineDecryptorNextChunk", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineDecryptorNextChunk(UIntPtr ptr, byte[] data, UIntPtr dataLength, byte[] aad, UIntPtr aadLength, byte[] result, UIntPtr resultSize); + + [DllImport(LibName, EntryPoint = "OnlineEncryptorLastChunk", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineEncryptorLastChunk(UIntPtr ptr, byte[] data, UIntPtr dataLength, byte[] aad, UIntPtr aadLength, byte[] result, UIntPtr resultSize); + + [DllImport(LibName, EntryPoint = "OnlineDecryptorLastChunk", CallingConvention = CallingConvention.Cdecl)] + internal static extern long OnlineDecryptorLastChunk(UIntPtr ptr, byte[] data, UIntPtr dataLength, byte[] aad, UIntPtr aadLength, byte[] result, UIntPtr resultSize); + [DllImport(LibName, EntryPoint = "ValidateHeader", CallingConvention = CallingConvention.Cdecl)] internal static extern long ValidateHeader(byte[] data, UIntPtr dataLength, ushort dataType); diff --git a/wrappers/csharp/src/NativeError.cs b/wrappers/csharp/src/NativeError.cs index 532bc402f..0b13d9465 100644 --- a/wrappers/csharp/src/NativeError.cs +++ b/wrappers/csharp/src/NativeError.cs @@ -84,5 +84,15 @@ public enum NativeError /// The version of the multiple data is inconsistent: -42 /// InconsistentVersion = -42, + + /// + /// The length of the data to encrypt/decrypt during online encryption is not the same as the chunk size: -43 + /// + InvalidChunkLength = -43, + + /// + /// The mutex is poisoned and cannot be locked: -44 + /// + PoisonedMutex = -44, } -} \ No newline at end of file +} From eeb55cac64eb7f1f3e0b8b7ffcdfb0ec1893a0e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Dugr=C3=A9?= Date: Fri, 20 Dec 2024 15:08:59 -0500 Subject: [PATCH 13/21] [STREAM] Attempt to fix segfault --- ffi/src/lib.rs | 8 +- src/header.rs | 16 +- wrappers/csharp/src/DecryptionStream.cs | 241 ++++++++++++++++++ wrappers/csharp/src/EncryptionStream.cs | 75 +++--- wrappers/csharp/src/Enums.cs | 5 + wrappers/csharp/src/Native.Core.cs | 4 +- wrappers/csharp/src/Native.cs | 4 +- wrappers/csharp/tests/unit-tests/TestData.cs | 2 + .../csharp/tests/unit-tests/TestStreams.cs | 40 +++ 9 files changed, 347 insertions(+), 48 deletions(-) create mode 100644 wrappers/csharp/src/DecryptionStream.cs create mode 100644 wrappers/csharp/tests/unit-tests/TestStreams.cs diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index ca407ffe3..d81785e51 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -43,6 +43,7 @@ use devolutions_crypto::Result; use std::borrow::Borrow; use std::ffi::c_void; +use std::mem::MaybeUninit; use std::slice; use std::sync::Mutex; @@ -1068,7 +1069,9 @@ pub unsafe extern "C" fn OnlineEncryptorLastChunk( return Error::NullPointer.error_code(); }; + // TODO: This conversion seems to cause a memory corruption let encryptor = Box::from_raw(ptr as *mut Mutex); + let encryptor = match encryptor.into_inner() { Ok(c) => c, Err(_) => return Error::PoisonedMutex.error_code(), @@ -1078,8 +1081,8 @@ pub unsafe extern "C" fn OnlineEncryptorLastChunk( let aad = slice::from_raw_parts(aad, aad_size); let encrypted = match encryptor.encrypt_last_chunk(data, aad) { - Ok(e) => e, - Err(e) => return e.error_code(), + Ok(e) => e, + Err(e) => return e.error_code(), }; if result_size < encrypted.len() { @@ -1105,6 +1108,7 @@ pub unsafe extern "C" fn OnlineDecryptorLastChunk( return Error::NullPointer.error_code(); }; + // TODO: This conversion seems to cause a memory corruption let decryptor = Box::from_raw(ptr as *mut Mutex); let decryptor = match decryptor.into_inner() { Ok(c) => c, diff --git a/src/header.rs b/src/header.rs index 6cb6ecb7f..37140c119 100644 --- a/src/header.rs +++ b/src/header.rs @@ -18,12 +18,12 @@ const SIGNATURE: u16 = 0x0C0D; pub trait HeaderType { cfg_if! { if #[cfg(feature = "fuzz")] { - type Version: Into + TryFrom + Clone + Copy + Default + Zeroize + std::fmt::Debug + Arbitrary; - type Subtype: Into + TryFrom + Clone + Copy + Default + Zeroize + std::fmt::Debug + Arbitrary; + type Version: Into + TryFrom + Clone + Default + Zeroize + std::fmt::Debug + Arbitrary; + type Subtype: Into + TryFrom + Clone + Default + Zeroize + std::fmt::Debug + Arbitrary; } else { - type Version: Into + TryFrom + Clone + Copy + Default + Zeroize + std::fmt::Debug; - type Subtype: Into + TryFrom + Clone + Copy + Default + Zeroize + std::fmt::Debug; + type Version: Into + TryFrom + Clone + Default + Zeroize + std::fmt::Debug; + type Subtype: Into + TryFrom + Clone + Default + Zeroize + std::fmt::Debug; } } @@ -118,17 +118,17 @@ where M: HeaderType, { fn from(header: &Header) -> Self { - let data = [0u8; 8]; - let mut cursor = Cursor::new(data); + let mut data = [0u8; 8]; + let mut cursor = Cursor::new(data.as_mut_slice()); cursor.write_u16::(header.signature).unwrap(); cursor .write_u16::(header.data_type.into()) .unwrap(); cursor - .write_u16::(header.data_subtype.into()) + .write_u16::(header.data_subtype.clone().into()) .unwrap(); cursor - .write_u16::(header.version.into()) + .write_u16::(header.version.clone().into()) .unwrap(); data } diff --git a/wrappers/csharp/src/DecryptionStream.cs b/wrappers/csharp/src/DecryptionStream.cs new file mode 100644 index 000000000..4412266e6 --- /dev/null +++ b/wrappers/csharp/src/DecryptionStream.cs @@ -0,0 +1,241 @@ +using System; +using System.IO; + +namespace Devolutions.Cryptography +{ + public class DecryptionStream + : Stream, IDisposable + { + private UIntPtr _native_ptr = UIntPtr.Zero; + private readonly int _chunkLength; + private readonly int _tagLength; + private bool _finalBlockTransformed = false; + private readonly bool _leaveOpen; + + public int ChunkLength { get { return _chunkLength; } } + + public int TagLength { get { return _tagLength; } } + + public bool HasFlushedFinalBlock + { + get { return _finalBlockTransformed; } + } + + private readonly byte[] _inputBuffer; + + private int _inputBufferOffset = 0; + private bool _disposed = false; + + private readonly Stream _outputStream; + + public DecryptionStream(byte[] key, byte[] aad, byte[] header, bool asymmetric, Stream outputStream) + : this(key, aad, header, asymmetric, outputStream, false) { } + + public DecryptionStream(byte[] key, byte[] aad, byte[] header, bool asymmetric, Stream outputStream, bool leaveOpen) { + _outputStream = outputStream; + _leaveOpen = leaveOpen; + + long result = Native.NewOnlineDecryptor(key, (UIntPtr)key.Length, aad, (UIntPtr)aad.Length, header, (UIntPtr)header.Length, asymmetric, ref _native_ptr); + + if (result < 0) + { + Utils.HandleError(result); + } + + long tagSize = Native.OnlineDecryptorGetTagSize(_native_ptr); + + if (tagSize < 0) + { + Utils.HandleError(tagSize); + } + + long chunkLength = Native.OnlineDecryptorGetChunkSize(_native_ptr); + + if (tagSize < 0) + { + Utils.HandleError(chunkLength); + } + + _tagLength = (int) tagSize; + _chunkLength = (int)chunkLength + _tagLength; + + _inputBuffer = new byte[_chunkLength]; + } + + public byte[] GetHeader() + { + long headerSize = Native.OnlineDecryptorGetHeaderSize(_native_ptr); + + if (headerSize < 0) + { + Utils.HandleError(headerSize); + } + + byte[] header = new byte[headerSize]; + + long result = Native.OnlineDecryptorGetHeader(_native_ptr, header, (UIntPtr)headerSize); + + if (result < 0) + { + Utils.HandleError(result); + } + + return header; + } + + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => true; + + public override long Length => throw new NotSupportedException(); + + public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + + public void FlushFinalBlock() + { + if(HasFlushedFinalBlock) + { + throw new NotSupportedException(); + } + + if (_inputBufferOffset > 0) + { + byte[] outputBuffer = DecryptLastChunk(); + + _outputStream.Write(outputBuffer, 0, outputBuffer.Length); + } + + Array.Clear(_inputBuffer, 0, _inputBuffer.Length); + + _finalBlockTransformed = true; + } + + public override void Flush() + { + return; + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + if(HasFlushedFinalBlock) + { + // Cannot write as stream is already closed + return; + }; + + while (count > ChunkLength - _inputBufferOffset) + { + // Here we write every finished blocks + int countToAdd = ChunkLength - _inputBufferOffset; + Buffer.BlockCopy(buffer, offset, _inputBuffer, _inputBufferOffset, countToAdd); + + // Encrypt the buffer + byte[] outputBuffer = DecryptChunk(); + + // Write the output to the stream + _outputStream.Write(outputBuffer, 0, outputBuffer.Length); + + count -= countToAdd; + offset += countToAdd; + _inputBufferOffset = 0; + } + + if (count > 0) { + Buffer.BlockCopy(buffer, offset, _inputBuffer, _inputBufferOffset, count); + + _inputBufferOffset += count; + } + } + + private byte[] DecryptChunk() + { + byte[] aad = new byte[0]; + byte[] outputBuffer = new byte[ChunkLength - TagLength]; + + long result = Native.OnlineDecryptorNextChunk(_native_ptr, _inputBuffer, (UIntPtr) ChunkLength, aad, UIntPtr.Zero, outputBuffer, (UIntPtr) outputBuffer.Length); + + if (result < 0) { + Utils.HandleError(result); + } + + return outputBuffer; + } + + private byte[] DecryptLastChunk() + { + byte[] aad = new byte[0]; + byte[] outputBuffer = new byte[_inputBufferOffset - TagLength]; + + long result = Native.OnlineDecryptorLastChunk(_native_ptr, _inputBuffer, (UIntPtr) _inputBufferOffset, aad, UIntPtr.Zero, outputBuffer, (UIntPtr)outputBuffer.Length); + + if (result < 0) + { + Utils.HandleError(result); + } + + // Here, the pointer is freed, so let's set it to 0 + _native_ptr = UIntPtr.Zero; + + return outputBuffer; + } + + public new void Dispose() + { + base.Dispose(); + + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected override void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + if(!HasFlushedFinalBlock) + { + FlushFinalBlock(); + }; + + if(!_leaveOpen) + { + _outputStream.Close(); + } + } + base.Dispose(disposing: disposing); + + // If the ptr has not been freed yet, do it now + FreeNativeObject(); + + _disposed = true; + } + } + + private void FreeNativeObject() + { + if (_native_ptr != UIntPtr.Zero) + { + Native.FreeOnlineDecryptor(_native_ptr); + _native_ptr = UIntPtr.Zero; + } + } + } +} \ No newline at end of file diff --git a/wrappers/csharp/src/EncryptionStream.cs b/wrappers/csharp/src/EncryptionStream.cs index 1f14fa791..b4d7633f3 100644 --- a/wrappers/csharp/src/EncryptionStream.cs +++ b/wrappers/csharp/src/EncryptionStream.cs @@ -6,11 +6,11 @@ namespace Devolutions.Cryptography public class EncryptionStream : Stream, IDisposable { - private UIntPtr native_ptr = UIntPtr.Zero; + private UIntPtr _native_ptr = UIntPtr.Zero; private readonly int _chunkLength; private readonly int _tagLength; private bool _finalBlockTransformed = false; - private bool _leaveOpen; + private readonly bool _leaveOpen; public int ChunkLength { get { return _chunkLength; } } @@ -21,29 +21,29 @@ public bool HasFlushedFinalBlock get { return _finalBlockTransformed; } } - private readonly byte[] inputBuffer; + private readonly byte[] _inputBuffer; - private int inputBufferOffset = 0; - private bool disposed = false; + private int _inputBufferOffset = 0; + private bool _disposed = false; - private readonly Stream outputStream; + private readonly Stream _outputStream; public EncryptionStream(byte[] key, byte[] aad, int chunkLength, bool asymmetric, int version, Stream outputStream) : this(key, aad, chunkLength, asymmetric, version, outputStream, false) { } public EncryptionStream(byte[] key, byte[] aad, int chunkLength, bool asymmetric, int version, Stream outputStream, bool leaveOpen) { _chunkLength = chunkLength; - this.outputStream = outputStream; - this._leaveOpen = leaveOpen; + _outputStream = outputStream; + _leaveOpen = leaveOpen; - long result = Native.NewOnlineEncryptor(key, (UIntPtr)key.Length, aad, (UIntPtr)aad.Length, (uint)chunkLength, asymmetric, (ushort)version, out native_ptr); + long result = Native.NewOnlineEncryptor(key, (UIntPtr)key.Length, aad, (UIntPtr)aad.Length, (uint)chunkLength, asymmetric, (ushort)version, ref _native_ptr); if (result < 0) { Utils.HandleError(result); } - long tagSize = Native.OnlineEncryptorGetTagSize(native_ptr); + long tagSize = Native.OnlineEncryptorGetTagSize(_native_ptr); if (tagSize < 0) { @@ -52,12 +52,12 @@ public EncryptionStream(byte[] key, byte[] aad, int chunkLength, bool asymmetric _tagLength = (int) tagSize; - inputBuffer = new byte[chunkLength]; + _inputBuffer = new byte[chunkLength]; } public byte[] GetHeader() { - long headerSize = Native.OnlineEncryptorGetHeaderSize(native_ptr); + long headerSize = Native.OnlineEncryptorGetHeaderSize(_native_ptr); if (headerSize < 0) { @@ -66,7 +66,7 @@ public byte[] GetHeader() byte[] header = new byte[headerSize]; - long result = Native.OnlineEncryptorGetHeader(native_ptr, header, (UIntPtr)headerSize); + long result = Native.OnlineEncryptorGetHeader(_native_ptr, header, (UIntPtr)headerSize); if (result < 0) { @@ -93,13 +93,15 @@ public void FlushFinalBlock() throw new NotSupportedException(); } - if (inputBufferOffset > 0) + if (_inputBufferOffset > 0) { byte[] outputBuffer = EncryptLastChunk(); - outputStream.Write(outputBuffer, 0, outputBuffer.Length); + _outputStream.Write(outputBuffer, 0, outputBuffer.Length); } + Array.Clear(_inputBuffer, 0, _inputBuffer.Length); + _finalBlockTransformed = true; } @@ -131,27 +133,27 @@ public override void Write(byte[] buffer, int offset, int count) return; }; - while (count > ChunkLength - inputBufferOffset) + while (count > ChunkLength - _inputBufferOffset) { // Here we write every finished blocks - int countToAdd = ChunkLength - inputBufferOffset; - Buffer.BlockCopy(buffer, offset, inputBuffer, inputBufferOffset, countToAdd); + int countToAdd = ChunkLength - _inputBufferOffset; + Buffer.BlockCopy(buffer, offset, _inputBuffer, _inputBufferOffset, countToAdd); // Encrypt the buffer byte[] outputBuffer = EncryptChunk(); // Write the output to the stream - outputStream.Write(outputBuffer, 0, outputBuffer.Length); + _outputStream.Write(outputBuffer, 0, outputBuffer.Length); count -= countToAdd; offset += countToAdd; - inputBufferOffset = 0; + _inputBufferOffset = 0; } if (count > 0) { - Buffer.BlockCopy(buffer, offset, inputBuffer, inputBufferOffset, count); + Buffer.BlockCopy(buffer, offset, _inputBuffer, _inputBufferOffset, count); - inputBufferOffset += count; + _inputBufferOffset += count; } } @@ -160,7 +162,7 @@ private byte[] EncryptChunk() byte[] aad = new byte[0]; byte[] outputBuffer = new byte[ChunkLength + TagLength]; - long result = Native.OnlineEncryptorNextChunk(native_ptr, inputBuffer, (UIntPtr) ChunkLength, aad, UIntPtr.Zero, outputBuffer, (UIntPtr) outputBuffer.Length); + long result = Native.OnlineEncryptorNextChunk(_native_ptr, _inputBuffer, (UIntPtr) ChunkLength, aad, UIntPtr.Zero, outputBuffer, (UIntPtr) outputBuffer.Length); if (result < 0) { Utils.HandleError(result); @@ -172,9 +174,9 @@ private byte[] EncryptChunk() private byte[] EncryptLastChunk() { byte[] aad = new byte[0]; - byte[] outputBuffer = new byte[inputBufferOffset + TagLength]; + byte[] outputBuffer = new byte[_inputBufferOffset + TagLength]; - long result = Native.OnlineEncryptorLastChunk(native_ptr, inputBuffer, (UIntPtr) inputBufferOffset, aad, UIntPtr.Zero, outputBuffer, (UIntPtr)outputBuffer.Length); + long result = Native.OnlineEncryptorLastChunk(_native_ptr, _inputBuffer, (UIntPtr)_inputBufferOffset, aad, UIntPtr.Zero, outputBuffer, (UIntPtr)outputBuffer.Length); if (result < 0) { @@ -182,7 +184,7 @@ private byte[] EncryptLastChunk() } // Here, the pointer is freed, so let's set it to 0 - native_ptr = UIntPtr.Zero; + _native_ptr = UIntPtr.Zero; return outputBuffer; } @@ -197,7 +199,7 @@ private byte[] EncryptLastChunk() protected override void Dispose(bool disposing) { - if (!disposed) + if (!_disposed) { if (disposing) { @@ -208,19 +210,24 @@ protected override void Dispose(bool disposing) if(!_leaveOpen) { - outputStream.Close(); + _outputStream.Close(); } } base.Dispose(disposing: disposing); // If the ptr has not been freed yet, do it now - if (native_ptr != UIntPtr.Zero) - { - Native.FreeOnlineEncryptor(native_ptr); - native_ptr = UIntPtr.Zero; - } + FreeNativeObject(); + + _disposed = true; + } + } - disposed = true; + private void FreeNativeObject() + { + if (_native_ptr != UIntPtr.Zero) + { + Native.FreeOnlineEncryptor(_native_ptr); + _native_ptr = UIntPtr.Zero; } } } diff --git a/wrappers/csharp/src/Enums.cs b/wrappers/csharp/src/Enums.cs index ffd712fc6..e37d54c2a 100644 --- a/wrappers/csharp/src/Enums.cs +++ b/wrappers/csharp/src/Enums.cs @@ -42,6 +42,11 @@ public enum DataType /// A wrapped signature /// Signature = 6, + + /// + /// TODO + /// + OnlineCiphertext = 7 } /// diff --git a/wrappers/csharp/src/Native.Core.cs b/wrappers/csharp/src/Native.Core.cs index 5778b1893..6ea4dbe98 100644 --- a/wrappers/csharp/src/Native.Core.cs +++ b/wrappers/csharp/src/Native.Core.cs @@ -102,10 +102,10 @@ public static partial class Native internal static extern long MixKeyExchangeSizeNative(); [DllImport(LibName, EntryPoint = "NewOnlineEncryptor", CallingConvention = CallingConvention.Cdecl)] - internal static extern long NewOnlineEncryptor(byte[] key, UIntPtr keyLength, byte[] aad, UIntPtr aadLength, UInt32 chunkLength, bool asymmetric, UInt16 version, out UIntPtr output); + internal static extern long NewOnlineEncryptor(byte[] key, UIntPtr keyLength, byte[] aad, UIntPtr aadLength, UInt32 chunkLength, bool asymmetric, UInt16 version, ref UIntPtr output); [DllImport(LibName, EntryPoint = "NewOnlineDecryptor", CallingConvention = CallingConvention.Cdecl)] - internal static extern long NewOnlineDecryptor(byte[] key, UIntPtr keyLength, byte[] aad, UIntPtr aadLength, byte[] header, UIntPtr headerLength, bool asymmetric, out UIntPtr output); + internal static extern long NewOnlineDecryptor(byte[] key, UIntPtr keyLength, byte[] aad, UIntPtr aadLength, byte[] header, UIntPtr headerLength, bool asymmetric, ref UIntPtr output); [DllImport(LibName, EntryPoint = "FreeOnlineEncryptor", CallingConvention = CallingConvention.Cdecl)] internal static extern long FreeOnlineEncryptor(UIntPtr ptr); diff --git a/wrappers/csharp/src/Native.cs b/wrappers/csharp/src/Native.cs index 14dc016c1..bd6d3969f 100644 --- a/wrappers/csharp/src/Native.cs +++ b/wrappers/csharp/src/Native.cs @@ -31,8 +31,8 @@ public static partial class Native #endif #if !DEBUG - private const string NativeVersion = "||NATIVE_VERSION||"; - private const string ManagedVersion = "||MANAGED_VERSION||"; + private const string NativeVersion = "0.9.1"; + private const string ManagedVersion = "2023.11.02"; #endif static Native() diff --git a/wrappers/csharp/tests/unit-tests/TestData.cs b/wrappers/csharp/tests/unit-tests/TestData.cs index 823c4a5ef..51d7badcd 100644 --- a/wrappers/csharp/tests/unit-tests/TestData.cs +++ b/wrappers/csharp/tests/unit-tests/TestData.cs @@ -18,6 +18,8 @@ public static class TestData public const string Base64TestData2 = "QUJDDE"; + public const string Base64TestDataStream = "DO5C7xhZ5J1QHqS4z606FiLH6vhZz7GgLfV3dsMkDA3tnzAkSvyl+xaV7ZLNGBe9JcdXI5oZHIaSttbFQaNrPtB/kz0PUGxgErrJ6YOymVKRiX7ZQa+ePv/DZyOUwcUHgRqAF/x0r8i4+WeNrZBbf1EQ3YuLJpWbI4//KManC37n6k0kH7U2r4MtFTHwiuCVSPa56hPgOSccm8lzhx6Sb2CR5+1Rs/Ez7tEG2Rd6IwJ+GhCeaOVetWVNmGcc4l/2EJtyxIdKQ0FAppmyAtHgGXFeoIPDWhWwrLlhtYF+/7Vnn1jtvI4kHtBzHRmxX+EaUJ8G9Ho9C+1Z5sBcAezDUji6gQ+IMlDcj2eR3ZuA1Lj/AnuB31eZXIAW0ailcbTBpitzXBCEREg6SWQwKHqgb8ao0ooJ6Cbit5I0Fxba7b7vsa3WeowiAsxUgNfrZFKvwLV8K4yAvP66ypM9tnOqt4c/Vy+OgiPoZqOTa1hfgcNgFMGgb1DcqLnGOBm6gWONCT5rD93DPxMKiDFeEuTJo/mN+7lTiCJczk1Gol2FZSQGqjs1laHSA2Oxdi3jvZSRuS58/XGVH1Sn2PvA6BOsTiZUK/9Ie7A2urqo1l4afg8oTCgtHkoPcM7PfGvnNEyispq7gP5V0IO83zgQE159ICHqOilbidUSEn+FGWtBSkfmE9BwfxrpM8w4DnIufX52W8Nbyf81NbWQAN+bRVNvXHdZdCmqwAqE3HTTSDnyQF046JGQ4N9YV8cIxs8YwJ6cEj/ilRmIHGRwTcD8PfzGz+cr/yN6o6BkrYHMfpHz984VzBR2Xf+w3PA3GeKwFKHZ0Lskfp+SXvUZfzkjOn7GQcDwuCbu/NFLLosad7WdUUMgWsZiHUXlCfSMu/y5kovEgN3j7Ky1O/LTrqPIeG+xA774unNhnr4N7brIaJM5qSuWZYgsSiR48FDzug3QVHJWwZt72BOcp7eFRNHkJN2wYKlVjSd2rrXZy1qjV6H1DvLpmKBLm47ihybvAmgoZr9Z2ZkT2UUceOmmJ5TmoEIK+RwoRumO7U0Ry4VyvNF7biZcpMqNTdwXAGbK1xs+sIretzQFb8+JvAbQAuPSJGei+A1C2m/QKGawAm6+0mK3qYCcKsh4w644a+5+63/fjsYfk0GM3cX5En8HCspcCq9y3gWxX76Wbq8+IKxQDXpXBkD3fO5qRsTshlG/tg+b0un6dAeOvF7SVsxQjjKiXjk5SN3w+py4rQdkN6IFPW1ieeSf/BW9xudat9dSAFgR0YgMaZ6AryqsQtUUwDp67+8odTGooadsZXW6sGVCs5/Vr4xnTle2F6eLNw=="; + public const string StringTestData = "ABC"; public const string TestPassword = "Key123"; diff --git a/wrappers/csharp/tests/unit-tests/TestStreams.cs b/wrappers/csharp/tests/unit-tests/TestStreams.cs new file mode 100644 index 000000000..940b8a8e8 --- /dev/null +++ b/wrappers/csharp/tests/unit-tests/TestStreams.cs @@ -0,0 +1,40 @@ +#pragma warning disable SA1600 // Elements should be documented +namespace Devolutions.Crypto.Tests +{ + using System.IO; + using Devolutions.Cryptography; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class TestStreams + { + [TestMethod] + public void EncryptStream() + { + byte[] base64DataAsUtf8ByteArray = Utils.StringToUtf8ByteArray(TestData.Base64TestDataStream); + + byte[] header; + + using (MemoryStream ms = new MemoryStream()) + { + using (EncryptionStream ec = new EncryptionStream(TestData.BytesTestKey, System.Array.Empty(), 1000, false, 0, ms)) + { + header = ec.GetHeader(); + + Assert.IsTrue(Utils.ValidateHeader(header, DataType.OnlineCiphertext)); + + using (StreamWriter writer = new StreamWriter(ec)) + { + //writer.Write(new byte[3]); + writer.Write(base64DataAsUtf8ByteArray); + } + } + + byte[] result = ms.ToArray(); + + Assert.AreNotEqual(result, base64DataAsUtf8ByteArray); + Assert.AreNotEqual(result, new byte[1000]); + } + } + } +} \ No newline at end of file From eee35c7ac33c2fa3bc002f7ae33fc7f25162ba4b Mon Sep 17 00:00:00 2001 From: Mathieu Morrissette Date: Tue, 14 Jan 2025 12:26:50 -0500 Subject: [PATCH 14/21] [WIP] Fix last chunk size --- ffi/src/lib.rs | 2 -- src/online_ciphertext/online_ciphertext_v1.rs | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index d81785e51..f826fdac0 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -1069,7 +1069,6 @@ pub unsafe extern "C" fn OnlineEncryptorLastChunk( return Error::NullPointer.error_code(); }; - // TODO: This conversion seems to cause a memory corruption let encryptor = Box::from_raw(ptr as *mut Mutex); let encryptor = match encryptor.into_inner() { @@ -1108,7 +1107,6 @@ pub unsafe extern "C" fn OnlineDecryptorLastChunk( return Error::NullPointer.error_code(); }; - // TODO: This conversion seems to cause a memory corruption let decryptor = Box::from_raw(ptr as *mut Mutex); let decryptor = match decryptor.into_inner() { Ok(c) => c, diff --git a/src/online_ciphertext/online_ciphertext_v1.rs b/src/online_ciphertext/online_ciphertext_v1.rs index 242fd66fb..5911cb0a5 100644 --- a/src/online_ciphertext/online_ciphertext_v1.rs +++ b/src/online_ciphertext/online_ciphertext_v1.rs @@ -236,7 +236,7 @@ macro_rules! online_ciphertext_impl { data: &[u8], aad: &[u8], ) -> Result> { - if (data.len() as u32) != self.header.get_chunk_size() { + if (data.len() as u32) > self.header.get_chunk_size() { return Err(Error::InvalidChunkLength); }; @@ -261,7 +261,7 @@ macro_rules! online_ciphertext_impl { data: &mut Vec, aad: &[u8], ) -> Result<()> { - if (data.len() as u32) != self.header.get_chunk_size() { + if (data.len() as u32) > self.header.get_chunk_size() { return Err(Error::InvalidChunkLength); }; From 8b968988e06025b4b71d0830fcc5a7d074ef3c4f Mon Sep 17 00:00:00 2001 From: Mathieu Morrissette Date: Tue, 14 Jan 2025 12:28:21 -0500 Subject: [PATCH 15/21] [WIP] Cargo fmt --- ffi/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index f826fdac0..9a7f5ca3d 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -1070,7 +1070,7 @@ pub unsafe extern "C" fn OnlineEncryptorLastChunk( }; let encryptor = Box::from_raw(ptr as *mut Mutex); - + let encryptor = match encryptor.into_inner() { Ok(c) => c, Err(_) => return Error::PoisonedMutex.error_code(), @@ -1080,8 +1080,8 @@ pub unsafe extern "C" fn OnlineEncryptorLastChunk( let aad = slice::from_raw_parts(aad, aad_size); let encrypted = match encryptor.encrypt_last_chunk(data, aad) { - Ok(e) => e, - Err(e) => return e.error_code(), + Ok(e) => e, + Err(e) => return e.error_code(), }; if result_size < encrypted.len() { From de9e6d0598e518bda34f882f34f91515d04b152a Mon Sep 17 00:00:00 2001 From: Mathieu Morrissette Date: Tue, 14 Jan 2025 12:31:34 -0500 Subject: [PATCH 16/21] dotnet format --- wrappers/csharp/src/DecryptionStream.cs | 23 +++++++++++++---------- wrappers/csharp/src/EncryptionStream.cs | 21 ++++++++++++--------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/wrappers/csharp/src/DecryptionStream.cs b/wrappers/csharp/src/DecryptionStream.cs index 4412266e6..48e88e50c 100644 --- a/wrappers/csharp/src/DecryptionStream.cs +++ b/wrappers/csharp/src/DecryptionStream.cs @@ -31,7 +31,8 @@ public bool HasFlushedFinalBlock public DecryptionStream(byte[] key, byte[] aad, byte[] header, bool asymmetric, Stream outputStream) : this(key, aad, header, asymmetric, outputStream, false) { } - public DecryptionStream(byte[] key, byte[] aad, byte[] header, bool asymmetric, Stream outputStream, bool leaveOpen) { + public DecryptionStream(byte[] key, byte[] aad, byte[] header, bool asymmetric, Stream outputStream, bool leaveOpen) + { _outputStream = outputStream; _leaveOpen = leaveOpen; @@ -56,7 +57,7 @@ public DecryptionStream(byte[] key, byte[] aad, byte[] header, bool asymmetric, Utils.HandleError(chunkLength); } - _tagLength = (int) tagSize; + _tagLength = (int)tagSize; _chunkLength = (int)chunkLength + _tagLength; _inputBuffer = new byte[_chunkLength]; @@ -95,7 +96,7 @@ public byte[] GetHeader() public void FlushFinalBlock() { - if(HasFlushedFinalBlock) + if (HasFlushedFinalBlock) { throw new NotSupportedException(); } @@ -134,7 +135,7 @@ public override void SetLength(long value) public override void Write(byte[] buffer, int offset, int count) { - if(HasFlushedFinalBlock) + if (HasFlushedFinalBlock) { // Cannot write as stream is already closed return; @@ -157,7 +158,8 @@ public override void Write(byte[] buffer, int offset, int count) _inputBufferOffset = 0; } - if (count > 0) { + if (count > 0) + { Buffer.BlockCopy(buffer, offset, _inputBuffer, _inputBufferOffset, count); _inputBufferOffset += count; @@ -169,9 +171,10 @@ private byte[] DecryptChunk() byte[] aad = new byte[0]; byte[] outputBuffer = new byte[ChunkLength - TagLength]; - long result = Native.OnlineDecryptorNextChunk(_native_ptr, _inputBuffer, (UIntPtr) ChunkLength, aad, UIntPtr.Zero, outputBuffer, (UIntPtr) outputBuffer.Length); + long result = Native.OnlineDecryptorNextChunk(_native_ptr, _inputBuffer, (UIntPtr)ChunkLength, aad, UIntPtr.Zero, outputBuffer, (UIntPtr)outputBuffer.Length); - if (result < 0) { + if (result < 0) + { Utils.HandleError(result); } @@ -183,7 +186,7 @@ private byte[] DecryptLastChunk() byte[] aad = new byte[0]; byte[] outputBuffer = new byte[_inputBufferOffset - TagLength]; - long result = Native.OnlineDecryptorLastChunk(_native_ptr, _inputBuffer, (UIntPtr) _inputBufferOffset, aad, UIntPtr.Zero, outputBuffer, (UIntPtr)outputBuffer.Length); + long result = Native.OnlineDecryptorLastChunk(_native_ptr, _inputBuffer, (UIntPtr)_inputBufferOffset, aad, UIntPtr.Zero, outputBuffer, (UIntPtr)outputBuffer.Length); if (result < 0) { @@ -210,12 +213,12 @@ protected override void Dispose(bool disposing) { if (disposing) { - if(!HasFlushedFinalBlock) + if (!HasFlushedFinalBlock) { FlushFinalBlock(); }; - if(!_leaveOpen) + if (!_leaveOpen) { _outputStream.Close(); } diff --git a/wrappers/csharp/src/EncryptionStream.cs b/wrappers/csharp/src/EncryptionStream.cs index b4d7633f3..43da6d1eb 100644 --- a/wrappers/csharp/src/EncryptionStream.cs +++ b/wrappers/csharp/src/EncryptionStream.cs @@ -31,7 +31,8 @@ public bool HasFlushedFinalBlock public EncryptionStream(byte[] key, byte[] aad, int chunkLength, bool asymmetric, int version, Stream outputStream) : this(key, aad, chunkLength, asymmetric, version, outputStream, false) { } - public EncryptionStream(byte[] key, byte[] aad, int chunkLength, bool asymmetric, int version, Stream outputStream, bool leaveOpen) { + public EncryptionStream(byte[] key, byte[] aad, int chunkLength, bool asymmetric, int version, Stream outputStream, bool leaveOpen) + { _chunkLength = chunkLength; _outputStream = outputStream; _leaveOpen = leaveOpen; @@ -50,7 +51,7 @@ public EncryptionStream(byte[] key, byte[] aad, int chunkLength, bool asymmetric Utils.HandleError(tagSize); } - _tagLength = (int) tagSize; + _tagLength = (int)tagSize; _inputBuffer = new byte[chunkLength]; } @@ -88,7 +89,7 @@ public byte[] GetHeader() public void FlushFinalBlock() { - if(HasFlushedFinalBlock) + if (HasFlushedFinalBlock) { throw new NotSupportedException(); } @@ -127,7 +128,7 @@ public override void SetLength(long value) public override void Write(byte[] buffer, int offset, int count) { - if(HasFlushedFinalBlock) + if (HasFlushedFinalBlock) { // Cannot write as stream is already closed return; @@ -150,7 +151,8 @@ public override void Write(byte[] buffer, int offset, int count) _inputBufferOffset = 0; } - if (count > 0) { + if (count > 0) + { Buffer.BlockCopy(buffer, offset, _inputBuffer, _inputBufferOffset, count); _inputBufferOffset += count; @@ -162,9 +164,10 @@ private byte[] EncryptChunk() byte[] aad = new byte[0]; byte[] outputBuffer = new byte[ChunkLength + TagLength]; - long result = Native.OnlineEncryptorNextChunk(_native_ptr, _inputBuffer, (UIntPtr) ChunkLength, aad, UIntPtr.Zero, outputBuffer, (UIntPtr) outputBuffer.Length); + long result = Native.OnlineEncryptorNextChunk(_native_ptr, _inputBuffer, (UIntPtr)ChunkLength, aad, UIntPtr.Zero, outputBuffer, (UIntPtr)outputBuffer.Length); - if (result < 0) { + if (result < 0) + { Utils.HandleError(result); } @@ -203,12 +206,12 @@ protected override void Dispose(bool disposing) { if (disposing) { - if(!HasFlushedFinalBlock) + if (!HasFlushedFinalBlock) { FlushFinalBlock(); }; - if(!_leaveOpen) + if (!_leaveOpen) { _outputStream.Close(); } From e2c0e0610fdd1c93f68cb5ff4d604d44c15412a0 Mon Sep 17 00:00:00 2001 From: Mathieu Morrissette Date: Tue, 14 Jan 2025 16:30:38 -0500 Subject: [PATCH 17/21] Decrypt Stream Test C# --- src/online_ciphertext/online_ciphertext_v1.rs | 8 +-- wrappers/csharp/tests/unit-tests/TestData.cs | 6 ++- .../csharp/tests/unit-tests/TestStreams.cs | 54 +++++++++++++------ 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/online_ciphertext/online_ciphertext_v1.rs b/src/online_ciphertext/online_ciphertext_v1.rs index 5911cb0a5..70f21613b 100644 --- a/src/online_ciphertext/online_ciphertext_v1.rs +++ b/src/online_ciphertext/online_ciphertext_v1.rs @@ -190,7 +190,7 @@ macro_rules! online_ciphertext_impl { data: &[u8], aad: &[u8], ) -> Result> { - if (data.len() as u32) != self.header.get_chunk_size() { + if data.len() as u32 != self.header.get_chunk_size() && data.len() as u32 != (self.header.get_chunk_size() + self.get_tag_size() as u32){ return Err(Error::InvalidChunkLength); }; @@ -215,7 +215,7 @@ macro_rules! online_ciphertext_impl { data: &mut Vec, aad: &[u8], ) -> Result<()> { - if (data.len() as u32) != self.header.get_chunk_size() { + if data.len() as u32 != self.header.get_chunk_size() && data.len() as u32 != (self.header.get_chunk_size() + self.get_tag_size() as u32){ return Err(Error::InvalidChunkLength); }; @@ -236,7 +236,7 @@ macro_rules! online_ciphertext_impl { data: &[u8], aad: &[u8], ) -> Result> { - if (data.len() as u32) > self.header.get_chunk_size() { + if (data.len() as u32) > (self.header.get_chunk_size() + self.get_tag_size() as u32) { return Err(Error::InvalidChunkLength); }; @@ -261,7 +261,7 @@ macro_rules! online_ciphertext_impl { data: &mut Vec, aad: &[u8], ) -> Result<()> { - if (data.len() as u32) > self.header.get_chunk_size() { + if (data.len() as u32) > (self.header.get_chunk_size() + self.get_tag_size() as u32) { return Err(Error::InvalidChunkLength); }; diff --git a/wrappers/csharp/tests/unit-tests/TestData.cs b/wrappers/csharp/tests/unit-tests/TestData.cs index 51d7badcd..a77b8431a 100644 --- a/wrappers/csharp/tests/unit-tests/TestData.cs +++ b/wrappers/csharp/tests/unit-tests/TestData.cs @@ -18,8 +18,12 @@ public static class TestData public const string Base64TestData2 = "QUJDDE"; - public const string Base64TestDataStream = "DO5C7xhZ5J1QHqS4z606FiLH6vhZz7GgLfV3dsMkDA3tnzAkSvyl+xaV7ZLNGBe9JcdXI5oZHIaSttbFQaNrPtB/kz0PUGxgErrJ6YOymVKRiX7ZQa+ePv/DZyOUwcUHgRqAF/x0r8i4+WeNrZBbf1EQ3YuLJpWbI4//KManC37n6k0kH7U2r4MtFTHwiuCVSPa56hPgOSccm8lzhx6Sb2CR5+1Rs/Ez7tEG2Rd6IwJ+GhCeaOVetWVNmGcc4l/2EJtyxIdKQ0FAppmyAtHgGXFeoIPDWhWwrLlhtYF+/7Vnn1jtvI4kHtBzHRmxX+EaUJ8G9Ho9C+1Z5sBcAezDUji6gQ+IMlDcj2eR3ZuA1Lj/AnuB31eZXIAW0ailcbTBpitzXBCEREg6SWQwKHqgb8ao0ooJ6Cbit5I0Fxba7b7vsa3WeowiAsxUgNfrZFKvwLV8K4yAvP66ypM9tnOqt4c/Vy+OgiPoZqOTa1hfgcNgFMGgb1DcqLnGOBm6gWONCT5rD93DPxMKiDFeEuTJo/mN+7lTiCJczk1Gol2FZSQGqjs1laHSA2Oxdi3jvZSRuS58/XGVH1Sn2PvA6BOsTiZUK/9Ie7A2urqo1l4afg8oTCgtHkoPcM7PfGvnNEyispq7gP5V0IO83zgQE159ICHqOilbidUSEn+FGWtBSkfmE9BwfxrpM8w4DnIufX52W8Nbyf81NbWQAN+bRVNvXHdZdCmqwAqE3HTTSDnyQF046JGQ4N9YV8cIxs8YwJ6cEj/ilRmIHGRwTcD8PfzGz+cr/yN6o6BkrYHMfpHz984VzBR2Xf+w3PA3GeKwFKHZ0Lskfp+SXvUZfzkjOn7GQcDwuCbu/NFLLosad7WdUUMgWsZiHUXlCfSMu/y5kovEgN3j7Ky1O/LTrqPIeG+xA774unNhnr4N7brIaJM5qSuWZYgsSiR48FDzug3QVHJWwZt72BOcp7eFRNHkJN2wYKlVjSd2rrXZy1qjV6H1DvLpmKBLm47ihybvAmgoZr9Z2ZkT2UUceOmmJ5TmoEIK+RwoRumO7U0Ry4VyvNF7biZcpMqNTdwXAGbK1xs+sIretzQFb8+JvAbQAuPSJGei+A1C2m/QKGawAm6+0mK3qYCcKsh4w644a+5+63/fjsYfk0GM3cX5En8HCspcCq9y3gWxX76Wbq8+IKxQDXpXBkD3fO5qRsTshlG/tg+b0un6dAeOvF7SVsxQjjKiXjk5SN3w+py4rQdkN6IFPW1ieeSf/BW9xudat9dSAFgR0YgMaZ6AryqsQtUUwDp67+8odTGooadsZXW6sGVCs5/Vr4xnTle2F6eLNw=="; + public const string Base64TestDataStream = "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gTnVsbGFtIHF1YW0gbnVuYywgcGxhY2VyYXQgbWF4aW11cyBmYWNpbGlzaXMgZGlnbmlzc2ltLCB0aW5jaWR1bnQgaW4gdHVycGlzLiBEdWlzIHRlbXBvciBtYWduYSBldSBjb252YWxsaXMgbWF0dGlzLiBOdWxsYW0gcmhvbmN1cyBsYWN1cyBub24gbmliaCBtb2xlc3RpZSBzb2RhbGVzLiBJbiBub24gdHJpc3RpcXVlIG51bmMuIE51bGxhIGlkIGdyYXZpZGEgbnVuYy4gRG9uZWMgZWdlc3RhcyBtaSBlZ2V0IHRlbGx1cyBlZmZpY2l0dXIsIG1vbGxpcyBsb2JvcnRpcyB1cm5hIHRyaXN0aXF1ZS4gVml2YW11cyBzZWQgdGVsbHVzIHZpdGFlIG1ldHVzIG1hdHRpcyBsYWNpbmlhLiBWZXN0aWJ1bHVtIGRhcGlidXMgZXN0IGV1IHB1bHZpbmFyIGxhb3JlZXQuIEV0aWFtIGNvbW1vZG8gZXJvcyBhdCBmYWNpbGlzaXMgbW9sbGlzLiBDdXJhYml0dXIgcnV0cnVtIHRpbmNpZHVudCBzZW0sIHZpdGFlIHBsYWNlcmF0IG51bmMgbGFjaW5pYSBuZWMuIE9yY2kgdmFyaXVzIG5hdG9xdWUgcGVuYXRpYnVzIGV0IG1hZ25pcyBkaXMgcGFydHVyaWVudCBtb250ZXMsIG5hc2NldHVyIHJpZGljdWx1cyBtdXMuIFBoYXNlbGx1cyBjdXJzdXMgYXVndWUgdXQgbmlzbCB0ZW1wdXMgbGFjaW5pYS4gCk51bGxhIHZpdGFlIGZlcm1lbnR1bSBlc3QsIGV0IHZlaGljdWxhIG1hdXJpcy4gSW50ZXJkdW0gZXQgbWFsZXN1YWRhIGZhbWVzIGFjIGFudGUgaXBzdW0gcHJpbWlzIGluIGZhdWNpYnVzLiBQaGFzZWxsdXMgc2l0IGFtZXQgbWFnbmEgcGVsbGVudGVzcXVlLCBtYXhpbXVzIGp1c3RvIHZpdGFlLCBtYWxlc3VhZGEgYW50ZS4gVmVzdGlidWx1bSBhbnRlIGlwc3VtIHByaW1pcyBpbiBmYXVjaWJ1cyBvcmNpIGx1Y3R1cyBldCB1bHRyaWNlcyBwb3N1ZXJlIGN1YmlsaWEgY3VyYWU7IFN1c3BlbmRpc3NlIHRlbXB1cyBkb2xvciBldSBhdWd1ZSByaG9uY3VzIHBoYXJldHJhLiBEb25lYyB2aXRhZSBuaXNpIHBlbGxlbnRlc3F1ZSwgY29udmFsbGlzIG9kaW8gYXQsIGludGVyZHVtIGF1Z3VlLiBQZWxsZW50ZXNxdWUgYXQgcHVydXMgYSBuaWJoIGxhY2luaWEgdWxsYW1jb3JwZXIgYWMgYSBudWxsYS4gQWVuZWFuIG5pYmggbGlndWxhLCBoZW5kcmVyaXQgaW4gbHVjdHVzIGV0LCBzb2RhbGVzIG5vbiB0dXJwaXMuIE51bGxhIGZhY2lsaXNpLiBOdWxsYSBsYW9yZWV0IG1hc3NhIGZlbGlzLCBhIGRpY3R1bSBsaWd1bGEgYmxhbmRpdCB1dC4gClNlZCBzb2RhbGVzIHJpc3VzIGp1c3RvLCB1dCBmZXJtZW50dW0gc2FwaWVuIGRpY3R1bSBzZWQuIFNlZCB1bHRyaWNlcyB2ZWxpdCBldCBmZWxpcyBmZXJtZW50dW0gaW50ZXJkdW0uIFZlc3RpYnVsdW0gY29uc2VjdGV0dXIgbGFvcmVldCBpcHN1bS4gUGVsbGVudGVzcXVlIGhhYml0YW50IG1vcmJpIHRyaXN0aXF1ZSBzZW5lY3R1cyBldCBuZXR1cyBldCBtYWxlc3VhZGEgZmFtZXMgYWMgdHVycGlzIGVnZXN0YXMuIENyYXMgaW4gZXggc3VzY2lwaXQsIGFjY3Vtc2FuIHB1cnVzIHZpdGFlLCByaG9uY3VzIGVsaXQuIE1hZWNlbmFzIHF1aXMgc2FwaWVuIG5vbiBxdWFtIGFjY3Vtc2FuIHZlaGljdWxhIHNlZCB2aXRhZSBwdXJ1cy4gUHJvaW4gY29uc2VxdWF0IGlwc3VtIGluIGxhY3VzIGxvYm9ydGlzIHRlbXB1cy4gRG9uZWMgYWMgYWxpcXVldCBtYXNzYS4g"; + public const string Base64HeaderDataStreamEncrypted = "DQwHAAEAAADoAwAAOU63lZRBVkAapzo92zfg/KZy6/o="; + + public const string Base64TestDataStreamEncrypted = "OF1pXe1lexpcY4iBF8Vq5CGtRhK9QyvYar8U5fLHxZpnzEeUDAW7LbxoOUhjQqRW+VMnACB61r/FLNMUz0dVxcuvV1bZj1RxO3x/4xCybMMRc394UyG08KdI3+FAveGbjBG3r2G49RtqAS+IJb31VAIbuEmxkiEG0KgQlEKp9ZXevJGZwlG5VT9RIiKYtJ1FB1/crTSuANtZhSfVvO/iGc7TnTI+Fd23/GHY8/aFAeL9AAnG6qp+X/N4l0fP1klZFlP/0A+Q9xshz4fZ8N0RHjSrEU2YbV79LnLIwBd1U0NPArVUmiJd2Nblmc/6dfH9wB6i0VQYzk5yz4UQZ/n/9aF2h9jkMjpUVPyDwq+Eelzqb6ZJUNn1bpontHqe7PBCBj5GHTBt8FjOI92ZIpHte9OKk3i/Ni1HmqGdjVmne1oP023x9IX9mX4+i8XeCwn+RQ9xmoYRgdK2UzSs9KSyA9dOhQyH5rXF6gL7QtSgDsPa6uVuuk5NnrNyTNanTTUkc1W612WzcKPRpSOH+IeIGPUuEMju5X0qsDPRziNg5P78z8QDNlGYe4r+d7uYTiQsFhqsl7/GgyXFIJbEKtpUuhpk82nSZAyhF1jRv4pdU5hI9rg0FOo9E0S/iE5UkICUfHRpLw9X2PaJIzyIuIHoTJ/BITt1FZIk19T+IuC3ITSc/ziP3ptRjR9EK5hSyVXdWEn+N5ac/umeHsf5/nY+H3FN7Z1EzNG5Jglb5iOECy3NWted0jggPJcJnDsQCWuig2AY7dU8dA9IpmDFMT8bVjfBwW0oqgFdhd1Tc9+eyO36imr0PZOjCLYLppbTB8ddb/6D5WtD98P0xwXxIqFLT0KaT6CiskN2QetVBq+MDKqf1Sq4YnGuiRrE8UBVmOAfekZfsZVFClXpoLPVPfCTa+l5WHad3TMuEDFjYiU/wX9jjPXV8xAheCpN+qZb9V6w0quF6hhH22+3e4oUENLd2sJVrotcAh919StKwmq+mYz4b890z76gw5YTdk82i83ZHj9R+x4769bMlATxARC999dxwwqB3EGh8B4O25O5r9SQkLMtgqqmE5fpgYWrPlqitV1zlyMvEZi284B4uIM5xgTJW0jaJmigEVFJ08D4/DGP3mPfNfz+G7bFc/FaNAFyIZjd2Y64208cbPJPCCFMqLtlcMIaKQhnTNs8rielVAxWH63V/FOpw+9Zj8uB6m1EQLAFbgIqmgRdVelk6/lKI8XsI0D+Oe5H+X8GwM5Ts9s5+xAzYSkBQs5k8Gx6FH8M7KBhZIyV8TB5PPTANKfMqt9+mx8BeUTYPGtbdEqsHosuis6+hegM57F5OJ0hL/hGhSXJZbzFYsiiI0OLsYVuexDZ4UoB7cI6cTpIDcL7a0xS1U/qKErr6MFZz6mpJ2x0a1E14Dk1/d6uCOAYkYawwxl3UQpHli7WOk1XU3iS8NJzshixpWb9k4Ac1+bEu6uXjeSXAU24rbMtm6ck9en7y/LcSCuqlH7W22obKcuIGHRwgLSIMvthB/SA+g9BDEJVIEjdfOQeaqJv4WENT+797OWp9888X7MSLf95Ecn+w4dtk7zGgZA65y565QbEVKPDrvE76ed1u0gDY4YOPVWJNMzlE+RBowPSYZz0SiKIQwsqXpuQMWl1Id0+WMLzMtFyBKmAxpAeCEuAUiBiuYjv5Bfv44slCYB2BP3lEcqtZtC5uVf7BiXI+5gFyja3KmzKNjD6uLxXmyg5bjH3113qkRg5SPxFMAzgSXxERu3gru85qSiDExbbV5Ppv2OVJZwqkKbWZgcdv16R3PMbUzzLleIj09p4G3U04U7B4QrIdtR1lb3BsPVYHA3fX12U5f3/sIJSnZ9Nl2YnjX5JpW9achzKq0THDn8xTQhWbUnXyTRt5xXr9ENf+wegFAKYpxoafSDcZnd4veDjaXYFkQJPgHpDib7LjzVRa47j76UWgoEKdYlwJHcsPi6I3ag0vQCFeVdQfdrinwgpr2A5ZgNrc9Cav9tuGiM29sBIEtW80MTT0IjljAzYzsSaFD3pzxvR4phCA53XMphRgFsbT9tTaLiNPfIqFNXCPZ0tBIh3Ur/qHg/fIsUZj6fmiL582Y4ltkEG6VPpXH4mBJXA4GSe50P9IHOWvNZgOUT18IXEFrkSYVZweh9yPvzsvI7tbI36xLYyYiS7kn47kgh75m+8XttDj3wYpKLDkSk832Z3cPsxFXtJqMrDyIftSsVBPoNaBEcv2FSZuuFwQ7PqJ+Rn73f1bFbV2R6iYupYZ0GxH6N8pXk+LkfJM7w="; + public const string StringTestData = "ABC"; public const string TestPassword = "Key123"; diff --git a/wrappers/csharp/tests/unit-tests/TestStreams.cs b/wrappers/csharp/tests/unit-tests/TestStreams.cs index 940b8a8e8..88d40edd0 100644 --- a/wrappers/csharp/tests/unit-tests/TestStreams.cs +++ b/wrappers/csharp/tests/unit-tests/TestStreams.cs @@ -1,6 +1,7 @@ #pragma warning disable SA1600 // Elements should be documented namespace Devolutions.Crypto.Tests { + using System; using System.IO; using Devolutions.Cryptography; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -11,30 +12,49 @@ public class TestStreams [TestMethod] public void EncryptStream() { - byte[] base64DataAsUtf8ByteArray = Utils.StringToUtf8ByteArray(TestData.Base64TestDataStream); + byte[] base64DataAsUtf8ByteArray = Utils.Base64StringToByteArray(TestData.Base64TestDataStream); - byte[] header; - - using (MemoryStream ms = new MemoryStream()) + using MemoryStream ms = new MemoryStream(); + using (EncryptionStream ec = new EncryptionStream(TestData.BytesTestKey, Array.Empty(), 1000, false, 0, ms)) { - using (EncryptionStream ec = new EncryptionStream(TestData.BytesTestKey, System.Array.Empty(), 1000, false, 0, ms)) - { - header = ec.GetHeader(); + byte[] header = ec.GetHeader(); + + Assert.IsTrue(Utils.ValidateHeader(header, DataType.OnlineCiphertext)); + + ec.Write(base64DataAsUtf8ByteArray, 0, base64DataAsUtf8ByteArray.Length); + ec.FlushFinalBlock(); + } + + byte[] result = ms.ToArray(); + + Assert.AreNotEqual(result, base64DataAsUtf8ByteArray); + Assert.AreNotEqual(result, new byte[1000]); + Assert.IsTrue(result.Length == 1721); + } - Assert.IsTrue(Utils.ValidateHeader(header, DataType.OnlineCiphertext)); + [TestMethod] + public void DecryptStream() + { + byte[] base64DataAsUtf8ByteArray = Utils.Base64StringToByteArray(TestData.Base64TestDataStreamEncrypted); - using (StreamWriter writer = new StreamWriter(ec)) - { - //writer.Write(new byte[3]); - writer.Write(base64DataAsUtf8ByteArray); - } - } + using MemoryStream ms = new MemoryStream(); - byte[] result = ms.ToArray(); + byte[] header = Utils.Base64StringToByteArray((TestData.Base64HeaderDataStreamEncrypted)); + using (DecryptionStream ec = + new DecryptionStream(TestData.BytesTestKey, Array.Empty(), header, false, ms, false)) + { + Assert.IsTrue(Utils.ValidateHeader(header, DataType.OnlineCiphertext)); - Assert.AreNotEqual(result, base64DataAsUtf8ByteArray); - Assert.AreNotEqual(result, new byte[1000]); + ec.Write(base64DataAsUtf8ByteArray, 0, base64DataAsUtf8ByteArray.Length); + ec.FlushFinalBlock(); } + + byte[] result = ms.ToArray(); + + Assert.AreNotEqual(result, base64DataAsUtf8ByteArray); + Assert.AreNotEqual(result, new byte[1000]); + Assert.IsTrue(result.Length == 1689); + Assert.IsTrue(Utils.EncodeToBase64String(result) == TestData.Base64TestDataStream); } } } \ No newline at end of file From d1bc39fb36a4f864e8457630097e62d930385522 Mon Sep 17 00:00:00 2001 From: Mathieu Morrissette Date: Wed, 15 Jan 2025 08:55:52 -0500 Subject: [PATCH 18/21] test --- .github/workflows/kotlin/action.yml | 8 +++---- .../native/native-build-linux/action.yml | 21 +++++++++++++++---- .../src/devolutions_crypto.udl | 2 ++ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/.github/workflows/kotlin/action.yml b/.github/workflows/kotlin/action.yml index 37a7439ee..cc16e7149 100644 --- a/.github/workflows/kotlin/action.yml +++ b/.github/workflows/kotlin/action.yml @@ -57,16 +57,16 @@ runs: echo " [target.aarch64-linux-android] - ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-ar\" + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar\" linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang\" [target.armv7-linux-androideabi] - ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ar\" + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar\" linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang\" [target.i686-linux-android] - ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android-ar\" + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar\" linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android21-clang\" [target.x86_64-linux-android] - ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android-ar\" + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar\" linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android21-clang\"" >> ./.cargo/config.toml - name: Build diff --git a/.github/workflows/native/native-build-linux/action.yml b/.github/workflows/native/native-build-linux/action.yml index 299a64e1a..fe406674c 100644 --- a/.github/workflows/native/native-build-linux/action.yml +++ b/.github/workflows/native/native-build-linux/action.yml @@ -68,16 +68,16 @@ runs: echo " [target.aarch64-linux-android] - ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-ar\" + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar\" linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang\" [target.armv7-linux-androideabi] - ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/arm-linux-androideabi-ar\" + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar\" linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang\" [target.i686-linux-android] - ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android-ar\" + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar\" linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android21-clang\" [target.x86_64-linux-android] - ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android-ar\" + ar = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar\" linker = \"$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android21-clang\"" >> ./.cargo/config.toml @@ -89,7 +89,20 @@ runs: export ANDROID_SDK_ROOT="${ANDROID_ROOT}/sdk" export ANDROID_NDK=$ANDROID_SDK_ROOT/ndk-bundle ln -sfn $ANDROID_SDK_ROOT/ndk/27.2.12479018 $ANDROID_NDK + + ls $ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/ + cat ../../.cargo/config.toml + # Set environment variables for blake3 custom build script + export CC_aarch64_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang" + export AR_aarch64_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" + export CC_armv7_linux_androideabi="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang" + export AR_armv7_linux_androideabi="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" + export CC_i686_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android21-clang" + export AR_i686_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" + export CC_x86_64_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android21-clang" + export AR_x86_64_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" + cargo build -p devolutions-crypto-ffi --release --target=aarch64-linux-android cargo build -p devolutions-crypto-ffi --release --target=armv7-linux-androideabi cargo build -p devolutions-crypto-ffi --release --target=i686-linux-android diff --git a/uniffi/devolutions-crypto-uniffi/src/devolutions_crypto.udl b/uniffi/devolutions-crypto-uniffi/src/devolutions_crypto.udl index 81a785044..859db11ce 100644 --- a/uniffi/devolutions-crypto-uniffi/src/devolutions_crypto.udl +++ b/uniffi/devolutions-crypto-uniffi/src/devolutions_crypto.udl @@ -68,6 +68,8 @@ enum DevolutionsCryptoError { "IoError", "NotEnoughShares", "InconsistentVersion", + "InvalidChunkLength", + "PoisonedMutex", }; interface Argon2ParametersBuilder { From 2c21c7d70c2724bc92accb7be01024b9925d3dec Mon Sep 17 00:00:00 2001 From: Mathieu Morrissette Date: Wed, 15 Jan 2025 10:14:58 -0500 Subject: [PATCH 19/21] changelog --- .github/workflows/kotlin/action.yml | 10 ++++++++++ CHANGELOG.md | 6 ++++++ Cargo.lock | 12 ++++++------ Cargo.toml | 2 +- .../src/devolutions_crypto.udl | 1 + 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/.github/workflows/kotlin/action.yml b/.github/workflows/kotlin/action.yml index cc16e7149..60e8f18cb 100644 --- a/.github/workflows/kotlin/action.yml +++ b/.github/workflows/kotlin/action.yml @@ -78,6 +78,16 @@ runs: export ANDROID_NDK=$ANDROID_SDK_ROOT/ndk-bundle ln -sfn $ANDROID_SDK_ROOT/ndk/27.2.12479018 $ANDROID_NDK + # Set environment variables for blake3 custom build script + export CC_aarch64_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang" + export AR_aarch64_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" + export CC_armv7_linux_androideabi="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang" + export AR_armv7_linux_androideabi="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" + export CC_i686_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android21-clang" + export AR_i686_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" + export CC_x86_64_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android21-clang" + export AR_x86_64_linux_android="$ANDROID_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" + make release RELEASE=1 chmod +x gradlew diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cfc1c8e8..b15a99528 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +## [0.9.2] - 2025-01-20 +- Online encryption feature + +## [0.9.1] - 2024-06-21 +- AAD feature + ## [0.9.0] - 2023-10-23 - Removes the `derive_keypair` function as it was not used and was broken. - Added `constant_time_equals` in the utils. diff --git a/Cargo.lock b/Cargo.lock index 62854506d..6db2c95be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -504,7 +504,7 @@ dependencies = [ [[package]] name = "devolutions-crypto" -version = "0.9.1" +version = "0.9.2" dependencies = [ "aead", "aes", @@ -542,7 +542,7 @@ dependencies = [ [[package]] name = "devolutions-crypto-cli" -version = "0.9.1" +version = "0.9.2" dependencies = [ "base64 0.11.0", "clap", @@ -551,7 +551,7 @@ dependencies = [ [[package]] name = "devolutions-crypto-ffi" -version = "0.9.1" +version = "0.9.2" dependencies = [ "base64 0.21.7", "devolutions-crypto", @@ -560,7 +560,7 @@ dependencies = [ [[package]] name = "devolutions-crypto-fuzz" -version = "0.9.1" +version = "0.9.2" dependencies = [ "arbitrary", "devolutions-crypto", @@ -569,7 +569,7 @@ dependencies = [ [[package]] name = "devolutions-crypto-python" -version = "0.9.1" +version = "0.9.2" dependencies = [ "base64 0.21.7", "devolutions-crypto", @@ -579,7 +579,7 @@ dependencies = [ [[package]] name = "devolutions-crypto-uniffi" -version = "0.9.1" +version = "0.9.2" dependencies = [ "devolutions-crypto", "uniffi", diff --git a/Cargo.toml b/Cargo.toml index d782ba1b7..9670d48d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -package.version = "0.9.1" +package.version = "0.9.2" members = [ "cli", "ffi", diff --git a/uniffi/devolutions-crypto-uniffi/src/devolutions_crypto.udl b/uniffi/devolutions-crypto-uniffi/src/devolutions_crypto.udl index 859db11ce..44043c362 100644 --- a/uniffi/devolutions-crypto-uniffi/src/devolutions_crypto.udl +++ b/uniffi/devolutions-crypto-uniffi/src/devolutions_crypto.udl @@ -6,6 +6,7 @@ enum DataType { "Share", "SigningKey", "Signature", + "OnlineCiphertext", }; enum CiphertextVersion { From 47b11ac7dfd2de1f352e76612f9062c85d57aacd Mon Sep 17 00:00:00 2001 From: Mathieu Morrissette Date: Wed, 15 Jan 2025 10:34:11 -0500 Subject: [PATCH 20/21] fix nuget --- wrappers/csharp/src/Native.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wrappers/csharp/src/Native.cs b/wrappers/csharp/src/Native.cs index bd6d3969f..14dc016c1 100644 --- a/wrappers/csharp/src/Native.cs +++ b/wrappers/csharp/src/Native.cs @@ -31,8 +31,8 @@ public static partial class Native #endif #if !DEBUG - private const string NativeVersion = "0.9.1"; - private const string ManagedVersion = "2023.11.02"; + private const string NativeVersion = "||NATIVE_VERSION||"; + private const string ManagedVersion = "||MANAGED_VERSION||"; #endif static Native() From 60de9025ffa01c76ebf29aa56ce8cf69256b054d Mon Sep 17 00:00:00 2001 From: Mathieu Morrissette Date: Wed, 15 Jan 2025 10:35:28 -0500 Subject: [PATCH 21/21] update crates --- Cargo.lock | 257 ++++++++++++++++++++-------------------- Cargo.toml | 7 +- src/argon2parameters.rs | 8 +- 3 files changed, 132 insertions(+), 140 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6db2c95be..278fa27e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,9 +25,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anstream" @@ -70,19 +70,20 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", + "once_cell", "windows-sys", ] [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "arbitrary" @@ -131,7 +132,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -265,9 +266,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "camino" @@ -280,9 +281,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] @@ -298,7 +299,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -312,9 +313,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.1" +version = "1.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b" dependencies = [ "shlex", ] @@ -362,9 +363,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.21" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" dependencies = [ "clap_builder", "clap_derive", @@ -372,9 +373,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.21" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" dependencies = [ "anstream", "anstyle", @@ -384,21 +385,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" @@ -406,16 +407,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - [[package]] name = "const-oid" version = "0.9.6" @@ -430,19 +421,13 @@ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "cpufeatures" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - [[package]] name = "crypto-common" version = "0.1.6" @@ -478,7 +463,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -532,7 +517,7 @@ dependencies = [ "sha2", "strum", "subtle", - "thiserror", + "thiserror 2.0.11", "typed-builder", "wasm-bindgen", "wasm-bindgen-test", @@ -642,9 +627,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "foldhash" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" [[package]] name = "fs-err" @@ -680,9 +665,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "goblin" @@ -697,9 +682,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", @@ -723,9 +708,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown", @@ -755,24 +740,25 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.164" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libfuzzer-sys" @@ -786,9 +772,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "memchr" @@ -865,7 +851,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -925,9 +911,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "ppv-lite86" @@ -949,9 +935,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.91" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307e3004becf10f5a6e0d59d20f3cd28231b0e0827a96cd3e0ce6d14bc1e4bb3" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -1004,7 +990,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1017,7 +1003,7 @@ dependencies = [ "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1031,9 +1017,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -1070,14 +1056,13 @@ dependencies = [ [[package]] name = "rust-argon2" -version = "1.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5885493fdf0be6cdff808d1533ce878d21cfa49c7086fa00c66355cd9141bfc" +checksum = "9d9848531d60c9cbbcf9d166c885316c24bc0e2a9d3eba0956bb6cbbd79bc6e8" dependencies = [ "base64 0.21.7", "blake2b_simd", "constant_time_eq", - "crossbeam-utils", ] [[package]] @@ -1091,9 +1076,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" @@ -1119,12 +1104,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scroll" version = "0.12.0" @@ -1142,7 +1121,7 @@ checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1158,18 +1137,18 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] @@ -1187,20 +1166,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" dependencies = [ "itoa", "memchr", @@ -1287,7 +1266,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1309,9 +1288,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.89" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -1339,7 +1318,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", ] [[package]] @@ -1350,7 +1338,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", ] [[package]] @@ -1396,7 +1395,7 @@ checksum = "560b82d656506509d43abe30e0ba64c56b1953ab3d4fe7ba5902747a7a3cedd5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1407,9 +1406,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicase" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" @@ -1445,7 +1444,7 @@ name = "uniffi-builder-macro" version = "0.1.0" dependencies = [ "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1489,7 +1488,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "802d2051a700e3ec894c79f80d2705b69d85844dafbbe5d1a92776f8f48b563a" dependencies = [ "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1519,7 +1518,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.89", + "syn 2.0.96", "toml", "uniffi_meta", ] @@ -1608,47 +1607,48 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1656,33 +1656,34 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-bindgen-test" -version = "0.3.45" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d381749acb0943d357dcbd8f0b100640679883fcdeeef04def49daf8d33a5426" +checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" dependencies = [ - "console_error_panic_hook", "js-sys", "minicov", - "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test-macro", @@ -1690,20 +1691,20 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.45" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0" +checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -1802,9 +1803,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" dependencies = [ "memchr", ] @@ -1839,7 +1840,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1859,5 +1860,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] diff --git a/Cargo.toml b/Cargo.toml index 9670d48d6..9e779e9aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,8 +48,9 @@ subtle = "2" zeroize = { version = "1.8" } rand = "0.8" rand_core = { version = "0.6" } -thiserror = "1.0.64" +thiserror = "2.0.11" typed-builder = "0.20.0" +rust-argon2 = { version = "2.1", default-features = false } ed25519-dalek = { version = "2", features = [ "rand_core" ] } x25519-dalek = { version = "2", features = [ "static_secrets" ] } @@ -63,7 +64,6 @@ paste = "1.0.15" dyn-clone = "1.0.17" [target.'cfg(target_arch="wasm32")'.dependencies] -rust-argon2 = { version = "1.0", default-features = false } wasm-bindgen = { version = "0.2.92", optional = true } serde-wasm-bindgen = { version = "0.6.5", optional = true } js-sys = { version = "0.3.69", optional = true } @@ -75,9 +75,6 @@ js-sys = { version = "0.3.69", optional = true } # all the features selected is used when building a Cargo project) getrandom = { version = "0.2", features = ["js", "wasm-bindgen"] } -[target.'cfg(not(target_arch="wasm32"))'.dependencies] -rust-argon2 = "1.0" - [target.'cfg(target_arch="wasm32")'.dev-dependencies] wasm-bindgen-test = "0.3" diff --git a/src/argon2parameters.rs b/src/argon2parameters.rs index 5bebbd754..636d0e3ea 100644 --- a/src/argon2parameters.rs +++ b/src/argon2parameters.rs @@ -3,7 +3,7 @@ use std::{ io::{Cursor, Read, Write}, }; -use argon2::{Config, ThreadMode, Variant, Version}; +use argon2::{Config, Variant, Version}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use rand_core::{OsRng, RngCore}; use typed_builder::TypedBuilder; @@ -207,12 +207,6 @@ impl Argon2Parameters { time_cost: self.iterations, variant: self.variant, version: self.version, - - #[cfg(target_arch = "wasm32")] - thread_mode: ThreadMode::Sequential, - - #[cfg(not(target_arch = "wasm32"))] - thread_mode: ThreadMode::Parallel, }; Ok(argon2::hash_raw(password, &self.salt, &config)?)