1use std::fs::File;
60use std::io::Read;
61use std::path::Path;
62use std::string::FromUtf8Error;
63
64use aes::cipher::block_padding::UnpadError;
65use aes::cipher::inout::PadError;
66use data_encoding::BASE64_MIME;
67use thiserror::Error;
68
69use crate::helpers::EncodedExt;
70
71pub mod key;
72pub use key::PrivateKeyWithHashAlg;
73
74mod format;
75pub use format::*;
76pub use signature;
78pub use ssh_encoding;
79pub use ssh_key::{self, Algorithm, Certificate, EcdsaCurve, HashAlg, PrivateKey, PublicKey};
80
81pub mod agent;
83
84#[cfg(not(target_arch = "wasm32"))]
85pub mod known_hosts;
86
87#[cfg(not(target_arch = "wasm32"))]
88pub use known_hosts::{check_known_hosts, check_known_hosts_path};
89
90#[derive(Debug, Error)]
91pub enum Error {
92 #[error("Could not read key")]
94 CouldNotReadKey,
95 #[error("Unsupported key type {}", key_type_string)]
97 UnsupportedKeyType {
98 key_type_string: String,
99 key_type_raw: Vec<u8>,
100 },
101 #[error("Invalid Ed25519 key data")]
103 Ed25519KeyError(#[from] ed25519_dalek::SignatureError),
104 #[error("Invalid ECDSA key data")]
106 EcdsaKeyError(#[from] p256::elliptic_curve::Error),
107 #[error("The key is encrypted")]
109 KeyIsEncrypted,
110 #[error("The key is corrupt")]
112 KeyIsCorrupt,
113 #[error("No home directory found")]
115 NoHomeDir,
116 #[error("The server key changed at line {}", line)]
118 KeyChanged { line: usize },
119 #[error("Unknown key algorithm: {0}")]
121 UnknownAlgorithm(::pkcs8::ObjectIdentifier),
122 #[error("Index out of bounds")]
124 IndexOutOfBounds,
125 #[error("Unknown signature type: {}", sig_type)]
127 UnknownSignatureType { sig_type: String },
128 #[error("Invalid signature")]
129 InvalidSignature,
130 #[error("Invalid parameters")]
131 InvalidParameters,
132 #[error("Agent protocol error")]
134 AgentProtocolError,
135 #[error("Agent failure")]
136 AgentFailure,
137 #[error(transparent)]
138 IO(#[from] std::io::Error),
139
140 #[cfg(feature = "rsa")]
141 #[error("Rsa: {0}")]
142 Rsa(#[from] rsa::Error),
143
144 #[error(transparent)]
145 Pad(#[from] PadError),
146
147 #[error(transparent)]
148 Unpad(#[from] UnpadError),
149
150 #[error("Base64 decoding error: {0}")]
151 Decode(#[from] data_encoding::DecodeError),
152 #[error("Der: {0}")]
153 Der(#[from] der::Error),
154 #[error("Spki: {0}")]
155 Spki(#[from] spki::Error),
156 #[cfg(feature = "rsa")]
157 #[error("Pkcs1: {0}")]
158 Pkcs1(#[from] pkcs1::Error),
159 #[error("Pkcs8: {0}")]
160 Pkcs8(#[from] ::pkcs8::Error),
161 #[error("Sec1: {0}")]
162 Sec1(#[from] sec1::Error),
163
164 #[error("SshKey: {0}")]
165 SshKey(#[from] ssh_key::Error),
166 #[error("SshEncoding: {0}")]
167 SshEncoding(#[from] ssh_encoding::Error),
168
169 #[error("Environment variable `{0}` not found")]
170 EnvVar(&'static str),
171 #[error(
172 "Unable to connect to ssh-agent. The environment variable `SSH_AUTH_SOCK` was set, but it \
173 points to a nonexistent file or directory."
174 )]
175 BadAuthSock,
176
177 #[error(transparent)]
178 Utf8(#[from] FromUtf8Error),
179
180 #[error("ASN1 decoding error: {0}")]
181 #[cfg(feature = "legacy-ed25519-pkcs8-parser")]
182 LegacyASN1(::yasna::ASN1Error),
183
184 #[cfg(windows)]
185 #[error("Pageant: {0}")]
186 Pageant(#[from] pageant::Error),
187}
188
189#[cfg(feature = "legacy-ed25519-pkcs8-parser")]
190impl From<yasna::ASN1Error> for Error {
191 fn from(e: yasna::ASN1Error) -> Error {
192 Error::LegacyASN1(e)
193 }
194}
195
196pub fn load_public_key<P: AsRef<Path>>(path: P) -> Result<ssh_key::PublicKey, Error> {
202 let mut pubkey = String::new();
203 let mut file = File::open(path.as_ref())?;
204 file.read_to_string(&mut pubkey)?;
205
206 let mut split = pubkey.split_whitespace();
207 match (split.next(), split.next()) {
208 (Some(_), Some(key)) => parse_public_key_base64(key),
209 (Some(key), None) => parse_public_key_base64(key),
210 _ => Err(Error::CouldNotReadKey),
211 }
212}
213
214pub fn parse_public_key_base64(key: &str) -> Result<ssh_key::PublicKey, Error> {
222 let base = BASE64_MIME.decode(key.as_bytes())?;
223 key::parse_public_key(&base)
224}
225
226pub trait PublicKeyBase64 {
227 fn public_key_bytes(&self) -> Vec<u8>;
229 fn public_key_base64(&self) -> String {
230 let mut s = BASE64_MIME.encode(&self.public_key_bytes());
231 assert_eq!(s.pop(), Some('\n'));
232 assert_eq!(s.pop(), Some('\r'));
233 s.replace("\r\n", "")
234 }
235}
236
237impl PublicKeyBase64 for ssh_key::PublicKey {
238 fn public_key_bytes(&self) -> Vec<u8> {
239 self.key_data().encoded().unwrap_or_default()
240 }
241}
242
243impl PublicKeyBase64 for PrivateKey {
244 fn public_key_bytes(&self) -> Vec<u8> {
245 self.public_key().public_key_bytes()
246 }
247}
248
249pub fn load_secret_key<P: AsRef<Path>>(
251 secret_: P,
252 password: Option<&str>,
253) -> Result<PrivateKey, Error> {
254 let mut secret_file = std::fs::File::open(secret_)?;
255 let mut secret = String::new();
256 secret_file.read_to_string(&mut secret)?;
257 decode_secret_key(&secret, password)
258}
259
260pub fn load_openssh_certificate<P: AsRef<Path>>(cert_: P) -> Result<Certificate, ssh_key::Error> {
262 let mut cert_file = std::fs::File::open(cert_)?;
263 let mut cert = String::new();
264 cert_file.read_to_string(&mut cert)?;
265
266 Certificate::from_openssh(&cert)
267}
268
269fn is_base64_char(c: char) -> bool {
270 c.is_ascii_lowercase()
271 || c.is_ascii_uppercase()
272 || c.is_ascii_digit()
273 || c == '/'
274 || c == '+'
275 || c == '='
276}
277
278#[cfg(test)]
279mod test {
280
281 #[cfg(unix)]
282 use futures::Future;
283
284 use super::*;
285 #[cfg(unix)]
286 use crate::keys::agent::AgentIdentity;
287 use crate::keys::key::PublicKeyExt;
288
289 const ED25519_KEY: &str = "-----BEGIN OPENSSH PRIVATE KEY-----
290b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABDLGyfA39
291J2FcJygtYqi5ISAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIN+Wjn4+4Fcvl2Jl
292KpggT+wCRxpSvtqqpVrQrKN1/A22AAAAkOHDLnYZvYS6H9Q3S3Nk4ri3R2jAZlQlBbUos5
293FkHpYgNw65KCWCTXtP7ye2czMC3zjn2r98pJLobsLYQgRiHIv/CUdAdsqbvMPECB+wl/UQ
294e+JpiSq66Z6GIt0801skPh20jxOO3F52SoX1IeO5D5PXfZrfSZlw6S8c7bwyp2FHxDewRx
2957/wNsnDM0T7nLv/Q==
296-----END OPENSSH PRIVATE KEY-----";
297
298 const ED25519_AESCTR_KEY: &str = "-----BEGIN OPENSSH PRIVATE KEY-----
300b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABD1phlku5
301A2G7Q9iP+DcOc9AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIHeLC1lWiCYrXsf/
30285O/pkbUFZ6OGIt49PX3nw8iRoXEAAAAkKRF0st5ZI7xxo9g6A4m4l6NarkQre3mycqNXQ
303dP3jryYgvsCIBAA5jMWSjrmnOTXhidqcOy4xYCrAttzSnZ/cUadfBenL+DQq6neffw7j8r
3040tbCxVGp6yCQlKrgSZf6c0Hy7dNEIU2bJFGxLe6/kWChcUAt/5Ll5rI7DVQPJdLgehLzvv
305sJWR7W+cGvJ/vLsw==
306-----END OPENSSH PRIVATE KEY-----";
307
308 #[cfg(feature = "rsa")]
309 const RSA_KEY: &str = "-----BEGIN OPENSSH PRIVATE KEY-----
310b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
311NhAAAAAwEAAQAAAQEAuSvQ9m76zhRB4m0BUKPf17lwccj7KQ1Qtse63AOqP/VYItqEH8un
312rxPogXNBgrcCEm/ccLZZsyE3qgp3DRQkkqvJhZ6O8VBPsXxjZesRCqoFNCczy+Mf0R/Qmv
313Rnpu5+4DDLz0p7vrsRZW9ji/c98KzxeUonWgkplQaCBYLN875WdeUYMGtb1MLfNCEj177j
314gZl3CzttLRK3su6dckowXcXYv1gPTPZAwJb49J43o1QhV7+1zdwXvuFM6zuYHdu9ZHSKir
3156k1dXOET3/U+LWG5ofAo8oxUWv/7vs6h7MeajwkUeIBOWYtD+wGYRvVpxvj7nyOoWtg+jm
3160X6ndnsD+QAAA8irV+ZAq1fmQAAAAAdzc2gtcnNhAAABAQC5K9D2bvrOFEHibQFQo9/XuX
317BxyPspDVC2x7rcA6o/9Vgi2oQfy6evE+iBc0GCtwISb9xwtlmzITeqCncNFCSSq8mFno7x
318UE+xfGNl6xEKqgU0JzPL4x/RH9Ca9Gem7n7gMMvPSnu+uxFlb2OL9z3wrPF5SidaCSmVBo
319IFgs3zvlZ15Rgwa1vUwt80ISPXvuOBmXcLO20tErey7p1ySjBdxdi/WA9M9kDAlvj0njej
320VCFXv7XN3Be+4UzrO5gd271kdIqKvqTV1c4RPf9T4tYbmh8CjyjFRa//u+zqHsx5qPCRR4
321gE5Zi0P7AZhG9WnG+PufI6ha2D6ObRfqd2ewP5AAAAAwEAAQAAAQAdELqhI/RsSpO45eFR
3229hcZtnrm8WQzImrr9dfn1w9vMKSf++rHTuFIQvi48Q10ZiOGH1bbvlPAIVOqdjAPtnyzJR
323HhzmyjhjasJlk30zj+kod0kz63HzSMT9EfsYNfmYoCyMYFCKz52EU3xc87Vhi74XmZz0D0
324CgIj6TyZftmzC4YJCiwwU8K+29nxBhcbFRxpgwAksFL6PCSQsPl4y7yvXGcX+7lpZD8547
325v58q3jIkH1g2tBOusIuaiphDDStVJhVdKA55Z0Kju2kvCqsRIlf1efrq43blRgJFFFCxNZ
3268Cpolt4lOHhg+o3ucjILlCOgjDV8dB21YLxmgN5q+xFNAAAAgQC1P+eLUkHDFXnleCEVrW
327xL/DFxEyneLQz3IawGdw7cyAb7vxsYrGUvbVUFkxeiv397pDHLZ5U+t5cOYDBZ7G43Mt2g
328YfWBuRNvYhHA9Sdf38m5qPA6XCvm51f+FxInwd/kwRKH01RHJuRGsl/4Apu4DqVob8y00V
329WTYyV6JBNDkQAAAIEA322lj7ZJXfK/oLhMM/RS+DvaMea1g/q43mdRJFQQso4XRCL6IIVn
330oZXFeOxrMIRByVZBw+FSeB6OayWcZMySpJQBo70GdJOc3pJb3js0T+P2XA9+/jwXS58K9a
331+IkgLkv9XkfxNGNKyPEEzXC8QQzvjs1LbmO59VLko8ypwHq/cAAACBANQqaULI0qdwa0vm
332d3Ae1+k3YLZ0kapSQGVIMT2lkrhKV35tj7HIFpUPa4vitHzcUwtjYhqFezVF+JyPbJ/Fsp
333XmEc0g1fFnQp5/SkUwoN2zm8Up52GBelkq2Jk57mOMzWO0QzzNuNV/feJk02b2aE8rrAqP
334QR+u0AypRPmzHnOPAAAAEXJvb3RAMTQwOTExNTQ5NDBkAQ==
335-----END OPENSSH PRIVATE KEY-----";
336
337 #[test]
338 fn test_decode_ed25519_secret_key() {
339 env_logger::try_init().unwrap_or(());
340 decode_secret_key(ED25519_KEY, Some("blabla")).unwrap();
341 }
342
343 #[test]
344 fn test_decode_ed25519_aesctr_secret_key() {
345 env_logger::try_init().unwrap_or(());
346 decode_secret_key(ED25519_AESCTR_KEY, Some("test")).unwrap();
347 }
348
349 const RFC8410_ED25519_PRIVATE_ONLY_KEY: &str = "-----BEGIN PRIVATE KEY-----
351MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
352-----END PRIVATE KEY-----";
353
354 #[test]
355 fn test_decode_rfc8410_ed25519_private_only_key() {
356 env_logger::try_init().unwrap_or(());
357 assert!(
358 decode_secret_key(RFC8410_ED25519_PRIVATE_ONLY_KEY, None)
359 .unwrap()
360 .algorithm()
361 == ssh_key::Algorithm::Ed25519,
362 );
363 }
365
366 const RFC8410_ED25519_PRIVATE_PUBLIC_KEY: &str = "-----BEGIN PRIVATE KEY-----
368MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
369oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB
370Z9w7lshQhqowtrbLDFw4rXAxZuE=
371-----END PRIVATE KEY-----";
372
373 #[test]
374 fn test_decode_rfc8410_ed25519_private_public_key() {
375 env_logger::try_init().unwrap_or(());
376 assert!(
377 decode_secret_key(RFC8410_ED25519_PRIVATE_PUBLIC_KEY, None)
378 .unwrap()
379 .algorithm()
380 == ssh_key::Algorithm::Ed25519,
381 );
382 }
384
385 #[cfg(feature = "rsa")]
386 #[test]
387 fn test_decode_rsa_secret_key() {
388 env_logger::try_init().unwrap_or(());
389 decode_secret_key(RSA_KEY, None).unwrap();
390 }
391
392 #[test]
393 fn test_decode_openssh_p256_secret_key() {
394 let key = "-----BEGIN OPENSSH PRIVATE KEY-----
396b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
3971zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQ/i+HCsmZZPy0JhtT64vW7EmeA1DeA
398M/VnPq3vAhu+xooJ7IMMK3lUHlBDosyvA2enNbCWyvNQc25dVt4oh9RhAAAAqHG7WMFxu1
399jBAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBD+L4cKyZlk/LQmG
4001Pri9bsSZ4DUN4Az9Wc+re8CG77GignsgwwreVQeUEOizK8DZ6c1sJbK81Bzbl1W3iiH1G
401EAAAAgLAmXR6IlN0SdiD6o8qr+vUr0mXLbajs/m0UlegElOmoAAAANcm9iZXJ0QGJic2Rl
402dgECAw==
403-----END OPENSSH PRIVATE KEY-----
404";
405 assert!(
406 decode_secret_key(key, None).unwrap().algorithm()
407 == ssh_key::Algorithm::Ecdsa {
408 curve: ssh_key::EcdsaCurve::NistP256
409 },
410 );
411 }
412
413 #[test]
414 fn test_decode_openssh_p384_secret_key() {
415 let key = "-----BEGIN OPENSSH PRIVATE KEY-----
417b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS
4181zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQTkLnKPk/1NZD9mQ8XoebD7ASv9/svh
4195jO75HF7RYAqKK3fl5wsHe4VTJAOT3qH841yTcK79l0dwhHhHeg60byL7F9xOEzr2kqGeY
420Uwrl7fVaL7hfHzt6z+sG8smSQ3tF8AAADYHjjBch44wXIAAAATZWNkc2Etc2hhMi1uaXN0
421cDM4NAAAAAhuaXN0cDM4NAAAAGEE5C5yj5P9TWQ/ZkPF6Hmw+wEr/f7L4eYzu+Rxe0WAKi
422it35ecLB3uFUyQDk96h/ONck3Cu/ZdHcIR4R3oOtG8i+xfcThM69pKhnmFMK5e31Wi+4Xx
42387es/rBvLJkkN7RfAAAAMFzt6053dxaQT0Ta/CGfZna0nibHzxa55zgBmje/Ho3QDNlBCH
424Ylv0h4Wyzto8NfLQAAAA1yb2JlcnRAYmJzZGV2AQID
425-----END OPENSSH PRIVATE KEY-----
426";
427 assert!(
428 decode_secret_key(key, None).unwrap().algorithm()
429 == ssh_key::Algorithm::Ecdsa {
430 curve: ssh_key::EcdsaCurve::NistP384
431 },
432 );
433 }
434
435 #[test]
436 fn test_decode_openssh_p521_secret_key() {
437 let key = "-----BEGIN OPENSSH PRIVATE KEY-----
439b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS
4401zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQA7a9awmFeDjzYiuUOwMfXkKTevfQI
441iGlduu8BkjBOWXpffJpKsdTyJI/xI05l34OvqfCCkPUcfFWHK+LVRGahMBgBcGB9ZZOEEq
442iKNIT6C9WcJTGDqcBSzQ2yTSOxPXfUmVTr4D76vbYu5bjd9aBKx8HdfMvPeo0WD0ds/LjX
443LdJoDXcAAAEQ9fxlIfX8ZSEAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ
444AAAIUEAO2vWsJhXg482IrlDsDH15Ck3r30CIhpXbrvAZIwTll6X3yaSrHU8iSP8SNOZd+D
445r6nwgpD1HHxVhyvi1URmoTAYAXBgfWWThBKoijSE+gvVnCUxg6nAUs0Nsk0jsT131JlU6+
446A++r22LuW43fWgSsfB3XzLz3qNFg9HbPy41y3SaA13AAAAQgH4DaftY0e/KsN695VJ06wy
447Ve0k2ddxoEsSE15H4lgNHM2iuYKzIqZJOReHRCTff6QGgMYPDqDfFfL1Hc1Ntql0pwAAAA
4481yb2JlcnRAYmJzZGV2AQIDBAU=
449-----END OPENSSH PRIVATE KEY-----
450";
451 assert!(
452 decode_secret_key(key, None).unwrap().algorithm()
453 == ssh_key::Algorithm::Ecdsa {
454 curve: ssh_key::EcdsaCurve::NistP521
455 },
456 );
457 }
458
459 #[test]
460 fn test_fingerprint() {
461 let key = parse_public_key_base64(
462 "AAAAC3NzaC1lZDI1NTE5AAAAILagOJFgwaMNhBWQINinKOXmqS4Gh5NgxgriXwdOoINJ",
463 )
464 .unwrap();
465 assert_eq!(
466 format!("{}", key.fingerprint(ssh_key::HashAlg::Sha256)),
467 "SHA256:ldyiXa1JQakitNU5tErauu8DvWQ1dZ7aXu+rm7KQuog"
468 );
469 }
470
471 #[test]
472 fn test_parse_p256_public_key() {
473 env_logger::try_init().unwrap_or(());
474 let key = "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMxBTpMIGvo7CnordO7wP0QQRqpBwUjOLl4eMhfucfE1sjTYyK5wmTl1UqoSDS1PtRVTBdl+0+9pquFb46U7fwg=";
475
476 assert!(
477 parse_public_key_base64(key).unwrap().algorithm()
478 == ssh_key::Algorithm::Ecdsa {
479 curve: ssh_key::EcdsaCurve::NistP256
480 },
481 );
482 }
483
484 #[test]
485 fn test_parse_p384_public_key() {
486 env_logger::try_init().unwrap_or(());
487 let key = "AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBBVFgxJxpCaAALZG/S5BHT8/IUQ5mfuKaj7Av9g7Jw59fBEGHfPBz1wFtHGYw5bdLmfVZTIDfogDid5zqJeAKr1AcD06DKTXDzd2EpUjqeLfQ5b3erHuX758fgu/pSDGRA==";
488
489 assert!(
490 parse_public_key_base64(key).unwrap().algorithm()
491 == ssh_key::Algorithm::Ecdsa {
492 curve: ssh_key::EcdsaCurve::NistP384
493 }
494 );
495 }
496
497 #[test]
498 fn test_parse_p521_public_key() {
499 env_logger::try_init().unwrap_or(());
500 let key = "AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAAQepXEpOrzlX22r4E5zEHjhHWeZUe//zaevTanOWRBnnaCGWJFGCdjeAbNOuAmLtXc+HZdJTCZGREeSLSrpJa71QDCgZl0N7DkDUanCpHZJe/DCK6qwtHYbEMn28iLMlGCOrCIa060EyJHbp1xcJx4I1SKj/f/fm3DhhID/do6zyf8Cg==";
501
502 assert!(
503 parse_public_key_base64(key).unwrap().algorithm()
504 == ssh_key::Algorithm::Ecdsa {
505 curve: ssh_key::EcdsaCurve::NistP521
506 }
507 );
508 }
509
510 #[test]
511 fn test_srhb() {
512 env_logger::try_init().unwrap_or(());
513 let key = "AAAAB3NzaC1yc2EAAAADAQABAAACAQC0Xtz3tSNgbUQAXem4d+d6hMx7S8Nwm/DOO2AWyWCru+n/+jQ7wz2b5+3oG2+7GbWZNGj8HCc6wJSA3jUsgv1N6PImIWclD14qvoqY3Dea1J0CJgXnnM1xKzBz9C6pDHGvdtySg+yzEO41Xt4u7HFn4Zx5SGuI2NBsF5mtMLZXSi33jCIWVIkrJVd7sZaY8jiqeVZBB/UvkLPWewGVuSXZHT84pNw4+S0Rh6P6zdNutK+JbeuO+5Bav4h9iw4t2sdRkEiWg/AdMoSKmo97Gigq2mKdW12ivnXxz3VfxrCgYJj9WwaUUWSfnAju5SiNly0cTEAN4dJ7yB0mfLKope1kRhPsNaOuUmMUqlu/hBDM/luOCzNjyVJ+0LLB7SV5vOiV7xkVd4KbEGKou8eeCR3yjFazUe/D1pjYPssPL8cJhTSuMc+/UC9zD8yeEZhB9V+vW4NMUR+lh5+XeOzenl65lWYd/nBZXLBbpUMf1AOfbz65xluwCxr2D2lj46iApSIpvE63i3LzFkbGl9GdUiuZJLMFJzOWdhGGc97cB5OVyf8umZLqMHjaImxHEHrnPh1MOVpv87HYJtSBEsN4/omINCMZrk++CRYAIRKRpPKFWV7NQHcvw3m7XLR3KaTYe+0/MINIZwGdou9fLUU3zSd521vDjA/weasH0CyDHq7sZw==";
514
515 parse_public_key_base64(key).unwrap();
516 }
517
518 #[cfg(feature = "rsa")]
519 #[test]
520 fn test_nikao() {
521 env_logger::try_init().unwrap_or(());
522 let key = "-----BEGIN RSA PRIVATE KEY-----
523MIIEpQIBAAKCAQEAw/FG8YLVoXhsUVZcWaY7iZekMxQ2TAfSVh0LTnRuzsumeLhb
5240fh4scIt4C4MLwpGe/u3vj290C28jLkOtysqnIpB4iBUrFNRmEz2YuvjOzkFE8Ju
5250l1VrTZ9APhpLZvzT2N7YmTXcLz1yWopCe4KqTHczEP4lfkothxEoACXMaxezt5o
526wIYfagDaaH6jXJgJk1SQ5VYrROVpDjjX8/Zg01H1faFQUikYx0M8EwL1fY5B80Hd
5276DYSok8kUZGfkZT8HQ54DBgocjSs449CVqkVoQC1aDB+LZpMWovY15q7hFgfQmYD
528qulbZRWDxxogS6ui/zUR2IpX7wpQMKKkBS1qdQIDAQABAoIBAQCodpcCKfS2gSzP
529uapowY1KvP/FkskkEU18EDiaWWyzi1AzVn5LRo+udT6wEacUAoebLU5K2BaMF+aW
530Lr1CKnDWaeA/JIDoMDJk+TaU0i5pyppc5LwXTXvOEpzi6rCzL/O++88nR4AbQ7sm
531Uom6KdksotwtGvttJe0ktaUi058qaoFZbels5Fwk5bM5GHDdV6De8uQjSfYV813P
532tM/6A5rRVBjC5uY0ocBHxPXkqAdHfJuVk0uApjLrbm6k0M2dg1X5oyhDOf7ZIzAg
533QGPgvtsVZkQlyrD1OoCMPwzgULPXTe8SktaP9EGvKdMf5kQOqUstqfyx+E4OZa0A
534T82weLjBAoGBAOUChhaLQShL3Vsml/Nuhhw5LsxU7Li34QWM6P5AH0HMtsSncH8X
535ULYcUKGbCmmMkVb7GtsrHa4ozy0fjq0Iq9cgufolytlvC0t1vKRsOY6poC2MQgaZ
536bqRa05IKwhZdHTr9SUwB/ngtVNWRzzbFKLkn2W5oCpQGStAKqz3LbKstAoGBANsJ
537EyrXPbWbG+QWzerCIi6shQl+vzOd3cxqWyWJVaZglCXtlyySV2eKWRW7TcVvaXQr
538Nzm/99GNnux3pUCY6szy+9eevjFLLHbd+knzCZWKTZiWZWr503h/ztfFwrMzhoAh
539z4nukD/OETugPvtG01c2sxZb/F8LH9KORznhlSlpAoGBAJnqg1J9j3JU4tZTbwcG
540fo5ThHeCkINp2owPc70GPbvMqf4sBzjz46QyDaM//9SGzFwocplhNhaKiQvrzMnR
541LSVucnCEm/xdXLr/y6S6tEiFCwnx3aJv1uQRw2bBYkcDmBTAjVXPdUcyOHU+BYXr
542Jv6ioMlKlel8/SUsNoFWypeVAoGAXhr3Bjf1xlm+0O9PRyZjQ0RR4DN5eHbB/XpQ
543cL8hclsaK3V5tuek79JL1f9kOYhVeVi74G7uzTSYbCY3dJp+ftGCjDAirNEMaIGU
544cEMgAgSqs/0h06VESwg2WRQZQ57GkbR1E2DQzuj9FG4TwSe700OoC9o3gqon4PHJ
545/j9CM8kCgYEAtPJf3xaeqtbiVVzpPAGcuPyajTzU0QHPrXEl8zr/+iSK4Thc1K+c
546b9sblB+ssEUQD5IQkhTWcsXdslINQeL77WhIMZ2vBAH8Hcin4jgcLmwUZfpfnnFs
547QaChXiDsryJZwsRnruvMRX9nedtqHrgnIsJLTXjppIhGhq5Kg4RQfOU=
548-----END RSA PRIVATE KEY-----
549";
550 decode_secret_key(key, None).unwrap();
551 }
552
553 #[cfg(feature = "rsa")]
554 #[test]
555 fn test_decode_pkcs8_rsa_secret_key() {
556 let key = "-----BEGIN PRIVATE KEY-----
558MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDTwWfiCKHw/1F6
559pvm6hZpFSjCVSu4Pp0/M4xT9Cec1+2uj/6uEE9Vh/UhlerkxVbrW/YaqjnlAiemZ
5600RGN+sq7b8LxsgvOAo7gdBv13TLkKxNFiRbSy8S257uA9/K7G4Uw+NW22zoLSKCp
561pdJOFzaYMIT/UX9EOq9hIIn4bS4nXJ4V5+aHBtMddHHDQPEDHBHuifpP2L4Wopzu
562WoQoVtN9cwHSLh0Bd7uT+X9useIJrFzcsxVXwD2WGfR59Ue3rxRu6JqC46Klf55R
5635NQ8OQ+7NHXjW5HO076W1GXcnhGKT5CGjglTdk5XxQkNZsz72cHu7RDaADdWAWnE
564hSyH7flrAgMBAAECggEAbFdpCjn2eTJ4grOJ1AflTYxO3SOQN8wXxTFuHKUDehgg
565E7GNFK99HnyTnPA0bmx5guQGEZ+BpCarsXpJbAYj0dC1wimhZo7igS6G272H+zua
566yZoBZmrBQ/++bJbvxxGmjM7TsZHq2bkYEpR3zGKOGUHB2kvdPJB2CNC4JrXdxl7q
567djjsr5f/SreDmHqcNBe1LcyWLSsuKTfwTKhsE1qEe6QA2uOpUuFrsdPoeYrfgapu
568sK6qnpxvOTJHCN/9jjetrP2fGl78FMBYfXzjAyKSKzLvzOwMAmcHxy50RgUvezx7
569A1RwMpB7VoV0MOpcAjlQ1T7YDH9avdPMzp0EZ24y+QKBgQD/MxDJjHu33w13MnIg
570R4BrgXvrgL89Zde5tML2/U9C2LRvFjbBvgnYdqLsuqxDxGY/8XerrAkubi7Fx7QI
571m2uvTOZF915UT/64T35zk8nAAFhzicCosVCnBEySvdwaaBKoj/ywemGrwoyprgFe
572r8LGSo42uJi0zNf5IxmVzrDlRwKBgQDUa3P/+GxgpUYnmlt63/7sII6HDssdTHa9
573x5uPy8/2ackNR7FruEAJR1jz6akvKnvtbCBeRxLeOFwsseFta8rb2vks7a/3I8ph
574gJlbw5Bttpc+QsNgC61TdSKVsfWWae+YT77cfGPM4RaLlxRnccW1/HZjP2AMiDYG
575WCiluO+svQKBgQC3a/yk4FQL1EXZZmigysOCgY6Ptfm+J3TmBQYcf/R4F0mYjl7M
5764coxyxNPEty92Gulieh5ey0eMhNsFB1SEmNTm/HmV+V0tApgbsJ0T8SyO41Xfar7
577lHZjlLN0xQFt+V9vyA3Wyh9pVGvFiUtywuE7pFqS+hrH2HNindfF1MlQAQKBgQDF
578YxBIxKzY5duaA2qMdMcq3lnzEIEXua0BTxGz/n1CCizkZUFtyqnetWjoRrGK/Zxp
579FDfDw6G50397nNPQXQEFaaZv5HLGYYC3N8vKJKD6AljqZxmsD03BprA7kEGYwtn8
580m+XMdt46TNMpZXt1YJiLMo1ETmjPXGdvX85tqLs2tQKBgQDCbwd+OBzSiic3IQlD
581E/OHAXH6HNHmUL3VD5IiRh4At2VAIl8JsmafUvvbtr5dfT3PA8HB6sDG4iXQsBbR
582oTSAo/DtIWt1SllGx6MvcPqL1hp1UWfoIGTnE3unHtgPId+DnjMbTcuZOuGl7evf
583abw8VeY2goORjpBXsfydBETbgQ==
584-----END PRIVATE KEY-----
585";
586 assert!(decode_secret_key(key, None).unwrap().algorithm().is_rsa());
587 test_decode_encode_symmetry(key);
588 }
589
590 #[test]
591 fn test_decode_pkcs8_p256_secret_key() {
592 let key = "-----BEGIN PRIVATE KEY-----
594MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgE0C7/pyJDcZTAgWo
595ydj6EE8QkZ91jtGoGmdYAVd7LaqhRANCAATWkGOof7R/PAUuOr2+ZPUgB8rGVvgr
596qa92U3p4fkJToKXku5eq/32OBj23YMtz76jO3yfMbtG3l1JWLowPA8tV
597-----END PRIVATE KEY-----
598";
599 assert!(
600 decode_secret_key(key, None).unwrap().algorithm()
601 == ssh_key::Algorithm::Ecdsa {
602 curve: ssh_key::EcdsaCurve::NistP256
603 },
604 );
605 test_decode_encode_symmetry(key);
606 }
607
608 #[test]
609 fn test_decode_pkcs8_p384_secret_key() {
610 let key = "-----BEGIN PRIVATE KEY-----
612MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCaqAL30kg+T5BUOYG9
613MrzeDXiUwy9LM8qJGNXiMYou0pVjFZPZT3jAsrUQo47PLQ6hZANiAARuEHbXJBYK
6149uyJj4PjT56OHjT2GqMa6i+FTG9vdLtu4OLUkXku+kOuFNjKvEI1JYBrJTpw9kSZ
615CI3WfCsQvVjoC7m8qRyxuvR3Rv8gGXR1coQciIoCurLnn9zOFvXCS2Y=
616-----END PRIVATE KEY-----
617";
618 assert!(
619 decode_secret_key(key, None).unwrap().algorithm()
620 == ssh_key::Algorithm::Ecdsa {
621 curve: ssh_key::EcdsaCurve::NistP384
622 },
623 );
624 test_decode_encode_symmetry(key);
625 }
626
627 #[test]
628 fn test_decode_pkcs8_p521_secret_key() {
629 let key = "-----BEGIN PRIVATE KEY-----
631MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB1As9UBUsCiMK7Rzs
632EoMgqDM/TK7y7+HgCWzw5UujXvSXCzYCeBgfJszn7dVoJE9G/1ejmpnVTnypdKEu
633iIvd4LyhgYkDgYYABAADBCrg7hkomJbCsPMuMcq68ulmo/6Tv8BDS13F8T14v5RN
634/0iT/+nwp6CnbBFewMI2TOh/UZNyPpQ8wOFNn9zBmAFCMzkQibnSWK0hrRstY5LT
635iaOYDwInbFDsHu8j3TGs29KxyVXMexeV6ROQyXzjVC/quT1R5cOQ7EadE4HvaWhT
636Ow==
637-----END PRIVATE KEY-----
638";
639 assert!(
640 decode_secret_key(key, None).unwrap().algorithm()
641 == ssh_key::Algorithm::Ecdsa {
642 curve: ssh_key::EcdsaCurve::NistP521
643 },
644 );
645 test_decode_encode_symmetry(key);
646 }
647
648 #[test]
649 #[cfg(feature = "legacy-ed25519-pkcs8-parser")]
650 fn test_decode_pkcs8_ed25519_generated_by_russh_0_43() -> Result<(), crate::keys::Error> {
651 let key = "-----BEGIN PRIVATE KEY-----
653MHMCAQEwBQYDK2VwBEIEQBHw4cXPpGgA+KdvPF5gxrzML+oa3yQk0JzIbWvmqM5H30RyBF8GrOWz
654p77UAd3O4PgYzzFcUc79g8yKtbKhzJGhIwMhAN9EcgRfBqzls6e+1AHdzuD4GM8xXFHO/YPMirWy
655ocyR
656
657-----END PRIVATE KEY-----
658";
659
660 assert!(decode_secret_key(key, None)?.algorithm() == ssh_key::Algorithm::Ed25519,);
661
662 let k = decode_secret_key(key, None)?;
663 let inner = k.key_data().ed25519().unwrap();
664
665 assert_eq!(
666 &inner.private.to_bytes(),
667 &[
668 17, 240, 225, 197, 207, 164, 104, 0, 248, 167, 111, 60, 94, 96, 198, 188, 204, 47,
669 234, 26, 223, 36, 36, 208, 156, 200, 109, 107, 230, 168, 206, 71
670 ]
671 );
672
673 Ok(())
674 }
675
676 fn test_decode_encode_symmetry(key: &str) {
677 let original_key_bytes = data_encoding::BASE64_MIME
678 .decode(
679 key.lines()
680 .filter(|line| !line.starts_with("-----"))
681 .collect::<Vec<&str>>()
682 .join("")
683 .as_bytes(),
684 )
685 .unwrap();
686 let decoded_key = decode_secret_key(key, None).unwrap();
687 let encoded_key_bytes = pkcs8::encode_pkcs8(&decoded_key).unwrap();
688 assert_eq!(original_key_bytes, encoded_key_bytes);
689 }
690
691 #[cfg(feature = "rsa")]
692 #[test]
693 fn test_o01eg() {
694 env_logger::try_init().unwrap_or(());
695
696 let key = "-----BEGIN RSA PRIVATE KEY-----
697Proc-Type: 4,ENCRYPTED
698DEK-Info: AES-128-CBC,EA77308AAF46981303D8C44D548D097E
699
700QR18hXmAgGehm1QMMYGF34PAtBpTj+8/ZPFx2zZxir7pzDpfYoNAIf/fzLsW1ruG
7010xo/ZK/T3/TpMgjmLsCR6q+KU4jmCcCqWQIGWYJt9ljFI5y/CXr5uqP3DKcqtdxQ
702fbBAfXJ8ITF+Tj0Cljm2S1KYHor+mkil5Lf/ZNiHxcLfoI3xRnpd+2cemN9Ly9eY
703HNTbeWbLosfjwdfPJNWFNV5flm/j49klx/UhXhr5HNFNgp/MlTrvkH4rBt4wYPpE
704cZBykt4Fo1KGl95pT22inGxQEXVHF1Cfzrf5doYWxjiRTmfhpPSz/Tt0ev3+jIb8
705Htx6N8tNBoVxwCiQb7jj3XNim2OGohIp5vgW9sh6RDfIvr1jphVOgCTFKSo37xk0
706156EoCVo3VcLf+p0/QitbUHR+RGW/PvUJV/wFR5ShYqjI+N2iPhkD24kftJ/MjPt
707AAwCm/GYoYjGDhIzQMB+FETZKU5kz23MQtZFbYjzkcI/RE87c4fkToekNCdQrsoZ
708wG0Ne2CxrwwEnipHCqT4qY+lZB9EbqQgbWOXJgxA7lfznBFjdSX7uDc/mnIt9Y6B
709MZRXH3PTfotHlHMe+Ypt5lfPBi/nruOl5wLo3L4kY5pUyqR0cXKNycIJZb/pJAnE
710ryIb59pZP7njvoHzRqnC9dycnTFW3geK5LU+4+JMUS32F636aorunRCl6IBmVQHL
711uZ+ue714fn/Sn6H4dw6IH1HMDG1hr8ozP4sNUCiAQ05LsjDMGTdrUsr2iBBpkQhu
712VhUDZy9g/5XF1EgiMbZahmqi5WaJ5K75ToINHb7RjOE7MEiuZ+RPpmYLE0HXyn9X
713HTx0ZGr022dDI6nkvUm6OvEwLUUmmGKRHKe0y1EdICGNV+HWqnlhGDbLWeMyUcIY
714M6Zh9Dw3WXD3kROf5MrJ6n9MDIXx9jy7nmBh7m6zKjBVIw94TE0dsRcWb0O1IoqS
715zLQ6ihno+KsQHDyMVLEUz1TuE52rIpBmqexDm3PdDfCgsNdBKP6QSTcoqcfHKeex
716K93FWgSlvFFQQAkJumJJ+B7ZWnK+2pdjdtWwTpflAKNqc8t//WmjWZzCtbhTHCXV
7171dnMk7azWltBAuXnjW+OqmuAzyh3ayKgqfW66mzSuyQNa1KqFhqpJxOG7IHvxVfQ
718kYeSpqODnL87Zd/dU8s0lOxz3/ymtjPMHlOZ/nHNqW90IIeUwWJKJ46Kv6zXqM1t
719MeD1lvysBbU9rmcUdop0D3MOgGpKkinR5gy4pUsARBiz4WhIm8muZFIObWes/GDS
720zmmkQRO1IcfXKAHbq/OdwbLBm4vM9nk8vPfszoEQCnfOSd7aWrLRjDR+q2RnzNzh
721K+fodaJ864JFIfB/A+aVviVWvBSt0eEbEawhTmNPerMrAQ8tRRhmNxqlDP4gOczi
722iKUmK5recsXk5us5Ik7peIR/f9GAghpoJkF0HrHio47SfABuK30pzcj62uNWGljS
7233d9UQLCepT6RiPFhks/lgimbtSoiJHql1H9Q/3q4MuO2PuG7FXzlTnui3zGw/Vvy
724br8gXU8KyiY9sZVbmplRPF+ar462zcI2kt0a18mr0vbrdqp2eMjb37QDbVBJ+rPE
725-----END RSA PRIVATE KEY-----
726";
727 decode_secret_key(key, Some("12345")).unwrap();
728 }
729
730 #[cfg(feature = "rsa")]
731 pub const PKCS8_RSA: &str = "-----BEGIN RSA PRIVATE KEY-----
732MIIEpAIBAAKCAQEAwBGetHjW+3bDQpVktdemnk7JXgu1NBWUM+ysifYLDBvJ9ttX
733GNZSyQKA4v/dNr0FhAJ8I9BuOTjYCy1YfKylhl5D/DiSSXFPsQzERMmGgAlYvU2U
734+FTxpBC11EZg69CPVMKKevfoUD+PZA5zB7Hc1dXFfwqFc5249SdbAwD39VTbrOUI
735WECvWZs6/ucQxHHXP2O9qxWqhzb/ddOnqsDHUNoeceiNiCf2anNymovrIMjAqq1R
736t2UP3f06/Zt7Jx5AxKqS4seFkaDlMAK8JkEDuMDOdKI36raHkKanfx8CnGMSNjFQ
737QtvnpD8VSGkDTJN3Qs14vj2wvS477BQXkBKN1QIDAQABAoIBABb6xLMw9f+2ENyJ
738hTggagXsxTjkS7TElCu2OFp1PpMfTAWl7oDBO7xi+UqvdCcVbHCD35hlWpqsC2Ui
7398sBP46n040ts9UumK/Ox5FWaiuYMuDpF6vnfJ94KRcb0+KmeFVf9wpW9zWS0hhJh
740jC+yfwpyfiOZ/ad8imGCaOguGHyYiiwbRf381T/1FlaOGSae88h+O8SKTG1Oahq4
7410HZ/KBQf9pij0mfVQhYBzsNu2JsHNx9+DwJkrXT7K9SHBpiBAKisTTCnQmS89GtE
7426J2+bq96WgugiM7X6OPnmBmE/q1TgV18OhT+rlvvNi5/n8Z1ag5Xlg1Rtq/bxByP
743CeIVHsECgYEA9dX+LQdv/Mg/VGIos2LbpJUhJDj0XWnTRq9Kk2tVzr+9aL5VikEb
74409UPIEa2ToL6LjlkDOnyqIMd/WY1W0+9Zf1ttg43S/6Rvv1W8YQde0Nc7QTcuZ1K
7459jSSP9hzsa3KZtx0fCtvVHm+ac9fP6u80tqumbiD2F0cnCZcSxOb4+UCgYEAyAKJ
74670nNKegH4rTCStAqR7WGAsdPE3hBsC814jguplCpb4TwID+U78Xxu0DQF8WtVJ10
747SJuR0R2q4L9uYWpo0MxdawSK5s9Am27MtJL0mkFQX0QiM7hSZ3oqimsdUdXwxCGg
748oktxCUUHDIPJNVd4Xjg0JTh4UZT6WK9hl1zLQzECgYEAiZRCFGc2KCzVLF9m0cXA
749kGIZUxFAyMqBv+w3+zq1oegyk1z5uE7pyOpS9cg9HME2TAo4UPXYpLAEZ5z8vWZp
75045sp/BoGnlQQsudK8gzzBtnTNp5i/MnnetQ/CNYVIVnWjSxRUHBqdMdRZhv0/Uga
751e5KA5myZ9MtfSJA7VJTbyHUCgYBCcS13M1IXaMAt3JRqm+pftfqVs7YeJqXTrGs/
752AiDlGQigRk4quFR2rpAV/3rhWsawxDmb4So4iJ16Wb2GWP4G1sz1vyWRdSnmOJGC
753LwtYrvfPHegqvEGLpHa7UsgDpol77hvZriwXwzmLO8A8mxkeW5dfAfpeR5o+mcxW
754pvnTEQKBgQCKx6Ln0ku6jDyuDzA9xV2/PET5D75X61R2yhdxi8zurY/5Qon3OWzk
755jn/nHT3AZghGngOnzyv9wPMKt9BTHyTB6DlB6bRVLDkmNqZh5Wi8U1/IjyNYI0t2
756xV/JrzLAwPoKk3bkqys3bUmgo6DxVC/6RmMwPQ0rmpw78kOgEej90g==
757-----END RSA PRIVATE KEY-----
758";
759
760 #[cfg(feature = "rsa")]
761 #[test]
762 fn test_pkcs8() {
763 env_logger::try_init().unwrap_or(());
764 println!("test");
765 decode_secret_key(PKCS8_RSA, Some("blabla")).unwrap();
766 }
767
768 #[cfg(feature = "rsa")]
769 const PKCS8_ENCRYPTED: &str = "-----BEGIN ENCRYPTED PRIVATE KEY-----
770MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQITo1O0b8YrS0CAggA
771MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBtLH4T1KOfo1GGr7salhR8BIIE
7720KN9ednYwcTGSX3hg7fROhTw7JAJ1D4IdT1fsoGeNu2BFuIgF3cthGHe6S5zceI2
773MpkfwvHbsOlDFWMUIAb/VY8/iYxhNmd5J6NStMYRC9NC0fVzOmrJqE1wITqxtORx
774IkzqkgFUbaaiFFQPepsh5CvQfAgGEWV329SsTOKIgyTj97RxfZIKA+TR5J5g2dJY
775j346SvHhSxJ4Jc0asccgMb0HGh9UUDzDSql0OIdbnZW5KzYJPOx+aDqnpbz7UzY/
776P8N0w/pEiGmkdkNyvGsdttcjFpOWlLnLDhtLx8dDwi/sbEYHtpMzsYC9jPn3hnds
777TcotqjoSZ31O6rJD4z18FOQb4iZs3MohwEdDd9XKblTfYKM62aQJWH6cVQcg+1C7
778jX9l2wmyK26Tkkl5Qg/qSfzrCveke5muZgZkFwL0GCcgPJ8RixSB4GOdSMa/hAMU
779kvFAtoV2GluIgmSe1pG5cNMhurxM1dPPf4WnD+9hkFFSsMkTAuxDZIdDk3FA8zof
780Yhv0ZTfvT6V+vgH3Hv7Tqcxomy5Qr3tj5vvAqqDU6k7fC4FvkxDh2mG5ovWvc4Nb
781Xv8sed0LGpYitIOMldu6650LoZAqJVv5N4cAA2Edqldf7S2Iz1QnA/usXkQd4tLa
782Z80+sDNv9eCVkfaJ6kOVLk/ghLdXWJYRLenfQZtVUXrPkaPpNXgD0dlaTN8KuvML
783Uw/UGa+4ybnPsdVflI0YkJKbxouhp4iB4S5ACAwqHVmsH5GRnujf10qLoS7RjDAl
784o/wSHxdT9BECp7TT8ID65u2mlJvH13iJbktPczGXt07nBiBse6OxsClfBtHkRLzE
785QF6UMEXsJnIIMRfrZQnduC8FUOkfPOSXc8r9SeZ3GhfbV/DmWZvFPCpjzKYPsM5+
786N8Bw/iZ7NIH4xzNOgwdp5BzjH9hRtCt4sUKVVlWfEDtTnkHNOusQGKu7HkBF87YZ
787RN/Nd3gvHob668JOcGchcOzcsqsgzhGMD8+G9T9oZkFCYtwUXQU2XjMN0R4VtQgZ
788rAxWyQau9xXMGyDC67gQ5xSn+oqMK0HmoW8jh2LG/cUowHFAkUxdzGadnjGhMOI2
789zwNJPIjF93eDF/+zW5E1l0iGdiYyHkJbWSvcCuvTwma9FIDB45vOh5mSR+YjjSM5
790nq3THSWNi7Cxqz12Q1+i9pz92T2myYKBBtu1WDh+2KOn5DUkfEadY5SsIu/Rb7ub
7915FBihk2RN3y/iZk+36I69HgGg1OElYjps3D+A9AjVby10zxxLAz8U28YqJZm4wA/
792T0HLxBiVw+rsHmLP79KvsT2+b4Diqih+VTXouPWC/W+lELYKSlqnJCat77IxgM9e
793YIhzD47OgWl33GJ/R10+RDoDvY4koYE+V5NLglEhbwjloo9Ryv5ywBJNS7mfXMsK
794/uf+l2AscZTZ1mhtL38efTQCIRjyFHc3V31DI0UdETADi+/Omz+bXu0D5VvX+7c6
795b1iVZKpJw8KUjzeUV8yOZhvGu3LrQbhkTPVYL555iP1KN0Eya88ra+FUKMwLgjYr
796JkUx4iad4dTsGPodwEP/Y9oX/Qk3ZQr+REZ8lg6IBoKKqqrQeBJ9gkm1jfKE6Xkc
797Cog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/MtlOAr5rux
798-----END ENCRYPTED PRIVATE KEY-----";
799
800 #[test]
801 fn test_gpg() {
802 env_logger::try_init().unwrap_or(());
803 let key = [
804 0, 0, 0, 7, 115, 115, 104, 45, 114, 115, 97, 0, 0, 0, 3, 1, 0, 1, 0, 0, 1, 129, 0, 163,
805 72, 59, 242, 4, 248, 139, 217, 57, 126, 18, 195, 170, 3, 94, 154, 9, 150, 89, 171, 236,
806 192, 178, 185, 149, 73, 210, 121, 95, 126, 225, 209, 199, 208, 89, 130, 175, 229, 163,
807 102, 176, 155, 69, 199, 155, 71, 214, 170, 61, 202, 2, 207, 66, 198, 147, 65, 10, 176,
808 20, 105, 197, 133, 101, 126, 193, 252, 245, 254, 182, 14, 250, 118, 113, 18, 220, 38,
809 220, 75, 247, 50, 163, 39, 2, 61, 62, 28, 79, 199, 238, 189, 33, 194, 190, 22, 87, 91,
810 1, 215, 115, 99, 138, 124, 197, 127, 237, 228, 170, 42, 25, 117, 1, 106, 36, 54, 163,
811 163, 207, 129, 133, 133, 28, 185, 170, 217, 12, 37, 113, 181, 182, 180, 178, 23, 198,
812 233, 31, 214, 226, 114, 146, 74, 205, 177, 82, 232, 238, 165, 44, 5, 250, 150, 236, 45,
813 30, 189, 254, 118, 55, 154, 21, 20, 184, 235, 223, 5, 20, 132, 249, 147, 179, 88, 146,
814 6, 100, 229, 200, 221, 157, 135, 203, 57, 204, 43, 27, 58, 85, 54, 219, 138, 18, 37,
815 80, 106, 182, 95, 124, 140, 90, 29, 48, 193, 112, 19, 53, 84, 201, 153, 52, 249, 15,
816 41, 5, 11, 147, 18, 8, 27, 31, 114, 45, 224, 118, 111, 176, 86, 88, 23, 150, 184, 252,
817 128, 52, 228, 90, 30, 34, 135, 234, 123, 28, 239, 90, 202, 239, 188, 175, 8, 141, 80,
818 59, 194, 80, 43, 205, 34, 137, 45, 140, 244, 181, 182, 229, 247, 94, 216, 115, 173,
819 107, 184, 170, 102, 78, 249, 4, 186, 234, 169, 148, 98, 128, 33, 115, 232, 126, 84, 76,
820 222, 145, 90, 58, 1, 4, 163, 243, 93, 215, 154, 205, 152, 178, 109, 241, 197, 82, 148,
821 222, 78, 44, 193, 248, 212, 157, 118, 217, 75, 211, 23, 229, 121, 28, 180, 208, 173,
822 204, 14, 111, 226, 25, 163, 220, 95, 78, 175, 189, 168, 67, 159, 179, 176, 200, 150,
823 202, 248, 174, 109, 25, 89, 176, 220, 226, 208, 187, 84, 169, 157, 14, 88, 217, 221,
824 117, 254, 51, 45, 93, 184, 80, 225, 158, 29, 76, 38, 69, 72, 71, 76, 50, 191, 210, 95,
825 152, 175, 26, 207, 91, 7,
826 ];
827 ssh_key::PublicKey::decode(&key).unwrap();
828 }
829
830 #[cfg(feature = "rsa")]
831 #[test]
832 fn test_pkcs8_encrypted() {
833 env_logger::try_init().unwrap_or(());
834 println!("test");
835 decode_secret_key(PKCS8_ENCRYPTED, Some("blabla")).unwrap();
836 }
837
838 #[cfg(unix)]
839 async fn test_client_agent(key: PrivateKey) -> Result<(), Box<dyn std::error::Error>> {
840 env_logger::try_init().unwrap_or(());
841 use std::process::Stdio;
842
843 let dir = tempfile::tempdir()?;
844 let agent_path = dir.path().join("agent");
845 let mut agent = tokio::process::Command::new("ssh-agent")
846 .arg("-a")
847 .arg(&agent_path)
848 .arg("-D")
849 .stdout(Stdio::null())
850 .stderr(Stdio::null())
851 .spawn()?;
852
853 while agent_path.canonicalize().is_err() {
855 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
856 }
857
858 let public = key.public_key();
859 let stream = tokio::net::UnixStream::connect(&agent_path).await?;
860 let mut client = agent::client::AgentClient::connect(stream);
861 client.add_identity(&key, &[]).await?;
862 client.request_identities().await?;
863 let buf = b"blabla".to_vec();
864 let len = buf.len();
865 let buf = client
866 .sign_request(
867 &AgentIdentity::from(public.clone()),
868 Some(HashAlg::Sha256),
869 buf,
870 )
871 .await
872 .unwrap();
873 let (a, b) = buf.split_at(len);
874
875 match key.public_key().key_data() {
876 ssh_key::public::KeyData::Ed25519 { .. } => {
877 let sig = &b[b.len() - 64..];
878 let sig = ssh_key::Signature::new(key.algorithm(), sig)?;
879 use signature::Verifier;
880 assert!(Verifier::verify(public, a, &sig).is_ok());
881 }
882 ssh_key::public::KeyData::Ecdsa { .. } => {}
883 _ => {}
884 }
885
886 agent.kill().await?;
887 agent.wait().await?;
888
889 Ok(())
890 }
891
892 #[tokio::test]
893 #[cfg(unix)]
894 async fn test_client_agent_ed25519() {
895 let key = decode_secret_key(ED25519_KEY, Some("blabla")).unwrap();
896 test_client_agent(key).await.expect("ssh-agent test failed")
897 }
898
899 #[tokio::test]
900 #[cfg(all(unix, feature = "rsa"))]
901 async fn test_client_agent_rsa() {
902 let key = decode_secret_key(PKCS8_ENCRYPTED, Some("blabla")).unwrap();
903 test_client_agent(key).await.expect("ssh-agent test failed")
904 }
905
906 #[tokio::test]
907 #[cfg(all(unix, feature = "rsa"))]
908 async fn test_client_agent_openssh_rsa() {
909 let key = decode_secret_key(RSA_KEY, None).unwrap();
910 test_client_agent(key).await.expect("ssh-agent test failed")
911 }
912
913 #[test]
914 #[cfg(all(unix, feature = "rsa"))]
915 fn test_agent() {
916 env_logger::try_init().unwrap_or(());
917 let dir = tempfile::tempdir().unwrap();
918 let agent_path = dir.path().join("agent");
919
920 let core = tokio::runtime::Runtime::new().unwrap();
921 use agent;
922 use signature::Verifier;
923
924 #[derive(Clone)]
925 struct X {}
926 impl agent::server::Agent for X {
927 fn confirm(
928 self,
929 _: std::sync::Arc<PrivateKey>,
930 ) -> Box<dyn Future<Output = (Self, bool)> + Send + Unpin> {
931 Box::new(futures::future::ready((self, true)))
932 }
933 }
934 let agent_path_ = agent_path.clone();
935 let (tx, rx) = tokio::sync::oneshot::channel();
936 core.spawn(async move {
937 let mut listener = tokio::net::UnixListener::bind(&agent_path_).unwrap();
938 let _ = tx.send(());
939 agent::server::serve(
940 Incoming {
941 listener: &mut listener,
942 },
943 X {},
944 )
945 .await
946 });
947
948 let key = decode_secret_key(PKCS8_ENCRYPTED, Some("blabla")).unwrap();
949 core.block_on(async move {
950 let public = key.public_key();
951 rx.await.unwrap();
953 let stream = tokio::net::UnixStream::connect(&agent_path).await.unwrap();
954 let mut client = agent::client::AgentClient::connect(stream);
955 client
956 .add_identity(&key, &[agent::Constraint::KeyLifetime { seconds: 60 }])
957 .await
958 .unwrap();
959 client.request_identities().await.unwrap();
960 let buf = b"blabla".to_vec();
961 let len = buf.len();
962 let buf = client
963 .sign_request(&AgentIdentity::from(public.clone()), None, buf)
964 .await
965 .unwrap();
966 let (a, b) = buf.split_at(len);
967 if let ssh_key::public::KeyData::Ed25519 { .. } = public.key_data() {
968 let sig = &b[b.len() - 64..];
969 let sig = ssh_key::Signature::new(key.algorithm(), sig).unwrap();
970 assert!(Verifier::verify(public, a, &sig).is_ok());
971 }
972 })
973 }
974
975 #[cfg(unix)]
976 struct Incoming<'a> {
977 listener: &'a mut tokio::net::UnixListener,
978 }
979
980 #[cfg(unix)]
981 impl futures::stream::Stream for Incoming<'_> {
982 type Item = Result<tokio::net::UnixStream, std::io::Error>;
983
984 fn poll_next(
985 self: std::pin::Pin<&mut Self>,
986 cx: &mut std::task::Context<'_>,
987 ) -> std::task::Poll<Option<Self::Item>> {
988 let (sock, _addr) = futures::ready!(self.get_mut().listener.poll_accept(cx))?;
989 std::task::Poll::Ready(Some(Ok(sock)))
990 }
991 }
992
993 #[cfg(unix)]
995 async fn spawn_agent() -> Result<
996 (tokio::process::Child, std::path::PathBuf, tempfile::TempDir),
997 Box<dyn std::error::Error>,
998 > {
999 use std::process::Stdio;
1000
1001 let dir = tempfile::tempdir()?;
1002 let agent_path = dir.path().join("agent");
1003 let agent = tokio::process::Command::new("ssh-agent")
1004 .arg("-a")
1005 .arg(&agent_path)
1006 .arg("-D")
1007 .stdout(Stdio::null())
1008 .stderr(Stdio::null())
1009 .spawn()?;
1010
1011 while agent_path.canonicalize().is_err() {
1013 tokio::time::sleep(std::time::Duration::from_millis(10)).await;
1014 }
1015
1016 Ok((agent, agent_path, dir))
1017 }
1018
1019 #[cfg(unix)]
1021 fn create_test_cert(ca_key: &PrivateKey, user_key: &PrivateKey) -> ssh_key::Certificate {
1022 use ssh_key::certificate;
1023 use std::time::{SystemTime, UNIX_EPOCH};
1024
1025 let now = SystemTime::now()
1026 .duration_since(UNIX_EPOCH)
1027 .unwrap()
1028 .as_secs();
1029 let valid_after = now - 3600; let valid_before = now + 86400 * 365; let mut builder = certificate::Builder::new_with_random_nonce(
1033 &mut rand::rng(),
1034 user_key.public_key(),
1035 valid_after,
1036 valid_before,
1037 )
1038 .unwrap();
1039
1040 builder.serial(1).unwrap();
1041 builder.key_id("test-cert").unwrap();
1042 builder.cert_type(certificate::CertType::User).unwrap();
1043 builder.valid_principal("testuser").unwrap();
1044 builder.sign(ca_key).unwrap()
1045 }
1046
1047 #[tokio::test]
1048 #[cfg(unix)]
1049 async fn test_request_identities_full_with_keys_and_certs() {
1050 use crate::keys::agent::{AgentIdentity, client::AgentClient};
1051 use std::io::Write;
1052 use std::process::Stdio;
1053
1054 env_logger::try_init().unwrap_or(());
1055
1056 let (mut agent, agent_path, dir) = spawn_agent().await.unwrap();
1057
1058 let ca_key = PrivateKey::random(&mut rand::rng(), ssh_key::Algorithm::Ed25519).unwrap();
1060 let user_key = PrivateKey::random(&mut rand::rng(), ssh_key::Algorithm::Ed25519).unwrap();
1061 let plain_key = PrivateKey::random(&mut rand::rng(), ssh_key::Algorithm::Ed25519).unwrap();
1062
1063 let cert = create_test_cert(&ca_key, &user_key);
1065
1066 let user_key_path = dir.path().join("user_key");
1068 let cert_path = dir.path().join("user_key-cert.pub");
1069 let plain_key_path = dir.path().join("plain_key");
1070
1071 let mut f = std::fs::File::create(&user_key_path).unwrap();
1073 f.write_all(
1074 user_key
1075 .to_openssh(ssh_key::LineEnding::LF)
1076 .unwrap()
1077 .as_bytes(),
1078 )
1079 .unwrap();
1080 std::fs::set_permissions(
1081 &user_key_path,
1082 std::os::unix::fs::PermissionsExt::from_mode(0o600),
1083 )
1084 .unwrap();
1085
1086 let mut f = std::fs::File::create(&cert_path).unwrap();
1088 f.write_all(cert.to_openssh().unwrap().as_bytes()).unwrap();
1089
1090 let mut f = std::fs::File::create(&plain_key_path).unwrap();
1092 f.write_all(
1093 plain_key
1094 .to_openssh(ssh_key::LineEnding::LF)
1095 .unwrap()
1096 .as_bytes(),
1097 )
1098 .unwrap();
1099 std::fs::set_permissions(
1100 &plain_key_path,
1101 std::os::unix::fs::PermissionsExt::from_mode(0o600),
1102 )
1103 .unwrap();
1104
1105 let status = tokio::process::Command::new("ssh-add")
1107 .arg(&user_key_path)
1108 .env("SSH_AUTH_SOCK", &agent_path)
1109 .stdout(Stdio::null())
1110 .stderr(Stdio::null())
1111 .status()
1112 .await
1113 .unwrap();
1114 assert!(status.success(), "ssh-add for certificate failed");
1115
1116 let status = tokio::process::Command::new("ssh-add")
1118 .arg(&plain_key_path)
1119 .env("SSH_AUTH_SOCK", &agent_path)
1120 .stdout(Stdio::null())
1121 .stderr(Stdio::null())
1122 .status()
1123 .await
1124 .unwrap();
1125 assert!(status.success(), "ssh-add for plain key failed");
1126
1127 let stream = tokio::net::UnixStream::connect(&agent_path).await.unwrap();
1129 let mut client = AgentClient::connect(stream);
1130
1131 let identities = client.request_identities().await.unwrap();
1132
1133 assert!(!identities.is_empty(), "Expected at least one identity");
1136
1137 let mut key_count = 0;
1139 let mut cert_count = 0;
1140
1141 for identity in &identities {
1142 match identity {
1143 AgentIdentity::PublicKey { .. } => key_count += 1,
1144 AgentIdentity::Certificate { certificate: c, .. } => {
1145 cert_count += 1;
1146 assert_eq!(c.key_id(), "test-cert");
1148 let pk = identity.public_key();
1150 assert_eq!(pk.key_data(), c.public_key());
1151 }
1152 }
1153 let _ = identity.comment();
1155 }
1156
1157 assert!(
1159 key_count >= 1,
1160 "Expected at least 1 public key, got {}",
1161 key_count
1162 );
1163 assert!(
1164 cert_count >= 1,
1165 "Expected at least 1 certificate, got {}",
1166 cert_count
1167 );
1168
1169 agent.kill().await.unwrap();
1170 agent.wait().await.unwrap();
1171 }
1172
1173 #[tokio::test]
1174 #[cfg(unix)]
1175 async fn test_sign_request_cert() {
1176 use crate::keys::agent::client::AgentClient;
1177 use std::io::Write;
1178 use std::process::Stdio;
1179
1180 env_logger::try_init().unwrap_or(());
1181
1182 let (mut agent, agent_path, dir) = spawn_agent().await.unwrap();
1183
1184 let ca_key = PrivateKey::random(&mut rand::rng(), ssh_key::Algorithm::Ed25519).unwrap();
1186 let user_key = PrivateKey::random(&mut rand::rng(), ssh_key::Algorithm::Ed25519).unwrap();
1187
1188 let cert = create_test_cert(&ca_key, &user_key);
1190
1191 let user_key_path = dir.path().join("user_key");
1193 let cert_path = dir.path().join("user_key-cert.pub");
1194
1195 let mut f = std::fs::File::create(&user_key_path).unwrap();
1196 f.write_all(
1197 user_key
1198 .to_openssh(ssh_key::LineEnding::LF)
1199 .unwrap()
1200 .as_bytes(),
1201 )
1202 .unwrap();
1203 std::fs::set_permissions(
1204 &user_key_path,
1205 std::os::unix::fs::PermissionsExt::from_mode(0o600),
1206 )
1207 .unwrap();
1208
1209 let mut f = std::fs::File::create(&cert_path).unwrap();
1210 f.write_all(cert.to_openssh().unwrap().as_bytes()).unwrap();
1211
1212 let status = tokio::process::Command::new("ssh-add")
1214 .arg(&user_key_path)
1215 .env("SSH_AUTH_SOCK", &agent_path)
1216 .stdout(Stdio::null())
1217 .stderr(Stdio::null())
1218 .status()
1219 .await
1220 .unwrap();
1221 assert!(status.success(), "ssh-add failed");
1222
1223 let stream = tokio::net::UnixStream::connect(&agent_path).await.unwrap();
1225 let mut client = AgentClient::connect(stream);
1226
1227 let data_to_sign = b"test data to sign";
1229 let buf = data_to_sign.to_vec();
1230 let len = buf.len();
1231
1232 let signed_buf = client.sign_request(&cert.into(), None, buf).await.unwrap();
1234
1235 assert!(signed_buf.len() > len, "Signed buffer should be larger");
1237
1238 let (original, sig_data) = signed_buf.split_at(len);
1240 assert_eq!(original, data_to_sign);
1241
1242 assert!(sig_data.len() > 64, "Signature data should include type");
1245
1246 agent.kill().await.unwrap();
1247 agent.wait().await.unwrap();
1248 }
1249
1250 #[tokio::test]
1251 #[cfg(unix)]
1252 async fn test_sign_request_cert_missing_key_returns_agent_failure() {
1253 use crate::keys::agent::client::AgentClient;
1254
1255 env_logger::try_init().unwrap_or(());
1256
1257 let (mut agent, agent_path, _dir) = spawn_agent().await.unwrap();
1258
1259 let ca_key = PrivateKey::random(&mut rand::rng(), ssh_key::Algorithm::Ed25519).unwrap();
1261 let user_key = PrivateKey::random(&mut rand::rng(), ssh_key::Algorithm::Ed25519).unwrap();
1262
1263 let cert = create_test_cert(&ca_key, &user_key);
1265
1266 let stream = tokio::net::UnixStream::connect(&agent_path).await.unwrap();
1268 let mut client = AgentClient::connect(stream);
1269
1270 let identities = client.request_identities().await.unwrap();
1272 assert!(identities.is_empty(), "Agent should have no keys");
1273
1274 let data_to_sign = b"test data to sign";
1276 let buf = data_to_sign.to_vec();
1277
1278 let result = client.sign_request(&cert.into(), None, buf).await;
1280
1281 assert!(
1283 result.is_err(),
1284 "Signing should fail when key is not in agent"
1285 );
1286 match result {
1287 Err(Error::AgentFailure) => {
1288 }
1290 Err(e) => {
1291 panic!("Expected AgentFailure error, got: {:?}", e);
1292 }
1293 Ok(_) => {
1294 panic!("Expected error, but signing succeeded");
1295 }
1296 }
1297
1298 agent.kill().await.unwrap();
1299 agent.wait().await.unwrap();
1300 }
1301
1302 #[tokio::test]
1303 #[cfg(unix)]
1304 async fn test_sign_request_missing_key_returns_agent_failure() {
1305 use crate::keys::agent::client::AgentClient;
1306
1307 env_logger::try_init().unwrap_or(());
1308
1309 let (mut agent, agent_path, _dir) = spawn_agent().await.unwrap();
1310
1311 let key = PrivateKey::random(&mut rand::rng(), ssh_key::Algorithm::Ed25519).unwrap();
1313
1314 let stream = tokio::net::UnixStream::connect(&agent_path).await.unwrap();
1316 let mut client = AgentClient::connect(stream);
1317
1318 let identities = client.request_identities().await.unwrap();
1320 assert!(identities.is_empty(), "Agent should have no keys");
1321
1322 let data_to_sign = b"test data to sign";
1324 let buf = data_to_sign.to_vec();
1325
1326 let result = client
1328 .sign_request(&key.public_key().clone().into(), None, buf)
1329 .await;
1330
1331 assert!(
1333 result.is_err(),
1334 "Signing should fail when key is not in agent"
1335 );
1336 match result {
1337 Err(Error::AgentFailure) => {
1338 }
1340 Err(e) => {
1341 panic!("Expected AgentFailure error, got: {:?}", e);
1342 }
1343 Ok(_) => {
1344 panic!("Expected error, but signing succeeded");
1345 }
1346 }
1347
1348 agent.kill().await.unwrap();
1349 agent.wait().await.unwrap();
1350 }
1351
1352 #[cfg(all(unix, feature = "rsa"))]
1354 fn create_test_rsa_cert(ca_key: &PrivateKey, user_key: &PrivateKey) -> ssh_key::Certificate {
1355 use ssh_key::certificate;
1356 use std::time::{SystemTime, UNIX_EPOCH};
1357
1358 let now = SystemTime::now()
1359 .duration_since(UNIX_EPOCH)
1360 .unwrap()
1361 .as_secs();
1362 let valid_after = now - 3600; let valid_before = now + 86400 * 365; let mut builder = certificate::Builder::new_with_random_nonce(
1366 &mut rand::rng(),
1367 user_key.public_key(),
1368 valid_after,
1369 valid_before,
1370 )
1371 .unwrap();
1372
1373 builder.serial(1).unwrap();
1374 builder.key_id("test-rsa-cert").unwrap();
1375 builder.cert_type(certificate::CertType::User).unwrap();
1376 builder.valid_principal("testuser").unwrap();
1377 builder.sign(ca_key).unwrap()
1378 }
1379
1380 #[tokio::test]
1381 #[cfg(all(unix, feature = "rsa"))]
1382 async fn test_sign_request_cert_rsa() {
1383 use crate::keys::agent::client::AgentClient;
1384 use std::io::Write;
1385 use std::process::Stdio;
1386
1387 env_logger::try_init().unwrap_or(());
1388
1389 let (mut agent, agent_path, dir) = spawn_agent().await.unwrap();
1390
1391 let ca_key = PrivateKey::random(
1393 &mut rand::rng(),
1394 ssh_key::Algorithm::Rsa {
1395 hash: Some(HashAlg::Sha256),
1396 },
1397 )
1398 .unwrap();
1399 let user_key = PrivateKey::random(
1400 &mut rand::rng(),
1401 ssh_key::Algorithm::Rsa {
1402 hash: Some(HashAlg::Sha256),
1403 },
1404 )
1405 .unwrap();
1406
1407 let cert = create_test_rsa_cert(&ca_key, &user_key);
1409
1410 let user_key_path = dir.path().join("user_rsa_key");
1412 let cert_path = dir.path().join("user_rsa_key-cert.pub");
1413
1414 let mut f = std::fs::File::create(&user_key_path).unwrap();
1415 f.write_all(
1416 user_key
1417 .to_openssh(ssh_key::LineEnding::LF)
1418 .unwrap()
1419 .as_bytes(),
1420 )
1421 .unwrap();
1422 std::fs::set_permissions(
1423 &user_key_path,
1424 std::os::unix::fs::PermissionsExt::from_mode(0o600),
1425 )
1426 .unwrap();
1427
1428 let mut f = std::fs::File::create(&cert_path).unwrap();
1429 f.write_all(cert.to_openssh().unwrap().as_bytes()).unwrap();
1430
1431 let status = tokio::process::Command::new("ssh-add")
1433 .arg(&user_key_path)
1434 .env("SSH_AUTH_SOCK", &agent_path)
1435 .stdout(Stdio::null())
1436 .stderr(Stdio::null())
1437 .status()
1438 .await
1439 .unwrap();
1440 assert!(status.success(), "ssh-add failed");
1441
1442 let stream = tokio::net::UnixStream::connect(&agent_path).await.unwrap();
1444 let mut client = AgentClient::connect(stream);
1445
1446 let data_to_sign = b"test data to sign with RSA cert";
1448 let buf = data_to_sign.to_vec();
1449 let len = buf.len();
1450
1451 let signed_buf = client
1453 .sign_request(&cert.into(), Some(HashAlg::Sha256), buf)
1454 .await
1455 .unwrap();
1456
1457 assert!(signed_buf.len() > len, "Signed buffer should be larger");
1459
1460 let (original, sig_data) = signed_buf.split_at(len);
1462 assert_eq!(original, data_to_sign);
1463
1464 assert!(
1466 sig_data.len() > 100,
1467 "RSA signature data should be substantial"
1468 );
1469
1470 agent.kill().await.unwrap();
1471 agent.wait().await.unwrap();
1472 }
1473
1474 #[tokio::test]
1475 #[cfg(all(unix, feature = "rsa"))]
1476 async fn test_sign_request_cert_rsa_sha512() {
1477 use crate::keys::agent::client::AgentClient;
1478 use std::io::Write;
1479 use std::process::Stdio;
1480
1481 env_logger::try_init().unwrap_or(());
1482
1483 let (mut agent, agent_path, dir) = spawn_agent().await.unwrap();
1484
1485 let ca_key = PrivateKey::random(
1487 &mut rand::rng(),
1488 ssh_key::Algorithm::Rsa {
1489 hash: Some(HashAlg::Sha512),
1490 },
1491 )
1492 .unwrap();
1493 let user_key = PrivateKey::random(
1494 &mut rand::rng(),
1495 ssh_key::Algorithm::Rsa {
1496 hash: Some(HashAlg::Sha512),
1497 },
1498 )
1499 .unwrap();
1500
1501 let cert = create_test_rsa_cert(&ca_key, &user_key);
1503
1504 let user_key_path = dir.path().join("user_rsa_key");
1506 let cert_path = dir.path().join("user_rsa_key-cert.pub");
1507
1508 let mut f = std::fs::File::create(&user_key_path).unwrap();
1509 f.write_all(
1510 user_key
1511 .to_openssh(ssh_key::LineEnding::LF)
1512 .unwrap()
1513 .as_bytes(),
1514 )
1515 .unwrap();
1516 std::fs::set_permissions(
1517 &user_key_path,
1518 std::os::unix::fs::PermissionsExt::from_mode(0o600),
1519 )
1520 .unwrap();
1521
1522 let mut f = std::fs::File::create(&cert_path).unwrap();
1523 f.write_all(cert.to_openssh().unwrap().as_bytes()).unwrap();
1524
1525 let status = tokio::process::Command::new("ssh-add")
1527 .arg(&user_key_path)
1528 .env("SSH_AUTH_SOCK", &agent_path)
1529 .stdout(Stdio::null())
1530 .stderr(Stdio::null())
1531 .status()
1532 .await
1533 .unwrap();
1534 assert!(status.success(), "ssh-add failed");
1535
1536 let stream = tokio::net::UnixStream::connect(&agent_path).await.unwrap();
1538 let mut client = AgentClient::connect(stream);
1539
1540 let data_to_sign = b"test data to sign with RSA cert SHA-512";
1542 let buf = data_to_sign.to_vec();
1543 let len = buf.len();
1544
1545 let signed_buf = client
1547 .sign_request(&cert.into(), Some(HashAlg::Sha512), buf)
1548 .await
1549 .unwrap();
1550
1551 assert!(signed_buf.len() > len, "Signed buffer should be larger");
1553
1554 let (original, sig_data) = signed_buf.split_at(len);
1556 assert_eq!(original, data_to_sign);
1557
1558 assert!(
1560 sig_data.len() > 100,
1561 "RSA signature data should be substantial"
1562 );
1563
1564 agent.kill().await.unwrap();
1565 agent.wait().await.unwrap();
1566 }
1567}