From 5c7a5aad80be1d082fb4e8a097b7b5586f02de0a Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 11 Sep 2024 11:34:42 +0200 Subject: [PATCH 01/10] Add ssh-key item type --- crates/bitwarden-exporters/src/json.rs | 37 +++++++++++++--- crates/bitwarden-exporters/src/lib.rs | 8 ++++ crates/bitwarden-exporters/src/models.rs | 9 ++++ .../bitwarden-vault/src/cipher/attachment.rs | 3 ++ crates/bitwarden-vault/src/cipher/cipher.rs | 35 ++++++++++++++- crates/bitwarden-vault/src/cipher/mod.rs | 1 + crates/bitwarden-vault/src/cipher/ssh_key.rs | 43 +++++++++++++++++++ .../src/mobile/client_ciphers.rs | 2 + 8 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 crates/bitwarden-vault/src/cipher/ssh_key.rs diff --git a/crates/bitwarden-exporters/src/json.rs b/crates/bitwarden-exporters/src/json.rs index 3f6c72c1f..4c778c9cc 100644 --- a/crates/bitwarden-exporters/src/json.rs +++ b/crates/bitwarden-exporters/src/json.rs @@ -2,7 +2,9 @@ use chrono::{DateTime, Utc}; use thiserror::Error; use uuid::Uuid; -use crate::{Card, Cipher, CipherType, Field, Folder, Identity, Login, LoginUri, SecureNote}; +use crate::{ + Card, Cipher, CipherType, Field, Folder, Identity, Login, LoginUri, SecureNote, SshKey, +}; #[derive(Error, Debug)] pub enum JsonError { @@ -69,6 +71,8 @@ struct JsonCipher { card: Option, #[serde(skip_serializing_if = "Option::is_none")] secure_note: Option, + #[serde(skip_serializing_if = "Option::is_none")] + ssh_key: Option, favorite: bool, reprompt: u8, @@ -206,6 +210,24 @@ impl From for JsonIdentity { } } +#[derive(serde::Serialize)] +#[serde(rename_all = "camelCase")] +struct JsonSshKey { + private_key: Option, + public_key: Option, + fingerprint: Option, +} + +impl From for JsonSshKey { + fn from(ssh_key: SshKey) -> Self { + JsonSshKey { + private_key: ssh_key.private_key, + public_key: ssh_key.public_key, + fingerprint: ssh_key.fingerprint, + } + } +} + #[derive(serde::Serialize)] #[serde(rename_all = "camelCase")] struct JsonField { @@ -233,13 +255,15 @@ impl From for JsonCipher { CipherType::SecureNote(_) => 2, CipherType::Card(_) => 3, CipherType::Identity(_) => 4, + CipherType::SshKey(_) => 5, }; - let (login, secure_note, card, identity) = match cipher.r#type { - CipherType::Login(l) => (Some((*l).into()), None, None, None), - CipherType::SecureNote(s) => (None, Some((*s).into()), None, None), - CipherType::Card(c) => (None, None, Some((*c).into()), None), - CipherType::Identity(i) => (None, None, None, Some((*i).into())), + let (login, secure_note, card, identity, ssh_key) = match cipher.r#type { + CipherType::Login(l) => (Some((*l).into()), None, None, None, None), + CipherType::SecureNote(s) => (None, Some((*s).into()), None, None, None), + CipherType::Card(c) => (None, None, Some((*c).into()), None, None), + CipherType::Identity(i) => (None, None, None, Some((*i).into()), None), + CipherType::SshKey(ssh) => (None, None, None, None, Some((*ssh).into())), }; JsonCipher { @@ -254,6 +278,7 @@ impl From for JsonCipher { identity, card, secure_note, + ssh_key, favorite: cipher.favorite, reprompt: cipher.reprompt, fields: cipher.fields.into_iter().map(|f| f.into()).collect(), diff --git a/crates/bitwarden-exporters/src/lib.rs b/crates/bitwarden-exporters/src/lib.rs index 75b0503e2..16d9e3974 100644 --- a/crates/bitwarden-exporters/src/lib.rs +++ b/crates/bitwarden-exporters/src/lib.rs @@ -70,6 +70,7 @@ pub enum CipherType { SecureNote(Box), Card(Box), Identity(Box), + SshKey(Box), } impl fmt::Display for CipherType { @@ -79,6 +80,7 @@ impl fmt::Display for CipherType { CipherType::SecureNote(_) => write!(f, "note"), CipherType::Card(_) => write!(f, "card"), CipherType::Identity(_) => write!(f, "identity"), + CipherType::SshKey(_) => write!(f, "ssh_key"), } } } @@ -132,3 +134,9 @@ pub struct Identity { pub passport_number: Option, pub license_number: Option, } + +pub struct SshKey { + pub private_key: Option, + pub public_key: Option, + pub fingerprint: Option, +} diff --git a/crates/bitwarden-exporters/src/models.rs b/crates/bitwarden-exporters/src/models.rs index 1d56e7233..1d2e80d93 100644 --- a/crates/bitwarden-exporters/src/models.rs +++ b/crates/bitwarden-exporters/src/models.rs @@ -74,6 +74,14 @@ impl TryFrom for crate::Cipher { license_number: i.license_number, })) } + CipherType::SshKey => { + let s = require!(value.ssh_key); + crate::CipherType::SshKey(Box::new(crate::SshKey { + private_key: s.private_key, + public_key: s.public_key, + fingerprint: s.fingerprint, + })) + } }; Ok(Self { @@ -172,6 +180,7 @@ mod tests { identity: None, card: None, secure_note: None, + ssh_key: None, favorite: false, reprompt: CipherRepromptType::None, organization_use_totp: true, diff --git a/crates/bitwarden-vault/src/cipher/attachment.rs b/crates/bitwarden-vault/src/cipher/attachment.rs index d1a058c01..bf589879c 100644 --- a/crates/bitwarden-vault/src/cipher/attachment.rs +++ b/crates/bitwarden-vault/src/cipher/attachment.rs @@ -208,6 +208,7 @@ mod tests { identity: None, card: None, secure_note: None, + ssh_key: None, favorite: false, reprompt: CipherRepromptType::None, organization_use_totp: false, @@ -258,6 +259,7 @@ mod tests { identity: None, card: None, secure_note: None, + ssh_key: None, favorite: false, reprompt: CipherRepromptType::None, organization_use_totp: false, @@ -312,6 +314,7 @@ mod tests { identity: None, card: None, secure_note: None, + ssh_key: None, favorite: false, reprompt: CipherRepromptType::None, organization_use_totp: false, diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index 8ac4bfe79..716004c36 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -14,7 +14,7 @@ use uuid::Uuid; use super::{ attachment, card, field, identity, local_data::{LocalData, LocalDataView}, - secure_note, + secure_note, ssh_key, }; use crate::{ password_history, Fido2CredentialFullView, Fido2CredentialView, Login, LoginView, @@ -41,6 +41,7 @@ pub enum CipherType { SecureNote = 2, Card = 3, Identity = 4, + SshKey = 5, } #[derive(Clone, Copy, Serialize_repr, Deserialize_repr, Debug, JsonSchema, PartialEq)] @@ -72,6 +73,7 @@ pub struct Cipher { pub identity: Option, pub card: Option, pub secure_note: Option, + pub ssh_key: Option, pub favorite: bool, pub reprompt: CipherRepromptType, @@ -109,6 +111,7 @@ pub struct CipherView { pub identity: Option, pub card: Option, pub secure_note: Option, + pub ssh_key: Option, pub favorite: bool, pub reprompt: CipherRepromptType, @@ -137,6 +140,7 @@ pub enum CipherListViewType { SecureNote, Card, Identity, + SshKey, } #[derive(Serialize, Deserialize, Debug, JsonSchema, PartialEq)] @@ -211,6 +215,7 @@ impl KeyEncryptable for CipherView { identity: self.identity.encrypt_with_key(key)?, card: self.card.encrypt_with_key(key)?, secure_note: self.secure_note.encrypt_with_key(key)?, + ssh_key: self.ssh_key.encrypt_with_key(key)?, favorite: self.favorite, reprompt: self.reprompt, organization_use_totp: self.organization_use_totp, @@ -245,6 +250,7 @@ impl KeyDecryptable for Cipher { identity: self.identity.decrypt_with_key(key).ok().flatten(), card: self.card.decrypt_with_key(key).ok().flatten(), secure_note: self.secure_note.decrypt_with_key(key).ok().flatten(), + ssh_key: self.ssh_key.decrypt_with_key(key).ok().flatten(), favorite: self.favorite, reprompt: self.reprompt, organization_use_totp: self.organization_use_totp, @@ -329,6 +335,19 @@ impl Cipher { .transpose()?, ) } + CipherType::SshKey => { + let Some(ssh_key) = &self.ssh_key else { + return Ok(String::new()); + }; + + build_subtitle_ssh_key( + ssh_key + .fingerprint + .as_ref() + .map(|c| c.decrypt_with_key(key)) + .transpose()?, + ) + } }) } } @@ -389,6 +408,14 @@ fn build_subtitle_identity(first_name: Option, last_name: Option sub_title } +fn build_subtitle_ssh_key(fingerprint: Option) -> String { + if let Some(fingerprint) = &fingerprint { + return fingerprint.clone(); + } + + "".to_string() +} + impl CipherView { pub fn generate_cipher_key(&mut self, key: &SymmetricCryptoKey) -> Result<(), CryptoError> { let old_ciphers_key = Cipher::get_cipher_key(key, &self.key)?; @@ -553,6 +580,7 @@ impl KeyDecryptable for Cipher { CipherType::SecureNote => CipherListViewType::SecureNote, CipherType::Card => CipherListViewType::Card, CipherType::Identity => CipherListViewType::Identity, + CipherType::SshKey => CipherListViewType::SshKey, }, favorite: self.favorite, reprompt: self.reprompt, @@ -614,6 +642,8 @@ impl TryFrom for Cipher { identity: cipher.identity.map(|i| (*i).try_into()).transpose()?, card: cipher.card.map(|c| (*c).try_into()).transpose()?, secure_note: cipher.secure_note.map(|s| (*s).try_into()).transpose()?, + // todo: add ssh_key when api bindings have been updated + ssh_key: None, favorite: cipher.favorite.unwrap_or(false), reprompt: cipher .reprompt @@ -650,6 +680,7 @@ impl From for CipherType { bitwarden_api_api::models::CipherType::SecureNote => CipherType::SecureNote, bitwarden_api_api::models::CipherType::Card => CipherType::Card, bitwarden_api_api::models::CipherType::Identity => CipherType::Identity, + // todo: add ssh_key when api bindings have been updated } } } @@ -695,6 +726,7 @@ mod tests { identity: None, card: None, secure_note: None, + ssh_key: None, favorite: false, reprompt: CipherRepromptType::None, organization_use_totp: true, @@ -753,6 +785,7 @@ mod tests { identity: None, card: None, secure_note: None, + ssh_key: None, favorite: false, reprompt: CipherRepromptType::None, organization_use_totp: false, diff --git a/crates/bitwarden-vault/src/cipher/mod.rs b/crates/bitwarden-vault/src/cipher/mod.rs index 67513d524..a4a6088dc 100644 --- a/crates/bitwarden-vault/src/cipher/mod.rs +++ b/crates/bitwarden-vault/src/cipher/mod.rs @@ -8,6 +8,7 @@ pub(crate) mod linked_id; pub(crate) mod local_data; pub(crate) mod login; pub(crate) mod secure_note; +pub(crate) mod ssh_key; pub use attachment::{ Attachment, AttachmentEncryptResult, AttachmentFile, AttachmentFileView, AttachmentView, diff --git a/crates/bitwarden-vault/src/cipher/ssh_key.rs b/crates/bitwarden-vault/src/cipher/ssh_key.rs new file mode 100644 index 000000000..9715d5eea --- /dev/null +++ b/crates/bitwarden-vault/src/cipher/ssh_key.rs @@ -0,0 +1,43 @@ +use bitwarden_crypto::{ + CryptoError, EncString, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +pub struct SshKey { + pub private_key: Option, + pub public_key: Option, + pub fingerprint: Option, +} + +#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +pub struct SshKeyView { + pub private_key: Option, + pub public_key: Option, + pub fingerprint: Option, +} + +impl KeyEncryptable for SshKeyView { + fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { + Ok(SshKey { + private_key: self.private_key.encrypt_with_key(key)?, + public_key: self.public_key.encrypt_with_key(key)?, + fingerprint: self.fingerprint.encrypt_with_key(key)?, + }) + } +} + +impl KeyDecryptable for SshKey { + fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { + Ok(SshKeyView { + private_key: self.private_key.decrypt_with_key(key).ok().flatten(), + public_key: self.public_key.decrypt_with_key(key).ok().flatten(), + fingerprint: self.fingerprint.decrypt_with_key(key).ok().flatten(), + }) + } +} diff --git a/crates/bitwarden-vault/src/mobile/client_ciphers.rs b/crates/bitwarden-vault/src/mobile/client_ciphers.rs index 345f04df1..063fb4bc6 100644 --- a/crates/bitwarden-vault/src/mobile/client_ciphers.rs +++ b/crates/bitwarden-vault/src/mobile/client_ciphers.rs @@ -118,6 +118,7 @@ mod tests { identity: None, card: None, secure_note: None, + ssh_key: None, favorite: false, reprompt: CipherRepromptType::None, organization_use_totp: true, @@ -159,6 +160,7 @@ mod tests { identity: None, card: None, secure_note: None, + ssh_key: None, favorite: false, reprompt: CipherRepromptType::None, organization_use_totp: true, From c3c18ad3923914e943f2590cc1480440e2a77e3b Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 13 Sep 2024 12:43:14 +0200 Subject: [PATCH 02/10] Rename TODO and clean up fingerprint subtitle for ssh ciphers --- crates/bitwarden-vault/src/cipher/cipher.rs | 25 +++++++-------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index 716004c36..d86622a68 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -340,13 +340,12 @@ impl Cipher { return Ok(String::new()); }; - build_subtitle_ssh_key( - ssh_key - .fingerprint - .as_ref() - .map(|c| c.decrypt_with_key(key)) - .transpose()?, - ) + ssh_key + .fingerprint + .as_ref() + .map(|c| c.decrypt_with_key(key)) + .transpose()? + .unwrap_or_default() } }) } @@ -408,14 +407,6 @@ fn build_subtitle_identity(first_name: Option, last_name: Option sub_title } -fn build_subtitle_ssh_key(fingerprint: Option) -> String { - if let Some(fingerprint) = &fingerprint { - return fingerprint.clone(); - } - - "".to_string() -} - impl CipherView { pub fn generate_cipher_key(&mut self, key: &SymmetricCryptoKey) -> Result<(), CryptoError> { let old_ciphers_key = Cipher::get_cipher_key(key, &self.key)?; @@ -642,7 +633,7 @@ impl TryFrom for Cipher { identity: cipher.identity.map(|i| (*i).try_into()).transpose()?, card: cipher.card.map(|c| (*c).try_into()).transpose()?, secure_note: cipher.secure_note.map(|s| (*s).try_into()).transpose()?, - // todo: add ssh_key when api bindings have been updated + // TODO: add ssh_key when api bindings have been updated ssh_key: None, favorite: cipher.favorite.unwrap_or(false), reprompt: cipher @@ -680,7 +671,7 @@ impl From for CipherType { bitwarden_api_api::models::CipherType::SecureNote => CipherType::SecureNote, bitwarden_api_api::models::CipherType::Card => CipherType::Card, bitwarden_api_api::models::CipherType::Identity => CipherType::Identity, - // todo: add ssh_key when api bindings have been updated + // TODO: add ssh_key when api bindings have been updated } } } From b5a0d92e189da9492d1fb085680c6504ed4d4058 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 13 Sep 2024 15:59:24 +0200 Subject: [PATCH 03/10] Add export test --- crates/bitwarden-exporters/src/json.rs | 54 ++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/crates/bitwarden-exporters/src/json.rs b/crates/bitwarden-exporters/src/json.rs index 4c778c9cc..6008ed0cd 100644 --- a/crates/bitwarden-exporters/src/json.rs +++ b/crates/bitwarden-exporters/src/json.rs @@ -619,6 +619,60 @@ mod tests { ) } + #[test] + fn test_convert_ssh_key() { + let cipher = Cipher { + id: "23f0f877-42b1-4820-a850-b10700bc41eb".parse().unwrap(), + folder_id: None, + + name: "My ssh key".to_string(), + notes: None, + + r#type: CipherType::SshKey(Box::new(SshKey { + private_key: Some("private".to_string()), + public_key: Some("public".to_string()), + fingerprint: Some("fingerprint".to_string()), + })), + + favorite: false, + reprompt: 0, + + fields: vec![], + + revision_date: "2024-01-30T11:25:25.466Z".parse().unwrap(), + creation_date: "2024-01-30T11:25:25.466Z".parse().unwrap(), + deleted_date: None, + }; + + let json = serde_json::to_string(&JsonCipher::from(cipher)).unwrap(); + + let expected = r#"{ + "passwordHistory": null, + "revisionDate": "2024-01-30T11:25:25.466Z", + "creationDate": "2024-01-30T11:25:25.466Z", + "deletedDate": null, + "id": "23f0f877-42b1-4820-a850-b10700bc41eb", + "organizationId": null, + "folderId": null, + "type": 5, + "reprompt": 0, + "name": "My ssh key", + "notes": null, + "sshKey": { + "privateKey": "private", + "publicKey": "public", + "fingerprint": "fingerprint" + }, + "favorite": false, + "collectionIds": null + }"#; + + assert_eq!( + json.parse::().unwrap(), + expected.parse::().unwrap() + ) + } + #[test] pub fn test_export() { let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); From ff179ea9f2013843048449ecdee05e81baa3301a Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 13 Sep 2024 16:19:00 +0200 Subject: [PATCH 04/10] Add tests --- .../resources/json_export.json | 20 +++++++++++++++++ crates/bitwarden-exporters/src/json.rs | 22 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/crates/bitwarden-exporters/resources/json_export.json b/crates/bitwarden-exporters/resources/json_export.json index ad5380550..27a0921d9 100644 --- a/crates/bitwarden-exporters/resources/json_export.json +++ b/crates/bitwarden-exporters/resources/json_export.json @@ -141,6 +141,26 @@ "revisionDate": "2024-01-30T17:54:50.706Z", "creationDate": "2024-01-30T17:54:50.706Z", "deletedDate": null + }, + { + "id": "646594a9-a9cb-4082-9d57-0024c3fbcaa9", + "folderId": null, + "organizationId": null, + "collectionIds": null, + "name": "My ssh key", + "notes": null, + "type": 5, + "sshKey": { + "privateKey": "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\nQyNTUxOQAAACBinNE5chMtCHh3BV0H1+CpPlEQBwR5cD+Xb9i8MaHGiwAAAKAy48fwMuPH\n8AAAAAtzc2gtZWQyNTUxOQAAACBinNE5chMtCHh3BV0H1+CpPlEQBwR5cD+Xb9i8MaHGiw\nAAAEAYUCIdfLI14K3XIy9V0FDZLQoZ9gcjOnvFjb4uA335HmKc0TlyEy0IeHcFXQfX4Kk+\nURAHBHlwP5dv2LwxocaLAAAAHHF1ZXh0ZW5ATWFjQm9vay1Qcm8tMTYubG9jYWwB\n-----END OPENSSH PRIVATE KEY-----", + "publicKey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGKc0TlyEy0IeHcFXQfX4Kk+URAHBHlwP5dv2LwxocaL", + "fingerprint": "SHA256:1JjFjvPRkj1Gbf2qRP1dgHiIzEuNAEvp+92x99jw3K0" + }, + "favorite": false, + "reprompt": 0, + "passwordHistory": null, + "revisionDate": "2024-01-30T11:25:25.466Z", + "creationDate": "2024-01-30T11:25:25.466Z", + "deletedDate": null } ] } \ No newline at end of file diff --git a/crates/bitwarden-exporters/src/json.rs b/crates/bitwarden-exporters/src/json.rs index 6008ed0cd..753940aee 100644 --- a/crates/bitwarden-exporters/src/json.rs +++ b/crates/bitwarden-exporters/src/json.rs @@ -829,6 +829,28 @@ mod tests { creation_date: "2024-01-30T17:54:50.706Z".parse().unwrap(), deleted_date: None, }, + Cipher { + id: "646594a9-a9cb-4082-9d57-0024c3fbcaa9".parse().unwrap(), + folder_id: None, + + name: "My ssh key".to_string(), + notes: None, + + r#type: CipherType::SshKey(Box::new(SshKey { + private_key: Some("-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\nQyNTUxOQAAACBinNE5chMtCHh3BV0H1+CpPlEQBwR5cD+Xb9i8MaHGiwAAAKAy48fwMuPH\n8AAAAAtzc2gtZWQyNTUxOQAAACBinNE5chMtCHh3BV0H1+CpPlEQBwR5cD+Xb9i8MaHGiw\nAAAEAYUCIdfLI14K3XIy9V0FDZLQoZ9gcjOnvFjb4uA335HmKc0TlyEy0IeHcFXQfX4Kk+\nURAHBHlwP5dv2LwxocaLAAAAHHF1ZXh0ZW5ATWFjQm9vay1Qcm8tMTYubG9jYWwB\n-----END OPENSSH PRIVATE KEY-----".to_string()), + public_key: Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGKc0TlyEy0IeHcFXQfX4Kk+URAHBHlwP5dv2LwxocaL".to_string()), + fingerprint: Some("SHA256:1JjFjvPRkj1Gbf2qRP1dgHiIzEuNAEvp+92x99jw3K0".to_string()), + })), + + favorite: false, + reprompt: 0, + + fields: vec![], + + revision_date: "2024-01-30T11:25:25.466Z".parse().unwrap(), + creation_date: "2024-01-30T11:25:25.466Z".parse().unwrap(), + deleted_date: None, + } ], ) .unwrap(); From a1c88182b1d269f7b946933cca74bbaaf6342a5e Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Sat, 14 Sep 2024 12:54:10 +0200 Subject: [PATCH 05/10] Fix linting --- crates/bitwarden-exporters/src/json.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/bitwarden-exporters/src/json.rs b/crates/bitwarden-exporters/src/json.rs index 753940aee..7ed85d451 100644 --- a/crates/bitwarden-exporters/src/json.rs +++ b/crates/bitwarden-exporters/src/json.rs @@ -832,21 +832,21 @@ mod tests { Cipher { id: "646594a9-a9cb-4082-9d57-0024c3fbcaa9".parse().unwrap(), folder_id: None, - + name: "My ssh key".to_string(), notes: None, - + r#type: CipherType::SshKey(Box::new(SshKey { private_key: Some("-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\nQyNTUxOQAAACBinNE5chMtCHh3BV0H1+CpPlEQBwR5cD+Xb9i8MaHGiwAAAKAy48fwMuPH\n8AAAAAtzc2gtZWQyNTUxOQAAACBinNE5chMtCHh3BV0H1+CpPlEQBwR5cD+Xb9i8MaHGiw\nAAAEAYUCIdfLI14K3XIy9V0FDZLQoZ9gcjOnvFjb4uA335HmKc0TlyEy0IeHcFXQfX4Kk+\nURAHBHlwP5dv2LwxocaLAAAAHHF1ZXh0ZW5ATWFjQm9vay1Qcm8tMTYubG9jYWwB\n-----END OPENSSH PRIVATE KEY-----".to_string()), public_key: Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGKc0TlyEy0IeHcFXQfX4Kk+URAHBHlwP5dv2LwxocaL".to_string()), fingerprint: Some("SHA256:1JjFjvPRkj1Gbf2qRP1dgHiIzEuNAEvp+92x99jw3K0".to_string()), })), - + favorite: false, reprompt: 0, - + fields: vec![], - + revision_date: "2024-01-30T11:25:25.466Z".parse().unwrap(), creation_date: "2024-01-30T11:25:25.466Z".parse().unwrap(), deleted_date: None, From d698115728eb31f5306581b3726c3d210abba3fa Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Sat, 14 Sep 2024 13:15:53 +0200 Subject: [PATCH 06/10] Add test for subtitle --- crates/bitwarden-vault/src/cipher/cipher.rs | 42 +++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index d86622a68..b0aacb2d3 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -691,6 +691,7 @@ mod tests { use std::collections::HashMap; use attachment::AttachmentView; + use ssh_key::SshKey; use super::*; use crate::Fido2Credential; @@ -1183,4 +1184,45 @@ mod tests { let subtitle = build_subtitle_identity(first_name, last_name); assert_eq!(subtitle, ""); } + + #[test] + fn test_subtitle_ssh_key() { + let key = "hvBMMb1t79YssFZkpetYsM3deyVuQv4r88Uj9gvYe0+G8EwxvW3v1iywVmSl61iwzd17JW5C/ivzxSP2C9h7Tw==".to_string(); + let key = SymmetricCryptoKey::try_from(key).unwrap(); + let test_subtitle = "SHA256:1JjFjvPRkj1Gbf2qRP1dgHiIzEuNAEvp+92x99jw3K0".to_string(); + let fingerprint_encrypted = test_subtitle.to_owned().encrypt_with_key(&key).unwrap(); + let ssh_key_cipher = Cipher { + id: Some("090c19ea-a61a-4df6-8963-262b97bc6266".parse().unwrap()), + organization_id: None, + folder_id: None, + collection_ids: vec![], + r#type: CipherType::SshKey, + key: None, + name: "My test ssh key".to_string().encrypt_with_key(&key).unwrap(), + notes: None, + login: None, + identity: None, + card: None, + secure_note: None, + ssh_key: Some(SshKey { + private_key: None, + public_key: None, + fingerprint: Some(fingerprint_encrypted), + }), + favorite: false, + reprompt: CipherRepromptType::None, + organization_use_totp: false, + edit: true, + view_password: true, + local_data: None, + attachments: None, + fields: None, + password_history: None, + creation_date: "2024-01-01T00:00:00.000Z".parse().unwrap(), + deleted_date: None, + revision_date: "2024-01-01T00:00:00.000Z".parse().unwrap(), + }; + let subtitle = ssh_key_cipher.get_decrypted_subtitle(&key).unwrap(); + assert_eq!(subtitle, test_subtitle); + } } From e27270475a85a6bb217d4c9692b3423ac4f40bdd Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Sat, 14 Sep 2024 13:16:32 +0200 Subject: [PATCH 07/10] Rename test subtitle to original subtitle --- crates/bitwarden-vault/src/cipher/cipher.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index b0aacb2d3..1724dae39 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -1189,8 +1189,8 @@ mod tests { fn test_subtitle_ssh_key() { let key = "hvBMMb1t79YssFZkpetYsM3deyVuQv4r88Uj9gvYe0+G8EwxvW3v1iywVmSl61iwzd17JW5C/ivzxSP2C9h7Tw==".to_string(); let key = SymmetricCryptoKey::try_from(key).unwrap(); - let test_subtitle = "SHA256:1JjFjvPRkj1Gbf2qRP1dgHiIzEuNAEvp+92x99jw3K0".to_string(); - let fingerprint_encrypted = test_subtitle.to_owned().encrypt_with_key(&key).unwrap(); + let original_subtitle = "SHA256:1JjFjvPRkj1Gbf2qRP1dgHiIzEuNAEvp+92x99jw3K0".to_string(); + let fingerprint_encrypted = original_subtitle.to_owned().encrypt_with_key(&key).unwrap(); let ssh_key_cipher = Cipher { id: Some("090c19ea-a61a-4df6-8963-262b97bc6266".parse().unwrap()), organization_id: None, @@ -1223,6 +1223,6 @@ mod tests { revision_date: "2024-01-01T00:00:00.000Z".parse().unwrap(), }; let subtitle = ssh_key_cipher.get_decrypted_subtitle(&key).unwrap(); - assert_eq!(subtitle, test_subtitle); + assert_eq!(subtitle, original_subtitle); } } From a687404e9184c7c57121b02bece922fba801c29d Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 24 Sep 2024 12:50:36 +0200 Subject: [PATCH 08/10] Cargo fmt --- crates/bitwarden-vault/src/cipher/cipher.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden-vault/src/cipher/cipher.rs b/crates/bitwarden-vault/src/cipher/cipher.rs index 1724dae39..846d332cd 100644 --- a/crates/bitwarden-vault/src/cipher/cipher.rs +++ b/crates/bitwarden-vault/src/cipher/cipher.rs @@ -1198,7 +1198,10 @@ mod tests { collection_ids: vec![], r#type: CipherType::SshKey, key: None, - name: "My test ssh key".to_string().encrypt_with_key(&key).unwrap(), + name: "My test ssh key" + .to_string() + .encrypt_with_key(&key) + .unwrap(), notes: None, login: None, identity: None, @@ -1223,6 +1226,6 @@ mod tests { revision_date: "2024-01-01T00:00:00.000Z".parse().unwrap(), }; let subtitle = ssh_key_cipher.get_decrypted_subtitle(&key).unwrap(); - assert_eq!(subtitle, original_subtitle); + assert_eq!(subtitle, original_subtitle); } } From eac218c72d6d5f3c3e6b2afadf370760fc1cda5d Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 1 Oct 2024 16:37:48 +0200 Subject: [PATCH 09/10] Add documentation for ssh key formats --- crates/bitwarden-vault/src/cipher/ssh_key.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/bitwarden-vault/src/cipher/ssh_key.rs b/crates/bitwarden-vault/src/cipher/ssh_key.rs index 9715d5eea..467adb4a6 100644 --- a/crates/bitwarden-vault/src/cipher/ssh_key.rs +++ b/crates/bitwarden-vault/src/cipher/ssh_key.rs @@ -8,8 +8,11 @@ use serde::{Deserialize, Serialize}; #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct SshKey { + /// Ssh private key (ed25519/rsa) in unencrypted openssh private key format https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key pub private_key: Option, + /// Ssh public key (ed25519/rsa) according to RFC4253 https://datatracker.ietf.org/doc/html/rfc4253#section-6.6 pub public_key: Option, + /// Ssh fingerprint using SHA256 in the format: SHA256:BASE64_ENCODED_FINGERPRINT pub fingerprint: Option, } @@ -17,8 +20,11 @@ pub struct SshKey { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] pub struct SshKeyView { + /// Ssh private key (ed25519/rsa) in unencrypted openssh private key format https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key pub private_key: Option, + /// Ssh public key (ed25519/rsa) according to RFC4253 https://datatracker.ietf.org/doc/html/rfc4253#section-6.6 pub public_key: Option, + /// Ssh fingerprint using SHA256 in the format: SHA256:BASE64_ENCODED_FINGERPRINT pub fingerprint: Option, } From b6723e5a1aae550a015116c21d66ad612278dce8 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 1 Oct 2024 16:39:57 +0200 Subject: [PATCH 10/10] Add documentation for ssh key export format --- crates/bitwarden-exporters/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/bitwarden-exporters/src/lib.rs b/crates/bitwarden-exporters/src/lib.rs index 16d9e3974..895d31dc0 100644 --- a/crates/bitwarden-exporters/src/lib.rs +++ b/crates/bitwarden-exporters/src/lib.rs @@ -136,7 +136,10 @@ pub struct Identity { } pub struct SshKey { + /// Ssh private key (ed25519/rsa) in unencrypted openssh private key format https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key pub private_key: Option, + /// Ssh public key (ed25519/rsa) according to RFC4253 https://datatracker.ietf.org/doc/html/rfc4253#section-6.6 pub public_key: Option, + /// Ssh fingerprint using SHA256 in the format: SHA256:BASE64_ENCODED_FINGERPRINT pub fingerprint: Option, }