bitwarden_ssh/
generator.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
use serde::{Deserialize, Serialize};
use ssh_key::{rand_core::CryptoRngCore, Algorithm};
#[cfg(feature = "wasm")]
use tsify_next::Tsify;

use crate::{error, error::KeyGenerationError, SshKey};

#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
pub enum KeyAlgorithm {
    Ed25519,
    Rsa3072,
    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<SshKey, error::KeyGenerationError> {
    let rng = rand::thread_rng();
    generate_sshkey_internal(key_algorithm, rng)
}

fn generate_sshkey_internal(
    key_algorithm: KeyAlgorithm,
    mut rng: impl CryptoRngCore,
) -> Result<SshKey, error::KeyGenerationError> {
    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),
    }?;

    private_key
        .try_into()
        .map_err(|_| KeyGenerationError::KeyConversionError)
}

fn create_rsa_key(
    mut rng: impl CryptoRngCore,
    bits: usize,
) -> Result<ssh_key::PrivateKey, error::KeyGenerationError> {
    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)]
mod tests {
    use rand::SeedableRng;

    use super::KeyAlgorithm;
    use crate::generator::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!("../resources/generator/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!("../resources/generator/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!("../resources/generator/rsa4096_key").replace("\r\n", "\n");
        assert_eq!(result.unwrap().private_key, target);
    }
}