blindplane-access 0.1.0

Signed enterprise access grants, capability policies, revocation and encrypted audit events for Blindplane
Documentation
//! Bounded canonical encoding helpers.

use crate::{ACCESS_FORMAT_VERSION, AccessError};

pub(crate) const MAGIC: &[u8; 4] = b"BPAC";

/// Bounds applied while decoding untrusted access objects.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AccessValidationPolicy {
    /// Maximum UTF-8 byte length of an identifier.
    pub max_identifier_bytes: usize,
    /// Maximum number of capability rules in one policy.
    pub max_rules: usize,
    /// Maximum HPKE ciphertext size in one grant.
    pub max_wrapped_grant_bytes: usize,
    /// Maximum plaintext body size in one audit event.
    pub max_event_body_bytes: usize,
}

impl Default for AccessValidationPolicy {
    fn default() -> Self {
        Self {
            max_identifier_bytes: 255,
            max_rules: 512,
            max_wrapped_grant_bytes: 4 * 1024,
            max_event_body_bytes: 8 * 1024 * 1024,
        }
    }
}

pub(crate) fn push_header(out: &mut Vec<u8>, tag: u8) {
    out.extend_from_slice(MAGIC);
    out.extend_from_slice(&ACCESS_FORMAT_VERSION.to_be_bytes());
    out.push(tag);
}

pub(crate) fn push_bytes(out: &mut Vec<u8>, bytes: &[u8]) {
    let length = u32::try_from(bytes.len()).expect("access values are bounded below u32::MAX");
    out.extend_from_slice(&length.to_be_bytes());
    out.extend_from_slice(bytes);
}

pub(crate) fn push_string(out: &mut Vec<u8>, value: &str) {
    push_bytes(out, value.as_bytes());
}

pub(crate) struct Cursor<'a> {
    bytes: &'a [u8],
    offset: usize,
}

impl<'a> Cursor<'a> {
    pub(crate) const fn new(bytes: &'a [u8]) -> Self {
        Self { bytes, offset: 0 }
    }

    pub(crate) fn take_header(&mut self, expected_tag: u8) -> Result<(), AccessError> {
        if self.take_exact(MAGIC.len())? != MAGIC {
            return Err(AccessError::WrongObjectType);
        }
        let version = self.take_u16()?;
        if version != ACCESS_FORMAT_VERSION {
            return Err(AccessError::UnsupportedVersion(version));
        }
        if self.take_u8()? != expected_tag {
            return Err(AccessError::WrongObjectType);
        }
        Ok(())
    }

    pub(crate) const fn is_empty(&self) -> bool {
        self.offset == self.bytes.len()
    }

    pub(crate) fn take_exact(&mut self, length: usize) -> Result<&'a [u8], AccessError> {
        let end = self
            .offset
            .checked_add(length)
            .ok_or(AccessError::Truncated)?;
        if end > self.bytes.len() {
            return Err(AccessError::Truncated);
        }
        let value = &self.bytes[self.offset..end];
        self.offset = end;
        Ok(value)
    }

    pub(crate) fn take_u8(&mut self) -> Result<u8, AccessError> {
        Ok(self.take_exact(1)?[0])
    }

    pub(crate) fn take_u16(&mut self) -> Result<u16, AccessError> {
        Ok(u16::from_be_bytes(
            self.take_exact(2)?.try_into().expect("two bytes"),
        ))
    }

    pub(crate) fn take_u32(&mut self) -> Result<u32, AccessError> {
        Ok(u32::from_be_bytes(
            self.take_exact(4)?.try_into().expect("four bytes"),
        ))
    }

    pub(crate) fn take_u64(&mut self) -> Result<u64, AccessError> {
        Ok(u64::from_be_bytes(
            self.take_exact(8)?.try_into().expect("eight bytes"),
        ))
    }

    pub(crate) fn take_array32(&mut self) -> Result<[u8; 32], AccessError> {
        Ok(self.take_exact(32)?.try_into().expect("32 bytes"))
    }

    pub(crate) fn take_array64(&mut self) -> Result<[u8; 64], AccessError> {
        Ok(self.take_exact(64)?.try_into().expect("64 bytes"))
    }

    pub(crate) fn take_len(&mut self, limit: usize) -> Result<usize, AccessError> {
        let length = self.take_u32()?;
        let length = usize::try_from(length).expect("u32 fits into usize on supported targets");
        if length > limit {
            return Err(AccessError::LengthLimit(length));
        }
        Ok(length)
    }

    pub(crate) fn take_bytes(&mut self, limit: usize) -> Result<&'a [u8], AccessError> {
        let length = self.take_len(limit)?;
        self.take_exact(length)
    }

    pub(crate) fn take_string(&mut self, limit: usize) -> Result<String, AccessError> {
        let bytes = self.take_bytes(limit)?;
        core::str::from_utf8(bytes)
            .map(str::to_owned)
            .map_err(|_| AccessError::InvalidUtf8)
    }
}

pub(crate) fn validate_identifier(value: &str, limit: usize) -> Result<(), AccessError> {
    if value.is_empty() || value.len() > limit {
        Err(AccessError::InvalidIdentifier)
    } else {
        Ok(())
    }
}