bitwarden_ssh/
generator.rs

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