Skip to content

Commit

Permalink
Add bitwarden-ssh crate with keygeneration support
Browse files Browse the repository at this point in the history
  • Loading branch information
quexten committed Nov 21, 2024
1 parent 1b10d83 commit 8c9c7b3
Show file tree
Hide file tree
Showing 10 changed files with 460 additions and 0 deletions.
282 changes: 282 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ bitwarden-fido = { path = "crates/bitwarden-fido", version = "=1.0.0" }
bitwarden-generators = { path = "crates/bitwarden-generators", version = "=1.0.0" }
bitwarden-send = { path = "crates/bitwarden-send", version = "=1.0.0" }
bitwarden-sm = { path = "bitwarden_license/bitwarden-sm", version = "=1.0.0" }
bitwarden-ssh = { path = "crates/bitwarden-ssh", version = "=1.0.0" }
bitwarden-vault = { path = "crates/bitwarden-vault", version = "=1.0.0" }

# External crates that are expected to maintain a consistent version across all crates
Expand Down
43 changes: 43 additions & 0 deletions crates/bitwarden-ssh/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[package]
name = "bitwarden-ssh"
description = """
Internal crate for the bitwarden crate. Do not use.
"""

version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
homepage.workspace = true
repository.workspace = true
license-file.workspace = true
keywords.workspace = true

[features]
uniffi = ["dep:uniffi", "bitwarden-core/uniffi", "bitwarden-vault/uniffi"]

[dependencies]
async-trait = ">=0.1.80, <0.2"
base64 = ">=0.22.1, <0.23"
bitwarden-core = { workspace = true }
bitwarden-crypto = { workspace = true }
bitwarden-vault = { workspace = true }
chrono = { workspace = true }
coset = { version = "0.3.7" }
itertools = "0.13.0"
log = ">=0.4.18, <0.5"
p256 = { version = ">=0.13.2, <0.14" }
rand = "0.8.5"
rand_chacha = "0.3.1"
reqwest = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
ssh-encoding = "0.2.0"
ssh-key = { version = "0.6.7", features = ["ed25519", "encryption", "rsa", "getrandom"] }
thiserror = { workspace = true }
uniffi = { workspace = true, optional = true }
uuid = { workspace = true }

[lints]
workspace = true
6 changes: 6 additions & 0 deletions crates/bitwarden-ssh/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Bitwarden SSH

This is an internal crate for the Bitwarden SDK do not depend on this directly and use the
[`bitwarden`](https://crates.io/crates/bitwarden) crate instead.

This crate does not follow semantic versioning and the public interface may change at any time.
9 changes: 9 additions & 0 deletions crates/bitwarden-ssh/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use thiserror::Error;

#[derive(Error, Debug)]
pub enum KeyGenerationError {
#[error("Failed to generate key: {0}")]
KeyGenerationError(String),
#[error("Failed to convert key: {0}")]
KeyConversionError(String),
}
51 changes: 51 additions & 0 deletions crates/bitwarden-ssh/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
use ssh_key::{Algorithm, HashAlg, LineEnding};

mod error;
pub mod models;

pub enum KeyAlgorithm {
Ed25519,
Rsa3072,
Rsa4096,
}

pub async fn generate_keypair(
key_algorithm: KeyAlgorithm,
) -> Result<models::SshKey, error::KeyGenerationError> {
// sourced from cryptographically secure entropy source, with sources for all targets: https://docs.rs/getrandom
// if it cannot be securely sourced, this will panic instead of leading to a weak key
let mut rng: ChaCha8Rng = ChaCha8Rng::from_entropy();

let key = match key_algorithm {
KeyAlgorithm::Ed25519 => ssh_key::PrivateKey::random(&mut rng, Algorithm::Ed25519),
KeyAlgorithm::Rsa3072 | KeyAlgorithm::Rsa4096 => {
let bits = match key_algorithm {
KeyAlgorithm::Rsa3072 => 3072,
KeyAlgorithm::Rsa4096 => 4096,
_ => unreachable!(),
};

let rsa_keypair = ssh_key::private::RsaKeypair::random(&mut rng, bits)
.or_else(|e| Err(error::KeyGenerationError::KeyGenerationError(e.to_string())))?;

let private_key = ssh_key::PrivateKey::new(
ssh_key::private::KeypairData::from(rsa_keypair),
"".to_string(),
)
.or_else(|e| Err(error::KeyGenerationError::KeyGenerationError(e.to_string())))?;
Ok(private_key)
}
}
.or_else(|e| Err(error::KeyGenerationError::KeyGenerationError(e.to_string())))?;

let private_key_openssh = key
.to_openssh(LineEnding::LF)
.or_else(|e| Err(error::KeyGenerationError::KeyConversionError(e.to_string())))?;
Ok(models::SshKey {
private_key: private_key_openssh.to_string(),
public_key: key.public_key().to_string(),
key_fingerprint: key.fingerprint(HashAlg::Sha256).to_string(),
})
}
5 changes: 5 additions & 0 deletions crates/bitwarden-ssh/src/models.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub struct SshKey {
pub private_key: String,
pub public_key: String,
pub key_fingerprint: String,
}
1 change: 1 addition & 0 deletions crates/bitwarden-wasm-internal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ crate-type = ["cdylib"]
bitwarden-core = { workspace = true, features = ["wasm", "internal"] }
bitwarden-crypto = { workspace = true, features = ["wasm"] }
bitwarden-vault = { workspace = true, features = ["wasm"] }
bitwarden-ssh = { workspace = true }
console_error_panic_hook = "0.1.7"
console_log = { version = "1.0.0", features = ["color"] }
js-sys = "0.3.68"
Expand Down
1 change: 1 addition & 0 deletions crates/bitwarden-wasm-internal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod client;
mod crypto;
mod custom_types;
mod error;
pub mod ssh;
mod vault;

pub use client::BitwardenClient;
Expand Down
61 changes: 61 additions & 0 deletions crates/bitwarden-wasm-internal/src/ssh.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use bitwarden_ssh;
use wasm_bindgen::prelude::*;

use crate::error::{GenericError, Result};

#[wasm_bindgen]
pub enum KeyAlgorithm {
Ed25519,
Rsa3072,
Rsa4096,
}

// impl conversion
impl Into<bitwarden_ssh::KeyAlgorithm> for KeyAlgorithm {
fn into(self) -> bitwarden_ssh::KeyAlgorithm {
match self {
KeyAlgorithm::Ed25519 => bitwarden_ssh::KeyAlgorithm::Ed25519,
KeyAlgorithm::Rsa3072 => bitwarden_ssh::KeyAlgorithm::Rsa3072,
KeyAlgorithm::Rsa4096 => bitwarden_ssh::KeyAlgorithm::Rsa4096,
}
}
}

#[wasm_bindgen]
pub struct SshKey {
private_key: String,
public_key: String,
key_fingerprint: String,
}

impl From<bitwarden_ssh::models::SshKey> for SshKey {
fn from(key: bitwarden_ssh::models::SshKey) -> Self {
SshKey {
private_key: key.private_key,
public_key: key.public_key,
key_fingerprint: key.key_fingerprint,
}
}
}

impl SshKey {
pub fn private_key(&self) -> &str {
&self.private_key
}

pub fn public_key(&self) -> &str {
&self.public_key
}

pub fn key_fingerprint(&self) -> &str {
&self.key_fingerprint
}
}

#[wasm_bindgen]
pub async fn generate_ssh_key(key_algorithm: KeyAlgorithm) -> Result<SshKey> {
bitwarden_ssh::generate_keypair(key_algorithm.into())
.await
.map(|key| SshKey::from(key))
.map_err(|e| GenericError(e.to_string()))
}

0 comments on commit 8c9c7b3

Please sign in to comment.