Skip to content

Commit 97cdad2

Browse files
authored
[PM-23653] Add support for ssh keys (#375)
## ๐ŸŽŸ๏ธ Tracking <!-- Paste the link to the Jira or GitHub issue or otherwise describe / point to where this change is coming from. --> https://bitwarden.atlassian.net/browse/PM-23653 ## ๐Ÿ“” Objective <!-- Describe what the purpose of this PR is, for example what bug you're fixing or new feature you're adding. --> Adds support for importing ssh keys using CXF. ## โฐ Reminders before review - Contributor guidelines followed - All formatters and local linters executed and passed - Written new unit and / or integration tests where applicable - Protected functional changes with optionality (feature flags) - Used internationalization (i18n) for all UI strings - CI builds passed - Communicated to DevOps any deployment requirements - Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team ## ๐Ÿฆฎ Reviewer guidelines <!-- Suggested interactions but feel free to use (or not) as you desire! --> - ๐Ÿ‘ (`:+1:`) or similar for great changes - ๐Ÿ“ (`:memo:`) or โ„น๏ธ (`:information_source:`) for notes or general info - โ“ (`:question:`) for questions - ๐Ÿค” (`:thinking:`) or ๐Ÿ’ญ (`:thought_balloon:`) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion - ๐ŸŽจ (`:art:`) for suggestions / improvements - โŒ (`:x:`) or โš ๏ธ (`:warning:`) for more significant problems or concerns needing attention - ๐ŸŒฑ (`:seedling:`) or โ™ป๏ธ (`:recycle:`) for future improvements or indications of technical debt - โ› (`:pick:`) for minor or nitpick changes
1 parent 04fad91 commit 97cdad2

File tree

6 files changed

+139
-11
lines changed

6 files changed

+139
-11
lines changed

โ€ŽCargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

โ€Žcrates/bitwarden-exporters/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ bitwarden-core = { workspace = true }
3030
bitwarden-crypto = { workspace = true }
3131
bitwarden-error = { workspace = true }
3232
bitwarden-fido = { workspace = true }
33+
bitwarden-ssh = { workspace = true }
3334
bitwarden-vault = { workspace = true }
3435
chrono = { workspace = true, features = ["std"] }
3536
credential-exchange-format = { git = "https://github.com/bitwarden/credential-exchange", rev = "38e8a013c13644f832c457555baaa536fe481b77" }

โ€Žcrates/bitwarden-exporters/src/cxf/import.rs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use chrono::{DateTime, Utc};
22
use credential_exchange_format::{
33
Account as CxfAccount, AddressCredential, ApiKeyCredential, BasicAuthCredential, Credential,
44
CreditCardCredential, DriversLicenseCredential, IdentityDocumentCredential, Item,
5-
NoteCredential, PasskeyCredential, PassportCredential, PersonNameCredential, TotpCredential,
6-
WifiCredential,
5+
NoteCredential, PasskeyCredential, PassportCredential, PersonNameCredential, SshKeyCredential,
6+
TotpCredential, WifiCredential,
77
};
88

99
use crate::{
@@ -16,6 +16,7 @@ use crate::{
1616
},
1717
login::to_login,
1818
note::extract_note_content,
19+
ssh::to_ssh,
1920
wifi::wifi_to_fields,
2021
CxfError,
2122
},
@@ -126,6 +127,16 @@ pub(super) fn parse_item(value: Item) -> Vec<ImportingCipher> {
126127
add_item(CipherType::Identity(Box::new(identity)), custom_fields);
127128
});
128129

130+
// SSH Key credentials
131+
if let Some(ssh) = grouped.ssh.first() {
132+
match to_ssh(ssh) {
133+
Ok((ssh_key, fields)) => add_item(CipherType::SshKey(Box::new(ssh_key)), fields),
134+
Err(_) => {
135+
// Include information about the failed items, or import as note?
136+
}
137+
}
138+
}
139+
129140
// Standalone Note credentials -> Secure Note (only if no other credentials exist)
130141
if !grouped.note.is_empty() && output.is_empty() {
131142
let standalone_note_content = grouped.note.first().map(extract_note_content);
@@ -172,12 +183,16 @@ fn group_credentials_by_type(credentials: Vec<Credential>) -> GroupedCredentials
172183
Credential::BasicAuth(basic_auth) => Some(basic_auth.as_ref()),
173184
_ => None,
174185
}),
186+
credit_card: filter_credentials(&credentials, |c| match c {
187+
Credential::CreditCard(credit_card) => Some(credit_card.as_ref()),
188+
_ => None,
189+
}),
175190
passkey: filter_credentials(&credentials, |c| match c {
176191
Credential::Passkey(passkey) => Some(passkey.as_ref()),
177192
_ => None,
178193
}),
179-
credit_card: filter_credentials(&credentials, |c| match c {
180-
Credential::CreditCard(credit_card) => Some(credit_card.as_ref()),
194+
ssh: filter_credentials(&credentials, |c| match c {
195+
Credential::SshKey(ssh) => Some(ssh.as_ref()),
181196
_ => None,
182197
}),
183198
totp: filter_credentials(&credentials, |c| match c {
@@ -216,18 +231,19 @@ fn group_credentials_by_type(credentials: Vec<Credential>) -> GroupedCredentials
216231
}
217232

218233
struct GroupedCredentials {
234+
address: Vec<AddressCredential>,
219235
api_key: Vec<ApiKeyCredential>,
220236
basic_auth: Vec<BasicAuthCredential>,
221-
passkey: Vec<PasskeyCredential>,
222237
credit_card: Vec<CreditCardCredential>,
223-
totp: Vec<TotpCredential>,
224-
wifi: Vec<WifiCredential>,
225-
address: Vec<AddressCredential>,
226-
passport: Vec<PassportCredential>,
227-
person_name: Vec<PersonNameCredential>,
228238
drivers_license: Vec<DriversLicenseCredential>,
229239
identity_document: Vec<IdentityDocumentCredential>,
230240
note: Vec<NoteCredential>,
241+
passkey: Vec<PasskeyCredential>,
242+
passport: Vec<PassportCredential>,
243+
person_name: Vec<PersonNameCredential>,
244+
ssh: Vec<SshKeyCredential>,
245+
totp: Vec<TotpCredential>,
246+
wifi: Vec<WifiCredential>,
231247
}
232248

233249
#[cfg(test)]

โ€Žcrates/bitwarden-exporters/src/cxf/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ mod identity;
2020
mod import_sample_tests;
2121
mod login;
2222
mod note;
23+
mod ssh;
2324
mod wifi;
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use bitwarden_ssh::{error::SshKeyImportError, import::import_pkcs8_der};
2+
use bitwarden_vault::FieldType;
3+
use credential_exchange_format::SshKeyCredential;
4+
5+
use crate::{cxf::editable_field::create_field, Field, SshKey};
6+
7+
/// Convert SSH key credentials to SshKey and custom fields
8+
pub(super) fn to_ssh(
9+
credential: &SshKeyCredential,
10+
) -> Result<(SshKey, Vec<Field>), SshKeyImportError> {
11+
// Convert to OpenSSH format
12+
let encoded_key: Vec<u8> = credential.private_key.as_ref().into();
13+
let encoded_key = import_pkcs8_der(&encoded_key)?;
14+
15+
let ssh = SshKey {
16+
private_key: encoded_key.private_key,
17+
public_key: encoded_key.public_key,
18+
fingerprint: encoded_key.fingerprint,
19+
};
20+
21+
let fields = [
22+
credential.key_comment.as_ref().map(|comment| Field {
23+
name: Some("Key Comment".into()),
24+
value: Some(comment.into()),
25+
r#type: FieldType::Text as u8,
26+
linked_id: None,
27+
}),
28+
credential
29+
.creation_date
30+
.as_ref()
31+
.map(|date| create_field("Creation Date", date)),
32+
credential
33+
.expiry_date
34+
.as_ref()
35+
.map(|date| create_field("Expiry Date", date)),
36+
credential
37+
.key_generation_source
38+
.as_ref()
39+
.map(|source| create_field("Key Generation Source", source)),
40+
]
41+
.into_iter()
42+
.flatten()
43+
.collect();
44+
45+
Ok((ssh, fields))
46+
}
47+
48+
#[cfg(test)]
49+
mod tests {
50+
use bitwarden_vault::FieldType;
51+
use chrono::NaiveDate;
52+
use credential_exchange_format::EditableFieldDate;
53+
54+
use super::*;
55+
56+
#[test]
57+
fn test_to_ssh() {
58+
let credential = SshKeyCredential {
59+
key_type: "ssh-ed25519".into(),
60+
private_key: "MIIG_QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQCn4-QiJojZ9mgc9KYJIvDWGaz4qFhf0CButg6L8zEoHKwuiN-mqcEciCCOa9BNiJmm8NTTehZvrrglGG59zIbqYtDAHjVn-vtb49xPzIv-M651Yqj08lIbR9tEIHKCq7aH8GlDm8NgG9EzJGjlL7okQym4TH1MHl-s4mUyr_qb2unlZBDixAQsphU8iCLftukWCIkmQg4CSj1Gh3WbBlZ-EX5eW0EXuAw4XsSbBTWV9CHRowVIpYqPvEYSpHsoCjEcd988p19hpiGknA0J4z7JfUlNgyT_1chb8GCTDT-2DCBRApbsIg6TOBVS-PR6emAQ3eZzUW0-3_oRM4ip0ujltQy8uU6gvYIAqx5wXGMThVpZcUgahKiSsVo_s4b84iMe4DG3W8jz4qi6yyNv0VedEzPUZ1lXd1GJFoy9uKNuSTe-1ksicAcluZN6LuNsPHcPxFCzOcmoNnVXEKAXInt-ys__5CDVasroZSAHZnDjUD4oNsLI3VIOnGxgXrkwSH0CAwEAAQKCAYAA2SDMf7OBHw1OGM9OQa1ZS4u-ktfQHhn31-FxbrhWGp-lDt8gYABVf6Y4dKN6rMtn7D9gVSAlZCAn3Hx8aWAvcXHaspxe9YXiZDTh-Kd8EIXxBQn-TiDA5LH0dryABqmMp20vYKtR7OS3lIIXfFBSrBMwdunKzLwmKwZLWq0SWf6vVbwpxRyR9CyByodF6DjmZK3QB2qQ3jqlL1HWXL0VnyArY7HLvUvfLLK4vMPqnsSH-FdHvhcEhwqMlWT44g-fhqWtCJNnjDgLK3FPbI8Pz9TF8dWJvOmp5Q6iSBua1e9x2LizVuNSqiFc7ZTLeoG4nDj7T2BtqB0E1rNUDEN1aBo-UZmHJK7LrzfW_B-ssi2WwIpfxYa1lO6HFod5_YQiXV1GunyH1chCsbvOFtXvAHASO4HTKlJNbWhRF1GXqnKpAaHDPCVuwp3eq6Yf0oLbXrL3KFZ3jwWiWbpQXRVvpqzaJwZn3CN1yQgYS9j17a9wrPky-BoJxXjZ_oImWLECgcEA0lkLwiHvmTYFTCC7PN938Agk9_NQs5PQ18MRn9OJmyfSpYqf_gNp-Md7xUgtF_MTif7uelp2J7DYf6fj9EYf9g4EuW-SQgFP4pfiJn1-zGFeTQq1ISvwjsA4E8ZSt-GIumjZTg6YiL1_A79u4wm24swt7iqnVViOPtPGOM34S1tAamjZzq2eZDmAF6pAfmuTMdinCMR1E1kNJYbxeqLiqQCXuwBBnHOOOJofN3AkvzjRUBB9udvniqYxH3PQcxPxAoHBAMxT5KwBhZhnJedYN87Kkcpl7xdMkpU8b-aXeZoNykCeoC-wgIQexnSWmFk4HPkCNxvCWlbkOT1MHrTAKFnaOww23Ob-Vi6A9n0rozo9vtoJig114GB0gUqEmtfLhO1P5AE8yzogE-ILHyp0BqXt8vGIfzpDnCkN-GKl8gOOMPrR4NAcLO-Rshc5nLs7BGB4SEi126Y6mSfp85m0--1QhWMz9HzqJEHCWKVcZYdCdEONP9js04EUnK33KtlJIWzZTQKBwAT0pBpGwmZRp35Lpx2gBitZhcVxrg0NBnaO2fNyAGPvZD8SLQLHAdAiov_a23Uc_PDbWLL5Pp9gwzj-s5glrssVOXdE8aUscr1b5rARdNNL1_Tos6u8ZUZ3sNqGaZx7a8U4gyYboexWyo9EC1C-AdkGBm7-AkM4euFwC9N6xsa_t5zKK5d676hc0m-8SxivYCBkgkrqlfeGuZCQxU-mVsC0it6U-va8ojUjLGkZ80OuCwBf4xZl3-acU7vx9o8_gQKBwB7BrhU6MWrsc-cr_1KQaXum9mNyckomi82RFYvb8Yrilcg38FBy9XqNRKeBa9MLw1HZYpHbzsXsVF7u4eQMloDTLVNUC5L6dKAI1owoyTa24uH90WWTg_a8mTZMe1jhgrew-AJq27NV6z4PswR9GenDmyshDDudz7rBsflZCQRoXUfWRelV7BHU6UPBsXn4ASF4xnRyM6WvcKy9coKZcUqqgm3fLM_9OizCCMJgfXHBrE-x7nBqst746qlEedSRrQKBwQCVYwwKCHNlZxl0_NMkDJ-hp7_InHF6mz_3VO58iCb19TLDVUC2dDGPXNYwWTT9PclefwV5HNBHcAfTzgB4dpQyNiDyV914HL7DFEGduoPnwBYjeFre54v0YjjnskjJO7myircdbdX__i-7LMUw5aZZXCC8a5BD_rdV6IKJWJG5QBXbe5fVf1XwOjBTzlhIPIqhNFfSu-mFikp5BRwHGBqsKMju6inYmW6YADeY_SvOQjDEB37RqGZxqyIx8V2ZYwU"
61+
.try_into()
62+
.unwrap(),
63+
key_comment: Some("Work SSH Key".into()),
64+
creation_date: Some(
65+
EditableFieldDate(NaiveDate::from_ymd_opt(2023, 1, 1).unwrap()).into(),
66+
),
67+
expiry_date: Some(
68+
EditableFieldDate(NaiveDate::from_ymd_opt(2025, 1, 1).unwrap()).into(),
69+
),
70+
key_generation_source: Some("Generated using OpenSSH".to_owned().into()),
71+
};
72+
73+
let (ssh, fields) = to_ssh(&credential).unwrap();
74+
75+
assert_eq!(ssh.private_key, "-----BEGIN OPENSSH PRIVATE KEY-----\n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\nScrfcq2UkhbNlNAAAAAAEC\n-----END OPENSSH PRIVATE KEY-----\n");
76+
assert_eq!(
77+
ssh.public_key,
78+
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCn4+QiJojZ9mgc9KYJIvDWGaz4qFhf0CButg6L8zEoHKwuiN+mqcEciCCOa9BNiJmm8NTTehZvrrglGG59zIbqYtDAHjVn+vtb49xPzIv+M651Yqj08lIbR9tEIHKCq7aH8GlDm8NgG9EzJGjlL7okQym4TH1MHl+s4mUyr/qb2unlZBDixAQsphU8iCLftukWCIkmQg4CSj1Gh3WbBlZ+EX5eW0EXuAw4XsSbBTWV9CHRowVIpYqPvEYSpHsoCjEcd988p19hpiGknA0J4z7JfUlNgyT/1chb8GCTDT+2DCBRApbsIg6TOBVS+PR6emAQ3eZzUW0+3/oRM4ip0ujltQy8uU6gvYIAqx5wXGMThVpZcUgahKiSsVo/s4b84iMe4DG3W8jz4qi6yyNv0VedEzPUZ1lXd1GJFoy9uKNuSTe+1ksicAcluZN6LuNsPHcPxFCzOcmoNnVXEKAXInt+ys//5CDVasroZSAHZnDjUD4oNsLI3VIOnGxgXrkwSH0="
79+
);
80+
assert_eq!(
81+
ssh.fingerprint,
82+
"SHA256:vWqZh87vgxDk0eDx0VqWR001mXyFGTdRF4Q2JVW/Q9w"
83+
);
84+
85+
assert_eq!(fields.len(), 4);
86+
assert_eq!(
87+
fields[0],
88+
Field {
89+
name: Some("Key Comment".to_string()),
90+
value: Some("Work SSH Key".to_string()),
91+
r#type: FieldType::Text as u8,
92+
linked_id: None,
93+
}
94+
);
95+
assert_eq!(fields[1].value.as_deref(), Some("2023-01-01"));
96+
assert_eq!(fields[2].value.as_deref(), Some("2025-01-01"));
97+
assert_eq!(fields[3].value.as_deref(), Some("Generated using OpenSSH"));
98+
}
99+
}

โ€Žcrates/bitwarden-ssh/src/import.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ pub fn import_key(
3232
}
3333
}
3434

35+
/// Import a DER encoded private key, and returns a decoded [SshKeyView]. This is primarily used for
36+
/// importing SSH keys from other Credential Managers through Credential Exchange.
37+
pub fn import_pkcs8_der(encoded_key: &[u8]) -> Result<SshKeyView, SshKeyImportError> {
38+
import_der_key(encoded_key)
39+
}
40+
3541
fn import_pkcs8_key(
3642
encoded_key: String,
3743
password: Option<String>,
@@ -49,8 +55,12 @@ fn import_pkcs8_key(
4955
SecretDocument::from_pkcs8_pem(&encoded_key).map_err(|_| SshKeyImportError::ParsingError)?
5056
};
5157

58+
import_der_key(doc.as_bytes())
59+
}
60+
61+
fn import_der_key(encoded_key: &[u8]) -> Result<SshKeyView, SshKeyImportError> {
5262
let private_key_info =
53-
PrivateKeyInfo::from_der(doc.as_bytes()).map_err(|_| SshKeyImportError::ParsingError)?;
63+
PrivateKeyInfo::from_der(encoded_key).map_err(|_| SshKeyImportError::ParsingError)?;
5464

5565
let private_key = match private_key_info.algorithm.oid {
5666
ed25519::pkcs8::ALGORITHM_OID => {

0 commit comments

Comments
ย (0)