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::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#[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 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 remaining_methods: MethodSet,
144 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 }
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}