Skip to content

Commit

Permalink
Begin documenting the crypto module (#318)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hinton authored Nov 2, 2023
1 parent eefc58b commit 1338396
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 2 deletions.
9 changes: 9 additions & 0 deletions Cargo.lock

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

38 changes: 37 additions & 1 deletion crates/bitwarden/src/crypto/aes_ops.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
//! # AES operations
//!
//! Contains low level AES operations used by the rest of the library.
//!
//! **Warning**: Consider carefully if you have to use these functions directly, as generally we
//! expose higher level functions that are easier to use and more secure.
//!
//! In most cases you should use the [EncString] with [KeyEncryptable][super::KeyEncryptable] &
//! [KeyDecryptable][super::KeyDecryptable] instead.
use aes::cipher::{
block_padding::Pkcs7, generic_array::GenericArray, typenum::U32, BlockDecryptMut,
BlockEncryptMut, KeyIvInit,
Expand All @@ -10,6 +20,9 @@ use crate::{
error::{CryptoError, Result},
};

/// Decrypt using AES-256 in CBC mode.
///
/// Behaves similar to [decrypt_aes256_hmac], but does not validate the MAC.
pub fn decrypt_aes256(iv: &[u8; 16], data: Vec<u8>, key: GenericArray<u8, U32>) -> Result<Vec<u8>> {
// Decrypt data
let iv = GenericArray::from_slice(iv);
Expand All @@ -18,13 +31,16 @@ pub fn decrypt_aes256(iv: &[u8; 16], data: Vec<u8>, key: GenericArray<u8, U32>)
.decrypt_padded_mut::<Pkcs7>(&mut data)
.map_err(|_| CryptoError::KeyDecrypt)?;

//Data is decrypted in place and returns a subslice of the original Vec, to avoid cloning it, we truncate to the subslice length
// Data is decrypted in place and returns a subslice of the original Vec, to avoid cloning it, we truncate to the subslice length
let decrypted_len = decrypted_key_slice.len();
data.truncate(decrypted_len);

Ok(data)
}

/// Decrypt using AES-256 in CBC mode with MAC.
///
/// Behaves similar to [decrypt_aes256], but also validates the MAC.
pub fn decrypt_aes256_hmac(
iv: &[u8; 16],
mac: &[u8; 32],
Expand All @@ -39,12 +55,26 @@ pub fn decrypt_aes256_hmac(
decrypt_aes256(iv, data, key)
}

/// Encrypt using AES-256 in CBC mode.
///
/// Behaves similar to [encrypt_aes256_hmac], but does't generate a MAC.
///
/// ## Returns
///
/// A AesCbc256_B64 EncString
pub fn encrypt_aes256(data_dec: &[u8], key: GenericArray<u8, U32>) -> Result<EncString> {
let (iv, data) = encrypt_aes256_internal(data_dec, key);

Ok(EncString::AesCbc256_B64 { iv, data })
}

/// Encrypt using AES-256 in CBC mode with MAC.
///
/// Behaves similar to [encrypt_aes256], but also generate a MAC.
///
/// ## Returns
///
/// A AesCbc256_HmacSha256_B64 EncString
pub fn encrypt_aes256_hmac(
data_dec: &[u8],
mac_key: GenericArray<u8, U32>,
Expand All @@ -56,6 +86,11 @@ pub fn encrypt_aes256_hmac(
Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data })
}

/// Encrypt using AES-256 in CBC mode.
///
/// Used internally by:
/// - [encrypt_aes256]
/// - [encrypt_aes256_hmac]
fn encrypt_aes256_internal(data_dec: &[u8], key: GenericArray<u8, U32>) -> ([u8; 16], Vec<u8>) {
let mut iv = [0u8; 16];
rand::thread_rng().fill_bytes(&mut iv);
Expand All @@ -65,6 +100,7 @@ fn encrypt_aes256_internal(data_dec: &[u8], key: GenericArray<u8, U32>) -> ([u8;
(iv, data)
}

/// Validate a MAC using HMAC-SHA256.
fn validate_mac(mac_key: &[u8], iv: &[u8], data: &[u8]) -> Result<[u8; 32]> {
let mut hmac = PbkdfSha256Hmac::new_from_slice(mac_key).expect("HMAC can take key of any size");
hmac.update(iv);
Expand Down
43 changes: 42 additions & 1 deletion crates/bitwarden/src/crypto/enc_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,45 @@ use crate::{

use super::{KeyDecryptable, KeyEncryptable, LocateKey};

/// # Encrypted string primitive
///
/// [EncString] is a Bitwarden specific primitive that represents an encrypted string. They are
/// are used together with the [KeyDecryptable] and [KeyEncryptable] traits to encrypt and decrypt
/// data using [SymmetricCryptoKey]s.
///
/// The flexibility of the [EncString] type allows for different encryption algorithms to be used
/// which is represented by the different variants of the enum.
///
/// ## Note
///
/// We are currently in the progress of splitting the [EncString] into distinct AES and RSA
/// variants. To provide better control of which encryption algorithm is expected.
///
/// For backwards compatibility we will rarely if ever be able to remove support for decrypting old
/// variants, but we should be opinionated in which variants are used for encrypting.
///
/// ## Variants
/// - [AesCbc256_B64](EncString::AesCbc256_B64)
/// - [AesCbc128_HmacSha256_B64](EncString::AesCbc128_HmacSha256_B64)
/// - [AesCbc256_HmacSha256_B64](EncString::AesCbc256_HmacSha256_B64)
/// - [Rsa2048_OaepSha256_B64](EncString::Rsa2048_OaepSha256_B64)
/// - [Rsa2048_OaepSha1_B64](EncString::Rsa2048_OaepSha1_B64)
///
/// ## Serialization
///
/// [EncString] implements [Display] and [FromStr] to allow for easy serialization and uses a
/// custom scheme to represent the different variants.
///
/// The scheme is one of the following schemes:
/// - `[type].[iv]|[data]`
/// - `[type].[iv]|[data]|[mac]`
/// - `[type].[data]`
///
/// Where:
/// - `[type]`: is a digit number representing the variant.
/// - `[iv]`: (optional) is the initialization vector used for encryption.
/// - `[data]`: is the encrypted data.
/// - `[mac]`: (optional) is the MAC used to validate the integrity of the data.
#[derive(Clone)]
#[allow(unused, non_camel_case_types)]
pub enum EncString {
Expand Down Expand Up @@ -40,13 +79,14 @@ pub enum EncString {
Rsa2048_OaepSha1_HmacSha256_B64 { data: Vec<u8> },
}

// We manually implement these to make sure we don't print any sensitive data
/// To avoid printing sensitive information, [EncString] debug prints to `EncString`.
impl std::fmt::Debug for EncString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EncString").finish()
}
}

/// Deserializes an [EncString] from a string.
impl FromStr for EncString {
type Err = Error;

Expand Down Expand Up @@ -291,6 +331,7 @@ impl serde::Serialize for EncString {
}

impl EncString {
/// The numerical representation of the encryption type of the [EncString].
const fn enc_type(&self) -> u8 {
match self {
EncString::AesCbc256_B64 { .. } => 0,
Expand Down
2 changes: 2 additions & 0 deletions crates/bitwarden/src/crypto/encryptable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ pub trait LocateKey {
}
}

/// Deprecated: please use LocateKey and KeyDecryptable instead
pub trait Encryptable<Output> {
fn encrypt(self, enc: &EncryptionSettings, org_id: &Option<Uuid>) -> Result<Output>;
}

/// Deprecated: please use LocateKey and KeyDecryptable instead
pub trait Decryptable<Output> {
fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option<Uuid>) -> Result<Output>;
}
Expand Down
6 changes: 6 additions & 0 deletions crates/bitwarden/src/crypto/fingerprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ use crate::{
wordlist::EFF_LONG_WORD_LIST,
};

/// Computes a fingerprint of the given `fingerprint_material` using the given `public_key`.
///
/// This is commonly used for account fingerprints. With the following arguments:
/// - `fingerprint_material`: user's id.
/// - `public_key`: user's public key.
pub(crate) fn fingerprint(fingerprint_material: &str, public_key: &[u8]) -> Result<String> {
let mut h = sha2::Sha256::new();
h.update(public_key);
Expand All @@ -22,6 +27,7 @@ pub(crate) fn fingerprint(fingerprint_material: &str, public_key: &[u8]) -> Resu
Ok(hash_word(user_fingerprint).unwrap())
}

/// Derive a 5 word phrase from a 32 byte hash.
fn hash_word(hash: [u8; 32]) -> Result<String> {
let minimum_entropy = 64;

Expand Down

0 comments on commit 1338396

Please sign in to comment.