Skip to content

Commit

Permalink
Merge pull request #210 from ANSSI-FR/internal_config
Browse files Browse the repository at this point in the history
[Format-v2] Generate cryptographic materials *inside* MLA, instead of using the user configuration
  • Loading branch information
commial authored Jul 26, 2024
2 parents 4efe7d6 + dc0c339 commit 5168e4a
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 91 deletions.
1 change: 1 addition & 0 deletions bindings/C/mla.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions bindings/C/mla.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions bindings/C/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub enum MLAStatus {
ConfigErrorECIESComputationError = 0x140006,
ConfigErrorKeyCommitmentComputationError = 0x140007,
ConfigErrorKeyCommitmentCheckingError = 0x140008,
ConfigErrorNoRecipients = 0x140009,
DuplicateFilename = 0x150000,
AuthenticatedDecryptionWrongTag = 0x160000,
HKDFInvalidKeyLength = 0x170000,
Expand Down Expand Up @@ -162,6 +163,9 @@ impl From<MLAError> for MLAStatus {
MLAError::ConfigError(ConfigError::CompressionLevelOutOfRange) => {
MLAStatus::ConfigErrorCompressionLevelOutOfRange
}
MLAError::ConfigError(ConfigError::NoRecipients) => {
MLAStatus::ConfigErrorNoRecipients
}
MLAError::ConfigError(ConfigError::EncryptionKeyIsMissing) => {
MLAStatus::ConfigErrorEncryptionKeyIsMissing
}
Expand Down
51 changes: 39 additions & 12 deletions mla/src/config.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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,
Expand All @@ -26,6 +39,13 @@ pub struct ArchivePersistentConfig {
pub encrypt: Option<EncryptionPersistentConfig>,
}

/// Internal config, to be used only during MLA processing, by MLA
#[derive(Default)]
pub(crate) struct InternalConfig {
// Layers specifics
pub(crate) encrypt: Option<InternalEncryptionConfig>,
}

pub type ConfigResult<'a> = Result<&'a mut ArchiveWriterConfig, ConfigError>;

impl ArchiveWriterConfig {
Expand Down Expand Up @@ -57,17 +77,24 @@ impl ArchiveWriterConfig {
}

/// Get the persistent version, to be stored in the header
pub fn to_persistent(&self) -> Result<ArchivePersistentConfig, ConfigError> {
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
Expand Down
3 changes: 3 additions & 0 deletions mla/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
162 changes: 89 additions & 73 deletions mla/src/layers/encrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ 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};
use zeroize::Zeroize;

use super::traits::InnerReaderTrait;

Expand Down Expand Up @@ -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::<Key>();
let nonce = csprng.gen::<[u8; NONCE_SIZE]>();

Self { key, nonce }
}
}

/// Configuration stored in the header, to be reloaded
#[derive(Serialize, Deserialize)]
pub struct EncryptionPersistentConfig {
Expand All @@ -146,71 +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<PublicKey>,
/// Symmetric encryption Key
key: Key,
/// Symmetric encryption nonce
nonce: [u8; NONCE_SIZE],
}

impl std::default::Default for EncryptionConfig {
fn default() -> Self {
// 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
let mut csprng = ChaChaRng::from_entropy();
let key = csprng.gen::<Key>();
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<EncryptionPersistentConfig, ConfigError> {
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,
))
}
}

Expand All @@ -220,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)]
Expand Down Expand Up @@ -288,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
Expand All @@ -300,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<Self, Error> {
pub fn new(
inner: InnerWriterType<'a, W>,
internal_config: &InternalEncryptionConfig,
) -> Result<Self, Error> {
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,
Expand Down Expand Up @@ -675,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,
},
Expand Down Expand Up @@ -795,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,
},
Expand Down
Loading

0 comments on commit 5168e4a

Please sign in to comment.