Skip to content

Commit 633f639

Browse files
committed
fixup
1 parent 0d1d073 commit 633f639

5 files changed

Lines changed: 116 additions & 44 deletions

File tree

russh/src/auth.rs

Lines changed: 70 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,16 @@ impl MethodSet {
126126
])
127127
}
128128

129+
pub(crate) fn server_supported() -> Self {
130+
Self(vec![
131+
MethodKind::None,
132+
MethodKind::Password,
133+
MethodKind::PublicKey,
134+
MethodKind::HostBased,
135+
MethodKind::KeyboardInteractive,
136+
])
137+
}
138+
129139
pub fn remove(&mut self, method: MethodKind) {
130140
self.0.retain(|x| *x != method);
131141
}
@@ -168,27 +178,71 @@ pub trait Signer: Sized {
168178
) -> impl Future<Output = Result<Vec<u8>, Self::Error>> + Send;
169179
}
170180

181+
/// One step of a GSSAPI security context exchange, as produced by a
182+
/// [`GssapiAuthenticator`].
171183
#[derive(Debug, Clone, PartialEq, Eq)]
172184
pub enum GssapiStep {
185+
/// The context is not established yet; send `token` to the server and
186+
/// wait for its next token.
173187
Continue {
174188
token: Vec<u8>,
175189
},
190+
/// The context is established. `token` is the final output token, if any.
191+
/// `mic` is the MIC computed over the `mic_data` passed to
192+
/// [`GssapiAuthenticator::gssapi_step`]. Implementations MUST produce a
193+
/// MIC whenever the established context supports integrity protection
194+
/// (RFC 4462, Section 3.5); `None` falls back to
195+
/// `SSH_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE`.
176196
Complete {
177197
token: Option<Vec<u8>>,
178198
mic: Option<Vec<u8>>,
179199
},
180200
}
181201

202+
/// A GSS-API error reported by the server during `gssapi-with-mic`
203+
/// authentication. Informational: the server follows up with an
204+
/// authentication failure.
205+
#[derive(Debug, Clone, PartialEq, Eq)]
206+
pub enum GssapiError {
207+
/// `SSH_MSG_USERAUTH_GSSAPI_ERROR` (RFC 4462, Section 3.8).
208+
Status {
209+
major_status: u32,
210+
minor_status: u32,
211+
message: String,
212+
},
213+
/// `SSH_MSG_USERAUTH_GSSAPI_ERRTOK` (RFC 4462, Section 3.10). May be
214+
/// passed to `GSS_Init_sec_context()` to obtain mechanism-specific
215+
/// error details.
216+
ErrorToken(Vec<u8>),
217+
}
218+
182219
#[cfg_attr(feature = "async-trait", async_trait::async_trait)]
183220
pub trait GssapiAuthenticator: Sized {
184221
type Error: From<crate::SendError>;
185222

223+
/// Advance the GSSAPI security context.
224+
///
225+
/// `selected_mechanism` is `Some` on the first step and carries the
226+
/// DER-encoded OID of the mechanism the server selected; implementations
227+
/// must verify it is one of the mechanisms they offered (RFC 4462,
228+
/// Section 3.3). It is `None` on subsequent steps.
229+
///
230+
/// `input_token` is the token received from the server, if any.
231+
/// `mic_data` is the data to compute the final MIC over once the context
232+
/// is established.
186233
fn gssapi_step(
187234
&mut self,
188-
selected_mechanism: Vec<u8>,
235+
selected_mechanism: Option<Vec<u8>>,
189236
input_token: Option<Vec<u8>>,
190237
mic_data: Vec<u8>,
191238
) -> impl Future<Output = Result<GssapiStep, Self::Error>> + Send;
239+
240+
/// Called when the server reports a GSS-API error; the server follows up
241+
/// with an authentication failure. The default implementation ignores
242+
/// the error.
243+
fn gssapi_error(&mut self, _error: GssapiError) -> impl Future<Output = ()> + Send {
244+
async {}
245+
}
192246
}
193247

194248
#[derive(Debug, Error)]
@@ -304,33 +358,22 @@ impl AuthRequest {
304358
}
305359

306360
pub(crate) fn new(method: &Method) -> Self {
307-
match method {
308-
Method::KeyboardInteractive { submethods } => Self {
309-
initial_methods: MethodSet::all(),
310-
methods: MethodSet::all(),
311-
partial_success: false,
312-
current: Some(CurrentRequest::KeyboardInteractive {
361+
let current = match method {
362+
Method::KeyboardInteractive { submethods } => {
363+
Some(CurrentRequest::KeyboardInteractive {
313364
submethods: submethods.to_string(),
314-
}),
315-
principal: None,
316-
rejection_count: 0,
317-
},
318-
Method::GssapiWithMic { .. } => Self {
319-
initial_methods: MethodSet::all(),
320-
methods: MethodSet::all(),
321-
partial_success: false,
322-
current: Some(CurrentRequest::GssapiWithMic),
323-
principal: None,
324-
rejection_count: 0,
325-
},
326-
_ => Self {
327-
initial_methods: MethodSet::all(),
328-
methods: MethodSet::all(),
329-
partial_success: false,
330-
current: None,
331-
principal: None,
332-
rejection_count: 0,
333-
},
365+
})
366+
}
367+
Method::GssapiWithMic { .. } => Some(CurrentRequest::GssapiWithMic),
368+
_ => None,
369+
};
370+
Self {
371+
initial_methods: MethodSet::all(),
372+
methods: MethodSet::all(),
373+
partial_success: false,
374+
current,
375+
principal: None,
376+
rejection_count: 0,
334377
}
335378
}
336379

russh/src/client/encrypted.rs

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -342,9 +342,18 @@ impl Session {
342342
let message = map_err!(String::decode(&mut r))?;
343343
let _language_tag = map_err!(String::decode(&mut r))?;
344344
map_err!(ensure_end(&r))?;
345-
debug!(
345+
warn!(
346346
"userauth_gssapi_error major={major_status} minor={minor_status}: {message}"
347347
);
348+
self.sender
349+
.send(Reply::AuthGssapiError {
350+
error: auth::GssapiError::Status {
351+
major_status,
352+
minor_status,
353+
message,
354+
},
355+
})
356+
.map_err(|_| crate::Error::SendError)?;
348357
return Ok(());
349358
}
350359
return Err(crate::Error::Inconsistent.into());
@@ -353,9 +362,14 @@ impl Session {
353362
if let Some(auth::CurrentRequest::GssapiWithMic) =
354363
auth_request.current
355364
{
356-
let _token = map_err!(Bytes::decode(&mut r))?;
365+
let token = map_err!(Bytes::decode(&mut r))?.to_vec();
357366
map_err!(ensure_end(&r))?;
358-
debug!("userauth_gssapi_errtok");
367+
warn!("userauth_gssapi_errtok ({} bytes)", token.len());
368+
self.sender
369+
.send(Reply::AuthGssapiError {
370+
error: auth::GssapiError::ErrorToken(token),
371+
})
372+
.map_err(|_| crate::Error::SendError)?;
359373
return Ok(());
360374
}
361375
return Err(crate::Error::Inconsistent.into());
@@ -1010,7 +1024,7 @@ mod tests {
10101024
use crate::compression::{Compression, Decompress};
10111025
use crate::kex::{KEXES, NONE};
10121026
use crate::session::Exchange;
1013-
use crate::{CryptoVec, MethodKind};
1027+
use crate::{CryptoVec, MethodKind, MethodSet};
10141028

10151029
fn test_encrypted() -> Encrypted {
10161030
Encrypted {
@@ -1150,6 +1164,19 @@ mod tests {
11501164
);
11511165
ensure_end(&complete).unwrap();
11521166
}
1167+
1168+
#[test]
1169+
fn client_send_gssapi_mic_skips_empty_final_token() {
1170+
let mut encrypted = test_encrypted();
1171+
encrypted.client_send_gssapi_mic(Some(b""), b"mic").unwrap();
1172+
1173+
let payloads = payloads(&encrypted.write);
1174+
assert_eq!(payloads.len(), 1);
1175+
let mut mic = payloads[0];
1176+
assert_eq!(u8::decode(&mut mic).unwrap(), msg::USERAUTH_GSSAPI_MIC);
1177+
assert_eq!(Vec::<u8>::decode(&mut mic).unwrap(), b"mic".to_vec());
1178+
ensure_end(&mic).unwrap();
1179+
}
11531180
}
11541181

11551182
impl Encrypted {
@@ -1242,8 +1269,7 @@ impl Encrypted {
12421269
user.as_bytes().encode(&mut self.write)?;
12431270
"ssh-connection".encode(&mut self.write)?;
12441271
"gssapi-with-mic".encode(&mut self.write)?;
1245-
(mechanism_oids.len().try_into().unwrap_or(0) as u32)
1246-
.encode(&mut self.write)?;
1272+
(mechanism_oids.len() as u32).encode(&mut self.write)?;
12471273
for oid in mechanism_oids {
12481274
oid.as_slice().encode(&mut self.write)?;
12491275
}
@@ -1280,9 +1306,7 @@ impl Encrypted {
12801306
token: Option<&[u8]>,
12811307
mic: &[u8],
12821308
) -> Result<(), crate::Error> {
1283-
if let Some(token) = token
1284-
&& !token.is_empty()
1285-
{
1309+
if let Some(token) = token.filter(|t| !t.is_empty()) {
12861310
self.client_send_gssapi_token(token)?;
12871311
}
12881312
push_packet!(self.write, {
@@ -1296,9 +1320,7 @@ impl Encrypted {
12961320
&mut self,
12971321
token: Option<&[u8]>,
12981322
) -> Result<(), crate::Error> {
1299-
if let Some(token) = token
1300-
&& !token.is_empty()
1301-
{
1323+
if let Some(token) = token.filter(|t| !t.is_empty()) {
13021324
self.client_send_gssapi_token(token)?;
13031325
}
13041326
push_packet!(self.write, {

russh/src/client/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ enum Reply {
138138
token: Vec<u8>,
139139
mic_data: Vec<u8>,
140140
},
141+
AuthGssapiError {
142+
error: auth::GssapiError,
143+
},
141144
}
142145

143146
#[derive(Debug)]
@@ -579,16 +582,19 @@ impl<H: Handler> Handle<H> {
579582
mic_data,
580583
}) => {
581584
let step = authenticator
582-
.gssapi_step(selected_mechanism, None, mic_data)
585+
.gssapi_step(Some(selected_mechanism), None, mic_data)
583586
.await?;
584587
self.send_gssapi_step(step).await?;
585588
}
586589
Some(Reply::AuthGssapiToken { token, mic_data }) => {
587590
let step = authenticator
588-
.gssapi_step(Vec::new(), Some(token), mic_data)
591+
.gssapi_step(None, Some(token), mic_data)
589592
.await?;
590593
self.send_gssapi_step(step).await?;
591594
}
595+
Some(Reply::AuthGssapiError { error }) => {
596+
authenticator.gssapi_error(error).await;
597+
}
592598
None => {
593599
return Ok(AuthResult::Failure {
594600
remaining_methods: MethodSet::empty(),

russh/src/lib_inner.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ mod ssh_read;
3232
mod sshbuffer;
3333

3434
pub use negotiation::{Names, Preferred};
35-
pub use auth::{GssapiAuthenticator, GssapiStep};
3635

3736
mod pty;
3837

@@ -292,7 +291,9 @@ impl Default for Limits {
292291
}
293292
}
294293

295-
pub use auth::{AgentAuthError, MethodKind, MethodSet, Signer};
294+
pub use auth::{
295+
AgentAuthError, GssapiAuthenticator, GssapiError, GssapiStep, MethodKind, MethodSet, Signer,
296+
};
296297

297298
/// A reason for disconnection.
298299
#[allow(missing_docs)] // This should be relatively self-explanatory.

russh/src/server/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ impl Default for Config {
108108
"_",
109109
env!("CARGO_PKG_VERSION")
110110
))),
111-
methods: auth::MethodSet::all(),
111+
methods: auth::MethodSet::server_supported(),
112112
auth_rejection_time: std::time::Duration::from_secs(1),
113113
auth_rejection_time_initial: None,
114114
keys: Vec::new(),

0 commit comments

Comments
 (0)