Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pkcs5/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ spki = "0.8"

# optional dependencies
aes = { version = "0.9", optional = true, default-features = false }
aes-gcm = { version = "0.11.0-rc.3", optional = true, default-features = false, features = ["aes"] }
cbc = { version = "0.2", optional = true }
des = { version = "0.9", optional = true, default-features = false }
pbkdf2 = { version = "0.13", optional = true, default-features = false, features = ["hmac"] }
Expand All @@ -39,7 +40,7 @@ alloc = []
3des = ["dep:des", "pbes2"]
des-insecure = ["dep:des", "pbes2"]
getrandom = ["dep:getrandom", "rand_core"]
pbes2 = ["dep:aes", "dep:cbc", "dep:pbkdf2", "dep:scrypt", "dep:sha2"]
pbes2 = ["dep:aes", "dep:aes-gcm", "dep:cbc", "dep:pbkdf2", "dep:scrypt", "dep:sha2"]
rand_core = ["dep:rand_core"]
sha1-insecure = ["dep:sha1", "pbes2"]

Expand Down
78 changes: 78 additions & 0 deletions pkcs5/src/pbes2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ pub const AES_192_CBC_OID: ObjectIdentifier =
pub const AES_256_CBC_OID: ObjectIdentifier =
ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.1.42");

/// 128-bit Advanced Encryption Standard (AES) algorithm with Galois Counter Mode
pub const AES_128_GCM_OID: ObjectIdentifier =
ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.1.6");

/// 256-bit Advanced Encryption Standard (AES) algorithm with Galois Counter Mode
pub const AES_256_GCM_OID: ObjectIdentifier =
ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.1.46");

/// DES operating in CBC mode
#[cfg(feature = "des-insecure")]
pub const DES_CBC_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.14.3.2.7");
Expand All @@ -55,6 +63,12 @@ pub const PBES2_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.11
/// AES cipher block size
const AES_BLOCK_SIZE: usize = 16;

/// GCM nonce size
///
/// We could use any value here but GCM is most efficient
/// with 96 bit nonces
const GCM_NONCE_SIZE: usize = 12;

/// DES / Triple DES block size
#[cfg(any(feature = "3des", feature = "des-insecure"))]
const DES_BLOCK_SIZE: usize = 8;
Expand Down Expand Up @@ -255,6 +269,46 @@ impl Parameters {
Ok(Self { kdf, encryption })
}

/// Initialize PBES2 parameters using scrypt as the password-based
/// key derivation function and AES-128-GCM as the symmetric cipher.
///
/// For more information on scrypt parameters, see documentation for the
/// [`scrypt::Params`] struct.
///
/// # Errors
/// Propagates errors from [`ScryptParams::from_params_and_salt`].
// TODO(tarcieri): encapsulate `scrypt::Params`?
#[cfg(feature = "pbes2")]
pub fn scrypt_aes128gcm(
params: scrypt::Params,
salt: &[u8],
gcm_nonce: [u8; GCM_NONCE_SIZE],
) -> Result<Self> {
let kdf = ScryptParams::from_params_and_salt(params, salt)?.into();
let encryption = EncryptionScheme::Aes128Gcm { nonce: gcm_nonce };
Ok(Self { kdf, encryption })
}

/// Initialize PBES2 parameters using scrypt as the password-based
/// key derivation function and AES-256-GCM as the symmetric cipher.
///
/// For more information on scrypt parameters, see documentation for the
/// [`scrypt::Params`] struct.
///
/// # Errors
/// Propagates errors from [`ScryptParams::from_params_and_salt`].
// TODO(tarcieri): encapsulate `scrypt::Params`?
#[cfg(feature = "pbes2")]
pub fn scrypt_aes256gcm(
params: scrypt::Params,
salt: &[u8],
gcm_nonce: [u8; GCM_NONCE_SIZE],
) -> Result<Self> {
let kdf = ScryptParams::from_params_and_salt(params, salt)?.into();
let encryption = EncryptionScheme::Aes256Gcm { nonce: gcm_nonce };
Ok(Self { kdf, encryption })
}

/// Attempt to decrypt the given ciphertext, allocating and returning a
/// byte vector containing the plaintext.
///
Expand Down Expand Up @@ -387,6 +441,18 @@ pub enum EncryptionScheme {
iv: [u8; AES_BLOCK_SIZE],
},

/// AES-128 in CBC mode
Aes128Gcm {
/// GCM nonce
nonce: [u8; GCM_NONCE_SIZE],
},

/// AES-256 in GCM mode
Aes256Gcm {
/// GCM nonce
nonce: [u8; GCM_NONCE_SIZE],
},

/// 3-Key Triple DES in CBC mode
#[cfg(feature = "3des")]
DesEde3Cbc {
Expand All @@ -410,6 +476,8 @@ impl EncryptionScheme {
Self::Aes128Cbc { .. } => 16,
Self::Aes192Cbc { .. } => 24,
Self::Aes256Cbc { .. } => 32,
Self::Aes128Gcm { .. } => 16,
Self::Aes256Gcm { .. } => 32,
#[cfg(feature = "des-insecure")]
Self::DesCbc { .. } => 8,
#[cfg(feature = "3des")]
Expand All @@ -424,6 +492,8 @@ impl EncryptionScheme {
Self::Aes128Cbc { .. } => AES_128_CBC_OID,
Self::Aes192Cbc { .. } => AES_192_CBC_OID,
Self::Aes256Cbc { .. } => AES_256_CBC_OID,
Self::Aes128Gcm { .. } => AES_128_GCM_OID,
Self::Aes256Gcm { .. } => AES_256_GCM_OID,
#[cfg(feature = "des-insecure")]
Self::DesCbc { .. } => DES_CBC_OID,
#[cfg(feature = "3des")]
Expand Down Expand Up @@ -468,6 +538,12 @@ impl TryFrom<AlgorithmIdentifierRef<'_>> for EncryptionScheme {
AES_256_CBC_OID => Ok(Self::Aes256Cbc {
iv: iv.try_into().map_err(|_| Tag::OctetString.value_error())?,
}),
AES_128_GCM_OID => Ok(Self::Aes128Gcm {
nonce: iv.try_into().map_err(|_| Tag::OctetString.value_error())?,
}),
AES_256_GCM_OID => Ok(Self::Aes256Gcm {
nonce: iv.try_into().map_err(|_| Tag::OctetString.value_error())?,
}),
#[cfg(feature = "des-insecure")]
DES_CBC_OID => Ok(Self::DesCbc {
iv: iv[0..DES_BLOCK_SIZE]
Expand All @@ -493,6 +569,8 @@ impl<'a> TryFrom<&'a EncryptionScheme> for AlgorithmIdentifierRef<'a> {
EncryptionScheme::Aes128Cbc { iv } => iv.as_slice(),
EncryptionScheme::Aes192Cbc { iv } => iv.as_slice(),
EncryptionScheme::Aes256Cbc { iv } => iv.as_slice(),
EncryptionScheme::Aes128Gcm { nonce } => nonce.as_slice(),
EncryptionScheme::Aes256Gcm { nonce } => nonce.as_slice(),
#[cfg(feature = "des-insecure")]
EncryptionScheme::DesCbc { iv } => iv.as_slice(),
#[cfg(feature = "3des")]
Expand Down
74 changes: 73 additions & 1 deletion pkcs5/src/pbes2/encryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use super::{EncryptionScheme, Kdf, Parameters, Pbkdf2Params, Pbkdf2Prf, ScryptParams};
use crate::{Error, Result};
use aes_gcm::{KeyInit as GcmKeyInit, Nonce, Tag, aead::AeadInOut};
use cbc::cipher::{
BlockCipherDecrypt, BlockCipherEncrypt, BlockModeDecrypt, BlockModeEncrypt, KeyInit, KeyIvInit,
block_padding::Pkcs7,
Expand All @@ -12,7 +13,7 @@ use pbkdf2::{
digest::{
FixedOutput, HashMarker, Update,
block_api::BlockSizeUser,
typenum::{IsLess, NonZero, True, U256},
typenum::{IsLess, NonZero, True, U12, U16, U256},
},
},
pbkdf2_hmac,
Expand Down Expand Up @@ -47,6 +48,65 @@ fn cbc_decrypt<'a, C: BlockCipherDecrypt + KeyInit>(
.map_err(|_| Error::DecryptFailed)
}

fn gcm_encrypt<C, NonceSize, TagSize>(
es: EncryptionScheme,
key: EncryptionKey,
nonce: Nonce<NonceSize>,
buffer: &mut [u8],
pos: usize,
) -> Result<&[u8]>
where
C: BlockSizeUser<BlockSize = U16> + GcmKeyInit + BlockCipherEncrypt,
aes_gcm::AesGcm<C, NonceSize, TagSize>: GcmKeyInit,
TagSize: aes_gcm::TagSize,
NonceSize: aes::cipher::array::ArraySize,
{
if buffer.len() < TagSize::USIZE + pos {
return Err(Error::EncryptFailed);
}
let gcm =
<aes_gcm::AesGcm<C, NonceSize, TagSize> as GcmKeyInit>::new_from_slice(key.as_slice())
.map_err(|_| es.to_alg_params_invalid())?;
let tag = gcm
.encrypt_inout_detached(&nonce, &[], (&mut buffer[..pos]).into())
.map_err(|_| Error::EncryptFailed)?;
buffer[pos..].copy_from_slice(tag.as_ref());
Ok(&buffer[0..pos + TagSize::USIZE])
}

fn gcm_decrypt<C, NonceSize, TagSize>(
es: EncryptionScheme,
key: EncryptionKey,
nonce: Nonce<NonceSize>,
buffer: &mut [u8],
) -> Result<&[u8]>
where
C: BlockSizeUser<BlockSize = U16> + GcmKeyInit + BlockCipherEncrypt,
aes_gcm::AesGcm<C, NonceSize, TagSize>: GcmKeyInit,
TagSize: aes_gcm::TagSize,
NonceSize: aes::cipher::array::ArraySize,
{
let msg_len = buffer
.len()
.checked_sub(TagSize::USIZE)
.ok_or(Error::DecryptFailed)?;

let gcm =
<aes_gcm::AesGcm<C, NonceSize, TagSize> as GcmKeyInit>::new_from_slice(key.as_slice())
.map_err(|_| es.to_alg_params_invalid())?;

let tag = Tag::try_from(&buffer[msg_len..]).map_err(|_| Error::DecryptFailed)?;

if gcm
.decrypt_inout_detached(&nonce, &[], (&mut buffer[..msg_len]).into(), &tag)
.is_err()
{
return Err(Error::DecryptFailed);
}

Ok(&buffer[..msg_len])
}

pub fn encrypt_in_place<'b>(
params: &Parameters,
password: impl AsRef<[u8]>,
Expand All @@ -64,6 +124,12 @@ pub fn encrypt_in_place<'b>(
EncryptionScheme::Aes128Cbc { iv } => cbc_encrypt::<aes::Aes128Enc>(es, key, &iv, buf, pos),
EncryptionScheme::Aes192Cbc { iv } => cbc_encrypt::<aes::Aes192Enc>(es, key, &iv, buf, pos),
EncryptionScheme::Aes256Cbc { iv } => cbc_encrypt::<aes::Aes256Enc>(es, key, &iv, buf, pos),
EncryptionScheme::Aes128Gcm { nonce } => {
gcm_encrypt::<aes::Aes128Enc, U12, U16>(es, key, Nonce::from(nonce), buf, pos)
}
EncryptionScheme::Aes256Gcm { nonce } => {
gcm_encrypt::<aes::Aes256Enc, U12, U16>(es, key, Nonce::from(nonce), buf, pos)
}
#[cfg(feature = "3des")]
EncryptionScheme::DesEde3Cbc { iv } => cbc_encrypt::<des::TdesEde3>(es, key, &iv, buf, pos),
#[cfg(feature = "des-insecure")]
Expand All @@ -86,6 +152,12 @@ pub fn decrypt_in_place<'a>(
EncryptionScheme::Aes128Cbc { iv } => cbc_decrypt::<aes::Aes128Dec>(es, key, &iv, buf),
EncryptionScheme::Aes192Cbc { iv } => cbc_decrypt::<aes::Aes192Dec>(es, key, &iv, buf),
EncryptionScheme::Aes256Cbc { iv } => cbc_decrypt::<aes::Aes256Dec>(es, key, &iv, buf),
EncryptionScheme::Aes128Gcm { nonce } => {
gcm_decrypt::<aes::Aes128Enc, U12, U16>(es, key, Nonce::from(nonce), buf)
}
EncryptionScheme::Aes256Gcm { nonce } => {
gcm_decrypt::<aes::Aes256Enc, U12, U16>(es, key, Nonce::from(nonce), buf)
}
#[cfg(feature = "3des")]
EncryptionScheme::DesEde3Cbc { iv } => cbc_decrypt::<des::TdesEde3>(es, key, &iv, buf),
#[cfg(feature = "des-insecure")]
Expand Down
Loading
Loading