use super::security_layer::{SecurityLayer, decode_offer_layers};
use super::{GssError, GssInitiator, InitStep};
#[derive(Debug)]
pub enum ClientStep {
Token(Vec<u8>),
Done,
}
#[derive(Debug, thiserror::Error)]
pub enum ClientExchangeError {
#[error(transparent)]
Gss(#[from] GssError),
#[error(transparent)]
Layer(#[from] super::security_layer::LayerError),
#[error("server offered no supported security layer")]
NoCommonLayer,
}
enum State {
Establishing,
AwaitingOffer,
Done,
}
pub struct GssapiClientExchange {
initiator: Box<dyn GssInitiator>,
state: State,
max_recv_size: u32,
authzid: Option<String>,
}
impl GssapiClientExchange {
#[must_use]
pub fn new(
initiator: Box<dyn GssInitiator>,
max_recv_size: u32,
authzid: Option<String>,
) -> Self {
Self {
initiator,
state: State::Establishing,
max_recv_size,
authzid,
}
}
pub fn step(&mut self, server_token: Option<&[u8]>) -> Result<ClientStep, ClientExchangeError> {
match self.state {
State::Establishing => match self.initiator.step(server_token)? {
InitStep::Continue(t) => Ok(ClientStep::Token(t)),
InitStep::Established(t) => {
self.state = State::AwaitingOffer;
Ok(ClientStep::Token(t.unwrap_or_default()))
}
},
State::AwaitingOffer => {
let token = server_token.ok_or(ClientExchangeError::NoCommonLayer)?;
let offered = decode_offer_layers(&self.initiator.unwrap(token)?)?;
if offered.0 & SecurityLayer::AUTH.0 == 0 {
return Err(ClientExchangeError::NoCommonLayer);
}
let s = self.max_recv_size.to_be_bytes();
let mut reply = vec![SecurityLayer::AUTH.0, s[1], s[2], s[3]];
if let Some(z) = &self.authzid {
reply.extend_from_slice(z.as_bytes());
}
let wrapped = self.initiator.wrap(&reply, false)?;
self.state = State::Done;
Ok(ClientStep::Token(wrapped))
}
State::Done => Ok(ClientStep::Done),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gssapi::{GssError, GssInitiator, InitStep};
use assert2::assert;
struct FakeInitiator {
done: bool,
}
impl GssInitiator for FakeInitiator {
fn step(&mut self, _server_token: Option<&[u8]>) -> Result<InitStep, GssError> {
if self.done {
Ok(InitStep::Established(None))
} else {
self.done = true;
Ok(InitStep::Continue(b"AP-REQ".to_vec()))
}
}
fn wrap(&self, p: &[u8], _c: bool) -> Result<Vec<u8>, GssError> {
Ok(p.to_vec())
}
fn unwrap(&self, t: &[u8]) -> Result<Vec<u8>, GssError> {
Ok(t.to_vec())
}
}
#[test]
fn produces_first_token_then_replies_to_offer() {
let mut ex =
GssapiClientExchange::new(Box::new(FakeInitiator { done: false }), 0x1_0000, None);
let first = ex.step(None).unwrap();
assert!(matches!(first, ClientStep::Token(_)));
let _ = ex.step(Some(b"AP-REP")).unwrap();
let offer = vec![0x01u8, 0x00, 0x10, 0x00];
let reply = match ex.step(Some(&offer)).unwrap() {
ClientStep::Token(t) => t,
ClientStep::Done => panic!("expected reply token"),
};
assert!(reply[0] == 0x01);
}
}