1use bitwarden_vault::SshKeyView;
2use ed25519;
3use pem_rfc7468::PemLabel;
4use pkcs8::{der::Decode, pkcs5, DecodePrivateKey, PrivateKeyInfo, SecretDocument};
5use ssh_key::private::{Ed25519Keypair, RsaKeypair};
6
7use crate::{error::SshKeyImportError, ssh_private_key_to_view};
8
9pub fn import_key(
18 encoded_key: String,
19 password: Option<String>,
20) -> Result<SshKeyView, SshKeyImportError> {
21 let label = pem_rfc7468::decode_label(encoded_key.as_bytes())
22 .map_err(|_| SshKeyImportError::ParsingError)?;
23
24 match label {
25 pkcs8::PrivateKeyInfo::PEM_LABEL => import_pkcs8_key(encoded_key, None),
26 pkcs8::EncryptedPrivateKeyInfo::PEM_LABEL => import_pkcs8_key(
27 encoded_key,
28 Some(password.ok_or(SshKeyImportError::PasswordRequired)?),
29 ),
30 ssh_key::PrivateKey::PEM_LABEL => import_openssh_key(encoded_key, password),
31 _ => Err(SshKeyImportError::UnsupportedKeyType),
32 }
33}
34
35fn import_pkcs8_key(
36 encoded_key: String,
37 password: Option<String>,
38) -> Result<SshKeyView, SshKeyImportError> {
39 let doc = if let Some(password) = password {
40 SecretDocument::from_pkcs8_encrypted_pem(&encoded_key, password.as_bytes()).map_err(
41 |err| match err {
42 pkcs8::Error::EncryptedPrivateKey(pkcs5::Error::DecryptFailed) => {
43 SshKeyImportError::WrongPassword
44 }
45 _ => SshKeyImportError::ParsingError,
46 },
47 )?
48 } else {
49 SecretDocument::from_pkcs8_pem(&encoded_key).map_err(|_| SshKeyImportError::ParsingError)?
50 };
51
52 let private_key_info =
53 PrivateKeyInfo::from_der(doc.as_bytes()).map_err(|_| SshKeyImportError::ParsingError)?;
54
55 let private_key = match private_key_info.algorithm.oid {
56 ed25519::pkcs8::ALGORITHM_OID => {
57 let private_key: ed25519::KeypairBytes = private_key_info
58 .try_into()
59 .map_err(|_| SshKeyImportError::ParsingError)?;
60
61 ssh_key::private::PrivateKey::from(Ed25519Keypair::from(&private_key.secret_key.into()))
62 }
63 rsa::pkcs1::ALGORITHM_OID => {
64 let private_key: rsa::RsaPrivateKey = private_key_info
65 .try_into()
66 .map_err(|_| SshKeyImportError::ParsingError)?;
67
68 ssh_key::private::PrivateKey::from(
69 RsaKeypair::try_from(private_key).map_err(|_| SshKeyImportError::ParsingError)?,
70 )
71 }
72 _ => return Err(SshKeyImportError::UnsupportedKeyType),
73 };
74
75 ssh_private_key_to_view(private_key).map_err(|_| SshKeyImportError::ParsingError)
76}
77
78fn import_openssh_key(
79 encoded_key: String,
80 password: Option<String>,
81) -> Result<SshKeyView, SshKeyImportError> {
82 let private_key =
83 ssh_key::private::PrivateKey::from_openssh(&encoded_key).map_err(|err| match err {
84 ssh_key::Error::AlgorithmUnknown | ssh_key::Error::AlgorithmUnsupported { .. } => {
85 SshKeyImportError::UnsupportedKeyType
86 }
87 _ => SshKeyImportError::ParsingError,
88 })?;
89
90 let private_key = if private_key.is_encrypted() {
91 let password = password.ok_or(SshKeyImportError::PasswordRequired)?;
92 private_key
93 .decrypt(password.as_bytes())
94 .map_err(|_| SshKeyImportError::WrongPassword)?
95 } else {
96 private_key
97 };
98
99 ssh_private_key_to_view(private_key).map_err(|_| SshKeyImportError::ParsingError)
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn import_key_ed25519_openssh_unencrypted() {
108 let private_key = include_str!("../resources/import/ed25519_openssh_unencrypted");
109 let public_key = include_str!("../resources/import/ed25519_openssh_unencrypted.pub").trim();
110 let result = import_key(private_key.to_string(), Some("".to_string())).unwrap();
111 assert_eq!(result.public_key, public_key);
112 }
113
114 #[test]
115 fn import_key_ed25519_openssh_encrypted() {
116 let private_key = include_str!("../resources/import/ed25519_openssh_encrypted");
117 let public_key = include_str!("../resources/import/ed25519_openssh_encrypted.pub").trim();
118 let result = import_key(private_key.to_string(), Some("password".to_string())).unwrap();
119 assert_eq!(result.public_key, public_key);
120 }
121
122 #[test]
123 fn import_key_rsa_openssh_unencrypted() {
124 let private_key = include_str!("../resources/import/rsa_openssh_unencrypted");
125 let public_key = include_str!("../resources/import/rsa_openssh_unencrypted.pub").trim();
126 let result = import_key(private_key.to_string(), Some("".to_string())).unwrap();
127 assert_eq!(result.public_key, public_key);
128 }
129
130 #[test]
131 fn import_key_rsa_openssh_encrypted() {
132 let private_key = include_str!("../resources/import/rsa_openssh_encrypted");
133 let public_key = include_str!("../resources/import/rsa_openssh_encrypted.pub").trim();
134 let result = import_key(private_key.to_string(), Some("password".to_string())).unwrap();
135 assert_eq!(result.public_key, public_key);
136 }
137
138 #[test]
139 fn import_key_ed25519_pkcs8_unencrypted() {
140 let private_key = include_str!("../resources/import/ed25519_pkcs8_unencrypted");
141 let public_key = include_str!("../resources/import/ed25519_pkcs8_unencrypted.pub")
142 .replace("testkey", "");
143 let public_key = public_key.trim();
144 let result = import_key(private_key.to_string(), Some("".to_string())).unwrap();
145 assert_eq!(result.public_key, public_key);
146 }
147
148 #[test]
149 fn import_key_rsa_pkcs8_unencrypted() {
150 let private_key = include_str!("../resources/import/rsa_pkcs8_unencrypted");
151 let public_key =
153 include_str!("../resources/import/rsa_pkcs8_unencrypted.pub").replace("testkey", "");
154 let public_key = public_key.trim();
155 let result = import_key(private_key.to_string(), Some("".to_string())).unwrap();
156 assert_eq!(result.public_key, public_key);
157 }
158
159 #[test]
160 fn import_key_rsa_pkcs8_encrypted() {
161 let private_key = include_str!("../resources/import/rsa_pkcs8_encrypted");
162 let public_key =
163 include_str!("../resources/import/rsa_pkcs8_encrypted.pub").replace("testkey", "");
164 let public_key = public_key.trim();
165 let result = import_key(private_key.to_string(), Some("password".to_string())).unwrap();
166 assert_eq!(result.public_key, public_key);
167 }
168
169 #[test]
170 fn import_key_ed25519_openssh_encrypted_wrong_password() {
171 let private_key = include_str!("../resources/import/ed25519_openssh_encrypted");
172 let result = import_key(private_key.to_string(), Some("wrongpassword".to_string()));
173 assert_eq!(result.unwrap_err(), SshKeyImportError::WrongPassword);
174 }
175
176 #[test]
177 fn import_non_key_error() {
178 let result = import_key("not a key".to_string(), Some("".to_string()));
179 assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError);
180 }
181
182 #[test]
183 fn import_wrong_label_error() {
184 let private_key = include_str!("../resources/import/wrong_label");
185 let result = import_key(private_key.to_string(), Some("".to_string()));
186 assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType);
187 }
188
189 #[test]
190 fn import_ecdsa_error() {
191 let private_key = include_str!("../resources/import/ecdsa_openssh_unencrypted");
192 let result = import_key(private_key.to_string(), Some("".to_string()));
193 assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType);
194 }
195
196 #[test]
201 fn import_key_ed25519_putty() {
202 let private_key = include_str!("../resources/import/ed25519_putty_openssh_unencrypted");
203 let result = import_key(private_key.to_string(), Some("".to_string()));
204 assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError);
205 }
206
207 #[test]
212 fn import_key_rsa_openssh_putty() {
213 let private_key = include_str!("../resources/import/rsa_putty_openssh_unencrypted");
214 let result = import_key(private_key.to_string(), Some("".to_string()));
215 assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError);
216 }
217
218 #[test]
219 fn import_key_rsa_pkcs8_putty() {
220 let private_key = include_str!("../resources/import/rsa_putty_pkcs1_unencrypted");
221 let result = import_key(private_key.to_string(), Some("".to_string()));
222 assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType);
223 }
224}