spacedls 0.4.0

no_std CCSDS 355.0-B-2 (SDLS) Space Data Link Security implementation
Documentation
use crate::core::{AuthParams, AuthProvider, AuthSpec, VerifyMacResult};
use core::{convert::Infallible, marker::PhantomData};
use ctutils::CtEq;
use hmac::{EagerHash, Hmac, KeyInit, Mac, digest::OutputSizeUser};

/// Type-level specification for HMAC-SHA, deriving key and MAC sizes from the hash.
#[derive(Debug, Clone, Copy, Default)]
pub struct HmacShaSpec<Sha>(PhantomData<Sha>);

impl<Sha> AuthSpec for HmacShaSpec<Sha>
where Sha: EagerHash
{
    type KeySize = Sha::BlockSize;
    type MacSize = <Sha::Core as OutputSizeUser>::OutputSize;
}

/// Software HMAC-SHA authentication provider. MAC verification uses constant-time comparison.
#[derive(Debug, Clone, Copy)]
pub struct HmacSha<Sha>(PhantomData<Sha>);

impl<Sha> Default for HmacSha<Sha> {
    fn default() -> Self { Self(PhantomData) }
}

impl<Sha> AuthProvider for HmacSha<Sha>
where Sha: EagerHash
{
    type Spec = HmacShaSpec<Sha>;
    type SignError = Infallible;
    type VerifyError = Infallible;

    fn sign(
        &self,
        p: &AuthParams<Self::Spec>,
        data: &[&[u8]],
    ) -> Result<crate::core::Mac<<Self::Spec as AuthSpec>::MacSize>, Self::SignError> {
        let mut mac = <Hmac<Sha> as KeyInit>::new(&p.key);
        for chunk in data {
            mac.update(chunk);
        }

        Ok(mac.finalize().into_bytes().into())
    }

    fn verify(
        &self,
        p: &AuthParams<Self::Spec>,
        data: &[&[u8]],
        mac: &[u8],
    ) -> Result<VerifyMacResult, Infallible> {
        let n = mac.len();

        let mut verifier = <Hmac<Sha> as KeyInit>::new(&p.key);
        for chunk in data {
            verifier.update(chunk);
        }
        let actual_mac = verifier.finalize().into_bytes();

        if n == 0 || n > actual_mac.len() {
            return Ok(VerifyMacResult::InvalidMac);
        }
        if mac.ct_eq(&actual_mac[0..mac.len()]).into() {
            if n < actual_mac.len() {
                Ok(VerifyMacResult::OkButTruncated)
            } else {
                Ok(VerifyMacResult::Ok)
            }
        } else {
            Ok(VerifyMacResult::InvalidMac)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::HmacSha;
    use crate::core::{AuthParams, AuthProvider, VerifyMacResult};
    use assert_matches::assert_matches;
    use sha2::Sha256;
    use std::vec;

    type Provider = HmacSha<Sha256>;

    #[test]
    fn computes_and_accepts_valid_mac() {
        let provider = Provider::default();
        let params = AuthParams { key: [0x11_u8; 64].into() };
        let data = b"ccsds-auth-payload";

        let mac = provider.sign(&params, &[data]).unwrap();
        assert_matches!(
            provider.verify(&params, &[data], mac.as_slice()).unwrap(),
            VerifyMacResult::Ok
        );
    }

    #[test]
    fn rejects_tampered_mac() {
        let provider = Provider::default();
        let params = AuthParams { key: [0x22_u8; 64].into() };
        let data = b"ccsds-auth-payload";

        let mut mac = provider.sign(&params, &[data]).unwrap();
        mac[0] ^= 0x01;

        let result = provider.verify(&params, &[data], mac.as_slice());
        assert_matches!(result.unwrap(), VerifyMacResult::InvalidMac);
    }

    #[test]
    fn accepts_truncated_mac() {
        let provider = Provider::default();
        let params = AuthParams { key: [0x22_u8; 64].into() };
        let data = b"ccsds-auth-payload";

        let mac = provider.sign(&params, &[data]).unwrap();

        let result = provider.verify(&params, &[data], &mac.as_slice()[0..15]);
        assert_matches!(result.unwrap(), VerifyMacResult::OkButTruncated);
    }

    #[test]
    fn rejects_empty_mac() {
        let provider = Provider::default();
        let params = AuthParams { key: [0x22_u8; 64].into() };
        let data = b"ccsds-auth-payload";

        let result = provider.verify(&params, &[data], &[]);
        assert_matches!(result.unwrap(), VerifyMacResult::InvalidMac);
    }

    #[test]
    fn rejects_oversized_mac() {
        let provider = Provider::default();
        let params = AuthParams { key: [0x22_u8; 64].into() };
        let data = b"ccsds-auth-payload";

        let bad = vec![0_u8; 33];
        let result = provider.verify(&params, &[data], &bad);
        assert_matches!(result.unwrap(), VerifyMacResult::InvalidMac);
    }
}