Skip to content
4 changes: 4 additions & 0 deletions crates/bitwarden-crypto/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ pub enum CryptoError {
MissingKeyId(String),
#[error("Key operation not supported by key: {0:?}")]
KeyOperationNotSupported(KeyOperation),
#[error("Cannot encrypt a restricted (partial) view; its secret fields were never decrypted")]
EncryptRestrictedView,
#[error("A restricted (partial) cipher can only be decrypted when it is organization-owned")]
RestrictedCipherRequiresOrganization,

// Note: These variants will be moved into their own key store error in a follow up ticket,
// since the crypto error is growing too large
Expand Down
1 change: 1 addition & 0 deletions crates/bitwarden-exporters/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ impl From<ImportingCipher> for CipherView {
};

Self {
partial: false,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a guarantee? Are we never allowing import/export of partials?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, partials is not import/exportable.

id: None,
organization_id: None,
folder_id: value.folder_id.map(FolderId::new),
Expand Down
2 changes: 2 additions & 0 deletions crates/bitwarden-exporters/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ mod tests {

let test_id: uuid::Uuid = "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap();
let view = CipherView {
partial: false,
Comment thread
Hinton marked this conversation as resolved.
r#type: CipherType::Login,
login: Some(LoginView {
username: Some("test_username".to_string()),
Expand Down Expand Up @@ -330,6 +331,7 @@ mod tests {

let test_id: uuid::Uuid = "fd411a1a-fec8-4070-985d-0e6560860e69".parse().unwrap();
let cipher_view = CipherView {
partial: false,
r#type: CipherType::Login,
login: Some(LoginView {
username: Some("test_username".to_string()),
Expand Down
1 change: 1 addition & 0 deletions crates/bitwarden-fido/src/authenticator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,7 @@ mod tests {
};

CipherView {
partial: false,
id: Some("c2c7e624-dcfd-4f23-af41-b177014ffcb5".parse().unwrap()),
organization_id: None,
folder_id: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ mod tests {

fn make_test_cipher(attachments: Option<Vec<Attachment>>) -> Cipher {
Cipher {
partial_data: None,
Comment thread
Hinton marked this conversation as resolved.
id: None,
organization_id: None,
folder_id: None,
Expand Down Expand Up @@ -307,6 +308,7 @@ mod tests {
fn make_cipher_view() -> bitwarden_vault::CipherView {
use bitwarden_vault::{CipherView, LoginView};
CipherView {
partial: false,
id: None,
organization_id: None,
folder_id: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ mod tests {

// Add a cipher with an old attachment (key is None)
sync.ciphers = vec![Cipher {
partial_data: None,
id: None,
organization_id: None,
folder_id: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,7 @@ mod tests {

// Add a cipher with an old attachment (key is None)
sync.ciphers = vec![Cipher {
partial_data: None,
id: None,
organization_id: None,
folder_id: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,7 @@ mod tests {
.encrypt(&mut ctx, cipher_key)
.unwrap();
Cipher {
partial_data: None,
id: None,
organization_id: None,
folder_id: None,
Expand Down
3 changes: 3 additions & 0 deletions crates/bitwarden-vault/src/cipher/attachment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ mod tests {

let attachment_file = AttachmentFileView {
cipher: Cipher {
partial_data: None,
id: None,
organization_id: None,
folder_id: None,
Expand Down Expand Up @@ -467,6 +468,7 @@ mod tests {
};

let cipher = Cipher {
partial_data: None,
id: None,
organization_id: None,
folder_id: None,
Expand Down Expand Up @@ -531,6 +533,7 @@ mod tests {
};

let cipher = Cipher {
partial_data: None,
id: None,
organization_id: None,
folder_id: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ mod tests {

fn test_cipher() -> Cipher {
Cipher {
partial_data: None,
id: TEST_CIPHER_ID.parse().ok(),
name: Some(TEST_CIPHER_NAME.parse().unwrap()),
r#type: CipherType::Login,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ mod tests {

fn test_cipher() -> Cipher {
Cipher {
partial_data: None,
id: TEST_CIPHER_ID.parse().ok(),
name: Some(TEST_CIPHER_NAME.parse().unwrap()),
r#type: CipherType::Login,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ mod tests {

fn test_cipher() -> Cipher {
Cipher {
partial_data: None,
id: TEST_CIPHER_ID.parse().ok(),
name: Some(TEST_CIPHER_NAME.parse().unwrap()),
r#type: CipherType::Login,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ mod tests {

fn cipher_with(name: EncString, attachments: Option<Vec<Attachment>>) -> Cipher {
Cipher {
partial_data: None,
id: TEST_CIPHER_ID.parse().ok(),
name: Some(name),
r#type: CipherType::Login,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ pub(crate) mod test_support {

pub(crate) fn create_shell_cipher_view(cipher_type: CipherType) -> CipherView {
CipherView {
partial: false,
id: None,
organization_id: None,
folder_id: None,
Expand Down
11 changes: 11 additions & 0 deletions crates/bitwarden-vault/src/cipher/blob/encryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ pub(crate) fn encrypt_blob_cipher_with_wrapping_key(
ctx: &mut KeyStoreContext<KeySlotIds>,
wrapping_key: SymmetricKeySlotId,
) -> Result<Cipher, BlobEncryptionError> {
// Fail closed: a restricted (partial) view has all secret fields stripped; re-encrypting it
// would overwrite the item's secrets with empty values. See `decrypt_restricted_cipher_view`.
if view.partial {
return Err(BlobEncryptionError::Crypto(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Hinton where does the partial view come from? Assuming that - as asked above - only org ciphers have partial views, then blob ciphers won't immediately be a problem, but become a problem as soon as we undertake organizations moving to blob ciphers.

Is the plan to:

  1. always keep the partial format with the name / uri separated out for partial ciphers?
  2. move to a (small) data blob

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this path is actually always correct. You wouldn't want to re-encrypt the partial data. Updates should be done on the full cipher.

Blobs will impact conversion to PAM and PAM updates, though, since the server can no longer separate our the partial data from the full data.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partials comes from the sync.

  1. Partials define their own DTO which has a separate type from the cipher blobs.
  2. Yes. We initially wanted blobs but it was deemed not viable at this stage.

CryptoError::EncryptRestrictedView,
));
}

if view.key.is_none() {
view.generate_cipher_key(ctx, wrapping_key)?;
}
Expand All @@ -108,6 +116,7 @@ pub(crate) fn encrypt_blob_cipher_with_wrapping_key(
let name = "".encrypt(ctx, cipher_key)?;

Ok(Cipher {
partial_data: None,
// Metadata
id: view.id,
organization_id: view.organization_id,
Expand Down Expand Up @@ -175,6 +184,7 @@ pub(crate) fn decrypt_blob_cipher(
let local_data = cipher.local_data.decrypt(ctx, cipher_key).ok().flatten();

let mut view = CipherView {
partial: false,
// Metadata
id: cipher.id,
organization_id: cipher.organization_id,
Expand Down Expand Up @@ -250,6 +260,7 @@ mod tests {
)
.unwrap();
Cipher {
partial_data: None,
id: None,
organization_id: None,
folder_id: None,
Expand Down
Loading
Loading