Skip to content

Commit

Permalink
[PM-8301] Create bitwarden-vault (#825)
Browse files Browse the repository at this point in the history
Continue extracting functionality from `bitwarden` to new crates.

- Extracts `VaultLocked` to `bitwarden-core`.
- Moved `bitwarden/src/vault` models to `bitwarden-vault` crate.
- Refactored Authenticator logic to not impl vault owned models.
  • Loading branch information
Hinton committed Jun 10, 2024
1 parent 5ee45a1 commit 9acd362
Show file tree
Hide file tree
Showing 51 changed files with 638 additions and 538 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/publish-rust-crates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ on:
required: true
default: true
type: boolean
publish_bitwarden-vault:
description: "Publish bitwarden-valt crate"
required: true
default: true
type: boolean

defaults:
run:
Expand Down Expand Up @@ -91,6 +96,7 @@ jobs:
PUBLISH_BITWARDEN_CLI: ${{ github.event.inputs.publish_bitwarden-cli }}
PUBLISH_BITWARDEN_GENERATORS: ${{ github.event.inputs.publish_bitwarden-generators }}
PUBLISH_BITWARDEN_EXPORTERS: ${{ github.event.inputs.publish_bitwarden-exporters }}
PUBLISH_BITWARDEN_VAULT: ${{ github.event.inputs.publish_bitwarden-vault }}
run: |
if [[ "$PUBLISH_BITWARDEN" == "false" ]] && [[ "$PUBLISH_BITWARDEN_API_API" == "false" ]] && [[ "$PUBLISH_BITWARDEN_API_IDENTITY" == "false" ]]; then
echo "==================================="
Expand Down Expand Up @@ -142,6 +148,11 @@ jobs:
PACKAGES_LIST="$PACKAGES_LIST bitwarden-exporters"
fi
if [[ "$PUBLISH_BITWARDEN_VAULT" == "true" ]]; then
PACKAGES_COMMAND="$PACKAGES_COMMAND -p bitwarden-vault"
PACKAGES_LIST="$PACKAGES_LIST bitwarden-vault"
fi
echo "Packages command: " $PACKAGES_COMMAND
echo "Packages list: " $PACKAGES_LIST
Expand Down
28 changes: 25 additions & 3 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ bitwarden-core = { path = "crates/bitwarden-core", version = "=0.5.0" }
bitwarden-crypto = { path = "crates/bitwarden-crypto", version = "=0.5.0" }
bitwarden-exporters = { path = "crates/bitwarden-exporters", version = "=0.5.0" }
bitwarden-generators = { path = "crates/bitwarden-generators", version = "=0.5.0" }
bitwarden-vault = { path = "crates/bitwarden-vault", version = "=0.5.0" }

[workspace.lints.clippy]
unwrap_used = "deny"
Expand Down
4 changes: 4 additions & 0 deletions crates/bitwarden-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ use thiserror::Error;
#[error("The response received was missing a required field: {0}")]
pub struct MissingFieldError(pub &'static str);

#[derive(Debug, Error)]
#[error("The client vault is locked and needs to be unlocked before use")]
pub struct VaultLocked;

/// This macro is used to require that a value is present or return an error otherwise.
/// It is equivalent to using `val.ok_or(Error::MissingFields)?`, but easier to use and
/// with a more descriptive error message.
Expand Down
2 changes: 1 addition & 1 deletion crates/bitwarden-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ uniffi::setup_scaffolding!();
mod uniffi_support;

mod error;
pub use error::MissingFieldError;
pub use error::{MissingFieldError, VaultLocked};
44 changes: 44 additions & 0 deletions crates/bitwarden-vault/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
[package]
name = "bitwarden-vault"
description = """
Internal crate for the bitwarden crate. Do not use.
"""

version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
homepage.workspace = true
repository.workspace = true
license-file.workspace = true
keywords.workspace = true

[features]
uniffi = [
"bitwarden-core/uniffi",
"bitwarden-crypto/uniffi",
"dep:uniffi",
] # Uniffi bindings

[dependencies]
base64 = ">=0.21.2, <0.23"
bitwarden-api-api = { workspace = true }
bitwarden-core = { workspace = true }
bitwarden-crypto = { workspace = true }
bitwarden-exporters = { workspace = true }
chrono = { version = ">=0.4.26, <0.5", default-features = false }
rand = ">=0.8.5, <0.9"
hmac = ">=0.12.1, <0.13"
reqwest = { version = ">=0.12, <0.13", default-features = false }
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"
serde_repr = ">=0.1.12, <0.2"
sha1 = ">=0.10.5, <0.11"
sha2 = ">=0.10.6, <0.11"
thiserror = ">=1.0.40, <2.0"
uniffi = { version = "=0.27.2", optional = true }
uuid = { version = ">=1.3.3, <2.0", features = ["serde"] }

[lints]
workspace = true
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use super::Cipher;
use crate::error::{Error, Result};
use crate::VaultParseError;

#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
Expand Down Expand Up @@ -125,9 +125,11 @@ impl KeyDecryptable<SymmetricCryptoKey, AttachmentView> for Attachment {
}

impl TryFrom<bitwarden_api_api::models::AttachmentResponseModel> for Attachment {
type Error = Error;
type Error = VaultParseError;

fn try_from(attachment: bitwarden_api_api::models::AttachmentResponseModel) -> Result<Self> {
fn try_from(
attachment: bitwarden_api_api::models::AttachmentResponseModel,
) -> Result<Self, Self::Error> {
Ok(Self {
id: attachment.id,
url: attachment.url,
Expand All @@ -144,7 +146,7 @@ mod tests {
use base64::{engine::general_purpose::STANDARD, Engine};
use bitwarden_crypto::{EncString, KeyDecryptable, SymmetricCryptoKey};

use crate::vault::{
use crate::{
cipher::cipher::{CipherRepromptType, CipherType},
Attachment, AttachmentFile, Cipher,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use bitwarden_crypto::{
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::error::{Error, Result};
use crate::VaultParseError;

#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
Expand Down Expand Up @@ -58,9 +58,9 @@ impl KeyDecryptable<SymmetricCryptoKey, CardView> for Card {
}

impl TryFrom<CipherCardModel> for Card {
type Error = Error;
type Error = VaultParseError;

fn try_from(card: CipherCardModel) -> Result<Self> {
fn try_from(card: CipherCardModel) -> Result<Self, Self::Error> {
Ok(Self {
cardholder_name: EncString::try_from_optional(card.cardholder_name)?,
exp_month: EncString::try_from_optional(card.exp_month)?,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use bitwarden_api_api::models::CipherDetailsResponseModel;
use bitwarden_core::require;
use bitwarden_core::{require, MissingFieldError, VaultLocked};
use bitwarden_crypto::{
CryptoError, EncString, KeyContainer, KeyDecryptable, KeyEncryptable, LocateKey,
SymmetricCryptoKey,
Expand All @@ -8,6 +8,7 @@ use chrono::{DateTime, Utc};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use thiserror::Error;
use uuid::Uuid;

use super::{
Expand All @@ -16,11 +17,20 @@ use super::{
login, secure_note,
};
#[cfg(feature = "uniffi")]
use crate::{client::encryption_settings::EncryptionSettings, vault::Fido2CredentialView};
use crate::{
error::{Error, Result},
vault::{password_history, Fido2CredentialFullView},
};
use crate::Fido2CredentialView;
use crate::{password_history, Fido2CredentialFullView, VaultParseError};

#[derive(Debug, Error)]
pub enum CipherError {
#[error(transparent)]
MissingFieldError(#[from] MissingFieldError),
#[error(transparent)]
VaultLocked(#[from] VaultLocked),
#[error(transparent)]
CryptoError(#[from] CryptoError),
#[error("This cipher contains attachments without keys. Those attachments will need to be reuploaded to complete the operation")]
AttachmentsWithoutKeys,
}

#[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema)]
#[repr(u8)]
Expand Down Expand Up @@ -343,7 +353,7 @@ fn build_subtitle_identity(first_name: Option<String>, last_name: Option<String>
}

impl CipherView {
pub fn generate_cipher_key(&mut self, key: &SymmetricCryptoKey) -> Result<()> {
pub fn generate_cipher_key(&mut self, key: &SymmetricCryptoKey) -> Result<(), CryptoError> {
let old_ciphers_key = Cipher::get_cipher_key(key, &self.key)?;
let old_key = old_ciphers_key.as_ref().unwrap_or(key);

Expand Down Expand Up @@ -374,7 +384,7 @@ impl CipherView {
&mut self,
old_key: &SymmetricCryptoKey,
new_key: &SymmetricCryptoKey,
) -> Result<()> {
) -> Result<(), CryptoError> {
if let Some(attachments) = &mut self.attachments {
for attachment in attachments {
if let Some(attachment_key) = &mut attachment.key {
Expand All @@ -387,11 +397,11 @@ impl CipherView {
}

#[cfg(feature = "uniffi")]
pub(crate) fn decrypt_fido2_credentials(
pub fn decrypt_fido2_credentials(
&self,
enc: &EncryptionSettings,
) -> Result<Vec<Fido2CredentialView>> {
let key = self.locate_key(enc, &None).ok_or(Error::VaultLocked)?;
enc: &dyn KeyContainer,
) -> Result<Vec<Fido2CredentialView>, CipherError> {
let key = self.locate_key(enc, &None).ok_or(VaultLocked)?;
let cipher_key = Cipher::get_cipher_key(key, &self.key)?;

let key = cipher_key.as_ref().unwrap_or(key);
Expand All @@ -409,7 +419,7 @@ impl CipherView {
&mut self,
old_key: &SymmetricCryptoKey,
new_key: &SymmetricCryptoKey,
) -> Result<()> {
) -> Result<(), CryptoError> {
if let Some(login) = self.login.as_mut() {
if let Some(fido2_credentials) = &mut login.fido2_credentials {
let dec_fido2_credentials: Vec<Fido2CredentialFullView> =
Expand All @@ -424,18 +434,14 @@ impl CipherView {
&mut self,
enc: &dyn KeyContainer,
organization_id: Uuid,
) -> Result<()> {
let old_key = enc
.get_key(&self.organization_id)
.ok_or(Error::VaultLocked)?;
) -> Result<(), CipherError> {
let old_key = enc.get_key(&self.organization_id).ok_or(VaultLocked)?;

let new_key = enc
.get_key(&Some(organization_id))
.ok_or(Error::VaultLocked)?;
let new_key = enc.get_key(&Some(organization_id)).ok_or(VaultLocked)?;

// If any attachment is missing a key we can't reencrypt the attachment keys
if self.attachments.iter().flatten().any(|a| a.key.is_none()) {
return Err("This cipher contains attachments without keys. Those attachments will need to be reuploaded to complete the operation".into());
return Err(CipherError::AttachmentsWithoutKeys);
}

// If the cipher has a key, we need to re-encrypt it with the new organization key
Expand All @@ -453,14 +459,12 @@ impl CipherView {
}

#[cfg(feature = "uniffi")]
pub(crate) fn set_new_fido2_credentials(
pub fn set_new_fido2_credentials(
&mut self,
enc: &dyn KeyContainer,
creds: Vec<Fido2CredentialFullView>,
) -> Result<()> {
let key = enc
.get_key(&self.organization_id)
.ok_or(Error::VaultLocked)?;
) -> Result<(), CipherError> {
let key = enc.get_key(&self.organization_id).ok_or(VaultLocked)?;

let ciphers_key = Cipher::get_cipher_key(key, &self.key)?;
let ciphers_key = ciphers_key.as_ref().unwrap_or(key);
Expand All @@ -472,13 +476,11 @@ impl CipherView {
}

#[cfg(feature = "uniffi")]
pub(crate) fn get_fido2_credentials(
pub fn get_fido2_credentials(
&self,
enc: &dyn KeyContainer,
) -> Result<Vec<Fido2CredentialFullView>> {
let key = enc
.get_key(&self.organization_id)
.ok_or(Error::VaultLocked)?;
) -> Result<Vec<Fido2CredentialFullView>, CipherError> {
let key = enc.get_key(&self.organization_id).ok_or(VaultLocked)?;

let ciphers_key = Cipher::get_cipher_key(key, &self.key)?;
let ciphers_key = ciphers_key.as_ref().unwrap_or(key);
Expand Down Expand Up @@ -539,9 +541,9 @@ impl LocateKey for CipherView {
}

impl TryFrom<CipherDetailsResponseModel> for Cipher {
type Error = Error;
type Error = VaultParseError;

fn try_from(cipher: CipherDetailsResponseModel) -> Result<Self> {
fn try_from(cipher: CipherDetailsResponseModel) -> Result<Self, Self::Error> {
Ok(Self {
id: cipher.id,
organization_id: cipher.organization_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};

use super::linked_id::LinkedIdType;
use crate::error::{Error, Result};
use crate::VaultParseError;

#[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema)]
#[repr(u8)]
Expand Down Expand Up @@ -65,9 +65,9 @@ impl KeyDecryptable<SymmetricCryptoKey, FieldView> for Field {
}

impl TryFrom<CipherFieldModel> for Field {
type Error = Error;
type Error = VaultParseError;

fn try_from(model: CipherFieldModel) -> Result<Self> {
fn try_from(model: CipherFieldModel) -> Result<Self, Self::Error> {
Ok(Self {
name: EncString::try_from_optional(model.name)?,
value: EncString::try_from_optional(model.value)?,
Expand Down
Loading

0 comments on commit 9acd362

Please sign in to comment.