From 4d6d9b6d050f5ded21511c59de230cc27ffbe3f8 Mon Sep 17 00:00:00 2001 From: Camille Mougey Date: Wed, 24 Jul 2024 17:50:22 +0200 Subject: [PATCH 1/3] Encrypt: force CryptoRngCore on the CSPRNG used --- mla/src/layers/encrypt.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/mla/src/layers/encrypt.rs b/mla/src/layers/encrypt.rs index ba67fd1..fc19ed4 100644 --- a/mla/src/layers/encrypt.rs +++ b/mla/src/layers/encrypt.rs @@ -13,7 +13,7 @@ use std::io::{BufReader, Cursor, Read, Seek, SeekFrom, Write}; use crate::config::{ArchiveReaderConfig, ArchiveWriterConfig}; use crate::errors::ConfigError; use rand::{Rng, SeedableRng}; -use rand_chacha::ChaChaRng; +use rand_chacha::{rand_core::CryptoRngCore, ChaChaRng}; use x25519_dalek::{PublicKey, StaticSecret}; use serde::{Deserialize, Deserializer, Serialize}; @@ -155,8 +155,8 @@ pub struct EncryptionConfig { nonce: [u8; NONCE_SIZE], } -impl std::default::Default for EncryptionConfig { - fn default() -> Self { +/// Return a Cryptographic random number generator +fn get_crypto_rng() -> impl CryptoRngCore { // Use OsRng from crate rand, that uses getrandom() from crate getrandom. // getrandom provides implementations for many systems, listed on // https://docs.rs/getrandom/0.1.14/getrandom/ @@ -172,7 +172,14 @@ impl std::default::Default for EncryptionConfig { // https://github.com/rust-random/rand/blob/rand_core-0.5.1/rand_core/src/lib.rs#L378 // and this function is documented as "secure" in // https://docs.rs/rand/0.7.3/rand/trait.SeedableRng.html#method.from_entropy - let mut csprng = ChaChaRng::from_entropy(); + // + // For the same reasons, force at compile time that the Rng implements CryptoRngCore + ChaChaRng::from_entropy() +} + +impl std::default::Default for EncryptionConfig { + fn default() -> Self { + let mut csprng = get_crypto_rng(); let key = csprng.gen::(); let nonce = csprng.gen::<[u8; NONCE_SIZE]>(); EncryptionConfig { From 359f53f8a6e1efeb602d9d6839664bfd90b343b5 Mon Sep 17 00:00:00 2001 From: Camille Mougey Date: Thu, 25 Jul 2024 11:39:53 +0200 Subject: [PATCH 2/3] Config: split configuration into persistent and internal, avoiding "key" outside MLA --- mla/src/config.rs | 51 +++++++++--- mla/src/errors.rs | 3 + mla/src/layers/encrypt.rs | 167 ++++++++++++++++++++------------------ mla/src/lib.rs | 23 ++++-- 4 files changed, 147 insertions(+), 97 deletions(-) diff --git a/mla/src/config.rs b/mla/src/config.rs index bb76ea0..2e4f473 100644 --- a/mla/src/config.rs +++ b/mla/src/config.rs @@ -1,12 +1,25 @@ use crate::errors::ConfigError; use crate::layers::compress::CompressionConfig; use crate::layers::encrypt::{ - EncryptionConfig, EncryptionPersistentConfig, EncryptionReaderConfig, + EncryptionConfig, EncryptionPersistentConfig, EncryptionReaderConfig, InternalEncryptionConfig, }; use crate::Layers; use serde::{Deserialize, Serialize}; /// This module implements the configuration capabilities of MLA Archive +/// +/// Workflow: +/// ```asciiart +/// `ArchiveWriterConfig` <--> User, add recipients, set the compression level, etc. +/// | +/// v +/// -------------- MLA --- +/// | +/// .to_persistent() +/// / \ +/// MLA specifics <= InternalConfig PersistentConfig => to be stored in the header, +/// for futur reloaded by the Reader +/// ``` /// User's configuration used to prepare an archive pub struct ArchiveWriterConfig { @@ -17,7 +30,7 @@ pub struct ArchiveWriterConfig { pub(crate) encrypt: EncryptionConfig, } -/// Internal configuration stored in the header, to be reloaded +/// Configuration stored in the header, to be reloaded #[derive(Serialize, Deserialize)] pub struct ArchivePersistentConfig { pub layers_enabled: Layers, @@ -26,6 +39,13 @@ pub struct ArchivePersistentConfig { pub encrypt: Option, } +/// Internal config, to be used only during MLA processing, by MLA +#[derive(Default)] +pub(crate) struct InternalConfig { + // Layers specifics + pub(crate) encrypt: Option, +} + pub type ConfigResult<'a> = Result<&'a mut ArchiveWriterConfig, ConfigError>; impl ArchiveWriterConfig { @@ -57,17 +77,24 @@ impl ArchiveWriterConfig { } /// Get the persistent version, to be stored in the header - pub fn to_persistent(&self) -> Result { - Ok(ArchivePersistentConfig { - layers_enabled: self.layers_enabled, - encrypt: { - if self.is_layers_enabled(Layers::ENCRYPT) { - Some(self.encrypt.to_persistent()?) - } else { - None - } + pub(crate) fn to_persistent( + &self, + ) -> Result<(ArchivePersistentConfig, InternalConfig), ConfigError> { + let (encrypt_persistent, encrypt_internal) = if self.is_layers_enabled(Layers::ENCRYPT) { + let (persistent, internal) = self.encrypt.to_persistent()?; + (Some(persistent), Some(internal)) + } else { + (None, None) + }; + Ok(( + ArchivePersistentConfig { + layers_enabled: self.layers_enabled, + encrypt: encrypt_persistent, + }, + InternalConfig { + encrypt: encrypt_internal, }, - }) + )) } /// Check if layers are enabled diff --git a/mla/src/errors.rs b/mla/src/errors.rs index 1f14ac6..3f855be 100644 --- a/mla/src/errors.rs +++ b/mla/src/errors.rs @@ -186,6 +186,9 @@ pub enum ConfigError { // Compression specifics CompressionLevelOutOfRange, // Encryption specifics + /// No recipients provided, encryption can't continue + NoRecipients, + /// Internal state has not yet been created. A call to `to_persistent` might be missing EncryptionKeyIsMissing, PrivateKeyNotSet, PrivateKeyNotFound, diff --git a/mla/src/layers/encrypt.rs b/mla/src/layers/encrypt.rs index fc19ed4..b7572ed 100644 --- a/mla/src/layers/encrypt.rs +++ b/mla/src/layers/encrypt.rs @@ -17,6 +17,7 @@ use rand_chacha::{rand_core::CryptoRngCore, ChaChaRng}; use x25519_dalek::{PublicKey, StaticSecret}; use serde::{Deserialize, Deserializer, Serialize}; +use zeroize::Zeroize; use super::traits::InnerReaderTrait; @@ -135,6 +136,46 @@ impl<'de> Deserialize<'de> for KeyCommitmentAndTag { } } +/// Return a Cryptographic random number generator +fn get_crypto_rng() -> impl CryptoRngCore { + // Use OsRng from crate rand, that uses getrandom() from crate getrandom. + // getrandom provides implementations for many systems, listed on + // https://docs.rs/getrandom/0.1.14/getrandom/ + // On Linux it uses `getrandom()` syscall and falls back on `/dev/urandom`. + // On Windows it uses `RtlGenRandom` API (available since Windows XP/Windows Server 2003). + // + // So this seems to be secure, but unfortunately there is no strong + // warranty that this would stay this way forever. + // In order to be "better safe than sorry", seed `ChaChaRng` from the + // bytes generated by `OsRng` in order to build a CSPRNG + // (Cryptographically Secure PseudoRandom Number Generator). + // This is actually what `ChaChaRng::from_entropy()` does: + // https://github.com/rust-random/rand/blob/rand_core-0.5.1/rand_core/src/lib.rs#L378 + // and this function is documented as "secure" in + // https://docs.rs/rand/0.7.3/rand/trait.SeedableRng.html#method.from_entropy + // + // For the same reasons, force at compile time that the Rng implements CryptoRngCore + ChaChaRng::from_entropy() +} + +#[derive(Zeroize)] +/// Cryptographic material used for encryption in the Encrypt layer +/// Part of this data must be kept secret and drop as soon as possible +pub(crate) struct InternalEncryptionConfig { + pub(crate) key: Key, + pub(crate) nonce: [u8; NONCE_SIZE], +} + +impl Default for InternalEncryptionConfig { + fn default() -> Self { + let mut csprng = get_crypto_rng(); + let key = csprng.gen::(); + let nonce = csprng.gen::<[u8; NONCE_SIZE]>(); + + Self { key, nonce } + } +} + /// Configuration stored in the header, to be reloaded #[derive(Serialize, Deserialize)] pub struct EncryptionPersistentConfig { @@ -146,78 +187,55 @@ pub struct EncryptionPersistentConfig { key_commitment: KeyCommitmentAndTag, } +#[derive(Default)] +/// ArchiveWriterConfig specific configuration for the Encryption, to let API users specify encryption options pub struct EncryptionConfig { - /// Public keys with which to encrypt the symmetric encryption key below + /// Public keys of recipients ecc_keys: Vec, - /// Symmetric encryption Key - key: Key, - /// Symmetric encryption nonce - nonce: [u8; NONCE_SIZE], -} - -/// Return a Cryptographic random number generator -fn get_crypto_rng() -> impl CryptoRngCore { - // Use OsRng from crate rand, that uses getrandom() from crate getrandom. - // getrandom provides implementations for many systems, listed on - // https://docs.rs/getrandom/0.1.14/getrandom/ - // On Linux it uses `getrandom()` syscall and falls back on `/dev/urandom`. - // On Windows it uses `RtlGenRandom` API (available since Windows XP/Windows Server 2003). - // - // So this seems to be secure, but unfortunately there is no strong - // warranty that this would stay this way forever. - // In order to be "better safe than sorry", seed `ChaChaRng` from the - // bytes generated by `OsRng` in order to build a CSPRNG - // (Cryptographically Secure PseudoRandom Number Generator). - // This is actually what `ChaChaRng::from_entropy()` does: - // https://github.com/rust-random/rand/blob/rand_core-0.5.1/rand_core/src/lib.rs#L378 - // and this function is documented as "secure" in - // https://docs.rs/rand/0.7.3/rand/trait.SeedableRng.html#method.from_entropy - // - // For the same reasons, force at compile time that the Rng implements CryptoRngCore - ChaChaRng::from_entropy() -} - -impl std::default::Default for EncryptionConfig { - fn default() -> Self { - let mut csprng = get_crypto_rng(); - let key = csprng.gen::(); - let nonce = csprng.gen::<[u8; NONCE_SIZE]>(); - EncryptionConfig { - ecc_keys: Vec::new(), - key, - nonce, - } - } } impl EncryptionConfig { /// Consistency check pub fn check(&self) -> Result<(), ConfigError> { if self.ecc_keys.is_empty() { - Err(ConfigError::EncryptionKeyIsMissing) + Err(ConfigError::NoRecipients) } else { Ok(()) } } - pub fn to_persistent(&self) -> Result { - let mut rng = ChaChaRng::from_entropy(); - - if let Ok(multi_recipient) = - store_key_for_multi_recipients(&self.ecc_keys, &self.key, &mut rng) - { - if let Ok(key_commitment) = build_key_commitment_chain(&self.key, &self.nonce) { - Ok(EncryptionPersistentConfig { - multi_recipient, - nonce: self.nonce, - key_commitment, - }) - } else { - Err(ConfigError::KeyCommitmentComputationError) - } - } else { - Err(ConfigError::ECIESComputationError) - } + /// Create a persistent version, to be reloaded, of the configuration + /// This method also create the cryptographic material, so it can only be called once + pub(crate) fn to_persistent( + &self, + ) -> Result<(EncryptionPersistentConfig, InternalEncryptionConfig), ConfigError> { + // This will generate the main encrypt layer key & nonce + let cryptographic_material = InternalEncryptionConfig::default(); + + // Store a wrapped version of the key for each recipient + // As this function call be call only once, recipient list can't be changed after + let multi_recipient = store_key_for_multi_recipients( + &self.ecc_keys, + &cryptographic_material.key, + &mut get_crypto_rng(), + ) + .or(Err(ConfigError::ECIESComputationError))?; + + // Add a key commitment + let key_commitment = + build_key_commitment_chain(&cryptographic_material.key, &cryptographic_material.nonce) + .or(Err(ConfigError::KeyCommitmentComputationError))?; + + // Create the persistent version, to be exported + let nonce = cryptographic_material.nonce; + Ok(( + EncryptionPersistentConfig { + multi_recipient, + nonce, + key_commitment, + }, + cryptographic_material, + )) } } @@ -227,16 +245,6 @@ impl ArchiveWriterConfig { self.encrypt.ecc_keys.extend_from_slice(keys); self } - - /// Return the key used for encryption - pub fn encryption_key(&self) -> &Key { - &self.encrypt.key - } - - /// Return the nonce used for encryption - pub fn encryption_nonce(&self) -> &[u8; NONCE_SIZE] { - &self.encrypt.nonce - } } #[derive(Default)] @@ -295,7 +303,7 @@ impl ArchiveReaderConfig { // ---------- Writer ---------- -pub struct EncryptionLayerWriter<'a, W: 'a + InnerWriterTrait> { +pub(crate) struct EncryptionLayerWriter<'a, W: 'a + InnerWriterTrait> { inner: InnerWriterType<'a, W>, cipher: AesGcm256, /// Symmetric encryption Key @@ -307,14 +315,17 @@ pub struct EncryptionLayerWriter<'a, W: 'a + InnerWriterTrait> { } impl<'a, W: 'a + InnerWriterTrait> EncryptionLayerWriter<'a, W> { - pub fn new(inner: InnerWriterType<'a, W>, config: &EncryptionConfig) -> Result { + pub fn new( + inner: InnerWriterType<'a, W>, + internal_config: &InternalEncryptionConfig, + ) -> Result { Ok(Self { inner, - key: config.key, - nonce_prefix: config.nonce, + key: internal_config.key, + nonce_prefix: internal_config.nonce, cipher: AesGcm256::new( - &config.key, - &build_nonce(config.nonce, FIRST_DATA_CHUNK_NUMBER), + &internal_config.key, + &build_nonce(internal_config.nonce, FIRST_DATA_CHUNK_NUMBER), ASSOCIATED_DATA, )?, current_chunk_offset: 0, @@ -682,8 +693,7 @@ mod tests { let mut encrypt_w = Box::new( EncryptionLayerWriter::new( Box::new(RawLayerWriter::new(file)), - &EncryptionConfig { - ecc_keys: Vec::new(), + &InternalEncryptionConfig { key: KEY, nonce: NONCE, }, @@ -802,8 +812,7 @@ mod tests { let mut encrypt_w = Box::new( EncryptionLayerWriter::new( Box::new(RawLayerWriter::new(file)), - &EncryptionConfig { - ecc_keys: Vec::new(), + &InternalEncryptionConfig { key: KEY, nonce: NONCE, }, diff --git a/mla/src/lib.rs b/mla/src/lib.rs index cc0b0cf..97b8842 100644 --- a/mla/src/lib.rs +++ b/mla/src/lib.rs @@ -6,6 +6,8 @@ use std::io::{Read, Seek, SeekFrom, Write}; extern crate bitflags; use bincode::Options; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use config::InternalConfig; +use errors::ConfigError; use layers::traits::InnerReaderTrait; use serde::{Deserialize, Serialize}; @@ -431,7 +433,7 @@ pub struct ArchiveWriter<'a, W: 'a + InnerWriterTrait> { // config is not used for now after archive creation, // but it could in the future #[allow(dead_code)] - config: ArchiveWriterConfig, + internal_config: InternalConfig, /// /// Internals part: /// @@ -473,17 +475,26 @@ impl<'a, W: InnerWriterTrait> ArchiveWriter<'a, W> { // Ensure config is correct config.check()?; + // Seperate config into the internal state and the persistent config + let (persistent, internal) = config.to_persistent()?; + // Write archive header let mut dest: InnerWriterType = Box::new(RawLayerWriter::new(dest)); ArchiveHeader { format_version: MLA_FORMAT_VERSION, - config: config.to_persistent()?, + config: persistent, } .dump(&mut dest)?; // Enable layers depending on user option if config.is_layers_enabled(Layers::ENCRYPT) { - dest = Box::new(EncryptionLayerWriter::new(dest, &config.encrypt)?); + dest = Box::new(EncryptionLayerWriter::new( + dest, + internal + .encrypt + .as_ref() + .ok_or(ConfigError::EncryptionKeyIsMissing)?, + )?); } if config.is_layers_enabled(Layers::COMPRESS) { dest = Box::new(CompressionLayerWriter::new(dest, &config.compress)); @@ -495,7 +506,7 @@ impl<'a, W: InnerWriterTrait> ArchiveWriter<'a, W> { // Build initial archive Ok(ArchiveWriter { - config, + internal_config: internal, dest: final_dest, state: ArchiveWriterState::OpenedFiles { ids: Vec::new(), @@ -1375,8 +1386,8 @@ pub(crate) mod tests { mla.end_file(id).unwrap(); mla.finalize().unwrap(); - let mla_key = *mla.config.encryption_key(); - let mla_nonce = *mla.config.encryption_nonce(); + let mla_key = mla.internal_config.encrypt.as_ref().unwrap().key; + let mla_nonce = mla.internal_config.encrypt.as_ref().unwrap().nonce; let dest = mla.into_raw(); let buf = Cursor::new(dest.as_slice()); let mut config = ArchiveReaderConfig::new(); From dc0c339e2119164cba53b06f6dfd5216f7308404 Mon Sep 17 00:00:00 2001 From: Camille Mougey Date: Thu, 25 Jul 2024 11:51:11 +0200 Subject: [PATCH 3/3] Bindings/C: update with new error code --- bindings/C/mla.h | 1 + bindings/C/mla.hpp | 1 + bindings/C/src/lib.rs | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/bindings/C/mla.h b/bindings/C/mla.h index a1630d0..dd04f62 100644 --- a/bindings/C/mla.h +++ b/bindings/C/mla.h @@ -35,6 +35,7 @@ enum MLAStatus { MLA_STATUS_CONFIG_ERROR_ECIES_COMPUTATION_ERROR = 1310726, MLA_STATUS_CONFIG_ERROR_KEY_COMMITMENT_COMPUTATION_ERROR = 1310727, MLA_STATUS_CONFIG_ERROR_KEY_COMMITMENT_CHECKING_ERROR = 1310728, + MLA_STATUS_CONFIG_ERROR_NO_RECIPIENTS = 1310729, MLA_STATUS_DUPLICATE_FILENAME = 1376256, MLA_STATUS_AUTHENTICATED_DECRYPTION_WRONG_TAG = 1441792, MLA_STATUS_HKDF_INVALID_KEY_LENGTH = 1507328, diff --git a/bindings/C/mla.hpp b/bindings/C/mla.hpp index 3da2d72..f521259 100644 --- a/bindings/C/mla.hpp +++ b/bindings/C/mla.hpp @@ -36,6 +36,7 @@ enum class MLAStatus : uint64_t { MLA_STATUS_CONFIG_ERROR_ECIES_COMPUTATION_ERROR = 1310726, MLA_STATUS_CONFIG_ERROR_KEY_COMMITMENT_COMPUTATION_ERROR = 1310727, MLA_STATUS_CONFIG_ERROR_KEY_COMMITMENT_CHECKING_ERROR = 1310728, + MLA_STATUS_CONFIG_ERROR_NO_RECIPIENTS = 1310729, MLA_STATUS_DUPLICATE_FILENAME = 1376256, MLA_STATUS_AUTHENTICATED_DECRYPTION_WRONG_TAG = 1441792, MLA_STATUS_HKDF_INVALID_KEY_LENGTH = 1507328, diff --git a/bindings/C/src/lib.rs b/bindings/C/src/lib.rs index bffecba..7557207 100644 --- a/bindings/C/src/lib.rs +++ b/bindings/C/src/lib.rs @@ -51,6 +51,7 @@ pub enum MLAStatus { ConfigErrorECIESComputationError = 0x140006, ConfigErrorKeyCommitmentComputationError = 0x140007, ConfigErrorKeyCommitmentCheckingError = 0x140008, + ConfigErrorNoRecipients = 0x140009, DuplicateFilename = 0x150000, AuthenticatedDecryptionWrongTag = 0x160000, HKDFInvalidKeyLength = 0x170000, @@ -162,6 +163,9 @@ impl From for MLAStatus { MLAError::ConfigError(ConfigError::CompressionLevelOutOfRange) => { MLAStatus::ConfigErrorCompressionLevelOutOfRange } + MLAError::ConfigError(ConfigError::NoRecipients) => { + MLAStatus::ConfigErrorNoRecipients + } MLAError::ConfigError(ConfigError::EncryptionKeyIsMissing) => { MLAStatus::ConfigErrorEncryptionKeyIsMissing }