Skip to main content

bitwarden_ssh/
generator.rs

1use bitwarden_vault::SshKeyView;
2use rand::CryptoRng;
3use serde::{Deserialize, Serialize};
4use ssh_key::{Algorithm, EcdsaCurve};
5#[cfg(feature = "wasm")]
6use tsify::Tsify;
7
8use crate::{
9    error::{self, KeyGenerationError},
10    ssh_private_key_to_view,
11};
12
13#[derive(Serialize, Deserialize)]
14#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
15#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
16pub enum KeyAlgorithm {
17    Ed25519,
18    Rsa3072,
19    Rsa4096,
20    EcdsaP256,
21    EcdsaP384,
22    EcdsaP521,
23}
24
25/**
26 * Generate a new SSH key pair, for the provided key algorithm, returning
27 * an [SshKeyView] struct containing the private key, public key, and key fingerprint,
28 * with the private key in OpenSSH format.
29 */
30pub fn generate_sshkey(
31    key_algorithm: KeyAlgorithm,
32) -> Result<SshKeyView, error::KeyGenerationError> {
33    let mut rng = bitwarden_random::rng();
34    generate_sshkey_internal(key_algorithm, &mut rng)
35}
36
37fn generate_sshkey_internal<R: CryptoRng + ?Sized>(
38    key_algorithm: KeyAlgorithm,
39    rng: &mut R,
40) -> Result<SshKeyView, error::KeyGenerationError> {
41    let private_key = match key_algorithm {
42        KeyAlgorithm::Ed25519 => ssh_key::PrivateKey::random(rng, Algorithm::Ed25519)
43            .map_err(KeyGenerationError::KeyGeneration),
44        KeyAlgorithm::Rsa3072 => create_rsa_key(rng, 3072),
45        KeyAlgorithm::Rsa4096 => create_rsa_key(rng, 4096),
46        KeyAlgorithm::EcdsaP256 => ssh_key::PrivateKey::random(
47            rng,
48            Algorithm::Ecdsa {
49                curve: EcdsaCurve::NistP256,
50            },
51        )
52        .map_err(KeyGenerationError::KeyGeneration),
53        KeyAlgorithm::EcdsaP384 => ssh_key::PrivateKey::random(
54            rng,
55            Algorithm::Ecdsa {
56                curve: EcdsaCurve::NistP384,
57            },
58        )
59        .map_err(KeyGenerationError::KeyGeneration),
60        KeyAlgorithm::EcdsaP521 => ssh_key::PrivateKey::random(
61            rng,
62            Algorithm::Ecdsa {
63                curve: EcdsaCurve::NistP521,
64            },
65        )
66        .map_err(KeyGenerationError::KeyGeneration),
67    }?;
68
69    ssh_private_key_to_view(private_key).map_err(|_| KeyGenerationError::KeyConversion)
70}
71
72fn create_rsa_key<R: CryptoRng + ?Sized>(
73    rng: &mut R,
74    bits: usize,
75) -> Result<ssh_key::PrivateKey, error::KeyGenerationError> {
76    let rsa_keypair = ssh_key::private::RsaKeypair::random(rng, bits)
77        .map_err(KeyGenerationError::KeyGeneration)?;
78    let private_key =
79        ssh_key::PrivateKey::new(ssh_key::private::KeypairData::from(rsa_keypair), "")
80            .map_err(KeyGenerationError::KeyGeneration)?;
81    Ok(private_key)
82}
83
84#[cfg(test)]
85mod tests {
86    use rand::SeedableRng;
87
88    use super::KeyAlgorithm;
89    use crate::generator::generate_sshkey_internal;
90
91    #[test]
92    fn generate_ssh_key_ed25519() {
93        let mut rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]);
94        let key_algorithm = KeyAlgorithm::Ed25519;
95        let result = generate_sshkey_internal(key_algorithm, &mut rng);
96        let target = include_str!("../resources/generator/ed25519_key").replace("\r\n", "\n");
97        assert_eq!(result.unwrap().private_key, target);
98    }
99
100    #[test]
101    fn generate_ssh_key_rsa3072() {
102        let mut rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]);
103        let key_algorithm = KeyAlgorithm::Rsa3072;
104        let result = generate_sshkey_internal(key_algorithm, &mut rng);
105        let target = include_str!("../resources/generator/rsa3072_key").replace("\r\n", "\n");
106        assert_eq!(result.unwrap().private_key, target);
107    }
108
109    #[test]
110    fn generate_ssh_key_rsa4096() {
111        let mut rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]);
112        let key_algorithm = KeyAlgorithm::Rsa4096;
113        let result = generate_sshkey_internal(key_algorithm, &mut rng);
114        let target = include_str!("../resources/generator/rsa4096_key").replace("\r\n", "\n");
115        assert_eq!(result.unwrap().private_key, target);
116    }
117
118    #[test]
119    fn generate_ssh_key_ecdsa_p256() {
120        let mut rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]);
121        let key_algorithm = KeyAlgorithm::EcdsaP256;
122        let result = generate_sshkey_internal(key_algorithm, &mut rng);
123        let target = include_str!("../resources/generator/ecdsa_p256_key").replace("\r\n", "\n");
124        assert_eq!(result.unwrap().private_key, target);
125    }
126
127    #[test]
128    fn generate_ssh_key_ecdsa_p384() {
129        let mut rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]);
130        let key_algorithm = KeyAlgorithm::EcdsaP384;
131        let result = generate_sshkey_internal(key_algorithm, &mut rng);
132        let target = include_str!("../resources/generator/ecdsa_p384_key").replace("\r\n", "\n");
133        assert_eq!(result.unwrap().private_key, target);
134    }
135
136    #[test]
137    fn generate_ssh_key_ecdsa_p521() {
138        let mut rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]);
139        let key_algorithm = KeyAlgorithm::EcdsaP521;
140        let result = generate_sshkey_internal(key_algorithm, &mut rng);
141        let target = include_str!("../resources/generator/ecdsa_p521_key").replace("\r\n", "\n");
142        assert_eq!(result.unwrap().private_key, target);
143    }
144}