russh_keys/
lib.rs

1#![deny(trivial_casts, unstable_features, unused_import_braces)]
2#![deny(
3    clippy::unwrap_used,
4    clippy::expect_used,
5    clippy::indexing_slicing,
6    clippy::panic
7)]
8//! This crate contains methods to deal with SSH keys, as defined in
9//! crate Russh. This includes in particular various functions for
10//! opening key files, deciphering encrypted keys, and dealing with
11//! agents.
12//!
13//! The following example shows how to do all these in a single example:
14//! start and SSH agent server, connect to it with a client, decipher
15//! an encrypted private key (the password is `b"blabla"`), send it to
16//! the agent, and ask the agent to sign a piece of data
17//! (`b"Please sign this"`, below).
18//!
19//!```
20//! use russh_keys::*;
21//! use futures::Future;
22//!
23//! #[derive(Clone)]
24//! struct X{}
25//! impl agent::server::Agent for X {
26//!     fn confirm(self, _: std::sync::Arc<PrivateKey>) -> Box<dyn Future<Output = (Self, bool)> + Send + Unpin> {
27//!         Box::new(futures::future::ready((self, true)))
28//!     }
29//! }
30//!
31//! const PKCS8_ENCRYPTED: &'static str = "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQITo1O0b8YrS0CAggA\nMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBtLH4T1KOfo1GGr7salhR8BIIE\n0KN9ednYwcTGSX3hg7fROhTw7JAJ1D4IdT1fsoGeNu2BFuIgF3cthGHe6S5zceI2\nMpkfwvHbsOlDFWMUIAb/VY8/iYxhNmd5J6NStMYRC9NC0fVzOmrJqE1wITqxtORx\nIkzqkgFUbaaiFFQPepsh5CvQfAgGEWV329SsTOKIgyTj97RxfZIKA+TR5J5g2dJY\nj346SvHhSxJ4Jc0asccgMb0HGh9UUDzDSql0OIdbnZW5KzYJPOx+aDqnpbz7UzY/\nP8N0w/pEiGmkdkNyvGsdttcjFpOWlLnLDhtLx8dDwi/sbEYHtpMzsYC9jPn3hnds\nTcotqjoSZ31O6rJD4z18FOQb4iZs3MohwEdDd9XKblTfYKM62aQJWH6cVQcg+1C7\njX9l2wmyK26Tkkl5Qg/qSfzrCveke5muZgZkFwL0GCcgPJ8RixSB4GOdSMa/hAMU\nkvFAtoV2GluIgmSe1pG5cNMhurxM1dPPf4WnD+9hkFFSsMkTAuxDZIdDk3FA8zof\nYhv0ZTfvT6V+vgH3Hv7Tqcxomy5Qr3tj5vvAqqDU6k7fC4FvkxDh2mG5ovWvc4Nb\nXv8sed0LGpYitIOMldu6650LoZAqJVv5N4cAA2Edqldf7S2Iz1QnA/usXkQd4tLa\nZ80+sDNv9eCVkfaJ6kOVLk/ghLdXWJYRLenfQZtVUXrPkaPpNXgD0dlaTN8KuvML\nUw/UGa+4ybnPsdVflI0YkJKbxouhp4iB4S5ACAwqHVmsH5GRnujf10qLoS7RjDAl\no/wSHxdT9BECp7TT8ID65u2mlJvH13iJbktPczGXt07nBiBse6OxsClfBtHkRLzE\nQF6UMEXsJnIIMRfrZQnduC8FUOkfPOSXc8r9SeZ3GhfbV/DmWZvFPCpjzKYPsM5+\nN8Bw/iZ7NIH4xzNOgwdp5BzjH9hRtCt4sUKVVlWfEDtTnkHNOusQGKu7HkBF87YZ\nRN/Nd3gvHob668JOcGchcOzcsqsgzhGMD8+G9T9oZkFCYtwUXQU2XjMN0R4VtQgZ\nrAxWyQau9xXMGyDC67gQ5xSn+oqMK0HmoW8jh2LG/cUowHFAkUxdzGadnjGhMOI2\nzwNJPIjF93eDF/+zW5E1l0iGdiYyHkJbWSvcCuvTwma9FIDB45vOh5mSR+YjjSM5\nnq3THSWNi7Cxqz12Q1+i9pz92T2myYKBBtu1WDh+2KOn5DUkfEadY5SsIu/Rb7ub\n5FBihk2RN3y/iZk+36I69HgGg1OElYjps3D+A9AjVby10zxxLAz8U28YqJZm4wA/\nT0HLxBiVw+rsHmLP79KvsT2+b4Diqih+VTXouPWC/W+lELYKSlqnJCat77IxgM9e\nYIhzD47OgWl33GJ/R10+RDoDvY4koYE+V5NLglEhbwjloo9Ryv5ywBJNS7mfXMsK\n/uf+l2AscZTZ1mhtL38efTQCIRjyFHc3V31DI0UdETADi+/Omz+bXu0D5VvX+7c6\nb1iVZKpJw8KUjzeUV8yOZhvGu3LrQbhkTPVYL555iP1KN0Eya88ra+FUKMwLgjYr\nJkUx4iad4dTsGPodwEP/Y9oX/Qk3ZQr+REZ8lg6IBoKKqqrQeBJ9gkm1jfKE6Xkc\nCog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/MtlOAr5rux\n-----END ENCRYPTED PRIVATE KEY-----\n";
32//!
33//! #[cfg(unix)]
34//! fn main() {
35//!    env_logger::try_init().unwrap_or(());
36//!    let dir = tempdir::TempDir::new("russh").unwrap();
37//!    let agent_path = dir.path().join("agent");
38//!
39//!    let mut core = tokio::runtime::Runtime::new().unwrap();
40//!    let agent_path_ = agent_path.clone();
41//!    // Starting a server
42//!    core.spawn(async move {
43//!        let mut listener = tokio::net::UnixListener::bind(&agent_path_)
44//!            .unwrap();
45//!        russh_keys::agent::server::serve(tokio_stream::wrappers::UnixListenerStream::new(listener), X {}).await
46//!    });
47//!    let key = decode_secret_key(PKCS8_ENCRYPTED, Some("blabla")).unwrap();
48//!    let public = key.public_key().clone();
49//!    core.block_on(async move {
50//!        let stream = tokio::net::UnixStream::connect(&agent_path).await?;
51//!        let mut client = agent::client::AgentClient::connect(stream);
52//!        client.add_identity(&key, &[agent::Constraint::KeyLifetime { seconds: 60 }]).await?;
53//!        client.request_identities().await?;
54//!        let buf = b"signed message";
55//!        let sig = client.sign_request(&public, russh_cryptovec::CryptoVec::from_slice(&buf[..])).await.unwrap();
56//!        // Here, `sig` is encoded in a format usable internally by the SSH protocol.
57//!        Ok::<(), Error>(())
58//!    }).unwrap()
59//! }
60//!
61//! #[cfg(not(unix))]
62//! fn main() {}
63//!
64//! ```
65
66use std::fs::File;
67use std::io::Read;
68use std::path::Path;
69use std::string::FromUtf8Error;
70
71use aes::cipher::block_padding::UnpadError;
72use aes::cipher::inout::PadError;
73use data_encoding::BASE64_MIME;
74use helpers::EncodedExt;
75use thiserror::Error;
76
77pub mod key;
78
79mod format;
80#[doc(hidden)]
81pub mod helpers;
82pub use format::*;
83// Reexports
84pub use signature;
85pub use ssh_encoding;
86pub use ssh_key::{self, Algorithm, Certificate, EcdsaCurve, HashAlg, PrivateKey, PublicKey};
87
88/// OpenSSH agent protocol implementation
89pub mod agent;
90
91#[cfg(not(target_arch = "wasm32"))]
92pub mod known_hosts;
93
94#[cfg(not(target_arch = "wasm32"))]
95pub use known_hosts::{check_known_hosts, check_known_hosts_path};
96
97#[derive(Debug, Error)]
98pub enum Error {
99    /// The key could not be read, for an unknown reason
100    #[error("Could not read key")]
101    CouldNotReadKey,
102    /// The type of the key is unsupported
103    #[error("Unsupported key type {}", key_type_string)]
104    UnsupportedKeyType {
105        key_type_string: String,
106        key_type_raw: Vec<u8>,
107    },
108    /// The type of the key is unsupported
109    #[error("Invalid Ed25519 key data")]
110    Ed25519KeyError(#[from] ed25519_dalek::SignatureError),
111    /// The type of the key is unsupported
112    #[error("Invalid ECDSA key data")]
113    EcdsaKeyError(#[from] p256::elliptic_curve::Error),
114    /// The key is encrypted (should supply a password?)
115    #[error("The key is encrypted")]
116    KeyIsEncrypted,
117    /// The key contents are inconsistent
118    #[error("The key is corrupt")]
119    KeyIsCorrupt,
120    /// Home directory could not be found
121    #[error("No home directory found")]
122    NoHomeDir,
123    /// The server key has changed
124    #[error("The server key changed at line {}", line)]
125    KeyChanged { line: usize },
126    /// The key uses an unsupported algorithm
127    #[error("Unknown key algorithm: {0}")]
128    UnknownAlgorithm(::pkcs8::ObjectIdentifier),
129    /// Index out of bounds
130    #[error("Index out of bounds")]
131    IndexOutOfBounds,
132    /// Unknown signature type
133    #[error("Unknown signature type: {}", sig_type)]
134    UnknownSignatureType { sig_type: String },
135    #[error("Invalid signature")]
136    InvalidSignature,
137    #[error("Invalid parameters")]
138    InvalidParameters,
139    /// Agent protocol error
140    #[error("Agent protocol error")]
141    AgentProtocolError,
142    #[error("Agent failure")]
143    AgentFailure,
144    #[error(transparent)]
145    IO(#[from] std::io::Error),
146
147    #[error("Rsa: {0}")]
148    Rsa(#[from] rsa::Error),
149
150    #[error(transparent)]
151    Pad(#[from] PadError),
152
153    #[error(transparent)]
154    Unpad(#[from] UnpadError),
155
156    #[error("Base64 decoding error: {0}")]
157    Decode(#[from] data_encoding::DecodeError),
158    #[error("Der: {0}")]
159    Der(#[from] der::Error),
160    #[error("Spki: {0}")]
161    Spki(#[from] spki::Error),
162    #[error("Pkcs1: {0}")]
163    Pkcs1(#[from] pkcs1::Error),
164    #[error("Pkcs8: {0}")]
165    Pkcs8(#[from] ::pkcs8::Error),
166    #[error("Sec1: {0}")]
167    Sec1(#[from] sec1::Error),
168
169    #[error("SshKey: {0}")]
170    SshKey(#[from] ssh_key::Error),
171    #[error("SshEncoding: {0}")]
172    SshEncoding(#[from] ssh_encoding::Error),
173
174    #[error("Environment variable `{0}` not found")]
175    EnvVar(&'static str),
176    #[error(
177        "Unable to connect to ssh-agent. The environment variable `SSH_AUTH_SOCK` was set, but it \
178         points to a nonexistent file or directory."
179    )]
180    BadAuthSock,
181
182    #[error(transparent)]
183    Utf8(#[from] FromUtf8Error),
184
185    #[error("ASN1 decoding error: {0}")]
186    #[cfg(feature = "legacy-ed25519-pkcs8-parser")]
187    LegacyASN1(::yasna::ASN1Error),
188
189    #[cfg(windows)]
190    #[error("Pageant: {0}")]
191    Pageant(#[from] pageant::Error),
192}
193
194#[cfg(feature = "legacy-ed25519-pkcs8-parser")]
195impl From<yasna::ASN1Error> for Error {
196    fn from(e: yasna::ASN1Error) -> Error {
197        Error::LegacyASN1(e)
198    }
199}
200
201/// Load a public key from a file. Ed25519, EC-DSA and RSA keys are supported.
202///
203/// ```
204/// russh_keys::load_public_key("../files/id_ed25519.pub").unwrap();
205/// ```
206pub fn load_public_key<P: AsRef<Path>>(path: P) -> Result<ssh_key::PublicKey, Error> {
207    let mut pubkey = String::new();
208    let mut file = File::open(path.as_ref())?;
209    file.read_to_string(&mut pubkey)?;
210
211    let mut split = pubkey.split_whitespace();
212    match (split.next(), split.next()) {
213        (Some(_), Some(key)) => parse_public_key_base64(key),
214        (Some(key), None) => parse_public_key_base64(key),
215        _ => Err(Error::CouldNotReadKey),
216    }
217}
218
219/// Reads a public key from the standard encoding. In some cases, the
220/// encoding is prefixed with a key type identifier and a space (such
221/// as `ssh-ed25519 AAAAC3N...`).
222///
223/// ```
224/// russh_keys::parse_public_key_base64("AAAAC3NzaC1lZDI1NTE5AAAAIJdD7y3aLq454yWBdwLWbieU1ebz9/cu7/QEXn9OIeZJ").is_ok();
225/// ```
226pub fn parse_public_key_base64(key: &str) -> Result<ssh_key::PublicKey, Error> {
227    let base = BASE64_MIME.decode(key.as_bytes())?;
228    key::parse_public_key(&base)
229}
230
231pub trait PublicKeyBase64 {
232    /// Create the base64 part of the public key blob.
233    fn public_key_bytes(&self) -> Vec<u8>;
234    fn public_key_base64(&self) -> String {
235        let mut s = BASE64_MIME.encode(&self.public_key_bytes());
236        assert_eq!(s.pop(), Some('\n'));
237        assert_eq!(s.pop(), Some('\r'));
238        s.replace("\r\n", "")
239    }
240}
241
242impl PublicKeyBase64 for ssh_key::PublicKey {
243    fn public_key_bytes(&self) -> Vec<u8> {
244        self.key_data().encoded().unwrap_or_default()
245    }
246}
247
248impl PublicKeyBase64 for PrivateKey {
249    fn public_key_bytes(&self) -> Vec<u8> {
250        self.public_key().public_key_bytes()
251    }
252}
253
254/// Load a secret key, deciphering it with the supplied password if necessary.
255pub fn load_secret_key<P: AsRef<Path>>(
256    secret_: P,
257    password: Option<&str>,
258) -> Result<PrivateKey, Error> {
259    let mut secret_file = std::fs::File::open(secret_)?;
260    let mut secret = String::new();
261    secret_file.read_to_string(&mut secret)?;
262    decode_secret_key(&secret, password)
263}
264
265/// Load a openssh certificate
266pub fn load_openssh_certificate<P: AsRef<Path>>(cert_: P) -> Result<Certificate, ssh_key::Error> {
267    let mut cert_file = std::fs::File::open(cert_)?;
268    let mut cert = String::new();
269    cert_file.read_to_string(&mut cert)?;
270
271    Certificate::from_openssh(&cert)
272}
273
274fn is_base64_char(c: char) -> bool {
275    c.is_ascii_lowercase()
276        || c.is_ascii_uppercase()
277        || c.is_ascii_digit()
278        || c == '/'
279        || c == '+'
280        || c == '='
281}
282
283#[cfg(test)]
284mod test {
285
286    #[cfg(unix)]
287    use futures::Future;
288
289    use super::*;
290    use crate::key::PublicKeyExt;
291
292    const ED25519_KEY: &str = "-----BEGIN OPENSSH PRIVATE KEY-----
293b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABDLGyfA39
294J2FcJygtYqi5ISAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIN+Wjn4+4Fcvl2Jl
295KpggT+wCRxpSvtqqpVrQrKN1/A22AAAAkOHDLnYZvYS6H9Q3S3Nk4ri3R2jAZlQlBbUos5
296FkHpYgNw65KCWCTXtP7ye2czMC3zjn2r98pJLobsLYQgRiHIv/CUdAdsqbvMPECB+wl/UQ
297e+JpiSq66Z6GIt0801skPh20jxOO3F52SoX1IeO5D5PXfZrfSZlw6S8c7bwyp2FHxDewRx
2987/wNsnDM0T7nLv/Q==
299-----END OPENSSH PRIVATE KEY-----";
300
301    // password is 'test'
302    const ED25519_AESCTR_KEY: &str = "-----BEGIN OPENSSH PRIVATE KEY-----
303b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABD1phlku5
304A2G7Q9iP+DcOc9AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIHeLC1lWiCYrXsf/
30585O/pkbUFZ6OGIt49PX3nw8iRoXEAAAAkKRF0st5ZI7xxo9g6A4m4l6NarkQre3mycqNXQ
306dP3jryYgvsCIBAA5jMWSjrmnOTXhidqcOy4xYCrAttzSnZ/cUadfBenL+DQq6neffw7j8r
3070tbCxVGp6yCQlKrgSZf6c0Hy7dNEIU2bJFGxLe6/kWChcUAt/5Ll5rI7DVQPJdLgehLzvv
308sJWR7W+cGvJ/vLsw==
309-----END OPENSSH PRIVATE KEY-----";
310
311    const RSA_KEY: &str = "-----BEGIN OPENSSH PRIVATE KEY-----
312b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
313NhAAAAAwEAAQAAAQEAuSvQ9m76zhRB4m0BUKPf17lwccj7KQ1Qtse63AOqP/VYItqEH8un
314rxPogXNBgrcCEm/ccLZZsyE3qgp3DRQkkqvJhZ6O8VBPsXxjZesRCqoFNCczy+Mf0R/Qmv
315Rnpu5+4DDLz0p7vrsRZW9ji/c98KzxeUonWgkplQaCBYLN875WdeUYMGtb1MLfNCEj177j
316gZl3CzttLRK3su6dckowXcXYv1gPTPZAwJb49J43o1QhV7+1zdwXvuFM6zuYHdu9ZHSKir
3176k1dXOET3/U+LWG5ofAo8oxUWv/7vs6h7MeajwkUeIBOWYtD+wGYRvVpxvj7nyOoWtg+jm
3180X6ndnsD+QAAA8irV+ZAq1fmQAAAAAdzc2gtcnNhAAABAQC5K9D2bvrOFEHibQFQo9/XuX
319BxyPspDVC2x7rcA6o/9Vgi2oQfy6evE+iBc0GCtwISb9xwtlmzITeqCncNFCSSq8mFno7x
320UE+xfGNl6xEKqgU0JzPL4x/RH9Ca9Gem7n7gMMvPSnu+uxFlb2OL9z3wrPF5SidaCSmVBo
321IFgs3zvlZ15Rgwa1vUwt80ISPXvuOBmXcLO20tErey7p1ySjBdxdi/WA9M9kDAlvj0njej
322VCFXv7XN3Be+4UzrO5gd271kdIqKvqTV1c4RPf9T4tYbmh8CjyjFRa//u+zqHsx5qPCRR4
323gE5Zi0P7AZhG9WnG+PufI6ha2D6ObRfqd2ewP5AAAAAwEAAQAAAQAdELqhI/RsSpO45eFR
3249hcZtnrm8WQzImrr9dfn1w9vMKSf++rHTuFIQvi48Q10ZiOGH1bbvlPAIVOqdjAPtnyzJR
325HhzmyjhjasJlk30zj+kod0kz63HzSMT9EfsYNfmYoCyMYFCKz52EU3xc87Vhi74XmZz0D0
326CgIj6TyZftmzC4YJCiwwU8K+29nxBhcbFRxpgwAksFL6PCSQsPl4y7yvXGcX+7lpZD8547
327v58q3jIkH1g2tBOusIuaiphDDStVJhVdKA55Z0Kju2kvCqsRIlf1efrq43blRgJFFFCxNZ
3288Cpolt4lOHhg+o3ucjILlCOgjDV8dB21YLxmgN5q+xFNAAAAgQC1P+eLUkHDFXnleCEVrW
329xL/DFxEyneLQz3IawGdw7cyAb7vxsYrGUvbVUFkxeiv397pDHLZ5U+t5cOYDBZ7G43Mt2g
330YfWBuRNvYhHA9Sdf38m5qPA6XCvm51f+FxInwd/kwRKH01RHJuRGsl/4Apu4DqVob8y00V
331WTYyV6JBNDkQAAAIEA322lj7ZJXfK/oLhMM/RS+DvaMea1g/q43mdRJFQQso4XRCL6IIVn
332oZXFeOxrMIRByVZBw+FSeB6OayWcZMySpJQBo70GdJOc3pJb3js0T+P2XA9+/jwXS58K9a
333+IkgLkv9XkfxNGNKyPEEzXC8QQzvjs1LbmO59VLko8ypwHq/cAAACBANQqaULI0qdwa0vm
334d3Ae1+k3YLZ0kapSQGVIMT2lkrhKV35tj7HIFpUPa4vitHzcUwtjYhqFezVF+JyPbJ/Fsp
335XmEc0g1fFnQp5/SkUwoN2zm8Up52GBelkq2Jk57mOMzWO0QzzNuNV/feJk02b2aE8rrAqP
336QR+u0AypRPmzHnOPAAAAEXJvb3RAMTQwOTExNTQ5NDBkAQ==
337-----END OPENSSH PRIVATE KEY-----";
338
339    #[test]
340    fn test_decode_ed25519_secret_key() {
341        env_logger::try_init().unwrap_or(());
342        decode_secret_key(ED25519_KEY, Some("blabla")).unwrap();
343    }
344
345    #[test]
346    fn test_decode_ed25519_aesctr_secret_key() {
347        env_logger::try_init().unwrap_or(());
348        decode_secret_key(ED25519_AESCTR_KEY, Some("test")).unwrap();
349    }
350
351    // Key from RFC 8410 Section 10.3. This is a key using PrivateKeyInfo structure.
352    const RFC8410_ED25519_PRIVATE_ONLY_KEY: &str = "-----BEGIN PRIVATE KEY-----
353MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
354-----END PRIVATE KEY-----";
355
356    #[test]
357    fn test_decode_rfc8410_ed25519_private_only_key() {
358        env_logger::try_init().unwrap_or(());
359        assert!(
360            decode_secret_key(RFC8410_ED25519_PRIVATE_ONLY_KEY, None)
361                .unwrap()
362                .algorithm()
363                == ssh_key::Algorithm::Ed25519,
364        );
365        // We always encode public key, skip test_decode_encode_symmetry.
366    }
367
368    // Key from RFC 8410 Section 10.3. This is a key using OneAsymmetricKey structure.
369    const RFC8410_ED25519_PRIVATE_PUBLIC_KEY: &str = "-----BEGIN PRIVATE KEY-----
370MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
371oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB
372Z9w7lshQhqowtrbLDFw4rXAxZuE=
373-----END PRIVATE KEY-----";
374
375    #[test]
376    fn test_decode_rfc8410_ed25519_private_public_key() {
377        env_logger::try_init().unwrap_or(());
378        assert!(
379            decode_secret_key(RFC8410_ED25519_PRIVATE_PUBLIC_KEY, None)
380                .unwrap()
381                .algorithm()
382                == ssh_key::Algorithm::Ed25519,
383        );
384        // We can't encode attributes, skip test_decode_encode_symmetry.
385    }
386
387    #[test]
388    fn test_decode_rsa_secret_key() {
389        env_logger::try_init().unwrap_or(());
390        decode_secret_key(RSA_KEY, None).unwrap();
391    }
392
393    #[test]
394    fn test_decode_openssh_p256_secret_key() {
395        // Generated using: ssh-keygen -t ecdsa -b 256 -m rfc4716 -f $file
396        let key = "-----BEGIN OPENSSH PRIVATE KEY-----
397b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
3981zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQ/i+HCsmZZPy0JhtT64vW7EmeA1DeA
399M/VnPq3vAhu+xooJ7IMMK3lUHlBDosyvA2enNbCWyvNQc25dVt4oh9RhAAAAqHG7WMFxu1
400jBAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBD+L4cKyZlk/LQmG
4011Pri9bsSZ4DUN4Az9Wc+re8CG77GignsgwwreVQeUEOizK8DZ6c1sJbK81Bzbl1W3iiH1G
402EAAAAgLAmXR6IlN0SdiD6o8qr+vUr0mXLbajs/m0UlegElOmoAAAANcm9iZXJ0QGJic2Rl
403dgECAw==
404-----END OPENSSH PRIVATE KEY-----
405";
406        assert!(
407            decode_secret_key(key, None).unwrap().algorithm()
408                == ssh_key::Algorithm::Ecdsa {
409                    curve: ssh_key::EcdsaCurve::NistP256
410                },
411        );
412    }
413
414    #[test]
415    fn test_decode_openssh_p384_secret_key() {
416        // Generated using: ssh-keygen -t ecdsa -b 384 -m rfc4716 -f $file
417        let key = "-----BEGIN OPENSSH PRIVATE KEY-----
418b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS
4191zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQTkLnKPk/1NZD9mQ8XoebD7ASv9/svh
4205jO75HF7RYAqKK3fl5wsHe4VTJAOT3qH841yTcK79l0dwhHhHeg60byL7F9xOEzr2kqGeY
421Uwrl7fVaL7hfHzt6z+sG8smSQ3tF8AAADYHjjBch44wXIAAAATZWNkc2Etc2hhMi1uaXN0
422cDM4NAAAAAhuaXN0cDM4NAAAAGEE5C5yj5P9TWQ/ZkPF6Hmw+wEr/f7L4eYzu+Rxe0WAKi
423it35ecLB3uFUyQDk96h/ONck3Cu/ZdHcIR4R3oOtG8i+xfcThM69pKhnmFMK5e31Wi+4Xx
42487es/rBvLJkkN7RfAAAAMFzt6053dxaQT0Ta/CGfZna0nibHzxa55zgBmje/Ho3QDNlBCH
425Ylv0h4Wyzto8NfLQAAAA1yb2JlcnRAYmJzZGV2AQID
426-----END OPENSSH PRIVATE KEY-----
427";
428        assert!(
429            decode_secret_key(key, None).unwrap().algorithm()
430                == ssh_key::Algorithm::Ecdsa {
431                    curve: ssh_key::EcdsaCurve::NistP384
432                },
433        );
434    }
435
436    #[test]
437    fn test_decode_openssh_p521_secret_key() {
438        // Generated using: ssh-keygen -t ecdsa -b 521 -m rfc4716 -f $file
439        let key = "-----BEGIN OPENSSH PRIVATE KEY-----
440b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS
4411zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQA7a9awmFeDjzYiuUOwMfXkKTevfQI
442iGlduu8BkjBOWXpffJpKsdTyJI/xI05l34OvqfCCkPUcfFWHK+LVRGahMBgBcGB9ZZOEEq
443iKNIT6C9WcJTGDqcBSzQ2yTSOxPXfUmVTr4D76vbYu5bjd9aBKx8HdfMvPeo0WD0ds/LjX
444LdJoDXcAAAEQ9fxlIfX8ZSEAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ
445AAAIUEAO2vWsJhXg482IrlDsDH15Ck3r30CIhpXbrvAZIwTll6X3yaSrHU8iSP8SNOZd+D
446r6nwgpD1HHxVhyvi1URmoTAYAXBgfWWThBKoijSE+gvVnCUxg6nAUs0Nsk0jsT131JlU6+
447A++r22LuW43fWgSsfB3XzLz3qNFg9HbPy41y3SaA13AAAAQgH4DaftY0e/KsN695VJ06wy
448Ve0k2ddxoEsSE15H4lgNHM2iuYKzIqZJOReHRCTff6QGgMYPDqDfFfL1Hc1Ntql0pwAAAA
4491yb2JlcnRAYmJzZGV2AQIDBAU=
450-----END OPENSSH PRIVATE KEY-----
451";
452        assert!(
453            decode_secret_key(key, None).unwrap().algorithm()
454                == ssh_key::Algorithm::Ecdsa {
455                    curve: ssh_key::EcdsaCurve::NistP521
456                },
457        );
458    }
459
460    #[test]
461    fn test_fingerprint() {
462        let key = parse_public_key_base64(
463            "AAAAC3NzaC1lZDI1NTE5AAAAILagOJFgwaMNhBWQINinKOXmqS4Gh5NgxgriXwdOoINJ",
464        )
465        .unwrap();
466        assert_eq!(
467            format!("{}", key.fingerprint(ssh_key::HashAlg::Sha256)),
468            "SHA256:ldyiXa1JQakitNU5tErauu8DvWQ1dZ7aXu+rm7KQuog"
469        );
470    }
471
472    #[test]
473    fn test_parse_p256_public_key() {
474        env_logger::try_init().unwrap_or(());
475        let key = "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMxBTpMIGvo7CnordO7wP0QQRqpBwUjOLl4eMhfucfE1sjTYyK5wmTl1UqoSDS1PtRVTBdl+0+9pquFb46U7fwg=";
476
477        assert!(
478            parse_public_key_base64(key).unwrap().algorithm()
479                == ssh_key::Algorithm::Ecdsa {
480                    curve: ssh_key::EcdsaCurve::NistP256
481                },
482        );
483    }
484
485    #[test]
486    fn test_parse_p384_public_key() {
487        env_logger::try_init().unwrap_or(());
488        let key = "AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBBVFgxJxpCaAALZG/S5BHT8/IUQ5mfuKaj7Av9g7Jw59fBEGHfPBz1wFtHGYw5bdLmfVZTIDfogDid5zqJeAKr1AcD06DKTXDzd2EpUjqeLfQ5b3erHuX758fgu/pSDGRA==";
489
490        assert!(
491            parse_public_key_base64(key).unwrap().algorithm()
492                == ssh_key::Algorithm::Ecdsa {
493                    curve: ssh_key::EcdsaCurve::NistP384
494                }
495        );
496    }
497
498    #[test]
499    fn test_parse_p521_public_key() {
500        env_logger::try_init().unwrap_or(());
501        let key = "AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAAQepXEpOrzlX22r4E5zEHjhHWeZUe//zaevTanOWRBnnaCGWJFGCdjeAbNOuAmLtXc+HZdJTCZGREeSLSrpJa71QDCgZl0N7DkDUanCpHZJe/DCK6qwtHYbEMn28iLMlGCOrCIa060EyJHbp1xcJx4I1SKj/f/fm3DhhID/do6zyf8Cg==";
502
503        assert!(
504            parse_public_key_base64(key).unwrap().algorithm()
505                == ssh_key::Algorithm::Ecdsa {
506                    curve: ssh_key::EcdsaCurve::NistP521
507                }
508        );
509    }
510
511    #[test]
512    fn test_srhb() {
513        env_logger::try_init().unwrap_or(());
514        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==";
515
516        parse_public_key_base64(key).unwrap();
517    }
518
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    #[test]
554    fn test_decode_pkcs8_rsa_secret_key() {
555        // Generated using: ssh-keygen -t rsa -b 1024 -m pkcs8 -f $file
556        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        // Generated using: ssh-keygen -t ecdsa -b 256 -m pkcs8 -f $file
592        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        // Generated using: ssh-keygen -t ecdsa -b 384 -m pkcs8 -f $file
610        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        // Generated using: ssh-keygen -t ecdsa -b 521 -m pkcs8 -f $file
629        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::Error> {
650        // Generated by russh 0.43
651        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    #[test]
691    fn test_o01eg() {
692        env_logger::try_init().unwrap_or(());
693
694        let key = "-----BEGIN RSA PRIVATE KEY-----
695Proc-Type: 4,ENCRYPTED
696DEK-Info: AES-128-CBC,EA77308AAF46981303D8C44D548D097E
697
698QR18hXmAgGehm1QMMYGF34PAtBpTj+8/ZPFx2zZxir7pzDpfYoNAIf/fzLsW1ruG
6990xo/ZK/T3/TpMgjmLsCR6q+KU4jmCcCqWQIGWYJt9ljFI5y/CXr5uqP3DKcqtdxQ
700fbBAfXJ8ITF+Tj0Cljm2S1KYHor+mkil5Lf/ZNiHxcLfoI3xRnpd+2cemN9Ly9eY
701HNTbeWbLosfjwdfPJNWFNV5flm/j49klx/UhXhr5HNFNgp/MlTrvkH4rBt4wYPpE
702cZBykt4Fo1KGl95pT22inGxQEXVHF1Cfzrf5doYWxjiRTmfhpPSz/Tt0ev3+jIb8
703Htx6N8tNBoVxwCiQb7jj3XNim2OGohIp5vgW9sh6RDfIvr1jphVOgCTFKSo37xk0
704156EoCVo3VcLf+p0/QitbUHR+RGW/PvUJV/wFR5ShYqjI+N2iPhkD24kftJ/MjPt
705AAwCm/GYoYjGDhIzQMB+FETZKU5kz23MQtZFbYjzkcI/RE87c4fkToekNCdQrsoZ
706wG0Ne2CxrwwEnipHCqT4qY+lZB9EbqQgbWOXJgxA7lfznBFjdSX7uDc/mnIt9Y6B
707MZRXH3PTfotHlHMe+Ypt5lfPBi/nruOl5wLo3L4kY5pUyqR0cXKNycIJZb/pJAnE
708ryIb59pZP7njvoHzRqnC9dycnTFW3geK5LU+4+JMUS32F636aorunRCl6IBmVQHL
709uZ+ue714fn/Sn6H4dw6IH1HMDG1hr8ozP4sNUCiAQ05LsjDMGTdrUsr2iBBpkQhu
710VhUDZy9g/5XF1EgiMbZahmqi5WaJ5K75ToINHb7RjOE7MEiuZ+RPpmYLE0HXyn9X
711HTx0ZGr022dDI6nkvUm6OvEwLUUmmGKRHKe0y1EdICGNV+HWqnlhGDbLWeMyUcIY
712M6Zh9Dw3WXD3kROf5MrJ6n9MDIXx9jy7nmBh7m6zKjBVIw94TE0dsRcWb0O1IoqS
713zLQ6ihno+KsQHDyMVLEUz1TuE52rIpBmqexDm3PdDfCgsNdBKP6QSTcoqcfHKeex
714K93FWgSlvFFQQAkJumJJ+B7ZWnK+2pdjdtWwTpflAKNqc8t//WmjWZzCtbhTHCXV
7151dnMk7azWltBAuXnjW+OqmuAzyh3ayKgqfW66mzSuyQNa1KqFhqpJxOG7IHvxVfQ
716kYeSpqODnL87Zd/dU8s0lOxz3/ymtjPMHlOZ/nHNqW90IIeUwWJKJ46Kv6zXqM1t
717MeD1lvysBbU9rmcUdop0D3MOgGpKkinR5gy4pUsARBiz4WhIm8muZFIObWes/GDS
718zmmkQRO1IcfXKAHbq/OdwbLBm4vM9nk8vPfszoEQCnfOSd7aWrLRjDR+q2RnzNzh
719K+fodaJ864JFIfB/A+aVviVWvBSt0eEbEawhTmNPerMrAQ8tRRhmNxqlDP4gOczi
720iKUmK5recsXk5us5Ik7peIR/f9GAghpoJkF0HrHio47SfABuK30pzcj62uNWGljS
7213d9UQLCepT6RiPFhks/lgimbtSoiJHql1H9Q/3q4MuO2PuG7FXzlTnui3zGw/Vvy
722br8gXU8KyiY9sZVbmplRPF+ar462zcI2kt0a18mr0vbrdqp2eMjb37QDbVBJ+rPE
723-----END RSA PRIVATE KEY-----
724";
725        decode_secret_key(key, Some("12345")).unwrap();
726    }
727
728    pub const PKCS8_RSA: &str = "-----BEGIN RSA PRIVATE KEY-----
729MIIEpAIBAAKCAQEAwBGetHjW+3bDQpVktdemnk7JXgu1NBWUM+ysifYLDBvJ9ttX
730GNZSyQKA4v/dNr0FhAJ8I9BuOTjYCy1YfKylhl5D/DiSSXFPsQzERMmGgAlYvU2U
731+FTxpBC11EZg69CPVMKKevfoUD+PZA5zB7Hc1dXFfwqFc5249SdbAwD39VTbrOUI
732WECvWZs6/ucQxHHXP2O9qxWqhzb/ddOnqsDHUNoeceiNiCf2anNymovrIMjAqq1R
733t2UP3f06/Zt7Jx5AxKqS4seFkaDlMAK8JkEDuMDOdKI36raHkKanfx8CnGMSNjFQ
734QtvnpD8VSGkDTJN3Qs14vj2wvS477BQXkBKN1QIDAQABAoIBABb6xLMw9f+2ENyJ
735hTggagXsxTjkS7TElCu2OFp1PpMfTAWl7oDBO7xi+UqvdCcVbHCD35hlWpqsC2Ui
7368sBP46n040ts9UumK/Ox5FWaiuYMuDpF6vnfJ94KRcb0+KmeFVf9wpW9zWS0hhJh
737jC+yfwpyfiOZ/ad8imGCaOguGHyYiiwbRf381T/1FlaOGSae88h+O8SKTG1Oahq4
7380HZ/KBQf9pij0mfVQhYBzsNu2JsHNx9+DwJkrXT7K9SHBpiBAKisTTCnQmS89GtE
7396J2+bq96WgugiM7X6OPnmBmE/q1TgV18OhT+rlvvNi5/n8Z1ag5Xlg1Rtq/bxByP
740CeIVHsECgYEA9dX+LQdv/Mg/VGIos2LbpJUhJDj0XWnTRq9Kk2tVzr+9aL5VikEb
74109UPIEa2ToL6LjlkDOnyqIMd/WY1W0+9Zf1ttg43S/6Rvv1W8YQde0Nc7QTcuZ1K
7429jSSP9hzsa3KZtx0fCtvVHm+ac9fP6u80tqumbiD2F0cnCZcSxOb4+UCgYEAyAKJ
74370nNKegH4rTCStAqR7WGAsdPE3hBsC814jguplCpb4TwID+U78Xxu0DQF8WtVJ10
744SJuR0R2q4L9uYWpo0MxdawSK5s9Am27MtJL0mkFQX0QiM7hSZ3oqimsdUdXwxCGg
745oktxCUUHDIPJNVd4Xjg0JTh4UZT6WK9hl1zLQzECgYEAiZRCFGc2KCzVLF9m0cXA
746kGIZUxFAyMqBv+w3+zq1oegyk1z5uE7pyOpS9cg9HME2TAo4UPXYpLAEZ5z8vWZp
74745sp/BoGnlQQsudK8gzzBtnTNp5i/MnnetQ/CNYVIVnWjSxRUHBqdMdRZhv0/Uga
748e5KA5myZ9MtfSJA7VJTbyHUCgYBCcS13M1IXaMAt3JRqm+pftfqVs7YeJqXTrGs/
749AiDlGQigRk4quFR2rpAV/3rhWsawxDmb4So4iJ16Wb2GWP4G1sz1vyWRdSnmOJGC
750LwtYrvfPHegqvEGLpHa7UsgDpol77hvZriwXwzmLO8A8mxkeW5dfAfpeR5o+mcxW
751pvnTEQKBgQCKx6Ln0ku6jDyuDzA9xV2/PET5D75X61R2yhdxi8zurY/5Qon3OWzk
752jn/nHT3AZghGngOnzyv9wPMKt9BTHyTB6DlB6bRVLDkmNqZh5Wi8U1/IjyNYI0t2
753xV/JrzLAwPoKk3bkqys3bUmgo6DxVC/6RmMwPQ0rmpw78kOgEej90g==
754-----END RSA PRIVATE KEY-----
755";
756
757    #[test]
758    fn test_pkcs8() {
759        env_logger::try_init().unwrap_or(());
760        println!("test");
761        decode_secret_key(PKCS8_RSA, Some("blabla")).unwrap();
762    }
763
764    const PKCS8_ENCRYPTED: &str = "-----BEGIN ENCRYPTED PRIVATE KEY-----
765MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQITo1O0b8YrS0CAggA
766MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBtLH4T1KOfo1GGr7salhR8BIIE
7670KN9ednYwcTGSX3hg7fROhTw7JAJ1D4IdT1fsoGeNu2BFuIgF3cthGHe6S5zceI2
768MpkfwvHbsOlDFWMUIAb/VY8/iYxhNmd5J6NStMYRC9NC0fVzOmrJqE1wITqxtORx
769IkzqkgFUbaaiFFQPepsh5CvQfAgGEWV329SsTOKIgyTj97RxfZIKA+TR5J5g2dJY
770j346SvHhSxJ4Jc0asccgMb0HGh9UUDzDSql0OIdbnZW5KzYJPOx+aDqnpbz7UzY/
771P8N0w/pEiGmkdkNyvGsdttcjFpOWlLnLDhtLx8dDwi/sbEYHtpMzsYC9jPn3hnds
772TcotqjoSZ31O6rJD4z18FOQb4iZs3MohwEdDd9XKblTfYKM62aQJWH6cVQcg+1C7
773jX9l2wmyK26Tkkl5Qg/qSfzrCveke5muZgZkFwL0GCcgPJ8RixSB4GOdSMa/hAMU
774kvFAtoV2GluIgmSe1pG5cNMhurxM1dPPf4WnD+9hkFFSsMkTAuxDZIdDk3FA8zof
775Yhv0ZTfvT6V+vgH3Hv7Tqcxomy5Qr3tj5vvAqqDU6k7fC4FvkxDh2mG5ovWvc4Nb
776Xv8sed0LGpYitIOMldu6650LoZAqJVv5N4cAA2Edqldf7S2Iz1QnA/usXkQd4tLa
777Z80+sDNv9eCVkfaJ6kOVLk/ghLdXWJYRLenfQZtVUXrPkaPpNXgD0dlaTN8KuvML
778Uw/UGa+4ybnPsdVflI0YkJKbxouhp4iB4S5ACAwqHVmsH5GRnujf10qLoS7RjDAl
779o/wSHxdT9BECp7TT8ID65u2mlJvH13iJbktPczGXt07nBiBse6OxsClfBtHkRLzE
780QF6UMEXsJnIIMRfrZQnduC8FUOkfPOSXc8r9SeZ3GhfbV/DmWZvFPCpjzKYPsM5+
781N8Bw/iZ7NIH4xzNOgwdp5BzjH9hRtCt4sUKVVlWfEDtTnkHNOusQGKu7HkBF87YZ
782RN/Nd3gvHob668JOcGchcOzcsqsgzhGMD8+G9T9oZkFCYtwUXQU2XjMN0R4VtQgZ
783rAxWyQau9xXMGyDC67gQ5xSn+oqMK0HmoW8jh2LG/cUowHFAkUxdzGadnjGhMOI2
784zwNJPIjF93eDF/+zW5E1l0iGdiYyHkJbWSvcCuvTwma9FIDB45vOh5mSR+YjjSM5
785nq3THSWNi7Cxqz12Q1+i9pz92T2myYKBBtu1WDh+2KOn5DUkfEadY5SsIu/Rb7ub
7865FBihk2RN3y/iZk+36I69HgGg1OElYjps3D+A9AjVby10zxxLAz8U28YqJZm4wA/
787T0HLxBiVw+rsHmLP79KvsT2+b4Diqih+VTXouPWC/W+lELYKSlqnJCat77IxgM9e
788YIhzD47OgWl33GJ/R10+RDoDvY4koYE+V5NLglEhbwjloo9Ryv5ywBJNS7mfXMsK
789/uf+l2AscZTZ1mhtL38efTQCIRjyFHc3V31DI0UdETADi+/Omz+bXu0D5VvX+7c6
790b1iVZKpJw8KUjzeUV8yOZhvGu3LrQbhkTPVYL555iP1KN0Eya88ra+FUKMwLgjYr
791JkUx4iad4dTsGPodwEP/Y9oX/Qk3ZQr+REZ8lg6IBoKKqqrQeBJ9gkm1jfKE6Xkc
792Cog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/MtlOAr5rux
793-----END ENCRYPTED PRIVATE KEY-----";
794
795    #[test]
796    fn test_gpg() {
797        env_logger::try_init().unwrap_or(());
798        let key = [
799            0, 0, 0, 7, 115, 115, 104, 45, 114, 115, 97, 0, 0, 0, 3, 1, 0, 1, 0, 0, 1, 129, 0, 163,
800            72, 59, 242, 4, 248, 139, 217, 57, 126, 18, 195, 170, 3, 94, 154, 9, 150, 89, 171, 236,
801            192, 178, 185, 149, 73, 210, 121, 95, 126, 225, 209, 199, 208, 89, 130, 175, 229, 163,
802            102, 176, 155, 69, 199, 155, 71, 214, 170, 61, 202, 2, 207, 66, 198, 147, 65, 10, 176,
803            20, 105, 197, 133, 101, 126, 193, 252, 245, 254, 182, 14, 250, 118, 113, 18, 220, 38,
804            220, 75, 247, 50, 163, 39, 2, 61, 62, 28, 79, 199, 238, 189, 33, 194, 190, 22, 87, 91,
805            1, 215, 115, 99, 138, 124, 197, 127, 237, 228, 170, 42, 25, 117, 1, 106, 36, 54, 163,
806            163, 207, 129, 133, 133, 28, 185, 170, 217, 12, 37, 113, 181, 182, 180, 178, 23, 198,
807            233, 31, 214, 226, 114, 146, 74, 205, 177, 82, 232, 238, 165, 44, 5, 250, 150, 236, 45,
808            30, 189, 254, 118, 55, 154, 21, 20, 184, 235, 223, 5, 20, 132, 249, 147, 179, 88, 146,
809            6, 100, 229, 200, 221, 157, 135, 203, 57, 204, 43, 27, 58, 85, 54, 219, 138, 18, 37,
810            80, 106, 182, 95, 124, 140, 90, 29, 48, 193, 112, 19, 53, 84, 201, 153, 52, 249, 15,
811            41, 5, 11, 147, 18, 8, 27, 31, 114, 45, 224, 118, 111, 176, 86, 88, 23, 150, 184, 252,
812            128, 52, 228, 90, 30, 34, 135, 234, 123, 28, 239, 90, 202, 239, 188, 175, 8, 141, 80,
813            59, 194, 80, 43, 205, 34, 137, 45, 140, 244, 181, 182, 229, 247, 94, 216, 115, 173,
814            107, 184, 170, 102, 78, 249, 4, 186, 234, 169, 148, 98, 128, 33, 115, 232, 126, 84, 76,
815            222, 145, 90, 58, 1, 4, 163, 243, 93, 215, 154, 205, 152, 178, 109, 241, 197, 82, 148,
816            222, 78, 44, 193, 248, 212, 157, 118, 217, 75, 211, 23, 229, 121, 28, 180, 208, 173,
817            204, 14, 111, 226, 25, 163, 220, 95, 78, 175, 189, 168, 67, 159, 179, 176, 200, 150,
818            202, 248, 174, 109, 25, 89, 176, 220, 226, 208, 187, 84, 169, 157, 14, 88, 217, 221,
819            117, 254, 51, 45, 93, 184, 80, 225, 158, 29, 76, 38, 69, 72, 71, 76, 50, 191, 210, 95,
820            152, 175, 26, 207, 91, 7,
821        ];
822        ssh_key::PublicKey::decode(&key).unwrap();
823    }
824
825    #[test]
826    fn test_pkcs8_encrypted() {
827        env_logger::try_init().unwrap_or(());
828        println!("test");
829        decode_secret_key(PKCS8_ENCRYPTED, Some("blabla")).unwrap();
830    }
831
832    #[cfg(unix)]
833    async fn test_client_agent(key: PrivateKey) -> Result<(), Box<dyn std::error::Error>> {
834        env_logger::try_init().unwrap_or(());
835        use std::process::Stdio;
836
837        let dir = tempdir::TempDir::new("russh")?;
838        let agent_path = dir.path().join("agent");
839        let mut agent = tokio::process::Command::new("ssh-agent")
840            .arg("-a")
841            .arg(&agent_path)
842            .arg("-D")
843            .stdout(Stdio::null())
844            .stderr(Stdio::null())
845            .spawn()?;
846
847        // Wait for the socket to be created
848        while agent_path.canonicalize().is_err() {
849            tokio::time::sleep(std::time::Duration::from_millis(10)).await;
850        }
851
852        let public = key.public_key();
853        let stream = tokio::net::UnixStream::connect(&agent_path).await?;
854        let mut client = agent::client::AgentClient::connect(stream);
855        client.add_identity(&key, &[]).await?;
856        client.request_identities().await?;
857        let buf = russh_cryptovec::CryptoVec::from_slice(b"blabla");
858        let len = buf.len();
859        let buf = client.sign_request(public, buf).await.unwrap();
860        let (a, b) = buf.split_at(len);
861
862        match key.public_key().key_data() {
863            ssh_key::public::KeyData::Ed25519 { .. } => {
864                let sig = &b[b.len() - 64..];
865                let sig = ssh_key::Signature::new(key.algorithm(), sig)?;
866                use signature::Verifier;
867                assert!(Verifier::verify(public, a, &sig).is_ok());
868            }
869            ssh_key::public::KeyData::Ecdsa { .. } => {}
870            _ => {}
871        }
872
873        agent.kill().await?;
874        agent.wait().await?;
875
876        Ok(())
877    }
878
879    #[tokio::test]
880    #[cfg(unix)]
881    async fn test_client_agent_ed25519() {
882        let key = decode_secret_key(ED25519_KEY, Some("blabla")).unwrap();
883        test_client_agent(key).await.expect("ssh-agent test failed")
884    }
885
886    #[tokio::test]
887    #[cfg(unix)]
888    async fn test_client_agent_rsa() {
889        let key = decode_secret_key(PKCS8_ENCRYPTED, Some("blabla")).unwrap();
890        test_client_agent(key).await.expect("ssh-agent test failed")
891    }
892
893    #[tokio::test]
894    #[cfg(unix)]
895    async fn test_client_agent_openssh_rsa() {
896        let key = decode_secret_key(RSA_KEY, None).unwrap();
897        test_client_agent(key).await.expect("ssh-agent test failed")
898    }
899
900    #[test]
901    #[cfg(unix)]
902    fn test_agent() {
903        env_logger::try_init().unwrap_or(());
904        let dir = tempdir::TempDir::new("russh").unwrap();
905        let agent_path = dir.path().join("agent");
906
907        let core = tokio::runtime::Runtime::new().unwrap();
908        use agent;
909        use signature::Verifier;
910
911        #[derive(Clone)]
912        struct X {}
913        impl agent::server::Agent for X {
914            fn confirm(
915                self,
916                _: std::sync::Arc<PrivateKey>,
917            ) -> Box<dyn Future<Output = (Self, bool)> + Send + Unpin> {
918                Box::new(futures::future::ready((self, true)))
919            }
920        }
921        let agent_path_ = agent_path.clone();
922        core.spawn(async move {
923            let mut listener = tokio::net::UnixListener::bind(&agent_path_).unwrap();
924
925            agent::server::serve(
926                Incoming {
927                    listener: &mut listener,
928                },
929                X {},
930            )
931            .await
932        });
933        let key = decode_secret_key(PKCS8_ENCRYPTED, Some("blabla")).unwrap();
934        core.block_on(async move {
935            let public = key.public_key();
936            let stream = tokio::net::UnixStream::connect(&agent_path).await.unwrap();
937            let mut client = agent::client::AgentClient::connect(stream);
938            client
939                .add_identity(&key, &[agent::Constraint::KeyLifetime { seconds: 60 }])
940                .await
941                .unwrap();
942            client.request_identities().await.unwrap();
943            let buf = russh_cryptovec::CryptoVec::from_slice(b"blabla");
944            let len = buf.len();
945            let buf = client.sign_request(public, buf).await.unwrap();
946            let (a, b) = buf.split_at(len);
947            if let ssh_key::public::KeyData::Ed25519 { .. } = public.key_data() {
948                let sig = &b[b.len() - 64..];
949                let sig = ssh_key::Signature::new(key.algorithm(), sig).unwrap();
950                assert!(Verifier::verify(public, a, &sig).is_ok());
951            }
952        })
953    }
954
955    #[cfg(unix)]
956    struct Incoming<'a> {
957        listener: &'a mut tokio::net::UnixListener,
958    }
959
960    #[cfg(unix)]
961    impl futures::stream::Stream for Incoming<'_> {
962        type Item = Result<tokio::net::UnixStream, std::io::Error>;
963
964        fn poll_next(
965            self: std::pin::Pin<&mut Self>,
966            cx: &mut std::task::Context<'_>,
967        ) -> std::task::Poll<Option<Self::Item>> {
968            let (sock, _addr) = futures::ready!(self.get_mut().listener.poll_accept(cx))?;
969            std::task::Poll::Ready(Some(Ok(sock)))
970        }
971    }
972}