use crate::context::EmptyProvider;
use crate::error::SessionError;
use crate::mechanism::{Authentication, MechanismData, State};
use crate::mechanisms::gssapi::properties::{Error, GssSecurityLayer, SecurityLayer};
use crate::prelude::State::Finished;
use crate::session::MessageSent;
use alloc::mem;
use core::fmt;
use crate::io::Write;
use libgssapi::context::{CtxFlags, SecurityContext, ServerCtx};
use libgssapi::credential::{Cred, CredUsage};
use libgssapi::oid::{OidSet, GSS_MECH_KRB5};
#[derive(Debug, Default)]
pub struct Gssapi {
state: GssapiState,
}
#[derive(Default)]
enum GssapiState {
#[default]
Initial,
Pending(ServerCtx),
Installed(ServerCtx, SecurityLayer),
Final(ServerCtx, SecurityLayer),
Done(Option<(ServerCtx, bool)>),
Errored,
}
impl fmt::Debug for GssapiState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Initial => f.write_str("Initial"),
Self::Pending(..) => f.write_str("Pending"),
Self::Installed(..) => f.write_str("Installed"),
Self::Final(..) => f.write_str("Final"),
Self::Done(Some((_, true))) => f.write_str("Done<Confidentiality>"),
Self::Done(Some((_, false))) => f.write_str("Done<Integrity>"),
Self::Done(None) => f.write_str("Done<NoSecurity>"),
Self::Errored => f.write_str("Errored"),
}
}
}
impl Authentication for Gssapi {
fn step(
&mut self,
session: &mut MechanismData,
input: Option<&[u8]>,
writer: &mut dyn Write,
) -> Result<State, SessionError> {
match mem::replace(&mut self.state, GssapiState::Errored) {
GssapiState::Initial => {
let mut krb5 = OidSet::new().map_err(Error::Gss)?;
krb5.add(&GSS_MECH_KRB5).map_err(Error::Gss)?;
let cred = Cred::acquire(None, None, CredUsage::Accept, Some(&krb5))
.map_err(Error::Gss)?;
let ctx = ServerCtx::new(cred);
self.state = GssapiState::Pending(ctx);
self.step(session, input, writer)
}
GssapiState::Pending(mut ctx) => {
let input = input.ok_or(SessionError::InputDataRequired)?;
let token = ctx.step(input).map_err(Error::Gss)?;
if ctx.is_complete() {
let mut acceptable = session
.maybe_need_with::<GssSecurityLayer, _, _>(&EmptyProvider, |acceptable| {
Ok(*acceptable)
})?
.unwrap_or_default();
let ctx_flags = ctx.flags().map_err(Error::Gss)?;
if !ctx_flags.contains(CtxFlags::GSS_C_MUTUAL_FLAG | CtxFlags::GSS_C_CONF_FLAG)
{
acceptable.set(SecurityLayer::CONFIDENTIALITY, false);
}
if !ctx_flags.contains(CtxFlags::GSS_C_INTEG_FLAG) {
acceptable.set(SecurityLayer::INTEGRITY, false);
}
if acceptable.is_empty() {
return Err(Error::BadContext.into());
}
self.state = GssapiState::Installed(ctx, acceptable);
}
if let Some(token) = token {
writer.write_all(&token)?;
Ok(State::Running)
} else {
self.step(session, None, writer)
}
}
GssapiState::Installed(mut ctx, supported) => {
let out_bytes = if supported
.intersects(SecurityLayer::CONFIDENTIALITY | SecurityLayer::INTEGRITY)
{
[supported.bits(), 0xFF, 0xFF, 0xFF]
} else {
[supported.bits(), 0x00, 0x00, 0x00]
};
let wrapped = ctx.wrap(false, &out_bytes).map_err(Error::Gss)?;
writer.write_all(&wrapped)?;
self.state = GssapiState::Final(ctx, supported);
Ok(State::Running)
}
GssapiState::Final(mut ctx, supported) => {
let input = input.ok_or(SessionError::InputDataRequired)?;
let unwrapped = ctx.unwrap(input).map_err(Error::Gss)?;
if unwrapped.len() != 4 {
Err(Error::BadFinalToken)?;
}
let selected =
SecurityLayer::from_bits(unwrapped[0]).ok_or(Error::BadFinalToken)?;
if !selected.intersects(supported) {
Err(Error::BadFinalToken)?;
}
let wrap_state = if selected.contains(SecurityLayer::CONFIDENTIALITY) {
Some((ctx, true))
} else if selected.contains(SecurityLayer::INTEGRITY) {
Some((ctx, false))
} else {
None
};
self.state = GssapiState::Done(wrap_state);
Ok(Finished(MessageSent::No))
}
_ => Err(SessionError::MechanismDone),
}
}
fn has_security_layer(&self) -> bool {
matches!(self.state, GssapiState::Done(Some(_)))
}
fn encode(&mut self, input: &[u8], writer: &mut dyn Write) -> Result<usize, SessionError> {
match self.state {
GssapiState::Done(Some((ref mut ctx, encrypt))) => {
let wrapped = ctx.wrap(encrypt, input).map_err(Error::Gss)?;
writer.write_all(&wrapped)?;
Ok(input.len())
}
_ => Err(SessionError::NoSecurityLayer),
}
}
fn decode(&mut self, input: &[u8], writer: &mut dyn Write) -> Result<usize, SessionError> {
match self.state {
GssapiState::Done(Some((ref mut ctx, _))) => {
let unwrapped = ctx.unwrap(input).map_err(Error::Gss)?;
writer.write_all(&unwrapped)?;
Ok(input.len())
}
_ => Err(SessionError::NoSecurityLayer),
}
}
}