ssh/config/
auth.rs

1use crate::algorithm::{
2    hash::{self, HashCtx, HashType},
3    PubKey,
4};
5use crate::model::Data;
6use crate::{SshError, SshResult};
7use rsa::pkcs1::DecodeRsaPrivateKey;
8use rsa::pkcs1v15::Pkcs1v15Sign;
9use rsa::traits::PublicKeyParts;
10use std::fmt::Debug;
11use std::fs::File;
12use std::io::Read;
13use std::path::Path;
14
15const KEY_FILE_MAGIC_START: &str = "-----BEGIN OPENSSH PRIVATE KEY-----";
16
17#[derive(Clone, Default)]
18pub struct KeyPair {
19    pub(super) private_key: String,
20    pub(super) key_type: KeyType,
21}
22
23impl KeyPair {
24    pub fn from_str(key_str: &str) -> SshResult<Self> {
25        // first validate the key
26        let key_str = key_str.trim().to_owned();
27
28        let (key_type, private_key) = if rsa::RsaPrivateKey::from_pkcs1_pem(&key_str).is_ok() {
29            (KeyType::PemRsa, key_str)
30        } else if key_str.starts_with(KEY_FILE_MAGIC_START) {
31            match ssh_key::PrivateKey::from_openssh(&key_str) {
32                Ok(prk) => match prk.algorithm() {
33                    ssh_key::Algorithm::Rsa { hash: _hash } => (KeyType::SshRsa, key_str),
34                    ssh_key::Algorithm::Ed25519 => (KeyType::SshEd25519, key_str),
35                    x => {
36                        return Err(SshError::SshPubKeyError(format!(
37                            "Currently don't support the key file type {}",
38                            x
39                        )))
40                    }
41                },
42                Err(e) => return Err(SshError::SshPubKeyError(e.to_string())),
43            }
44        } else {
45            return Err(SshError::SshPubKeyError(
46                "Unable to detect the pulic key type".to_owned(),
47            ));
48        };
49
50        // then store it
51        let pair = KeyPair {
52            private_key,
53            key_type,
54        };
55        Ok(pair)
56    }
57
58    pub fn get_blob(&self, alg: &PubKey) -> Vec<u8> {
59        match self.key_type {
60            KeyType::PemRsa => {
61                // already valid key string, just unwrap it.
62                let rprk = rsa::RsaPrivateKey::from_pkcs1_pem(&self.private_key).unwrap();
63                let rpuk = rprk.to_public_key();
64                let es = rpuk.e().to_bytes_be();
65                let ns = rpuk.n().to_bytes_be();
66                let mut blob = Data::new();
67                blob.put_str(alg.as_ref());
68                blob.put_mpint(&es);
69                blob.put_mpint(&ns);
70                blob.to_vec()
71            }
72            KeyType::SshRsa => {
73                let prk = ssh_key::PrivateKey::from_openssh(&self.private_key).unwrap();
74                let rsa = prk.key_data().rsa().unwrap();
75                let es = rsa.public.e.as_bytes();
76                let ns = rsa.public.n.as_bytes();
77                let mut blob = Data::new();
78                blob.put_str(alg.as_ref());
79                blob.put_mpint(es);
80                blob.put_mpint(ns);
81                blob.to_vec()
82            }
83            KeyType::SshEd25519 => {
84                let prk = ssh_key::PrivateKey::from_openssh(&self.private_key).unwrap();
85                let ed25519 = prk.key_data().ed25519().unwrap();
86                let mut blob = Data::new();
87                blob.put_str(alg.as_ref());
88                blob.put_u8s(ed25519.public.as_ref());
89                blob.to_vec()
90            }
91        }
92    }
93
94    fn sign(&self, sd: &[u8], alg: &PubKey) -> Vec<u8> {
95        match self.key_type {
96            KeyType::PemRsa | KeyType::SshRsa => {
97                let (scheme, digest) = match alg {
98                    PubKey::RsaSha2_512 => (
99                        Pkcs1v15Sign::new::<sha2::Sha512>(),
100                        ring::digest::digest(&ring::digest::SHA512, sd),
101                    ),
102                    PubKey::RsaSha2_256 => (
103                        Pkcs1v15Sign::new::<sha2::Sha256>(),
104                        ring::digest::digest(&ring::digest::SHA256, sd),
105                    ),
106                    #[cfg(feature = "deprecated-rsa-sha1")]
107                    PubKey::SshRsa => (
108                        Pkcs1v15Sign::new::<sha1::Sha1>(),
109                        ring::digest::digest(&ring::digest::SHA1_FOR_LEGACY_USE_ONLY, sd),
110                    ),
111                    _ => unreachable!(),
112                };
113
114                let msg = digest.as_ref();
115                let rprk = match self.key_type {
116                    KeyType::PemRsa => {
117                        rsa::RsaPrivateKey::from_pkcs1_pem(self.private_key.as_str()).unwrap()
118                    }
119                    KeyType::SshRsa => {
120                        // the sign method in ssh_key itself can only work with sha2-512
121                        // so we convert it to the raw rsa key
122                        let prk = ssh_key::PrivateKey::from_openssh(&self.private_key).unwrap();
123                        let rsa = prk.key_data().rsa().unwrap();
124                        rsa::RsaPrivateKey::try_from(rsa).unwrap()
125                    }
126                    _ => unreachable!(),
127                };
128
129                rprk.sign(scheme, msg).unwrap()
130            }
131            KeyType::SshEd25519 => {
132                use signature::Signer;
133                let prk = ssh_key::PrivateKey::from_openssh(&self.private_key).unwrap();
134                let sign = prk.try_sign(sd).unwrap();
135                sign.as_bytes().to_vec()
136            }
137        }
138    }
139
140    pub(crate) fn signature(
141        &self,
142        buf: &[u8],
143        hash_ctx: HashCtx,
144        hash_type: HashType,
145        alg: &PubKey,
146    ) -> Vec<u8> {
147        let session_id = hash::digest(hash_ctx.as_bytes().as_slice(), hash_type);
148        let mut sd = Data::new();
149        sd.put_u8s(session_id.as_slice());
150        sd.extend_from_slice(buf);
151        let sign = self.sign(&sd, alg);
152        let mut ss = Data::new();
153        ss.put_str(alg.as_ref());
154        ss.put_u8s(&sign);
155        ss.to_vec()
156    }
157}
158
159#[derive(Clone, Default)]
160pub(super) enum KeyType {
161    #[default]
162    PemRsa,
163    SshRsa,
164    SshEd25519,
165}
166
167#[derive(Clone, Default)]
168pub(crate) struct AuthInfo {
169    pub username: String,
170    pub password: String,
171    pub key_pair: Option<KeyPair>,
172}
173
174impl Debug for AuthInfo {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        write!(f, "username: {}", self.username)?;
177        Ok(())
178    }
179}
180
181impl AuthInfo {
182    pub fn username<U>(&mut self, u: U) -> SshResult<()>
183    where
184        U: ToString,
185    {
186        self.username = u.to_string();
187        Ok(())
188    }
189
190    pub fn password<P>(&mut self, p: P) -> SshResult<()>
191    where
192        P: ToString,
193    {
194        self.password = p.to_string();
195        Ok(())
196    }
197
198    pub fn private_key<K>(&mut self, k: K) -> SshResult<()>
199    where
200        K: ToString,
201    {
202        self.key_pair = Some((KeyPair::from_str(&k.to_string()))?);
203        Ok(())
204    }
205
206    pub fn private_key_path<P>(&mut self, p: P) -> SshResult<()>
207    where
208        P: AsRef<Path>,
209    {
210        let mut file = File::open(p)?;
211        let mut prks = String::new();
212        file.read_to_string(&mut prks)?;
213
214        self.key_pair = Some((KeyPair::from_str(&prks))?);
215        Ok(())
216    }
217}