russh_keys/format/
mod.rs

1use std::convert::TryInto;
2use std::io::Write;
3
4use data_encoding::{BASE64_MIME, HEXLOWER_PERMISSIVE};
5use pkcs1::DecodeRsaPrivateKey;
6use ssh_key::private::RsaKeypair;
7use ssh_key::PrivateKey;
8
9use super::is_base64_char;
10use crate::Error;
11
12pub mod openssh;
13
14#[cfg(feature = "legacy-ed25519-pkcs8-parser")]
15mod pkcs8_legacy;
16
17pub use self::openssh::*;
18
19pub mod pkcs5;
20pub use self::pkcs5::*;
21
22pub mod pkcs8;
23
24const AES_128_CBC: &str = "DEK-Info: AES-128-CBC,";
25
26#[derive(Clone, Copy, Debug)]
27/// AES encryption key.
28pub enum Encryption {
29    /// Key for AES128
30    Aes128Cbc([u8; 16]),
31    /// Key for AES256
32    Aes256Cbc([u8; 16]),
33}
34
35#[derive(Clone, Debug)]
36enum Format {
37    Rsa,
38    Openssh,
39    Pkcs5Encrypted(Encryption),
40    Pkcs8Encrypted,
41    Pkcs8,
42}
43
44/// Decode a secret key, possibly deciphering it with the supplied
45/// password.
46pub fn decode_secret_key(secret: &str, password: Option<&str>) -> Result<PrivateKey, Error> {
47    let mut format = None;
48    let secret = {
49        let mut started = false;
50        let mut sec = String::new();
51        for l in secret.lines() {
52            if started {
53                if l.starts_with("-----END ") {
54                    break;
55                }
56                if l.chars().all(is_base64_char) {
57                    sec.push_str(l)
58                } else if l.starts_with(AES_128_CBC) {
59                    let iv_: Vec<u8> =
60                        HEXLOWER_PERMISSIVE.decode(l.split_at(AES_128_CBC.len()).1.as_bytes())?;
61                    if iv_.len() != 16 {
62                        return Err(Error::CouldNotReadKey);
63                    }
64                    let mut iv = [0; 16];
65                    iv.clone_from_slice(&iv_);
66                    format = Some(Format::Pkcs5Encrypted(Encryption::Aes128Cbc(iv)))
67                }
68            }
69            if l == "-----BEGIN OPENSSH PRIVATE KEY-----" {
70                started = true;
71                format = Some(Format::Openssh);
72            } else if l == "-----BEGIN RSA PRIVATE KEY-----" {
73                started = true;
74                format = Some(Format::Rsa);
75            } else if l == "-----BEGIN ENCRYPTED PRIVATE KEY-----" {
76                started = true;
77                format = Some(Format::Pkcs8Encrypted);
78            } else if l == "-----BEGIN PRIVATE KEY-----" {
79                started = true;
80                format = Some(Format::Pkcs8);
81            }
82        }
83        sec
84    };
85
86    let secret = BASE64_MIME.decode(secret.as_bytes())?;
87    match format {
88        Some(Format::Openssh) => decode_openssh(&secret, password),
89        Some(Format::Rsa) => Ok(decode_rsa_pkcs1_der(&secret)?.into()),
90        Some(Format::Pkcs5Encrypted(enc)) => decode_pkcs5(&secret, password, enc),
91        Some(Format::Pkcs8Encrypted) | Some(Format::Pkcs8) => {
92            let result = self::pkcs8::decode_pkcs8(&secret, password.map(|x| x.as_bytes()));
93            #[cfg(feature = "legacy-ed25519-pkcs8-parser")]
94            {
95                if result.is_err() {
96                    let legacy_result =
97                        pkcs8_legacy::decode_pkcs8(&secret, password.map(|x| x.as_bytes()));
98                    if let Ok(key) = legacy_result {
99                        return Ok(key);
100                    }
101                }
102            }
103            result
104        }
105        None => Err(Error::CouldNotReadKey),
106    }
107}
108
109pub fn encode_pkcs8_pem<W: Write>(key: &PrivateKey, mut w: W) -> Result<(), Error> {
110    let x = self::pkcs8::encode_pkcs8(key)?;
111    w.write_all(b"-----BEGIN PRIVATE KEY-----\n")?;
112    w.write_all(BASE64_MIME.encode(&x).as_bytes())?;
113    w.write_all(b"\n-----END PRIVATE KEY-----\n")?;
114    Ok(())
115}
116
117pub fn encode_pkcs8_pem_encrypted<W: Write>(
118    key: &PrivateKey,
119    pass: &[u8],
120    rounds: u32,
121    mut w: W,
122) -> Result<(), Error> {
123    let x = self::pkcs8::encode_pkcs8_encrypted(pass, rounds, key)?;
124    w.write_all(b"-----BEGIN ENCRYPTED PRIVATE KEY-----\n")?;
125    w.write_all(BASE64_MIME.encode(&x).as_bytes())?;
126    w.write_all(b"\n-----END ENCRYPTED PRIVATE KEY-----\n")?;
127    Ok(())
128}
129
130fn decode_rsa_pkcs1_der(secret: &[u8]) -> Result<RsaKeypair, Error> {
131    Ok(rsa::RsaPrivateKey::from_pkcs1_der(secret)?.try_into()?)
132}