bssh_russh/
auth.rs

1// Copyright 2016 Pierre-Étienne Meunier
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15
16use std::future::Future;
17use std::ops::Deref;
18use std::str::FromStr;
19use std::sync::Arc;
20
21use ssh_key::{Certificate, HashAlg, PrivateKey};
22use thiserror::Error;
23use tokio::io::{AsyncRead, AsyncWrite};
24
25use crate::CryptoVec;
26use crate::helpers::NameList;
27use crate::keys::PrivateKeyWithHashAlg;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum MethodKind {
31    None,
32    Password,
33    PublicKey,
34    HostBased,
35    KeyboardInteractive,
36}
37
38impl From<&MethodKind> for &'static str {
39    fn from(value: &MethodKind) -> Self {
40        match value {
41            MethodKind::None => "none",
42            MethodKind::Password => "password",
43            MethodKind::PublicKey => "publickey",
44            MethodKind::HostBased => "hostbased",
45            MethodKind::KeyboardInteractive => "keyboard-interactive",
46        }
47    }
48}
49
50impl FromStr for MethodKind {
51    fn from_str(b: &str) -> Result<MethodKind, Self::Err> {
52        match b {
53            "none" => Ok(MethodKind::None),
54            "password" => Ok(MethodKind::Password),
55            "publickey" => Ok(MethodKind::PublicKey),
56            "hostbased" => Ok(MethodKind::HostBased),
57            "keyboard-interactive" => Ok(MethodKind::KeyboardInteractive),
58            _ => Err(()),
59        }
60    }
61
62    type Err = ();
63}
64
65impl From<&MethodKind> for String {
66    fn from(value: &MethodKind) -> Self {
67        <&str>::from(value).to_string()
68    }
69}
70
71/// An ordered set of authentication methods.
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct MethodSet(Vec<MethodKind>);
74
75impl Deref for MethodSet {
76    type Target = [MethodKind];
77
78    fn deref(&self) -> &Self::Target {
79        &self.0
80    }
81}
82
83impl From<&[MethodKind]> for MethodSet {
84    fn from(value: &[MethodKind]) -> Self {
85        let mut this = Self::empty();
86        for method in value {
87            this.push(*method);
88        }
89        this
90    }
91}
92
93impl From<&MethodSet> for NameList {
94    fn from(value: &MethodSet) -> Self {
95        Self(value.iter().map(|x| x.into()).collect())
96    }
97}
98
99impl From<&NameList> for MethodSet {
100    fn from(value: &NameList) -> Self {
101        Self(
102            value
103                .0
104                .iter()
105                .filter_map(|x| MethodKind::from_str(x).ok())
106                .collect(),
107        )
108    }
109}
110
111impl MethodSet {
112    pub fn empty() -> Self {
113        Self(Vec::new())
114    }
115
116    pub fn all() -> Self {
117        Self(vec![
118            MethodKind::None,
119            MethodKind::Password,
120            MethodKind::PublicKey,
121            MethodKind::HostBased,
122            MethodKind::KeyboardInteractive,
123        ])
124    }
125
126    pub fn remove(&mut self, method: MethodKind) {
127        self.0.retain(|x| *x != method);
128    }
129
130    /// Push a method to the end of the list.
131    /// If the method is already in the list, it is moved to the end.
132    pub fn push(&mut self, method: MethodKind) {
133        self.remove(method);
134        self.0.push(method);
135    }
136}
137
138#[derive(Debug, Clone, PartialEq, Eq)]
139pub enum AuthResult {
140    Success,
141    Failure {
142        /// The server suggests to proceed with these auth methods
143        remaining_methods: MethodSet,
144        /// The server says that though auth method has been accepted,
145        /// further authentication is required
146        partial_success: bool,
147    },
148}
149
150impl AuthResult {
151    pub fn success(&self) -> bool {
152        matches!(self, AuthResult::Success)
153    }
154}
155
156#[cfg_attr(feature = "async-trait", async_trait::async_trait)]
157pub trait Signer: Sized {
158    type Error: From<crate::SendError>;
159
160    fn auth_publickey_sign(
161        &mut self,
162        key: &ssh_key::PublicKey,
163        hash_alg: Option<HashAlg>,
164        to_sign: CryptoVec,
165    ) -> impl Future<Output = Result<CryptoVec, Self::Error>> + Send;
166}
167
168#[derive(Debug, Error)]
169pub enum AgentAuthError {
170    #[error(transparent)]
171    Send(#[from] crate::SendError),
172    #[error(transparent)]
173    Key(#[from] crate::keys::Error),
174}
175
176#[cfg_attr(feature = "async-trait", async_trait::async_trait)]
177impl<R: AsyncRead + AsyncWrite + Unpin + Send + 'static> Signer
178    for crate::keys::agent::client::AgentClient<R>
179{
180    type Error = AgentAuthError;
181
182    #[allow(clippy::manual_async_fn)]
183    fn auth_publickey_sign(
184        &mut self,
185        key: &ssh_key::PublicKey,
186        hash_alg: Option<HashAlg>,
187        to_sign: CryptoVec,
188    ) -> impl Future<Output = Result<CryptoVec, Self::Error>> {
189        async move {
190            self.sign_request(key, hash_alg, to_sign)
191                .await
192                .map_err(Into::into)
193        }
194    }
195}
196
197#[derive(Debug)]
198#[allow(clippy::large_enum_variant)]
199pub enum Method {
200    None,
201    Password {
202        password: String,
203    },
204    PublicKey {
205        key: PrivateKeyWithHashAlg,
206    },
207    OpenSshCertificate {
208        key: Arc<PrivateKey>,
209        cert: Certificate,
210    },
211    FuturePublicKey {
212        key: ssh_key::PublicKey,
213        hash_alg: Option<HashAlg>,
214    },
215    KeyboardInteractive {
216        submethods: String,
217    },
218    // Hostbased,
219}
220
221#[doc(hidden)]
222#[derive(Debug)]
223pub struct AuthRequest {
224    pub methods: MethodSet,
225    #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
226    pub partial_success: bool,
227    pub current: Option<CurrentRequest>,
228    #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
229    pub rejection_count: usize,
230}
231
232#[doc(hidden)]
233#[derive(Debug)]
234pub enum CurrentRequest {
235    #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
236    PublicKey {
237        #[allow(dead_code)]
238        key: CryptoVec,
239        #[allow(dead_code)]
240        algo: CryptoVec,
241        sent_pk_ok: bool,
242    },
243    KeyboardInteractive {
244        #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
245        submethods: String,
246    },
247}
248
249impl AuthRequest {
250    pub(crate) fn new(method: &Method) -> Self {
251        match method {
252            Method::KeyboardInteractive { submethods } => Self {
253                methods: MethodSet::all(),
254                partial_success: false,
255                current: Some(CurrentRequest::KeyboardInteractive {
256                    submethods: submethods.to_string(),
257                }),
258                rejection_count: 0,
259            },
260            _ => Self {
261                methods: MethodSet::all(),
262                partial_success: false,
263                current: None,
264                rejection_count: 0,
265            },
266        }
267    }
268}