Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
070349c
Added cxf_example.json
abergs Jul 25, 2025
6f91f2d
removed serde
abergs Jul 25, 2025
18a3e29
Merge branch 'main' into cxf/sample
abergs Aug 12, 2025
88a4ba7
correct private key value
abergs Aug 12, 2025
b476b7d
Added Header
abergs Aug 12, 2025
44922d8
removed account import
abergs Aug 12, 2025
fb5537b
Use latest cxf library version
abergs Aug 12, 2025
101ec68
Parse apple and parse cxf
abergs Aug 12, 2025
71845ea
Moved parse cxf
abergs Aug 12, 2025
b447a6c
Update Cargo.toml
abergs Aug 12, 2025
4153278
Moved parse_cxf_spec back
abergs Aug 12, 2025
acea084
PM-23649: CXF Import note (#382)
abergs Aug 14, 2025
57f3dc5
PM-23648: All Identities (passport, driving-license, id-doc, name) ->โ€ฆ
abergs Aug 14, 2025
2336543
PM-23654: Map CXF totp (#381)
abergs Aug 14, 2025
8ffdc1a
Move the tests
abergs Aug 14, 2025
570cd17
Moved parse_cxf_spec into tests
abergs Aug 14, 2025
965bf1e
pub(super)
abergs Aug 14, 2025
c6aa4e4
Move totp into login.rs
abergs Aug 14, 2025
56a752c
tidbits
abergs Aug 14, 2025
30aae71
Improved notes
abergs Aug 14, 2025
34edcb2
Re-use add_item
abergs Aug 14, 2025
5df36a3
Clean up
abergs Aug 14, 2025
1d92a3d
fmt
abergs Aug 14, 2025
b6ce873
nightly fmt
abergs Aug 14, 2025
2dbe813
moved cxf sample tests to their own file
abergs Aug 15, 2025
f9b8a2a
Update import_sample_tests.rs
abergs Aug 15, 2025
8f73166
Added split name unit tests
abergs Aug 15, 2025
4d1f8f5
Refactor to use convert_otp_algorithm and test it
abergs Aug 15, 2025
04fad91
[PM-23645] Update cards to support pin and valid_from (#374)
Hinton Aug 15, 2025
97cdad2
[PM-23653] Add support for ssh keys (#375)
Hinton Aug 15, 2025
1f1162c
Clean up tests
Hinton Aug 18, 2025
0e551c9
fmt
Hinton Aug 18, 2025
ab4001f
Update crates/bitwarden-exporters/src/cxf/identity.rs
Hinton Aug 18, 2025
8159c66
Rename ssh
Hinton Aug 18, 2025
bf4d43d
Update to expect owned
Hinton Aug 18, 2025
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
6 changes: 3 additions & 3 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion crates/bitwarden-exporters/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ bitwarden-core = { workspace = true }
bitwarden-crypto = { workspace = true }
bitwarden-error = { workspace = true }
bitwarden-fido = { workspace = true }
bitwarden-ssh = { workspace = true }
bitwarden-vault = { workspace = true }
chrono = { workspace = true, features = ["std"] }
credential-exchange-format = ">=0.1, <0.2"
credential-exchange-format = { git = "https://github.com/bitwarden/credential-exchange", rev = "38e8a013c13644f832c457555baaa536fe481b77" }
csv = "1.3.0"
num-traits = ">=0.2, <0.3"
serde = { workspace = true }
Expand Down
599 changes: 599 additions & 0 deletions crates/bitwarden-exporters/resources/cxf_example.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion crates/bitwarden-exporters/src/cxf/api_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use credential_exchange_format::ApiKeyCredential;
use crate::{cxf::editable_field::create_field, Field};

/// Convert API key credentials to custom fields
pub fn api_key_to_fields(api_key: &ApiKeyCredential) -> Vec<Field> {
pub(super) fn api_key_to_fields(api_key: &ApiKeyCredential) -> Vec<Field> {
[
api_key.key.as_ref().map(|key| create_field("API Key", key)),
api_key
Expand Down
50 changes: 46 additions & 4 deletions crates/bitwarden-exporters/src/cxf/card.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use chrono::Month;
use credential_exchange_format::{Credential, CreditCardCredential, EditableFieldYearMonth};
use num_traits::FromPrimitive;

use crate::Card;
use crate::{cxf::editable_field::create_field, Card, Field};

impl From<Card> for Vec<Credential> {
fn from(value: Card) -> Self {
Expand Down Expand Up @@ -57,6 +57,23 @@ impl From<&CreditCardCredential> for Card {
}
}

pub(super) fn to_card(credential: &CreditCardCredential) -> (Card, Vec<Field>) {
let card = credential.into();

let fields = [
credential.pin.as_ref().map(|v| create_field("PIN", v)),
credential
.valid_from
.as_ref()
.map(|v| create_field("Valid From", v)),
]
.into_iter()
.flatten()
.collect();

(card, fields)
}

/// Sanitize credit card brand
///
/// Performs a fuzzy match on the string to find a matching brand. By converting to lowercase and
Expand All @@ -83,6 +100,7 @@ fn sanitize_brand(value: &str) -> Option<String> {

#[cfg(test)]
mod tests {
use bitwarden_vault::FieldType;
use chrono::Month;
use credential_exchange_format::EditableFieldYearMonth;

Expand Down Expand Up @@ -150,23 +168,47 @@ mod tests {
full_name: Some("John Doe".to_string().into()),
card_type: Some("Visa".to_string().into()),
verification_number: Some("123".to_string().into()),
pin: None,
pin: Some("4567".to_string().into()),
expiry_date: Some(
EditableFieldYearMonth {
year: 2025,
month: Month::December,
}
.into(),
),
valid_from: None,
valid_from: Some(
EditableFieldYearMonth {
year: 2024,
month: Month::January,
}
.into(),
),
};

let card: Card = (&credit_card).into();
let (card, fields) = to_card(&credit_card);
assert_eq!(card.cardholder_name, Some("John Doe".to_string()));
assert_eq!(card.exp_month, Some("12".to_string()));
assert_eq!(card.exp_year, Some("2025".to_string()));
assert_eq!(card.code, Some("123".to_string()));
assert_eq!(card.brand, Some("Visa".to_string()));
assert_eq!(card.number, Some("4111111111111111".to_string()));

assert_eq!(
fields,
vec![
Field {
name: Some("PIN".to_string()),
value: Some("4567".to_string()),
r#type: FieldType::Hidden as u8,
linked_id: None,
},
Field {
name: Some("Valid From".to_string()),
value: Some("2024-01".to_string()),
r#type: FieldType::Text as u8,
linked_id: None,
},
]
)
}
}
53 changes: 51 additions & 2 deletions crates/bitwarden-exporters/src/cxf/editable_field.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use bitwarden_vault::FieldType;
use credential_exchange_format::{
EditableField, EditableFieldBoolean, EditableFieldConcealedString, EditableFieldDate,
EditableFieldString, EditableFieldWifiNetworkSecurityType,
EditableField, EditableFieldBoolean, EditableFieldConcealedString, EditableFieldCountryCode,
EditableFieldDate, EditableFieldString, EditableFieldWifiNetworkSecurityType,
EditableFieldYearMonth,
};

use crate::Field;
Expand Down Expand Up @@ -58,6 +59,14 @@ impl EditableFieldToField for EditableField<EditableFieldWifiNetworkSecurityType
}
}

impl EditableFieldToField for EditableField<EditableFieldCountryCode> {
const FIELD_TYPE: FieldType = FieldType::Text;

fn field_value(&self) -> String {
self.value.0.clone()
}
}

impl EditableFieldToField for EditableField<EditableFieldDate> {
const FIELD_TYPE: FieldType = FieldType::Text;

Expand All @@ -66,6 +75,18 @@ impl EditableFieldToField for EditableField<EditableFieldDate> {
}
}

impl EditableFieldToField for EditableField<EditableFieldYearMonth> {
const FIELD_TYPE: FieldType = FieldType::Text;

fn field_value(&self) -> String {
format!(
"{:04}-{:02}",
self.value.year,
self.value.month.number_from_month()
)
}
}

/// Convert WiFi security type enum to human-readable string
fn security_type_to_string(security_type: &EditableFieldWifiNetworkSecurityType) -> &str {
use EditableFieldWifiNetworkSecurityType::*;
Expand All @@ -76,6 +97,7 @@ fn security_type_to_string(security_type: &EditableFieldWifiNetworkSecurityType)
Wpa3Personal => "WPA3 Personal",
Wep => "WEP",
Other(s) => s,
_ => "Unknown",
}
}

Expand Down Expand Up @@ -248,4 +270,31 @@ mod tests {
}
);
}

#[test]
fn test_create_field_year_month() {
use chrono::Month;

let editable_field = EditableField {
id: None,
label: None,
value: EditableFieldYearMonth {
year: 2025,
month: Month::December,
},
extensions: None,
};

let field = create_field("Card Expiry", &editable_field);

assert_eq!(
field,
Field {
name: Some("Card Expiry".to_string()),
value: Some("2025-12".to_string()),
r#type: FieldType::Text as u8,
linked_id: None,
}
);
}
}
Loading
Loading