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 import_pkcs8_der_key(doc.as_bytes())
53}
54
55pub fn import_pkcs8_der_key(encoded_key: &[u8]) -> Result<SshKeyView, SshKeyImportError> {
58 let private_key_info =
59 PrivateKeyInfo::from_der(encoded_key).map_err(|_| SshKeyImportError::ParsingError)?;
60
61 let private_key = match private_key_info.algorithm.oid {
62 ed25519::pkcs8::ALGORITHM_OID => {
63 let private_key: ed25519::KeypairBytes = private_key_info
64 .try_into()
65 .map_err(|_| SshKeyImportError::ParsingError)?;
66
67 ssh_key::private::PrivateKey::from(Ed25519Keypair::from(&private_key.secret_key.into()))
68 }
69 rsa::pkcs1::ALGORITHM_OID => {
70 let private_key: rsa::RsaPrivateKey = private_key_info
71 .try_into()
72 .map_err(|_| SshKeyImportError::ParsingError)?;
73
74 ssh_key::private::PrivateKey::from(
75 RsaKeypair::try_from(private_key).map_err(|_| SshKeyImportError::ParsingError)?,
76 )
77 }
78 _ => return Err(SshKeyImportError::UnsupportedKeyType),
79 };
80
81 ssh_private_key_to_view(private_key).map_err(|_| SshKeyImportError::ParsingError)
82}
83
84fn import_openssh_key(
85 encoded_key: String,
86 password: Option<String>,
87) -> Result<SshKeyView, SshKeyImportError> {
88 let private_key =
89 ssh_key::private::PrivateKey::from_openssh(&encoded_key).map_err(|err| match err {
90 ssh_key::Error::AlgorithmUnknown | ssh_key::Error::AlgorithmUnsupported { .. } => {
91 SshKeyImportError::UnsupportedKeyType
92 }
93 _ => SshKeyImportError::ParsingError,
94 })?;
95
96 let private_key = if private_key.is_encrypted() {
97 let password = password.ok_or(SshKeyImportError::PasswordRequired)?;
98 private_key
99 .decrypt(password.as_bytes())
100 .map_err(|_| SshKeyImportError::WrongPassword)?
101 } else {
102 private_key
103 };
104
105 ssh_private_key_to_view(private_key).map_err(|_| SshKeyImportError::ParsingError)
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn import_key_ed25519_openssh_unencrypted() {
114 let private_key = include_str!("../resources/import/ed25519_openssh_unencrypted");
115 let public_key = include_str!("../resources/import/ed25519_openssh_unencrypted.pub").trim();
116 let result = import_key(private_key.to_string(), Some("".to_string())).unwrap();
117 assert_eq!(result.public_key, public_key);
118 }
119
120 #[test]
121 fn import_key_ed25519_openssh_encrypted() {
122 let private_key = include_str!("../resources/import/ed25519_openssh_encrypted");
123 let public_key = include_str!("../resources/import/ed25519_openssh_encrypted.pub").trim();
124 let result = import_key(private_key.to_string(), Some("password".to_string())).unwrap();
125 assert_eq!(result.public_key, public_key);
126 }
127
128 #[test]
129 fn import_key_rsa_openssh_unencrypted() {
130 let private_key = include_str!("../resources/import/rsa_openssh_unencrypted");
131 let public_key = include_str!("../resources/import/rsa_openssh_unencrypted.pub").trim();
132 let result = import_key(private_key.to_string(), Some("".to_string())).unwrap();
133 assert_eq!(result.public_key, public_key);
134 }
135
136 #[test]
137 fn import_key_rsa_openssh_encrypted() {
138 let private_key = include_str!("../resources/import/rsa_openssh_encrypted");
139 let public_key = include_str!("../resources/import/rsa_openssh_encrypted.pub").trim();
140 let result = import_key(private_key.to_string(), Some("password".to_string())).unwrap();
141 assert_eq!(result.public_key, public_key);
142 }
143
144 #[test]
145 fn import_key_ed25519_pkcs8_unencrypted() {
146 let private_key = include_str!("../resources/import/ed25519_pkcs8_unencrypted");
147 let public_key = include_str!("../resources/import/ed25519_pkcs8_unencrypted.pub")
148 .replace("testkey", "");
149 let public_key = public_key.trim();
150 let result = import_key(private_key.to_string(), Some("".to_string())).unwrap();
151 assert_eq!(result.public_key, public_key);
152 }
153
154 #[test]
155 fn import_key_rsa_pkcs8_unencrypted() {
156 let private_key = include_str!("../resources/import/rsa_pkcs8_unencrypted");
157 let public_key =
159 include_str!("../resources/import/rsa_pkcs8_unencrypted.pub").replace("testkey", "");
160 let public_key = public_key.trim();
161 let result = import_key(private_key.to_string(), Some("".to_string())).unwrap();
162 assert_eq!(result.public_key, public_key);
163 }
164
165 #[test]
166 fn import_key_rsa_pkcs8_encrypted() {
167 let private_key = include_str!("../resources/import/rsa_pkcs8_encrypted");
168 let public_key =
169 include_str!("../resources/import/rsa_pkcs8_encrypted.pub").replace("testkey", "");
170 let public_key = public_key.trim();
171 let result = import_key(private_key.to_string(), Some("password".to_string())).unwrap();
172 assert_eq!(result.public_key, public_key);
173 }
174
175 #[test]
176 fn import_key_ed25519_openssh_encrypted_wrong_password() {
177 let private_key = include_str!("../resources/import/ed25519_openssh_encrypted");
178 let result = import_key(private_key.to_string(), Some("wrongpassword".to_string()));
179 assert_eq!(result.unwrap_err(), SshKeyImportError::WrongPassword);
180 }
181
182 #[test]
183 fn import_non_key_error() {
184 let result = import_key("not a key".to_string(), Some("".to_string()));
185 assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError);
186 }
187
188 #[test]
189 fn import_wrong_label_error() {
190 let private_key = include_str!("../resources/import/wrong_label");
191 let result = import_key(private_key.to_string(), Some("".to_string()));
192 assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType);
193 }
194
195 #[test]
196 fn import_ecdsa_error() {
197 let private_key = include_str!("../resources/import/ecdsa_openssh_unencrypted");
198 let result = import_key(private_key.to_string(), Some("".to_string()));
199 assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType);
200 }
201
202 #[test]
207 fn import_key_ed25519_putty() {
208 let private_key = include_str!("../resources/import/ed25519_putty_openssh_unencrypted");
209 let result = import_key(private_key.to_string(), Some("".to_string()));
210 assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError);
211 }
212
213 #[test]
218 fn import_key_rsa_openssh_putty() {
219 let private_key = include_str!("../resources/import/rsa_putty_openssh_unencrypted");
220 let result = import_key(private_key.to_string(), Some("".to_string()));
221 assert_eq!(result.unwrap_err(), SshKeyImportError::ParsingError);
222 }
223
224 #[test]
225 fn import_key_rsa_pkcs8_putty() {
226 let private_key = include_str!("../resources/import/rsa_putty_pkcs1_unencrypted");
227 let result = import_key(private_key.to_string(), Some("".to_string()));
228 assert_eq!(result.unwrap_err(), SshKeyImportError::UnsupportedKeyType);
229 }
230}