From d2cb4f9c388eb3fb13e3268258557eabfa1202fd Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 15 Jan 2024 16:13:12 +0100 Subject: [PATCH] [PM-5691] Extract crypto to separate crate (#402) We should make an effort to break up the large `bitwarden` crate into smaller more isolated crates. This extracts the cryptographic primitives into a crate, which better isolates them and may improve compile time slightly. We should continue to extract further areas. --- .github/workflows/build-rust-crates.yml | 1 + .github/workflows/publish-rust-crates.yml | 11 +++ .github/workflows/version-bump.yml | 11 ++- .gitignore | 3 +- Cargo.lock | 39 ++++++-- crates/bitwarden-crypto/Cargo.toml | 45 +++++++++ .../src/aes.rs} | 23 ++--- .../src}/enc_string/asymmetric.rs | 26 ++--- .../src}/enc_string/mod.rs | 2 +- .../src}/enc_string/symmetric.rs | 34 +++---- .../src}/encryptable.rs | 39 ++++---- crates/bitwarden-crypto/src/error.rs | 58 +++++++++++ .../src}/fingerprint.rs | 25 +++-- .../src/keys}/asymmetric_crypto_key.rs | 10 +- .../src/keys}/key_encryptable.rs | 0 .../src/keys}/master_key.rs | 56 +++++++---- crates/bitwarden-crypto/src/keys/mod.rs | 18 ++++ .../src/keys}/shareable_key.rs | 4 +- .../src/keys}/symmetric_crypto_key.rs | 23 ++--- crates/bitwarden-crypto/src/keys/user_key.rs | 19 ++++ crates/bitwarden-crypto/src/lib.rs | 48 +++++++++ .../crypto => bitwarden-crypto/src}/rsa.rs | 13 ++- crates/bitwarden-crypto/src/uniffi_support.rs | 31 ++++++ crates/bitwarden-crypto/src/util.rs | 67 +++++++++++++ .../src/wordlist.rs | 3 +- crates/bitwarden-crypto/uniffi.toml | 8 ++ crates/bitwarden-uniffi/Cargo.toml | 3 + crates/bitwarden-uniffi/src/auth/mod.rs | 7 +- crates/bitwarden-uniffi/src/docs.rs | 3 +- crates/bitwarden-uniffi/src/uniffi_support.rs | 2 +- crates/bitwarden/Cargo.toml | 23 ++--- crates/bitwarden/src/auth/client_auth.rs | 7 +- .../bitwarden/src/auth/login/access_token.rs | 2 +- crates/bitwarden/src/auth/login/api_key.rs | 2 +- crates/bitwarden/src/auth/login/mod.rs | 39 +++++++- crates/bitwarden/src/auth/login/password.rs | 7 +- crates/bitwarden/src/auth/login/two_factor.rs | 3 +- crates/bitwarden/src/auth/mod.rs | 10 +- .../bitwarden/src/auth/password/validate.rs | 5 +- crates/bitwarden/src/auth/register.rs | 9 +- crates/bitwarden/src/client/access_token.rs | 7 +- crates/bitwarden/src/client/client.rs | 18 ++-- .../src/client/encryption_settings.rs | 22 +++-- crates/bitwarden/src/client/kdf.rs | 60 ------------ crates/bitwarden/src/client/mod.rs | 1 - crates/bitwarden/src/crypto/mod.rs | 97 ------------------- crates/bitwarden/src/crypto/user_key.rs | 19 ---- crates/bitwarden/src/error.rs | 37 +------ crates/bitwarden/src/lib.rs | 2 - crates/bitwarden/src/mobile/client_kdf.rs | 6 +- crates/bitwarden/src/mobile/crypto.rs | 15 ++- crates/bitwarden/src/mobile/kdf.rs | 11 +-- .../src/mobile/vault/client_ciphers.rs | 3 +- .../src/mobile/vault/client_collection.rs | 3 +- .../src/mobile/vault/client_folders.rs | 3 +- .../mobile/vault/client_password_history.rs | 3 +- .../src/mobile/vault/client_sends.rs | 7 +- .../bitwarden/src/mobile/vault/client_totp.rs | 7 +- .../src/platform/generate_fingerprint.rs | 8 +- .../src/platform/get_user_api_key.rs | 2 +- .../src/secrets_manager/projects/create.rs | 2 +- .../projects/project_response.rs | 2 +- .../src/secrets_manager/projects/update.rs | 2 +- .../src/secrets_manager/secrets/create.rs | 2 +- .../src/secrets_manager/secrets/list.rs | 2 +- .../secrets/secret_response.rs | 2 +- .../src/secrets_manager/secrets/update.rs | 2 +- crates/bitwarden/src/secrets_manager/state.rs | 5 +- .../src/tool/generators/passphrase.rs | 4 +- .../bitwarden/src/tool/generators/username.rs | 7 +- crates/bitwarden/src/uniffi_support.rs | 36 +------ .../bitwarden/src/vault/cipher/attachment.rs | 12 +-- crates/bitwarden/src/vault/cipher/card.rs | 12 +-- crates/bitwarden/src/vault/cipher/cipher.rs | 20 ++-- crates/bitwarden/src/vault/cipher/field.rs | 12 +-- crates/bitwarden/src/vault/cipher/identity.rs | 12 +-- .../bitwarden/src/vault/cipher/local_data.rs | 10 +- crates/bitwarden/src/vault/cipher/login.rs | 16 +-- .../bitwarden/src/vault/cipher/secure_note.rs | 10 +- crates/bitwarden/src/vault/collection.rs | 13 ++- crates/bitwarden/src/vault/folder.rs | 12 +-- .../bitwarden/src/vault/password_history.rs | 15 +-- crates/bitwarden/src/vault/send.rs | 39 ++++---- crates/bitwarden/src/vault/totp.rs | 6 +- crates/bitwarden/tests/register.rs | 2 +- .../bitwarden/myapplication/MainActivity.kt | 2 +- languages/swift/build.sh | 8 +- 87 files changed, 737 insertions(+), 600 deletions(-) create mode 100644 crates/bitwarden-crypto/Cargo.toml rename crates/{bitwarden/src/crypto/aes_ops.rs => bitwarden-crypto/src/aes.rs} (88%) rename crates/{bitwarden/src/crypto => bitwarden-crypto/src}/enc_string/asymmetric.rs (95%) rename crates/{bitwarden/src/crypto => bitwarden-crypto/src}/enc_string/mod.rs (98%) rename crates/{bitwarden/src/crypto => bitwarden-crypto/src}/enc_string/symmetric.rs (90%) rename crates/{bitwarden/src/crypto => bitwarden-crypto/src}/encryptable.rs (58%) create mode 100644 crates/bitwarden-crypto/src/error.rs rename crates/{bitwarden/src/crypto => bitwarden-crypto/src}/fingerprint.rs (81%) rename crates/{bitwarden/src/crypto => bitwarden-crypto/src/keys}/asymmetric_crypto_key.rs (97%) rename crates/{bitwarden/src/crypto => bitwarden-crypto/src/keys}/key_encryptable.rs (100%) rename crates/{bitwarden/src/crypto => bitwarden-crypto/src/keys}/master_key.rs (85%) create mode 100644 crates/bitwarden-crypto/src/keys/mod.rs rename crates/{bitwarden/src/crypto => bitwarden-crypto/src/keys}/shareable_key.rs (94%) rename crates/{bitwarden/src/crypto => bitwarden-crypto/src/keys}/symmetric_crypto_key.rs (87%) create mode 100644 crates/bitwarden-crypto/src/keys/user_key.rs create mode 100644 crates/bitwarden-crypto/src/lib.rs rename crates/{bitwarden/src/crypto => bitwarden-crypto/src}/rsa.rs (75%) create mode 100644 crates/bitwarden-crypto/src/uniffi_support.rs create mode 100644 crates/bitwarden-crypto/src/util.rs rename crates/{bitwarden => bitwarden-crypto}/src/wordlist.rs (99%) create mode 100644 crates/bitwarden-crypto/uniffi.toml delete mode 100644 crates/bitwarden/src/client/kdf.rs delete mode 100644 crates/bitwarden/src/crypto/mod.rs delete mode 100644 crates/bitwarden/src/crypto/user_key.rs diff --git a/.github/workflows/build-rust-crates.yml b/.github/workflows/build-rust-crates.yml index b008540fb..9acc1b37d 100644 --- a/.github/workflows/build-rust-crates.yml +++ b/.github/workflows/build-rust-crates.yml @@ -29,6 +29,7 @@ jobs: package: - bitwarden + - bitwarden-crypto - bitwarden-api-api - bitwarden-api-identity diff --git a/.github/workflows/publish-rust-crates.yml b/.github/workflows/publish-rust-crates.yml index aef94b37e..231ad5626 100644 --- a/.github/workflows/publish-rust-crates.yml +++ b/.github/workflows/publish-rust-crates.yml @@ -29,6 +29,11 @@ on: required: true default: true type: boolean + publish_bitwarden-crypto: + description: "Publish bitwarden-crypto crate" + required: true + default: true + type: boolean defaults: run: @@ -61,6 +66,7 @@ jobs: PUBLISH_BITWARDEN: ${{ github.event.inputs.publish_bitwarden }} PUBLISH_BITWARDEN_API_API: ${{ github.event.inputs.publish_bitwarden-api-api }} PUBLISH_BITWARDEN_API_IDENTITY: ${{ github.event.inputs.publish_bitwarden-api-identity }} + PUBLISH_BITWARDEN_CRYPTO: ${{ github.event.inputs.publish_bitwarden-crypto }} run: | if [[ "$PUBLISH_BITWARDEN" == "false" ]] && [[ "$PUBLISH_BITWARDEN_API_API" == "false" ]] && [[ "$PUBLISH_BITWARDEN_API_IDENTITY" == "false" ]]; then echo "===================================" @@ -87,6 +93,11 @@ jobs: PACKAGES_LIST="$PACKAGES_LIST bitwarden-api-identity" fi + if [[ "$PUBLISH_BITWARDEN_CRYPTO" == "true" ]]; then + PACKAGES_COMMAND="$PACKAGES_COMMAND -p bitwarden-crypto" + PACKAGES_LIST="$PACKAGES_LIST bitwarden-crypto" + fi + echo "Packages command: " $PACKAGES_COMMAND echo "Packages list: " $PACKAGES_LIST diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index b27a6a989..f3c428c57 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -10,12 +10,13 @@ on: required: true type: choice options: - - napi - bitwarden - bitwarden-api-api - bitwarden-api-identity - - cli + - bitwarden-crypto - bitwarden-json + - cli + - napi version_number: description: "New version (example: '2024.1.0')" required: true @@ -116,6 +117,12 @@ jobs: if: ${{ inputs.project == 'bitwarden-api-identity' }} run: cargo-set-version set-version -p bitwarden-api-identity ${{ inputs.version_number }} + ### bitwarden-crypto + + - name: Bump bitwarden-crypto crate Version + if: ${{ inputs.project == 'bitwarden-crypto' }} + run: cargo-set-version set-version -p bitwarden-crypto ${{ inputs.version_number }} + ### cli - name: Bump cli Version diff --git a/.gitignore b/.gitignore index 23cbaba20..63c5875a3 100644 --- a/.gitignore +++ b/.gitignore @@ -50,8 +50,7 @@ languages/swift/BitwardenFFI.xcframework languages/swift/tmp languages/swift/.build languages/swift/.swiftpm -languages/kotlin/sdk/src/main/java/com/bitwarden/sdk/bitwarden_uniffi.kt -languages/kotlin/sdk/src/main/java/com/bitwarden/core/bitwarden.kt +languages/kotlin/sdk/src/main/java/com/bitwarden/**/*.kt # Schemas crates/bitwarden-napi/src-ts/bitwarden_client/schemas.ts diff --git a/Cargo.lock b/Cargo.lock index ad513bd84..31a4b4d05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -346,26 +346,18 @@ checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" name = "bitwarden" version = "0.4.0" dependencies = [ - "aes", - "argon2", "base64 0.21.5", "bitwarden-api-api", "bitwarden-api-identity", - "cbc", + "bitwarden-crypto", "chrono", "data-encoding", "getrandom 0.2.11", - "hkdf", "hmac", - "lazy_static", "log", - "num-bigint", - "num-traits", - "pbkdf2", "rand 0.8.5", "rand_chacha 0.3.1", "reqwest", - "rsa", "rustls-platform-verifier", "schemars", "serde", @@ -374,7 +366,6 @@ dependencies = [ "serde_repr", "sha1", "sha2", - "subtle", "thiserror", "tokio", "uniffi", @@ -428,6 +419,33 @@ dependencies = [ "supports-color", ] +[[package]] +name = "bitwarden-crypto" +version = "0.1.0" +dependencies = [ + "aes", + "argon2", + "base64 0.21.5", + "cbc", + "hkdf", + "hmac", + "num-bigint", + "num-traits", + "pbkdf2", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rsa", + "schemars", + "serde", + "serde_json", + "sha1", + "sha2", + "subtle", + "thiserror", + "uniffi", + "uuid", +] + [[package]] name = "bitwarden-json" version = "0.3.0" @@ -469,6 +487,7 @@ version = "0.1.0" dependencies = [ "async-lock", "bitwarden", + "bitwarden-crypto", "chrono", "env_logger", "schemars", diff --git a/crates/bitwarden-crypto/Cargo.toml b/crates/bitwarden-crypto/Cargo.toml new file mode 100644 index 000000000..4b7ac6cc4 --- /dev/null +++ b/crates/bitwarden-crypto/Cargo.toml @@ -0,0 +1,45 @@ +[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" + +[features] +default = [] + +mobile = ["uniffi"] + +[dependencies] +aes = ">=0.8.2, <0.9" +argon2 = { version = ">=0.5.0, <0.6", features = [ + "alloc", +], default-features = false } +base64 = ">=0.21.2, <0.22" +cbc = { version = ">=0.1.2, <0.2", features = ["alloc"] } +hkdf = ">=0.12.3, <0.13" +hmac = ">=0.12.1, <0.13" +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"] } +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 } +uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } + +[dev-dependencies] +rand_chacha = "0.3.1" +serde_json = ">=1.0.96, <2.0" diff --git a/crates/bitwarden/src/crypto/aes_ops.rs b/crates/bitwarden-crypto/src/aes.rs similarity index 88% rename from crates/bitwarden/src/crypto/aes_ops.rs rename to crates/bitwarden-crypto/src/aes.rs index 00e71a3fd..cb4bf3102 100644 --- a/crates/bitwarden/src/crypto/aes_ops.rs +++ b/crates/bitwarden-crypto/src/aes.rs @@ -2,11 +2,8 @@ //! //! 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. +//! In most cases you should use the [EncString][crate::EncString] with +//! [KeyEncryptable][crate::KeyEncryptable] & [KeyDecryptable][crate::KeyDecryptable] instead. use aes::cipher::{ block_padding::Pkcs7, generic_array::GenericArray, typenum::U32, BlockDecryptMut, @@ -16,14 +13,18 @@ use hmac::Mac; use subtle::ConstantTimeEq; use crate::{ - crypto::{PbkdfSha256Hmac, PBKDF_SHA256_HMAC_OUT_SIZE}, error::{CryptoError, Result}, + util::{PbkdfSha256Hmac, PBKDF_SHA256_HMAC_OUT_SIZE}, }; /// 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, key: GenericArray) -> Result> { +pub(crate) fn decrypt_aes256( + iv: &[u8; 16], + data: Vec, + key: GenericArray, +) -> Result> { // Decrypt data let iv = GenericArray::from_slice(iv); let mut data = data; @@ -41,7 +42,7 @@ pub fn decrypt_aes256(iv: &[u8; 16], data: Vec, key: GenericArray) /// Decrypt using AES-256 in CBC mode with MAC. /// /// Behaves similar to [decrypt_aes256], but also validates the MAC. -pub fn decrypt_aes256_hmac( +pub(crate) fn decrypt_aes256_hmac( iv: &[u8; 16], mac: &[u8; 32], data: Vec, @@ -50,7 +51,7 @@ pub fn decrypt_aes256_hmac( ) -> Result> { 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) } @@ -63,7 +64,7 @@ pub fn decrypt_aes256_hmac( /// /// A AesCbc256_B64 EncString #[allow(unused)] -pub fn encrypt_aes256(data_dec: &[u8], key: GenericArray) -> ([u8; 16], Vec) { +pub(crate) fn encrypt_aes256(data_dec: &[u8], key: GenericArray) -> ([u8; 16], Vec) { let rng = rand::thread_rng(); let (iv, data) = encrypt_aes256_internal(rng, data_dec, key); @@ -77,7 +78,7 @@ pub fn encrypt_aes256(data_dec: &[u8], key: GenericArray) -> ([u8; 16], /// ## Returns /// /// A AesCbc256_HmacSha256_B64 EncString -pub fn encrypt_aes256_hmac( +pub(crate) fn encrypt_aes256_hmac( data_dec: &[u8], mac_key: GenericArray, key: GenericArray, diff --git a/crates/bitwarden/src/crypto/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs similarity index 95% rename from crates/bitwarden/src/crypto/enc_string/asymmetric.rs rename to crates/bitwarden-crypto/src/enc_string/asymmetric.rs index a2bbee8cb..0c48ce14a 100644 --- a/crates/bitwarden/src/crypto/enc_string/asymmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs @@ -4,13 +4,12 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use rsa::Oaep; use serde::Deserialize; +use super::{from_b64_vec, split_enc_string}; use crate::{ - crypto::{AsymmetricCryptoKey, KeyDecryptable}, - error::{CryptoError, EncStringParseError, Error, Result}, + error::{CryptoError, EncStringParseError, Result}, + AsymmetricCryptoKey, KeyDecryptable, }; -use super::{from_b64_vec, split_enc_string}; - /// # Encrypted string primitive /// /// [AsymmEncString] is a Bitwarden specific primitive that represents an asymmetrically encrypted string. They are @@ -64,7 +63,7 @@ impl std::fmt::Debug for AsymmEncString { /// Deserializes an [AsymmEncString] from a string. impl FromStr for AsymmEncString { - type Err = Error; + type Err = CryptoError; fn from_str(s: &str) -> Result { let (enc_type, parts) = split_enc_string(s); @@ -153,7 +152,7 @@ impl AsymmEncString { impl KeyDecryptable> for AsymmEncString { fn decrypt_with_key(&self, key: &AsymmetricCryptoKey) -> Result> { use AsymmEncString::*; - Ok(match self { + match self { Rsa2048_OaepSha256_B64 { data } => key.key.decrypt(Oaep::new::(), data), Rsa2048_OaepSha1_B64 { data } => key.key.decrypt(Oaep::new::(), data), #[allow(deprecated)] @@ -165,14 +164,14 @@ impl KeyDecryptable> for AsymmEncString { key.key.decrypt(Oaep::new::(), data) } } - .map_err(|_| CryptoError::KeyDecrypt)?) + .map_err(|_| CryptoError::KeyDecrypt) } } impl KeyDecryptable for AsymmEncString { fn decrypt_with_key(&self, key: &AsymmetricCryptoKey) -> Result { let dec: Vec = self.decrypt_with_key(key)?; - String::from_utf8(dec).map_err(|_| CryptoError::InvalidUtf8String.into()) + String::from_utf8(dec).map_err(|_| CryptoError::InvalidUtf8String) } } @@ -190,7 +189,7 @@ impl schemars::JsonSchema for AsymmEncString { #[cfg(test)] mod tests { - use super::AsymmEncString; + use super::{AsymmEncString, AsymmetricCryptoKey, KeyDecryptable}; const RSA_PRIVATE_KEY: &str = "-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS @@ -221,11 +220,8 @@ WjyxP5ZvXu7U96jaJRI8PFMoE06WeVYcdIzrID2HvqH+w0UQJFrLJ/0Mn4stFAEz XKZBokBGnjFnTnKcs7nv/O8= -----END PRIVATE KEY-----"; - #[cfg(feature = "internal")] #[test] fn test_enc_string_rsa2048_oaep_sha256_b64() { - use crate::crypto::{AsymmetricCryptoKey, KeyDecryptable}; - let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); let enc_str: &str = "3.YFqzW9LL/uLjCnl0RRLtndzGJ1FV27mcwQwGjfJPOVrgCX9nJSUYCCDd0iTIyOZ/zRxG47b6L1Z3qgkEfcxjmrSBq60gijc3E2TBMAg7OCLVcjORZ+i1sOVOudmOPWro6uA8refMrg4lqbieDlbLMzjVEwxfi5WpcL876cD0vYyRwvLO3bzFrsE7x33HHHtZeOPW79RqMn5efsB5Dj9wVheC9Ix9AYDjbo+rjg9qR6guwKmS7k2MSaIQlrDR7yu8LP+ePtiSjx+gszJV5jQGfcx60dtiLQzLS/mUD+RmU7B950Bpx0H7x56lT5yXZbWK5YkoP6qd8B8D2aKbP68Ywg=="; let enc_string: AsymmEncString = enc_str.parse().unwrap(); @@ -236,11 +232,8 @@ XKZBokBGnjFnTnKcs7nv/O8= assert_eq!(res, "EncryptMe!"); } - #[cfg(feature = "internal")] #[test] fn test_enc_string_rsa2048_oaep_sha1_b64() { - use crate::crypto::{AsymmetricCryptoKey, KeyDecryptable}; - let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); let enc_str: &str = "4.ZheRb3PCfAunyFdQYPfyrFqpuvmln9H9w5nDjt88i5A7ug1XE0LJdQHCIYJl0YOZ1gCOGkhFu/CRY2StiLmT3iRKrrVBbC1+qRMjNNyDvRcFi91LWsmRXhONVSPjywzrJJXglsztDqGkLO93dKXNhuKpcmtBLsvgkphk/aFvxbaOvJ/FHdK/iV0dMGNhc/9tbys8laTdwBlI5xIChpRcrfH+XpSFM88+Bu03uK67N9G6eU1UmET+pISJwJvMuIDMqH+qkT7OOzgL3t6I0H2LDj+CnsumnQmDsvQzDiNfTR0IgjpoE9YH2LvPXVP2wVUkiTwXD9cG/E7XeoiduHyHjw=="; let enc_string: AsymmEncString = enc_str.parse().unwrap(); @@ -251,11 +244,8 @@ XKZBokBGnjFnTnKcs7nv/O8= assert_eq!(res, "EncryptMe!"); } - #[cfg(feature = "internal")] #[test] fn test_enc_string_rsa2048_oaep_sha1_hmac_sha256_b64() { - use crate::crypto::{AsymmetricCryptoKey, KeyDecryptable}; - let private_key = AsymmetricCryptoKey::from_pem(RSA_PRIVATE_KEY).unwrap(); let enc_str: &str = "6.ThnNc67nNr7GELyuhGGfsXNP2zJnNqhrIsjntEQ27r2qmn8vwdHbTbfO0cwt6YgSibDN0PjiCZ1O3Wb/IFq+vwvyRwFqF9145wBF8CQCbkhV+M0XvO99kh0daovtt120Nve/5ETI5PbPag9VdalKRQWZypJaqQHm5TAQVf4F5wtLlCLMBkzqTk+wkFe7BPMTGn07T+O3eJbTxXvyMZewQ7icJF0MZVA7VyWX9qElmZ89FCKowbf1BMr5pbcQ+0KdXcSVW3to43VkTp7k7COwsuH3M/i1AuVP5YN8ixjyRpvaeGqX/ap2nCHK2Wj5VxgCGT7XEls6ZknnAp9nB9qVjQ==|s3ntw5H/KKD/qsS0lUghTHl5Sm9j6m7YEdNHf0OeAFQ="; let enc_string: AsymmEncString = enc_str.parse().unwrap(); diff --git a/crates/bitwarden/src/crypto/enc_string/mod.rs b/crates/bitwarden-crypto/src/enc_string/mod.rs similarity index 98% rename from crates/bitwarden/src/crypto/enc_string/mod.rs rename to crates/bitwarden-crypto/src/enc_string/mod.rs index a998e6056..740e9589d 100644 --- a/crates/bitwarden/src/crypto/enc_string/mod.rs +++ b/crates/bitwarden-crypto/src/enc_string/mod.rs @@ -1,3 +1,4 @@ +/// Encrypted string types mod asymmetric; mod symmetric; @@ -9,7 +10,6 @@ pub use symmetric::EncString; use crate::error::{EncStringParseError, Result}; -#[cfg(feature = "mobile")] fn check_length(buf: &[u8], expected: usize) -> Result<()> { if buf.len() < expected { return Err(EncStringParseError::InvalidLength { diff --git a/crates/bitwarden/src/crypto/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs similarity index 90% rename from crates/bitwarden/src/crypto/enc_string/symmetric.rs rename to crates/bitwarden-crypto/src/enc_string/symmetric.rs index 801aa9c43..d6dd44374 100644 --- a/crates/bitwarden/src/crypto/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -4,12 +4,10 @@ use aes::cipher::{generic_array::GenericArray, typenum::U32}; use base64::{engine::general_purpose::STANDARD, Engine}; use serde::Deserialize; -#[cfg(feature = "mobile")] -use super::check_length; -use super::{from_b64, from_b64_vec, split_enc_string}; +use super::{check_length, from_b64, from_b64_vec, split_enc_string}; use crate::{ - crypto::{decrypt_aes256_hmac, KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey}, - error::{CryptoError, EncStringParseError, Error, Result}, + error::{CryptoError, EncStringParseError, Result}, + KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey, }; /// # Encrypted string primitive @@ -73,7 +71,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 { let (enc_type, parts) = split_enc_string(s); @@ -107,13 +105,11 @@ impl FromStr for EncString { impl EncString { /// Synthetic sugar for mapping `Option` to `Result>` - #[cfg(feature = "internal")] - pub(crate) fn try_from_optional(s: Option) -> Result, Error> { + pub fn try_from_optional(s: Option) -> Result, CryptoError> { s.map(|s| s.parse()).transpose() } - #[cfg(feature = "mobile")] - pub(crate) fn from_buffer(buf: &[u8]) -> Result { + pub fn from_buffer(buf: &[u8]) -> Result { if buf.is_empty() { return Err(EncStringParseError::NoType.into()); } @@ -147,8 +143,7 @@ impl EncString { } } - #[cfg(feature = "mobile")] - pub(crate) fn to_buffer(&self) -> Result> { + pub fn to_buffer(&self) -> Result> { let mut buf; match self { @@ -207,12 +202,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, key: GenericArray, ) -> Result { - let (iv, mac, data) = crate::crypto::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 }) } @@ -238,10 +233,10 @@ impl KeyDecryptable> 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 = crate::aes::decrypt_aes256_hmac(iv, mac, data.clone(), mac_key, key.key)?; Ok(dec) } - _ => Err(CryptoError::InvalidKey.into()), + _ => Err(CryptoError::InvalidKey), } } } @@ -255,7 +250,7 @@ impl KeyEncryptable for String { impl KeyDecryptable for EncString { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { let dec: Vec = self.decrypt_with_key(key)?; - String::from_utf8(dec).map_err(|_| CryptoError::InvalidUtf8String.into()) + String::from_utf8(dec).map_err(|_| CryptoError::InvalidUtf8String) } } @@ -274,9 +269,7 @@ impl schemars::JsonSchema for EncString { #[cfg(test)] mod tests { use super::EncString; - use crate::crypto::{ - symmetric_crypto_key::derive_symmetric_key, KeyDecryptable, KeyEncryptable, - }; + use crate::{derive_symmetric_key, KeyDecryptable, KeyEncryptable}; #[test] fn test_enc_string_roundtrip() { @@ -305,7 +298,6 @@ mod tests { assert_eq!(serde_json::to_string(&t).unwrap(), serialized); } - #[cfg(feature = "mobile")] #[test] fn test_enc_from_to_buffer() { let enc_str: &str = "2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8="; diff --git a/crates/bitwarden/src/crypto/encryptable.rs b/crates/bitwarden-crypto/src/encryptable.rs similarity index 58% rename from crates/bitwarden/src/crypto/encryptable.rs rename to crates/bitwarden-crypto/src/encryptable.rs index 8a8d24d7d..9f1256afa 100644 --- a/crates/bitwarden/src/crypto/encryptable.rs +++ b/crates/bitwarden-crypto/src/encryptable.rs @@ -2,17 +2,16 @@ use std::{collections::HashMap, hash::Hash}; use uuid::Uuid; -use crate::{ - client::encryption_settings::EncryptionSettings, - error::{Error, Result}, -}; +use crate::{CryptoError, KeyDecryptable, KeyEncryptable, Result, SymmetricCryptoKey}; -use super::{KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}; +pub trait KeyContainer { + fn get_key(&self, org_id: &Option) -> Option<&SymmetricCryptoKey>; +} pub trait LocateKey { fn locate_key<'a>( &self, - enc: &'a EncryptionSettings, + enc: &'a dyn KeyContainer, org_id: &Option, ) -> Option<&'a SymmetricCryptoKey> { enc.get_key(org_id) @@ -21,36 +20,40 @@ pub trait LocateKey { /// Deprecated: please use LocateKey and KeyDecryptable instead pub trait Encryptable { - fn encrypt(self, enc: &EncryptionSettings, org_id: &Option) -> Result; + fn encrypt(self, enc: &dyn KeyContainer, org_id: &Option) -> Result; } /// Deprecated: please use LocateKey and KeyDecryptable instead pub trait Decryptable { - fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result; + fn decrypt(&self, enc: &dyn KeyContainer, org_id: &Option) -> Result; } impl + LocateKey, Output> Encryptable for T { - fn encrypt(self, enc: &EncryptionSettings, org_id: &Option) -> Result { - let key = self.locate_key(enc, org_id).ok_or(Error::VaultLocked)?; + fn encrypt(self, enc: &dyn KeyContainer, org_id: &Option) -> Result { + let key = self + .locate_key(enc, org_id) + .ok_or(CryptoError::MissingKey)?; self.encrypt_with_key(key) } } impl + LocateKey, Output> Decryptable for T { - fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result { - let key = self.locate_key(enc, org_id).ok_or(Error::VaultLocked)?; + fn decrypt(&self, enc: &dyn KeyContainer, org_id: &Option) -> Result { + let key = self + .locate_key(enc, org_id) + .ok_or(CryptoError::MissingKey)?; self.decrypt_with_key(key) } } impl, Output> Encryptable> for Vec { - fn encrypt(self, enc: &EncryptionSettings, org_id: &Option) -> Result> { + fn encrypt(self, enc: &dyn KeyContainer, org_id: &Option) -> Result> { self.into_iter().map(|e| e.encrypt(enc, org_id)).collect() } } impl, Output> Decryptable> for Vec { - fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option) -> Result> { + fn decrypt(&self, enc: &dyn KeyContainer, org_id: &Option) -> Result> { self.iter().map(|e| e.decrypt(enc, org_id)).collect() } } @@ -58,11 +61,7 @@ impl, Output> Decryptable> for Vec { impl, Output, Id: Hash + Eq> Encryptable> for HashMap { - fn encrypt( - self, - enc: &EncryptionSettings, - org_id: &Option, - ) -> Result> { + fn encrypt(self, enc: &dyn KeyContainer, org_id: &Option) -> Result> { self.into_iter() .map(|(id, e)| Ok((id, e.encrypt(enc, org_id)?))) .collect() @@ -74,7 +73,7 @@ impl, Output, Id: Hash + Eq + Copy> Decryptable, ) -> Result> { self.iter() diff --git a/crates/bitwarden-crypto/src/error.rs b/crates/bitwarden-crypto/src/error.rs new file mode 100644 index 000000000..059cc88f1 --- /dev/null +++ b/crates/bitwarden-crypto/src/error.rs @@ -0,0 +1,58 @@ +use std::fmt::Debug; + +use thiserror::Error; + +use crate::fingerprint::FingerprintError; + +#[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("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), + + #[error("Fingerprint error, {0}")] + FingerprintError(#[from] FingerprintError), + + #[error("Number is zero")] + ZeroNumber, +} + +#[derive(Debug, Error)] +pub enum EncStringParseError { + #[error("No type detected, missing '.' separator")] + NoType, + #[error("Invalid symmetric type, got type {enc_type} with {parts} parts")] + InvalidTypeSymm { enc_type: String, parts: usize }, + #[error("Invalid asymmetric type, got type {enc_type} with {parts} parts")] + InvalidTypeAsymm { 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, +} + +/// Alias for `Result`. +pub(crate) type Result = std::result::Result; diff --git a/crates/bitwarden/src/crypto/fingerprint.rs b/crates/bitwarden-crypto/src/fingerprint.rs similarity index 81% rename from crates/bitwarden/src/crypto/fingerprint.rs rename to crates/bitwarden-crypto/src/fingerprint.rs index 707a07ca6..d3e69d577 100644 --- a/crates/bitwarden/src/crypto/fingerprint.rs +++ b/crates/bitwarden-crypto/src/fingerprint.rs @@ -1,27 +1,34 @@ +//! # Fingerprint +//! +//! Provides a way to derive fingerprints from fingerprint material and public keys. This is most +//! commonly used for account fingerprints, where the fingerprint material is the user's id and the +//! public key is the user's public key. + use num_bigint::BigUint; use num_traits::cast::ToPrimitive; use sha2::Digest; +use thiserror::Error; -use crate::{error::Result, wordlist::EFF_LONG_WORD_LIST}; +use crate::{error::Result, wordlist::EFF_LONG_WORD_LIST, CryptoError}; /// 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 { +pub fn fingerprint(fingerprint_material: &str, public_key: &[u8]) -> Result { let mut h = sha2::Sha256::new(); h.update(public_key); h.finalize(); let hkdf = - hkdf::Hkdf::::from_prk(public_key).map_err(|_| "hkdf::InvalidLength")?; + hkdf::Hkdf::::from_prk(public_key).map_err(|_| CryptoError::InvalidKeyLen)?; let mut user_fingerprint = [0u8; 32]; hkdf.expand(fingerprint_material.as_bytes(), &mut user_fingerprint) - .map_err(|_| "hkdf::expand")?; + .map_err(|_| CryptoError::InvalidKeyLen)?; - Ok(hash_word(user_fingerprint).unwrap()) + hash_word(user_fingerprint) } /// Derive a 5 word phrase from a 32 byte hash. @@ -34,7 +41,7 @@ fn hash_word(hash: [u8; 32]) -> Result { let hash_arr: Vec = hash.to_vec(); let entropy_available = hash_arr.len() * 4; if num_words as f64 * entropy_per_word > entropy_available as f64 { - return Err("Output entropy of hash function is too small".into()); + return Err(FingerprintError::EntropyTooSmall.into()); } let mut phrase = Vec::new(); @@ -50,6 +57,12 @@ fn hash_word(hash: [u8; 32]) -> Result { Ok(phrase.join("-")) } +#[derive(Debug, Error)] +pub enum FingerprintError { + #[error("Entropy is too small")] + EntropyTooSmall, +} + #[cfg(test)] mod tests { use super::fingerprint; diff --git a/crates/bitwarden/src/crypto/asymmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs similarity index 97% rename from crates/bitwarden/src/crypto/asymmetric_crypto_key.rs rename to crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs index 93098faa4..a3e06800b 100644 --- a/crates/bitwarden/src/crypto/asymmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs @@ -1,13 +1,11 @@ use rsa::RsaPrivateKey; -use crate::{ - crypto::CryptoKey, - error::{CryptoError, Result}, -}; +use super::key_encryptable::CryptoKey; +use crate::error::{CryptoError, Result}; -/// An asymmetric encryption key. Used to encrypt and decrypt [`EncString`](crate::crypto::EncString) +/// An asymmetric encryption key. Used to encrypt and decrypt [`EncString`](crate::EncString) pub struct AsymmetricCryptoKey { - pub(in crate::crypto) key: RsaPrivateKey, + pub(crate) key: RsaPrivateKey, } impl AsymmetricCryptoKey { diff --git a/crates/bitwarden/src/crypto/key_encryptable.rs b/crates/bitwarden-crypto/src/keys/key_encryptable.rs similarity index 100% rename from crates/bitwarden/src/crypto/key_encryptable.rs rename to crates/bitwarden-crypto/src/keys/key_encryptable.rs diff --git a/crates/bitwarden/src/crypto/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs similarity index 85% rename from crates/bitwarden/src/crypto/master_key.rs rename to crates/bitwarden-crypto/src/keys/master_key.rs index b22e11c6b..bdd626a1a 100644 --- a/crates/bitwarden/src/crypto/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -1,10 +1,29 @@ +use std::num::NonZeroU32; + use aes::cipher::{generic_array::GenericArray, typenum::U32}; use base64::{engine::general_purpose::STANDARD, Engine}; use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; use sha2::Digest; -use super::{hkdf_expand, EncString, KeyDecryptable, SymmetricCryptoKey, UserKey}; -use crate::{client::kdf::Kdf, error::Result}; +use crate::{ + util::{self, hkdf_expand}, + EncString, KeyDecryptable, Result, SymmetricCryptoKey, UserKey, +}; + +#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "mobile", derive(uniffi::Enum))] +pub enum Kdf { + PBKDF2 { + iterations: NonZeroU32, + }, + Argon2id { + iterations: NonZeroU32, + memory: NonZeroU32, + parallelism: NonZeroU32, + }, +} #[derive(Copy, Clone, JsonSchema)] #[cfg_attr(feature = "mobile", derive(uniffi::Enum))] @@ -13,8 +32,10 @@ pub enum HashPurpose { LocalAuthorization = 2, } -/// A Master Key. -pub(crate) struct MasterKey(SymmetricCryptoKey); +/// Master Key. +/// +/// Derived from the users master password, used to protect the [UserKey]. +pub struct MasterKey(SymmetricCryptoKey); impl MasterKey { /// Derives a users master key from their password, email and KDF. @@ -22,29 +43,27 @@ impl MasterKey { derive_key(password, email, kdf).map(Self) } - /// Derive the master key hash, used for server authorization. - pub(crate) fn derive_master_key_hash( - &self, - password: &[u8], - purpose: HashPurpose, - ) -> Result { - let hash = super::pbkdf2(&self.0.key, password, purpose as u32); + /// Derive the master key hash, used for local and remote password validation. + pub fn derive_master_key_hash(&self, password: &[u8], purpose: HashPurpose) -> Result { + let hash = util::pbkdf2(&self.0.key, password, purpose as u32); Ok(STANDARD.encode(hash)) } - pub(crate) fn make_user_key(&self) -> Result<(UserKey, EncString)> { + /// Generate a new random user key and encrypt it with the master key. + pub fn make_user_key(&self) -> Result<(UserKey, EncString)> { make_user_key(rand::thread_rng(), self) } - pub(crate) fn decrypt_user_key(&self, user_key: EncString) -> Result { + /// Decrypt the users user key + pub fn decrypt_user_key(&self, user_key: EncString) -> Result { let stretched_key = stretch_master_key(self)?; let dec: Vec = user_key.decrypt_with_key(&stretched_key)?; SymmetricCryptoKey::try_from(dec.as_slice()) } - pub(crate) fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result { + pub fn encrypt_user_key(&self, user_key: &SymmetricCryptoKey) -> Result { let stretched_key = stretch_master_key(self)?; EncString::encrypt_aes256_hmac( @@ -68,7 +87,7 @@ fn make_user_key( /// Derive a generic key from a secret and salt using the provided KDF. fn derive_key(secret: &[u8], salt: &[u8], kdf: &Kdf) -> Result { let hash = match kdf { - Kdf::PBKDF2 { iterations } => super::pbkdf2(secret, salt, iterations.get()), + Kdf::PBKDF2 { iterations } => crate::util::pbkdf2(secret, salt, iterations.get()), Kdf::Argon2id { iterations, @@ -117,11 +136,8 @@ mod tests { use rand::SeedableRng; - use super::{make_user_key, stretch_master_key, HashPurpose, MasterKey}; - use crate::{ - client::kdf::Kdf, - crypto::{symmetric_crypto_key::derive_symmetric_key, SymmetricCryptoKey}, - }; + use super::{make_user_key, stretch_master_key, HashPurpose, Kdf, MasterKey}; + use crate::{keys::symmetric_crypto_key::derive_symmetric_key, SymmetricCryptoKey}; #[test] fn test_master_key_derive_pbkdf2() { diff --git a/crates/bitwarden-crypto/src/keys/mod.rs b/crates/bitwarden-crypto/src/keys/mod.rs new file mode 100644 index 000000000..4f53456ef --- /dev/null +++ b/crates/bitwarden-crypto/src/keys/mod.rs @@ -0,0 +1,18 @@ +mod key_encryptable; +pub use key_encryptable::{KeyDecryptable, KeyEncryptable}; + +mod master_key; +pub use master_key::{HashPurpose, Kdf, MasterKey}; + +mod shareable_key; +pub use shareable_key::derive_shareable_key; + +mod symmetric_crypto_key; +#[cfg(test)] +pub use symmetric_crypto_key::derive_symmetric_key; +pub use symmetric_crypto_key::SymmetricCryptoKey; +mod asymmetric_crypto_key; +pub use asymmetric_crypto_key::AsymmetricCryptoKey; + +mod user_key; +pub use user_key::UserKey; diff --git a/crates/bitwarden/src/crypto/shareable_key.rs b/crates/bitwarden-crypto/src/keys/shareable_key.rs similarity index 94% rename from crates/bitwarden/src/crypto/shareable_key.rs rename to crates/bitwarden-crypto/src/keys/shareable_key.rs index 4127b0712..a13f17155 100644 --- a/crates/bitwarden/src/crypto/shareable_key.rs +++ b/crates/bitwarden-crypto/src/keys/shareable_key.rs @@ -1,13 +1,13 @@ use aes::cipher::{generic_array::GenericArray, typenum::U64}; use hmac::{Hmac, Mac}; -use crate::crypto::{hkdf_expand, SymmetricCryptoKey}; +use crate::{keys::SymmetricCryptoKey, util::hkdf_expand}; /// 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>, diff --git a/crates/bitwarden/src/crypto/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs similarity index 87% rename from crates/bitwarden/src/crypto/symmetric_crypto_key.rs rename to crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs index 6f93d47f6..31b00fd54 100644 --- a/crates/bitwarden/src/crypto/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs @@ -4,15 +4,13 @@ use aes::cipher::{generic_array::GenericArray, typenum::U32}; use base64::{engine::general_purpose::STANDARD, Engine}; use rand::Rng; -use crate::{ - crypto::CryptoKey, - error::{CryptoError, Error}, -}; +use super::key_encryptable::CryptoKey; +use crate::CryptoError; -/// A symmetric encryption key. Used to encrypt and decrypt [`EncString`](crate::crypto::EncString) +/// A symmetric encryption key. Used to encrypt and decrypt [`EncString`](crate::EncString) pub struct SymmetricCryptoKey { - pub(super) key: GenericArray, - pub(super) mac_key: Option>, + pub(crate) key: GenericArray, + pub(crate) mac_key: Option>, } impl SymmetricCryptoKey { @@ -44,8 +42,7 @@ impl SymmetricCryptoKey { STANDARD.encode(&buf) } - #[cfg(feature = "internal")] - pub(super) fn to_vec(&self) -> Vec { + pub fn to_vec(&self) -> Vec { let mut buf = Vec::new(); buf.extend_from_slice(&self.key); if let Some(mac) = self.mac_key { @@ -56,7 +53,7 @@ impl SymmetricCryptoKey { } impl FromStr for SymmetricCryptoKey { - type Err = Error; + type Err = CryptoError; fn from_str(s: &str) -> Result { let bytes = STANDARD.decode(s).map_err(|_| CryptoError::InvalidKey)?; @@ -65,7 +62,7 @@ impl FromStr for SymmetricCryptoKey { } impl TryFrom<&[u8]> for SymmetricCryptoKey { - type Error = Error; + type Error = CryptoError; fn try_from(value: &[u8]) -> Result { if value.len() == Self::KEY_LEN + Self::MAC_LEN { @@ -79,7 +76,7 @@ impl TryFrom<&[u8]> for SymmetricCryptoKey { mac_key: None, }) } else { - Err(CryptoError::InvalidKeyLen.into()) + Err(CryptoError::InvalidKeyLen) } } } @@ -95,7 +92,7 @@ impl std::fmt::Debug for SymmetricCryptoKey { #[cfg(test)] pub fn derive_symmetric_key(name: &str) -> SymmetricCryptoKey { - use crate::crypto::{derive_shareable_key, generate_random_bytes}; + use crate::{derive_shareable_key, generate_random_bytes}; let secret: [u8; 16] = generate_random_bytes(); derive_shareable_key(secret, name, None) diff --git a/crates/bitwarden-crypto/src/keys/user_key.rs b/crates/bitwarden-crypto/src/keys/user_key.rs new file mode 100644 index 000000000..6620236ec --- /dev/null +++ b/crates/bitwarden-crypto/src/keys/user_key.rs @@ -0,0 +1,19 @@ +use crate::{ + rsa::{make_key_pair, RsaKeyPair}, + Result, SymmetricCryptoKey, +}; + +/// User Key +/// +/// The User Key is the symmetric encryption key used to decrypt the user's vault. +pub struct UserKey(pub SymmetricCryptoKey); + +impl UserKey { + pub fn new(key: SymmetricCryptoKey) -> Self { + Self(key) + } + + pub fn make_key_pair(&self) -> Result { + make_key_pair(&self.0) + } +} diff --git a/crates/bitwarden-crypto/src/lib.rs b/crates/bitwarden-crypto/src/lib.rs new file mode 100644 index 000000000..a7399ce8e --- /dev/null +++ b/crates/bitwarden-crypto/src/lib.rs @@ -0,0 +1,48 @@ +//! # Bitwarden Cryptographic primitives +//! +//! This crate contains the cryptographic primitives used throughout the SDK. The crate makes a +//! best effort to abstract away cryptographic concepts into concepts such as [`EncString`], +//! [`AsymmEncString`] and [`SymmetricCryptoKey`]. +//! +//! ## Conventions: +//! +//! - Pure Functions that deterministically "derive" keys from input are prefixed with `derive_`. +//! - Functions that generate non deterministically keys are prefixed with `make_`. +//! +//! ## Differences from `clients` +//! +//! There are some noteworthy differences compared to the other Bitwarden +//! [clients](https://github.com/bitwarden/clients). These changes are made in an effort to +//! introduce conventions in how we name things, improve best practices and abstracting away +//! internal complexity. +//! +//! - `CryptoService.makeSendKey` & `AccessService.createAccessToken` are replaced by the generic +//! `derive_shareable_key` +//! - MasterKey operations such as `makeMasterKey` and `hashMasterKey` are moved to the MasterKey +//! struct. + +mod aes; +mod enc_string; +pub use enc_string::{AsymmEncString, EncString}; +mod encryptable; +pub use encryptable::{Decryptable, Encryptable, KeyContainer, LocateKey}; +mod error; +pub use error::CryptoError; +pub(crate) use error::Result; +mod fingerprint; +pub use fingerprint::fingerprint; +mod keys; +pub use keys::*; +mod rsa; +pub use crate::rsa::RsaKeyPair; +mod util; +pub use util::generate_random_bytes; +mod wordlist; +pub use util::pbkdf2; +pub use wordlist::EFF_LONG_WORD_LIST; + +#[cfg(feature = "mobile")] +uniffi::setup_scaffolding!(); + +#[cfg(feature = "mobile")] +mod uniffi_support; diff --git a/crates/bitwarden/src/crypto/rsa.rs b/crates/bitwarden-crypto/src/rsa.rs similarity index 75% rename from crates/bitwarden/src/crypto/rsa.rs rename to crates/bitwarden-crypto/src/rsa.rs index 9641237fc..ff665c247 100644 --- a/crates/bitwarden/src/crypto/rsa.rs +++ b/crates/bitwarden-crypto/src/rsa.rs @@ -5,10 +5,13 @@ use rsa::{ }; use crate::{ - crypto::{EncString, SymmetricCryptoKey}, - error::Result, + error::{Result, RsaError}, + EncString, SymmetricCryptoKey, }; +/// RSA Key Pair +/// +/// Consists of a public key and an encrypted private key. #[cfg_attr(feature = "mobile", derive(uniffi::Record))] pub struct RsaKeyPair { /// Base64 encoded DER representation of the public key @@ -17,7 +20,7 @@ pub struct RsaKeyPair { pub private: EncString, } -pub(super) fn make_key_pair(key: &SymmetricCryptoKey) -> Result { +pub(crate) fn make_key_pair(key: &SymmetricCryptoKey) -> Result { let mut rng = rand::thread_rng(); let bits = 2048; let priv_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); @@ -25,12 +28,12 @@ pub(super) fn make_key_pair(key: &SymmetricCryptoKey) -> Result { let spki = pub_key .to_public_key_der() - .map_err(|_| "unable to create public key")?; + .map_err(|_| RsaError::CreatePublicKey)?; let b64 = STANDARD.encode(spki.as_bytes()); let pkcs = priv_key .to_pkcs8_der() - .map_err(|_| "unable to create private key")?; + .map_err(|_| RsaError::CreatePrivateKey)?; let protected = EncString::encrypt_aes256_hmac(pkcs.as_bytes(), key.mac_key.unwrap(), key.key)?; diff --git a/crates/bitwarden-crypto/src/uniffi_support.rs b/crates/bitwarden-crypto/src/uniffi_support.rs new file mode 100644 index 000000000..0ff0194f9 --- /dev/null +++ b/crates/bitwarden-crypto/src/uniffi_support.rs @@ -0,0 +1,31 @@ +use std::num::NonZeroU32; + +use crate::{CryptoError, EncString, UniffiCustomTypeConverter}; + +uniffi::custom_type!(NonZeroU32, u32); + +impl UniffiCustomTypeConverter for NonZeroU32 { + type Builtin = u32; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + Self::new(val).ok_or(CryptoError::ZeroNumber.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.get() + } +} + +uniffi::custom_type!(EncString, String); + +impl UniffiCustomTypeConverter for EncString { + type Builtin = String; + + fn into_custom(val: Self::Builtin) -> uniffi::Result { + val.parse().map_err(|e: CryptoError| e.into()) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.to_string() + } +} diff --git a/crates/bitwarden-crypto/src/util.rs b/crates/bitwarden-crypto/src/util.rs new file mode 100644 index 000000000..261424e51 --- /dev/null +++ b/crates/bitwarden-crypto/src/util.rs @@ -0,0 +1,67 @@ +use ::aes::cipher::{generic_array::GenericArray, ArrayLength, Unsigned}; +use hmac::digest::OutputSizeUser; +use rand::{ + distributions::{Distribution, Standard}, + Rng, +}; + +use crate::{CryptoError, Result}; + +pub(crate) type PbkdfSha256Hmac = hmac::Hmac; +pub(crate) const PBKDF_SHA256_HMAC_OUT_SIZE: usize = + <::OutputSize as Unsigned>::USIZE; + +/// [RFC5869](https://datatracker.ietf.org/doc/html/rfc5869) HKDF-Expand operation +pub(crate) fn hkdf_expand>( + prk: &[u8], + info: Option<&str>, +) -> Result> { + let hkdf = hkdf::Hkdf::::from_prk(prk).map_err(|_| CryptoError::InvalidKeyLen)?; + let mut key = GenericArray::::default(); + + let i = info.map(|i| i.as_bytes()).unwrap_or(&[]); + hkdf.expand(i, &mut key) + .map_err(|_| CryptoError::InvalidKeyLen)?; + + Ok(key) +} + +/// Generate random bytes that are cryptographically secure +pub fn generate_random_bytes() -> T +where + Standard: Distribution, +{ + rand::thread_rng().gen() +} + +pub fn pbkdf2(password: &[u8], salt: &[u8], rounds: u32) -> [u8; PBKDF_SHA256_HMAC_OUT_SIZE] { + pbkdf2::pbkdf2_array::(password, salt, rounds) + .expect("hash is a valid fixed size") +} + +#[cfg(test)] +mod tests { + use aes::cipher::typenum::U64; + + use super::*; + + #[test] + fn test_hkdf_expand() { + let prk = &[ + 23, 152, 120, 41, 214, 16, 156, 133, 71, 226, 178, 135, 208, 255, 66, 101, 189, 70, + 173, 30, 39, 215, 175, 236, 38, 180, 180, 62, 196, 4, 159, 70, + ]; + let info = Some("info"); + + let result: GenericArray = hkdf_expand(prk, info).unwrap(); + + let expected_output: [u8; 64] = [ + 6, 114, 42, 38, 87, 231, 30, 109, 30, 255, 104, 129, 255, 94, 92, 108, 124, 145, 215, + 208, 17, 60, 135, 22, 70, 158, 40, 53, 45, 182, 8, 63, 65, 87, 239, 234, 185, 227, 153, + 122, 115, 205, 144, 56, 102, 149, 92, 139, 217, 102, 119, 57, 37, 57, 251, 178, 18, 52, + 94, 77, 132, 215, 239, 100, + ]; + + assert_eq!(result.as_slice(), expected_output); + } +} diff --git a/crates/bitwarden/src/wordlist.rs b/crates/bitwarden-crypto/src/wordlist.rs similarity index 99% rename from crates/bitwarden/src/wordlist.rs rename to crates/bitwarden-crypto/src/wordlist.rs index 4974969e6..4cc30ff74 100644 --- a/crates/bitwarden/src/wordlist.rs +++ b/crates/bitwarden-crypto/src/wordlist.rs @@ -1,6 +1,5 @@ // EFF's Long Wordlist from https://www.eff.org/dice -#[cfg(feature = "internal")] -pub(crate) const EFF_LONG_WORD_LIST: &[&str] = &[ +pub const EFF_LONG_WORD_LIST: &[&str] = &[ "abacus", "abdomen", "abdominal", diff --git a/crates/bitwarden-crypto/uniffi.toml b/crates/bitwarden-crypto/uniffi.toml new file mode 100644 index 000000000..f5826906b --- /dev/null +++ b/crates/bitwarden-crypto/uniffi.toml @@ -0,0 +1,8 @@ +[bindings.kotlin] +package_name = "com.bitwarden.crypto" +generate_immutable_records = true + +[bindings.swift] +ffi_module_name = "BitwardenCryptoFFI" +module_name = "BitwardenCrypto" +generate_immutable_records = true diff --git a/crates/bitwarden-uniffi/Cargo.toml b/crates/bitwarden-uniffi/Cargo.toml index 7fc87bb4b..eadaaf8d2 100644 --- a/crates/bitwarden-uniffi/Cargo.toml +++ b/crates/bitwarden-uniffi/Cargo.toml @@ -22,6 +22,9 @@ schemars = { version = ">=0.8, <0.9", optional = true } uniffi = "=0.25.2" bitwarden = { path = "../bitwarden", features = ["mobile", "internal"] } +bitwarden-crypto = { path = "../bitwarden-crypto", version = "=0.1.0", features = [ + "mobile", +] } [build-dependencies] uniffi = { version = "=0.25.2", features = ["build"] } diff --git a/crates/bitwarden-uniffi/src/auth/mod.rs b/crates/bitwarden-uniffi/src/auth/mod.rs index 1dc102b3e..f67aa3a02 100644 --- a/crates/bitwarden-uniffi/src/auth/mod.rs +++ b/crates/bitwarden-uniffi/src/auth/mod.rs @@ -1,10 +1,7 @@ use std::sync::Arc; -use bitwarden::{ - auth::{password::MasterPasswordPolicyOptions, RegisterKeyResponse}, - client::kdf::Kdf, - crypto::HashPurpose, -}; +use bitwarden::auth::{password::MasterPasswordPolicyOptions, RegisterKeyResponse}; +use bitwarden_crypto::{HashPurpose, Kdf}; use crate::{error::Result, Client}; diff --git a/crates/bitwarden-uniffi/src/docs.rs b/crates/bitwarden-uniffi/src/docs.rs index e7acbdcae..3ba0e061b 100644 --- a/crates/bitwarden-uniffi/src/docs.rs +++ b/crates/bitwarden-uniffi/src/docs.rs @@ -1,7 +1,5 @@ use bitwarden::{ auth::password::MasterPasswordPolicyOptions, - client::kdf::Kdf, - crypto::HashPurpose, mobile::crypto::{InitOrgCryptoRequest, InitUserCryptoRequest}, platform::FingerprintRequest, tool::{ExportFormat, PassphraseGeneratorRequest, PasswordGeneratorRequest}, @@ -10,6 +8,7 @@ use bitwarden::{ TotpResponse, }, }; +use bitwarden_crypto::{HashPurpose, Kdf}; use schemars::JsonSchema; #[derive(JsonSchema)] diff --git a/crates/bitwarden-uniffi/src/uniffi_support.rs b/crates/bitwarden-uniffi/src/uniffi_support.rs index 72a67f698..a91e3de5f 100644 --- a/crates/bitwarden-uniffi/src/uniffi_support.rs +++ b/crates/bitwarden-uniffi/src/uniffi_support.rs @@ -1,4 +1,4 @@ -use bitwarden::crypto::EncString; +use bitwarden_crypto::EncString; // Forward the type definitions to the main bitwarden crate type DateTime = chrono::DateTime; diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index 79910bca4..d69279881 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -15,21 +15,21 @@ rust-version = "1.57" [features] default = ["secrets"] -secrets = [] # Secrets manager API -internal = [] # Internal testing methods -mobile = ["uniffi", "internal"] # Mobile-specific features +secrets = [] # Secrets manager API +internal = [] # Internal testing methods +mobile = [ + "uniffi", + "internal", + "bitwarden-crypto/mobile", +] # Mobile-specific features wasm-bindgen = ["chrono/wasmbind"] [dependencies] -aes = ">=0.8.2, <0.9" -argon2 = { version = ">=0.5.0, <0.6", features = [ - "alloc", -], default-features = false } base64 = ">=0.21.2, <0.22" bitwarden-api-api = { path = "../bitwarden-api-api", version = "=0.2.3" } bitwarden-api-identity = { path = "../bitwarden-api-identity", version = "=0.2.3" } -cbc = { version = ">=0.1.2, <0.2", features = ["alloc"] } +bitwarden-crypto = { path = "../bitwarden-crypto", version = "=0.1.0" } chrono = { version = ">=0.4.26, <0.5", features = [ "clock", "serde", @@ -38,18 +38,12 @@ chrono = { version = ">=0.4.26, <0.5", features = [ data-encoding = ">=2.5.0, <3.0" # We don't use this directly (it's used by rand), but we need it here to enable WASM support getrandom = { version = ">=0.2.9, <0.3", features = ["js"] } -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" reqwest = { version = ">=0.11, <0.12", features = [ "json", ], default-features = false } -rsa = ">=0.9.2, <0.10" schemars = { version = ">=0.8.9, <0.9", features = ["uuid1", "chrono"] } serde = { version = ">=1.0, <2.0", features = ["derive"] } serde_json = ">=1.0.96, <2.0" @@ -57,7 +51,6 @@ serde_qs = ">=0.12.0, <0.13" serde_repr = ">=0.1.12, <0.2" 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, features = ["tokio"] } uuid = { version = ">=1.3.3, <2.0", features = ["serde"] } diff --git a/crates/bitwarden/src/auth/client_auth.rs b/crates/bitwarden/src/auth/client_auth.rs index 6c9ddbd4d..d4379d1eb 100644 --- a/crates/bitwarden/src/auth/client_auth.rs +++ b/crates/bitwarden/src/auth/client_auth.rs @@ -15,7 +15,7 @@ use crate::{ register::{make_register_keys, register}, RegisterKeyResponse, RegisterRequest, }, - client::kdf::Kdf, + client::Kdf, }; pub struct ClientAuth<'a> { @@ -70,9 +70,10 @@ impl<'a> ClientAuth<'a> { } pub async fn prelogin(&mut self, email: String) -> Result { - use crate::auth::login::request_prelogin; + use crate::auth::login::{parse_prelogin, request_prelogin}; - request_prelogin(self.client, email).await?.try_into() + let response = request_prelogin(self.client, email).await?; + parse_prelogin(response) } pub async fn login_password( diff --git a/crates/bitwarden/src/auth/login/access_token.rs b/crates/bitwarden/src/auth/login/access_token.rs index 43418d5f1..e4cce33e4 100644 --- a/crates/bitwarden/src/auth/login/access_token.rs +++ b/crates/bitwarden/src/auth/login/access_token.rs @@ -1,6 +1,7 @@ use std::path::{Path, PathBuf}; use base64::{engine::general_purpose::STANDARD, Engine}; +use bitwarden_crypto::{EncString, KeyDecryptable, SymmetricCryptoKey}; use chrono::Utc; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -13,7 +14,6 @@ use crate::{ JWTToken, }, client::{AccessToken, LoginMethod, ServiceAccountLoginMethod}, - crypto::{EncString, KeyDecryptable, SymmetricCryptoKey}, error::{Error, Result}, secrets_manager::state::{self, ClientState}, Client, diff --git a/crates/bitwarden/src/auth/login/api_key.rs b/crates/bitwarden/src/auth/login/api_key.rs index 9370559b4..e161ececd 100644 --- a/crates/bitwarden/src/auth/login/api_key.rs +++ b/crates/bitwarden/src/auth/login/api_key.rs @@ -1,3 +1,4 @@ +use bitwarden_crypto::EncString; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -8,7 +9,6 @@ use crate::{ JWTToken, }, client::{LoginMethod, UserLoginMethod}, - crypto::EncString, error::Result, Client, }; diff --git a/crates/bitwarden/src/auth/login/mod.rs b/crates/bitwarden/src/auth/login/mod.rs index d2f66b569..afd7873a4 100644 --- a/crates/bitwarden/src/auth/login/mod.rs +++ b/crates/bitwarden/src/auth/login/mod.rs @@ -1,6 +1,6 @@ #[cfg(feature = "internal")] use { - crate::{client::Client, error::Result}, + crate::{client::Kdf, error::Result, Client}, bitwarden_api_identity::{ apis::accounts_api::accounts_prelogin_post, models::{PreloginRequestModel, PreloginResponseModel}, @@ -45,3 +45,40 @@ pub(crate) async fn request_prelogin( let config = client.get_api_configurations().await; Ok(accounts_prelogin_post(&config.identity, Some(request_model)).await?) } + +#[cfg(feature = "internal")] +pub(crate) fn parse_prelogin(response: PreloginResponseModel) -> Result { + use std::num::NonZeroU32; + + use bitwarden_api_identity::models::KdfType; + + use crate::util::{ + default_argon2_iterations, default_argon2_memory, default_argon2_parallelism, + default_pbkdf2_iterations, + }; + + let kdf = response.kdf.ok_or("KDF not found")?; + + Ok(match kdf { + KdfType::Variant0 => Kdf::PBKDF2 { + iterations: response + .kdf_iterations + .and_then(|e| NonZeroU32::new(e as u32)) + .unwrap_or_else(default_pbkdf2_iterations), + }, + KdfType::Variant1 => Kdf::Argon2id { + iterations: response + .kdf_iterations + .and_then(|e| NonZeroU32::new(e as u32)) + .unwrap_or_else(default_argon2_iterations), + memory: response + .kdf_memory + .and_then(|e| NonZeroU32::new(e as u32)) + .unwrap_or_else(default_argon2_memory), + parallelism: response + .kdf_parallelism + .and_then(|e| NonZeroU32::new(e as u32)) + .unwrap_or_else(default_argon2_parallelism), + }, + }) +} diff --git a/crates/bitwarden/src/auth/login/password.rs b/crates/bitwarden/src/auth/login/password.rs index 7a93c68e0..e5d579aa2 100644 --- a/crates/bitwarden/src/auth/login/password.rs +++ b/crates/bitwarden/src/auth/login/password.rs @@ -6,8 +6,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "internal")] use crate::{ auth::{api::request::PasswordTokenRequest, login::TwoFactorRequest}, - client::{kdf::Kdf, LoginMethod}, - crypto::EncString, + client::{Kdf, LoginMethod}, Client, }; use crate::{ @@ -23,7 +22,9 @@ pub(crate) async fn login_password( client: &mut Client, input: &PasswordLoginRequest, ) -> Result { - use crate::{auth::determine_password_hash, client::UserLoginMethod, crypto::HashPurpose}; + use bitwarden_crypto::{EncString, HashPurpose}; + + use crate::{auth::determine_password_hash, client::UserLoginMethod}; info!("password logging in"); debug!("{:#?}, {:#?}", client, input); diff --git a/crates/bitwarden/src/auth/login/two_factor.rs b/crates/bitwarden/src/auth/login/two_factor.rs index cf3cb7907..45be042c7 100644 --- a/crates/bitwarden/src/auth/login/two_factor.rs +++ b/crates/bitwarden/src/auth/login/two_factor.rs @@ -1,9 +1,10 @@ use bitwarden_api_api::models::TwoFactorEmailRequestModel; +use bitwarden_crypto::HashPurpose; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; -use crate::{auth::determine_password_hash, crypto::HashPurpose, error::Result, Client}; +use crate::{auth::determine_password_hash, error::Result, Client}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] diff --git a/crates/bitwarden/src/auth/mod.rs b/crates/bitwarden/src/auth/mod.rs index 49be82d1c..e80ab28cf 100644 --- a/crates/bitwarden/src/auth/mod.rs +++ b/crates/bitwarden/src/auth/mod.rs @@ -10,14 +10,12 @@ pub use jwt_token::JWTToken; #[cfg(feature = "internal")] mod register; #[cfg(feature = "internal")] +use bitwarden_crypto::{HashPurpose, MasterKey}; +#[cfg(feature = "internal")] pub use register::{RegisterKeyResponse, RegisterRequest}; #[cfg(feature = "internal")] -use crate::{ - client::kdf::Kdf, - crypto::{HashPurpose, MasterKey}, - error::Result, -}; +use crate::{client::Kdf, error::Result}; #[cfg(feature = "internal")] async fn determine_password_hash( @@ -27,7 +25,7 @@ async fn determine_password_hash( purpose: HashPurpose, ) -> Result { let master_key = MasterKey::derive(password.as_bytes(), email.as_bytes(), kdf)?; - master_key.derive_master_key_hash(password.as_bytes(), purpose) + Ok(master_key.derive_master_key_hash(password.as_bytes(), purpose)?) } #[cfg(test)] diff --git a/crates/bitwarden/src/auth/password/validate.rs b/crates/bitwarden/src/auth/password/validate.rs index c86c1c40d..f6d22e11a 100644 --- a/crates/bitwarden/src/auth/password/validate.rs +++ b/crates/bitwarden/src/auth/password/validate.rs @@ -1,7 +1,8 @@ +use bitwarden_crypto::HashPurpose; + use crate::{ auth::determine_password_hash, client::{LoginMethod, UserLoginMethod}, - crypto::HashPurpose, error::{Error, Result}, Client, }; @@ -40,7 +41,7 @@ mod tests { use std::num::NonZeroU32; use super::validate_password; - use crate::client::{kdf::Kdf, Client, LoginMethod, UserLoginMethod}; + use crate::client::{Client, Kdf, LoginMethod, UserLoginMethod}; let mut client = Client::new(None); client.set_login_method(LoginMethod::User(UserLoginMethod::Username { diff --git a/crates/bitwarden/src/auth/register.rs b/crates/bitwarden/src/auth/register.rs index 206c1905b..2b0c9503b 100644 --- a/crates/bitwarden/src/auth/register.rs +++ b/crates/bitwarden/src/auth/register.rs @@ -2,16 +2,11 @@ use bitwarden_api_identity::{ apis::accounts_api::accounts_register_post, models::{KeysRequestModel, RegisterRequestModel}, }; +use bitwarden_crypto::{HashPurpose, MasterKey, RsaKeyPair}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{ - client::kdf::Kdf, - crypto::{HashPurpose, MasterKey, RsaKeyPair}, - error::Result, - util::default_pbkdf2_iterations, - Client, -}; +use crate::{client::Kdf, error::Result, util::default_pbkdf2_iterations, Client}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] diff --git a/crates/bitwarden/src/client/access_token.rs b/crates/bitwarden/src/client/access_token.rs index 854922c14..4a6d5ed8c 100644 --- a/crates/bitwarden/src/client/access_token.rs +++ b/crates/bitwarden/src/client/access_token.rs @@ -1,13 +1,10 @@ use std::{fmt::Debug, str::FromStr}; use base64::Engine; +use bitwarden_crypto::{derive_shareable_key, SymmetricCryptoKey}; use uuid::Uuid; -use crate::{ - crypto::{derive_shareable_key, SymmetricCryptoKey}, - error::AccessTokenInvalidError, - util::STANDARD_INDIFFERENT, -}; +use crate::{error::AccessTokenInvalidError, util::STANDARD_INDIFFERENT}; pub struct AccessToken { pub access_token_id: Uuid, diff --git a/crates/bitwarden/src/client/client.rs b/crates/bitwarden/src/client/client.rs index 2c5d9b387..34d99c0a3 100644 --- a/crates/bitwarden/src/client/client.rs +++ b/crates/bitwarden/src/client/client.rs @@ -1,5 +1,10 @@ use std::path::PathBuf; +#[cfg(feature = "internal")] +pub use bitwarden_crypto::Kdf; +use bitwarden_crypto::SymmetricCryptoKey; +#[cfg(feature = "internal")] +use bitwarden_crypto::{AsymmEncString, EncString}; use chrono::Utc; use reqwest::header::{self}; use uuid::Uuid; @@ -8,20 +13,15 @@ use super::AccessToken; #[cfg(feature = "secrets")] use crate::auth::login::{AccessTokenLoginRequest, AccessTokenLoginResponse}; #[cfg(feature = "internal")] -use crate::{ - client::kdf::Kdf, - crypto::{AsymmEncString, EncString}, - platform::{ - get_user_api_key, sync, SecretVerificationRequest, SyncRequest, SyncResponse, - UserApiKeyResponse, - }, +use crate::platform::{ + get_user_api_key, sync, SecretVerificationRequest, SyncRequest, SyncResponse, + UserApiKeyResponse, }; use crate::{ client::{ client_settings::{ClientSettings, DeviceType}, encryption_settings::EncryptionSettings, }, - crypto::SymmetricCryptoKey, error::{Error, Result}, }; @@ -255,7 +255,7 @@ impl Client { pin_protected_user_key: EncString, private_key: EncString, ) -> Result<&EncryptionSettings> { - use crate::crypto::MasterKey; + use bitwarden_crypto::MasterKey; let pin_key = match &self.login_method { Some(LoginMethod::User( diff --git a/crates/bitwarden/src/client/encryption_settings.rs b/crates/bitwarden/src/client/encryption_settings.rs index 1a662adfb..612449cbd 100644 --- a/crates/bitwarden/src/client/encryption_settings.rs +++ b/crates/bitwarden/src/client/encryption_settings.rs @@ -1,14 +1,12 @@ use std::collections::HashMap; #[cfg(feature = "internal")] -use crate::{ - client::UserLoginMethod, - crypto::{AsymmEncString, EncString, KeyDecryptable}, - error::Result, -}; +use bitwarden_crypto::{AsymmEncString, EncString}; +use bitwarden_crypto::{AsymmetricCryptoKey, KeyContainer, SymmetricCryptoKey}; use uuid::Uuid; -use crate::crypto::{AsymmetricCryptoKey, SymmetricCryptoKey}; +#[cfg(feature = "internal")] +use crate::{client::UserLoginMethod, error::Result}; pub struct EncryptionSettings { user_key: SymmetricCryptoKey, @@ -31,7 +29,7 @@ impl EncryptionSettings { user_key: EncString, private_key: EncString, ) -> Result { - use crate::crypto::MasterKey; + use bitwarden_crypto::MasterKey; match login_method { UserLoginMethod::Username { email, kdf, .. } @@ -55,6 +53,8 @@ impl EncryptionSettings { user_key: SymmetricCryptoKey, private_key: EncString, ) -> Result { + use bitwarden_crypto::KeyDecryptable; + let private_key = { let dec: Vec = private_key.decrypt_with_key(&user_key)?; Some(AsymmetricCryptoKey::from_der(&dec)?) @@ -82,6 +82,8 @@ impl EncryptionSettings { &mut self, org_enc_keys: Vec<(Uuid, AsymmEncString)>, ) -> Result<&mut Self> { + use bitwarden_crypto::KeyDecryptable; + use crate::error::Error; let private_key = self.private_key.as_ref().ok_or(Error::VaultLocked)?; @@ -114,3 +116,9 @@ impl EncryptionSettings { } } } + +impl KeyContainer for EncryptionSettings { + fn get_key(&self, org_id: &Option) -> Option<&SymmetricCryptoKey> { + EncryptionSettings::get_key(self, org_id) + } +} diff --git a/crates/bitwarden/src/client/kdf.rs b/crates/bitwarden/src/client/kdf.rs deleted file mode 100644 index 2f6787d8d..000000000 --- a/crates/bitwarden/src/client/kdf.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::num::NonZeroU32; - -#[cfg(feature = "internal")] -use bitwarden_api_identity::models::{KdfType, PreloginResponseModel}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -#[cfg(feature = "internal")] -use crate::error::{Error, Result}; - -#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -#[cfg_attr(feature = "mobile", derive(uniffi::Enum))] -pub enum Kdf { - PBKDF2 { - iterations: NonZeroU32, - }, - Argon2id { - iterations: NonZeroU32, - memory: NonZeroU32, - parallelism: NonZeroU32, - }, -} - -#[cfg(feature = "internal")] -impl TryFrom for Kdf { - type Error = Error; - - fn try_from(response: PreloginResponseModel) -> Result { - use crate::util::{ - default_argon2_iterations, default_argon2_memory, default_argon2_parallelism, - default_pbkdf2_iterations, - }; - - let kdf = response.kdf.ok_or("KDF not found")?; - - Ok(match kdf { - KdfType::Variant0 => Kdf::PBKDF2 { - iterations: response - .kdf_iterations - .and_then(|e| NonZeroU32::new(e as u32)) - .unwrap_or_else(default_pbkdf2_iterations), - }, - KdfType::Variant1 => Kdf::Argon2id { - iterations: response - .kdf_iterations - .and_then(|e| NonZeroU32::new(e as u32)) - .unwrap_or_else(default_argon2_iterations), - memory: response - .kdf_memory - .and_then(|e| NonZeroU32::new(e as u32)) - .unwrap_or_else(default_argon2_memory), - parallelism: response - .kdf_parallelism - .and_then(|e| NonZeroU32::new(e as u32)) - .unwrap_or_else(default_argon2_parallelism), - }, - }) - } -} diff --git a/crates/bitwarden/src/client/mod.rs b/crates/bitwarden/src/client/mod.rs index 25a2f5db0..c3719fce2 100644 --- a/crates/bitwarden/src/client/mod.rs +++ b/crates/bitwarden/src/client/mod.rs @@ -6,7 +6,6 @@ pub(crate) mod access_token; mod client; pub mod client_settings; pub(crate) mod encryption_settings; -pub mod kdf; pub use access_token::AccessToken; pub use client::Client; diff --git a/crates/bitwarden/src/crypto/mod.rs b/crates/bitwarden/src/crypto/mod.rs deleted file mode 100644 index f21fdb256..000000000 --- a/crates/bitwarden/src/crypto/mod.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! # Cryptographic primitives -//! -//! This module contains the cryptographic primitives used throughout the SDK. The module makes a -//! best effort to abstract away cryptographic concepts into concepts such as -//! [`EncString`], [`SymmetricCryptoKey`] and [`AsymmetricCryptoKey`]. -//! -//! ## Conventions: -//! -//! - Pure Functions that deterministically "derive" keys from input are prefixed with `derive_`. -//! - Functions that generate new keys are prefixed with `make_`. -//! -//! ## Differences from [`clients`](https://github.com/bitwarden/clients) -//! -//! There are some noteworthy differences compared to the other Bitwarden clients. These changes -//! are made in an effort to introduce conventions in how we name things, improve best practices -//! and abstracting away internal complexity. -//! -//! - `CryptoService.makeSendKey` & `AccessService.createAccessToken` are replaced by the generic -//! `derive_shareable_key` -//! - MasterKey operations such as `makeMasterKey` and `hashMasterKey` are moved to the MasterKey -//! struct. -//! - -use aes::cipher::{generic_array::GenericArray, ArrayLength, Unsigned}; -use hmac::digest::OutputSizeUser; -#[cfg(any(test, feature = "internal"))] -use rand::{ - distributions::{Distribution, Standard}, - Rng, -}; - -use crate::error::Result; - -mod enc_string; -pub use enc_string::{AsymmEncString, EncString}; -mod encryptable; -pub use encryptable::{Decryptable, Encryptable, LocateKey}; -mod key_encryptable; -pub use key_encryptable::{CryptoKey, KeyDecryptable, KeyEncryptable}; -mod aes_ops; -use aes_ops::{decrypt_aes256_hmac, encrypt_aes256_hmac}; -mod symmetric_crypto_key; -pub use symmetric_crypto_key::SymmetricCryptoKey; -mod asymmetric_crypto_key; -pub use asymmetric_crypto_key::AsymmetricCryptoKey; - -mod shareable_key; -pub(crate) use shareable_key::derive_shareable_key; - -#[cfg(feature = "internal")] -mod master_key; -#[cfg(feature = "internal")] -pub use master_key::HashPurpose; -#[cfg(feature = "internal")] -pub(crate) use master_key::MasterKey; -#[cfg(feature = "internal")] -mod user_key; -#[cfg(feature = "internal")] -pub(crate) use user_key::UserKey; -#[cfg(feature = "internal")] -mod rsa; -#[cfg(feature = "internal")] -pub use self::rsa::RsaKeyPair; - -#[cfg(feature = "internal")] -mod fingerprint; -#[cfg(feature = "internal")] -pub(crate) use fingerprint::fingerprint; - -pub(crate) type PbkdfSha256Hmac = hmac::Hmac; -pub(crate) const PBKDF_SHA256_HMAC_OUT_SIZE: usize = - <::OutputSize as Unsigned>::USIZE; - -/// RFC5869 HKDF-Expand operation -fn hkdf_expand>(prk: &[u8], info: Option<&str>) -> Result> { - let hkdf = hkdf::Hkdf::::from_prk(prk).map_err(|_| "invalid prk length")?; - let mut key = GenericArray::::default(); - - let i = info.map(|i| i.as_bytes()).unwrap_or(&[]); - hkdf.expand(i, &mut key).map_err(|_| "invalid length")?; - - Ok(key) -} - -/// Generate random bytes that are cryptographically secure -#[cfg(any(test, feature = "internal"))] -pub(crate) fn generate_random_bytes() -> T -where - Standard: Distribution, -{ - rand::thread_rng().gen() -} - -pub fn pbkdf2(password: &[u8], salt: &[u8], rounds: u32) -> [u8; PBKDF_SHA256_HMAC_OUT_SIZE] { - pbkdf2::pbkdf2_array::(password, salt, rounds) - .expect("hash is a valid fixed size") -} diff --git a/crates/bitwarden/src/crypto/user_key.rs b/crates/bitwarden/src/crypto/user_key.rs deleted file mode 100644 index 7f5bae413..000000000 --- a/crates/bitwarden/src/crypto/user_key.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::{ - crypto::{ - rsa::{make_key_pair, RsaKeyPair}, - SymmetricCryptoKey, - }, - error::Result, -}; - -pub(crate) struct UserKey(pub(super) SymmetricCryptoKey); - -impl UserKey { - pub(crate) fn new(key: SymmetricCryptoKey) -> Self { - Self(key) - } - - pub(crate) fn make_key_pair(&self) -> Result { - make_key_pair(&self.0) - } -} diff --git a/crates/bitwarden/src/error.rs b/crates/bitwarden/src/error.rs index 48cb7da16..627e92e78 100644 --- a/crates/bitwarden/src/error.rs +++ b/crates/bitwarden/src/error.rs @@ -24,10 +24,7 @@ pub enum Error { MissingFields, #[error("Cryptography error, {0}")] - Crypto(#[from] CryptoError), - - #[error("Error parsing EncString: {0}")] - InvalidEncString(#[from] EncStringParseError), + Crypto(#[from] bitwarden_crypto::CryptoError), #[error("Error parsing Identity response: {0}")] IdentityFail(crate::auth::api::response::IdentityTokenFailResponse), @@ -86,38 +83,6 @@ pub enum AccessTokenInvalidError { InvalidBase64Length { expected: usize, got: usize }, } -#[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, - #[error("Missing key")] - MissingKey, -} - -#[derive(Debug, Error)] -pub enum EncStringParseError { - #[error("No type detected, missing '.' separator")] - NoType, - #[error("Invalid symmetric type, got type {enc_type} with {parts} parts")] - InvalidTypeSymm { enc_type: String, parts: usize }, - #[error("Invalid asymmetric type, got type {enc_type} with {parts} parts")] - InvalidTypeAsymm { 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 }, -} - // Ensure that the error messages implement Send and Sync #[cfg(test)] const _: () = { diff --git a/crates/bitwarden/src/lib.rs b/crates/bitwarden/src/lib.rs index a8dd60399..a0a1c1b95 100644 --- a/crates/bitwarden/src/lib.rs +++ b/crates/bitwarden/src/lib.rs @@ -55,7 +55,6 @@ uniffi::setup_scaffolding!(); pub mod admin_console; pub mod auth; pub mod client; -pub mod crypto; pub mod error; #[cfg(feature = "mobile")] pub mod mobile; @@ -70,7 +69,6 @@ pub(crate) mod uniffi_support; mod util; #[cfg(feature = "internal")] pub mod vault; -pub mod wordlist; pub use client::Client; diff --git a/crates/bitwarden/src/mobile/client_kdf.rs b/crates/bitwarden/src/mobile/client_kdf.rs index 6b895f78a..4e62e5d59 100644 --- a/crates/bitwarden/src/mobile/client_kdf.rs +++ b/crates/bitwarden/src/mobile/client_kdf.rs @@ -1,6 +1,6 @@ -use crate::{ - client::kdf::Kdf, crypto::HashPurpose, error::Result, mobile::kdf::hash_password, Client, -}; +use bitwarden_crypto::HashPurpose; + +use crate::{client::Kdf, error::Result, mobile::kdf::hash_password, Client}; pub struct ClientKdf<'a> { pub(crate) client: &'a crate::Client, diff --git a/crates/bitwarden/src/mobile/crypto.rs b/crates/bitwarden/src/mobile/crypto.rs index ca5dea703..8894b19db 100644 --- a/crates/bitwarden/src/mobile/crypto.rs +++ b/crates/bitwarden/src/mobile/crypto.rs @@ -1,11 +1,11 @@ use std::collections::HashMap; +use bitwarden_crypto::{AsymmEncString, EncString}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::{ - client::kdf::Kdf, - crypto::{AsymmEncString, EncString}, + client::Kdf, error::{Error, Result}, Client, }; @@ -50,7 +50,7 @@ pub enum InitUserCryptoMethod { #[cfg(feature = "internal")] pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequest) -> Result<()> { - use crate::crypto::SymmetricCryptoKey; + use bitwarden_crypto::SymmetricCryptoKey; let login_method = crate::client::LoginMethod::User(crate::client::UserLoginMethod::Username { client_id: "".to_string(), @@ -118,10 +118,9 @@ pub struct DerivePinKeyResponse { #[cfg(feature = "internal")] pub fn derive_pin_key(client: &mut Client, pin: String) -> Result { - use crate::{ - client::{LoginMethod, UserLoginMethod}, - crypto::{KeyEncryptable, MasterKey}, - }; + use bitwarden_crypto::{KeyEncryptable, MasterKey}; + + use crate::client::{LoginMethod, UserLoginMethod}; let derived_key = match &client.login_method { Some(LoginMethod::User( @@ -145,7 +144,7 @@ pub fn derive_pin_key(client: &mut Client, pin: String) -> Result Result { let master_key = MasterKey::derive(password.as_bytes(), email.as_bytes(), &kdf_params)?; - master_key.derive_master_key_hash(password.as_bytes(), purpose) + Ok(master_key.derive_master_key_hash(password.as_bytes(), purpose)?) } diff --git a/crates/bitwarden/src/mobile/vault/client_ciphers.rs b/crates/bitwarden/src/mobile/vault/client_ciphers.rs index 87b77cfd5..4e34021ee 100644 --- a/crates/bitwarden/src/mobile/vault/client_ciphers.rs +++ b/crates/bitwarden/src/mobile/vault/client_ciphers.rs @@ -1,6 +1,7 @@ +use bitwarden_crypto::{Decryptable, Encryptable}; + use super::client_vault::ClientVault; use crate::{ - crypto::{Decryptable, Encryptable}, error::Result, vault::{Cipher, CipherListView, CipherView}, Client, diff --git a/crates/bitwarden/src/mobile/vault/client_collection.rs b/crates/bitwarden/src/mobile/vault/client_collection.rs index 80535406c..9cb5d1711 100644 --- a/crates/bitwarden/src/mobile/vault/client_collection.rs +++ b/crates/bitwarden/src/mobile/vault/client_collection.rs @@ -1,6 +1,7 @@ +use bitwarden_crypto::Decryptable; + use super::client_vault::ClientVault; use crate::{ - crypto::Decryptable, error::Result, vault::{Collection, CollectionView}, Client, diff --git a/crates/bitwarden/src/mobile/vault/client_folders.rs b/crates/bitwarden/src/mobile/vault/client_folders.rs index b6f6a72f4..fec3ad7db 100644 --- a/crates/bitwarden/src/mobile/vault/client_folders.rs +++ b/crates/bitwarden/src/mobile/vault/client_folders.rs @@ -1,6 +1,7 @@ +use bitwarden_crypto::{Decryptable, Encryptable}; + use super::client_vault::ClientVault; use crate::{ - crypto::{Decryptable, Encryptable}, error::Result, vault::{Folder, FolderView}, Client, diff --git a/crates/bitwarden/src/mobile/vault/client_password_history.rs b/crates/bitwarden/src/mobile/vault/client_password_history.rs index d873f8dfc..99727232b 100644 --- a/crates/bitwarden/src/mobile/vault/client_password_history.rs +++ b/crates/bitwarden/src/mobile/vault/client_password_history.rs @@ -1,6 +1,7 @@ +use bitwarden_crypto::{Decryptable, Encryptable}; + use super::client_vault::ClientVault; use crate::{ - crypto::{Decryptable, Encryptable}, error::Result, vault::{PasswordHistory, PasswordHistoryView}, Client, diff --git a/crates/bitwarden/src/mobile/vault/client_sends.rs b/crates/bitwarden/src/mobile/vault/client_sends.rs index b1de05f44..45d9a7825 100644 --- a/crates/bitwarden/src/mobile/vault/client_sends.rs +++ b/crates/bitwarden/src/mobile/vault/client_sends.rs @@ -1,8 +1,9 @@ use std::path::Path; +use bitwarden_crypto::{Decryptable, EncString, Encryptable, KeyDecryptable, KeyEncryptable}; + use super::client_vault::ClientVault; use crate::{ - crypto::{Decryptable, EncString, Encryptable, KeyDecryptable, KeyEncryptable}, error::{Error, Result}, vault::{Send, SendListView, SendView}, Client, @@ -47,7 +48,7 @@ impl<'a> ClientSends<'a> { let key = Send::get_key(&send.key, key)?; let buf = EncString::from_buffer(encrypted_buffer)?; - buf.decrypt_with_key(&key) + Ok(buf.decrypt_with_key(&key)?) } pub async fn encrypt(&self, send_view: SendView) -> Result { @@ -79,7 +80,7 @@ impl<'a> ClientSends<'a> { let key = Send::get_key(&send.key, key)?; let enc = buffer.encrypt_with_key(&key)?; - enc.to_buffer() + Ok(enc.to_buffer()?) } } diff --git a/crates/bitwarden/src/mobile/vault/client_totp.rs b/crates/bitwarden/src/mobile/vault/client_totp.rs index 75bfd204c..1d4ffd486 100644 --- a/crates/bitwarden/src/mobile/vault/client_totp.rs +++ b/crates/bitwarden/src/mobile/vault/client_totp.rs @@ -1,9 +1,10 @@ use chrono::{DateTime, Utc}; -use crate::error::Result; -use crate::vault::{generate_totp, TotpResponse}; - use super::client_vault::ClientVault; +use crate::{ + error::Result, + vault::{generate_totp, TotpResponse}, +}; impl<'a> ClientVault<'a> { /// Generate a TOTP code from a provided key. diff --git a/crates/bitwarden/src/platform/generate_fingerprint.rs b/crates/bitwarden/src/platform/generate_fingerprint.rs index 1826e195f..59d81d652 100644 --- a/crates/bitwarden/src/platform/generate_fingerprint.rs +++ b/crates/bitwarden/src/platform/generate_fingerprint.rs @@ -1,9 +1,10 @@ use base64::{engine::general_purpose::STANDARD, Engine}; +use bitwarden_crypto::fingerprint; use log::info; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{crypto::fingerprint, error::Result}; +use crate::error::Result; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -53,13 +54,12 @@ pub(crate) fn generate_user_fingerprint( mod tests { use std::num::NonZeroU32; + use super::*; use crate::{ - client::{kdf::Kdf, LoginMethod, UserLoginMethod}, + client::{Kdf, LoginMethod, UserLoginMethod}, Client, }; - use super::*; - #[test] fn test_generate_user_fingerprint() { let user_key = "2.oZg5RYpU2HjUAKI1DUQCkg==|PyRzI9kZpt66P2OedH8CHOeU0/lgKLkhIJiKDijdyFqIemBSIBoslhfQh/P1TK9xgZp0smgD6+5+yNbZfOpBaCVrsT3WWAO78xOWizduRe4=|xfDLDZSJ+yZAdh388flVg7SMDBJuMs0+CHTjutKs4uQ="; diff --git a/crates/bitwarden/src/platform/get_user_api_key.rs b/crates/bitwarden/src/platform/get_user_api_key.rs index 994a7a7ab..2eaa21894 100644 --- a/crates/bitwarden/src/platform/get_user_api_key.rs +++ b/crates/bitwarden/src/platform/get_user_api_key.rs @@ -2,6 +2,7 @@ use bitwarden_api_api::{ apis::accounts_api::accounts_api_key_post, models::{ApiKeyResponseModel, SecretVerificationRequestModel}, }; +use bitwarden_crypto::{HashPurpose, MasterKey}; use log::{debug, info}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -9,7 +10,6 @@ use serde::{Deserialize, Serialize}; use super::SecretVerificationRequest; use crate::{ client::{LoginMethod, UserLoginMethod}, - crypto::{HashPurpose, MasterKey}, error::{Error, Result}, Client, }; diff --git a/crates/bitwarden/src/secrets_manager/projects/create.rs b/crates/bitwarden/src/secrets_manager/projects/create.rs index 996a3463e..ab3b7bd62 100644 --- a/crates/bitwarden/src/secrets_manager/projects/create.rs +++ b/crates/bitwarden/src/secrets_manager/projects/create.rs @@ -1,4 +1,5 @@ use bitwarden_api_api::models::ProjectCreateRequestModel; +use bitwarden_crypto::KeyEncryptable; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -6,7 +7,6 @@ use uuid::Uuid; use super::ProjectResponse; use crate::{ client::Client, - crypto::KeyEncryptable, error::{Error, Result}, }; diff --git a/crates/bitwarden/src/secrets_manager/projects/project_response.rs b/crates/bitwarden/src/secrets_manager/projects/project_response.rs index b8c82806b..1e6f6a158 100644 --- a/crates/bitwarden/src/secrets_manager/projects/project_response.rs +++ b/crates/bitwarden/src/secrets_manager/projects/project_response.rs @@ -1,4 +1,5 @@ use bitwarden_api_api::models::ProjectResponseModel; +use bitwarden_crypto::{Decryptable, EncString}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -6,7 +7,6 @@ use uuid::Uuid; use crate::{ client::encryption_settings::EncryptionSettings, - crypto::{Decryptable, EncString}, error::{Error, Result}, }; diff --git a/crates/bitwarden/src/secrets_manager/projects/update.rs b/crates/bitwarden/src/secrets_manager/projects/update.rs index 6a0479d88..e00609ff4 100644 --- a/crates/bitwarden/src/secrets_manager/projects/update.rs +++ b/crates/bitwarden/src/secrets_manager/projects/update.rs @@ -1,4 +1,5 @@ use bitwarden_api_api::models::ProjectUpdateRequestModel; +use bitwarden_crypto::KeyEncryptable; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -6,7 +7,6 @@ use uuid::Uuid; use super::ProjectResponse; use crate::{ client::Client, - crypto::KeyEncryptable, error::{Error, Result}, }; diff --git a/crates/bitwarden/src/secrets_manager/secrets/create.rs b/crates/bitwarden/src/secrets_manager/secrets/create.rs index a1bb81799..4f84223dc 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/create.rs +++ b/crates/bitwarden/src/secrets_manager/secrets/create.rs @@ -1,11 +1,11 @@ use bitwarden_api_api::models::SecretCreateRequestModel; +use bitwarden_crypto::KeyEncryptable; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::SecretResponse; use crate::{ - crypto::KeyEncryptable, error::{Error, Result}, Client, }; diff --git a/crates/bitwarden/src/secrets_manager/secrets/list.rs b/crates/bitwarden/src/secrets_manager/secrets/list.rs index 1f4ccba3f..7fa46d164 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/list.rs +++ b/crates/bitwarden/src/secrets_manager/secrets/list.rs @@ -1,13 +1,13 @@ use bitwarden_api_api::models::{ SecretWithProjectsListResponseModel, SecretsWithProjectsInnerSecret, }; +use bitwarden_crypto::{Decryptable, EncString}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::{ client::{encryption_settings::EncryptionSettings, Client}, - crypto::{Decryptable, EncString}, error::{Error, Result}, }; diff --git a/crates/bitwarden/src/secrets_manager/secrets/secret_response.rs b/crates/bitwarden/src/secrets_manager/secrets/secret_response.rs index a7fe49200..fe1a4d342 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/secret_response.rs +++ b/crates/bitwarden/src/secrets_manager/secrets/secret_response.rs @@ -1,6 +1,7 @@ use bitwarden_api_api::models::{ BaseSecretResponseModel, BaseSecretResponseModelListResponseModel, SecretResponseModel, }; +use bitwarden_crypto::{Decryptable, EncString}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -8,7 +9,6 @@ use uuid::Uuid; use crate::{ client::encryption_settings::EncryptionSettings, - crypto::{Decryptable, EncString}, error::{Error, Result}, }; diff --git a/crates/bitwarden/src/secrets_manager/secrets/update.rs b/crates/bitwarden/src/secrets_manager/secrets/update.rs index 970812c5a..f9e54f810 100644 --- a/crates/bitwarden/src/secrets_manager/secrets/update.rs +++ b/crates/bitwarden/src/secrets_manager/secrets/update.rs @@ -1,4 +1,5 @@ use bitwarden_api_api::models::SecretUpdateRequestModel; +use bitwarden_crypto::KeyEncryptable; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -6,7 +7,6 @@ use uuid::Uuid; use super::SecretResponse; use crate::{ client::Client, - crypto::KeyEncryptable, error::{Error, Result}, }; diff --git a/crates/bitwarden/src/secrets_manager/state.rs b/crates/bitwarden/src/secrets_manager/state.rs index d39603d34..4efa4403b 100644 --- a/crates/bitwarden/src/secrets_manager/state.rs +++ b/crates/bitwarden/src/secrets_manager/state.rs @@ -1,11 +1,12 @@ +use std::{fmt::Debug, path::Path}; + +use bitwarden_crypto::{EncString, KeyDecryptable, KeyEncryptable}; use serde::{Deserialize, Serialize}; use crate::{ client::AccessToken, - crypto::{EncString, KeyDecryptable, KeyEncryptable}, error::{Error, Result}, }; -use std::{fmt::Debug, path::Path}; const STATE_VERSION: u32 = 1; diff --git a/crates/bitwarden/src/tool/generators/passphrase.rs b/crates/bitwarden/src/tool/generators/passphrase.rs index 7c5c8e434..f64b39dc2 100644 --- a/crates/bitwarden/src/tool/generators/passphrase.rs +++ b/crates/bitwarden/src/tool/generators/passphrase.rs @@ -1,8 +1,10 @@ -use crate::{error::Result, util::capitalize_first_letter, wordlist::EFF_LONG_WORD_LIST}; +use bitwarden_crypto::EFF_LONG_WORD_LIST; use rand::{seq::SliceRandom, Rng, RngCore}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use crate::{error::Result, util::capitalize_first_letter}; + /// Passphrase generator request options. #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] diff --git a/crates/bitwarden/src/tool/generators/username.rs b/crates/bitwarden/src/tool/generators/username.rs index 666772714..20b101fa0 100644 --- a/crates/bitwarden/src/tool/generators/username.rs +++ b/crates/bitwarden/src/tool/generators/username.rs @@ -1,8 +1,10 @@ -use crate::{error::Result, util::capitalize_first_letter, wordlist::EFF_LONG_WORD_LIST}; +use bitwarden_crypto::EFF_LONG_WORD_LIST; use rand::{distributions::Distribution, seq::SliceRandom, Rng, RngCore}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use crate::{error::Result, util::capitalize_first_letter}; + #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Enum))] @@ -84,9 +86,10 @@ impl ForwarderServiceType { // Generate a username using the specified email forwarding service // This requires an HTTP client to be passed in, as the service will need to make API calls pub async fn generate(self, http: &reqwest::Client, website: Option) -> Result { - use crate::tool::generators::username_forwarders::*; use ForwarderServiceType::*; + use crate::tool::generators::username_forwarders::*; + match self { AddyIo { api_token, diff --git a/crates/bitwarden/src/uniffi_support.rs b/crates/bitwarden/src/uniffi_support.rs index 795aa9f50..fb7650d36 100644 --- a/crates/bitwarden/src/uniffi_support.rs +++ b/crates/bitwarden/src/uniffi_support.rs @@ -1,40 +1,12 @@ use std::{num::NonZeroU32, str::FromStr}; +use bitwarden_crypto::{AsymmEncString, EncString}; use uuid::Uuid; -use crate::{ - crypto::{AsymmEncString, EncString}, - error::Error, - UniffiCustomTypeConverter, -}; +use crate::UniffiCustomTypeConverter; -uniffi::custom_type!(NonZeroU32, u32); - -impl UniffiCustomTypeConverter for NonZeroU32 { - type Builtin = u32; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Self::new(val).ok_or(Error::from("Number is zero").into()) - } - - fn from_custom(obj: Self) -> Self::Builtin { - obj.get() - } -} - -uniffi::custom_type!(EncString, String); - -impl UniffiCustomTypeConverter for EncString { - type Builtin = String; - - fn into_custom(val: Self::Builtin) -> uniffi::Result { - Self::from_str(&val).map_err(|e| e.into()) - } - - fn from_custom(obj: Self) -> Self::Builtin { - obj.to_string() - } -} +uniffi::ffi_converter_forward!(NonZeroU32, bitwarden_crypto::UniFfiTag, crate::UniFfiTag); +uniffi::ffi_converter_forward!(EncString, bitwarden_crypto::UniFfiTag, crate::UniFfiTag); uniffi::custom_type!(AsymmEncString, String); diff --git a/crates/bitwarden/src/vault/cipher/attachment.rs b/crates/bitwarden/src/vault/cipher/attachment.rs index c22997c83..fa4a35fc7 100644 --- a/crates/bitwarden/src/vault/cipher/attachment.rs +++ b/crates/bitwarden/src/vault/cipher/attachment.rs @@ -1,10 +1,10 @@ +use bitwarden_crypto::{ + CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{ - crypto::{EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}, - error::{Error, Result}, -}; +use crate::error::{Error, Result}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -32,7 +32,7 @@ pub struct AttachmentView { } impl KeyEncryptable for AttachmentView { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(Attachment { id: self.id, url: self.url, @@ -45,7 +45,7 @@ impl KeyEncryptable for AttachmentView { } impl KeyDecryptable for Attachment { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(AttachmentView { id: self.id.clone(), url: self.url.clone(), diff --git a/crates/bitwarden/src/vault/cipher/card.rs b/crates/bitwarden/src/vault/cipher/card.rs index c0f97e1df..05c3aa631 100644 --- a/crates/bitwarden/src/vault/cipher/card.rs +++ b/crates/bitwarden/src/vault/cipher/card.rs @@ -1,11 +1,11 @@ use bitwarden_api_api::models::CipherCardModel; +use bitwarden_crypto::{ + CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{ - crypto::{EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}, - error::{Error, Result}, -}; +use crate::error::{Error, Result}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -32,7 +32,7 @@ pub struct CardView { } impl KeyEncryptable for CardView { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(Card { cardholder_name: self.cardholder_name.encrypt_with_key(key)?, exp_month: self.exp_month.encrypt_with_key(key)?, @@ -45,7 +45,7 @@ impl KeyEncryptable for CardView { } impl KeyDecryptable for Card { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(CardView { cardholder_name: self.cardholder_name.decrypt_with_key(key)?, exp_month: self.exp_month.decrypt_with_key(key)?, diff --git a/crates/bitwarden/src/vault/cipher/cipher.rs b/crates/bitwarden/src/vault/cipher/cipher.rs index d36855a19..07d8da226 100644 --- a/crates/bitwarden/src/vault/cipher/cipher.rs +++ b/crates/bitwarden/src/vault/cipher/cipher.rs @@ -1,4 +1,8 @@ use bitwarden_api_api::models::CipherDetailsResponseModel; +use bitwarden_crypto::{ + CryptoError, EncString, KeyContainer, KeyDecryptable, KeyEncryptable, LocateKey, + SymmetricCryptoKey, +}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -11,8 +15,6 @@ use super::{ login, secure_note, }; use crate::{ - client::encryption_settings::EncryptionSettings, - crypto::{EncString, KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey}, error::{Error, Result}, vault::password_history, }; @@ -136,7 +138,7 @@ pub struct CipherListView { } impl KeyEncryptable for CipherView { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { let ciphers_key = Cipher::get_cipher_key(key, &self.key)?; let key = ciphers_key.as_ref().unwrap_or(key); @@ -170,7 +172,7 @@ impl KeyEncryptable for CipherView { } impl KeyDecryptable for Cipher { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { let ciphers_key = Cipher::get_cipher_key(key, &self.key)?; let key = ciphers_key.as_ref().unwrap_or(key); @@ -211,7 +213,7 @@ impl Cipher { fn get_cipher_key( key: &SymmetricCryptoKey, ciphers_key: &Option, - ) -> Result> { + ) -> Result, CryptoError> { ciphers_key .as_ref() .map(|k| { @@ -221,7 +223,7 @@ impl Cipher { .transpose() } - fn get_decrypted_subtitle(&self, key: &SymmetricCryptoKey) -> Result { + fn get_decrypted_subtitle(&self, key: &SymmetricCryptoKey) -> Result { Ok(match self.r#type { CipherType::Login => { let Some(login) = &self.login else { @@ -287,7 +289,7 @@ impl Cipher { } impl KeyDecryptable for Cipher { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { let ciphers_key = Cipher::get_cipher_key(key, &self.key)?; let key = ciphers_key.as_ref().unwrap_or(key); @@ -318,7 +320,7 @@ impl KeyDecryptable for Cipher { impl LocateKey for Cipher { fn locate_key<'a>( &self, - enc: &'a EncryptionSettings, + enc: &'a dyn KeyContainer, _: &Option, ) -> Option<&'a SymmetricCryptoKey> { enc.get_key(&self.organization_id) @@ -327,7 +329,7 @@ impl LocateKey for Cipher { impl LocateKey for CipherView { fn locate_key<'a>( &self, - enc: &'a EncryptionSettings, + enc: &'a dyn KeyContainer, _: &Option, ) -> Option<&'a SymmetricCryptoKey> { enc.get_key(&self.organization_id) diff --git a/crates/bitwarden/src/vault/cipher/field.rs b/crates/bitwarden/src/vault/cipher/field.rs index bf9352d68..6bb106c32 100644 --- a/crates/bitwarden/src/vault/cipher/field.rs +++ b/crates/bitwarden/src/vault/cipher/field.rs @@ -1,13 +1,13 @@ use bitwarden_api_api::models::CipherFieldModel; +use bitwarden_crypto::{ + CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use super::linked_id::LinkedIdType; -use crate::{ - crypto::{EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}, - error::{Error, Result}, -}; +use crate::error::{Error, Result}; #[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema)] #[repr(u8)] @@ -42,7 +42,7 @@ pub struct FieldView { } impl KeyEncryptable for FieldView { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(Field { name: self.name.encrypt_with_key(key)?, value: self.value.encrypt_with_key(key)?, @@ -53,7 +53,7 @@ impl KeyEncryptable for FieldView { } impl KeyDecryptable for Field { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(FieldView { name: self.name.decrypt_with_key(key)?, value: self.value.decrypt_with_key(key)?, diff --git a/crates/bitwarden/src/vault/cipher/identity.rs b/crates/bitwarden/src/vault/cipher/identity.rs index c3082ccc9..c4154aeba 100644 --- a/crates/bitwarden/src/vault/cipher/identity.rs +++ b/crates/bitwarden/src/vault/cipher/identity.rs @@ -1,11 +1,11 @@ use bitwarden_api_api::models::CipherIdentityModel; +use bitwarden_crypto::{ + CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{ - crypto::{EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}, - error::{Error, Result}, -}; +use crate::error::{Error, Result}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -56,7 +56,7 @@ pub struct IdentityView { } impl KeyEncryptable for IdentityView { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(Identity { title: self.title.encrypt_with_key(key)?, first_name: self.first_name.encrypt_with_key(key)?, @@ -81,7 +81,7 @@ impl KeyEncryptable for IdentityView { } impl KeyDecryptable for Identity { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(IdentityView { title: self.title.decrypt_with_key(key)?, first_name: self.first_name.decrypt_with_key(key)?, diff --git a/crates/bitwarden/src/vault/cipher/local_data.rs b/crates/bitwarden/src/vault/cipher/local_data.rs index a3d0bf519..6a85512c2 100644 --- a/crates/bitwarden/src/vault/cipher/local_data.rs +++ b/crates/bitwarden/src/vault/cipher/local_data.rs @@ -1,11 +1,7 @@ +use bitwarden_crypto::{CryptoError, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{ - crypto::{KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}, - error::Result, -}; - #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "mobile", derive(uniffi::Record))] @@ -23,7 +19,7 @@ pub struct LocalDataView { } impl KeyEncryptable for LocalDataView { - fn encrypt_with_key(self, _key: &SymmetricCryptoKey) -> Result { + fn encrypt_with_key(self, _key: &SymmetricCryptoKey) -> Result { Ok(LocalData { last_used_date: self.last_used_date, last_launched: self.last_launched, @@ -32,7 +28,7 @@ impl KeyEncryptable for LocalDataView { } impl KeyDecryptable for LocalData { - fn decrypt_with_key(&self, _key: &SymmetricCryptoKey) -> Result { + fn decrypt_with_key(&self, _key: &SymmetricCryptoKey) -> Result { Ok(LocalDataView { last_used_date: self.last_used_date, last_launched: self.last_launched, diff --git a/crates/bitwarden/src/vault/cipher/login.rs b/crates/bitwarden/src/vault/cipher/login.rs index 8342fba53..139750d51 100644 --- a/crates/bitwarden/src/vault/cipher/login.rs +++ b/crates/bitwarden/src/vault/cipher/login.rs @@ -1,13 +1,13 @@ use bitwarden_api_api::models::{CipherLoginModel, CipherLoginUriModel}; +use bitwarden_crypto::{ + CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, +}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; -use crate::{ - crypto::{EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}, - error::{Error, Result}, -}; +use crate::error::{Error, Result}; #[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema)] #[repr(u8)] @@ -65,7 +65,7 @@ pub struct LoginView { } impl KeyEncryptable for LoginUriView { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(LoginUri { uri: self.uri.encrypt_with_key(key)?, r#match: self.r#match, @@ -74,7 +74,7 @@ impl KeyEncryptable for LoginUriView { } impl KeyEncryptable for LoginView { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(Login { username: self.username.encrypt_with_key(key)?, password: self.password.encrypt_with_key(key)?, @@ -87,7 +87,7 @@ impl KeyEncryptable for LoginView { } impl KeyDecryptable for LoginUri { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(LoginUriView { uri: self.uri.decrypt_with_key(key)?, r#match: self.r#match, @@ -96,7 +96,7 @@ impl KeyDecryptable for LoginUri { } impl KeyDecryptable for Login { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(LoginView { username: self.username.decrypt_with_key(key)?, password: self.password.decrypt_with_key(key)?, diff --git a/crates/bitwarden/src/vault/cipher/secure_note.rs b/crates/bitwarden/src/vault/cipher/secure_note.rs index ee24b74b3..8f7069ee1 100644 --- a/crates/bitwarden/src/vault/cipher/secure_note.rs +++ b/crates/bitwarden/src/vault/cipher/secure_note.rs @@ -1,12 +1,10 @@ use bitwarden_api_api::models::CipherSecureNoteModel; +use bitwarden_crypto::{CryptoError, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; -use crate::{ - crypto::{KeyDecryptable, KeyEncryptable, SymmetricCryptoKey}, - error::{Error, Result}, -}; +use crate::error::{Error, Result}; #[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema)] #[repr(u8)] @@ -30,7 +28,7 @@ pub struct SecureNoteView { } impl KeyEncryptable for SecureNoteView { - fn encrypt_with_key(self, _key: &SymmetricCryptoKey) -> Result { + fn encrypt_with_key(self, _key: &SymmetricCryptoKey) -> Result { Ok(SecureNote { r#type: self.r#type, }) @@ -38,7 +36,7 @@ impl KeyEncryptable for SecureNoteView { } impl KeyDecryptable for SecureNote { - fn decrypt_with_key(&self, _key: &SymmetricCryptoKey) -> Result { + fn decrypt_with_key(&self, _key: &SymmetricCryptoKey) -> Result { Ok(SecureNoteView { r#type: self.r#type, }) diff --git a/crates/bitwarden/src/vault/collection.rs b/crates/bitwarden/src/vault/collection.rs index 8b39a9d22..a78209fc9 100644 --- a/crates/bitwarden/src/vault/collection.rs +++ b/crates/bitwarden/src/vault/collection.rs @@ -1,13 +1,12 @@ use bitwarden_api_api::models::CollectionDetailsResponseModel; +use bitwarden_crypto::{ + CryptoError, EncString, KeyContainer, KeyDecryptable, LocateKey, SymmetricCryptoKey, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::{ - client::encryption_settings::EncryptionSettings, - crypto::{EncString, KeyDecryptable, LocateKey, SymmetricCryptoKey}, - error::{Error, Result}, -}; +use crate::error::{Error, Result}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -40,14 +39,14 @@ pub struct CollectionView { impl LocateKey for Collection { fn locate_key<'a>( &self, - enc: &'a EncryptionSettings, + enc: &'a dyn KeyContainer, _: &Option, ) -> Option<&'a SymmetricCryptoKey> { enc.get_key(&Some(self.organization_id)) } } impl KeyDecryptable for Collection { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(CollectionView { id: self.id, organization_id: self.organization_id, diff --git a/crates/bitwarden/src/vault/folder.rs b/crates/bitwarden/src/vault/folder.rs index f41e0e2b0..a6b9790bc 100644 --- a/crates/bitwarden/src/vault/folder.rs +++ b/crates/bitwarden/src/vault/folder.rs @@ -1,13 +1,13 @@ use bitwarden_api_api::models::FolderResponseModel; +use bitwarden_crypto::{ + CryptoError, EncString, KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey, +}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::{ - crypto::{EncString, KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey}, - error::{Error, Result}, -}; +use crate::error::{Error, Result}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase")] @@ -29,7 +29,7 @@ pub struct FolderView { impl LocateKey for FolderView {} impl KeyEncryptable for FolderView { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(Folder { id: self.id, name: self.name.encrypt_with_key(key)?, @@ -40,7 +40,7 @@ impl KeyEncryptable for FolderView { impl LocateKey for Folder {} impl KeyDecryptable for Folder { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(FolderView { id: self.id, name: self.name.decrypt_with_key(key)?, diff --git a/crates/bitwarden/src/vault/password_history.rs b/crates/bitwarden/src/vault/password_history.rs index 4475f1cff..333420b6a 100644 --- a/crates/bitwarden/src/vault/password_history.rs +++ b/crates/bitwarden/src/vault/password_history.rs @@ -1,12 +1,12 @@ use bitwarden_api_api::models::CipherPasswordHistoryModel; +use bitwarden_crypto::{ + CryptoError, EncString, KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey, +}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{ - crypto::{EncString, KeyDecryptable, KeyEncryptable, LocateKey, SymmetricCryptoKey}, - error::{Error, Result}, -}; +use crate::error::{Error, Result}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -26,7 +26,7 @@ pub struct PasswordHistoryView { impl LocateKey for PasswordHistoryView {} impl KeyEncryptable for PasswordHistoryView { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(PasswordHistory { password: self.password.encrypt_with_key(key)?, last_used_date: self.last_used_date, @@ -36,7 +36,10 @@ impl KeyEncryptable for PasswordHistoryView impl LocateKey for PasswordHistory {} impl KeyDecryptable for PasswordHistory { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + fn decrypt_with_key( + &self, + key: &SymmetricCryptoKey, + ) -> Result { Ok(PasswordHistoryView { password: self.password.decrypt_with_key(key)?, last_used_date: self.last_used_date, diff --git a/crates/bitwarden/src/vault/send.rs b/crates/bitwarden/src/vault/send.rs index 742d3c592..8ed405b4c 100644 --- a/crates/bitwarden/src/vault/send.rs +++ b/crates/bitwarden/src/vault/send.rs @@ -3,19 +3,17 @@ use base64::{ Engine, }; use bitwarden_api_api::models::{SendFileModel, SendResponseModel, SendTextModel}; +use bitwarden_crypto::{ + derive_shareable_key, generate_random_bytes, CryptoError, EncString, KeyDecryptable, + KeyEncryptable, LocateKey, SymmetricCryptoKey, +}; use chrono::{DateTime, Utc}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use uuid::Uuid; -use crate::{ - crypto::{ - derive_shareable_key, generate_random_bytes, EncString, KeyDecryptable, KeyEncryptable, - LocateKey, SymmetricCryptoKey, - }, - error::{CryptoError, Error, Result}, -}; +use crate::error::{Error, Result}; const SEND_ITERATIONS: u32 = 100_000; @@ -145,19 +143,19 @@ impl Send { pub(crate) fn get_key( send_key: &EncString, enc_key: &SymmetricCryptoKey, - ) -> Result { + ) -> Result { let key: Vec = send_key.decrypt_with_key(enc_key)?; Self::derive_shareable_key(&key) } - fn derive_shareable_key(key: &[u8]) -> Result { + fn derive_shareable_key(key: &[u8]) -> Result { let key = key.try_into().map_err(|_| CryptoError::InvalidKeyLen)?; Ok(derive_shareable_key(key, "send", Some("send"))) } } impl KeyDecryptable for SendText { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(SendTextView { text: self.text.decrypt_with_key(key)?, hidden: self.hidden, @@ -166,7 +164,7 @@ impl KeyDecryptable for SendText { } impl KeyEncryptable for SendTextView { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(SendText { text: self.text.encrypt_with_key(key)?, hidden: self.hidden, @@ -175,7 +173,7 @@ impl KeyEncryptable for SendTextView { } impl KeyDecryptable for SendFile { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(SendFileView { id: self.id.clone(), file_name: self.file_name.decrypt_with_key(key)?, @@ -186,7 +184,7 @@ impl KeyDecryptable for SendFile { } impl KeyEncryptable for SendFileView { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { Ok(SendFile { id: self.id.clone(), file_name: self.file_name.encrypt_with_key(key)?, @@ -198,7 +196,7 @@ impl KeyEncryptable for SendFileView { impl LocateKey for Send {} impl KeyDecryptable for Send { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { // For sends, we first decrypt the send key with the user key, and stretch it to it's full size // For the rest of the fields, we ignore the provided SymmetricCryptoKey and the stretched key let k: Vec = self.key.decrypt_with_key(key)?; @@ -231,7 +229,7 @@ impl KeyDecryptable for Send { } impl KeyDecryptable for Send { - fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { // For sends, we first decrypt the send key with the user key, and stretch it to it's full size // For the rest of the fields, we ignore the provided SymmetricCryptoKey and the stretched key let key = Send::get_key(&self.key, key)?; @@ -254,7 +252,7 @@ impl KeyDecryptable for Send { impl LocateKey for SendView {} impl KeyEncryptable for SendView { - fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { // For sends, we first decrypt the send key with the user key, and stretch it to it's full size // For the rest of the fields, we ignore the provided SymmetricCryptoKey and the stretched key let k = match (self.key, self.id) { @@ -268,7 +266,7 @@ impl KeyEncryptable for SendView { key.to_vec() } // Existing send without key - _ => return Err(CryptoError::InvalidKey.into()), + _ => return Err(CryptoError::InvalidKey), }; let send_key = Send::derive_shareable_key(&k)?; @@ -280,7 +278,7 @@ impl KeyEncryptable for SendView { notes: self.notes.encrypt_with_key(&send_key)?, key: k.encrypt_with_key(key)?, password: self.new_password.map(|password| { - let password = crate::crypto::pbkdf2(password.as_bytes(), &k, SEND_ITERATIONS); + let password = bitwarden_crypto::pbkdf2(password.as_bytes(), &k, SEND_ITERATIONS); STANDARD.encode(password) }), @@ -360,10 +358,11 @@ impl TryFrom for SendText { #[cfg(test)] mod tests { + use bitwarden_crypto::{KeyDecryptable, KeyEncryptable}; + use super::{Send, SendText, SendTextView, SendType}; use crate::{ - client::{encryption_settings::EncryptionSettings, kdf::Kdf, UserLoginMethod}, - crypto::{KeyDecryptable, KeyEncryptable}, + client::{encryption_settings::EncryptionSettings, Kdf, UserLoginMethod}, vault::SendView, }; diff --git a/crates/bitwarden/src/vault/totp.rs b/crates/bitwarden/src/vault/totp.rs index 6e863d57b..223c8c504 100644 --- a/crates/bitwarden/src/vault/totp.rs +++ b/crates/bitwarden/src/vault/totp.rs @@ -1,6 +1,5 @@ use std::{collections::HashMap, str::FromStr}; -use crate::error::{Error, Result}; use chrono::{DateTime, Utc}; use data_encoding::BASE32_NOPAD; use hmac::{Hmac, Mac}; @@ -8,6 +7,8 @@ use reqwest::Url; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use crate::error::{Error, Result}; + type HmacSha1 = Hmac; type HmacSha256 = Hmac; type HmacSha512 = Hmac; @@ -221,9 +222,10 @@ fn derive_binary(hash: Vec) -> u32 { #[cfg(test)] mod tests { - use super::*; use chrono::Utc; + use super::*; + #[test] fn test_generate_totp() { let cases = vec![ diff --git a/crates/bitwarden/tests/register.rs b/crates/bitwarden/tests/register.rs index 956fe86ce..8e523e26f 100644 --- a/crates/bitwarden/tests/register.rs +++ b/crates/bitwarden/tests/register.rs @@ -5,10 +5,10 @@ async fn test_register_initialize_crypto() { use std::num::NonZeroU32; use bitwarden::{ - client::kdf::Kdf, mobile::crypto::{InitUserCryptoMethod, InitUserCryptoRequest}, Client, }; + use bitwarden_crypto::Kdf; let mut client = Client::new(None); diff --git a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt index c38c14a07..dd79ff870 100644 --- a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt +++ b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt @@ -24,12 +24,12 @@ import androidx.compose.ui.unit.dp import androidx.fragment.app.FragmentActivity import com.bitwarden.core.DateTime import com.bitwarden.core.Folder -import com.bitwarden.core.HashPurpose import com.bitwarden.core.InitOrgCryptoRequest import com.bitwarden.core.InitUserCryptoMethod import com.bitwarden.core.InitUserCryptoRequest import com.bitwarden.core.Kdf import com.bitwarden.core.Uuid +import com.bitwarden.crypto.HashPurpose import com.bitwarden.myapplication.ui.theme.MyApplicationTheme import com.bitwarden.sdk.Client import io.ktor.client.HttpClient diff --git a/languages/swift/build.sh b/languages/swift/build.sh index 69da98415..87f9cecdb 100755 --- a/languages/swift/build.sh +++ b/languages/swift/build.sh @@ -28,14 +28,16 @@ cargo run -p uniffi-bindgen generate \ --out-dir tmp/bindings # Move generated swift bindings -mv ./tmp/bindings/BitwardenSDK.swift ./Sources/BitwardenSdk/ mv ./tmp/bindings/BitwardenCore.swift ./Sources/BitwardenSdk/ +mv ./tmp/bindings/BitwardenCrypto.swift ./Sources/BitwardenSdk/ +mv ./tmp/bindings/BitwardenSDK.swift ./Sources/BitwardenSdk/ # Massage the generated files to fit xcframework mkdir tmp/Headers -mv ./tmp/bindings/BitwardenFFI.h ./tmp/Headers/ mv ./tmp/bindings/BitwardenCoreFFI.h ./tmp/Headers/ -cat ./tmp/bindings/BitwardenFFI.modulemap ./tmp/bindings/BitwardenCoreFFI.modulemap > ./tmp/Headers/module.modulemap +mv ./tmp/bindings/BitwardenCryptoFFI.h ./tmp/Headers/ +mv ./tmp/bindings/BitwardenFFI.h ./tmp/Headers/ +cat ./tmp/bindings/BitwardenFFI.modulemap ./tmp/bindings/BitwardenCoreFFI.modulemap ./tmp/bindings/BitwardenCryptoFFI.modulemap > ./tmp/Headers/module.modulemap # Build xcframework xcodebuild -create-xcframework \