1use 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::helpers::NameList;
26use crate::keys::PrivateKeyWithHashAlg;
27use crate::keys::agent::AgentIdentity;
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#[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 .iter()
104 .filter_map(|x| MethodKind::from_str(x).ok())
105 .collect(),
106 )
107 }
108}
109
110impl MethodSet {
111 pub fn empty() -> Self {
112 Self(Vec::new())
113 }
114
115 pub fn all() -> Self {
116 Self(vec![
117 MethodKind::None,
118 MethodKind::Password,
119 MethodKind::PublicKey,
120 MethodKind::HostBased,
121 MethodKind::KeyboardInteractive,
122 ])
123 }
124
125 pub fn remove(&mut self, method: MethodKind) {
126 self.0.retain(|x| *x != method);
127 }
128
129 pub fn push(&mut self, method: MethodKind) {
132 self.remove(method);
133 self.0.push(method);
134 }
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub enum AuthResult {
139 Success,
140 Failure {
141 remaining_methods: MethodSet,
143 partial_success: bool,
146 },
147}
148
149impl AuthResult {
150 pub fn success(&self) -> bool {
151 matches!(self, AuthResult::Success)
152 }
153}
154
155#[cfg_attr(feature = "async-trait", async_trait::async_trait)]
156pub trait Signer: Sized {
157 type Error: From<crate::SendError>;
158
159 fn auth_sign(
160 &mut self,
161 key: &AgentIdentity,
162 hash_alg: Option<HashAlg>,
163 to_sign: Vec<u8>,
164 ) -> impl Future<Output = Result<Vec<u8>, Self::Error>> + Send;
165}
166
167#[derive(Debug, Error)]
168pub enum AgentAuthError {
169 #[error(transparent)]
170 Send(#[from] crate::SendError),
171 #[error(transparent)]
172 Key(#[from] crate::keys::Error),
173}
174
175#[cfg_attr(feature = "async-trait", async_trait::async_trait)]
176impl<R: AsyncRead + AsyncWrite + Unpin + Send + 'static> Signer
177 for crate::keys::agent::client::AgentClient<R>
178{
179 type Error = AgentAuthError;
180
181 #[allow(clippy::manual_async_fn)]
182 fn auth_sign(
183 &mut self,
184 key: &AgentIdentity,
185 hash_alg: Option<HashAlg>,
186 to_sign: Vec<u8>,
187 ) -> impl Future<Output = Result<Vec<u8>, Self::Error>> {
188 async move {
189 self.sign_request(key, hash_alg, to_sign)
190 .await
191 .map_err(Into::into)
192 }
193 }
194}
195
196#[derive(Debug)]
197#[allow(clippy::large_enum_variant)]
198pub enum Method {
199 None,
200 Password {
201 password: String,
202 },
203 PublicKey {
204 key: PrivateKeyWithHashAlg,
205 },
206 OpenSshCertificate {
207 key: Arc<PrivateKey>,
208 cert: Certificate,
209 },
210 FuturePublicKey {
211 key: ssh_key::PublicKey,
212 hash_alg: Option<HashAlg>,
213 },
214 FutureCertificate {
217 cert: Certificate,
218 hash_alg: Option<HashAlg>,
219 },
220 KeyboardInteractive {
221 submethods: String,
222 },
223 }
225
226#[doc(hidden)]
227#[derive(Debug)]
228pub struct AuthRequest {
229 initial_methods: MethodSet,
230 pub methods: MethodSet,
231 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
232 pub partial_success: bool,
233 pub current: Option<CurrentRequest>,
234 pub(crate) principal: Option<AuthPrincipal>,
235 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
236 pub rejection_count: usize,
237}
238
239#[doc(hidden)]
240#[derive(Debug)]
241pub(crate) struct AuthPrincipal {
242 user: String,
243 service: String,
244}
245
246#[doc(hidden)]
247#[derive(Debug)]
248pub enum CurrentRequest {
249 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
250 PublicKey {
251 #[allow(dead_code)]
252 key: Vec<u8>,
253 #[allow(dead_code)]
254 algo: Vec<u8>,
255 sent_pk_ok: bool,
256 },
257 KeyboardInteractive {
258 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
259 submethods: String,
260 },
261}
262
263impl AuthRequest {
264 pub(crate) fn server(methods: MethodSet) -> Self {
265 Self {
266 initial_methods: methods.clone(),
267 methods,
268 partial_success: false,
269 current: None,
270 principal: None,
271 rejection_count: 0,
272 }
273 }
274
275 pub(crate) fn new(method: &Method) -> Self {
276 match method {
277 Method::KeyboardInteractive { submethods } => Self {
278 initial_methods: MethodSet::all(),
279 methods: MethodSet::all(),
280 partial_success: false,
281 current: Some(CurrentRequest::KeyboardInteractive {
282 submethods: submethods.to_string(),
283 }),
284 principal: None,
285 rejection_count: 0,
286 },
287 _ => Self {
288 initial_methods: MethodSet::all(),
289 methods: MethodSet::all(),
290 partial_success: false,
291 current: None,
292 principal: None,
293 rejection_count: 0,
294 },
295 }
296 }
297
298 pub(crate) fn bind_or_reset_principal(&mut self, user: &str, service: &str) -> bool {
299 match &self.principal {
300 Some(bound) if bound.user == user && bound.service == service => false,
301 _ => {
302 self.principal = Some(AuthPrincipal {
303 user: user.to_owned(),
304 service: service.to_owned(),
305 });
306 self.methods = self.initial_methods.clone();
307 self.partial_success = false;
308 self.current = None;
309 true
310 }
311 }
312 }
313}