From 0f8a7ac9e9384434f5c80c3fbf010996d2197dea Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 16 Dec 2024 12:35:11 +0100 Subject: [PATCH 01/24] Add ssh import --- Cargo.lock | 42 +++ crates/bitwarden-ssh/Cargo.toml | 3 + crates/bitwarden-ssh/src/error.rs | 13 + crates/bitwarden-ssh/src/generator.rs | 91 ++++++ crates/bitwarden-ssh/src/import.rs | 273 ++++++++++++++++++ crates/bitwarden-ssh/src/lib.rs | 92 +----- .../src/test_keys/ecdsa_openssh_unencrypted | 8 + .../test_keys/ecdsa_openssh_unencrypted.pub | 1 + .../src/test_keys/ed25519_openssh_encrypted | 8 + .../test_keys/ed25519_openssh_encrypted.pub | 1 + .../src/test_keys/ed25519_openssh_unencrypted | 7 + .../test_keys/ed25519_openssh_unencrypted.pub | 1 + .../src/test_keys/ed25519_pkcs8_unencrypted | 4 + .../test_keys/ed25519_pkcs8_unencrypted.pub | 1 + .../ed25519_putty_openssh_unencrypted | 8 + .../src/test_keys/rsa_openssh_encrypted | 39 +++ .../src/test_keys/rsa_openssh_encrypted.pub | 1 + .../src/test_keys/rsa_openssh_unencrypted | 38 +++ .../src/test_keys/rsa_openssh_unencrypted.pub | 1 + .../src/test_keys/rsa_pkcs8_encrypted | 42 +++ .../src/test_keys/rsa_pkcs8_encrypted.pub | 1 + .../src/test_keys/rsa_pkcs8_unencrypted | 40 +++ .../src/test_keys/rsa_pkcs8_unencrypted.pub | 1 + .../test_keys/rsa_putty_openssh_unencrypted | 30 ++ .../src/test_keys/rsa_putty_pkcs1_unencrypted | 27 ++ crates/bitwarden-wasm-internal/src/ssh.rs | 14 +- 26 files changed, 696 insertions(+), 91 deletions(-) create mode 100644 crates/bitwarden-ssh/src/generator.rs create mode 100644 crates/bitwarden-ssh/src/import.rs create mode 100644 crates/bitwarden-ssh/src/test_keys/ecdsa_openssh_unencrypted create mode 100644 crates/bitwarden-ssh/src/test_keys/ecdsa_openssh_unencrypted.pub create mode 100644 crates/bitwarden-ssh/src/test_keys/ed25519_openssh_encrypted create mode 100644 crates/bitwarden-ssh/src/test_keys/ed25519_openssh_encrypted.pub create mode 100644 crates/bitwarden-ssh/src/test_keys/ed25519_openssh_unencrypted create mode 100644 crates/bitwarden-ssh/src/test_keys/ed25519_openssh_unencrypted.pub create mode 100644 crates/bitwarden-ssh/src/test_keys/ed25519_pkcs8_unencrypted create mode 100644 crates/bitwarden-ssh/src/test_keys/ed25519_pkcs8_unencrypted.pub create mode 100644 crates/bitwarden-ssh/src/test_keys/ed25519_putty_openssh_unencrypted create mode 100644 crates/bitwarden-ssh/src/test_keys/rsa_openssh_encrypted create mode 100644 crates/bitwarden-ssh/src/test_keys/rsa_openssh_encrypted.pub create mode 100644 crates/bitwarden-ssh/src/test_keys/rsa_openssh_unencrypted create mode 100644 crates/bitwarden-ssh/src/test_keys/rsa_openssh_unencrypted.pub create mode 100644 crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_encrypted create mode 100644 crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_encrypted.pub create mode 100644 crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_unencrypted create mode 100644 crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_unencrypted.pub create mode 100644 crates/bitwarden-ssh/src/test_keys/rsa_putty_openssh_unencrypted create mode 100644 crates/bitwarden-ssh/src/test_keys/rsa_putty_pkcs1_unencrypted diff --git a/Cargo.lock b/Cargo.lock index 8c251deb..048e654e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -580,8 +580,11 @@ name = "bitwarden-ssh" version = "1.0.0" dependencies = [ "bitwarden-error", + "ed25519", + "pkcs8", "rand", "rand_chacha", + "rsa", "serde", "ssh-key", "thiserror 1.0.69", @@ -1383,6 +1386,7 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ + "pkcs8", "signature", ] @@ -2660,6 +2664,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest", + "hmac", ] [[package]] @@ -2700,6 +2705,21 @@ dependencies = [ "spki", ] +[[package]] +name = "pkcs5" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" +dependencies = [ + "aes", + "cbc", + "der", + "pbkdf2", + "scrypt", + "sha2", + "spki", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -2707,6 +2727,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", + "pkcs5", + "rand_core", "spki", ] @@ -3194,6 +3216,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -3270,6 +3301,17 @@ dependencies = [ "syn", ] +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2", +] + [[package]] name = "sec1" version = "0.7.3" diff --git a/crates/bitwarden-ssh/Cargo.toml b/crates/bitwarden-ssh/Cargo.toml index f1f1095e..86ee1608 100644 --- a/crates/bitwarden-ssh/Cargo.toml +++ b/crates/bitwarden-ssh/Cargo.toml @@ -22,7 +22,10 @@ wasm = [ [dependencies] bitwarden-error = { workspace = true } +ed25519 = { version = "2.2.3", features = ["pkcs8"] } +pkcs8 = { version = "0.10.2", features = ["encryption"] } rand = "0.8.5" +rsa = "0.9.7" serde.workspace = true ssh-key = { version = "0.6.7", features = [ "ed25519", diff --git a/crates/bitwarden-ssh/src/error.rs b/crates/bitwarden-ssh/src/error.rs index f4c0409a..eed3a56b 100644 --- a/crates/bitwarden-ssh/src/error.rs +++ b/crates/bitwarden-ssh/src/error.rs @@ -9,3 +9,16 @@ pub enum KeyGenerationError { #[error("Failed to convert key: {0}")] KeyConversionError(ssh_key::Error), } + +#[bitwarden_error(flat)] +#[derive(Error, Debug, PartialEq)] +pub enum SshKeyImportError { + #[error("Failed to parse key")] + ParsingError, + #[error("Password required")] + PasswordRequired, + #[error("Wrong password")] + WrongPassword, + #[error("Unsupported key type")] + UnsupportedKeyType, +} diff --git a/crates/bitwarden-ssh/src/generator.rs b/crates/bitwarden-ssh/src/generator.rs new file mode 100644 index 00000000..dfcb9097 --- /dev/null +++ b/crates/bitwarden-ssh/src/generator.rs @@ -0,0 +1,91 @@ +use crate::error; +use crate::error::KeyGenerationError; +use crate::SshKey; +use ssh_key::{rand_core::CryptoRngCore, Algorithm, HashAlg, LineEnding}; + +#[cfg(feature = "wasm")] +use tsify_next::Tsify; + +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] +pub enum KeyAlgorithm { + Ed25519, + Rsa3072, + Rsa4096, +} + +pub fn generate_sshkey(key_algorithm: KeyAlgorithm) -> Result { + let rng = rand::thread_rng(); + generate_sshkey_internal(key_algorithm, rng) +} + +fn generate_sshkey_internal( + key_algorithm: KeyAlgorithm, + mut rng: impl CryptoRngCore, +) -> Result { + 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) + .map_err(KeyGenerationError::KeyGenerationError)?; + + let private_key = + ssh_key::PrivateKey::new(ssh_key::private::KeypairData::from(rsa_keypair), "") + .map_err(KeyGenerationError::KeyGenerationError)?; + Ok(private_key) + } + } + .map_err(KeyGenerationError::KeyGenerationError)?; + + let private_key_openssh = key + .to_openssh(LineEnding::LF) + .map_err(KeyGenerationError::KeyConversionError)?; + Ok(SshKey { + private_key: private_key_openssh.to_string(), + public_key: key.public_key().to_string(), + key_fingerprint: key.fingerprint(HashAlg::Sha256).to_string(), + }) +} + +#[cfg(test)] +mod tests { + use rand::SeedableRng; + + use super::KeyAlgorithm; + use crate::generate_sshkey_internal; + + #[test] + fn generate_ssh_key_ed25519() { + let rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]); + let key_algorithm = KeyAlgorithm::Ed25519; + let result = generate_sshkey_internal(key_algorithm, rng); + let target = include_str!("../tests/ed25519_key").replace("\r\n", "\n"); + assert_eq!(result.unwrap().private_key, target); + } + + #[test] + fn generate_ssh_key_rsa3072() { + let rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]); + let key_algorithm = KeyAlgorithm::Rsa3072; + let result = generate_sshkey_internal(key_algorithm, rng); + let target = include_str!("../tests/rsa3072_key").replace("\r\n", "\n"); + assert_eq!(result.unwrap().private_key, target); + } + + #[test] + fn generate_ssh_key_rsa4096() { + let rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]); + let key_algorithm = KeyAlgorithm::Rsa4096; + let result = generate_sshkey_internal(key_algorithm, rng); + let target = include_str!("../tests/rsa4096_key").replace("\r\n", "\n"); + assert_eq!(result.unwrap().private_key, target); + } +} diff --git a/crates/bitwarden-ssh/src/import.rs b/crates/bitwarden-ssh/src/import.rs new file mode 100644 index 00000000..cb730dad --- /dev/null +++ b/crates/bitwarden-ssh/src/import.rs @@ -0,0 +1,273 @@ +use ed25519; +use pkcs8::{ + der::Decode, EncryptedPrivateKeyInfo, ObjectIdentifier, PrivateKeyInfo, SecretDocument, +}; +use serde::{Deserialize, Serialize}; +use ssh_key::{ + private::{Ed25519Keypair, RsaKeypair}, + HashAlg, LineEnding, +}; + +use crate::{error::SshKeyImportError, SshKey}; + +#[cfg(feature = "wasm")] +use tsify_next::Tsify; + +const PKCS1_HEADER: &str = "-----BEGIN RSA PRIVATE KEY-----"; +const PKCS8_UNENCRYPTED_HEADER: &str = "-----BEGIN PRIVATE KEY-----"; +const PKCS8_ENCRYPTED_HEADER: &str = "-----BEGIN ENCRYPTED PRIVATE KEY-----"; +const OPENSSH_HEADER: &str = "-----BEGIN OPENSSH PRIVATE KEY-----"; + +pub const RSA_PKCS8_ALGORITHM_OID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1"); + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] +enum KeyType { + Ed25519, + Rsa, + Unknown, +} + +pub fn import_key(encoded_key: String, password: String) -> Result { + match encoded_key.lines().next() { + Some(PKCS1_HEADER) => Err(SshKeyImportError::UnsupportedKeyType), + Some(PKCS8_UNENCRYPTED_HEADER) => { + import_pkcs8_key(encoded_key, None).map_err(|_| SshKeyImportError::ParsingError) + } + Some(PKCS8_ENCRYPTED_HEADER) => import_pkcs8_key(encoded_key, Some(password)), + Some(OPENSSH_HEADER) => import_openssh_key(encoded_key, password), + _ => Err(SshKeyImportError::ParsingError), + } +} + +fn import_pkcs8_key( + encoded_key: String, + password: Option, +) -> Result { + let der = match SecretDocument::from_pem(&encoded_key) { + Ok((_, doc)) => doc, + Err(_) => return Err(SshKeyImportError::ParsingError), + }; + + let decrypted_der = match password.clone() { + Some(password) => { + let encrypted_private_key_info = EncryptedPrivateKeyInfo::from_der(der.as_bytes()) + .map_err(|_| SshKeyImportError::ParsingError)?; + encrypted_private_key_info + .decrypt(password.as_bytes()) + .map_err(|_| SshKeyImportError::WrongPassword)? + } + None => der, + }; + + let key_type: KeyType = match PrivateKeyInfo::from_der(decrypted_der.as_bytes()) + .map_err(|_| SshKeyImportError::ParsingError)? + .algorithm + .oid + { + ed25519::pkcs8::ALGORITHM_OID => KeyType::Ed25519, + RSA_PKCS8_ALGORITHM_OID => KeyType::Rsa, + _ => KeyType::Unknown, + }; + + match key_type { + KeyType::Ed25519 => { + let private_key: ed25519::KeypairBytes = match password { + Some(password) => { + pkcs8::DecodePrivateKey::from_pkcs8_encrypted_pem(&encoded_key, password) + .map_err(|err| match err { + ed25519::pkcs8::Error::EncryptedPrivateKey(_) => { + SshKeyImportError::WrongPassword + } + _ => SshKeyImportError::ParsingError, + })? + } + None => ed25519::pkcs8::DecodePrivateKey::from_pkcs8_pem(&encoded_key) + .map_err(|_| SshKeyImportError::ParsingError)?, + }; + let private_key = ssh_key::private::PrivateKey::from(Ed25519Keypair::from( + &private_key.secret_key.into(), + )); + return Ok(SshKey { + private_key: private_key.to_openssh(LineEnding::LF).unwrap().to_string(), + public_key: private_key.public_key().to_string(), + key_fingerprint: private_key.fingerprint(HashAlg::Sha256).to_string(), + }); + } + KeyType::Rsa => { + let private_key: rsa::RsaPrivateKey = match password { + Some(password) => { + pkcs8::DecodePrivateKey::from_pkcs8_encrypted_pem(&encoded_key, password) + .map_err(|err| match err { + pkcs8::Error::EncryptedPrivateKey(_) => { + SshKeyImportError::WrongPassword + } + _ => SshKeyImportError::ParsingError, + })? + } + None => pkcs8::DecodePrivateKey::from_pkcs8_pem(&encoded_key) + .map_err(|_| SshKeyImportError::ParsingError)?, + }; + + let private_key = ssh_key::private::PrivateKey::from( + RsaKeypair::try_from(private_key).map_err(|_| SshKeyImportError::ParsingError)?, + ); + return Ok(SshKey { + private_key: private_key.to_openssh(LineEnding::LF).unwrap().to_string(), + public_key: private_key.public_key().to_string(), + key_fingerprint: private_key.fingerprint(HashAlg::Sha256).to_string(), + }); + } + _ => return Err(SshKeyImportError::UnsupportedKeyType), + } +} + +fn import_openssh_key(encoded_key: String, password: String) -> Result { + let private_key = match ssh_key::private::PrivateKey::from_openssh(&encoded_key) { + Ok(k) => k, + Err(err) => match err { + ssh_key::Error::AlgorithmUnknown + | ssh_key::Error::AlgorithmUnsupported { algorithm: _ } => { + return Err(SshKeyImportError::UnsupportedKeyType) + } + _ => return Err(SshKeyImportError::ParsingError), + }, + }; + + if private_key.is_encrypted() && password.is_empty() { + return Err(SshKeyImportError::PasswordRequired); + } + let private_key = if private_key.is_encrypted() { + private_key + .decrypt(password.as_bytes()) + .map_err(|_| SshKeyImportError::WrongPassword)? + } else { + private_key + }; + let private_key_openssh = private_key + .to_openssh(LineEnding::LF) + .map_err(|_| SshKeyImportError::ParsingError)?; + + Ok(SshKey { + private_key: private_key_openssh.to_string(), + public_key: private_key.public_key().to_string(), + key_fingerprint: private_key.fingerprint(HashAlg::Sha256).to_string(), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn import_key_ed25519_openssh_unencrypted() { + let private_key = include_str!("./test_keys/ed25519_openssh_unencrypted"); + let public_key = include_str!("./test_keys/ed25519_openssh_unencrypted.pub").trim(); + let result = import_key(private_key.to_string(), "".to_string()).unwrap(); + assert_eq!(result.public_key, public_key); + } + + #[test] + fn import_key_ed25519_openssh_encrypted() { + let private_key = include_str!("./test_keys/ed25519_openssh_encrypted"); + let public_key = include_str!("./test_keys/ed25519_openssh_encrypted.pub").trim(); + let result = import_key(private_key.to_string(), "password".to_string()).unwrap(); + assert_eq!(result.public_key, public_key); + } + + #[test] + fn import_key_rsa_openssh_unencrypted() { + let private_key = include_str!("./test_keys/rsa_openssh_unencrypted"); + let public_key = include_str!("./test_keys/rsa_openssh_unencrypted.pub").trim(); + let result = import_key(private_key.to_string(), "".to_string()).unwrap(); + assert_eq!(result.public_key, public_key); + } + + #[test] + fn import_key_rsa_openssh_encrypted() { + let private_key = include_str!("./test_keys/rsa_openssh_encrypted"); + let public_key = include_str!("./test_keys/rsa_openssh_encrypted.pub").trim(); + let result = import_key(private_key.to_string(), "password".to_string()).unwrap(); + assert_eq!(result.public_key, public_key); + } + + #[test] + fn import_key_ed25519_pkcs8_unencrypted() { + let private_key = include_str!("./test_keys/ed25519_pkcs8_unencrypted"); + let public_key = + include_str!("./test_keys/ed25519_pkcs8_unencrypted.pub").replace("testkey", ""); + let public_key = public_key.trim(); + let result = import_key(private_key.to_string(), "".to_string()).unwrap(); + assert_eq!(result.public_key, public_key); + } + + #[test] + fn import_key_rsa_pkcs8_unencrypted() { + let private_key = include_str!("./test_keys/rsa_pkcs8_unencrypted"); + // for whatever reason pkcs8 + rsa does not include the comment in the public key + let public_key = + include_str!("./test_keys/rsa_pkcs8_unencrypted.pub").replace("testkey", ""); + let public_key = public_key.trim(); + let result = import_key(private_key.to_string(), "".to_string()).unwrap(); + assert_eq!(result.public_key, public_key); + } + + #[test] + fn import_key_rsa_pkcs8_encrypted() { + let private_key = include_str!("./test_keys/rsa_pkcs8_encrypted"); + let public_key = include_str!("./test_keys/rsa_pkcs8_encrypted.pub").replace("testkey", ""); + let public_key = public_key.trim(); + let result = import_key(private_key.to_string(), "password".to_string()).unwrap(); + assert_eq!(result.public_key, public_key); + } + + #[test] + fn import_key_ed25519_openssh_encrypted_wrong_password() { + let private_key = include_str!("./test_keys/ed25519_openssh_encrypted"); + let result = import_key(private_key.to_string(), "wrongpassword".to_string()); + assert_eq!(result.unwrap_err(), SshKeyImportError::WrongPassword); + } + + #[test] + fn import_non_key_error() { + let result = import_key("not a key".to_string(), "".to_string()); + assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError); + } + + #[test] + fn import_ecdsa_error() { + let private_key = include_str!("./test_keys/ecdsa_openssh_unencrypted"); + let result = import_key(private_key.to_string(), "".to_string()); + assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType); + } + + // Putty-exported keys should be supported, but are not due to a parser incompatibility. + // Should this test start failing, please change it to expect a correct key, and + // make sure the documentation support for putty-exported keys this is updated. + // https://bitwarden.atlassian.net/browse/PM-14989 + #[test] + fn import_key_ed25519_putty() { + let private_key = include_str!("./test_keys/ed25519_putty_openssh_unencrypted"); + let result = import_key(private_key.to_string(), "".to_string()); + assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError); + } + + // Putty-exported keys should be supported, but are not due to a parser incompatibility. + // Should this test start failing, please change it to expect a correct key, and + // make sure the documentation support for putty-exported keys this is updated. + // https://bitwarden.atlassian.net/browse/PM-14989 + #[test] + fn import_key_rsa_openssh_putty() { + let private_key = include_str!("./test_keys/rsa_putty_openssh_unencrypted"); + let result = import_key(private_key.to_string(), "".to_string()); + assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError); + } + + #[test] + fn import_key_rsa_pkcs8_putty() { + let private_key = include_str!("./test_keys/rsa_putty_pkcs1_unencrypted"); + let result = import_key(private_key.to_string(), "".to_string()); + assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType); + } +} diff --git a/crates/bitwarden-ssh/src/lib.rs b/crates/bitwarden-ssh/src/lib.rs index 7736ee33..abc5c435 100644 --- a/crates/bitwarden-ssh/src/lib.rs +++ b/crates/bitwarden-ssh/src/lib.rs @@ -1,100 +1,16 @@ -use error::KeyGenerationError; -use ssh_key::{rand_core::CryptoRngCore, Algorithm, HashAlg, LineEnding}; - pub mod error; +pub mod generator; +pub mod import; use serde::{Deserialize, Serialize}; + #[cfg(feature = "wasm")] use tsify_next::Tsify; #[derive(Serialize, Deserialize)] #[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] -pub enum KeyAlgorithm { - Ed25519, - Rsa3072, - Rsa4096, -} - -pub fn generate_sshkey( - key_algorithm: KeyAlgorithm, -) -> Result { - let rng = rand::thread_rng(); - generate_sshkey_internal(key_algorithm, rng) -} - -fn generate_sshkey_internal( - key_algorithm: KeyAlgorithm, - mut rng: impl CryptoRngCore, -) -> Result { - 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) - .map_err(KeyGenerationError::KeyGenerationError)?; - - let private_key = - ssh_key::PrivateKey::new(ssh_key::private::KeypairData::from(rsa_keypair), "") - .map_err(KeyGenerationError::KeyGenerationError)?; - Ok(private_key) - } - } - .map_err(KeyGenerationError::KeyGenerationError)?; - - let private_key_openssh = key - .to_openssh(LineEnding::LF) - .map_err(KeyGenerationError::KeyConversionError)?; - Ok(GenerateSshKeyResult { - private_key: private_key_openssh.to_string(), - public_key: key.public_key().to_string(), - key_fingerprint: key.fingerprint(HashAlg::Sha256).to_string(), - }) -} - -#[derive(Serialize, Deserialize)] -#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] -pub struct GenerateSshKeyResult { +pub struct SshKey { pub private_key: String, pub public_key: String, pub key_fingerprint: String, } - -#[cfg(test)] -mod tests { - use rand::SeedableRng; - - use super::KeyAlgorithm; - use crate::generate_sshkey_internal; - - #[test] - fn generate_ssh_key_ed25519() { - let rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]); - let key_algorithm = KeyAlgorithm::Ed25519; - let result = generate_sshkey_internal(key_algorithm, rng); - let target = include_str!("../tests/ed25519_key").replace("\r\n", "\n"); - assert_eq!(result.unwrap().private_key, target); - } - - #[test] - fn generate_ssh_key_rsa3072() { - let rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]); - let key_algorithm = KeyAlgorithm::Rsa3072; - let result = generate_sshkey_internal(key_algorithm, rng); - let target = include_str!("../tests/rsa3072_key").replace("\r\n", "\n"); - assert_eq!(result.unwrap().private_key, target); - } - - #[test] - fn generate_ssh_key_rsa4096() { - let rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]); - let key_algorithm = KeyAlgorithm::Rsa4096; - let result = generate_sshkey_internal(key_algorithm, rng); - let target = include_str!("../tests/rsa4096_key").replace("\r\n", "\n"); - assert_eq!(result.unwrap().private_key, target); - } -} diff --git a/crates/bitwarden-ssh/src/test_keys/ecdsa_openssh_unencrypted b/crates/bitwarden-ssh/src/test_keys/ecdsa_openssh_unencrypted new file mode 100644 index 00000000..9cf518f8 --- /dev/null +++ b/crates/bitwarden-ssh/src/test_keys/ecdsa_openssh_unencrypted @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS +1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQRQzzQ8nQEouF1FMSHkPx1nejNCzF7g +Yb8MHXLdBFM0uJkWs0vzgLJkttts2eDv3SHJqIH6qHpkLtEvgMXE5WcaAAAAoOO1BebjtQ +XmAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFDPNDydASi4XUUx +IeQ/HWd6M0LMXuBhvwwdct0EUzS4mRazS/OAsmS222zZ4O/dIcmogfqoemQu0S+AxcTlZx +oAAAAhAKnIXk6H0Hs3HblklaZ6UmEjjdE/0t7EdYixpMmtpJ4eAAAAB3Rlc3RrZXk= +-----END OPENSSH PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/src/test_keys/ecdsa_openssh_unencrypted.pub b/crates/bitwarden-ssh/src/test_keys/ecdsa_openssh_unencrypted.pub new file mode 100644 index 00000000..75e08b88 --- /dev/null +++ b/crates/bitwarden-ssh/src/test_keys/ecdsa_openssh_unencrypted.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFDPNDydASi4XUUxIeQ/HWd6M0LMXuBhvwwdct0EUzS4mRazS/OAsmS222zZ4O/dIcmogfqoemQu0S+AxcTlZxo= testkey diff --git a/crates/bitwarden-ssh/src/test_keys/ed25519_openssh_encrypted b/crates/bitwarden-ssh/src/test_keys/ed25519_openssh_encrypted new file mode 100644 index 00000000..d3244a3d --- /dev/null +++ b/crates/bitwarden-ssh/src/test_keys/ed25519_openssh_encrypted @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAUTNb0if +fqsoqtfv70CfukAAAAGAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIHGs3Uw3eyqnFjBI +2eb7Qto4KVc34ZdnBac59Bab54BLAAAAkPA6aovfxQbP6FoOfaRH6u22CxqiUM0bbMpuFf +WETn9FLaBE6LjoHH0ZI5rzNjJaQUNfx0cRcqsIrexw8YINrdVjySmEqrl5hw8gpgy0gGP5 +1Y6vKWdHdrxJCA9YMFOfDs0UhPfpLKZCwm2Sg+Bd8arlI8Gy7y4Jj/60v2bZOLhD2IZQnK +NdJ8xATiIINuTy4g== +-----END OPENSSH PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/src/test_keys/ed25519_openssh_encrypted.pub b/crates/bitwarden-ssh/src/test_keys/ed25519_openssh_encrypted.pub new file mode 100644 index 00000000..1188fa43 --- /dev/null +++ b/crates/bitwarden-ssh/src/test_keys/ed25519_openssh_encrypted.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHGs3Uw3eyqnFjBI2eb7Qto4KVc34ZdnBac59Bab54BL testkey diff --git a/crates/bitwarden-ssh/src/test_keys/ed25519_openssh_unencrypted b/crates/bitwarden-ssh/src/test_keys/ed25519_openssh_unencrypted new file mode 100644 index 00000000..08184f31 --- /dev/null +++ b/crates/bitwarden-ssh/src/test_keys/ed25519_openssh_unencrypted @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACAyQo22TXXNqvF+L8jUSSNeu8UqrsDjvf9pwIwDC9ML6gAAAJDSHpL60h6S ++gAAAAtzc2gtZWQyNTUxOQAAACAyQo22TXXNqvF+L8jUSSNeu8UqrsDjvf9pwIwDC9ML6g +AAAECLdlFLIJbEiFo/f0ROdXMNZAPHGPNhvbbftaPsUZEjaDJCjbZNdc2q8X4vyNRJI167 +xSquwOO9/2nAjAML0wvqAAAAB3Rlc3RrZXkBAgMEBQY= +-----END OPENSSH PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/src/test_keys/ed25519_openssh_unencrypted.pub b/crates/bitwarden-ssh/src/test_keys/ed25519_openssh_unencrypted.pub new file mode 100644 index 00000000..5c398822 --- /dev/null +++ b/crates/bitwarden-ssh/src/test_keys/ed25519_openssh_unencrypted.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDJCjbZNdc2q8X4vyNRJI167xSquwOO9/2nAjAML0wvq testkey diff --git a/crates/bitwarden-ssh/src/test_keys/ed25519_pkcs8_unencrypted b/crates/bitwarden-ssh/src/test_keys/ed25519_pkcs8_unencrypted new file mode 100644 index 00000000..09eb7286 --- /dev/null +++ b/crates/bitwarden-ssh/src/test_keys/ed25519_pkcs8_unencrypted @@ -0,0 +1,4 @@ +-----BEGIN PRIVATE KEY----- +MFECAQEwBQYDK2VwBCIEIDY6/OAdDr3PbDss9NsLXK4CxiKUvz5/R9uvjtIzj4Sz +gSEAxsxm1xpZ/4lKIRYm0JrJ5gRZUh7H24/YT/0qGVGzPa0= +-----END PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/src/test_keys/ed25519_pkcs8_unencrypted.pub b/crates/bitwarden-ssh/src/test_keys/ed25519_pkcs8_unencrypted.pub new file mode 100644 index 00000000..40997e18 --- /dev/null +++ b/crates/bitwarden-ssh/src/test_keys/ed25519_pkcs8_unencrypted.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMbMZtcaWf+JSiEWJtCayeYEWVIex9uP2E/9KhlRsz2t diff --git a/crates/bitwarden-ssh/src/test_keys/ed25519_putty_openssh_unencrypted b/crates/bitwarden-ssh/src/test_keys/ed25519_putty_openssh_unencrypted new file mode 100644 index 00000000..aa9c01b8 --- /dev/null +++ b/crates/bitwarden-ssh/src/test_keys/ed25519_putty_openssh_unencrypted @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtz +c2gtZWQyNTUxOQAAACDp0/9zFBCyZs5BFqXCJN5i1DTanzPGHpUeo2LP8FmQ9wAA +AKCyIXPqsiFz6gAAAAtzc2gtZWQyNTUxOQAAACDp0/9zFBCyZs5BFqXCJN5i1DTa +nzPGHpUeo2LP8FmQ9wAAAEDQioomhjmD+sh2nsxfQLJ5YYGASNUAlUZHe9Jx0p47 +H+nT/3MUELJmzkEWpcIk3mLUNNqfM8YelR6jYs/wWZD3AAAAEmVkZHNhLWtleS0y +MDI0MTExOAECAwQFBgcICQoL +-----END OPENSSH PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_openssh_encrypted b/crates/bitwarden-ssh/src/test_keys/rsa_openssh_encrypted new file mode 100644 index 00000000..bb7bbd85 --- /dev/null +++ b/crates/bitwarden-ssh/src/test_keys/rsa_openssh_encrypted @@ -0,0 +1,39 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +2MmKBw== +-----END OPENSSH PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_openssh_encrypted.pub b/crates/bitwarden-ssh/src/test_keys/rsa_openssh_encrypted.pub new file mode 100644 index 00000000..d37f573b --- /dev/null +++ b/crates/bitwarden-ssh/src/test_keys/rsa_openssh_encrypted.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC/v18xGP3qzRV9iWqyiuwHZ4GpC4K2NO2/i2Yv5A3/bnal7CmiMh/S78lphgxcWtFkwrwlb321FmdHBv6KOW+EzSiPvmsdkkbpfBXB3Qf2SlhZOZZ7lYeu8KAxL3exvvn8O1GGlUjXGUrFgmC60tHWDBc1Ncmo8a2dwDLmA/sbLa8su2dvYEFmRg1vaytLDpkn8GS7zAxrUl/g0W2RwkPsByduUziQuX90v9WAy7MqOlwBRq6t5o8wdDBVODe0VIXC7N1OS42YUsKF+N0XOnLiJrIIKkXpahMDpKZHeHQAdUQzsJVhKoLJR8DNDTYyhnJoQG7Q6m2gDTca9oAWvsBiNoEwCvwrt7cDNCz/GslH9HXQgfWcVXn8+fuZgvjO3CxUI16Ev33m0jWoOKJcgK/ZLRnk8SEvsJ8NO32MeR/qUb7IN/yUcDmPMI/3ecQsakF2cwNzHkyiGVo//yVTpf+vk8b89L+GXbYU5rtswtc2ZEGsQnUkaoNqS8mHqhWQBUk= testkey diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_openssh_unencrypted b/crates/bitwarden-ssh/src/test_keys/rsa_openssh_unencrypted new file mode 100644 index 00000000..0d2692e1 --- /dev/null +++ b/crates/bitwarden-ssh/src/test_keys/rsa_openssh_unencrypted @@ -0,0 +1,38 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn +NhAAAAAwEAAQAAAYEAtVIe0gnPtD6299/roT7ntZgVe+qIqIMIruJdI2xTanLGhNpBOlzg +WqokbQK+aXATcaB7iQL1SPxIWV2M4jEBQbZuimIgDQvKbJ4TZPKEe1VdsrfuIo+9pDK7cG +Kc+JiWhKjqeTRMj91/qR1fW5IWOUyE1rkwhTNkwJqtYKZLVmd4TXtQsYMMC+I0cz4krfk1 +Yqmaae/gj12h8BvE3Y+Koof4JoLsqPufH+H/bVEayv63RyAQ1/tUv9l+rwJ+svWV4X3zf3 +z40hGF43L/NGl90Vutbn7b9G/RgEdiXyLZciP3XbWbLUM+r7mG9KNuSeoixe5jok15UKqC +XXxVb5IEZ73kaubSfz9JtsqtKG/OjOq6Fbl3Ky7kjvJyGpIvesuSInlpzPXqbLUCLJJfOA +PUZ1wi8uuuRNePzQBMMhq8UtAbB2Dy16d+HlgghzQ00NxtbQMfDZBdApfxm3shIxkUcHzb +DSvriHVaGGoOkmHPAmsdMsMiekuUMe9ljdOhmdTxAAAFgF8XjBxfF4wcAAAAB3NzaC1yc2 +EAAAGBALVSHtIJz7Q+tvff66E+57WYFXvqiKiDCK7iXSNsU2pyxoTaQTpc4FqqJG0Cvmlw +E3Gge4kC9Uj8SFldjOIxAUG2bopiIA0LymyeE2TyhHtVXbK37iKPvaQyu3BinPiYloSo6n +k0TI/df6kdX1uSFjlMhNa5MIUzZMCarWCmS1ZneE17ULGDDAviNHM+JK35NWKpmmnv4I9d +ofAbxN2PiqKH+CaC7Kj7nx/h/21RGsr+t0cgENf7VL/Zfq8CfrL1leF98398+NIRheNy/z +RpfdFbrW5+2/Rv0YBHYl8i2XIj9121my1DPq+5hvSjbknqIsXuY6JNeVCqgl18VW+SBGe9 +5Grm0n8/SbbKrShvzozquhW5dysu5I7ychqSL3rLkiJ5acz16my1AiySXzgD1GdcIvLrrk +TXj80ATDIavFLQGwdg8tenfh5YIIc0NNDcbW0DHw2QXQKX8Zt7ISMZFHB82w0r64h1Whhq +DpJhzwJrHTLDInpLlDHvZY3ToZnU8QAAAAMBAAEAAAGAEL3wpRWtVTf+NnR5QgX4KJsOjs +bI0ABrVpSFo43uxNMss9sgLzagq5ZurxcUBFHKJdF63puEkPTkbEX4SnFaa5of6kylp3a5 +fd55rXY8F9Q5xtT3Wr8ZdFYP2xBr7INQUJb1MXRMBnOeBDw3UBH01d0UHexzB7WHXcZacG +Ria+u5XrQebwmJ3PYJwENSaTLrxDyjSplQy4QKfgxeWNPWaevylIG9vtue5Xd9WXdl6Szs +ONfD3mFxQZagPSIWl0kYIjS3P2ZpLe8+sakRcfci8RjEUP7U+QxqY5VaQScjyX1cSYeQLz +t+/6Tb167aNtQ8CVW3IzM2EEN1BrSbVxFkxWFLxogAHct06Kn87nPn2+PWGWOVCBp9KheO +FszWAJ0Kzjmaga2BpOJcrwjSpGopAb1YPIoRPVepVZlQ4gGwy5gXCFwykT9WTBoJfg0BMQ +r3MSNcoc97eBomIWEa34K0FuQ3rVjMv9ylfyLvDBbRqTJ5zebeOuU+yCQHZUKk8klRAAAA +wAsToNZvYWRsOMTWQom0EW1IHzoL8Cyua+uh72zZi/7enm4yHPJiu2KNgQXfB0GEEjHjbo +9peCW3gZGTV+Ee+cAqwYLlt0SMl/VJNxN3rEG7BAqPZb42Ii2XGjaxzFq0cliUGAdo6UEd +swU8d2I7m9vIZm4nDXzsWOBWgonTKBNyL0DQ6KNOGEyj8W0BTCm7Rzwy7EKzFWbIxr4lSc +vDrJ3t6kOd7jZTF58kRMT0nxR0bf43YzF/3/qSvLYhQm/OOAAAAMEA2F6Yp8SrpQDNDFxh +gi4GeywArrQO9r3EHjnBZi/bacxllSzCGXAvp7m9OKC1VD2wQP2JL1VEIZRUTuGGT6itrm +QpX8OgoxlEJrlC5W0kHumZ3MFGd33W11u37gOilmd6+VfVXBziNG2rFohweAgs8X+Sg5AA +nIfMV6ySXUlvLzMHpGeKRRnnQq9Cwn4rDkVQENLd1i4e2nWFhaPTUwVMR8YuOT766bywr3 +7vG1PQLF7hnf2c/oPHAru+XD9gJWs5AAAAwQDWiB2G23F4Tvq8FiK2mMusSjQzHupl83rm +o3BSNRCvCjaLx6bWhDPSA1edNEF7VuP6rSp+i+UfSORHwOnlgnrvtcJeoDuA72hUeYuqD/ +1C9gghdhKzGTVf/IGTX1tH3rn2Gq9TEyrJs/ITcoOyZprz7VbaD3bP/NEER+m1EHi2TS/3 +SXQEtRm+IIBwba+QLUcsrWdQyIO+1OCXywDrAw50s7tjgr/goHgXTcrSXaKcIEOlPgBZH3 +YPuVuEtRYgX3kAAAAHdGVzdGtleQECAwQ= +-----END OPENSSH PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_openssh_unencrypted.pub b/crates/bitwarden-ssh/src/test_keys/rsa_openssh_unencrypted.pub new file mode 100644 index 00000000..9ec8fec5 --- /dev/null +++ b/crates/bitwarden-ssh/src/test_keys/rsa_openssh_unencrypted.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC1Uh7SCc+0Prb33+uhPue1mBV76oiogwiu4l0jbFNqcsaE2kE6XOBaqiRtAr5pcBNxoHuJAvVI/EhZXYziMQFBtm6KYiANC8psnhNk8oR7VV2yt+4ij72kMrtwYpz4mJaEqOp5NEyP3X+pHV9bkhY5TITWuTCFM2TAmq1gpktWZ3hNe1CxgwwL4jRzPiSt+TViqZpp7+CPXaHwG8Tdj4qih/gmguyo+58f4f9tURrK/rdHIBDX+1S/2X6vAn6y9ZXhffN/fPjSEYXjcv80aX3RW61uftv0b9GAR2JfItlyI/ddtZstQz6vuYb0o25J6iLF7mOiTXlQqoJdfFVvkgRnveRq5tJ/P0m2yq0ob86M6roVuXcrLuSO8nIaki96y5IieWnM9epstQIskl84A9RnXCLy665E14/NAEwyGrxS0BsHYPLXp34eWCCHNDTQ3G1tAx8NkF0Cl/GbeyEjGRRwfNsNK+uIdVoYag6SYc8Cax0ywyJ6S5Qx72WN06GZ1PE= testkey diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_encrypted b/crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_encrypted new file mode 100644 index 00000000..e84d1f07 --- /dev/null +++ b/crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_encrypted @@ -0,0 +1,42 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIHdTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQXquAya5XFx11QEPm +KCSnlwICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEKVtEIkI2ELppfUQ +IwfNzowEggcQtWhXVz3LunYTSRVgnexcHEaGkUF6l6a0mGaLSczl+jdCwbbBxibU +EvN7+WMQ44shOk3LyThg0Irl22/7FuovmYc3TSeoMQH4mTROKF+9793v0UMAIAYd +ZhTsexTGncCOt//bq6Fl+L+qPNEkY/OjS+wI9MbOn/Agbcr8/IFSOxuSixxoTKgq +4QR5Ra3USCLyfm+3BoGPMk3tbEjrwjvzx/eTaWzt6hdc0yX4ehtqExF8WAYB43DW +3Y1slA1T464/f1j4KXhoEXDTBOuvNvnbr7lhap8LERIGYGnQKv2m2Kw57Wultnoe +joEQ+vTl5n92HI77H8tbgSbTYuEQ2n9pDD7AAzYGBn15c4dYEEGJYdHnqfkEF+6F +EgPa+Xhj2qqk5nd1bzPSv6iX7XfAX2sRzfZfoaFETmR0ZKbs0aMsndC5wVvd3LpA +m86VUihQxDvU8F4gizrNYj4NaNRv4lrxBj7Kb6BO/qT3DB8Uqu43oyrvA90iMigi +EvuCViwwhwCpe+AxCqLGrzvIpiZCksTOtSPEvnMehw2WA3yd/n88Nis5zD4b65+q +Tx9Q0Qm1LIi1Bq+s60+W1HK3KfaLrJaoX3JARZoWfxurZwtj+cMlo5zK1Ha2HHqQ +kVn21tOcQU/Yljt3Db+CKZ5Tos/rPywxGnkeMABzJgyajPHkYaSgWZrOEueihfS1 +5eDtEMBehEyHfcUrL7XGnn4lOzwQHZIEFnVdV0YGaQY8Wz212IjeWxV09gM2OEP6 +PEDI3GSsqOnGkPrnson5tsIUcvpk9smy9AA9qVhNowzeWCWmsF8K9fn/O94tIzyN +2EK0tkf8oDVROlbEh/jDa2aAHqPGCXBEqq1CbZXQpNk4FlRzkjtxdzPNiXLf45xO +IjOTTzgaVYWiKZD9ymNjNPIaDCPB6c4LtUm86xUQzXdztBm1AOI3PrNI6nIHxWbF +bPeEkJMRiN7C9j5nQMgQRB67CeLhzvqUdyfrYhzc7HY479sKDt9Qn8R0wpFw0QSA +G1gpGyxFaBFSdIsil5K4IZYXxh7qTlOKzaqArTI0Dnuk8Y67z8zaxN5BkvOfBd+Q +SoDz6dzn7KIJrK4XP3IoNfs6EVT/tlMPRY3Y/Ug+5YYjRE497cMxW8jdf3ZwgWHQ +JubPH+0IpwNNZOOf4JXALULsDj0N7rJ1iZAY67b+7YMin3Pz0AGQhQdEdqnhaxPh +oMvL9xFewkyujwCmPj1oQi1Uj2tc1i4ZpxY0XmYn/FQiQH9/XLdIlOMSTwGx86bw +90e9VJHfCmflLOpENvv5xr2isNbn0aXNAOQ4drWJaYLselW2Y4N1iqBCWJKFyDGw +4DevhhamEvsrdoKgvnuzfvA44kQGmfTjCuMu7IR5zkxevONNrynKcHkoWATzgxSS +leXCxzc9VA0W7XUSMypHGPNHJCwYZvSWGx0qGI3VREUk2J7OeVjXCFNeHFc2Le3P +dAm+DqRiyPBVX+yW+i7rjZLyypLzmYo9CyhlohOxTeGa6iTxBUZfYGoc0eJNqfgN +/5hkoPFYGkcd/p41SKSg7akrJPRc+uftH0oVI0wVorGSVOvwXRn7QM+wFKlv3DQD +ysMP7cOKqMyhJsqeW74/iWEmhbFIDKexSd/KTQ6PirVlzj7148Fl++yxaZpnZ6MY +iyzifvLcT701GaewIwi9YR9f1BWUXYHTjK3sB3lLPyMbA4w9bRkylcKrbGf85q0E +LXPlfh+1C9JctczDCqr2iLRoc/5j23GeN8RWfUNpZuxjFv9sxkV4iG+UapIuOBIc +Os4//3w24XcTXYqBdX2Y7+238xq6/94+4hIhXAcMFc2Nr3CEAZCuKYChVL9CSA3v +4sZM4rbOz6kWTC2G3SAtkLSk7hCJ6HLXzrnDb4++g3JYJWLeaQ+4ZaxWuKymnehN +xumXCwCn0stmCjXYV/yM3TeVnMfBTIB13KAjbn0czGW00nj79rNJJzkOlp9tIPen +pUPRFPWjgLF+hVQrwqJ3HPmt6Rt6mKzZ4FEpBXMDjvlKabnFvBdl3gbNHSfxhGHi +FzG3phg1CiXaURQUAf21PV+djfBha7kDwMXnpgZ+PIyGDxRj61StV/NSlhg+8GrL +ccoDOkfpy2zn++rmAqA21rTEChFN5djdsJw45GqPKUPOAgxKBsvqpoMIqq/C2pHP +iMiBriZULV9l0tHn5MMcNQbYAmp4BsTo6maHByAVm1/7/VPQn6EieuGroYgSk2H7 +pnwM01IUfGGP3NKlq9EiiF1gz8acZ5v8+jkZM2pIzh8Trw0mtwBpnyiyXmpbR/RG +m/TTU/gNQ/94ZaNJ/shPoBwikWXvOm+0Z0ZAwu3xefTyENGhjmb5GXshEN/5WwCm +NNrtUPlkGkYJrnSCVM/lHtjShwbLw2w/1sag1uDuXwirxxYh9r7D6HQ= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_encrypted.pub b/crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_encrypted.pub new file mode 100644 index 00000000..f3c1b15f --- /dev/null +++ b/crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_encrypted.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCcHkc0xfH4w9aW41S9M/BfancSY4QPc2O4G1cRjFfK8QrLEGDA7NiHtoEML0afcurRXD3NVxuKaAns0w6EoS4CjzXUqVHTLA4SUyuapr8k0Eu2xOpbCwC3jDovhckoKloq7BvE6rC2i5wjSMadtIJKt/dqWI3HLjUMz1BxQJAU/qAbicj1SFZSjA/MubVBzcq93XOvByMtlIFu7wami3FTc37rVkGeUFHtK8ZbvG3n1aaTF79bBgSPuoq5BfcMdGr4WfQyGQzgse4v4hQ8yKYrtE0jo0kf06hEORimwOIU/W5IH1r+/xFs7qGKcPnFSZRIFv5LfMPTo8b+OsBRflosyfUumDEX97GZE7DSQl0EJzNvWeKwl7dQ8RUJTkbph2CjrxY77DFim+165Uj/WRr4uq2qMNhA2xNSD19+TA6AHdpGw4WZd37q2/n+EddlaJEH8MzpgtHNG9MiYh5ScZ+AG0QugflozJcQNc7n8N9Lpu1sRoejV5RhurHg/TYwVK8= testkey diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_unencrypted b/crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_unencrypted new file mode 100644 index 00000000..0bfe2bc5 --- /dev/null +++ b/crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_unencrypted @@ -0,0 +1,40 @@ +-----BEGIN PRIVATE KEY----- +MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQCn4+QiJojZ9mgc +9KYJIvDWGaz4qFhf0CButg6L8zEoHKwuiN+mqcEciCCOa9BNiJmm8NTTehZvrrgl +GG59zIbqYtDAHjVn+vtb49xPzIv+M651Yqj08lIbR9tEIHKCq7aH8GlDm8NgG9Ez +JGjlL7okQym4TH1MHl+s4mUyr/qb2unlZBDixAQsphU8iCLftukWCIkmQg4CSj1G +h3WbBlZ+EX5eW0EXuAw4XsSbBTWV9CHRowVIpYqPvEYSpHsoCjEcd988p19hpiGk +nA0J4z7JfUlNgyT/1chb8GCTDT+2DCBRApbsIg6TOBVS+PR6emAQ3eZzUW0+3/oR +M4ip0ujltQy8uU6gvYIAqx5wXGMThVpZcUgahKiSsVo/s4b84iMe4DG3W8jz4qi6 +yyNv0VedEzPUZ1lXd1GJFoy9uKNuSTe+1ksicAcluZN6LuNsPHcPxFCzOcmoNnVX +EKAXInt+ys//5CDVasroZSAHZnDjUD4oNsLI3VIOnGxgXrkwSH0CAwEAAQKCAYAA +2SDMf7OBHw1OGM9OQa1ZS4u+ktfQHhn31+FxbrhWGp+lDt8gYABVf6Y4dKN6rMtn +7D9gVSAlZCAn3Hx8aWAvcXHaspxe9YXiZDTh+Kd8EIXxBQn+TiDA5LH0dryABqmM +p20vYKtR7OS3lIIXfFBSrBMwdunKzLwmKwZLWq0SWf6vVbwpxRyR9CyByodF6Djm +ZK3QB2qQ3jqlL1HWXL0VnyArY7HLvUvfLLK4vMPqnsSH+FdHvhcEhwqMlWT44g+f +hqWtCJNnjDgLK3FPbI8Pz9TF8dWJvOmp5Q6iSBua1e9x2LizVuNSqiFc7ZTLeoG4 +nDj7T2BtqB0E1rNUDEN1aBo+UZmHJK7LrzfW/B+ssi2WwIpfxYa1lO6HFod5/YQi +XV1GunyH1chCsbvOFtXvAHASO4HTKlJNbWhRF1GXqnKpAaHDPCVuwp3eq6Yf0oLb +XrL3KFZ3jwWiWbpQXRVvpqzaJwZn3CN1yQgYS9j17a9wrPky+BoJxXjZ/oImWLEC +gcEA0lkLwiHvmTYFTCC7PN938Agk9/NQs5PQ18MRn9OJmyfSpYqf/gNp+Md7xUgt +F/MTif7uelp2J7DYf6fj9EYf9g4EuW+SQgFP4pfiJn1+zGFeTQq1ISvwjsA4E8ZS +t+GIumjZTg6YiL1/A79u4wm24swt7iqnVViOPtPGOM34S1tAamjZzq2eZDmAF6pA +fmuTMdinCMR1E1kNJYbxeqLiqQCXuwBBnHOOOJofN3AkvzjRUBB9udvniqYxH3PQ +cxPxAoHBAMxT5KwBhZhnJedYN87Kkcpl7xdMkpU8b+aXeZoNykCeoC+wgIQexnSW +mFk4HPkCNxvCWlbkOT1MHrTAKFnaOww23Ob+Vi6A9n0rozo9vtoJig114GB0gUqE +mtfLhO1P5AE8yzogE+ILHyp0BqXt8vGIfzpDnCkN+GKl8gOOMPrR4NAcLO+Rshc5 +nLs7BGB4SEi126Y6mSfp85m0++1QhWMz9HzqJEHCWKVcZYdCdEONP9js04EUnK33 +KtlJIWzZTQKBwAT0pBpGwmZRp35Lpx2gBitZhcVxrg0NBnaO2fNyAGPvZD8SLQLH +AdAiov/a23Uc/PDbWLL5Pp9gwzj+s5glrssVOXdE8aUscr1b5rARdNNL1/Tos6u8 +ZUZ3sNqGaZx7a8U4gyYboexWyo9EC1C+AdkGBm7+AkM4euFwC9N6xsa/t5zKK5d6 +76hc0m+8SxivYCBkgkrqlfeGuZCQxU+mVsC0it6U+va8ojUjLGkZ80OuCwBf4xZl +3+acU7vx9o8/gQKBwB7BrhU6MWrsc+cr/1KQaXum9mNyckomi82RFYvb8Yrilcg3 +8FBy9XqNRKeBa9MLw1HZYpHbzsXsVF7u4eQMloDTLVNUC5L6dKAI1owoyTa24uH9 +0WWTg/a8mTZMe1jhgrew+AJq27NV6z4PswR9GenDmyshDDudz7rBsflZCQRoXUfW +RelV7BHU6UPBsXn4ASF4xnRyM6WvcKy9coKZcUqqgm3fLM/9OizCCMJgfXHBrE+x +7nBqst746qlEedSRrQKBwQCVYwwKCHNlZxl0/NMkDJ+hp7/InHF6mz/3VO58iCb1 +9TLDVUC2dDGPXNYwWTT9PclefwV5HNBHcAfTzgB4dpQyNiDyV914HL7DFEGduoPn +wBYjeFre54v0YjjnskjJO7myircdbdX//i+7LMUw5aZZXCC8a5BD/rdV6IKJWJG5 +QBXbe5fVf1XwOjBTzlhIPIqhNFfSu+mFikp5BRwHGBqsKMju6inYmW6YADeY/SvO +QjDEB37RqGZxqyIx8V2ZYwU= +-----END PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_unencrypted.pub b/crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_unencrypted.pub new file mode 100644 index 00000000..a3e04eed --- /dev/null +++ b/crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_unencrypted.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCn4+QiJojZ9mgc9KYJIvDWGaz4qFhf0CButg6L8zEoHKwuiN+mqcEciCCOa9BNiJmm8NTTehZvrrglGG59zIbqYtDAHjVn+vtb49xPzIv+M651Yqj08lIbR9tEIHKCq7aH8GlDm8NgG9EzJGjlL7okQym4TH1MHl+s4mUyr/qb2unlZBDixAQsphU8iCLftukWCIkmQg4CSj1Gh3WbBlZ+EX5eW0EXuAw4XsSbBTWV9CHRowVIpYqPvEYSpHsoCjEcd988p19hpiGknA0J4z7JfUlNgyT/1chb8GCTDT+2DCBRApbsIg6TOBVS+PR6emAQ3eZzUW0+3/oRM4ip0ujltQy8uU6gvYIAqx5wXGMThVpZcUgahKiSsVo/s4b84iMe4DG3W8jz4qi6yyNv0VedEzPUZ1lXd1GJFoy9uKNuSTe+1ksicAcluZN6LuNsPHcPxFCzOcmoNnVXEKAXInt+ys//5CDVasroZSAHZnDjUD4oNsLI3VIOnGxgXrkwSH0= testkey diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_putty_openssh_unencrypted b/crates/bitwarden-ssh/src/test_keys/rsa_putty_openssh_unencrypted new file mode 100644 index 00000000..bbb8edfe --- /dev/null +++ b/crates/bitwarden-ssh/src/test_keys/rsa_putty_openssh_unencrypted @@ -0,0 +1,30 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdz +c2gtcnNhAAAAAwEAAQAAAQEAootgTLcKjSgPLS2+RT3ZElhktL1CwIyM/+3IqEq0 +0fl/rRHBT8otklV3Ld7DOR50HVZSoV0u9qs0WOdxcjEJlJACDClmxZmFr0BQ/E2y +V5xzuMZj3Mj+fL26jTmM3ueRHZ0tU5ubSFvINIFyDGG70F7VgkpBA8zsviineMSU +t1iIPi/6feL6h7QAFUk6JdQJpPTs9Nb2+DAQ9lMS2614cxLXkfUIXA4NvHMfZGdU +dq1mBJIAWZ4PPJ6naUcu0lVYjuVEAOE4UoHxr6YlW4yyAF/I1YXBFpcHG7P0egvg +neTPli5Wzum0XDsOPivqr6z2E5k7nzyGXUaP5MjRfDDVLwAAA9D6lTpR+pU6UQAA +AAdzc2gtcnNhAAABAQCii2BMtwqNKA8tLb5FPdkSWGS0vULAjIz/7cioSrTR+X+t +EcFPyi2SVXct3sM5HnQdVlKhXS72qzRY53FyMQmUkAIMKWbFmYWvQFD8TbJXnHO4 +xmPcyP58vbqNOYze55EdnS1Tm5tIW8g0gXIMYbvQXtWCSkEDzOy+KKd4xJS3WIg+ +L/p94vqHtAAVSTol1Amk9Oz01vb4MBD2UxLbrXhzEteR9QhcDg28cx9kZ1R2rWYE +kgBZng88nqdpRy7SVViO5UQA4ThSgfGvpiVbjLIAX8jVhcEWlwcbs/R6C+Cd5M+W +LlbO6bRcOw4+K+qvrPYTmTufPIZdRo/kyNF8MNUvAAAAAwEAAQAAAQB6YVPVDq9s +DfA3RMyQF3vbOyA/kIu0q13xx1cflnfD7AT8CnUwnPloxt5fc+wqkko8WGUIRz93 +yvkzwrYAkvkymKZh/734IpmrlFIlVF5lZk8enIhNkCtDQho2AFGW9mSlFlUtMOhe +N3RqS9fRiLg+r1gzq7J9qQnKNpO48tFBpLkIqr8nZOVhEn8IASrQYBUoocClNrv6 +Pdl8ni5jqnZ/0K0nq4+41Ag1VMI4LUcRCucid8ci1HKdOmGXkvClbzuFMWv3UC0k +qDgzg/gOIgj75I7B34FYVx47UGZ6jmC7iRkHd6RiCHYkmsDSjRQHR6eRbtLPdl9w +TlG2NrwkbSlhAAAAgQCapfJLqew9aK8PKfe3FwiC9sb0itCAXPXHhD+pQ6Tl9UMZ +hmnG2g9qbowCprz3/kyix+nWL/Kx7eKAZYH2MBz6cxfqs2A+BSuxvX/hsnvF96BP +u1I47rGrd0NC78DTY2NDO4Ccirx6uN+AoCl4cC+KC00Kykww6TTEBrQsdQTk5QAA +AIEA7JwbIIMwDiQUt3EY/VU0SYvg67aOiyOYEWplSWCGdT58jnfS1H95kGVw+qXR +eSQ0VNv6LBz7XDRpfQlNXDNJRnDZuHBbk+T9ZwnynRLWuzK7VqZBPJoNoyLFSMW2 +DBhLVKIrg0MsBAnRBMDUlVDlzs2LoNLEra3dj8Zb9vMdlbEAAACBAK/db27GfXXg +OikZkIqWiFgBArtj0T4iFc7BLUJUeFtl0RP9LLjfvaxSdA1cmVYzzkgmuc2iZLF0 +37zuPkDrfYVRiw8rSihT3D+WDt3/Tt013WCuxVQOQSW+Qtw6yZpM92DKncbvYsUy +5DNklW1+TYxyn2ltM7SaZjmF8UeoTnDfAAAAEHJzYS1rZXktMjAyNDExMTgBAgME +BQYHCAkK +-----END OPENSSH PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_putty_pkcs1_unencrypted b/crates/bitwarden-ssh/src/test_keys/rsa_putty_pkcs1_unencrypted new file mode 100644 index 00000000..e5bedfbd --- /dev/null +++ b/crates/bitwarden-ssh/src/test_keys/rsa_putty_pkcs1_unencrypted @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAootgTLcKjSgPLS2+RT3ZElhktL1CwIyM/+3IqEq00fl/rRHB +T8otklV3Ld7DOR50HVZSoV0u9qs0WOdxcjEJlJACDClmxZmFr0BQ/E2yV5xzuMZj +3Mj+fL26jTmM3ueRHZ0tU5ubSFvINIFyDGG70F7VgkpBA8zsviineMSUt1iIPi/6 +feL6h7QAFUk6JdQJpPTs9Nb2+DAQ9lMS2614cxLXkfUIXA4NvHMfZGdUdq1mBJIA +WZ4PPJ6naUcu0lVYjuVEAOE4UoHxr6YlW4yyAF/I1YXBFpcHG7P0egvgneTPli5W +zum0XDsOPivqr6z2E5k7nzyGXUaP5MjRfDDVLwIDAQABAoIBAHphU9UOr2wN8DdE +zJAXe9s7ID+Qi7SrXfHHVx+Wd8PsBPwKdTCc+WjG3l9z7CqSSjxYZQhHP3fK+TPC +tgCS+TKYpmH/vfgimauUUiVUXmVmTx6ciE2QK0NCGjYAUZb2ZKUWVS0w6F43dGpL +19GIuD6vWDOrsn2pCco2k7jy0UGkuQiqvydk5WESfwgBKtBgFSihwKU2u/o92Xye +LmOqdn/QrSerj7jUCDVUwjgtRxEK5yJ3xyLUcp06YZeS8KVvO4Uxa/dQLSSoODOD ++A4iCPvkjsHfgVhXHjtQZnqOYLuJGQd3pGIIdiSawNKNFAdHp5Fu0s92X3BOUbY2 +vCRtKWECgYEA7JwbIIMwDiQUt3EY/VU0SYvg67aOiyOYEWplSWCGdT58jnfS1H95 +kGVw+qXReSQ0VNv6LBz7XDRpfQlNXDNJRnDZuHBbk+T9ZwnynRLWuzK7VqZBPJoN +oyLFSMW2DBhLVKIrg0MsBAnRBMDUlVDlzs2LoNLEra3dj8Zb9vMdlbECgYEAr91v +bsZ9deA6KRmQipaIWAECu2PRPiIVzsEtQlR4W2XRE/0suN+9rFJ0DVyZVjPOSCa5 +zaJksXTfvO4+QOt9hVGLDytKKFPcP5YO3f9O3TXdYK7FVA5BJb5C3DrJmkz3YMqd +xu9ixTLkM2SVbX5NjHKfaW0ztJpmOYXxR6hOcN8CgYASLZAb+Fg5zeXVjhfYZrJk +sB1wno7m+64UMHNlpsfNvCY/n88Pyldhk5mReCnWv8RRfLEEsJlTJSexloReAAay +JbtkYyV2AFLDls0P6kGbEjO4XX+Hk2JW1TYI+D+bQEaRUwA6zm9URBjN3661Zgix +0bLXgTnhCgmKoTexik4MkQKBgEZR14XGzlG81+SpOTeBG4F83ffJ4NfkTy395jf4 +iKubGa/Rcvl1VWU7DvZsyU9Dpb8J5Q+JWJPwdKoZ5UCWKPmO8nidSai4Z3/xY352 +4LTpHdzT5UlH7drGqftfck9FaUEFo3LxM2BAiijWlj1S3HVFO+Ku7JbRigCEQ0bw +0HSnAoGBAJql8kup7D1orw8p97cXCIL2xvSK0IBc9ceEP6lDpOX1QxmGacbaD2pu +jAKmvPf+TKLH6dYv8rHt4oBlgfYwHPpzF+qzYD4FK7G9f+Gye8X3oE+7Ujjusat3 +Q0LvwNNjY0M7gJyKvHq434CgKXhwL4oLTQrKTDDpNMQGtCx1BOTl +-----END RSA PRIVATE KEY----- diff --git a/crates/bitwarden-wasm-internal/src/ssh.rs b/crates/bitwarden-wasm-internal/src/ssh.rs index e3ac77bf..500245d0 100644 --- a/crates/bitwarden-wasm-internal/src/ssh.rs +++ b/crates/bitwarden-wasm-internal/src/ssh.rs @@ -2,7 +2,15 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn generate_ssh_key( - key_algorithm: bitwarden_ssh::KeyAlgorithm, -) -> Result { - bitwarden_ssh::generate_sshkey(key_algorithm) + key_algorithm: bitwarden_ssh::generator::KeyAlgorithm, +) -> Result { + bitwarden_ssh::generator::generate_sshkey(key_algorithm) +} + +#[wasm_bindgen] +pub fn import_ssh_key( + imported_key: &str, + password: &str, +) -> Result { + bitwarden_ssh::import::import_key(imported_key.to_string(), password.to_string()) } From 822d0c0a15c5c1460031ab3719c6c3724db9f986 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 16 Dec 2024 12:48:00 +0100 Subject: [PATCH 02/24] Fix tests --- crates/bitwarden-ssh/Cargo.toml | 4 +++- crates/bitwarden-ssh/src/generator.rs | 3 ++- crates/bitwarden-ssh/src/lib.rs | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-ssh/Cargo.toml b/crates/bitwarden-ssh/Cargo.toml index 86ee1608..d4790c1f 100644 --- a/crates/bitwarden-ssh/Cargo.toml +++ b/crates/bitwarden-ssh/Cargo.toml @@ -32,7 +32,9 @@ ssh-key = { version = "0.6.7", features = [ "encryption", "rsa", "getrandom", -] } + "rand_core", + "std", +], default-features = false } thiserror = { workspace = true } tsify-next = { workspace = true, optional = true } wasm-bindgen = { workspace = true, optional = true } diff --git a/crates/bitwarden-ssh/src/generator.rs b/crates/bitwarden-ssh/src/generator.rs index dfcb9097..258905c3 100644 --- a/crates/bitwarden-ssh/src/generator.rs +++ b/crates/bitwarden-ssh/src/generator.rs @@ -59,8 +59,9 @@ fn generate_sshkey_internal( mod tests { use rand::SeedableRng; + use crate::generator::generate_sshkey_internal; + use super::KeyAlgorithm; - use crate::generate_sshkey_internal; #[test] fn generate_ssh_key_ed25519() { diff --git a/crates/bitwarden-ssh/src/lib.rs b/crates/bitwarden-ssh/src/lib.rs index abc5c435..ce5337be 100644 --- a/crates/bitwarden-ssh/src/lib.rs +++ b/crates/bitwarden-ssh/src/lib.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "wasm")] use tsify_next::Tsify; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] #[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] pub struct SshKey { pub private_key: String, From c8e1801437ffe8b0a7431da2a3141169f5c8ae4f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 16 Dec 2024 12:51:44 +0100 Subject: [PATCH 03/24] Run cargo fmt --- crates/bitwarden-ssh/src/generator.rs | 10 +++------- crates/bitwarden-ssh/src/import.rs | 5 ++--- crates/bitwarden-ssh/src/lib.rs | 1 - 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/crates/bitwarden-ssh/src/generator.rs b/crates/bitwarden-ssh/src/generator.rs index 258905c3..4ccdb5e8 100644 --- a/crates/bitwarden-ssh/src/generator.rs +++ b/crates/bitwarden-ssh/src/generator.rs @@ -1,12 +1,9 @@ -use crate::error; -use crate::error::KeyGenerationError; -use crate::SshKey; +use serde::{Deserialize, Serialize}; use ssh_key::{rand_core::CryptoRngCore, Algorithm, HashAlg, LineEnding}; - #[cfg(feature = "wasm")] use tsify_next::Tsify; -use serde::{Deserialize, Serialize}; +use crate::{error, error::KeyGenerationError, SshKey}; #[derive(Serialize, Deserialize)] #[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] @@ -59,9 +56,8 @@ fn generate_sshkey_internal( mod tests { use rand::SeedableRng; - use crate::generator::generate_sshkey_internal; - use super::KeyAlgorithm; + use crate::generator::generate_sshkey_internal; #[test] fn generate_ssh_key_ed25519() { diff --git a/crates/bitwarden-ssh/src/import.rs b/crates/bitwarden-ssh/src/import.rs index cb730dad..af6a43d9 100644 --- a/crates/bitwarden-ssh/src/import.rs +++ b/crates/bitwarden-ssh/src/import.rs @@ -7,12 +7,11 @@ use ssh_key::{ private::{Ed25519Keypair, RsaKeypair}, HashAlg, LineEnding, }; - -use crate::{error::SshKeyImportError, SshKey}; - #[cfg(feature = "wasm")] use tsify_next::Tsify; +use crate::{error::SshKeyImportError, SshKey}; + const PKCS1_HEADER: &str = "-----BEGIN RSA PRIVATE KEY-----"; const PKCS8_UNENCRYPTED_HEADER: &str = "-----BEGIN PRIVATE KEY-----"; const PKCS8_ENCRYPTED_HEADER: &str = "-----BEGIN ENCRYPTED PRIVATE KEY-----"; diff --git a/crates/bitwarden-ssh/src/lib.rs b/crates/bitwarden-ssh/src/lib.rs index ce5337be..c070f127 100644 --- a/crates/bitwarden-ssh/src/lib.rs +++ b/crates/bitwarden-ssh/src/lib.rs @@ -3,7 +3,6 @@ pub mod generator; pub mod import; use serde::{Deserialize, Serialize}; - #[cfg(feature = "wasm")] use tsify_next::Tsify; From 53bf274457b2d6e4ec189f795de2424338305fe5 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 16 Dec 2024 15:12:47 +0100 Subject: [PATCH 04/24] Move test keys and remove unused dep feature --- Cargo.lock | 30 -------------- crates/bitwarden-ssh/Cargo.toml | 3 +- .../ecdsa_openssh_unencrypted | 0 .../ecdsa_openssh_unencrypted.pub | 0 .../ed25519_openssh_encrypted | 0 .../ed25519_openssh_encrypted.pub | 0 .../ed25519_openssh_unencrypted | 0 .../ed25519_openssh_unencrypted.pub | 0 .../ed25519_pkcs8_unencrypted | 0 .../ed25519_pkcs8_unencrypted.pub | 0 .../ed25519_putty_openssh_unencrypted | 0 .../rsa_openssh_encrypted | 0 .../rsa_openssh_encrypted.pub | 0 .../rsa_openssh_unencrypted | 0 .../rsa_openssh_unencrypted.pub | 0 .../rsa_pkcs8_encrypted | 0 .../rsa_pkcs8_encrypted.pub | 0 .../rsa_pkcs8_unencrypted | 0 .../rsa_pkcs8_unencrypted.pub | 0 .../rsa_putty_openssh_unencrypted | 0 .../rsa_putty_pkcs1_unencrypted | 0 crates/bitwarden-ssh/src/import.rs | 39 ++++++++++--------- 22 files changed, 21 insertions(+), 51 deletions(-) rename crates/bitwarden-ssh/{src/test_keys => resources}/ecdsa_openssh_unencrypted (100%) rename crates/bitwarden-ssh/{src/test_keys => resources}/ecdsa_openssh_unencrypted.pub (100%) rename crates/bitwarden-ssh/{src/test_keys => resources}/ed25519_openssh_encrypted (100%) rename crates/bitwarden-ssh/{src/test_keys => resources}/ed25519_openssh_encrypted.pub (100%) rename crates/bitwarden-ssh/{src/test_keys => resources}/ed25519_openssh_unencrypted (100%) rename crates/bitwarden-ssh/{src/test_keys => resources}/ed25519_openssh_unencrypted.pub (100%) rename crates/bitwarden-ssh/{src/test_keys => resources}/ed25519_pkcs8_unencrypted (100%) rename crates/bitwarden-ssh/{src/test_keys => resources}/ed25519_pkcs8_unencrypted.pub (100%) rename crates/bitwarden-ssh/{src/test_keys => resources}/ed25519_putty_openssh_unencrypted (100%) rename crates/bitwarden-ssh/{src/test_keys => resources}/rsa_openssh_encrypted (100%) rename crates/bitwarden-ssh/{src/test_keys => resources}/rsa_openssh_encrypted.pub (100%) rename crates/bitwarden-ssh/{src/test_keys => resources}/rsa_openssh_unencrypted (100%) rename crates/bitwarden-ssh/{src/test_keys => resources}/rsa_openssh_unencrypted.pub (100%) rename crates/bitwarden-ssh/{src/test_keys => resources}/rsa_pkcs8_encrypted (100%) rename crates/bitwarden-ssh/{src/test_keys => resources}/rsa_pkcs8_encrypted.pub (100%) rename crates/bitwarden-ssh/{src/test_keys => resources}/rsa_pkcs8_unencrypted (100%) rename crates/bitwarden-ssh/{src/test_keys => resources}/rsa_pkcs8_unencrypted.pub (100%) rename crates/bitwarden-ssh/{src/test_keys => resources}/rsa_putty_openssh_unencrypted (100%) rename crates/bitwarden-ssh/{src/test_keys => resources}/rsa_putty_pkcs1_unencrypted (100%) diff --git a/Cargo.lock b/Cargo.lock index 048e654e..263213d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2527,32 +2527,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "p384" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - -[[package]] -name = "p521" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" -dependencies = [ - "base16ct", - "ecdsa", - "elliptic-curve", - "primeorder", - "rand_core", - "sha2", -] - [[package]] name = "parking_lot" version = "0.12.3" @@ -3665,12 +3639,8 @@ dependencies = [ "bcrypt-pbkdf", "ed25519-dalek", "num-bigint-dig", - "p256", - "p384", - "p521", "rand_core", "rsa", - "sec1", "sha2", "signature", "ssh-cipher", diff --git a/crates/bitwarden-ssh/Cargo.toml b/crates/bitwarden-ssh/Cargo.toml index d4790c1f..0e2a2b34 100644 --- a/crates/bitwarden-ssh/Cargo.toml +++ b/crates/bitwarden-ssh/Cargo.toml @@ -4,6 +4,7 @@ description = """ Internal crate for the bitwarden crate. Do not use. """ +exclude = ["/resources"] version.workspace = true authors.workspace = true edition.workspace = true @@ -32,8 +33,6 @@ ssh-key = { version = "0.6.7", features = [ "encryption", "rsa", "getrandom", - "rand_core", - "std", ], default-features = false } thiserror = { workspace = true } tsify-next = { workspace = true, optional = true } diff --git a/crates/bitwarden-ssh/src/test_keys/ecdsa_openssh_unencrypted b/crates/bitwarden-ssh/resources/ecdsa_openssh_unencrypted similarity index 100% rename from crates/bitwarden-ssh/src/test_keys/ecdsa_openssh_unencrypted rename to crates/bitwarden-ssh/resources/ecdsa_openssh_unencrypted diff --git a/crates/bitwarden-ssh/src/test_keys/ecdsa_openssh_unencrypted.pub b/crates/bitwarden-ssh/resources/ecdsa_openssh_unencrypted.pub similarity index 100% rename from crates/bitwarden-ssh/src/test_keys/ecdsa_openssh_unencrypted.pub rename to crates/bitwarden-ssh/resources/ecdsa_openssh_unencrypted.pub diff --git a/crates/bitwarden-ssh/src/test_keys/ed25519_openssh_encrypted b/crates/bitwarden-ssh/resources/ed25519_openssh_encrypted similarity index 100% rename from crates/bitwarden-ssh/src/test_keys/ed25519_openssh_encrypted rename to crates/bitwarden-ssh/resources/ed25519_openssh_encrypted diff --git a/crates/bitwarden-ssh/src/test_keys/ed25519_openssh_encrypted.pub b/crates/bitwarden-ssh/resources/ed25519_openssh_encrypted.pub similarity index 100% rename from crates/bitwarden-ssh/src/test_keys/ed25519_openssh_encrypted.pub rename to crates/bitwarden-ssh/resources/ed25519_openssh_encrypted.pub diff --git a/crates/bitwarden-ssh/src/test_keys/ed25519_openssh_unencrypted b/crates/bitwarden-ssh/resources/ed25519_openssh_unencrypted similarity index 100% rename from crates/bitwarden-ssh/src/test_keys/ed25519_openssh_unencrypted rename to crates/bitwarden-ssh/resources/ed25519_openssh_unencrypted diff --git a/crates/bitwarden-ssh/src/test_keys/ed25519_openssh_unencrypted.pub b/crates/bitwarden-ssh/resources/ed25519_openssh_unencrypted.pub similarity index 100% rename from crates/bitwarden-ssh/src/test_keys/ed25519_openssh_unencrypted.pub rename to crates/bitwarden-ssh/resources/ed25519_openssh_unencrypted.pub diff --git a/crates/bitwarden-ssh/src/test_keys/ed25519_pkcs8_unencrypted b/crates/bitwarden-ssh/resources/ed25519_pkcs8_unencrypted similarity index 100% rename from crates/bitwarden-ssh/src/test_keys/ed25519_pkcs8_unencrypted rename to crates/bitwarden-ssh/resources/ed25519_pkcs8_unencrypted diff --git a/crates/bitwarden-ssh/src/test_keys/ed25519_pkcs8_unencrypted.pub b/crates/bitwarden-ssh/resources/ed25519_pkcs8_unencrypted.pub similarity index 100% rename from crates/bitwarden-ssh/src/test_keys/ed25519_pkcs8_unencrypted.pub rename to crates/bitwarden-ssh/resources/ed25519_pkcs8_unencrypted.pub diff --git a/crates/bitwarden-ssh/src/test_keys/ed25519_putty_openssh_unencrypted b/crates/bitwarden-ssh/resources/ed25519_putty_openssh_unencrypted similarity index 100% rename from crates/bitwarden-ssh/src/test_keys/ed25519_putty_openssh_unencrypted rename to crates/bitwarden-ssh/resources/ed25519_putty_openssh_unencrypted diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_openssh_encrypted b/crates/bitwarden-ssh/resources/rsa_openssh_encrypted similarity index 100% rename from crates/bitwarden-ssh/src/test_keys/rsa_openssh_encrypted rename to crates/bitwarden-ssh/resources/rsa_openssh_encrypted diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_openssh_encrypted.pub b/crates/bitwarden-ssh/resources/rsa_openssh_encrypted.pub similarity index 100% rename from crates/bitwarden-ssh/src/test_keys/rsa_openssh_encrypted.pub rename to crates/bitwarden-ssh/resources/rsa_openssh_encrypted.pub diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_openssh_unencrypted b/crates/bitwarden-ssh/resources/rsa_openssh_unencrypted similarity index 100% rename from crates/bitwarden-ssh/src/test_keys/rsa_openssh_unencrypted rename to crates/bitwarden-ssh/resources/rsa_openssh_unencrypted diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_openssh_unencrypted.pub b/crates/bitwarden-ssh/resources/rsa_openssh_unencrypted.pub similarity index 100% rename from crates/bitwarden-ssh/src/test_keys/rsa_openssh_unencrypted.pub rename to crates/bitwarden-ssh/resources/rsa_openssh_unencrypted.pub diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_encrypted b/crates/bitwarden-ssh/resources/rsa_pkcs8_encrypted similarity index 100% rename from crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_encrypted rename to crates/bitwarden-ssh/resources/rsa_pkcs8_encrypted diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_encrypted.pub b/crates/bitwarden-ssh/resources/rsa_pkcs8_encrypted.pub similarity index 100% rename from crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_encrypted.pub rename to crates/bitwarden-ssh/resources/rsa_pkcs8_encrypted.pub diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_unencrypted b/crates/bitwarden-ssh/resources/rsa_pkcs8_unencrypted similarity index 100% rename from crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_unencrypted rename to crates/bitwarden-ssh/resources/rsa_pkcs8_unencrypted diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_unencrypted.pub b/crates/bitwarden-ssh/resources/rsa_pkcs8_unencrypted.pub similarity index 100% rename from crates/bitwarden-ssh/src/test_keys/rsa_pkcs8_unencrypted.pub rename to crates/bitwarden-ssh/resources/rsa_pkcs8_unencrypted.pub diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_putty_openssh_unencrypted b/crates/bitwarden-ssh/resources/rsa_putty_openssh_unencrypted similarity index 100% rename from crates/bitwarden-ssh/src/test_keys/rsa_putty_openssh_unencrypted rename to crates/bitwarden-ssh/resources/rsa_putty_openssh_unencrypted diff --git a/crates/bitwarden-ssh/src/test_keys/rsa_putty_pkcs1_unencrypted b/crates/bitwarden-ssh/resources/rsa_putty_pkcs1_unencrypted similarity index 100% rename from crates/bitwarden-ssh/src/test_keys/rsa_putty_pkcs1_unencrypted rename to crates/bitwarden-ssh/resources/rsa_putty_pkcs1_unencrypted diff --git a/crates/bitwarden-ssh/src/import.rs b/crates/bitwarden-ssh/src/import.rs index af6a43d9..4247efea 100644 --- a/crates/bitwarden-ssh/src/import.rs +++ b/crates/bitwarden-ssh/src/import.rs @@ -161,41 +161,41 @@ mod tests { #[test] fn import_key_ed25519_openssh_unencrypted() { - let private_key = include_str!("./test_keys/ed25519_openssh_unencrypted"); - let public_key = include_str!("./test_keys/ed25519_openssh_unencrypted.pub").trim(); + let private_key = include_str!("../resources/ed25519_openssh_unencrypted"); + let public_key = include_str!("../resources/ed25519_openssh_unencrypted.pub").trim(); let result = import_key(private_key.to_string(), "".to_string()).unwrap(); assert_eq!(result.public_key, public_key); } #[test] fn import_key_ed25519_openssh_encrypted() { - let private_key = include_str!("./test_keys/ed25519_openssh_encrypted"); - let public_key = include_str!("./test_keys/ed25519_openssh_encrypted.pub").trim(); + let private_key = include_str!("../resources/ed25519_openssh_encrypted"); + let public_key = include_str!("../resources/ed25519_openssh_encrypted.pub").trim(); let result = import_key(private_key.to_string(), "password".to_string()).unwrap(); assert_eq!(result.public_key, public_key); } #[test] fn import_key_rsa_openssh_unencrypted() { - let private_key = include_str!("./test_keys/rsa_openssh_unencrypted"); - let public_key = include_str!("./test_keys/rsa_openssh_unencrypted.pub").trim(); + let private_key = include_str!("../resources/rsa_openssh_unencrypted"); + let public_key = include_str!("../resources/rsa_openssh_unencrypted.pub").trim(); let result = import_key(private_key.to_string(), "".to_string()).unwrap(); assert_eq!(result.public_key, public_key); } #[test] fn import_key_rsa_openssh_encrypted() { - let private_key = include_str!("./test_keys/rsa_openssh_encrypted"); - let public_key = include_str!("./test_keys/rsa_openssh_encrypted.pub").trim(); + let private_key = include_str!("../resources/rsa_openssh_encrypted"); + let public_key = include_str!("../resources/rsa_openssh_encrypted.pub").trim(); let result = import_key(private_key.to_string(), "password".to_string()).unwrap(); assert_eq!(result.public_key, public_key); } #[test] fn import_key_ed25519_pkcs8_unencrypted() { - let private_key = include_str!("./test_keys/ed25519_pkcs8_unencrypted"); + let private_key = include_str!("../resources/ed25519_pkcs8_unencrypted"); let public_key = - include_str!("./test_keys/ed25519_pkcs8_unencrypted.pub").replace("testkey", ""); + include_str!("../resources/ed25519_pkcs8_unencrypted.pub").replace("testkey", ""); let public_key = public_key.trim(); let result = import_key(private_key.to_string(), "".to_string()).unwrap(); assert_eq!(result.public_key, public_key); @@ -203,10 +203,10 @@ mod tests { #[test] fn import_key_rsa_pkcs8_unencrypted() { - let private_key = include_str!("./test_keys/rsa_pkcs8_unencrypted"); + let private_key = include_str!("../resources/rsa_pkcs8_unencrypted"); // for whatever reason pkcs8 + rsa does not include the comment in the public key let public_key = - include_str!("./test_keys/rsa_pkcs8_unencrypted.pub").replace("testkey", ""); + include_str!("../resources/rsa_pkcs8_unencrypted.pub").replace("testkey", ""); let public_key = public_key.trim(); let result = import_key(private_key.to_string(), "".to_string()).unwrap(); assert_eq!(result.public_key, public_key); @@ -214,8 +214,9 @@ mod tests { #[test] fn import_key_rsa_pkcs8_encrypted() { - let private_key = include_str!("./test_keys/rsa_pkcs8_encrypted"); - let public_key = include_str!("./test_keys/rsa_pkcs8_encrypted.pub").replace("testkey", ""); + let private_key = include_str!("../resources/rsa_pkcs8_encrypted"); + let public_key = + include_str!("../resources/rsa_pkcs8_encrypted.pub").replace("testkey", ""); let public_key = public_key.trim(); let result = import_key(private_key.to_string(), "password".to_string()).unwrap(); assert_eq!(result.public_key, public_key); @@ -223,7 +224,7 @@ mod tests { #[test] fn import_key_ed25519_openssh_encrypted_wrong_password() { - let private_key = include_str!("./test_keys/ed25519_openssh_encrypted"); + let private_key = include_str!("../resources/ed25519_openssh_encrypted"); let result = import_key(private_key.to_string(), "wrongpassword".to_string()); assert_eq!(result.unwrap_err(), SshKeyImportError::WrongPassword); } @@ -236,7 +237,7 @@ mod tests { #[test] fn import_ecdsa_error() { - let private_key = include_str!("./test_keys/ecdsa_openssh_unencrypted"); + let private_key = include_str!("../resources/ecdsa_openssh_unencrypted"); let result = import_key(private_key.to_string(), "".to_string()); assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType); } @@ -247,7 +248,7 @@ mod tests { // https://bitwarden.atlassian.net/browse/PM-14989 #[test] fn import_key_ed25519_putty() { - let private_key = include_str!("./test_keys/ed25519_putty_openssh_unencrypted"); + let private_key = include_str!("../resources/ed25519_putty_openssh_unencrypted"); let result = import_key(private_key.to_string(), "".to_string()); assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError); } @@ -258,14 +259,14 @@ mod tests { // https://bitwarden.atlassian.net/browse/PM-14989 #[test] fn import_key_rsa_openssh_putty() { - let private_key = include_str!("./test_keys/rsa_putty_openssh_unencrypted"); + let private_key = include_str!("../resources/rsa_putty_openssh_unencrypted"); let result = import_key(private_key.to_string(), "".to_string()); assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError); } #[test] fn import_key_rsa_pkcs8_putty() { - let private_key = include_str!("./test_keys/rsa_putty_pkcs1_unencrypted"); + let private_key = include_str!("../resources/rsa_putty_pkcs1_unencrypted"); let result = import_key(private_key.to_string(), "".to_string()); assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType); } From b2b974032a7a7b0173120ed005bd9ab5a1237145 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 16 Dec 2024 17:55:26 +0100 Subject: [PATCH 05/24] Fix linting and clean up deps --- crates/bitwarden-ssh/Cargo.toml | 13 ++++++------- crates/bitwarden-ssh/src/import.rs | 15 +++++++++------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/crates/bitwarden-ssh/Cargo.toml b/crates/bitwarden-ssh/Cargo.toml index 0e2a2b34..07076d20 100644 --- a/crates/bitwarden-ssh/Cargo.toml +++ b/crates/bitwarden-ssh/Cargo.toml @@ -18,21 +18,20 @@ keywords.workspace = true wasm = [ "bitwarden-error/wasm", "dep:tsify-next", - "dep:wasm-bindgen" + "dep:wasm-bindgen", ] # WASM support [dependencies] bitwarden-error = { workspace = true } -ed25519 = { version = "2.2.3", features = ["pkcs8"] } -pkcs8 = { version = "0.10.2", features = ["encryption"] } -rand = "0.8.5" -rsa = "0.9.7" +ed25519 = { version = ">=2.2.3, <3.0", features = ["pkcs8"] } +pkcs8 = { version = ">=0.10.2, <0.11", features = ["encryption"] } +rand = ">=0.8.5, <0.9" +rsa = ">=0.9.2, <0.10" serde.workspace = true -ssh-key = { version = "0.6.7", features = [ +ssh-key = { version = ">=0.6.7, <0.7", features = [ "ed25519", "encryption", "rsa", - "getrandom", ], default-features = false } thiserror = { workspace = true } tsify-next = { workspace = true, optional = true } diff --git a/crates/bitwarden-ssh/src/import.rs b/crates/bitwarden-ssh/src/import.rs index 4247efea..8f9fd575 100644 --- a/crates/bitwarden-ssh/src/import.rs +++ b/crates/bitwarden-ssh/src/import.rs @@ -88,11 +88,11 @@ fn import_pkcs8_key( let private_key = ssh_key::private::PrivateKey::from(Ed25519Keypair::from( &private_key.secret_key.into(), )); - return Ok(SshKey { + Ok(SshKey { private_key: private_key.to_openssh(LineEnding::LF).unwrap().to_string(), public_key: private_key.public_key().to_string(), key_fingerprint: private_key.fingerprint(HashAlg::Sha256).to_string(), - }); + }) } KeyType::Rsa => { let private_key: rsa::RsaPrivateKey = match password { @@ -112,13 +112,16 @@ fn import_pkcs8_key( let private_key = ssh_key::private::PrivateKey::from( RsaKeypair::try_from(private_key).map_err(|_| SshKeyImportError::ParsingError)?, ); - return Ok(SshKey { - private_key: private_key.to_openssh(LineEnding::LF).unwrap().to_string(), + let private_key_openssh = private_key + .to_openssh(LineEnding::LF) + .map_err(|_| SshKeyImportError::ParsingError)?; + Ok(SshKey { + private_key: private_key_openssh.to_string(), public_key: private_key.public_key().to_string(), key_fingerprint: private_key.fingerprint(HashAlg::Sha256).to_string(), - }); + }) } - _ => return Err(SshKeyImportError::UnsupportedKeyType), + _ => Err(SshKeyImportError::UnsupportedKeyType), } } From 799bc008edf868baa2b7626488a5bd9c2536a0d0 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 16 Dec 2024 18:00:35 +0100 Subject: [PATCH 06/24] Remove unwrap --- crates/bitwarden-ssh/src/import.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/bitwarden-ssh/src/import.rs b/crates/bitwarden-ssh/src/import.rs index 8f9fd575..2c61d555 100644 --- a/crates/bitwarden-ssh/src/import.rs +++ b/crates/bitwarden-ssh/src/import.rs @@ -88,8 +88,11 @@ fn import_pkcs8_key( let private_key = ssh_key::private::PrivateKey::from(Ed25519Keypair::from( &private_key.secret_key.into(), )); + let private_key_openssh = private_key + .to_openssh(LineEnding::LF) + .map_err(|_| SshKeyImportError::ParsingError)?; Ok(SshKey { - private_key: private_key.to_openssh(LineEnding::LF).unwrap().to_string(), + private_key: private_key_openssh.to_string(), public_key: private_key.public_key().to_string(), key_fingerprint: private_key.fingerprint(HashAlg::Sha256).to_string(), }) From 389d77623e5f23061f53fa880ecde20f4aec20ff Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 16 Dec 2024 19:13:56 +0100 Subject: [PATCH 07/24] Fix formatting --- Cargo.lock | 11 ++++++++++- crates/bitwarden-ssh/Cargo.toml | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4be90557..2db41498 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -588,7 +588,6 @@ dependencies = [ "rand", "rand_chacha", "rsa", - "serde", "ssh-key", "thiserror 1.0.69", "tsify-next", @@ -2577,6 +2576,16 @@ dependencies = [ "sha2", ] +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "elliptic-curve", + "primeorder", +] + [[package]] name = "parking_lot" version = "0.12.3" diff --git a/crates/bitwarden-ssh/Cargo.toml b/crates/bitwarden-ssh/Cargo.toml index 07076d20..77e7e366 100644 --- a/crates/bitwarden-ssh/Cargo.toml +++ b/crates/bitwarden-ssh/Cargo.toml @@ -3,8 +3,8 @@ name = "bitwarden-ssh" description = """ Internal crate for the bitwarden crate. Do not use. """ - exclude = ["/resources"] + version.workspace = true authors.workspace = true edition.workspace = true @@ -13,6 +13,7 @@ homepage.workspace = true repository.workspace = true license-file.workspace = true keywords.workspace = true +serde.workspace = true [features] wasm = [ @@ -27,7 +28,6 @@ ed25519 = { version = ">=2.2.3, <3.0", features = ["pkcs8"] } pkcs8 = { version = ">=0.10.2, <0.11", features = ["encryption"] } rand = ">=0.8.5, <0.9" rsa = ">=0.9.2, <0.10" -serde.workspace = true ssh-key = { version = ">=0.6.7, <0.7", features = [ "ed25519", "encryption", From b21255af20a1d612e83d5b73ed87ee54194a6e24 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 16 Dec 2024 19:16:08 +0100 Subject: [PATCH 08/24] Undo change to serde.workspace --- crates/bitwarden-ssh/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-ssh/Cargo.toml b/crates/bitwarden-ssh/Cargo.toml index 77e7e366..73172ead 100644 --- a/crates/bitwarden-ssh/Cargo.toml +++ b/crates/bitwarden-ssh/Cargo.toml @@ -13,7 +13,6 @@ homepage.workspace = true repository.workspace = true license-file.workspace = true keywords.workspace = true -serde.workspace = true [features] wasm = [ @@ -28,6 +27,7 @@ ed25519 = { version = ">=2.2.3, <3.0", features = ["pkcs8"] } pkcs8 = { version = ">=0.10.2, <0.11", features = ["encryption"] } rand = ">=0.8.5, <0.9" rsa = ">=0.9.2, <0.10" +serde.workspace = true ssh-key = { version = ">=0.6.7, <0.7", features = [ "ed25519", "encryption", From 2af2a63d4d09b5f289ea6e477bc990e6e1a4dc07 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 16 Dec 2024 19:24:27 +0100 Subject: [PATCH 09/24] Fix cargo sort --- Cargo.lock | 1 + crates/bitwarden-ssh/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 2db41498..a787ba5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -588,6 +588,7 @@ dependencies = [ "rand", "rand_chacha", "rsa", + "serde", "ssh-key", "thiserror 1.0.69", "tsify-next", diff --git a/crates/bitwarden-ssh/Cargo.toml b/crates/bitwarden-ssh/Cargo.toml index 73172ead..a4c33360 100644 --- a/crates/bitwarden-ssh/Cargo.toml +++ b/crates/bitwarden-ssh/Cargo.toml @@ -18,7 +18,7 @@ keywords.workspace = true wasm = [ "bitwarden-error/wasm", "dep:tsify-next", - "dep:wasm-bindgen", + "dep:wasm-bindgen" ] # WASM support [dependencies] From 3eab5598cd0ca19cc655d3d4512a4fc650d6e47e Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 18 Dec 2024 13:38:16 +0100 Subject: [PATCH 10/24] Cleanup according to feedback --- crates/bitwarden-ssh/src/import.rs | 158 +++++++++++------------------ crates/bitwarden-ssh/src/lib.rs | 19 ++++ 2 files changed, 76 insertions(+), 101 deletions(-) diff --git a/crates/bitwarden-ssh/src/import.rs b/crates/bitwarden-ssh/src/import.rs index 2c61d555..27f260c7 100644 --- a/crates/bitwarden-ssh/src/import.rs +++ b/crates/bitwarden-ssh/src/import.rs @@ -3,10 +3,7 @@ use pkcs8::{ der::Decode, EncryptedPrivateKeyInfo, ObjectIdentifier, PrivateKeyInfo, SecretDocument, }; use serde::{Deserialize, Serialize}; -use ssh_key::{ - private::{Ed25519Keypair, RsaKeypair}, - HashAlg, LineEnding, -}; +use ssh_key::private::{Ed25519Keypair, RsaKeypair}; #[cfg(feature = "wasm")] use tsify_next::Tsify; @@ -28,13 +25,16 @@ enum KeyType { Unknown, } -pub fn import_key(encoded_key: String, password: String) -> Result { +pub fn import_key( + encoded_key: String, + password: Option, +) -> Result { match encoded_key.lines().next() { Some(PKCS1_HEADER) => Err(SshKeyImportError::UnsupportedKeyType), Some(PKCS8_UNENCRYPTED_HEADER) => { import_pkcs8_key(encoded_key, None).map_err(|_| SshKeyImportError::ParsingError) } - Some(PKCS8_ENCRYPTED_HEADER) => import_pkcs8_key(encoded_key, Some(password)), + Some(PKCS8_ENCRYPTED_HEADER) => import_pkcs8_key(encoded_key, password), Some(OPENSSH_HEADER) => import_openssh_key(encoded_key, password), _ => Err(SshKeyImportError::ParsingError), } @@ -60,105 +60,61 @@ fn import_pkcs8_key( None => der, }; - let key_type: KeyType = match PrivateKeyInfo::from_der(decrypted_der.as_bytes()) - .map_err(|_| SshKeyImportError::ParsingError)? - .algorithm - .oid - { + let private_key_info = PrivateKeyInfo::from_der(decrypted_der.as_bytes()) + .map_err(|_| SshKeyImportError::ParsingError)?; + + let key_type: KeyType = match private_key_info.algorithm.oid { ed25519::pkcs8::ALGORITHM_OID => KeyType::Ed25519, RSA_PKCS8_ALGORITHM_OID => KeyType::Rsa, _ => KeyType::Unknown, }; - match key_type { + let private_key = match key_type { KeyType::Ed25519 => { - let private_key: ed25519::KeypairBytes = match password { - Some(password) => { - pkcs8::DecodePrivateKey::from_pkcs8_encrypted_pem(&encoded_key, password) - .map_err(|err| match err { - ed25519::pkcs8::Error::EncryptedPrivateKey(_) => { - SshKeyImportError::WrongPassword - } - _ => SshKeyImportError::ParsingError, - })? - } - None => ed25519::pkcs8::DecodePrivateKey::from_pkcs8_pem(&encoded_key) - .map_err(|_| SshKeyImportError::ParsingError)?, - }; - let private_key = ssh_key::private::PrivateKey::from(Ed25519Keypair::from( - &private_key.secret_key.into(), - )); - let private_key_openssh = private_key - .to_openssh(LineEnding::LF) + let private_key: ed25519::KeypairBytes = private_key_info + .try_into() .map_err(|_| SshKeyImportError::ParsingError)?; - Ok(SshKey { - private_key: private_key_openssh.to_string(), - public_key: private_key.public_key().to_string(), - key_fingerprint: private_key.fingerprint(HashAlg::Sha256).to_string(), - }) + ssh_key::private::PrivateKey::from(Ed25519Keypair::from(&private_key.secret_key.into())) } KeyType::Rsa => { - let private_key: rsa::RsaPrivateKey = match password { - Some(password) => { - pkcs8::DecodePrivateKey::from_pkcs8_encrypted_pem(&encoded_key, password) - .map_err(|err| match err { - pkcs8::Error::EncryptedPrivateKey(_) => { - SshKeyImportError::WrongPassword - } - _ => SshKeyImportError::ParsingError, - })? - } - None => pkcs8::DecodePrivateKey::from_pkcs8_pem(&encoded_key) - .map_err(|_| SshKeyImportError::ParsingError)?, - }; + let private_key: rsa::RsaPrivateKey = private_key_info + .try_into() + .map_err(|_| SshKeyImportError::ParsingError)?; - let private_key = ssh_key::private::PrivateKey::from( + ssh_key::private::PrivateKey::from( RsaKeypair::try_from(private_key).map_err(|_| SshKeyImportError::ParsingError)?, - ); - let private_key_openssh = private_key - .to_openssh(LineEnding::LF) - .map_err(|_| SshKeyImportError::ParsingError)?; - Ok(SshKey { - private_key: private_key_openssh.to_string(), - public_key: private_key.public_key().to_string(), - key_fingerprint: private_key.fingerprint(HashAlg::Sha256).to_string(), - }) + ) } - _ => Err(SshKeyImportError::UnsupportedKeyType), - } + _ => return Err(SshKeyImportError::UnsupportedKeyType), + }; + + private_key.try_into() } -fn import_openssh_key(encoded_key: String, password: String) -> Result { - let private_key = match ssh_key::private::PrivateKey::from_openssh(&encoded_key) { - Ok(k) => k, - Err(err) => match err { - ssh_key::Error::AlgorithmUnknown - | ssh_key::Error::AlgorithmUnsupported { algorithm: _ } => { - return Err(SshKeyImportError::UnsupportedKeyType) +fn import_openssh_key( + encoded_key: String, + password: Option, +) -> Result { + let private_key = + ssh_key::private::PrivateKey::from_openssh(&encoded_key).map_err(|err| match err { + ssh_key::Error::AlgorithmUnknown | ssh_key::Error::AlgorithmUnsupported { .. } => { + return SshKeyImportError::UnsupportedKeyType } - _ => return Err(SshKeyImportError::ParsingError), - }, - }; + _ => return SshKeyImportError::ParsingError, + })?; - if private_key.is_encrypted() && password.is_empty() { - return Err(SshKeyImportError::PasswordRequired); - } - let private_key = if private_key.is_encrypted() { - private_key - .decrypt(password.as_bytes()) - .map_err(|_| SshKeyImportError::WrongPassword)? + if private_key.is_encrypted() { + if let Some(password) = password { + private_key + .decrypt(password.as_bytes()) + .map_err(|_| SshKeyImportError::WrongPassword)? + .try_into() + } else { + return Err(SshKeyImportError::PasswordRequired); + } } else { - private_key - }; - let private_key_openssh = private_key - .to_openssh(LineEnding::LF) - .map_err(|_| SshKeyImportError::ParsingError)?; - - Ok(SshKey { - private_key: private_key_openssh.to_string(), - public_key: private_key.public_key().to_string(), - key_fingerprint: private_key.fingerprint(HashAlg::Sha256).to_string(), - }) + private_key.try_into() + } } #[cfg(test)] @@ -169,7 +125,7 @@ mod tests { fn import_key_ed25519_openssh_unencrypted() { let private_key = include_str!("../resources/ed25519_openssh_unencrypted"); let public_key = include_str!("../resources/ed25519_openssh_unencrypted.pub").trim(); - let result = import_key(private_key.to_string(), "".to_string()).unwrap(); + let result = import_key(private_key.to_string(), Some("".to_string())).unwrap(); assert_eq!(result.public_key, public_key); } @@ -177,7 +133,7 @@ mod tests { fn import_key_ed25519_openssh_encrypted() { let private_key = include_str!("../resources/ed25519_openssh_encrypted"); let public_key = include_str!("../resources/ed25519_openssh_encrypted.pub").trim(); - let result = import_key(private_key.to_string(), "password".to_string()).unwrap(); + let result = import_key(private_key.to_string(), Some("password".to_string())).unwrap(); assert_eq!(result.public_key, public_key); } @@ -185,7 +141,7 @@ mod tests { fn import_key_rsa_openssh_unencrypted() { let private_key = include_str!("../resources/rsa_openssh_unencrypted"); let public_key = include_str!("../resources/rsa_openssh_unencrypted.pub").trim(); - let result = import_key(private_key.to_string(), "".to_string()).unwrap(); + let result = import_key(private_key.to_string(), Some("".to_string())).unwrap(); assert_eq!(result.public_key, public_key); } @@ -193,7 +149,7 @@ mod tests { fn import_key_rsa_openssh_encrypted() { let private_key = include_str!("../resources/rsa_openssh_encrypted"); let public_key = include_str!("../resources/rsa_openssh_encrypted.pub").trim(); - let result = import_key(private_key.to_string(), "password".to_string()).unwrap(); + let result = import_key(private_key.to_string(), Some("password".to_string())).unwrap(); assert_eq!(result.public_key, public_key); } @@ -203,7 +159,7 @@ mod tests { let public_key = include_str!("../resources/ed25519_pkcs8_unencrypted.pub").replace("testkey", ""); let public_key = public_key.trim(); - let result = import_key(private_key.to_string(), "".to_string()).unwrap(); + let result = import_key(private_key.to_string(), Some("".to_string())).unwrap(); assert_eq!(result.public_key, public_key); } @@ -214,7 +170,7 @@ mod tests { let public_key = include_str!("../resources/rsa_pkcs8_unencrypted.pub").replace("testkey", ""); let public_key = public_key.trim(); - let result = import_key(private_key.to_string(), "".to_string()).unwrap(); + let result = import_key(private_key.to_string(), Some("".to_string())).unwrap(); assert_eq!(result.public_key, public_key); } @@ -224,27 +180,27 @@ mod tests { let public_key = include_str!("../resources/rsa_pkcs8_encrypted.pub").replace("testkey", ""); let public_key = public_key.trim(); - let result = import_key(private_key.to_string(), "password".to_string()).unwrap(); + let result = import_key(private_key.to_string(), Some("password".to_string())).unwrap(); assert_eq!(result.public_key, public_key); } #[test] fn import_key_ed25519_openssh_encrypted_wrong_password() { let private_key = include_str!("../resources/ed25519_openssh_encrypted"); - let result = import_key(private_key.to_string(), "wrongpassword".to_string()); + let result = import_key(private_key.to_string(), Some("wrongpassword".to_string())); assert_eq!(result.unwrap_err(), SshKeyImportError::WrongPassword); } #[test] fn import_non_key_error() { - let result = import_key("not a key".to_string(), "".to_string()); + let result = import_key("not a key".to_string(), Some("".to_string())); assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError); } #[test] fn import_ecdsa_error() { let private_key = include_str!("../resources/ecdsa_openssh_unencrypted"); - let result = import_key(private_key.to_string(), "".to_string()); + let result = import_key(private_key.to_string(), Some("".to_string())); assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType); } @@ -255,7 +211,7 @@ mod tests { #[test] fn import_key_ed25519_putty() { let private_key = include_str!("../resources/ed25519_putty_openssh_unencrypted"); - let result = import_key(private_key.to_string(), "".to_string()); + let result = import_key(private_key.to_string(), Some("".to_string())); assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError); } @@ -266,14 +222,14 @@ mod tests { #[test] fn import_key_rsa_openssh_putty() { let private_key = include_str!("../resources/rsa_putty_openssh_unencrypted"); - let result = import_key(private_key.to_string(), "".to_string()); + let result = import_key(private_key.to_string(), Some("".to_string())); assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError); } #[test] fn import_key_rsa_pkcs8_putty() { let private_key = include_str!("../resources/rsa_putty_pkcs1_unencrypted"); - let result = import_key(private_key.to_string(), "".to_string()); + let result = import_key(private_key.to_string(), Some("".to_string())); assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType); } } diff --git a/crates/bitwarden-ssh/src/lib.rs b/crates/bitwarden-ssh/src/lib.rs index c070f127..2d6eb43c 100644 --- a/crates/bitwarden-ssh/src/lib.rs +++ b/crates/bitwarden-ssh/src/lib.rs @@ -2,7 +2,10 @@ pub mod error; pub mod generator; pub mod import; +use error::SshKeyImportError; +use pkcs8::LineEnding; use serde::{Deserialize, Serialize}; +use ssh_key::{HashAlg, PrivateKey}; #[cfg(feature = "wasm")] use tsify_next::Tsify; @@ -13,3 +16,19 @@ pub struct SshKey { pub public_key: String, pub key_fingerprint: String, } + +impl TryFrom for SshKey { + type Error = SshKeyImportError; + + fn try_from(value: PrivateKey) -> Result { + let private_key_openssh = value + .to_openssh(LineEnding::LF) + .map_err(|_| SshKeyImportError::ParsingError)?; + + Ok(SshKey { + private_key: private_key_openssh.to_string(), + public_key: value.public_key().to_string(), + key_fingerprint: value.fingerprint(HashAlg::Sha256).to_string(), + }) + } +} From 3341c5b0ada07ba596f9e1df0bcc8e37e2dde131 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 18 Dec 2024 13:48:46 +0100 Subject: [PATCH 11/24] Further cleanup --- crates/bitwarden-ssh/src/import.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-ssh/src/import.rs b/crates/bitwarden-ssh/src/import.rs index 27f260c7..e3031bd3 100644 --- a/crates/bitwarden-ssh/src/import.rs +++ b/crates/bitwarden-ssh/src/import.rs @@ -98,9 +98,9 @@ fn import_openssh_key( let private_key = ssh_key::private::PrivateKey::from_openssh(&encoded_key).map_err(|err| match err { ssh_key::Error::AlgorithmUnknown | ssh_key::Error::AlgorithmUnsupported { .. } => { - return SshKeyImportError::UnsupportedKeyType + SshKeyImportError::UnsupportedKeyType } - _ => return SshKeyImportError::ParsingError, + _ => SshKeyImportError::ParsingError, })?; if private_key.is_encrypted() { @@ -110,7 +110,7 @@ fn import_openssh_key( .map_err(|_| SshKeyImportError::WrongPassword)? .try_into() } else { - return Err(SshKeyImportError::PasswordRequired); + Err(SshKeyImportError::PasswordRequired) } } else { private_key.try_into() From 1172d033af494fb2eff66833589bbaba26150216 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 18 Dec 2024 14:11:07 +0100 Subject: [PATCH 12/24] Cleanup secret document parsing for pkcs8 --- crates/bitwarden-ssh/src/import.rs | 43 +++++++++++++----------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/crates/bitwarden-ssh/src/import.rs b/crates/bitwarden-ssh/src/import.rs index e3031bd3..dbf5e200 100644 --- a/crates/bitwarden-ssh/src/import.rs +++ b/crates/bitwarden-ssh/src/import.rs @@ -1,7 +1,5 @@ use ed25519; -use pkcs8::{ - der::Decode, EncryptedPrivateKeyInfo, ObjectIdentifier, PrivateKeyInfo, SecretDocument, -}; +use pkcs8::{der::Decode, pkcs5, DecodePrivateKey, PrivateKeyInfo, SecretDocument}; use serde::{Deserialize, Serialize}; use ssh_key::private::{Ed25519Keypair, RsaKeypair}; #[cfg(feature = "wasm")] @@ -14,9 +12,6 @@ const PKCS8_UNENCRYPTED_HEADER: &str = "-----BEGIN PRIVATE KEY-----"; const PKCS8_ENCRYPTED_HEADER: &str = "-----BEGIN ENCRYPTED PRIVATE KEY-----"; const OPENSSH_HEADER: &str = "-----BEGIN OPENSSH PRIVATE KEY-----"; -pub const RSA_PKCS8_ALGORITHM_OID: ObjectIdentifier = - ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1"); - #[derive(Serialize, Deserialize, PartialEq, Debug)] #[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] enum KeyType { @@ -31,10 +26,11 @@ pub fn import_key( ) -> Result { match encoded_key.lines().next() { Some(PKCS1_HEADER) => Err(SshKeyImportError::UnsupportedKeyType), - Some(PKCS8_UNENCRYPTED_HEADER) => { - import_pkcs8_key(encoded_key, None).map_err(|_| SshKeyImportError::ParsingError) - } - Some(PKCS8_ENCRYPTED_HEADER) => import_pkcs8_key(encoded_key, password), + Some(PKCS8_UNENCRYPTED_HEADER) => import_pkcs8_key(encoded_key, None), + Some(PKCS8_ENCRYPTED_HEADER) => import_pkcs8_key( + encoded_key, + Some(password.ok_or(SshKeyImportError::PasswordRequired)?), + ), Some(OPENSSH_HEADER) => import_openssh_key(encoded_key, password), _ => Err(SshKeyImportError::ParsingError), } @@ -44,20 +40,17 @@ fn import_pkcs8_key( encoded_key: String, password: Option, ) -> Result { - let der = match SecretDocument::from_pem(&encoded_key) { - Ok((_, doc)) => doc, - Err(_) => return Err(SshKeyImportError::ParsingError), - }; - - let decrypted_der = match password.clone() { - Some(password) => { - let encrypted_private_key_info = EncryptedPrivateKeyInfo::from_der(der.as_bytes()) - .map_err(|_| SshKeyImportError::ParsingError)?; - encrypted_private_key_info - .decrypt(password.as_bytes()) - .map_err(|_| SshKeyImportError::WrongPassword)? - } - None => der, + let decrypted_der = if let Some(password) = password { + SecretDocument::from_pkcs8_encrypted_pem(&encoded_key, password.as_bytes()).map_err( + |err| match err { + pkcs8::Error::EncryptedPrivateKey(pkcs5::Error::DecryptFailed) => { + SshKeyImportError::WrongPassword + } + _ => SshKeyImportError::ParsingError, + }, + )? + } else { + SecretDocument::from_pkcs8_pem(&encoded_key).map_err(|_| SshKeyImportError::ParsingError)? }; let private_key_info = PrivateKeyInfo::from_der(decrypted_der.as_bytes()) @@ -65,7 +58,7 @@ fn import_pkcs8_key( let key_type: KeyType = match private_key_info.algorithm.oid { ed25519::pkcs8::ALGORITHM_OID => KeyType::Ed25519, - RSA_PKCS8_ALGORITHM_OID => KeyType::Rsa, + rsa::pkcs1::ALGORITHM_OID => KeyType::Rsa, _ => KeyType::Unknown, }; From 90f5cea3479bea991e8c8843d67a1794509c62d7 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 18 Dec 2024 14:14:05 +0100 Subject: [PATCH 13/24] Fix build --- crates/bitwarden-wasm-internal/src/ssh.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bitwarden-wasm-internal/src/ssh.rs b/crates/bitwarden-wasm-internal/src/ssh.rs index 500245d0..f3e68d8e 100644 --- a/crates/bitwarden-wasm-internal/src/ssh.rs +++ b/crates/bitwarden-wasm-internal/src/ssh.rs @@ -10,7 +10,7 @@ pub fn generate_ssh_key( #[wasm_bindgen] pub fn import_ssh_key( imported_key: &str, - password: &str, + password: Option, ) -> Result { - bitwarden_ssh::import::import_key(imported_key.to_string(), password.to_string()) + bitwarden_ssh::import::import_key(imported_key.to_string(), password) } From edbba7777c6c9fa7253770ea67d2304551a99a87 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 18 Dec 2024 14:23:24 +0100 Subject: [PATCH 14/24] Clean up label parsing --- Cargo.lock | 1 + crates/bitwarden-ssh/Cargo.toml | 1 + crates/bitwarden-ssh/src/import.rs | 21 ++++++++++++--------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a787ba5d..8b51c85b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -584,6 +584,7 @@ version = "1.0.0" dependencies = [ "bitwarden-error", "ed25519", + "pem-rfc7468", "pkcs8", "rand", "rand_chacha", diff --git a/crates/bitwarden-ssh/Cargo.toml b/crates/bitwarden-ssh/Cargo.toml index a4c33360..e17f53e3 100644 --- a/crates/bitwarden-ssh/Cargo.toml +++ b/crates/bitwarden-ssh/Cargo.toml @@ -24,6 +24,7 @@ wasm = [ [dependencies] bitwarden-error = { workspace = true } ed25519 = { version = ">=2.2.3, <3.0", features = ["pkcs8"] } +pem-rfc7468 = "0.7.0" pkcs8 = { version = ">=0.10.2, <0.11", features = ["encryption"] } rand = ">=0.8.5, <0.9" rsa = ">=0.9.2, <0.10" diff --git a/crates/bitwarden-ssh/src/import.rs b/crates/bitwarden-ssh/src/import.rs index dbf5e200..87235d67 100644 --- a/crates/bitwarden-ssh/src/import.rs +++ b/crates/bitwarden-ssh/src/import.rs @@ -7,10 +7,10 @@ use tsify_next::Tsify; use crate::{error::SshKeyImportError, SshKey}; -const PKCS1_HEADER: &str = "-----BEGIN RSA PRIVATE KEY-----"; -const PKCS8_UNENCRYPTED_HEADER: &str = "-----BEGIN PRIVATE KEY-----"; -const PKCS8_ENCRYPTED_HEADER: &str = "-----BEGIN ENCRYPTED PRIVATE KEY-----"; -const OPENSSH_HEADER: &str = "-----BEGIN OPENSSH PRIVATE KEY-----"; +const PKCS1_LABEL: &str = "RSA PRIVATE KEY"; +const PKCS8_UNENCRYPTED_LABEL: &str = "PRIVATE KEY"; +const PKCS8_ENCRYPTED_LABEL: &str = "ENCRYPTED PRIVATE KEY"; +const OPENSSH_LABEL: &str = "OPENSSH PRIVATE KEY"; #[derive(Serialize, Deserialize, PartialEq, Debug)] #[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] @@ -24,14 +24,17 @@ pub fn import_key( encoded_key: String, password: Option, ) -> Result { - match encoded_key.lines().next() { - Some(PKCS1_HEADER) => Err(SshKeyImportError::UnsupportedKeyType), - Some(PKCS8_UNENCRYPTED_HEADER) => import_pkcs8_key(encoded_key, None), - Some(PKCS8_ENCRYPTED_HEADER) => import_pkcs8_key( + let label = pem_rfc7468::decode_label(encoded_key.as_bytes()) + .map_err(|_| SshKeyImportError::ParsingError)?; + + match label { + PKCS1_LABEL => Err(SshKeyImportError::UnsupportedKeyType), + PKCS8_UNENCRYPTED_LABEL => import_pkcs8_key(encoded_key, None), + PKCS8_ENCRYPTED_LABEL => import_pkcs8_key( encoded_key, Some(password.ok_or(SshKeyImportError::PasswordRequired)?), ), - Some(OPENSSH_HEADER) => import_openssh_key(encoded_key, password), + OPENSSH_LABEL => import_openssh_key(encoded_key, password), _ => Err(SshKeyImportError::ParsingError), } } From 2653a8a669ac2fb520d623cf6abf88e4173080b7 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 18 Dec 2024 14:30:26 +0100 Subject: [PATCH 15/24] Remove unnecessary enum --- crates/bitwarden-ssh/src/import.rs | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/crates/bitwarden-ssh/src/import.rs b/crates/bitwarden-ssh/src/import.rs index 87235d67..c328b49d 100644 --- a/crates/bitwarden-ssh/src/import.rs +++ b/crates/bitwarden-ssh/src/import.rs @@ -1,9 +1,6 @@ use ed25519; use pkcs8::{der::Decode, pkcs5, DecodePrivateKey, PrivateKeyInfo, SecretDocument}; -use serde::{Deserialize, Serialize}; use ssh_key::private::{Ed25519Keypair, RsaKeypair}; -#[cfg(feature = "wasm")] -use tsify_next::Tsify; use crate::{error::SshKeyImportError, SshKey}; @@ -12,14 +9,6 @@ const PKCS8_UNENCRYPTED_LABEL: &str = "PRIVATE KEY"; const PKCS8_ENCRYPTED_LABEL: &str = "ENCRYPTED PRIVATE KEY"; const OPENSSH_LABEL: &str = "OPENSSH PRIVATE KEY"; -#[derive(Serialize, Deserialize, PartialEq, Debug)] -#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] -enum KeyType { - Ed25519, - Rsa, - Unknown, -} - pub fn import_key( encoded_key: String, password: Option, @@ -59,20 +48,14 @@ fn import_pkcs8_key( let private_key_info = PrivateKeyInfo::from_der(decrypted_der.as_bytes()) .map_err(|_| SshKeyImportError::ParsingError)?; - let key_type: KeyType = match private_key_info.algorithm.oid { - ed25519::pkcs8::ALGORITHM_OID => KeyType::Ed25519, - rsa::pkcs1::ALGORITHM_OID => KeyType::Rsa, - _ => KeyType::Unknown, - }; - - let private_key = match key_type { - KeyType::Ed25519 => { + let private_key = match private_key_info.algorithm.oid { + ed25519::pkcs8::ALGORITHM_OID => { let private_key: ed25519::KeypairBytes = private_key_info .try_into() .map_err(|_| SshKeyImportError::ParsingError)?; ssh_key::private::PrivateKey::from(Ed25519Keypair::from(&private_key.secret_key.into())) } - KeyType::Rsa => { + rsa::pkcs1::ALGORITHM_OID => { let private_key: rsa::RsaPrivateKey = private_key_info .try_into() .map_err(|_| SshKeyImportError::ParsingError)?; From 03ed7ebf3cc06238a688ddd025d845df89b42d07 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 18 Dec 2024 15:13:27 +0100 Subject: [PATCH 16/24] Rename decrypted der to der --- crates/bitwarden-ssh/src/import.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-ssh/src/import.rs b/crates/bitwarden-ssh/src/import.rs index c328b49d..6e4998c0 100644 --- a/crates/bitwarden-ssh/src/import.rs +++ b/crates/bitwarden-ssh/src/import.rs @@ -32,7 +32,7 @@ fn import_pkcs8_key( encoded_key: String, password: Option, ) -> Result { - let decrypted_der = if let Some(password) = password { + let der = if let Some(password) = password { SecretDocument::from_pkcs8_encrypted_pem(&encoded_key, password.as_bytes()).map_err( |err| match err { pkcs8::Error::EncryptedPrivateKey(pkcs5::Error::DecryptFailed) => { @@ -45,8 +45,8 @@ fn import_pkcs8_key( SecretDocument::from_pkcs8_pem(&encoded_key).map_err(|_| SshKeyImportError::ParsingError)? }; - let private_key_info = PrivateKeyInfo::from_der(decrypted_der.as_bytes()) - .map_err(|_| SshKeyImportError::ParsingError)?; + let private_key_info = + PrivateKeyInfo::from_der(der.as_bytes()).map_err(|_| SshKeyImportError::ParsingError)?; let private_key = match private_key_info.algorithm.oid { ed25519::pkcs8::ALGORITHM_OID => { From 31a401c1f30d7c79ba14eae06c6a7889e5226671 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 18 Dec 2024 19:03:38 +0100 Subject: [PATCH 17/24] Cleanup and address feedback --- .../generator}/ed25519_key | 0 .../generator}/rsa3072_key | 0 .../generator}/rsa4096_key | 0 .../import/ecdsa_openssh_unencrypted | 8 +++ .../ecdsa_openssh_unencrypted.pub | 0 .../{ => import}/ed25519_openssh_encrypted | 0 .../ed25519_openssh_encrypted.pub | 0 .../{ => import}/ed25519_openssh_unencrypted | 0 .../ed25519_openssh_unencrypted.pub | 0 .../{ => import}/ed25519_pkcs8_unencrypted | 0 .../ed25519_pkcs8_unencrypted.pub | 0 .../ed25519_putty_openssh_unencrypted | 0 .../{ => import}/rsa_openssh_encrypted | 0 .../{ => import}/rsa_openssh_encrypted.pub | 0 .../{ => import}/rsa_openssh_unencrypted | 0 .../{ => import}/rsa_openssh_unencrypted.pub | 0 .../{ => import}/rsa_pkcs8_encrypted | 0 .../{ => import}/rsa_pkcs8_encrypted.pub | 0 .../{ => import}/rsa_pkcs8_unencrypted | 0 .../{ => import}/rsa_pkcs8_unencrypted.pub | 0 .../rsa_putty_openssh_unencrypted | 0 .../{ => import}/rsa_putty_pkcs1_unencrypted | 0 .../wrong_label} | 0 crates/bitwarden-ssh/src/error.rs | 11 +++- crates/bitwarden-ssh/src/generator.rs | 57 ++++++++-------- crates/bitwarden-ssh/src/import.rs | 65 ++++++++++++------- crates/bitwarden-ssh/src/lib.rs | 8 ++- 27 files changed, 93 insertions(+), 56 deletions(-) rename crates/bitwarden-ssh/{tests => resources/generator}/ed25519_key (100%) rename crates/bitwarden-ssh/{tests => resources/generator}/rsa3072_key (100%) rename crates/bitwarden-ssh/{tests => resources/generator}/rsa4096_key (100%) create mode 100644 crates/bitwarden-ssh/resources/import/ecdsa_openssh_unencrypted rename crates/bitwarden-ssh/resources/{ => import}/ecdsa_openssh_unencrypted.pub (100%) rename crates/bitwarden-ssh/resources/{ => import}/ed25519_openssh_encrypted (100%) rename crates/bitwarden-ssh/resources/{ => import}/ed25519_openssh_encrypted.pub (100%) rename crates/bitwarden-ssh/resources/{ => import}/ed25519_openssh_unencrypted (100%) rename crates/bitwarden-ssh/resources/{ => import}/ed25519_openssh_unencrypted.pub (100%) rename crates/bitwarden-ssh/resources/{ => import}/ed25519_pkcs8_unencrypted (100%) rename crates/bitwarden-ssh/resources/{ => import}/ed25519_pkcs8_unencrypted.pub (100%) rename crates/bitwarden-ssh/resources/{ => import}/ed25519_putty_openssh_unencrypted (100%) rename crates/bitwarden-ssh/resources/{ => import}/rsa_openssh_encrypted (100%) rename crates/bitwarden-ssh/resources/{ => import}/rsa_openssh_encrypted.pub (100%) rename crates/bitwarden-ssh/resources/{ => import}/rsa_openssh_unencrypted (100%) rename crates/bitwarden-ssh/resources/{ => import}/rsa_openssh_unencrypted.pub (100%) rename crates/bitwarden-ssh/resources/{ => import}/rsa_pkcs8_encrypted (100%) rename crates/bitwarden-ssh/resources/{ => import}/rsa_pkcs8_encrypted.pub (100%) rename crates/bitwarden-ssh/resources/{ => import}/rsa_pkcs8_unencrypted (100%) rename crates/bitwarden-ssh/resources/{ => import}/rsa_pkcs8_unencrypted.pub (100%) rename crates/bitwarden-ssh/resources/{ => import}/rsa_putty_openssh_unencrypted (100%) rename crates/bitwarden-ssh/resources/{ => import}/rsa_putty_pkcs1_unencrypted (100%) rename crates/bitwarden-ssh/resources/{ecdsa_openssh_unencrypted => import/wrong_label} (100%) diff --git a/crates/bitwarden-ssh/tests/ed25519_key b/crates/bitwarden-ssh/resources/generator/ed25519_key similarity index 100% rename from crates/bitwarden-ssh/tests/ed25519_key rename to crates/bitwarden-ssh/resources/generator/ed25519_key diff --git a/crates/bitwarden-ssh/tests/rsa3072_key b/crates/bitwarden-ssh/resources/generator/rsa3072_key similarity index 100% rename from crates/bitwarden-ssh/tests/rsa3072_key rename to crates/bitwarden-ssh/resources/generator/rsa3072_key diff --git a/crates/bitwarden-ssh/tests/rsa4096_key b/crates/bitwarden-ssh/resources/generator/rsa4096_key similarity index 100% rename from crates/bitwarden-ssh/tests/rsa4096_key rename to crates/bitwarden-ssh/resources/generator/rsa4096_key diff --git a/crates/bitwarden-ssh/resources/import/ecdsa_openssh_unencrypted b/crates/bitwarden-ssh/resources/import/ecdsa_openssh_unencrypted new file mode 100644 index 00000000..93833b27 --- /dev/null +++ b/crates/bitwarden-ssh/resources/import/ecdsa_openssh_unencrypted @@ -0,0 +1,8 @@ +-----BEGIN WRONG LABEL KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS +1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQRQzzQ8nQEouF1FMSHkPx1nejNCzF7g +Yb8MHXLdBFM0uJkWs0vzgLJkttts2eDv3SHJqIH6qHpkLtEvgMXE5WcaAAAAoOO1BebjtQ +XmAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFDPNDydASi4XUUx +IeQ/HWd6M0LMXuBhvwwdct0EUzS4mRazS/OAsmS222zZ4O/dIcmogfqoemQu0S+AxcTlZx +oAAAAhAKnIXk6H0Hs3HblklaZ6UmEjjdE/0t7EdYixpMmtpJ4eAAAAB3Rlc3RrZXk= +-----END WRONG LABEL KEY----- diff --git a/crates/bitwarden-ssh/resources/ecdsa_openssh_unencrypted.pub b/crates/bitwarden-ssh/resources/import/ecdsa_openssh_unencrypted.pub similarity index 100% rename from crates/bitwarden-ssh/resources/ecdsa_openssh_unencrypted.pub rename to crates/bitwarden-ssh/resources/import/ecdsa_openssh_unencrypted.pub diff --git a/crates/bitwarden-ssh/resources/ed25519_openssh_encrypted b/crates/bitwarden-ssh/resources/import/ed25519_openssh_encrypted similarity index 100% rename from crates/bitwarden-ssh/resources/ed25519_openssh_encrypted rename to crates/bitwarden-ssh/resources/import/ed25519_openssh_encrypted diff --git a/crates/bitwarden-ssh/resources/ed25519_openssh_encrypted.pub b/crates/bitwarden-ssh/resources/import/ed25519_openssh_encrypted.pub similarity index 100% rename from crates/bitwarden-ssh/resources/ed25519_openssh_encrypted.pub rename to crates/bitwarden-ssh/resources/import/ed25519_openssh_encrypted.pub diff --git a/crates/bitwarden-ssh/resources/ed25519_openssh_unencrypted b/crates/bitwarden-ssh/resources/import/ed25519_openssh_unencrypted similarity index 100% rename from crates/bitwarden-ssh/resources/ed25519_openssh_unencrypted rename to crates/bitwarden-ssh/resources/import/ed25519_openssh_unencrypted diff --git a/crates/bitwarden-ssh/resources/ed25519_openssh_unencrypted.pub b/crates/bitwarden-ssh/resources/import/ed25519_openssh_unencrypted.pub similarity index 100% rename from crates/bitwarden-ssh/resources/ed25519_openssh_unencrypted.pub rename to crates/bitwarden-ssh/resources/import/ed25519_openssh_unencrypted.pub diff --git a/crates/bitwarden-ssh/resources/ed25519_pkcs8_unencrypted b/crates/bitwarden-ssh/resources/import/ed25519_pkcs8_unencrypted similarity index 100% rename from crates/bitwarden-ssh/resources/ed25519_pkcs8_unencrypted rename to crates/bitwarden-ssh/resources/import/ed25519_pkcs8_unencrypted diff --git a/crates/bitwarden-ssh/resources/ed25519_pkcs8_unencrypted.pub b/crates/bitwarden-ssh/resources/import/ed25519_pkcs8_unencrypted.pub similarity index 100% rename from crates/bitwarden-ssh/resources/ed25519_pkcs8_unencrypted.pub rename to crates/bitwarden-ssh/resources/import/ed25519_pkcs8_unencrypted.pub diff --git a/crates/bitwarden-ssh/resources/ed25519_putty_openssh_unencrypted b/crates/bitwarden-ssh/resources/import/ed25519_putty_openssh_unencrypted similarity index 100% rename from crates/bitwarden-ssh/resources/ed25519_putty_openssh_unencrypted rename to crates/bitwarden-ssh/resources/import/ed25519_putty_openssh_unencrypted diff --git a/crates/bitwarden-ssh/resources/rsa_openssh_encrypted b/crates/bitwarden-ssh/resources/import/rsa_openssh_encrypted similarity index 100% rename from crates/bitwarden-ssh/resources/rsa_openssh_encrypted rename to crates/bitwarden-ssh/resources/import/rsa_openssh_encrypted diff --git a/crates/bitwarden-ssh/resources/rsa_openssh_encrypted.pub b/crates/bitwarden-ssh/resources/import/rsa_openssh_encrypted.pub similarity index 100% rename from crates/bitwarden-ssh/resources/rsa_openssh_encrypted.pub rename to crates/bitwarden-ssh/resources/import/rsa_openssh_encrypted.pub diff --git a/crates/bitwarden-ssh/resources/rsa_openssh_unencrypted b/crates/bitwarden-ssh/resources/import/rsa_openssh_unencrypted similarity index 100% rename from crates/bitwarden-ssh/resources/rsa_openssh_unencrypted rename to crates/bitwarden-ssh/resources/import/rsa_openssh_unencrypted diff --git a/crates/bitwarden-ssh/resources/rsa_openssh_unencrypted.pub b/crates/bitwarden-ssh/resources/import/rsa_openssh_unencrypted.pub similarity index 100% rename from crates/bitwarden-ssh/resources/rsa_openssh_unencrypted.pub rename to crates/bitwarden-ssh/resources/import/rsa_openssh_unencrypted.pub diff --git a/crates/bitwarden-ssh/resources/rsa_pkcs8_encrypted b/crates/bitwarden-ssh/resources/import/rsa_pkcs8_encrypted similarity index 100% rename from crates/bitwarden-ssh/resources/rsa_pkcs8_encrypted rename to crates/bitwarden-ssh/resources/import/rsa_pkcs8_encrypted diff --git a/crates/bitwarden-ssh/resources/rsa_pkcs8_encrypted.pub b/crates/bitwarden-ssh/resources/import/rsa_pkcs8_encrypted.pub similarity index 100% rename from crates/bitwarden-ssh/resources/rsa_pkcs8_encrypted.pub rename to crates/bitwarden-ssh/resources/import/rsa_pkcs8_encrypted.pub diff --git a/crates/bitwarden-ssh/resources/rsa_pkcs8_unencrypted b/crates/bitwarden-ssh/resources/import/rsa_pkcs8_unencrypted similarity index 100% rename from crates/bitwarden-ssh/resources/rsa_pkcs8_unencrypted rename to crates/bitwarden-ssh/resources/import/rsa_pkcs8_unencrypted diff --git a/crates/bitwarden-ssh/resources/rsa_pkcs8_unencrypted.pub b/crates/bitwarden-ssh/resources/import/rsa_pkcs8_unencrypted.pub similarity index 100% rename from crates/bitwarden-ssh/resources/rsa_pkcs8_unencrypted.pub rename to crates/bitwarden-ssh/resources/import/rsa_pkcs8_unencrypted.pub diff --git a/crates/bitwarden-ssh/resources/rsa_putty_openssh_unencrypted b/crates/bitwarden-ssh/resources/import/rsa_putty_openssh_unencrypted similarity index 100% rename from crates/bitwarden-ssh/resources/rsa_putty_openssh_unencrypted rename to crates/bitwarden-ssh/resources/import/rsa_putty_openssh_unencrypted diff --git a/crates/bitwarden-ssh/resources/rsa_putty_pkcs1_unencrypted b/crates/bitwarden-ssh/resources/import/rsa_putty_pkcs1_unencrypted similarity index 100% rename from crates/bitwarden-ssh/resources/rsa_putty_pkcs1_unencrypted rename to crates/bitwarden-ssh/resources/import/rsa_putty_pkcs1_unencrypted diff --git a/crates/bitwarden-ssh/resources/ecdsa_openssh_unencrypted b/crates/bitwarden-ssh/resources/import/wrong_label similarity index 100% rename from crates/bitwarden-ssh/resources/ecdsa_openssh_unencrypted rename to crates/bitwarden-ssh/resources/import/wrong_label diff --git a/crates/bitwarden-ssh/src/error.rs b/crates/bitwarden-ssh/src/error.rs index eed3a56b..62883f54 100644 --- a/crates/bitwarden-ssh/src/error.rs +++ b/crates/bitwarden-ssh/src/error.rs @@ -6,8 +6,8 @@ use thiserror::Error; pub enum KeyGenerationError { #[error("Failed to generate key: {0}")] KeyGenerationError(ssh_key::Error), - #[error("Failed to convert key: {0}")] - KeyConversionError(ssh_key::Error), + #[error("Failed to convert key")] + KeyConversionError, } #[bitwarden_error(flat)] @@ -22,3 +22,10 @@ pub enum SshKeyImportError { #[error("Unsupported key type")] UnsupportedKeyType, } + +#[bitwarden_error(flat)] +#[derive(Error, Debug, PartialEq)] +pub enum SshKeyExportError { + #[error("Failed to convert key")] + KeyConversionError, +} diff --git a/crates/bitwarden-ssh/src/generator.rs b/crates/bitwarden-ssh/src/generator.rs index 4ccdb5e8..a1a14c9a 100644 --- a/crates/bitwarden-ssh/src/generator.rs +++ b/crates/bitwarden-ssh/src/generator.rs @@ -13,6 +13,11 @@ pub enum KeyAlgorithm { Rsa4096, } +/** + * Generate a new SSH key pair, for the provided key algorithm, returning + * an `SshKey` struct containing the private key, public key, and key fingerprint, + * with the private key in OpenSSH format. + */ pub fn generate_sshkey(key_algorithm: KeyAlgorithm) -> Result { let rng = rand::thread_rng(); generate_sshkey_internal(key_algorithm, rng) @@ -22,34 +27,28 @@ fn generate_sshkey_internal( key_algorithm: KeyAlgorithm, mut rng: impl CryptoRngCore, ) -> Result { - 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 private_key = match key_algorithm { + KeyAlgorithm::Ed25519 => ssh_key::PrivateKey::random(&mut rng, Algorithm::Ed25519) + .map_err(KeyGenerationError::KeyGenerationError), + KeyAlgorithm::Rsa3072 => create_rsa_key(&mut rng, 3072), + KeyAlgorithm::Rsa4096 => create_rsa_key(&mut rng, 4096), + }?; - let rsa_keypair = ssh_key::private::RsaKeypair::random(&mut rng, bits) - .map_err(KeyGenerationError::KeyGenerationError)?; - - let private_key = - ssh_key::PrivateKey::new(ssh_key::private::KeypairData::from(rsa_keypair), "") - .map_err(KeyGenerationError::KeyGenerationError)?; - Ok(private_key) - } - } - .map_err(KeyGenerationError::KeyGenerationError)?; + private_key + .try_into() + .map_err(|_| KeyGenerationError::KeyConversionError) +} - let private_key_openssh = key - .to_openssh(LineEnding::LF) - .map_err(KeyGenerationError::KeyConversionError)?; - Ok(SshKey { - private_key: private_key_openssh.to_string(), - public_key: key.public_key().to_string(), - key_fingerprint: key.fingerprint(HashAlg::Sha256).to_string(), - }) +fn create_rsa_key( + mut rng: impl CryptoRngCore, + bits: usize, +) -> Result { + let rsa_keypair = ssh_key::private::RsaKeypair::random(&mut rng, bits) + .map_err(KeyGenerationError::KeyGenerationError)?; + let private_key = + ssh_key::PrivateKey::new(ssh_key::private::KeypairData::from(rsa_keypair), "") + .map_err(KeyGenerationError::KeyGenerationError)?; + Ok(private_key) } #[cfg(test)] @@ -64,7 +63,7 @@ mod tests { let rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]); let key_algorithm = KeyAlgorithm::Ed25519; let result = generate_sshkey_internal(key_algorithm, rng); - let target = include_str!("../tests/ed25519_key").replace("\r\n", "\n"); + let target = include_str!("../resources/generator/ed25519_key").replace("\r\n", "\n"); assert_eq!(result.unwrap().private_key, target); } @@ -73,7 +72,7 @@ mod tests { let rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]); let key_algorithm = KeyAlgorithm::Rsa3072; let result = generate_sshkey_internal(key_algorithm, rng); - let target = include_str!("../tests/rsa3072_key").replace("\r\n", "\n"); + let target = include_str!("../resources/generator/rsa3072_key").replace("\r\n", "\n"); assert_eq!(result.unwrap().private_key, target); } @@ -82,7 +81,7 @@ mod tests { let rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]); let key_algorithm = KeyAlgorithm::Rsa4096; let result = generate_sshkey_internal(key_algorithm, rng); - let target = include_str!("../tests/rsa4096_key").replace("\r\n", "\n"); + let target = include_str!("../resources/generator/rsa4096_key").replace("\r\n", "\n"); assert_eq!(result.unwrap().private_key, target); } } diff --git a/crates/bitwarden-ssh/src/import.rs b/crates/bitwarden-ssh/src/import.rs index 6e4998c0..c932757f 100644 --- a/crates/bitwarden-ssh/src/import.rs +++ b/crates/bitwarden-ssh/src/import.rs @@ -9,6 +9,14 @@ const PKCS8_UNENCRYPTED_LABEL: &str = "PRIVATE KEY"; const PKCS8_ENCRYPTED_LABEL: &str = "ENCRYPTED PRIVATE KEY"; const OPENSSH_LABEL: &str = "OPENSSH PRIVATE KEY"; +/// Import a PKCS8 or OpenSSH encoded private key, and returns a decoded SshKey, +/// with the public key and fingerprint, and the private key in OpenSSH format. +/// A password can be provided for encrypted keys. +/// # Returns +/// - `Error::PasswordRequired` if the key is encrypted and no password is provided +/// - `Error::WrongPassword` if the password provided is incorrect +/// - `Error::UnsupportedKeyType` if the key type is not supported +/// - `Error::ParsingError` if the key is otherwise malformed and cannot be parsed pub fn import_key( encoded_key: String, password: Option, @@ -53,6 +61,7 @@ fn import_pkcs8_key( let private_key: ed25519::KeypairBytes = private_key_info .try_into() .map_err(|_| SshKeyImportError::ParsingError)?; + ssh_key::private::PrivateKey::from(Ed25519Keypair::from(&private_key.secret_key.into())) } rsa::pkcs1::ALGORITHM_OID => { @@ -67,7 +76,9 @@ fn import_pkcs8_key( _ => return Err(SshKeyImportError::UnsupportedKeyType), }; - private_key.try_into() + private_key + .try_into() + .map_err(|_| SshKeyImportError::ParsingError) } fn import_openssh_key( @@ -88,11 +99,14 @@ fn import_openssh_key( .decrypt(password.as_bytes()) .map_err(|_| SshKeyImportError::WrongPassword)? .try_into() + .map_err(|_| SshKeyImportError::ParsingError) } else { Err(SshKeyImportError::PasswordRequired) } } else { - private_key.try_into() + private_key + .try_into() + .map_err(|_| SshKeyImportError::ParsingError) } } @@ -102,41 +116,41 @@ mod tests { #[test] fn import_key_ed25519_openssh_unencrypted() { - let private_key = include_str!("../resources/ed25519_openssh_unencrypted"); - let public_key = include_str!("../resources/ed25519_openssh_unencrypted.pub").trim(); + let private_key = include_str!("../resources/import/ed25519_openssh_unencrypted"); + let public_key = include_str!("../resources/import/ed25519_openssh_unencrypted.pub").trim(); let result = import_key(private_key.to_string(), Some("".to_string())).unwrap(); assert_eq!(result.public_key, public_key); } #[test] fn import_key_ed25519_openssh_encrypted() { - let private_key = include_str!("../resources/ed25519_openssh_encrypted"); - let public_key = include_str!("../resources/ed25519_openssh_encrypted.pub").trim(); + let private_key = include_str!("../resources/import/ed25519_openssh_encrypted"); + let public_key = include_str!("../resources/import/ed25519_openssh_encrypted.pub").trim(); let result = import_key(private_key.to_string(), Some("password".to_string())).unwrap(); assert_eq!(result.public_key, public_key); } #[test] fn import_key_rsa_openssh_unencrypted() { - let private_key = include_str!("../resources/rsa_openssh_unencrypted"); - let public_key = include_str!("../resources/rsa_openssh_unencrypted.pub").trim(); + let private_key = include_str!("../resources/import/rsa_openssh_unencrypted"); + let public_key = include_str!("../resources/import/rsa_openssh_unencrypted.pub").trim(); let result = import_key(private_key.to_string(), Some("".to_string())).unwrap(); assert_eq!(result.public_key, public_key); } #[test] fn import_key_rsa_openssh_encrypted() { - let private_key = include_str!("../resources/rsa_openssh_encrypted"); - let public_key = include_str!("../resources/rsa_openssh_encrypted.pub").trim(); + let private_key = include_str!("../resources/import/rsa_openssh_encrypted"); + let public_key = include_str!("../resources/import/rsa_openssh_encrypted.pub").trim(); let result = import_key(private_key.to_string(), Some("password".to_string())).unwrap(); assert_eq!(result.public_key, public_key); } #[test] fn import_key_ed25519_pkcs8_unencrypted() { - let private_key = include_str!("../resources/ed25519_pkcs8_unencrypted"); - let public_key = - include_str!("../resources/ed25519_pkcs8_unencrypted.pub").replace("testkey", ""); + let private_key = include_str!("../resources/import/ed25519_pkcs8_unencrypted"); + let public_key = include_str!("../resources/import/ed25519_pkcs8_unencrypted.pub") + .replace("testkey", ""); let public_key = public_key.trim(); let result = import_key(private_key.to_string(), Some("".to_string())).unwrap(); assert_eq!(result.public_key, public_key); @@ -144,10 +158,10 @@ mod tests { #[test] fn import_key_rsa_pkcs8_unencrypted() { - let private_key = include_str!("../resources/rsa_pkcs8_unencrypted"); + let private_key = include_str!("../resources/import/rsa_pkcs8_unencrypted"); // for whatever reason pkcs8 + rsa does not include the comment in the public key let public_key = - include_str!("../resources/rsa_pkcs8_unencrypted.pub").replace("testkey", ""); + include_str!("../resources/import/rsa_pkcs8_unencrypted.pub").replace("testkey", ""); let public_key = public_key.trim(); let result = import_key(private_key.to_string(), Some("".to_string())).unwrap(); assert_eq!(result.public_key, public_key); @@ -155,9 +169,9 @@ mod tests { #[test] fn import_key_rsa_pkcs8_encrypted() { - let private_key = include_str!("../resources/rsa_pkcs8_encrypted"); + let private_key = include_str!("../resources/import/rsa_pkcs8_encrypted"); let public_key = - include_str!("../resources/rsa_pkcs8_encrypted.pub").replace("testkey", ""); + include_str!("../resources/import/rsa_pkcs8_encrypted.pub").replace("testkey", ""); let public_key = public_key.trim(); let result = import_key(private_key.to_string(), Some("password".to_string())).unwrap(); assert_eq!(result.public_key, public_key); @@ -165,7 +179,7 @@ mod tests { #[test] fn import_key_ed25519_openssh_encrypted_wrong_password() { - let private_key = include_str!("../resources/ed25519_openssh_encrypted"); + let private_key = include_str!("../resources/import/ed25519_openssh_encrypted"); let result = import_key(private_key.to_string(), Some("wrongpassword".to_string())); assert_eq!(result.unwrap_err(), SshKeyImportError::WrongPassword); } @@ -176,9 +190,16 @@ mod tests { assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError); } + #[test] + fn import_wrong_label_error() { + let private_key = include_str!("../resources/import/wrong_label"); + let result = import_key(private_key.to_string(), Some("".to_string())); + assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError); + } + #[test] fn import_ecdsa_error() { - let private_key = include_str!("../resources/ecdsa_openssh_unencrypted"); + let private_key = include_str!("../resources/import/ecdsa_openssh_unencrypted"); let result = import_key(private_key.to_string(), Some("".to_string())); assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType); } @@ -189,7 +210,7 @@ mod tests { // https://bitwarden.atlassian.net/browse/PM-14989 #[test] fn import_key_ed25519_putty() { - let private_key = include_str!("../resources/ed25519_putty_openssh_unencrypted"); + let private_key = include_str!("../resources/import/ed25519_putty_openssh_unencrypted"); let result = import_key(private_key.to_string(), Some("".to_string())); assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError); } @@ -200,14 +221,14 @@ mod tests { // https://bitwarden.atlassian.net/browse/PM-14989 #[test] fn import_key_rsa_openssh_putty() { - let private_key = include_str!("../resources/rsa_putty_openssh_unencrypted"); + let private_key = include_str!("../resources/import/rsa_putty_openssh_unencrypted"); let result = import_key(private_key.to_string(), Some("".to_string())); assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError); } #[test] fn import_key_rsa_pkcs8_putty() { - let private_key = include_str!("../resources/rsa_putty_pkcs1_unencrypted"); + let private_key = include_str!("../resources/import/rsa_putty_pkcs1_unencrypted"); let result = import_key(private_key.to_string(), Some("".to_string())); assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType); } diff --git a/crates/bitwarden-ssh/src/lib.rs b/crates/bitwarden-ssh/src/lib.rs index 2d6eb43c..117ba7e4 100644 --- a/crates/bitwarden-ssh/src/lib.rs +++ b/crates/bitwarden-ssh/src/lib.rs @@ -2,7 +2,8 @@ pub mod error; pub mod generator; pub mod import; -use error::SshKeyImportError; +use error::{SshKeyExportError, SshKeyImportError}; +use import::import_key; use pkcs8::LineEnding; use serde::{Deserialize, Serialize}; use ssh_key::{HashAlg, PrivateKey}; @@ -12,18 +13,19 @@ use tsify_next::Tsify; #[derive(Serialize, Deserialize, Debug)] #[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] pub struct SshKey { + /// The private key in OpenSSH format pub private_key: String, pub public_key: String, pub key_fingerprint: String, } impl TryFrom for SshKey { - type Error = SshKeyImportError; + type Error = SshKeyExportError; fn try_from(value: PrivateKey) -> Result { let private_key_openssh = value .to_openssh(LineEnding::LF) - .map_err(|_| SshKeyImportError::ParsingError)?; + .map_err(|_| SshKeyExportError::KeyConversionError)?; Ok(SshKey { private_key: private_key_openssh.to_string(), From 8858a2d2f84d40b1c2b6bd822e3d77eceee1ccbb Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 18 Dec 2024 19:05:02 +0100 Subject: [PATCH 18/24] Fix tests --- crates/bitwarden-ssh/src/generator.rs | 2 +- crates/bitwarden-ssh/src/lib.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-ssh/src/generator.rs b/crates/bitwarden-ssh/src/generator.rs index a1a14c9a..57e99630 100644 --- a/crates/bitwarden-ssh/src/generator.rs +++ b/crates/bitwarden-ssh/src/generator.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use ssh_key::{rand_core::CryptoRngCore, Algorithm, HashAlg, LineEnding}; +use ssh_key::{rand_core::CryptoRngCore, Algorithm}; #[cfg(feature = "wasm")] use tsify_next::Tsify; diff --git a/crates/bitwarden-ssh/src/lib.rs b/crates/bitwarden-ssh/src/lib.rs index 117ba7e4..1a972efe 100644 --- a/crates/bitwarden-ssh/src/lib.rs +++ b/crates/bitwarden-ssh/src/lib.rs @@ -2,8 +2,7 @@ pub mod error; pub mod generator; pub mod import; -use error::{SshKeyExportError, SshKeyImportError}; -use import::import_key; +use error::SshKeyExportError; use pkcs8::LineEnding; use serde::{Deserialize, Serialize}; use ssh_key::{HashAlg, PrivateKey}; From f68b7476bc2a69ab9e116787a62ca3066e3b5df9 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 18 Dec 2024 19:06:51 +0100 Subject: [PATCH 19/24] Fix tests --- .../bitwarden-ssh/resources/import/ecdsa_openssh_unencrypted | 4 ++-- crates/bitwarden-ssh/resources/import/wrong_label | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bitwarden-ssh/resources/import/ecdsa_openssh_unencrypted b/crates/bitwarden-ssh/resources/import/ecdsa_openssh_unencrypted index 93833b27..9cf518f8 100644 --- a/crates/bitwarden-ssh/resources/import/ecdsa_openssh_unencrypted +++ b/crates/bitwarden-ssh/resources/import/ecdsa_openssh_unencrypted @@ -1,8 +1,8 @@ ------BEGIN WRONG LABEL KEY----- +-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS 1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQRQzzQ8nQEouF1FMSHkPx1nejNCzF7g Yb8MHXLdBFM0uJkWs0vzgLJkttts2eDv3SHJqIH6qHpkLtEvgMXE5WcaAAAAoOO1BebjtQ XmAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFDPNDydASi4XUUx IeQ/HWd6M0LMXuBhvwwdct0EUzS4mRazS/OAsmS222zZ4O/dIcmogfqoemQu0S+AxcTlZx oAAAAhAKnIXk6H0Hs3HblklaZ6UmEjjdE/0t7EdYixpMmtpJ4eAAAAB3Rlc3RrZXk= ------END WRONG LABEL KEY----- +-----END OPENSSH PRIVATE KEY----- diff --git a/crates/bitwarden-ssh/resources/import/wrong_label b/crates/bitwarden-ssh/resources/import/wrong_label index 9cf518f8..93833b27 100644 --- a/crates/bitwarden-ssh/resources/import/wrong_label +++ b/crates/bitwarden-ssh/resources/import/wrong_label @@ -1,8 +1,8 @@ ------BEGIN OPENSSH PRIVATE KEY----- +-----BEGIN WRONG LABEL KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS 1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQRQzzQ8nQEouF1FMSHkPx1nejNCzF7g Yb8MHXLdBFM0uJkWs0vzgLJkttts2eDv3SHJqIH6qHpkLtEvgMXE5WcaAAAAoOO1BebjtQ XmAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFDPNDydASi4XUUx IeQ/HWd6M0LMXuBhvwwdct0EUzS4mRazS/OAsmS222zZ4O/dIcmogfqoemQu0S+AxcTlZx oAAAAhAKnIXk6H0Hs3HblklaZ6UmEjjdE/0t7EdYixpMmtpJ4eAAAAB3Rlc3RrZXk= ------END OPENSSH PRIVATE KEY----- +-----END WRONG LABEL KEY----- From 643de0f7d5beeaeffadfc46501df9118e1daa354 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 19 Dec 2024 12:00:42 +0100 Subject: [PATCH 20/24] Update crates/bitwarden-ssh/src/import.rs Co-authored-by: Oscar Hinton --- crates/bitwarden-ssh/src/import.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-ssh/src/import.rs b/crates/bitwarden-ssh/src/import.rs index c932757f..71913e97 100644 --- a/crates/bitwarden-ssh/src/import.rs +++ b/crates/bitwarden-ssh/src/import.rs @@ -26,12 +26,12 @@ pub fn import_key( match label { PKCS1_LABEL => Err(SshKeyImportError::UnsupportedKeyType), - PKCS8_UNENCRYPTED_LABEL => import_pkcs8_key(encoded_key, None), - PKCS8_ENCRYPTED_LABEL => import_pkcs8_key( + pkcs8::PrivateKeyInfo::PEM_LABEL => import_pkcs8_key(encoded_key, None), + pkcs8::EncryptedPrivateKeyInfo::PEM_LABEL => import_pkcs8_key( encoded_key, Some(password.ok_or(SshKeyImportError::PasswordRequired)?), ), - OPENSSH_LABEL => import_openssh_key(encoded_key, password), + ssh_key::PrivateKey::PEM_LABEL => import_openssh_key(encoded_key, password), _ => Err(SshKeyImportError::ParsingError), } } From 015cd8e982c4063d7189e453bcd374a0239356d9 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 19 Dec 2024 12:00:58 +0100 Subject: [PATCH 21/24] Update crates/bitwarden-ssh/src/import.rs Co-authored-by: Oscar Hinton --- crates/bitwarden-ssh/src/import.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-ssh/src/import.rs b/crates/bitwarden-ssh/src/import.rs index 71913e97..d789a80b 100644 --- a/crates/bitwarden-ssh/src/import.rs +++ b/crates/bitwarden-ssh/src/import.rs @@ -32,7 +32,7 @@ pub fn import_key( Some(password.ok_or(SshKeyImportError::PasswordRequired)?), ), ssh_key::PrivateKey::PEM_LABEL => import_openssh_key(encoded_key, password), - _ => Err(SshKeyImportError::ParsingError), + _ => Err(SshKeyImportError::UnsupportedKeyType), } } From 5afaae6162f1b88ad0ce42dd7c94feedd5c61a6b Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 19 Dec 2024 12:10:12 +0100 Subject: [PATCH 22/24] Add comments --- crates/bitwarden-ssh/src/import.rs | 36 ++++++++++------------- crates/bitwarden-wasm-internal/src/ssh.rs | 21 +++++++++++++ 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/crates/bitwarden-ssh/src/import.rs b/crates/bitwarden-ssh/src/import.rs index d789a80b..f24c2d47 100644 --- a/crates/bitwarden-ssh/src/import.rs +++ b/crates/bitwarden-ssh/src/import.rs @@ -1,14 +1,11 @@ use ed25519; +use pem_rfc7468::PemLabel; use pkcs8::{der::Decode, pkcs5, DecodePrivateKey, PrivateKeyInfo, SecretDocument}; +use rsa::pkcs1; use ssh_key::private::{Ed25519Keypair, RsaKeypair}; use crate::{error::SshKeyImportError, SshKey}; -const PKCS1_LABEL: &str = "RSA PRIVATE KEY"; -const PKCS8_UNENCRYPTED_LABEL: &str = "PRIVATE KEY"; -const PKCS8_ENCRYPTED_LABEL: &str = "ENCRYPTED PRIVATE KEY"; -const OPENSSH_LABEL: &str = "OPENSSH PRIVATE KEY"; - /// Import a PKCS8 or OpenSSH encoded private key, and returns a decoded SshKey, /// with the public key and fingerprint, and the private key in OpenSSH format. /// A password can be provided for encrypted keys. @@ -25,7 +22,7 @@ pub fn import_key( .map_err(|_| SshKeyImportError::ParsingError)?; match label { - PKCS1_LABEL => Err(SshKeyImportError::UnsupportedKeyType), + pkcs1::RsaPrivateKey::PEM_LABEL => Err(SshKeyImportError::UnsupportedKeyType), pkcs8::PrivateKeyInfo::PEM_LABEL => import_pkcs8_key(encoded_key, None), pkcs8::EncryptedPrivateKeyInfo::PEM_LABEL => import_pkcs8_key( encoded_key, @@ -40,7 +37,7 @@ fn import_pkcs8_key( encoded_key: String, password: Option, ) -> Result { - let der = if let Some(password) = password { + let doc = if let Some(password) = password { SecretDocument::from_pkcs8_encrypted_pem(&encoded_key, password.as_bytes()).map_err( |err| match err { pkcs8::Error::EncryptedPrivateKey(pkcs5::Error::DecryptFailed) => { @@ -54,7 +51,7 @@ fn import_pkcs8_key( }; let private_key_info = - PrivateKeyInfo::from_der(der.as_bytes()).map_err(|_| SshKeyImportError::ParsingError)?; + PrivateKeyInfo::from_der(doc.as_bytes()).map_err(|_| SshKeyImportError::ParsingError)?; let private_key = match private_key_info.algorithm.oid { ed25519::pkcs8::ALGORITHM_OID => { @@ -93,21 +90,18 @@ fn import_openssh_key( _ => SshKeyImportError::ParsingError, })?; - if private_key.is_encrypted() { - if let Some(password) = password { - private_key - .decrypt(password.as_bytes()) - .map_err(|_| SshKeyImportError::WrongPassword)? - .try_into() - .map_err(|_| SshKeyImportError::ParsingError) - } else { - Err(SshKeyImportError::PasswordRequired) - } + let private_key = if private_key.is_encrypted() { + let password = password.ok_or(SshKeyImportError::PasswordRequired)?; + private_key + .decrypt(password.as_bytes()) + .map_err(|_| SshKeyImportError::WrongPassword)? } else { private_key - .try_into() - .map_err(|_| SshKeyImportError::ParsingError) - } + }; + + private_key + .try_into() + .map_err(|_| SshKeyImportError::ParsingError) } #[cfg(test)] diff --git a/crates/bitwarden-wasm-internal/src/ssh.rs b/crates/bitwarden-wasm-internal/src/ssh.rs index f3e68d8e..1255a573 100644 --- a/crates/bitwarden-wasm-internal/src/ssh.rs +++ b/crates/bitwarden-wasm-internal/src/ssh.rs @@ -1,5 +1,13 @@ use wasm_bindgen::prelude::*; +/// Generate a new SSH key pair +/// +/// # Arguments +/// - `key_algorithm` - The algorithm to use for the key pair +/// +/// # Returns +/// - `Ok(SshKey)` if the key was successfully generated +/// - `Err(KeyGenerationError)` if the key could not be generated #[wasm_bindgen] pub fn generate_ssh_key( key_algorithm: bitwarden_ssh::generator::KeyAlgorithm, @@ -7,6 +15,19 @@ pub fn generate_ssh_key( bitwarden_ssh::generator::generate_sshkey(key_algorithm) } +/// Convert a PCKS8 or OpenSSH encrypted or unencrypted private key +/// to an OpenSSH private key with public key and fingerprint +/// +/// # Arguments +/// - `imported_key` - The private key to convert +/// - `password` - The password to use for decrypting the key +/// +/// # Returns +/// - `Ok(SshKey)` if the key was successfully coneverted +/// - `Err(PasswordRequired)` if the key is encrypted and no password was provided +/// - `Err(WrongPassword)` if the password provided is incorrect +/// - `Err(ParsingError)` if the key could not be parsed +/// - `Err(UnsupportedKeyType)` if the key type is not supported #[wasm_bindgen] pub fn import_ssh_key( imported_key: &str, From 184a009c171a54822edd3c0a635f9d07de0c54ea Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 19 Dec 2024 12:11:14 +0100 Subject: [PATCH 23/24] Fix tests --- crates/bitwarden-ssh/src/import.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitwarden-ssh/src/import.rs b/crates/bitwarden-ssh/src/import.rs index f24c2d47..4ad17651 100644 --- a/crates/bitwarden-ssh/src/import.rs +++ b/crates/bitwarden-ssh/src/import.rs @@ -188,7 +188,7 @@ mod tests { fn import_wrong_label_error() { let private_key = include_str!("../resources/import/wrong_label"); let result = import_key(private_key.to_string(), Some("".to_string())); - assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError); + assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType); } #[test] From 929797acd8e6e4625b713f6a9c0b8f2373d2dc1a Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 19 Dec 2024 12:37:31 +0100 Subject: [PATCH 24/24] Add links in comments --- crates/bitwarden-ssh/src/generator.rs | 2 +- crates/bitwarden-ssh/src/import.rs | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/bitwarden-ssh/src/generator.rs b/crates/bitwarden-ssh/src/generator.rs index 57e99630..51b8f9f7 100644 --- a/crates/bitwarden-ssh/src/generator.rs +++ b/crates/bitwarden-ssh/src/generator.rs @@ -15,7 +15,7 @@ pub enum KeyAlgorithm { /** * Generate a new SSH key pair, for the provided key algorithm, returning - * an `SshKey` struct containing the private key, public key, and key fingerprint, + * an [SshKey] struct containing the private key, public key, and key fingerprint, * with the private key in OpenSSH format. */ pub fn generate_sshkey(key_algorithm: KeyAlgorithm) -> Result { diff --git a/crates/bitwarden-ssh/src/import.rs b/crates/bitwarden-ssh/src/import.rs index 4ad17651..30e92de0 100644 --- a/crates/bitwarden-ssh/src/import.rs +++ b/crates/bitwarden-ssh/src/import.rs @@ -1,19 +1,18 @@ use ed25519; use pem_rfc7468::PemLabel; use pkcs8::{der::Decode, pkcs5, DecodePrivateKey, PrivateKeyInfo, SecretDocument}; -use rsa::pkcs1; use ssh_key::private::{Ed25519Keypair, RsaKeypair}; use crate::{error::SshKeyImportError, SshKey}; -/// Import a PKCS8 or OpenSSH encoded private key, and returns a decoded SshKey, +/// Import a PKCS8 or OpenSSH encoded private key, and returns a decoded [SshKey], /// with the public key and fingerprint, and the private key in OpenSSH format. /// A password can be provided for encrypted keys. /// # Returns -/// - `Error::PasswordRequired` if the key is encrypted and no password is provided -/// - `Error::WrongPassword` if the password provided is incorrect -/// - `Error::UnsupportedKeyType` if the key type is not supported -/// - `Error::ParsingError` if the key is otherwise malformed and cannot be parsed +/// - [SshKeyImportError::PasswordRequired] if the key is encrypted and no password is provided +/// - [SshKeyImportError::WrongPassword] if the password provided is incorrect +/// - [SshKeyImportError::UnsupportedKeyType] if the key type is not supported +/// - [SshKeyImportError::ParsingError] if the key is otherwise malformed and cannot be parsed pub fn import_key( encoded_key: String, password: Option, @@ -22,7 +21,6 @@ pub fn import_key( .map_err(|_| SshKeyImportError::ParsingError)?; match label { - pkcs1::RsaPrivateKey::PEM_LABEL => Err(SshKeyImportError::UnsupportedKeyType), pkcs8::PrivateKeyInfo::PEM_LABEL => import_pkcs8_key(encoded_key, None), pkcs8::EncryptedPrivateKeyInfo::PEM_LABEL => import_pkcs8_key( encoded_key,