Skip to content

Commit

Permalink
Add support for URI checksums
Browse files Browse the repository at this point in the history
  • Loading branch information
dani-garcia committed Mar 15, 2024
1 parent dd440ba commit a673f09
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 1 deletion.
12 changes: 11 additions & 1 deletion crates/bitwarden/src/mobile/vault/client_ciphers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ impl<'a> ClientCiphers<'a> {
cipher_view.generate_cipher_key(key)?;
}

// For compatibility reasons, we only create checksums for ciphers that have a key
if cipher_view.key.is_some() {
cipher_view.generate_checksums();
}

Check warning on line 30 in crates/bitwarden/src/mobile/vault/client_ciphers.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden/src/mobile/vault/client_ciphers.rs#L28-L30

Added lines #L28 - L30 were not covered by tests

let cipher = cipher_view.encrypt(enc, &None)?;

Ok(cipher)
Expand All @@ -32,7 +37,12 @@ impl<'a> ClientCiphers<'a> {
pub async fn decrypt(&self, cipher: Cipher) -> Result<CipherView> {
let enc = self.client.get_encryption_settings()?;

let cipher_view = cipher.decrypt(enc, &None)?;
let mut cipher_view: CipherView = cipher.decrypt(enc, &None)?;

Check warning on line 40 in crates/bitwarden/src/mobile/vault/client_ciphers.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden/src/mobile/vault/client_ciphers.rs#L40

Added line #L40 was not covered by tests

// For compatibility we only remove URLs with invalid checksums if the cipher has a key
if cipher_view.key.is_some() {
cipher_view.remove_invalid_checksums();
}

Check warning on line 45 in crates/bitwarden/src/mobile/vault/client_ciphers.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden/src/mobile/vault/client_ciphers.rs#L43-L45

Added lines #L43 - L45 were not covered by tests

Ok(cipher_view)
}
Expand Down
14 changes: 14 additions & 0 deletions crates/bitwarden/src/vault/cipher/cipher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,20 @@ impl CipherView {
self.key = Some(new_key.to_vec().encrypt_with_key(key)?);
Ok(())
}

pub fn generate_checksums(&mut self) {
if let Some(uris) = self.login.as_mut().and_then(|l| l.uris.as_mut()) {
for uri in uris {
uri.generate_checksum();
}
}
}

Check warning on line 309 in crates/bitwarden/src/vault/cipher/cipher.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden/src/vault/cipher/cipher.rs#L303-L309

Added lines #L303 - L309 were not covered by tests

pub fn remove_invalid_checksums(&mut self) {
if let Some(uris) = self.login.as_mut().and_then(|l| l.uris.as_mut()) {
uris.retain(|u| u.is_checksum_valid());
}
}

Check warning on line 315 in crates/bitwarden/src/vault/cipher/cipher.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden/src/vault/cipher/cipher.rs#L311-L315

Added lines #L311 - L315 were not covered by tests
}

impl KeyDecryptable<SymmetricCryptoKey, CipherListView> for Cipher {
Expand Down
83 changes: 83 additions & 0 deletions crates/bitwarden/src/vault/cipher/login.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use base64::{engine::general_purpose::STANDARD, Engine};
use bitwarden_api_api::models::{CipherLoginModel, CipherLoginUriModel};
use bitwarden_crypto::{
CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey,
Expand Down Expand Up @@ -28,6 +29,7 @@ pub enum UriMatchType {
pub struct LoginUri {
pub uri: Option<EncString>,
pub r#match: Option<UriMatchType>,
pub uri_checksum: Option<EncString>,
}

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
Expand All @@ -36,6 +38,35 @@ pub struct LoginUri {
pub struct LoginUriView {
pub uri: Option<String>,
pub r#match: Option<UriMatchType>,
pub uri_checksum: Option<String>,
}

impl LoginUriView {
pub(crate) fn is_checksum_valid(&self) -> bool {
let Some(uri) = &self.uri else {
return false;

Check warning on line 47 in crates/bitwarden/src/vault/cipher/login.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden/src/vault/cipher/login.rs#L47

Added line #L47 was not covered by tests
};
let Some(cs) = &self.uri_checksum else {
return false;
};
let Ok(cs) = STANDARD.decode(cs) else {
return false;

Check warning on line 53 in crates/bitwarden/src/vault/cipher/login.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden/src/vault/cipher/login.rs#L53

Added line #L53 was not covered by tests
};

use sha2::Digest;
let uri_hash = sha2::Sha256::new().chain_update(uri.as_bytes()).finalize();

uri_hash.as_slice() == cs
}

pub(crate) fn generate_checksum(&mut self) {
if let Some(uri) = &self.uri {
use sha2::Digest;
let uri_hash = sha2::Sha256::new().chain_update(uri.as_bytes()).finalize();
let uri_hash = STANDARD.encode(uri_hash.as_slice());
self.uri_checksum = Some(uri_hash);
}
}
}

#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
Expand Down Expand Up @@ -93,6 +124,7 @@ impl KeyEncryptable<SymmetricCryptoKey, LoginUri> for LoginUriView {
Ok(LoginUri {
uri: self.uri.encrypt_with_key(key)?,
r#match: self.r#match,
uri_checksum: self.uri_checksum.encrypt_with_key(key)?,

Check warning on line 127 in crates/bitwarden/src/vault/cipher/login.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden/src/vault/cipher/login.rs#L127

Added line #L127 was not covered by tests
})
}
}
Expand All @@ -116,6 +148,7 @@ impl KeyDecryptable<SymmetricCryptoKey, LoginUriView> for LoginUri {
Ok(LoginUriView {
uri: self.uri.decrypt_with_key(key)?,
r#match: self.r#match,
uri_checksum: self.uri_checksum.decrypt_with_key(key)?,

Check warning on line 151 in crates/bitwarden/src/vault/cipher/login.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden/src/vault/cipher/login.rs#L151

Added line #L151 was not covered by tests
})
}
}
Expand Down Expand Up @@ -166,6 +199,7 @@ impl TryFrom<CipherLoginUriModel> for LoginUri {
Ok(Self {
uri: EncString::try_from_optional(uri.uri)?,
r#match: uri.r#match.map(|m| m.into()),
uri_checksum: EncString::try_from_optional(uri.uri_checksum)?,

Check warning on line 202 in crates/bitwarden/src/vault/cipher/login.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden/src/vault/cipher/login.rs#L202

Added line #L202 was not covered by tests
})
}
}
Expand Down Expand Up @@ -208,3 +242,52 @@ impl TryFrom<bitwarden_api_api::models::CipherFido2CredentialModel> for Fido2Cre
})
}
}

#[cfg(test)]
mod tests {
#[test]
fn test_valid_checksum() {
let uri = super::LoginUriView {
uri: Some("https://example.com".to_string()),
r#match: Some(super::UriMatchType::Domain),
uri_checksum: Some("EAaArVRs5qV39C9S3zO0z9ynVoWeZkuNfeMpsVDQnOk=".to_string()),
};
assert!(uri.is_checksum_valid());
}

#[test]
fn test_invalid_checksum() {
let uri = super::LoginUriView {
uri: Some("https://example.com".to_string()),
r#match: Some(super::UriMatchType::Domain),
uri_checksum: Some("UtSgIv8LYfEdOu7yqjF7qXWhmouYGYC8RSr7/ryZg5Q=".to_string()),
};
assert!(!uri.is_checksum_valid());
}

#[test]
fn test_missing_checksum() {
let uri = super::LoginUriView {
uri: Some("https://example.com".to_string()),
r#match: Some(super::UriMatchType::Domain),
uri_checksum: None,
};
assert!(!uri.is_checksum_valid());
}

#[test]
fn test_generate_checksum() {
let mut uri = super::LoginUriView {
uri: Some("https://test.com".to_string()),
r#match: Some(super::UriMatchType::Domain),
uri_checksum: None,
};

uri.generate_checksum();

assert_eq!(
uri.uri_checksum.unwrap().as_str(),
"OWk2vQvwYD1nhLZdA+ltrpBWbDa2JmHyjUEWxRZSS8w="
);
}
}

0 comments on commit a673f09

Please sign in to comment.