cross-krb5 0.1.1

Safe cross platform Kerberos v5 interface
Documentation
//! Cross platform Kerberos 5
//! 
//! cross-krb5 is a single API for basic Kerberos 5 services on Windows, Mac OS, 
//! linux, and Unix like OSes. It provides most of what gssapi and sspi provide for
//! kerberos 5 mechanisms, namely mutual authentication, integrity, and encryption.
//! 
//! As well as providing a uniform API, services using cross-krb5 should interoperate
//! across all the supported OSes transparantly, and should interoperate with other
//! services on all platforms assuming they are not platform specific.
//! 
//! # Example
//! ```no_run
//! 
//! ```

use anyhow::Result;
use bytes::BytesMut;
use std::{ops::Deref, time::Duration};

pub trait K5Ctx {
    /// The type of buffer that will be returned by the context
    type Buf: Deref<Target = [u8]> + Send + Sync;

    /// perform 1 step of initialization. `token` is the token you received from the other 
    /// side, or `None` if you didn't receive one. If initialization is finished then `step` 
    /// will return `Ok(None)`, and at that point the context is ready to use. 
    /// If `step` returns `Ok(Some(buf))` then initialization isn't finished and you are 
    /// expected to send the contents of that buffer to the other side in order to finish it.
    /// This may go on for several rounds of back and forth.
    fn step(&self, token: Option<&[u8]>) -> Result<Option<Self::Buf>>;

    /// Wrap the specified message for sending to the other side. If `encrypt` 
    /// is true then the contents will be encrypted. Even if `encrypt` is false
    /// the integrity of the contents are protected, if the message is altered in
    /// transit the other side will know.
    fn wrap(&self, encrypt: bool, msg: &[u8]) -> Result<Self::Buf>;

    /// Wrap data in place using the underlying wrap_iov facility. If `encrypt` is true
    /// then the contents of `data` will be encrypted in place. `header`, `padding`, and `trailer`
    /// may be references to empty newly created `BytesMut` structures, they will be resized as needed,
    /// and can be reused for subsuquent calls in order to avoid allocation. In order to send the message
    /// produced by `wrap_iov` you should send in order the header, data, padding, and trailer.
    /// # Examples
    /// ```no_run
    /// use bytes::{BytesMut, Buf};
    /// # let ctx = unsafe { std::mem::transmute::<(), ClientCtx>(()); }
    /// let mut header = BytesMut::new();
    /// let mut data = BytesMut::from(b"hello world");
    /// let mut padding = BytesMut::new();
    /// let mut trailer = BytesMut::new();
    /// 
    /// ctx.wrap_iov(true, &mut header, &mut data, &mut padding, &mut trailer)
    ///     .expect("failed to encrypt");
    /// // use the bytes api to chain together the token without any allocation or copying
    /// let mut buf = header.split().chain(data.chain(padding.split().chain(trailer.split())));
    /// // then use your prefered `writev` implementation. tokio `write_buf` is quite convenient
    /// ```
    fn wrap_iov(
        &self,
        encrypt: bool,
        header: &mut BytesMut,
        data: &mut BytesMut,
        padding: &mut BytesMut,
        trailer: &mut BytesMut,
    ) -> Result<()>;
    
    /// Unwrap the specified message returning it's decrypted and verified contents
    fn unwrap(&self, msg: &[u8]) -> Result<Self::Buf>;

    /// Unwrap in place the message at the beginning of the specified `BytesMut` and then split it off 
    /// and return it. This won't copy or allocate, it just looks that way because the bytes crate is awesome.
    fn unwrap_iov(&self, len: usize, msg: &mut BytesMut) -> Result<BytesMut>;

    /// Return the remaining time this session has to live
    fn ttl(&self) -> Result<Duration>;
}

pub trait K5ServerCtx: K5Ctx {
    /// Return the user principal name of the client context associated with this server context.
    fn client(&self) -> Result<String>;
}

#[cfg(unix)]
mod unix;

#[cfg(unix)]
use crate::unix::{ClientCtx as ClientCtxImpl, ServerCtx as ServerCtxImpl};

#[cfg(windows)]
mod windows;

#[cfg(windows)]
use crate::windows::{ClientCtx as ClientCtxImpl, ServerCtx as ServerCtxImpl};

/// A Kerberos client context
#[derive(Clone, Debug)]
pub struct ClientCtx(ClientCtxImpl);

impl ClientCtx {
    /// Create a new client context. If `principal` is none then the credentials of
    /// the user the current process is running as will be used. `target_principal` is
    /// the service you intend to communicate with. This should be a service principal name as 
    /// described by GSSAPI, e.g. publish/ken-ohki.ryu-oh.org@RYU-OH.ORG, 
    /// the general form is <service>/host@REALM
    pub fn new(principal: Option<&str>, target_principal: &str) -> Result<Self> {
        Ok(ClientCtx(ClientCtxImpl::new(principal, target_principal)?))
    }
}

impl K5Ctx for ClientCtx {
    type Buf = <ClientCtxImpl as K5Ctx>::Buf;

    fn step(&self, token: Option<&[u8]>) -> Result<Option<Self::Buf>> {
        K5Ctx::step(&self.0, token)
    }

    fn wrap(&self, encrypt: bool, msg: &[u8]) -> Result<Self::Buf> {
        K5Ctx::wrap(&self.0, encrypt, msg)
    }

    fn wrap_iov(
        &self,
        encrypt: bool,
        header: &mut BytesMut,
        data: &mut BytesMut,
        padding: &mut BytesMut,
        trailer: &mut BytesMut,
    ) -> Result<()> {
        K5Ctx::wrap_iov(&self.0, encrypt, header, data, padding, trailer)
    }

    fn unwrap(&self, msg: &[u8]) -> Result<Self::Buf> {
        K5Ctx::unwrap(&self.0, msg)
    }

    fn unwrap_iov(&self, len: usize, msg: &mut BytesMut) -> Result<BytesMut> {
        K5Ctx::unwrap_iov(&self.0, len, msg)
    }

    fn ttl(&self) -> Result<Duration> {
        K5Ctx::ttl(&self.0)
    }
}

/// A Kerberos server context
#[derive(Clone, Debug)]
pub struct ServerCtx(ServerCtxImpl);

impl ServerCtx {
    /// Create a new server context. `principal` should be the service principal name 
    /// assigned to the service this context is associated with. This is equivelent to 
    /// the `target_principal` speficied in the client context. If it is left as `None` 
    /// it will use the user running the current process.
    pub fn new(principal: Option<&str>) -> Result<Self> {
        Ok(ServerCtx(ServerCtxImpl::new(principal)?))
    }
}

impl K5Ctx for ServerCtx {
    type Buf = <ServerCtxImpl as K5Ctx>::Buf;

    fn step(&self, token: Option<&[u8]>) -> Result<Option<Self::Buf>> {
        K5Ctx::step(&self.0, token)
    }

    fn wrap(&self, encrypt: bool, msg: &[u8]) -> Result<Self::Buf> {
        K5Ctx::wrap(&self.0, encrypt, msg)
    }

    fn wrap_iov(
        &self,
        encrypt: bool,
        header: &mut BytesMut,
        data: &mut BytesMut,
        padding: &mut BytesMut,
        trailer: &mut BytesMut,
    ) -> Result<()> {
        K5Ctx::wrap_iov(&self.0, encrypt, header, data, padding, trailer)
    }

    fn unwrap(&self, msg: &[u8]) -> Result<Self::Buf> {
        K5Ctx::unwrap(&self.0, msg)
    }

    fn unwrap_iov(&self, len: usize, msg: &mut BytesMut) -> Result<BytesMut> {
        K5Ctx::unwrap_iov(&self.0, len, msg)
    }

    fn ttl(&self) -> Result<Duration> {
        K5Ctx::ttl(&self.0)
    }
}

impl K5ServerCtx for ServerCtx {
    fn client(&self) -> Result<String> {
        K5ServerCtx::client(&self.0)
    }
}