1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#[allow(unused_imports)]
use {
crate::error::{Error, Result, TrapBug},
log::{debug, error, info, log, trace, warn},
};
use crate::sshnames::*;
use crate::*;
use event::ServEventId;
use kex::SessId;
use packets::{AuthMethod, Packet, Userauth60, UserauthPkOk, UserauthRequest};
use traffic::TrafSend;
use heapless::Vec;
/// Server authentication context
///
/// `methods_` can be during runtime, though if they
/// are changed after the auth process starts it's unknown
/// if client implementations will handle varying auth methods correctly.
#[derive(Debug)]
pub(crate) struct ServAuth {
pub authed: bool,
/// Used so that `AuthFirst` is only presented once to the application.
tried_first: bool,
/// Username previously used, as an array of bytes
pub username: Option<Vec<u8, { config::MAX_USERNAME }>>,
/// Whether to advertise password authentication and present it to the application
///
/// Enabled by default
pub method_password: bool,
/// Whether to advertise pubkey authentication and present it to the application.
///
/// Enabled by default
pub method_pubkey: bool,
}
impl Default for ServAuth {
fn default() -> Self {
Self {
authed: false,
tried_first: false,
username: None,
method_password: true,
method_pubkey: true,
}
}
}
impl ServAuth {
/// Configure which authentication methods are allowed
pub fn set_auth_methods(&mut self, password: bool, pubkey: bool) {
self.method_password = password;
self.method_pubkey = pubkey;
}
/// Returns an event for the app, or `DispatchEvent::None` if auth failure
/// has been returned immediately.
pub fn request(
&mut self,
sess_id: &SessId,
s: &mut TrafSend,
p: packets::UserauthRequest,
) -> Result<DispatchEvent> {
// TODO: what to do if they've already authed? we have to be careful in case
// app event handlers don't expect it?
if let Some(prev) = &self.username {
// Compare with an existing username
if prev != p.username.0 {
warn!("Client tried varying usernames");
return error::SSHProtoUnsupported.fail();
}
} else {
// Set new username and query app for auth methods
match Vec::from_slice(p.username.0) {
Result::Ok(u) => self.username = Some(u),
Result::Err(_) => {
warn!("Client tried too long username, {}", p.username.0.len());
return error::SSHProtoUnsupported.fail();
}
}
}
debug_assert!(self.username.is_some());
if self.authed {
trace!("Success after already authed");
s.send(packets::UserauthSuccess {})?;
return Ok(DispatchEvent::None);
}
let ev = match p.method {
AuthMethod::Password(_) if self.method_password => {
DispatchEvent::ServEvent(ServEventId::PasswordAuth)
}
AuthMethod::PubKey(_) if self.method_pubkey => {
self.request_pubkey(p, sess_id)?
}
_ => {
if !self.tried_first {
DispatchEvent::ServEvent(ServEventId::FirstAuth)
} else {
DispatchEvent::None
}
}
};
// FirstAuth would have been returned by now.
self.tried_first = true;
// Auth method isn't supported, send failure straight away.
// No concerns about timing leaks since it is independent of the username.
if ev.is_none() {
self.send_failure(s)?;
}
Ok(ev)
}
fn send_failure(&self, s: &mut TrafSend) -> Result<()> {
let methods = self.avail_methods();
let methods = (&methods).into();
s.send(packets::UserauthFailure { methods, partial: false })
}
fn request_pubkey(
&mut self,
mut p: packets::UserauthRequest,
sess_id: &SessId,
) -> Result<DispatchEvent> {
// Extract the signature separately. The message for the signature
// includes the auth packet without the signature part.
let (key, sig) = match &mut p.method {
AuthMethod::PubKey(m) => {
let sig = m.sig.take();
// When we have a signature, we need to set force_sig=true so that the encoded message for verification has the boolean set correctly
m.force_sig = sig.is_some();
(&m.pubkey.0, sig)
}
_ => return Err(Error::bug()),
};
if let PubKey::Unknown(u) = key {
debug!("Unknown pubkey type {u}");
return Ok(DispatchEvent::None);
}
if let Some(ref sig) = sig {
// Real signature, validate it.
if !self.verify_sig(&p, &sig.0, sess_id) {
// Auth failure. OK to return early here since
// this doesn't rely on any particular username, no concerns
// about timing leaks.
return Ok(DispatchEvent::None);
}
}
// Proceed to query the app whether login is allowed
let real_sig = sig.is_some();
Ok(DispatchEvent::ServEvent(ServEventId::PubkeyAuth { real_sig }))
}
pub fn resume_request(&mut self, allow: bool, s: &mut TrafSend) -> Result<()> {
if allow {
self.authed = true;
s.send(packets::UserauthSuccess {})
} else {
self.send_failure(s)
}
}
pub fn resume_pkok(&self, p: Packet, s: &mut TrafSend) -> Result<()> {
if let Packet::UserauthRequest(UserauthRequest {
method: AuthMethod::PubKey(m),
..
}) = p
{
s.send(Userauth60::PkOk(UserauthPkOk {
algo: m.sig_algo,
key: m.pubkey,
}))
} else {
Err(Error::bug())
}
}
/// Must be passed a MethodPubkey packet with a signature part None
fn verify_sig(
&self,
p: &packets::UserauthRequest,
sig: &Signature,
sess_id: &SessId,
) -> bool {
// Remove the signature from the packet - the signature message includes
// packet without that signature part.
let sig_type = match sig.sig_type() {
Ok(t) => t,
Err(_) => return false,
};
let key = match &p.method {
AuthMethod::PubKey(m) => &m.pubkey.0,
_ => {
debug_assert!(false, "Wrong method");
return false;
}
};
let msg = auth::AuthSigMsg::new(p.clone(), sess_id);
match sig_type.verify(key, &msg, sig) {
Ok(()) => true,
Err(e) => {
trace!("sig failed {e}");
false
}
}
}
fn avail_methods(&self) -> namelist::LocalNames {
let mut l = namelist::LocalNames::new();
// OK unwrap: buf is large enough
if self.method_password {
l.0.push(SSH_AUTHMETHOD_PASSWORD).unwrap()
}
if self.method_pubkey {
l.0.push(SSH_AUTHMETHOD_PUBLICKEY).unwrap()
}
l
}
}