Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 101 additions & 19 deletions russh/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub enum MethodKind {
PublicKey,
HostBased,
KeyboardInteractive,
GssapiWithMic,
}

impl From<&MethodKind> for &'static str {
Expand All @@ -43,6 +44,7 @@ impl From<&MethodKind> for &'static str {
MethodKind::PublicKey => "publickey",
MethodKind::HostBased => "hostbased",
MethodKind::KeyboardInteractive => "keyboard-interactive",
MethodKind::GssapiWithMic => "gssapi-with-mic",
}
}
}
Expand All @@ -55,6 +57,7 @@ impl FromStr for MethodKind {
"publickey" => Ok(MethodKind::PublicKey),
"hostbased" => Ok(MethodKind::HostBased),
"keyboard-interactive" => Ok(MethodKind::KeyboardInteractive),
"gssapi-with-mic" => Ok(MethodKind::GssapiWithMic),
_ => Err(()),
}
}
Expand Down Expand Up @@ -112,7 +115,18 @@ impl MethodSet {
Self(Vec::new())
}

pub fn all() -> Self {
pub fn client_supported() -> Self {
Self(vec![
MethodKind::None,
MethodKind::Password,
MethodKind::PublicKey,
MethodKind::HostBased,
MethodKind::KeyboardInteractive,
MethodKind::GssapiWithMic,
])
}

pub fn server_supported() -> Self {
Self(vec![
MethodKind::None,
MethodKind::Password,
Expand Down Expand Up @@ -164,6 +178,73 @@ pub trait Signer: Sized {
) -> impl Future<Output = Result<Vec<u8>, Self::Error>> + Send;
}

/// One step of a GSSAPI security context exchange, as produced by a
/// [`GssapiAuthenticator`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GssapiStep {
/// The context is not established yet; send `token` to the server and
/// wait for its next token.
Continue {
token: Vec<u8>,
},
/// The context is established. `token` is the final output token, if any.
/// `mic` is the MIC computed over the `mic_data` passed to
/// [`GssapiAuthenticator::gssapi_step`]. Implementations MUST produce a
/// MIC whenever the established context supports integrity protection
/// (RFC 4462, Section 3.5); `None` falls back to
/// `SSH_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE`.
Complete {
token: Option<Vec<u8>>,
mic: Option<Vec<u8>>,
},
}

/// A GSS-API error reported by the server during `gssapi-with-mic`
/// authentication. Informational: the server follows up with an
/// authentication failure.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GssapiError {
/// `SSH_MSG_USERAUTH_GSSAPI_ERROR` (RFC 4462, Section 3.8).
Status {
major_status: u32,
minor_status: u32,
message: String,
},
/// `SSH_MSG_USERAUTH_GSSAPI_ERRTOK` (RFC 4462, Section 3.10). May be
/// passed to `GSS_Init_sec_context()` to obtain mechanism-specific
/// error details.
ErrorToken(Vec<u8>),
}

#[cfg_attr(feature = "async-trait", async_trait::async_trait)]
pub trait GssapiAuthenticator: Sized {
type Error: From<crate::SendError>;

/// Advance the GSSAPI security context.
///
/// `selected_mechanism` is `Some` on the first step and carries the
/// DER-encoded OID of the mechanism the server selected; implementations
/// must verify it is one of the mechanisms they offered (RFC 4462,
/// Section 3.3). It is `None` on subsequent steps.
///
/// `input_token` is the token received from the server, if any.
/// `mic_data` is the data to compute the final MIC over once the context
/// is established.
fn gssapi_step(
&mut self,
selected_mechanism: Option<Vec<u8>>,
input_token: Option<Vec<u8>>,
mic_data: Vec<u8>,
) -> impl Future<Output = Result<GssapiStep, Self::Error>> + Send;

/// Called when the server reports a GSS-API error; the server follows up
/// with an authentication failure. The default implementation ignores
/// the error.
fn gssapi_error(&mut self, _error: GssapiError) -> impl Future<Output = ()> + Send {
async {}
}
}

#[derive(Debug, Error)]
pub enum AgentAuthError {
#[error(transparent)]
Expand Down Expand Up @@ -220,6 +301,9 @@ pub enum Method {
KeyboardInteractive {
submethods: String,
},
GssapiWithMic {
mechanism_oids: Vec<Vec<u8>>,
},
// Hostbased,
}

Expand Down Expand Up @@ -258,6 +342,7 @@ pub enum CurrentRequest {
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
submethods: String,
},
GssapiWithMic,
}

impl AuthRequest {
Expand All @@ -273,25 +358,22 @@ impl AuthRequest {
}

pub(crate) fn new(method: &Method) -> Self {
match method {
Method::KeyboardInteractive { submethods } => Self {
initial_methods: MethodSet::all(),
methods: MethodSet::all(),
partial_success: false,
current: Some(CurrentRequest::KeyboardInteractive {
let current = match method {
Method::KeyboardInteractive { submethods } => {
Some(CurrentRequest::KeyboardInteractive {
submethods: submethods.to_string(),
}),
principal: None,
rejection_count: 0,
},
_ => Self {
initial_methods: MethodSet::all(),
methods: MethodSet::all(),
partial_success: false,
current: None,
principal: None,
rejection_count: 0,
},
})
}
Method::GssapiWithMic { .. } => Some(CurrentRequest::GssapiWithMic),
_ => None,
};
Self {
initial_methods: MethodSet::client_supported(),
methods: MethodSet::client_supported(),
partial_success: false,
current,
principal: None,
rejection_count: 0,
}
}

Expand Down
Loading
Loading