Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PM-5691] Extract crypto to separate crate #402

Merged
merged 40 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
3c55a87
Experiment with using a separate crate for crypto
Hinton Dec 5, 2023
b454601
Move more code to bitwarden-crypto
Hinton Dec 7, 2023
f4e4827
Merge branch 'main' of github.com:bitwarden/sdk into ps/crypto-crate
Hinton Jan 1, 2024
7e0b392
Move remaining files, improve docs
Hinton Jan 4, 2024
b96f219
Merge branch 'main' into ps/crypto-crate
Hinton Jan 4, 2024
0af379f
Fix build error
Hinton Jan 4, 2024
3c4e603
Fix naming
Hinton Jan 4, 2024
9d7126b
Fix clippy
Hinton Jan 4, 2024
05a761d
Improve comments
Hinton Jan 8, 2024
0e96011
Merge branch 'main' of github.com:bitwarden/sdk into ps/crypto-crate
Hinton Jan 8, 2024
9e446cf
Fix ambigous
Hinton Jan 8, 2024
c1e7f6f
Fix android
Hinton Jan 8, 2024
1c571f4
Use mobile feature for crypto
Hinton Jan 8, 2024
c26c888
Move uniffi AsymmEncString to bitwarden crate
Hinton Jan 8, 2024
30c4489
Merge branch 'main' of github.com:bitwarden/sdk into ps/crypto-crate
Hinton Jan 8, 2024
7e3f8a2
Fix ios
Hinton Jan 8, 2024
075f175
Fix test
Hinton Jan 8, 2024
63e922e
Merge branch 'main' of github.com:bitwarden/sdk into ps/crypto-crate
Hinton Jan 9, 2024
2e8fba1
Merge branch 'main' of github.com:bitwarden/sdk into ps/crypto-crate
Hinton Jan 9, 2024
254c0db
Merge branch 'main' of github.com:bitwarden/sdk into ps/crypto-crate
Hinton Jan 9, 2024
490abc6
Fix clippy
Hinton Jan 9, 2024
bff92d5
Merge branch 'main' of github.com:bitwarden/sdk into ps/crypto-crate
Hinton Jan 11, 2024
e5584fd
fmt
Hinton Jan 11, 2024
b0c1a7b
Don't re-export HashPurpose
Hinton Jan 11, 2024
6188a5e
Forgot to update a place
Hinton Jan 11, 2024
16cbe12
Fix uniffi doc
Hinton Jan 11, 2024
d319867
Remove unused dependencies
Hinton Jan 11, 2024
0ae213a
Remove bitwarden::Kdf
Hinton Jan 11, 2024
4d686dd
Minor import tweaks
Hinton Jan 11, 2024
4bf7bb2
Remove unnecessary into
Hinton Jan 11, 2024
d9481b3
Remove InvalidLen
Hinton Jan 11, 2024
5cc63b0
Merge branch 'main' of github.com:bitwarden/sdk into ps/crypto-crate
Hinton Jan 11, 2024
6116292
Resolve linting errors
Hinton Jan 11, 2024
de160d9
Merge branch 'main' of github.com:bitwarden/sdk into ps/crypto-crate
Hinton Jan 12, 2024
dd70d0a
fmt and clippy
Hinton Jan 12, 2024
6bf18e1
Fix doc
Hinton Jan 15, 2024
f9d83e0
Remove test feature in crypto
Hinton Jan 15, 2024
f9dca85
Fix tests
Hinton Jan 15, 2024
fb300ba
Remove crypto/mod.rs, remove rsa from bitwarden crate
Hinton Jan 15, 2024
cf2a3b6
Update bump and publish workflows
Hinton Jan 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build-rust-crates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:

package:
- bitwarden
- bitwarden-crypto
- bitwarden-api-api
- bitwarden-api-identity

Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/publish-rust-crates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 "==================================="
Expand All @@ -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

Expand Down
11 changes: 9 additions & 2 deletions .github/workflows/version-bump.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
39 changes: 29 additions & 10 deletions Cargo.lock

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

45 changes: 45 additions & 0 deletions crates/bitwarden-crypto/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<u8>, key: GenericArray<u8, U32>) -> Result<Vec<u8>> {
pub(crate) fn decrypt_aes256(
iv: &[u8; 16],
data: Vec<u8>,
key: GenericArray<u8, U32>,
) -> Result<Vec<u8>> {
// Decrypt data
let iv = GenericArray::from_slice(iv);
let mut data = data;
Expand All @@ -41,7 +42,7 @@ pub fn decrypt_aes256(iv: &[u8; 16], data: Vec<u8>, key: GenericArray<u8, U32>)
/// 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<u8>,
Expand All @@ -50,7 +51,7 @@ pub fn decrypt_aes256_hmac(
) -> Result<Vec<u8>> {
let res = generate_mac(&mac_key, iv, &data)?;
if res.ct_ne(mac).into() {
return Err(CryptoError::InvalidMac.into());
return Err(CryptoError::InvalidMac);
}
decrypt_aes256(iv, data, key)
}
Expand All @@ -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, U32>) -> ([u8; 16], Vec<u8>) {
pub(crate) fn encrypt_aes256(data_dec: &[u8], key: GenericArray<u8, U32>) -> ([u8; 16], Vec<u8>) {
let rng = rand::thread_rng();
let (iv, data) = encrypt_aes256_internal(rng, data_dec, key);

Expand All @@ -77,7 +78,7 @@ pub fn encrypt_aes256(data_dec: &[u8], key: GenericArray<u8, U32>) -> ([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<u8, U32>,
key: GenericArray<u8, U32>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Self, Self::Err> {
let (enc_type, parts) = split_enc_string(s);
Expand Down Expand Up @@ -153,7 +152,7 @@ impl AsymmEncString {
impl KeyDecryptable<AsymmetricCryptoKey, Vec<u8>> for AsymmEncString {
fn decrypt_with_key(&self, key: &AsymmetricCryptoKey) -> Result<Vec<u8>> {
use AsymmEncString::*;
Ok(match self {
match self {
Rsa2048_OaepSha256_B64 { data } => key.key.decrypt(Oaep::new::<sha2::Sha256>(), data),
Rsa2048_OaepSha1_B64 { data } => key.key.decrypt(Oaep::new::<sha1::Sha1>(), data),
#[allow(deprecated)]
Expand All @@ -165,14 +164,14 @@ impl KeyDecryptable<AsymmetricCryptoKey, Vec<u8>> for AsymmEncString {
key.key.decrypt(Oaep::new::<sha1::Sha1>(), data)
}
}
.map_err(|_| CryptoError::KeyDecrypt)?)
.map_err(|_| CryptoError::KeyDecrypt)
}
}

impl KeyDecryptable<AsymmetricCryptoKey, String> for AsymmEncString {
fn decrypt_with_key(&self, key: &AsymmetricCryptoKey) -> Result<String> {
let dec: Vec<u8> = self.decrypt_with_key(key)?;
String::from_utf8(dec).map_err(|_| CryptoError::InvalidUtf8String.into())
String::from_utf8(dec).map_err(|_| CryptoError::InvalidUtf8String)
}
}

Expand All @@ -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
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// Encrypted string types
mod asymmetric;
mod symmetric;

Expand All @@ -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 {
Expand Down
Loading