Skip to content

Commit

Permalink
Move more code to bitwarden-crypto
Browse files Browse the repository at this point in the history
  • Loading branch information
Hinton committed Dec 7, 2023
1 parent 3c55a87 commit b454601
Show file tree
Hide file tree
Showing 51 changed files with 270 additions and 257 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

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

7 changes: 7 additions & 0 deletions crates/bitwarden-crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ keywords = ["bitwarden"]
edition = "2021"
rust-version = "1.57"

[features]
default = []

mobile = ["uniffi"]

[dependencies]
aes = ">=0.8.2, <0.9"
argon2 = { version = ">=0.5.0, <0.6", features = [
Expand All @@ -36,11 +41,13 @@ 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"] }
serde_json = ">=1.0.96, <2.0"
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 }
uuid = { version = ">=1.3.3, <2.0", features = ["serde"] }

[dev-dependencies]
rand_chacha = "0.3.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ 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::{
error::{CryptoError, EncStringParseError, Error, Result},
util::BASE64_ENGINE,
error::EncStringParseError, CryptoError, KeyDecryptable, KeyEncryptable, LocateKey, Result,
SymmetricCryptoKey, BASE64_ENGINE,
};

/// # Encrypted string primitive
Expand Down Expand Up @@ -88,7 +86,7 @@ impl std::fmt::Debug for EncString {

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

fn from_str(s: &str) -> Result<Self, Self::Err> {
let (enc_type, parts): (&str, Vec<_>) = {
Expand Down Expand Up @@ -164,8 +162,7 @@ impl FromStr for EncString {
}

impl EncString {
#[cfg(feature = "mobile")]
pub(crate) fn from_buffer(buf: &[u8]) -> Result<Self> {
pub fn from_buffer(buf: &[u8]) -> Result<Self> {
if buf.is_empty() {
return Err(EncStringParseError::NoType.into());
}
Expand Down Expand Up @@ -232,8 +229,7 @@ impl EncString {
}
}

#[cfg(feature = "mobile")]
pub(crate) fn to_buffer(&self) -> Result<Vec<u8>> {
pub fn to_buffer(&self) -> Result<Vec<u8>> {
let mut buf;

match self {
Expand Down Expand Up @@ -331,12 +327,12 @@ impl serde::Serialize for EncString {
}

impl EncString {
pub(crate) fn encrypt_aes256_hmac(
pub fn encrypt_aes256_hmac(
data_dec: &[u8],
mac_key: GenericArray<u8, U32>,
key: GenericArray<u8, U32>,
) -> Result<EncString> {
let (iv, mac, data) = bitwarden_crypto::aes::encrypt_aes256_hmac(data_dec, mac_key, key)?;
let (iv, mac, data) = crate::aes::encrypt_aes256_hmac(data_dec, mac_key, key)?;
Ok(EncString::AesCbc256_HmacSha256_B64 { iv, mac, data })
}

Expand Down Expand Up @@ -375,16 +371,10 @@ 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 = bitwarden_crypto::aes::decrypt_aes256_hmac(
iv,
mac,
data.clone(),
mac_key,
key.key,
)?;
let dec = crate::aes::decrypt_aes256_hmac(iv, mac, data.clone(), mac_key, key.key)?;
Ok(dec)
}
_ => Err(CryptoError::InvalidKey.into()),
_ => Err(CryptoError::InvalidKey),
}
}
}
Expand All @@ -398,16 +388,26 @@ impl KeyEncryptable<EncString> for String {
impl KeyDecryptable<String> for EncString {
fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result<String> {
let dec: Vec<u8> = self.decrypt_with_key(key)?;
String::from_utf8(dec).map_err(|_| CryptoError::InvalidUtf8String.into())
String::from_utf8(dec).map_err(|_| CryptoError::InvalidUtf8String)
}
}

// Usually we wouldn't want to expose EncStrings in the API or the schemas,
// but we need them in the mobile API, so define it here to limit the scope
impl schemars::JsonSchema for EncString {
fn schema_name() -> String {
"EncString".to_string()
}

fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
gen.subschema_for::<String>()
}
}

#[cfg(test)]
mod tests {
use bitwarden_crypto::symmetric_crypto_key::SymmetricCryptoKey;

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

#[test]
fn test_enc_string_roundtrip() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
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 crate::{CryptoError, KeyDecryptable, KeyEncryptable, Result, SymmetricCryptoKey};

pub trait KeyContainer {
fn get_key(&self, org_id: &Option<Uuid>) -> Option<&SymmetricCryptoKey>;
}

pub trait LocateKey {
fn locate_key<'a>(
&self,
enc: &'a EncryptionSettings,
enc: &'a dyn KeyContainer,
org_id: &Option<Uuid>,
) -> Option<&'a SymmetricCryptoKey> {
enc.get_key(org_id)
Expand All @@ -21,48 +20,48 @@ 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>;
fn encrypt(self, enc: &dyn KeyContainer, 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>;
fn decrypt(&self, enc: &dyn KeyContainer, org_id: &Option<Uuid>) -> Result<Output>;
}

impl<T: KeyEncryptable<Output> + LocateKey, Output> Encryptable<Output> for T {
fn encrypt(self, enc: &EncryptionSettings, org_id: &Option<Uuid>) -> Result<Output> {
let key = self.locate_key(enc, org_id).ok_or(Error::VaultLocked)?;
fn encrypt(self, enc: &dyn KeyContainer, org_id: &Option<Uuid>) -> Result<Output> {
let key = self
.locate_key(enc, org_id)
.ok_or(CryptoError::MissingKey)?;
self.encrypt_with_key(key)
}
}

impl<T: KeyDecryptable<Output> + LocateKey, Output> Decryptable<Output> for T {
fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option<Uuid>) -> Result<Output> {
let key = self.locate_key(enc, org_id).ok_or(Error::VaultLocked)?;
fn decrypt(&self, enc: &dyn KeyContainer, org_id: &Option<Uuid>) -> Result<Output> {
let key = self
.locate_key(enc, org_id)
.ok_or(CryptoError::MissingKey)?;
self.decrypt_with_key(key)
}
}

impl<T: Encryptable<Output>, Output> Encryptable<Vec<Output>> for Vec<T> {
fn encrypt(self, enc: &EncryptionSettings, org_id: &Option<Uuid>) -> Result<Vec<Output>> {
fn encrypt(self, enc: &dyn KeyContainer, org_id: &Option<Uuid>) -> Result<Vec<Output>> {
self.into_iter().map(|e| e.encrypt(enc, org_id)).collect()
}
}

impl<T: Decryptable<Output>, Output> Decryptable<Vec<Output>> for Vec<T> {
fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option<Uuid>) -> Result<Vec<Output>> {
fn decrypt(&self, enc: &dyn KeyContainer, org_id: &Option<Uuid>) -> Result<Vec<Output>> {
self.iter().map(|e| e.decrypt(enc, org_id)).collect()
}
}

impl<T: Encryptable<Output>, Output, Id: Hash + Eq> Encryptable<HashMap<Id, Output>>
for HashMap<Id, T>
{
fn encrypt(
self,
enc: &EncryptionSettings,
org_id: &Option<Uuid>,
) -> Result<HashMap<Id, Output>> {
fn encrypt(self, enc: &dyn KeyContainer, org_id: &Option<Uuid>) -> Result<HashMap<Id, Output>> {
self.into_iter()
.map(|(id, e)| Ok((id, e.encrypt(enc, org_id)?)))
.collect()
Expand All @@ -74,7 +73,7 @@ impl<T: Decryptable<Output>, Output, Id: Hash + Eq + Copy> Decryptable<HashMap<I
{
fn decrypt(
&self,
enc: &EncryptionSettings,
enc: &dyn KeyContainer,
org_id: &Option<Uuid>,
) -> Result<HashMap<Id, Output>> {
self.iter()
Expand Down
28 changes: 28 additions & 0 deletions crates/bitwarden-crypto/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,34 @@ pub enum CryptoError {
NoKeyForOrg,
#[error("The value is not a valid UTF8 String")]
InvalidUtf8String,
#[error("Missing Key")]
MissingKey,

#[error("EncString error, {0}")]
EncString(#[from] EncStringParseError),

#[error("Rsa error, {0}")]
RsaError(#[from] RsaError),
}

#[derive(Debug, Error)]
pub enum EncStringParseError {
#[error("No type detected, missing '.' separator")]
NoType,
#[error("Invalid type, got {enc_type} with {parts} parts")]
InvalidType { enc_type: String, parts: usize },
#[error("Error decoding base64: {0}")]
InvalidBase64(#[from] base64::DecodeError),
#[error("Invalid length: expected {expected}, got {got}")]
InvalidLength { expected: usize, got: usize },
}

#[derive(Debug, Error)]
pub enum RsaError {
#[error("Unable to create public key")]
CreatePublicKey,
#[error("Unable to create private key")]
CreatePrivateKey,
}

pub type Result<T, E = CryptoError> = std::result::Result<T, E>;
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
use std::{collections::HashMap, hash::Hash};

use bitwarden_crypto::symmetric_crypto_key::SymmetricCryptoKey;

use crate::error::Result;
use crate::{error::Result, symmetric_crypto_key::SymmetricCryptoKey};

pub trait KeyEncryptable<Output> {
fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result<Output>;
Expand Down
24 changes: 19 additions & 5 deletions crates/bitwarden-crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,27 @@ use base64::{
};
use hmac::digest::OutputSizeUser;

#[cfg(feature = "mobile")]
uniffi::setup_scaffolding!();

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

pub use error::CryptoError;
use error::Result;
mod key_encryptable;
pub use key_encryptable::{KeyDecryptable, KeyEncryptable};
mod shareable_key;
mod symmetric_crypto_key;
pub use error::{CryptoError, Result};
pub use shareable_key::derive_shareable_key;
pub use symmetric_crypto_key::SymmetricCryptoKey;
mod encryptable;
pub use encryptable::{Decryptable, Encryptable, KeyContainer, LocateKey};
mod enc_string;
pub use enc_string::EncString;
pub mod rsa;
mod user_key;
pub use user_key::UserKey;
mod uniffi_support;
pub use uniffi_support::*;

// TODO: Move into a util crate
const BASE64_ENGINE_CONFIG: GeneralPurposeConfig = GeneralPurposeConfig::new()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use base64::Engine;
use bitwarden_crypto::symmetric_crypto_key::SymmetricCryptoKey;
use rsa::{
pkcs8::{EncodePrivateKey, EncodePublicKey},
RsaPrivateKey, RsaPublicKey,
};

use crate::{
crypto::EncString,
error::{Error, Result},
util::BASE64_ENGINE,
error::{Result, RsaError},
EncString, SymmetricCryptoKey, BASE64_ENGINE,
};

#[cfg_attr(feature = "mobile", derive(uniffi::Record))]
Expand All @@ -19,20 +17,20 @@ pub struct RsaKeyPair {
pub private: EncString,
}

pub(super) fn make_key_pair(key: &SymmetricCryptoKey) -> Result<RsaKeyPair> {
pub fn make_key_pair(key: &SymmetricCryptoKey) -> Result<RsaKeyPair> {
let mut rng = rand::thread_rng();
let bits = 2048;
let priv_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
let pub_key = RsaPublicKey::from(&priv_key);

let spki = pub_key
.to_public_key_der()
.map_err(|_| Error::Internal("unable to create public key"))?;
.map_err(|_| RsaError::CreatePublicKey)?;

let b64 = BASE64_ENGINE.encode(spki.as_bytes());
let pkcs = priv_key
.to_pkcs8_der()
.map_err(|_| Error::Internal("unable to create private key"))?;
.map_err(|_| RsaError::CreatePrivateKey)?;

let protected = EncString::encrypt_aes256_hmac(pkcs.as_bytes(), key.mac_key.unwrap(), key.key)?;

Expand Down
15 changes: 15 additions & 0 deletions crates/bitwarden-crypto/src/uniffi_support.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use crate::{CryptoError, EncString, UniffiCustomTypeConverter};

uniffi::custom_type!(EncString, String);

impl UniffiCustomTypeConverter for EncString {
type Builtin = String;

fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
val.parse().map_err(|e: CryptoError| e.into())
}

fn from_custom(obj: Self) -> Self::Builtin {
obj.to_string()
}
}
16 changes: 16 additions & 0 deletions crates/bitwarden-crypto/src/user_key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use crate::{
rsa::{make_key_pair, RsaKeyPair},
Result, SymmetricCryptoKey,
};

pub struct UserKey(pub SymmetricCryptoKey);

impl UserKey {
pub fn new(key: SymmetricCryptoKey) -> Self {
Self(key)
}

pub fn make_key_pair(&self) -> Result<RsaKeyPair> {
make_key_pair(&self.0)
}
}
Loading

0 comments on commit b454601

Please sign in to comment.