Skip to content

Commit

Permalink
Experiment with using a separate crate for crypto
Browse files Browse the repository at this point in the history
  • Loading branch information
Hinton committed Dec 5, 2023
1 parent 327cbb8 commit 3c55a87
Show file tree
Hide file tree
Showing 33 changed files with 216 additions and 67 deletions.
32 changes: 32 additions & 0 deletions Cargo.lock

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

48 changes: 48 additions & 0 deletions crates/bitwarden-crypto/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
[package]
name = "bitwarden-crypto"
version = "0.1.0"
authors = ["Bitwarden Inc"]
license-file = "LICENSE"
repository = "https://github.com/bitwarden/sdk"
homepage = "https://bitwarden.com"
description = """
Bitwarden Cryptographic primitives
"""
keywords = ["bitwarden"]
edition = "2021"
rust-version = "1.57"

[dependencies]
aes = ">=0.8.2, <0.9"
argon2 = { version = ">=0.5.0, <0.6", features = [
"alloc",
], default-features = false }
assert_matches = ">=1.5.0, <2.0"
base64 = ">=0.21.2, <0.22"
cbc = { version = ">=0.1.2, <0.2", features = ["alloc"] }
chrono = { version = ">=0.4.26, <0.5", features = [
"clock",
"serde",
"std",
], default-features = false }
hkdf = ">=0.12.3, <0.13"
hmac = ">=0.12.1, <0.13"
lazy_static = ">=1.4.0, <2.0"
log = ">=0.4.18, <0.5"
num-bigint = ">=0.4, <0.5"
num-traits = ">=0.2.15, <0.3"
pbkdf2 = { version = ">=0.12.1, <0.13", default-features = false }
rand = ">=0.8.5, <0.9"
rsa = ">=0.9.2, <0.10"
schemars = { version = ">=0.8, <0.9", features = ["uuid1", "chrono"] }
serde = { version = ">=1.0, <2.0", features = ["derive"] }
sha1 = ">=0.10.5, <0.11"
sha2 = ">=0.10.6, <0.11"
subtle = ">=2.5.0, <3.0"
thiserror = ">=1.0.40, <2.0"
uniffi = { version = "=0.25.2", optional = true }

[dev-dependencies]
rand_chacha = "0.3.1"
tokio = { version = "1.34.0", features = ["rt", "macros"] }
wiremock = "0.5.22"
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use hmac::Mac;
use subtle::ConstantTimeEq;

use crate::{
crypto::{PbkdfSha256Hmac, PBKDF_SHA256_HMAC_OUT_SIZE},
error::{CryptoError, Result},
PbkdfSha256Hmac, PBKDF_SHA256_HMAC_OUT_SIZE,
};

/// Decrypt using AES-256 in CBC mode.
Expand Down Expand Up @@ -50,7 +50,7 @@ pub fn decrypt_aes256_hmac(
) -> Result<Vec<u8>> {
let res = generate_mac(&mac_key, iv, &data)?;
if res.ct_ne(mac).into() {
return Err(CryptoError::InvalidMac.into());
return Err(CryptoError::InvalidMac);
}
decrypt_aes256(iv, data, key)
}
Expand Down Expand Up @@ -125,9 +125,8 @@ mod tests {
use base64::Engine;
use rand::SeedableRng;

use crate::util::BASE64_ENGINE;

use super::*;
use crate::BASE64_ENGINE;

/// Helper function for generating a `GenericArray` of size 32 with each element being
/// a multiple of a given increment, starting from a given offset.
Expand Down
21 changes: 21 additions & 0 deletions crates/bitwarden-crypto/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use std::fmt::Debug;

use thiserror::Error;

#[derive(Debug, Error)]
pub enum CryptoError {
#[error("The provided key is not the expected type")]
InvalidKey,
#[error("The cipher's MAC doesn't match the expected value")]
InvalidMac,
#[error("Error while decrypting EncString")]
KeyDecrypt,
#[error("The cipher key has an invalid length")]
InvalidKeyLen,
#[error("There is no encryption key for the provided organization")]
NoKeyForOrg,
#[error("The value is not a valid UTF8 String")]
InvalidUtf8String,
}

pub type Result<T, E = CryptoError> = std::result::Result<T, E>;
38 changes: 38 additions & 0 deletions crates/bitwarden-crypto/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use ::aes::cipher::{generic_array::GenericArray, ArrayLength, Unsigned};
use base64::{
alphabet,
engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig},
};
use hmac::digest::OutputSizeUser;

pub mod aes;
mod error;
pub mod shareable_key;
pub mod symmetric_crypto_key;

pub use error::CryptoError;
use error::Result;

// TODO: Move into a util crate
const BASE64_ENGINE_CONFIG: GeneralPurposeConfig = GeneralPurposeConfig::new()
.with_encode_padding(true)
.with_decode_padding_mode(DecodePaddingMode::Indifferent);

pub const BASE64_ENGINE: GeneralPurpose =
GeneralPurpose::new(&alphabet::STANDARD, BASE64_ENGINE_CONFIG);

pub(crate) type PbkdfSha256Hmac = hmac::Hmac<sha2::Sha256>;
pub(crate) const PBKDF_SHA256_HMAC_OUT_SIZE: usize =
<<PbkdfSha256Hmac as OutputSizeUser>::OutputSize as Unsigned>::USIZE;

/// RFC5869 HKDF-Expand operation
fn hkdf_expand<T: ArrayLength<u8>>(prk: &[u8], info: Option<&str>) -> Result<GenericArray<u8, T>> {
let hkdf = hkdf::Hkdf::<sha2::Sha256>::from_prk(prk).map_err(|_| CryptoError::InvalidKeyLen)?;
let mut key = GenericArray::<u8, T>::default();

let i = info.map(|i| i.as_bytes()).unwrap_or(&[]);
hkdf.expand(i, &mut key)
.map_err(|_| CryptoError::InvalidKeyLen)?;

Ok(key)
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use aes::cipher::{generic_array::GenericArray, typenum::U64};
use hmac::{Hmac, Mac};

use crate::crypto::{hkdf_expand, SymmetricCryptoKey};
use crate::{hkdf_expand, symmetric_crypto_key::SymmetricCryptoKey};

/// Derive a shareable key using hkdf from secret and name.
///
/// A specialized variant of this function was called `CryptoService.makeSendKey` in the Bitwarden
/// `clients` repository.
pub(crate) fn derive_shareable_key(
pub fn derive_shareable_key(
secret: [u8; 16],
name: &str,
info: Option<&str>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@ use std::str::FromStr;
use aes::cipher::{generic_array::GenericArray, typenum::U32};
use base64::Engine;

use crate::{
crypto::derive_shareable_key,
error::{CryptoError, Error},
util::BASE64_ENGINE,
};
use crate::{shareable_key::derive_shareable_key, CryptoError, BASE64_ENGINE};

/// A symmetric encryption key. Used to encrypt and decrypt [`EncString`](crate::crypto::EncString)
pub struct SymmetricCryptoKey {
Expand Down Expand Up @@ -38,7 +34,7 @@ impl SymmetricCryptoKey {
}

impl FromStr for SymmetricCryptoKey {
type Err = Error;
type Err = CryptoError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = BASE64_ENGINE
Expand All @@ -49,7 +45,7 @@ impl FromStr for SymmetricCryptoKey {
}

impl TryFrom<&[u8]> for SymmetricCryptoKey {
type Error = Error;
type Error = CryptoError;

fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
if value.len() == Self::KEY_LEN + Self::MAC_LEN {
Expand Down
1 change: 1 addition & 0 deletions crates/bitwarden/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ assert_matches = ">=1.5.0, <2.0"
base64 = ">=0.21.2, <0.22"
bitwarden-api-api = { path = "../bitwarden-api-api", version = "=0.2.2" }
bitwarden-api-identity = { path = "../bitwarden-api-identity", version = "=0.2.2" }
bitwarden-crypto = { path = "../bitwarden-crypto", version = "=0.1.0" }
cbc = { version = ">=0.1.2, <0.2", features = ["alloc"] }
chrono = { version = ">=0.4.26, <0.5", features = [
"clock",
Expand Down
3 changes: 2 additions & 1 deletion crates/bitwarden/src/auth/login/access_token.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use base64::Engine;
use bitwarden_crypto::symmetric_crypto_key::SymmetricCryptoKey;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

Expand All @@ -9,7 +10,7 @@ use crate::{
JWTToken,
},
client::{AccessToken, LoginMethod, ServiceAccountLoginMethod},
crypto::{EncString, KeyDecryptable, SymmetricCryptoKey},
crypto::{EncString, KeyDecryptable},
error::{Error, Result},
util::BASE64_ENGINE,
Client,
Expand Down
9 changes: 4 additions & 5 deletions crates/bitwarden/src/client/access_token.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use std::str::FromStr;

use base64::Engine;
use bitwarden_crypto::{
shareable_key::derive_shareable_key, symmetric_crypto_key::SymmetricCryptoKey,
};
use uuid::Uuid;

use crate::{
crypto::{derive_shareable_key, SymmetricCryptoKey},
error::AccessTokenInvalidError,
util::BASE64_ENGINE,
};
use crate::{error::AccessTokenInvalidError, util::BASE64_ENGINE};

pub struct AccessToken {
pub service_account_id: Uuid,
Expand Down
2 changes: 1 addition & 1 deletion crates/bitwarden/src/client/client.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use bitwarden_crypto::symmetric_crypto_key::SymmetricCryptoKey;
use chrono::Utc;
use reqwest::header::{self};
use uuid::Uuid;
Expand All @@ -18,7 +19,6 @@ use crate::{
client_settings::{ClientSettings, DeviceType},
encryption_settings::EncryptionSettings,
},
crypto::SymmetricCryptoKey,
error::{Error, Result},
};

Expand Down
3 changes: 1 addition & 2 deletions crates/bitwarden/src/client/encryption_settings.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::collections::HashMap;

use bitwarden_crypto::symmetric_crypto_key::SymmetricCryptoKey;
use rsa::RsaPrivateKey;
use uuid::Uuid;
#[cfg(feature = "internal")]
Expand All @@ -12,8 +13,6 @@ use {
rsa::{pkcs8::DecodePrivateKey, Oaep},
};

use crate::crypto::SymmetricCryptoKey;

pub struct EncryptionSettings {
user_key: SymmetricCryptoKey,
private_key: Option<RsaPrivateKey>,
Expand Down
18 changes: 12 additions & 6 deletions crates/bitwarden/src/crypto/enc_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@ use std::{fmt::Display, str::FromStr};

use aes::cipher::{generic_array::GenericArray, typenum::U32};
use base64::Engine;
use bitwarden_crypto::symmetric_crypto_key::SymmetricCryptoKey;
use serde::{de::Visitor, Deserialize};

use super::{KeyDecryptable, KeyEncryptable, LocateKey};
use crate::{
crypto::{decrypt_aes256_hmac, SymmetricCryptoKey},
error::{CryptoError, EncStringParseError, Error, Result},
util::BASE64_ENGINE,
};

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

/// # Encrypted string primitive
///
/// [EncString] is a Bitwarden specific primitive that represents an encrypted string. They are
Expand Down Expand Up @@ -337,7 +336,7 @@ impl EncString {
mac_key: GenericArray<u8, U32>,
key: GenericArray<u8, U32>,
) -> Result<EncString> {
let (iv, mac, data) = super::encrypt_aes256_hmac(data_dec, mac_key, key)?;
let (iv, mac, data) = bitwarden_crypto::aes::encrypt_aes256_hmac(data_dec, mac_key, key)?;
Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data })
}

Expand Down Expand Up @@ -376,7 +375,13 @@ impl KeyDecryptable<Vec<u8>> for EncString {
match self {
EncString::AesCbc256_HmacSha256_B64 { iv, mac, data } => {
let mac_key = key.mac_key.ok_or(CryptoError::InvalidMac)?;
let dec = decrypt_aes256_hmac(iv, mac, data.clone(), mac_key, key.key)?;
let dec = bitwarden_crypto::aes::decrypt_aes256_hmac(
iv,
mac,
data.clone(),
mac_key,
key.key,
)?;
Ok(dec)
}
_ => Err(CryptoError::InvalidKey.into()),
Expand All @@ -399,9 +404,10 @@ impl KeyDecryptable<String> for EncString {

#[cfg(test)]
mod tests {
use crate::crypto::{KeyDecryptable, KeyEncryptable, SymmetricCryptoKey};
use bitwarden_crypto::symmetric_crypto_key::SymmetricCryptoKey;

use super::EncString;
use crate::crypto::{KeyDecryptable, KeyEncryptable};

#[test]
fn test_enc_string_roundtrip() {
Expand Down
4 changes: 2 additions & 2 deletions crates/bitwarden/src/crypto/encryptable.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use std::{collections::HashMap, hash::Hash};

use bitwarden_crypto::symmetric_crypto_key::SymmetricCryptoKey;
use uuid::Uuid;

use super::{KeyDecryptable, KeyEncryptable};
use crate::{
client::encryption_settings::EncryptionSettings,
error::{Error, Result},
};

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

pub trait LocateKey {
fn locate_key<'a>(
&self,
Expand Down
4 changes: 2 additions & 2 deletions crates/bitwarden/src/crypto/key_encryptable.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::{collections::HashMap, hash::Hash};

use crate::error::Result;
use bitwarden_crypto::symmetric_crypto_key::SymmetricCryptoKey;

use super::SymmetricCryptoKey;
use crate::error::Result;

pub trait KeyEncryptable<Output> {
fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result<Output>;
Expand Down
Loading

0 comments on commit 3c55a87

Please sign in to comment.