libkrimes 0.1.0

A pure rust, minimal kerberos library
Documentation
use super::CredentialCache;
use crate::ccache::{Credential, CredentialV4, Principal, PrincipalV4};
use crate::error::KrbError;
use crate::proto::{KerberosCredentials, Name};
use binrw::helpers::until_eof;
use binrw::io::TakeSeekExt;
use binrw::BinReaderExt;
use binrw::BinWrite;
use binrw::{binread, binwrite};
use std::fs;
use std::fs::File;
use std::fs::Permissions;
use std::io::{BufReader, Read, Write};
use std::os::unix::fs::MetadataExt;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::time::Duration;
use tracing::{debug, error, trace};

#[binwrite]
#[bw(big)]
#[binread]
#[derive(Debug)]
struct HeaderField {
    tag: u16,
    #[bw(try_calc(u16::try_from(value.len())))]
    value_len: u16,
    #[br(count = value_len)]
    value: Vec<u8>,
}

#[binwrite]
#[bw(big)]
#[binread]
struct FileCredentialCacheHeader {
    #[bw(calc = fields.iter().map(|x| (x.value.len() + 4) as u16).sum::<u16>())]
    length: u16,
    #[br(map_stream = |s| s.take_seek(length as u64), parse_with = until_eof)]
    fields: Vec<HeaderField>,
}

#[binwrite]
#[bw(big, magic = 4u8)]
#[binread]
#[br(magic = 4u8)]
pub(super) struct FileCredentialCacheV4 {
    header: FileCredentialCacheHeader,
    principal: Principal,
    #[br(parse_with = until_eof)]
    credentials: Vec<Credential>,
}

#[binwrite]
#[bw(big, magic = 5u8)]
#[binread]
#[br(magic = 5u8)]
pub(super) enum FileCredentialCache {
    V4(FileCredentialCacheV4),
}

impl FileCredentialCache {
    pub fn read(inner: &Vec<u8>) -> Result<Self, KrbError> {
        let mut reader = binrw::io::Cursor::new(inner);
        let ccache: FileCredentialCache = reader.read_type(binrw::Endian::Big).map_err(|e| {
            debug!(?e, "Failed to deserialize credential cache");
            KrbError::BinRWError
        })?;
        Ok(ccache)
    }
}

pub(super) struct FileCredentialCacheContext {
    pub path: PathBuf,
}

impl CredentialCache for FileCredentialCacheContext {
    fn init(&mut self, name: &Name, clock_skew: Option<Duration>) -> Result<(), KrbError> {
        let mut header = FileCredentialCacheHeader { fields: vec![] };

        if let Some(skew) = clock_skew {
            /*
             * At this time there is only one defined header field. Its tag value is 1,
             * its length is always 8, and its contents are two 32-bit integers giving
             * the seconds and microseconds of the time offset of the KDC relative to
             * the client. Adding this offset to the current time on the client should
             * give the current time on the KDC, if that offset has not changed since
             * the initial authentication.
             */
            let secs = skew.as_secs() as u32;
            let secs: [u8; 4] = secs.to_be_bytes();
            let msecs = skew.subsec_micros();
            let msecs: [u8; 4] = msecs.to_be_bytes();
            let mut field = HeaderField {
                tag: 1u16,
                value: vec![],
            };
            field.value.extend_from_slice(&secs);
            field.value.extend_from_slice(&msecs);
            header.fields.push(field);
        }

        let pv4: PrincipalV4 = name.try_into()?;
        let v4 = FileCredentialCacheV4 {
            header,
            principal: Principal::V4(pv4),
            credentials: vec![],
        };
        let fcc = FileCredentialCache::V4(v4);

        let mut f = File::create(&self.path).map_err(|io_err| {
            error!(?io_err, "Unable to create file at {:#?}", &self.path);
            KrbError::IoError
        })?;

        let perms = Permissions::from_mode(0o600);
        f.set_permissions(perms).map_err(|x| {
            error!(?x, "Unable to set permissions at {:#?}", &self.path);
            KrbError::IoError
        })?;

        fcc.write(&mut f).map_err(|x| {
            error!(?x, "Unable to create file at {:#?}", &self.path);
            KrbError::IoError
        })?;
        Ok(())
    }

    fn destroy(&mut self) -> Result<(), KrbError> {
        match fs::exists(&self.path) {
            Ok(true) => {
                let mut f = File::create(&self.path).map_err(|e| {
                    error!(?e, "Unable to open file at {:#?}", &self.path);
                    KrbError::IoError
                })?;
                let size = f
                    .metadata()
                    .map_err(|e| {
                        error!(?e, "Unable to fstat file at {:#?}", &self.path);
                        KrbError::IoError
                    })?
                    .size();
                let zeros = vec![0; size as usize];
                f.write_all(&zeros).map_err(|e| {
                    error!(?e, "Unable to write file at {:#?}", &self.path);
                    KrbError::IoError
                })?;
                f.flush().map_err(|e| {
                    error!(?e, "Unable to flush file at {:#?}", &self.path);
                    KrbError::IoError
                })?;
                drop(f);
                fs::remove_file(&self.path).map_err(|e| {
                    error!(?e, "Unable to delete file at {:#?}", &self.path);
                    KrbError::IoError
                })?;
                Ok(())
            }
            Ok(false) => Ok(()),
            Err(e) => {
                error!(?e, "Unable to open file at {:#?}", &self.path);
                Err(KrbError::IoError)
            }
        }?;
        self.path = PathBuf::new();
        Ok(())
    }

    fn store(&mut self, credentials: &KerberosCredentials) -> Result<(), KrbError> {
        let cred = Credential::V4(CredentialV4::new(
            &credentials.name,
            &credentials.ticket,
            &credentials.kdc_reply,
        )?);

        let f = File::open(&self.path).map_err(|io_err| {
            error!(?io_err, "Unable to open file at {:#?}", &self.path);
            KrbError::IoError
        })?;

        let mut reader = BufReader::new(&f);
        let mut buffer = Vec::new();
        reader.read_to_end(&mut buffer).map_err(|e| {
            error!(?self.path, ?e, "Failed to read credential cache");
            KrbError::IoError
        })?;

        let mut fcc = FileCredentialCache::read(&buffer)?;
        match &mut fcc {
            FileCredentialCache::V4(v4) => v4.credentials.push(cred),
        };
        drop(f);

        let mut f = File::create(&self.path).map_err(|io_err| {
            error!(?io_err, "Unable to open file at {:#?}", &self.path);
            KrbError::IoError
        })?;
        fcc.write(&mut f).map_err(|binrw_err| {
            error!(?binrw_err, "Unable to write binary data.");
            KrbError::BinRWError
        })?;
        Ok(())
    }
}

pub(super) fn resolve(ccache_name: &str) -> Result<Box<dyn CredentialCache>, KrbError> {
    trace!(?ccache_name, "Resolving file credential cache");
    let path = ccache_name.strip_prefix("FILE:").unwrap_or(ccache_name);
    trace!(?path, "Resolved file credential cache");

    let path = PathBuf::from(&path);

    let fcc = FileCredentialCacheContext { path };
    Ok(Box::new(fcc))
}

#[cfg(test)]
mod tests {
    use super::*;
    use binrw::BinWrite;

    #[tokio::test]
    async fn test_ccache_file_read_write() -> Result<(), KrbError> {
        /*
         * This is a file ccache produced by MIT's kinit
         * including cache configuration entries
         */
        let mit_buf = "0504000c00010008000000000000000000000001000000010000000b4558414d504c452e434f4d00000008746573747573657200000001000000010000000b4558414d504c452e434f4d00000008746573747573657200000001000000030000000c582d4341434845434f4e463a000000156b7262355f6363616368655f636f6e665f646174610000000a666173745f617661696c0000001e6b72627467742f4558414d504c452e434f4d404558414d504c452e434f4d0000000000000000000000000000000000000000000000000000000000000000000000000000037965730000000000000001000000010000000b4558414d504c452e434f4d00000008746573747573657200000002000000020000000b4558414d504c452e434f4d000000066b72627467740000000b4558414d504c452e434f4d001200000020de5604735e4216fdf4e7992177ac3d6b25416e6517edce48fcb8be73f9ecf46f66a7815b66a7815b66a80dfb66b0bbdb0000c100000000000000000000000001ba618201b6308201b2a003020105a10d1b0b4558414d504c452e434f4da220301ea003020102a11730151b066b72627467741b0b4558414d504c452e434f4da382017830820174a003020112a103020101a2820166048201620c0ded71bdab6134022d37fa7ea73856eb87044fa4340e36a2668c8fc74f21a9637fac7ccf2777202583b9fea5ca609cec1b1479f72a7374f2ae7e5347bcc64a66de1575bd8bc9eaa6ce96049e199d7a6f835dda18aea8b0d093d05bd4bba4fc5c2385f000297217adde3c23dff75705a4fafe58dee48774eeef2c969a8dd64ea3f754087d72c4796506ebb23fef404fbb41826483642af6f2a97680146319dd5541adbe2b6247766f36f0b5a673bffea5cc8b89e8c91359147f291e740e8f69377e88f984829d1791912c7da7cc7f6277470a91cf140b6c71da0f4e561722e0536a23af6da7a375343b6e5b72c4847f3c848d4e8b044ae313979f954db7a7210052922f587e6e5d21447aec02beaeab9371dd1ae9903dde0838b1fd9b791a4a4065565905664a62c92980053c8532586deeafd0e558df77de6e4ce2c653feff9aefc2c9b0a34ab2cc405e3bf4a8b49c9bf1c8d1c6f79be11fa71272edcfc3a5c51700000000";
        let mit_buf = hex::decode(mit_buf).expect("Invalid hex buffer");
        let krime_ccache = FileCredentialCache::read(&mit_buf)?;

        let mut c = std::io::Cursor::new(Vec::new());
        krime_ccache.write(&mut c).unwrap();
        let krime_buf = c.into_inner();

        assert_eq!(krime_buf, mit_buf);
        Ok(())
    }
}