Skip to main content

bitwarden_ssh/
generator.rs

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