Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 3 additions & 4 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion crates/bitwarden-core/src/key_management/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ pub(super) async fn initialize_user_crypto(
master_key,
user_key,
} => {
let mut bytes = master_key.as_bytes().to_vec();
let mut bytes = master_key.into_bytes();
let master_key = MasterKey::try_from(bytes.as_mut_slice())?;

client
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ impl<Ids: KeyIds> FromStr for PasswordProtectedKeyEnvelope<Ids> {
"Invalid PasswordProtectedKeyEnvelope Base64 encoding".to_string(),
)
})?;
Self::try_from(&data.as_bytes().to_vec()).map_err(|_| {
Self::try_from(&data.into_bytes()).map_err(|_| {
PasswordProtectedKeyEnvelopeError::ParsingError(
"Failed to parse PasswordProtectedKeyEnvelope".to_string(),
)
Expand Down
5 changes: 5 additions & 0 deletions crates/bitwarden-encoding/src/b64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ impl B64 {
pub fn as_bytes(&self) -> &[u8] {
&self.0
}

/// Returns the inner byte vector.
pub fn into_bytes(self) -> Vec<u8> {
self.0
}
}

// We manually implement this to handle both `String` and `&str`
Expand Down
5 changes: 5 additions & 0 deletions crates/bitwarden-encoding/src/b64url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ impl B64Url {
pub fn as_bytes(&self) -> &[u8] {
&self.0
}

/// Returns the inner byte vector.
pub fn into_bytes(self) -> Vec<u8> {
self.0
}
}

impl From<Vec<u8>> for B64Url {
Expand Down
2 changes: 1 addition & 1 deletion crates/bitwarden-exporters/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ wasm = [
] # WebAssembly bindings

[dependencies]
base64 = ">=0.22.1, <0.23"
bitwarden-collections = { workspace = true }
bitwarden-core = { workspace = true }
bitwarden-crypto = { workspace = true }
bitwarden-encoding = { workspace = true }
bitwarden-error = { workspace = true }
bitwarden-fido = { workspace = true }
bitwarden-ssh = { workspace = true }
Expand Down
85 changes: 23 additions & 62 deletions crates/bitwarden-exporters/src/cxf/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,9 +275,8 @@ struct GroupedCredentials {

#[cfg(test)]
mod tests {
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use chrono::{Duration, Month};
use credential_exchange_format::{CreditCardCredential, EditableFieldYearMonth};
use credential_exchange_format::{B64Url, CreditCardCredential, EditableFieldYearMonth};

use super::*;

Expand Down Expand Up @@ -320,35 +319,22 @@ mod tests {
#[test]
fn test_parse_passkey() {
let item = Item {
id: URL_SAFE_NO_PAD
.decode("Njk1RERENTItNkQ0Ny00NERBLTlFN0EtNDM1MjNEQjYzNjVF")
.unwrap()
.as_slice()
.into(),
id: B64Url::try_from("Njk1RERENTItNkQ0Ny00NERBLTlFN0EtNDM1MjNEQjYzNjVF")
.unwrap(),
creation_at: Some(1732181986),
modified_at: Some(1732182026),
title: "example.com".to_string(),
subtitle: None,
favorite: None,
credentials: vec![Credential::Passkey(Box::new(PasskeyCredential {
credential_id: URL_SAFE_NO_PAD
.decode("6NiHiekW4ZY8vYHa-ucbvA")
.unwrap()
.as_slice()
.into(),
credential_id: B64Url::try_from("6NiHiekW4ZY8vYHa-ucbvA")
.unwrap(),
rp_id: "example.com".to_string(),
username: "pj-fry".to_string(),
user_display_name: "Philip J. Fry".to_string(),
user_handle: URL_SAFE_NO_PAD
.decode("YWxleCBtdWxsZXI")
.unwrap()
.as_slice()
.into(),
key: URL_SAFE_NO_PAD
.decode("MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPzvtWYWmIsvqqr3LsZB0K-cbjuhJSGTGziL1LksHAPShRANCAAT-vqHTyEDS9QBNNi2BNLyu6TunubJT_L3G3i7KLpEDhMD15hi24IjGBH0QylJIrvlT4JN2tdRGF436XGc-VoAl")
.unwrap()
.as_slice()
.into(),
user_handle: B64Url::try_from("YWxleCBtdWxsZXI").unwrap(),
key: B64Url::try_from("MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPzvtWYWmIsvqqr3LsZB0K-cbjuhJSGTGziL1LksHAPShRANCAAT-vqHTyEDS9QBNNi2BNLyu6TunubJT_L3G3i7KLpEDhMD15hi24IjGBH0QylJIrvlT4JN2tdRGF436XGc-VoAl")
.unwrap(),
fido2_extensions: None,
}))],
tags: None,
Expand Down Expand Up @@ -407,11 +393,8 @@ mod tests {
use credential_exchange_format::{BasicAuthCredential, CredentialScope};

let item = Item {
id: URL_SAFE_NO_PAD
.decode("Njk1RERENTItNkQ0Ny00NERBLTlFN0EtNDM1MjNEQjYzNjVF")
.unwrap()
.as_slice()
.into(),
id: B64Url::try_from("Njk1RERENTItNkQ0Ny00NERBLTlFN0EtNDM1MjNEQjYzNjVF")
.unwrap(),
creation_at: Some(1732181986),
modified_at: Some(1732182026),
title: "Combined Login".to_string(),
Expand All @@ -423,24 +406,15 @@ mod tests {
password: Some("basic_password".to_string().into()),
})),
Credential::Passkey(Box::new(PasskeyCredential {
credential_id: URL_SAFE_NO_PAD
.decode("6NiHiekW4ZY8vYHa-ucbvA")
.unwrap()
.as_slice()
.into(),
credential_id: B64Url::try_from("6NiHiekW4ZY8vYHa-ucbvA")
.unwrap(),
rp_id: "passkey-domain.com".to_string(),
username: "passkey_username".to_string(),
user_display_name: "Passkey User".to_string(),
user_handle: URL_SAFE_NO_PAD
.decode("YWxleCBtdWxsZXI")
.unwrap()
.as_slice()
.into(),
key: URL_SAFE_NO_PAD
.decode("MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPzvtWYWmIsvqqr3LsZB0K-cbjuhJSGTGziL1LksHAPShRANCAAT-vqHTyEDS9QBNNi2BNLyu6TunubJT_L3G3i7KLpEDhMD15hi24IjGBH0QylJIrvlT4JN2tdRGF436XGc-VoAl")
.unwrap()
.as_slice()
.into(),
user_handle: B64Url::try_from("YWxleCBtdWxsZXI")
.unwrap(),
key: B64Url::try_from("MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPzvtWYWmIsvqqr3LsZB0K-cbjuhJSGTGziL1LksHAPShRANCAAT-vqHTyEDS9QBNNi2BNLyu6TunubJT_L3G3i7KLpEDhMD15hi24IjGBH0QylJIrvlT4JN2tdRGF436XGc-VoAl")
.unwrap(),
fido2_extensions: None,
}))
],
Expand Down Expand Up @@ -479,35 +453,22 @@ mod tests {
#[test]
fn test_passkey_with_empty_username() {
let item = Item {
id: URL_SAFE_NO_PAD
.decode("Njk1RERENTItNkQ0Ny00NERBLTlFN0EtNDM1MjNEQjYzNjVF")
.unwrap()
.as_slice()
.into(),
id: B64Url::try_from("Njk1RERENTItNkQ0Ny00NERBLTlFN0EtNDM1MjNEQjYzNjVF").unwrap(),
creation_at: Some(1732181986),
modified_at: Some(1732182026),
title: "Empty Username Passkey".to_string(),
subtitle: None,
favorite: None,
credentials: vec![Credential::Passkey(Box::new(PasskeyCredential {
credential_id: URL_SAFE_NO_PAD
.decode("6NiHiekW4ZY8vYHa-ucbvA")
.unwrap()
.as_slice()
.into(),
credential_id: B64Url::try_from("6NiHiekW4ZY8vYHa-ucbvA")
.unwrap(),
rp_id: "example.com".to_string(),
username: "".to_string(), // Empty username
user_display_name: "User Display".to_string(),
user_handle: URL_SAFE_NO_PAD
.decode("YWxleCBtdWxsZXI")
.unwrap()
.as_slice()
.into(),
key: URL_SAFE_NO_PAD
.decode("MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPzvtWYWmIsvqqr3LsZB0K-cbjuhJSGTGziL1LksHAPShRANCAAT-vqHTyEDS9QBNNi2BNLyu6TunubJT_L3G3i7KLpEDhMD15hi24IjGBH0QylJIrvlT4JN2tdRGF436XGc-VoAl")
.unwrap()
.as_slice()
.into(),
user_handle: B64Url::try_from("YWxleCBtdWxsZXI")
.unwrap(),
key: B64Url::try_from("MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPzvtWYWmIsvqqr3LsZB0K-cbjuhJSGTGziL1LksHAPShRANCAAT-vqHTyEDS9QBNNi2BNLyu6TunubJT_L3G3i7KLpEDhMD15hi24IjGBH0QylJIrvlT4JN2tdRGF436XGc-VoAl")
.unwrap(),
fido2_extensions: None,
}))],
tags: None,
Expand Down
19 changes: 9 additions & 10 deletions crates/bitwarden-exporters/src/cxf/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
//! Handles conversion between internal [Login] and credential exchange [BasicAuthCredential] and
//! [PasskeyCredential].

use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use bitwarden_core::MissingFieldError;
use bitwarden_fido::{string_to_guid_bytes, InvalidGuid};
use bitwarden_vault::{FieldType, Totp, TotpAlgorithm};
use chrono::{DateTime, Utc};
use credential_exchange_format::{
AndroidAppIdCredential, BasicAuthCredential, CredentialScope, OTPHashAlgorithm,
PasskeyCredential, TotpCredential,
AndroidAppIdCredential, B64Url, BasicAuthCredential, CredentialScope, NotB64UrlEncoded,
OTPHashAlgorithm, PasskeyCredential, TotpCredential,
};
use thiserror::Error;

Expand Down Expand Up @@ -82,7 +81,7 @@ pub(super) fn to_login(
key_type: "public-key".to_string(),
key_algorithm: "ECDSA".to_string(),
key_curve: "P-256".to_string(),
key_value: URL_SAFE_NO_PAD.encode(&p.key),
key_value: p.key.to_string(),
rp_id: p.rp_id.clone(),
user_handle: Some(p.user_handle.to_string()),
user_name: Some(p.username.clone()),
Expand Down Expand Up @@ -191,8 +190,8 @@ pub enum PasskeyError {
InvalidGuid(InvalidGuid),
#[error(transparent)]
MissingField(MissingFieldError),
#[error(transparent)]
InvalidBase64(#[from] base64::DecodeError),
#[error("Data isn't base64url encoded")]
InvalidBase64(NotB64UrlEncoded),
}

impl TryFrom<Fido2Credential> for PasskeyCredential {
Expand All @@ -212,11 +211,11 @@ impl TryFrom<Fido2Credential> for PasskeyCredential {
user_display_name: value.user_display_name.unwrap_or_default(),
user_handle: value
.user_handle
.map(|v| URL_SAFE_NO_PAD.decode(v))
.transpose()?
.map(|v| v.into())
.map(|v| B64Url::try_from(v.as_str()))
.transpose()
.map_err(PasskeyError::InvalidBase64)?
.ok_or(PasskeyError::MissingField(MissingFieldError("user_handle")))?,
key: URL_SAFE_NO_PAD.decode(value.key_value)?.into(),
key: B64Url::try_from(value.key_value.as_str()).map_err(PasskeyError::InvalidBase64)?,
fido2_extensions: None,
})
}
Expand Down
6 changes: 3 additions & 3 deletions crates/bitwarden-exporters/src/encrypted_json.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use base64::{engine::general_purpose::STANDARD, Engine};
use bitwarden_crypto::{generate_random_bytes, Kdf, KeyEncryptable, PinKey};
use bitwarden_encoding::B64;
use serde::Serialize;
use thiserror::Error;
use uuid::Uuid;
Expand Down Expand Up @@ -44,15 +44,15 @@ pub(crate) fn export_encrypted_json(
};

let salt = generate_random_bytes::<[u8; 16]>();
let salt = STANDARD.encode(salt);
let salt = B64::from(salt.as_slice());
let key = PinKey::derive(password.as_bytes(), salt.as_bytes(), &kdf)?;

let enc_key_validation = Uuid::new_v4().to_string();

let encrypted_export = EncryptedJsonExport {
encrypted: true,
password_protected: true,
salt,
salt: salt.to_string(),
kdf_type,
kdf_iterations,
kdf_memory,
Expand Down
2 changes: 1 addition & 1 deletion crates/bitwarden-fido/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ uniffi = ["dep:uniffi", "bitwarden-core/uniffi", "bitwarden-vault/uniffi"]

[dependencies]
async-trait = { workspace = true }
base64 = ">=0.22.1, <0.23"
bitwarden-core = { workspace = true }
bitwarden-crypto = { workspace = true }
bitwarden-encoding = { workspace = true }
bitwarden-vault = { workspace = true }
chrono = { workspace = true }
coset = ">=0.3.7, <0.4"
Expand Down
Loading
Loading