Skip to content

Commit

Permalink
[PM-11924] Add ssh-key item type (#1037)
Browse files Browse the repository at this point in the history
## 🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-11924

Server PR: https://bitwarden.atlassian.net/browse/PM-10394

## 📔 Objective

Add a new item type for ssh-keys.

## ⏰ 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

---------

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
  • Loading branch information
quexten and Hinton authored Oct 21, 2024
1 parent be75311 commit 38ecf13
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 7 deletions.
20 changes: 20 additions & 0 deletions crates/bitwarden-exporters/resources/json_export.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
]
}
113 changes: 107 additions & 6 deletions crates/bitwarden-exporters/src/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -69,6 +71,8 @@ struct JsonCipher {
card: Option<JsonCard>,
#[serde(skip_serializing_if = "Option::is_none")]
secure_note: Option<JsonSecureNote>,
#[serde(skip_serializing_if = "Option::is_none")]
ssh_key: Option<JsonSshKey>,

favorite: bool,
reprompt: u8,
Expand Down Expand Up @@ -206,6 +210,24 @@ impl From<Identity> for JsonIdentity {
}
}

#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct JsonSshKey {
private_key: Option<String>,
public_key: Option<String>,
fingerprint: Option<String>,
}

impl From<SshKey> 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 {
Expand Down Expand Up @@ -233,13 +255,15 @@ impl From<Cipher> 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 {
Expand All @@ -254,6 +278,7 @@ impl From<Cipher> for JsonCipher {
identity,
card,
secure_note,
ssh_key,
favorite: cipher.favorite,
reprompt: cipher.reprompt,
fields: cipher.fields.into_iter().map(|f| f.into()).collect(),
Expand Down Expand Up @@ -594,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::<serde_json::Value>().unwrap(),
expected.parse::<serde_json::Value>().unwrap()
)
}

#[test]
pub fn test_export() {
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
Expand Down Expand Up @@ -750,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();
Expand Down
11 changes: 11 additions & 0 deletions crates/bitwarden-exporters/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ pub enum CipherType {
SecureNote(Box<SecureNote>),
Card(Box<Card>),
Identity(Box<Identity>),
SshKey(Box<SshKey>),
}

impl fmt::Display for CipherType {
Expand All @@ -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"),
}
}
}
Expand Down Expand Up @@ -132,3 +134,12 @@ pub struct Identity {
pub passport_number: Option<String>,
pub license_number: Option<String>,
}

pub struct SshKey {
/// [OpenSSH private key](https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key), in PEM encoding.
pub private_key: Option<String>,
/// Ssh public key (ed25519/rsa) according to [RFC4253](https://datatracker.ietf.org/doc/html/rfc4253#section-6.6)
pub public_key: Option<String>,
/// SSH fingerprint using SHA256 in the format: `SHA256:BASE64_ENCODED_FINGERPRINT`
pub fingerprint: Option<String>,
}
9 changes: 9 additions & 0 deletions crates/bitwarden-exporters/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ impl TryFrom<CipherView> 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 {
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions crates/bitwarden-vault/src/cipher/attachment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 38ecf13

Please sign in to comment.