bitwarden_ssh/
generator.rs1use 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
30pub 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}