bitwarden_ssh/
generator.rs

1use bitwarden_vault::SshKeyView;
2use rand::CryptoRng;
3use serde::{Deserialize, Serialize};
4use ssh_key::Algorithm;
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}
21
22/**
23 * Generate a new SSH key pair, for the provided key algorithm, returning
24 * an [SshKeyView] struct containing the private key, public key, and key fingerprint,
25 * with the private key in OpenSSH format.
26 */
27pub fn generate_sshkey(
28    key_algorithm: KeyAlgorithm,
29) -> Result<SshKeyView, error::KeyGenerationError> {
30    let mut rng = rand::rng();
31    generate_sshkey_internal(key_algorithm, &mut rng)
32}
33
34fn generate_sshkey_internal<R: CryptoRng + ?Sized>(
35    key_algorithm: KeyAlgorithm,
36    rng: &mut R,
37) -> Result<SshKeyView, error::KeyGenerationError> {
38    let private_key = match key_algorithm {
39        KeyAlgorithm::Ed25519 => ssh_key::PrivateKey::random(rng, Algorithm::Ed25519)
40            .map_err(KeyGenerationError::KeyGeneration),
41        KeyAlgorithm::Rsa3072 => create_rsa_key(rng, 3072),
42        KeyAlgorithm::Rsa4096 => create_rsa_key(rng, 4096),
43    }?;
44
45    ssh_private_key_to_view(private_key).map_err(|_| KeyGenerationError::KeyConversion)
46}
47
48fn create_rsa_key<R: CryptoRng + ?Sized>(
49    rng: &mut R,
50    bits: usize,
51) -> Result<ssh_key::PrivateKey, error::KeyGenerationError> {
52    let rsa_keypair = ssh_key::private::RsaKeypair::random(rng, bits)
53        .map_err(KeyGenerationError::KeyGeneration)?;
54    let private_key =
55        ssh_key::PrivateKey::new(ssh_key::private::KeypairData::from(rsa_keypair), "")
56            .map_err(KeyGenerationError::KeyGeneration)?;
57    Ok(private_key)
58}
59
60#[cfg(test)]
61mod tests {
62    use rand::SeedableRng;
63
64    use super::KeyAlgorithm;
65    use crate::generator::generate_sshkey_internal;
66
67    #[test]
68    fn generate_ssh_key_ed25519() {
69        let mut rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]);
70        let key_algorithm = KeyAlgorithm::Ed25519;
71        let result = generate_sshkey_internal(key_algorithm, &mut rng);
72        let target = include_str!("../resources/generator/ed25519_key").replace("\r\n", "\n");
73        assert_eq!(result.unwrap().private_key, target);
74    }
75
76    #[test]
77    fn generate_ssh_key_rsa3072() {
78        let mut rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]);
79        let key_algorithm = KeyAlgorithm::Rsa3072;
80        let result = generate_sshkey_internal(key_algorithm, &mut rng);
81        let target = include_str!("../resources/generator/rsa3072_key").replace("\r\n", "\n");
82        assert_eq!(result.unwrap().private_key, target);
83    }
84
85    #[test]
86    fn generate_ssh_key_rsa4096() {
87        let mut rng = rand_chacha::ChaCha12Rng::from_seed([0u8; 32]);
88        let key_algorithm = KeyAlgorithm::Rsa4096;
89        let result = generate_sshkey_internal(key_algorithm, &mut rng);
90        let target = include_str!("../resources/generator/rsa4096_key").replace("\r\n", "\n");
91        assert_eq!(result.unwrap().private_key, target);
92    }
93}