Skip to content

Commit

Permalink
Update credential exchange crate
Browse files Browse the repository at this point in the history
  • Loading branch information
Hinton committed Dec 9, 2024
1 parent cc68ca9 commit dce7056
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 53 deletions.
2 changes: 1 addition & 1 deletion 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-exporters/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ bitwarden-crypto = { workspace = true }
bitwarden-fido = { workspace = true }
bitwarden-vault = { workspace = true }
chrono = { workspace = true, features = ["std"] }
credential-exchange-types = { git = "https://github.com/bitwarden/credential-exchange.git", rev = "28c947a776f727334598e4ef070292204d4e71d4" }
credential-exchange-types = { git = "https://github.com/bitwarden/credential-exchange.git", rev = "60bf99f097af72144b0eaa757ccb50fd46049f24" }
csv = "1.3.0"
schemars = { workspace = true }
serde = { workspace = true }
Expand Down
58 changes: 21 additions & 37 deletions crates/bitwarden-exporters/src/cxp/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use bitwarden_vault::{Totp, TotpAlgorithm};
use credential_exchange_types::{
format::{
Account as CxpAccount, BasicAuthCredential, Credential, EditableField, FieldType, Item,
ItemType, OTPHashAlgorithm, PasskeyCredential,
ItemType, NoteCredential, OTPHashAlgorithm, PasskeyCredential, TotpCredential,
},
B64Url,
};
Expand Down Expand Up @@ -54,13 +54,13 @@ impl TryFrom<Cipher> for Item {
let mut credentials: Vec<Credential> = value.r#type.clone().into();

if let Some(note) = value.notes {
credentials.push(Credential::Note { content: note });
credentials.push(Credential::Note(Box::new(NoteCredential { content: note })));
}

Ok(Self {
id: value.id.as_bytes().as_slice().into(),
creation_at: value.creation_date.timestamp() as u64,
modified_at: value.revision_date.timestamp() as u64,
creation_at: Some(value.creation_date.timestamp() as u64),
modified_at: Some(value.revision_date.timestamp() as u64),
ty: value.r#type.try_into()?,
title: value.name,
subtitle: None,
Expand Down Expand Up @@ -97,7 +97,8 @@ impl From<CipherType> for Vec<Credential> {
CipherType::Card(_) => vec![],
// TODO(PM-15451): Add support for identities.
CipherType::Identity(_) => vec![],
// Secure Notes only contains a note field which is handled by `TryFrom<Cipher> for Item`.
// Secure Notes only contains a note field which is handled by `TryFrom<Cipher> for
// Item`.
CipherType::SecureNote(_) => vec![],
// TODO(PM-15448): Add support for SSH Keys.
CipherType::SshKey(_) => vec![],
Expand All @@ -110,13 +111,13 @@ impl From<Login> for Vec<Credential> {
let mut credentials = vec![];

if login.username.is_some() || login.password.is_some() || !login.login_uris.is_empty() {
credentials.push(Credential::BasicAuth(login.clone().into()));
credentials.push(Credential::BasicAuth(Box::new(login.clone().into())));
}

if let Some(totp) = login.totp {
if let Ok(totp) = totp.parse::<Totp>() {
// TODO(PM-15389): Properly set username/issuer.
credentials.push(Credential::Totp {
credentials.push(Credential::Totp(Box::new(TotpCredential {
secret: totp.secret.into(),
period: totp.period as u8,
digits: totp.digits as u8,
Expand All @@ -128,15 +129,15 @@ impl From<Login> for Vec<Credential> {
TotpAlgorithm::Steam => OTPHashAlgorithm::Unknown("steam".to_string()),
},
issuer: None,
})
})))
}
}

if let Some(fido2_credentials) = login.fido2_credentials {
for fido2_credential in fido2_credentials {
let c = fido2_credential.try_into();
if let Ok(c) = c {
credentials.push(Credential::Passkey(c))
credentials.push(Credential::Passkey(Box::new(c)))
}
}
}
Expand Down Expand Up @@ -217,8 +218,6 @@ impl TryFrom<Fido2Credential> for PasskeyCredential {

#[cfg(test)]
mod tests {
use chrono::{DateTime, Utc};

use super::*;
use crate::{Field, LoginUri};

Expand Down Expand Up @@ -299,17 +298,9 @@ mod tests {

let item: Item = cipher.try_into().unwrap();

assert_eq!(
item.creation_at,
"2024-01-30T11:23:54.416Z"
.parse::<DateTime<Utc>>()
.unwrap()
.timestamp() as u64
);

assert_eq!(item.id.to_string(), "JcjEFLRGSOmhvbEHALvXQA");
assert_eq!(item.creation_at, 1706613834);
assert_eq!(item.modified_at, 1706623773);
assert_eq!(item.creation_at, Some(1706613834));
assert_eq!(item.modified_at, Some(1706623773));
assert_eq!(item.ty, ItemType::Login);
assert_eq!(item.title, "Bitwarden");
assert_eq!(item.subtitle, None);
Expand Down Expand Up @@ -343,20 +334,13 @@ mod tests {
let credential = &item.credentials[1];

match credential {
Credential::Totp {
secret,
period,
digits,
username,
algorithm,
issuer,
} => {
assert_eq!(String::from(secret.clone()), "JBSWY3DPEHPK3PXP");
assert_eq!(*period, 30);
assert_eq!(*digits, 6);
assert_eq!(username, "");
assert_eq!(*algorithm, OTPHashAlgorithm::Sha1);
assert!(issuer.is_none());
Credential::Totp(totp) => {
assert_eq!(String::from(totp.secret.clone()), "JBSWY3DPEHPK3PXP");
assert_eq!(totp.period, 30);
assert_eq!(totp.digits, 6);
assert_eq!(totp.username, "");
assert_eq!(totp.algorithm, OTPHashAlgorithm::Sha1);
assert!(totp.issuer.is_none());
}
_ => panic!("Expected Credential::Passkey"),
}
Expand All @@ -379,8 +363,8 @@ mod tests {
let credential = &item.credentials[3];

match credential {
Credential::Note { content } => {
assert_eq!(content, "My note");
Credential::Note(n) => {
assert_eq!(n.content, "My note");
}
_ => panic!("Expected Credential::Passkey"),
}
Expand Down
34 changes: 20 additions & 14 deletions crates/bitwarden-exporters/src/cxp/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ pub(crate) fn parse_cxf(payload: String) -> Result<Vec<ImportingCipher>, CxpErro
fn parse_item(value: Item) -> Vec<ImportingCipher> {
let grouped = group_credentials_by_type(value.credentials);

let creation_date = value
.creation_at
.and_then(|ts| DateTime::from_timestamp(ts as i64, 0))
.unwrap_or(Utc::now());
let revision_date = value
.modified_at
.and_then(|ts| DateTime::from_timestamp(ts as i64, 0))
.unwrap_or(Utc::now());

match value.ty {
ItemType::Login => {
let basic_auth = grouped.basic_auth.first();
Expand Down Expand Up @@ -51,8 +60,7 @@ fn parse_item(value: Item) -> Vec<ImportingCipher> {
rp_name: Some(p.rp_id.clone()),
user_display_name: Some(p.user_display_name.clone()),
discoverable: "true".to_string(),
creation_date: DateTime::from_timestamp(value.creation_at as i64, 0)
.unwrap_or(Utc::now()),
creation_date,
}]
}),
};
Expand All @@ -65,10 +73,8 @@ fn parse_item(value: Item) -> Vec<ImportingCipher> {
favorite: false,
reprompt: 0,
fields: vec![],
revision_date: DateTime::from_timestamp(value.modified_at as i64, 0)
.unwrap_or(Utc::now()),
creation_date: DateTime::from_timestamp(value.creation_at as i64, 0)
.unwrap_or(Utc::now()),
revision_date,
creation_date,
deleted_date: None,
}]
}
Expand All @@ -81,14 +87,14 @@ fn group_credentials_by_type(credentials: Vec<Credential>) -> GroupedCredentials
basic_auth: credentials
.iter()
.filter_map(|c| match c {
Credential::BasicAuth(basic_auth) => Some(basic_auth.clone()),
Credential::BasicAuth(basic_auth) => Some(*basic_auth.clone()),
_ => None,
})
.collect(),
passkey: credentials
.iter()
.filter_map(|c| match c {
Credential::Passkey(passkey) => Some(passkey.clone()),
Credential::Passkey(passkey) => Some(*passkey.clone()),
_ => None,
})
.collect(),
Expand All @@ -108,8 +114,8 @@ mod tests {
fn test_parse_item() {
let item = Item {
id: [0, 1, 2, 3, 4, 5, 6].as_ref().into(),
creation_at: 1706613834,
modified_at: 1706623773,
creation_at: Some(1706613834),
modified_at: Some(1706623773),
ty: ItemType::Login,
title: "Bitwarden".to_string(),
subtitle: None,
Expand All @@ -135,13 +141,13 @@ mod tests {
.unwrap()
.as_slice()
.into(),
creation_at: 1732181986,
modified_at: 1732182026,
creation_at: Some(1732181986),
modified_at: Some(1732182026),
ty: ItemType::Login,
title: "opotonniee.github.io".to_string(),
subtitle: None,
favorite: None,
credentials: vec![Credential::Passkey(PasskeyCredential {
credentials: vec![Credential::Passkey(Box::new(PasskeyCredential {
credential_id: URL_SAFE_NO_PAD
.decode("6NiHiekW4ZY8vYHa-ucbvA")
.unwrap()
Expand All @@ -161,7 +167,7 @@ mod tests {
.as_slice()
.into(),
fido2_extensions: None,
})],
}))],
tags: None,
extensions: None,
};
Expand Down

0 comments on commit dce7056

Please sign in to comment.