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