bitwarden_ssh/
generator.rs

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