use super::auth::AsAuth;
use super::authenc::AsAuthEnc;
use super::enc::AsEnc;
use super::error::SaOperationError;
use super::format::SDLSFrameFormat;
use super::format::{SecurityHeader, SecurityTrailer};
use super::mac::VerifyMacResult;
use super::param::WithKeySize;
use super::param::{AuthEncParams, AuthParams, EncParams};
use super::{
AuthEncProvider, AuthEncSpec, AuthProvider, EncProvider, EncSpec, Key, SequenceNumber,
ServiceKind, ServiceProviderGeneric,
};
use core::marker::PhantomData;
use hybrid_array::{Array, ArraySize};
use typenum::{NonZero, U0, Unsigned, Zero};
pub type KeyType<'a, S> =
&'a dyn Key<<<S as ServiceProviderGeneric>::Param as WithKeySize>::KeySize>;
pub struct SecurityAssociation<'a, S: ServiceProviderGeneric, F: SDLSFrameFormat, N: ArraySize> {
spi: u16,
service: S,
counter: SequenceNumber<N>,
key: KeyType<'a, S>,
abm: Option<&'a [u8]>,
_f_marker: PhantomData<F>,
}
impl<'a, S: ServiceProviderGeneric, F: SDLSFrameFormat, N: ArraySize>
SecurityAssociation<'a, S, F, N>
{
#[inline]
#[must_use]
pub fn spi(&self) -> u16 { self.spi }
#[inline]
pub fn set_spi(&mut self, spi: u16) { self.spi = spi }
#[inline]
#[must_use]
pub fn sn_length() -> usize { F::SNLen::USIZE }
#[inline]
#[must_use]
pub fn sn(&self) -> &SequenceNumber<N> { &self.counter }
#[inline]
pub fn sn_mut(&mut self) -> &mut SequenceNumber<N> { &mut self.counter }
#[inline]
#[must_use]
pub fn iv_length(&self) -> usize { F::IVLen::USIZE }
#[inline]
#[must_use]
pub fn mac_length(&self) -> usize { F::MacLen::USIZE }
#[inline]
#[must_use]
pub const fn service_type(&self) -> ServiceKind { S::KIND }
#[inline]
#[must_use]
pub fn get_key(&self) -> KeyType<'a, S> { self.key }
#[inline]
pub fn set_key(&mut self, key: KeyType<'a, S>) { self.key = key }
#[inline]
#[must_use]
pub fn abm(&self) -> Option<&[u8]> { self.abm }
#[inline]
pub fn set_abm(&mut self, abm: Option<&'a [u8]>) { self.abm = abm; }
#[inline]
fn apply_abm(abm: &[u8], buf: &mut [u8], frame_offset: usize) {
for (d, &m) in buf.iter_mut().zip(&abm[frame_offset..]) {
*d &= m;
}
}
#[inline]
fn check_abm_len<E>(&self, prefix_len: usize) -> Result<(), SaOperationError<E>> {
let abm_len = self.abm.as_ref().map_or(usize::MAX, |abm| abm.len());
let required = prefix_len + F::HeaderLen::USIZE;
if abm_len < required {
return Err(SaOperationError::AbmTooShort { abm_len, required });
}
Ok(())
}
fn with_masked_aad<R, const N_PRFX: usize>(
&self,
frame_prefix: &[u8; N_PRFX],
sec_bytes: &Array<u8, F::HeaderLen>,
f: impl FnOnce(&[u8], &[u8]) -> R,
) -> R {
if let Some(abm) = self.abm {
let prefix_len = frame_prefix.len();
let mut masked_prefix = [0u8; N_PRFX];
masked_prefix[..prefix_len].copy_from_slice(frame_prefix);
Self::apply_abm(abm, &mut masked_prefix[..prefix_len], 0);
let mut masked_sec = sec_bytes.clone();
Self::apply_abm(abm, masked_sec.as_mut_slice(), prefix_len);
f(&masked_prefix[..prefix_len], masked_sec.as_slice())
} else {
f(frame_prefix, sec_bytes.as_slice())
}
}
}
impl<'a, S, F> SecurityAssociation<'a, AsEnc<S>, F, U0>
where
S: EncProvider,
F: SDLSFrameFormat<IVLen = <S::Spec as EncSpec>::IvSize>,
F::MacLen: Zero,
F::SNLen: Zero,
F::IVLen: NonZero,
{
pub fn new_enc(service: S, spi: u16, key: KeyType<'a, AsEnc<S>>) -> Self {
Self {
service: service.into(),
spi,
counter: SequenceNumber::disabled(),
key,
abm: None,
_f_marker: Default::default(),
}
}
pub fn encrypt(
&self,
iv: Array<u8, F::IVLen>,
plain: &[u8],
cipher: &mut [u8],
) -> Result<(usize, SecurityHeader<F>), SaOperationError<S::EncryptError>> {
let key = self.key.get().ok_or(SaOperationError::KeyNotAvailable)?;
let pad_len = S::pad_len(plain.len());
let params = EncParams { key, iv };
let written =
self.service.encrypt(¶ms, plain, cipher).map_err(SaOperationError::Service)?;
let EncParams { iv, .. } = params;
Ok((written, SecurityHeader { spi: self.spi, iv, sn: Array::default(), pad_len }))
}
pub fn decrypt(
&self,
sec_hdr: SecurityHeader<F>,
cipher: &mut [u8],
) -> Result<usize, SaOperationError<S::DecryptError>> {
SaOperationError::is_valid_spi(sec_hdr.spi, self.spi)?;
let key = self.key.get().ok_or(SaOperationError::KeyNotAvailable)?;
let params = EncParams { key, iv: sec_hdr.iv };
self.service.decrypt(¶ms, cipher).map_err(SaOperationError::Service)
}
}
impl<'a, S, F> SecurityAssociation<'a, AsAuth<S>, F, F::SNLen>
where
S: AuthProvider,
F: SDLSFrameFormat,
F::IVLen: Zero,
F::PLLen: Zero,
F::SNLen: NonZero,
F::MacLen: NonZero,
{
pub fn new_auth(
service: S,
spi: u16,
key: KeyType<'a, AsAuth<S>>,
sn_window: u16,
abm: Option<&'a [u8]>,
) -> Self {
Self {
service: service.into(),
spi,
key,
counter: SequenceNumber::new(sn_window),
abm,
_f_marker: Default::default(),
}
}
pub fn sign<const N_PRFX: usize>(
&mut self,
frame_prefix: &[u8; N_PRFX],
data: &[u8],
) -> Result<(SecurityHeader<F>, SecurityTrailer<F>), SaOperationError<S::SignError>> {
self.check_abm_len(N_PRFX)?;
let key = self.key.get().ok_or(SaOperationError::KeyNotAvailable)?;
let params = AuthParams { key };
let next_sn = self.counter.next();
let sec_hdr =
SecurityHeader { spi: self.spi, iv: Array::default(), sn: next_sn, pad_len: 0 };
let sec_bytes = sec_hdr.serialize();
let mac = self
.with_masked_aad::<_, N_PRFX>(frame_prefix, &sec_bytes, |pfx, sec| {
self.service.sign(¶ms, &[pfx, sec, data])
})
.map_err(SaOperationError::Service)?;
self.counter.advance();
Ok((sec_hdr, SecurityTrailer::from_mac_bytes(mac.as_slice())))
}
pub fn verify<const N_PRFX: usize>(
&mut self,
frame_prefix: &[u8; N_PRFX],
data: &[u8],
sec_hdr: SecurityHeader<F>,
sec_trlr: SecurityTrailer<F>,
) -> Result<VerifyMacResult, SaOperationError<S::VerifyError>> {
self.check_abm_len(N_PRFX)?;
self.counter.is_replay(&sec_hdr.sn).map_err(SaOperationError::ReplayDetected)?;
SaOperationError::is_valid_spi(sec_hdr.spi, self.spi)?;
let key = self.key.get().ok_or(SaOperationError::KeyNotAvailable)?;
let params = AuthParams { key };
let sec_hdr_bytes = sec_hdr.serialize();
let mac = self
.with_masked_aad::<_, N_PRFX>(frame_prefix, &sec_hdr_bytes, |pfx, sec| {
self.service.verify(¶ms, &[pfx, sec, data], &sec_trlr.mac)
})
.map_err(SaOperationError::Service)?;
if !matches!(mac, VerifyMacResult::InvalidMac) {
self.counter.set_current(sec_hdr.sn);
}
Ok(mac)
}
}
impl<'a, S, F> SecurityAssociation<'a, AsAuthEnc<S>, F, F::SNLen>
where
S: AuthEncProvider,
F: SDLSFrameFormat<IVLen = <S::Spec as AuthEncSpec>::IvSize>,
F::PLLen: Zero,
F::IVLen: NonZero,
F::MacLen: NonZero,
F::SNLen: NonZero,
{
pub fn new_authenc(
service: S,
spi: u16,
key: KeyType<'a, AsAuthEnc<S>>,
sn_window: u16,
abm: Option<&'a [u8]>,
) -> Self {
Self {
service: service.into(),
spi,
key,
counter: SequenceNumber::new(sn_window),
abm,
_f_marker: Default::default(),
}
}
pub fn seal<const N_PRFX: usize>(
&mut self,
iv: Array<u8, F::IVLen>,
frame_prefix: &[u8; N_PRFX],
plain: &[u8],
cipher: &mut [u8],
) -> Result<(usize, SecurityHeader<F>, SecurityTrailer<F>), SaOperationError<S::SealError>>
{
self.check_abm_len(N_PRFX)?;
let key = self.key.get().ok_or(SaOperationError::KeyNotAvailable)?;
let pad_len = S::pad_len(plain.len());
let next_sn = self.counter.next();
let params = AuthEncParams { key, iv: iv.clone() };
let sec_hdr = SecurityHeader { spi: self.spi, iv, sn: next_sn, pad_len };
let sec_bytes = sec_hdr.serialize();
let (written, mac) = self
.with_masked_aad::<_, N_PRFX>(frame_prefix, &sec_bytes, |pfx, sec| {
self.service.seal(¶ms, &[pfx, sec], plain, cipher)
})
.map_err(SaOperationError::Service)?;
self.counter.advance();
Ok((written, sec_hdr, SecurityTrailer::from_mac_bytes(mac.as_slice())))
}
pub fn open<const N_PRFX: usize>(
&mut self,
frame_prefix: &[u8; N_PRFX],
cipher: &mut [u8],
sec_hdr: SecurityHeader<F>,
sec_trlr: SecurityTrailer<F>,
) -> Result<(usize, VerifyMacResult), SaOperationError<S::OpenError>> {
self.check_abm_len(N_PRFX)?;
self.counter.is_replay(&sec_hdr.sn).map_err(SaOperationError::ReplayDetected)?;
SaOperationError::is_valid_spi(sec_hdr.spi, self.spi)?;
let key = self.key.get().ok_or(SaOperationError::KeyNotAvailable)?;
let sec_hdr_bytes = sec_hdr.serialize();
let params = AuthEncParams { key, iv: sec_hdr.iv };
let (written, mac) = self
.with_masked_aad::<_, N_PRFX>(frame_prefix, &sec_hdr_bytes, |pfx, sec| {
self.service.open(¶ms, &[pfx, sec], cipher, &sec_trlr.mac)
})
.map_err(SaOperationError::Service)?;
if !matches!(mac, VerifyMacResult::InvalidMac) {
self.counter.set_current(sec_hdr.sn);
}
Ok((written, mac))
}
}
impl<'a, S, F> SecurityAssociation<'a, AsAuthEnc<S>, F, F::IVLen>
where
S: AuthEncProvider,
F: SDLSFrameFormat<IVLen = <S::Spec as AuthEncSpec>::IvSize>,
F::PLLen: Zero,
F::IVLen: NonZero,
F::MacLen: NonZero,
F::SNLen: Zero,
{
pub fn new_authenc_ctr(
service: S,
spi: u16,
key: KeyType<'a, AsAuthEnc<S>>,
sn_window: u16,
abm: Option<&'a [u8]>,
) -> Self {
Self {
service: service.into(),
spi,
key,
counter: SequenceNumber::new(sn_window),
abm,
_f_marker: Default::default(),
}
}
pub fn seal_ctr<const N_PRFX: usize>(
&mut self,
frame_prefix: &[u8; N_PRFX],
plain: &[u8],
cipher: &mut [u8],
) -> Result<(SecurityHeader<F>, SecurityTrailer<F>), SaOperationError<S::SealError>> {
self.check_abm_len(N_PRFX)?;
let key = self.key.get().ok_or(SaOperationError::KeyNotAvailable)?;
let pad_len = S::pad_len(plain.len());
let iv = self.counter.next();
let params = AuthEncParams { key, iv: iv.clone() };
let sec_hdr = SecurityHeader { spi: self.spi, iv, sn: Array::default(), pad_len };
let sec_bytes = sec_hdr.serialize();
let (_, mac) = self
.with_masked_aad::<_, N_PRFX>(frame_prefix, &sec_bytes, |pfx, sec| {
self.service.seal(¶ms, &[pfx, sec], plain, cipher)
})
.map_err(SaOperationError::Service)?;
self.counter.advance();
Ok((sec_hdr, SecurityTrailer::from_mac_bytes(mac.as_slice())))
}
pub fn open_ctr<const N_PRFX: usize>(
&mut self,
frame_prefix: &[u8; N_PRFX],
cipher: &mut [u8],
sec_hdr: SecurityHeader<F>,
sec_trlr: SecurityTrailer<F>,
) -> Result<(usize, VerifyMacResult), SaOperationError<S::OpenError>> {
self.check_abm_len(N_PRFX)?;
self.counter.is_replay(&sec_hdr.iv).map_err(SaOperationError::ReplayDetected)?;
SaOperationError::is_valid_spi(sec_hdr.spi, self.spi)?;
let key = self.key.get().ok_or(SaOperationError::KeyNotAvailable)?;
let sec_hdr_bytes = sec_hdr.serialize();
let params = AuthEncParams { key, iv: sec_hdr.iv };
let (written, mac) = self
.with_masked_aad::<_, N_PRFX>(frame_prefix, &sec_hdr_bytes, |pfx, sec| {
self.service.open(¶ms, &[pfx, sec], cipher, &sec_trlr.mac)
})
.map_err(SaOperationError::Service)?;
if !matches!(mac, VerifyMacResult::InvalidMac) {
self.counter.set_current(params.iv);
}
Ok((written, mac))
}
}
#[cfg(all(test, feature = "softcrypto"))]
mod tests {
use super::SecurityAssociation;
use crate::core::error::SaOperationError;
use crate::core::format::{SDLSFrameFormat, SecurityHeader};
use crate::core::key::{BIT128, BIT512};
use crate::core::{AsAuth, AsAuthEnc, AsEnc, ConstKey, VerifyMacResult};
use crate::softcrypto::{AesCbc, AesGcm, HmacSha};
use crate::testfmt::{AuthEncCtrFmt, AuthEncFmt, AuthFmt, EncFmt};
use aes::Aes128;
use assert_matches::assert_matches;
use cipher::block_padding::Pkcs7;
use hybrid_array::Array;
use sha2::Sha256;
use std::vec;
use typenum::U0;
type AuthSA<'a> = SecurityAssociation<
'a,
AsAuth<HmacSha<Sha256>>,
AuthFmt,
<AuthFmt as SDLSFrameFormat>::SNLen,
>;
type AuthEncSA<'a> = SecurityAssociation<
'a,
AsAuthEnc<AesGcm<Aes128>>,
AuthEncFmt,
<AuthEncFmt as SDLSFrameFormat>::SNLen,
>;
type AuthEncCtrSA<'a> = SecurityAssociation<
'a,
AsAuthEnc<AesGcm<Aes128>>,
AuthEncCtrFmt,
<AuthEncCtrFmt as SDLSFrameFormat>::IVLen,
>;
fn auth_pair<'a>(
key: &'a ConstKey<BIT512>,
sn_window: u16,
abm: Option<&'a [u8]>,
) -> (AuthSA<'a>, AuthSA<'a>) {
let sa = || SecurityAssociation::new_auth(HmacSha::default(), 0x2202, key, sn_window, abm);
(sa(), sa())
}
fn authenc_pair<'a>(
key: &'a ConstKey<BIT128>,
sn_window: u16,
abm: Option<&'a [u8]>,
) -> (AuthEncSA<'a>, AuthEncSA<'a>) {
let sa =
|| SecurityAssociation::new_authenc(AesGcm::default(), 0x3303, key, sn_window, abm);
(sa(), sa())
}
#[test]
fn enc_round_trip() {
let key = ConstKey::<BIT128>::new([
0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf,
0x4f, 0x3c,
]);
let iv: Array<u8, _> = [0u8; 16].into();
let sa = SecurityAssociation::<AsEnc<AesCbc<Aes128, Pkcs7>>, EncFmt, U0>::new_enc(
AesCbc::default(),
0x1101,
&key,
);
let plain = b"integration-through-sa";
let mut cipher = vec![0_u8; ((plain.len() / 16) + 1) * 16];
let (_, hdr) = sa.encrypt(iv, plain, &mut cipher).unwrap();
let written = plain.len() + usize::from(hdr.pad_len);
let mut decrypt_buf = cipher[..written].to_vec();
let recovered = sa.decrypt(hdr, &mut decrypt_buf).unwrap();
assert_eq!(&decrypt_buf[..recovered], plain);
}
#[test]
fn enc_rejects_wrong_spi() {
let key = ConstKey::<BIT128>::new([0xAA_u8; 16]);
let iv: Array<u8, _> = [0u8; 16].into();
let sa = SecurityAssociation::<AsEnc<AesCbc<Aes128, Pkcs7>>, EncFmt, U0>::new_enc(
AesCbc::default(),
0x1101,
&key,
);
let plain = b"spi-test";
let mut cipher = vec![0_u8; 16];
let (_, mut hdr) = sa.encrypt(iv, plain, &mut cipher).unwrap();
hdr.spi = 0x9999;
let mut decrypt_buf = cipher[..plain.len() + usize::from(hdr.pad_len)].to_vec();
let result = sa.decrypt(hdr, &mut decrypt_buf);
assert!(result.is_err());
}
#[test]
fn enc_pad_len_correct() {
let key = ConstKey::<BIT128>::new([0xBB_u8; 16]);
let iv: Array<u8, _> = [0u8; 16].into();
let sa = SecurityAssociation::<AsEnc<AesCbc<Aes128, Pkcs7>>, EncFmt, U0>::new_enc(
AesCbc::default(),
0x1101,
&key,
);
let plain = b"12345678901";
let mut cipher = vec![0_u8; 16];
let (_, hdr) = sa.encrypt(iv, plain, &mut cipher).unwrap();
assert_eq!(hdr.pad_len, 5);
}
#[test]
fn auth_sign_verify() {
let key = ConstKey::<BIT512>::new([0x11_u8; 64]);
let (mut sender, mut receiver) = auth_pair(&key, 16, None);
let payload = b"sa-auth-integration";
let (hdr, trlr) = sender.sign(b"", payload).unwrap();
assert_eq!(hdr.spi, 0x2202);
assert_eq!(hdr.sn, Array::from([0x00, 0x00, 0x00, 0x01]));
let verify = receiver.verify(b"", payload, hdr, trlr).unwrap();
assert_matches!(verify, VerifyMacResult::Ok);
}
#[test]
fn auth_sign_verify_with_prefix() {
let key = ConstKey::<BIT512>::new([0x11_u8; 64]);
let (mut sender, mut receiver) = auth_pair(&key, 16, None);
let prefix = b"\x20\x00\x01\x02\x03";
let payload = b"auth-with-prefix";
let (hdr, trlr) = sender.sign(prefix, payload).unwrap();
let verify = receiver.verify(prefix, payload, hdr, trlr).unwrap();
assert_matches!(verify, VerifyMacResult::Ok);
}
#[test]
fn auth_rejects_replay() {
let key = ConstKey::<BIT512>::new([0x11_u8; 64]);
let (mut sender, mut receiver) = auth_pair(&key, 4, None);
let payload = b"sa-auth-replay-test";
let (hdr1, trlr1) = sender.sign(b"", payload).unwrap();
let verify = receiver.verify(b"", payload, hdr1, trlr1).unwrap();
assert_matches!(verify, VerifyMacResult::Ok);
let (_hdr2, trlr2) = sender.sign(b"", payload).unwrap();
let old_hdr = SecurityHeader::<AuthFmt> {
spi: 0x2202,
iv: Array::default(),
sn: [0x00, 0x00, 0x00, 0x01].into(),
pad_len: 0,
};
let result = receiver.verify(b"", payload, old_hdr, trlr2);
assert!(result.is_err());
}
#[test]
fn auth_rejects_wrong_spi() {
let key = ConstKey::<BIT512>::new([0x11_u8; 64]);
let (mut sender, mut receiver) = auth_pair(&key, 16, None);
let (mut hdr, trlr) = sender.sign(b"", b"spi-test").unwrap();
hdr.spi = 0x9999;
let result = receiver.verify(b"", b"spi-test", hdr, trlr);
assert!(result.is_err());
}
#[test]
fn sn_window_sliding() {
let key = ConstKey::<BIT512>::new([0x11_u8; 64]);
let (mut sender, mut receiver) = auth_pair(&key, 4, None);
for i in 1..=10u64 {
let (hdr, trlr) = sender.sign(b"", b"sliding").unwrap();
assert_eq!(hdr.sn, Array::from([0x00, 0x00, 0x00, i as u8]));
let verify = receiver.verify(b"", b"sliding", hdr, trlr).unwrap();
assert_matches!(verify, VerifyMacResult::Ok);
}
}
#[test]
fn authenc_seal_open() {
let key = ConstKey::<BIT128>::new([0x77_u8; 16]);
let (mut sender, mut receiver) = authenc_pair(&key, 16, None);
let iv: Array<u8, _> = [0x33_u8; 12].into();
let prefix = b"sa-security-header";
let plain = b"sa-authenc-payload";
let mut cipher = vec![0_u8; plain.len()];
let (_, hdr, trlr) = sender.seal(iv, prefix, plain, &mut cipher).unwrap();
assert_eq!(hdr.spi, 0x3303);
assert_eq!(hdr.sn, Array::from([0x00, 0x00, 0x00, 0x01]));
let (recovered_len, verify) = receiver.open(prefix, &mut cipher, hdr, trlr).unwrap();
assert_matches!(verify, VerifyMacResult::Ok);
assert_eq!(&cipher[..recovered_len], plain);
}
#[test]
fn authenc_rejects_tampered_cipher() {
let key = ConstKey::<BIT128>::new([0x77_u8; 16]);
let (mut sender, mut receiver) = authenc_pair(&key, 16, None);
let iv: Array<u8, _> = [0x33_u8; 12].into();
let prefix = b"sa-security-header";
let plain = b"sa-authenc-payload";
let mut cipher = vec![0_u8; plain.len()];
let (_, hdr, trlr) = sender.seal(iv, prefix, plain, &mut cipher).unwrap();
cipher[0] ^= 0xFF;
let (_, verify) = receiver.open(prefix, &mut cipher, hdr, trlr).unwrap();
assert_matches!(verify, VerifyMacResult::InvalidMac);
}
#[test]
fn authenc_rejects_replay() {
let key = ConstKey::<BIT128>::new([0x77_u8; 16]);
let (mut sender, mut receiver) = authenc_pair(&key, 4, None);
let prefix = b"prefix";
let plain = b"replay-test";
let mut cipher = vec![0_u8; plain.len()];
let (_, hdr1, trlr1) =
sender.seal([0x33_u8; 12].into(), prefix, plain, &mut cipher).unwrap();
let (_, verify) = receiver.open(prefix, &mut cipher, hdr1, trlr1).unwrap();
assert_matches!(verify, VerifyMacResult::Ok);
let mut cipher2 = vec![0_u8; plain.len()];
let (_, _, trlr2) = sender.seal([0x44_u8; 12].into(), prefix, plain, &mut cipher2).unwrap();
let old_hdr = SecurityHeader::<AuthEncFmt> {
spi: 0x3303,
iv: [0x44_u8; 12].into(),
sn: Array::from([0x00, 0x00, 0x00, 0x01]),
pad_len: 0,
};
let result = receiver.open(prefix, &mut cipher2, old_hdr, trlr2);
assert!(result.is_err());
}
#[test]
fn authenc_rejects_wrong_spi() {
let key = ConstKey::<BIT128>::new([0x77_u8; 16]);
let (mut sender, mut receiver) = authenc_pair(&key, 16, None);
let iv: Array<u8, _> = [0x33_u8; 12].into();
let prefix = b"prefix";
let plain = b"spi-test";
let mut cipher = vec![0_u8; plain.len()];
let (_, mut hdr, trlr) = sender.seal(iv, prefix, plain, &mut cipher).unwrap();
hdr.spi = 0x9999;
let result = receiver.open(prefix, &mut cipher, hdr, trlr);
assert!(result.is_err());
}
#[test]
fn auth_sign_verify_with_abm() {
let key = ConstKey::<BIT512>::new([0x11_u8; 64]);
let abm: [u8; 11] = [0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
let (mut sender, mut receiver) = auth_pair(&key, 16, Some(&abm));
let prefix = b"\x01\x02\x03\x04\x05";
let (hdr, trlr) = sender.sign(prefix, b"abm-auth-test").unwrap();
let verify = receiver.verify(prefix, b"abm-auth-test", hdr, trlr).unwrap();
assert_matches!(verify, VerifyMacResult::Ok);
}
#[test]
fn auth_abm_changes_mac() {
let key = ConstKey::<BIT512>::new([0x11_u8; 64]);
let abm: [u8; 11] = [0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
let (mut sa_abm, _) = auth_pair(&key, 16, Some(&abm));
let (mut sa_plain, _) = auth_pair(&key, 16, None);
let prefix = b"\x01\x02\x03\x04\x05";
let (_, trlr_abm) = sa_abm.sign(prefix, b"same-payload").unwrap();
let (_, trlr_plain) = sa_plain.sign(prefix, b"same-payload").unwrap();
assert_ne!(*trlr_abm.mac, *trlr_plain.mac);
}
#[test]
fn abm_masked_prefix_ignored_on_verify() {
let key = ConstKey::<BIT512>::new([0x11_u8; 64]);
let abm: [u8; 11] = [0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
let (mut sender, mut receiver) = auth_pair(&key, 16, Some(&abm));
let prefix_a = b"\x01\x02\xAA\x04\x05";
let prefix_b = b"\x01\x02\xBB\x04\x05";
let payload = b"masked-byte-differs";
let (hdr, trlr) = sender.sign(prefix_a, payload).unwrap();
let verify = receiver.verify(prefix_b, payload, hdr, trlr).unwrap();
assert_matches!(verify, VerifyMacResult::Ok);
}
#[test]
fn authenc_seal_open_with_abm() {
let key = ConstKey::<BIT128>::new([0x77_u8; 16]);
let mut abm = [0xFF_u8; 23];
abm[1] = 0x00;
let (mut sender, mut receiver) = authenc_pair(&key, 16, Some(&abm));
let iv: Array<u8, _> = [0x33_u8; 12].into();
let prefix = b"\x01\x02\x03\x04\x05";
let plain = b"abm-authenc-test";
let mut cipher = vec![0_u8; plain.len()];
let (_, hdr, trlr) = sender.seal(iv, prefix, plain, &mut cipher).unwrap();
let (recovered_len, verify) = receiver.open(prefix, &mut cipher, hdr, trlr).unwrap();
assert_matches!(verify, VerifyMacResult::Ok);
assert_eq!(&cipher[..recovered_len], plain);
}
#[test]
fn abm_too_short_rejected() {
let key = ConstKey::<BIT512>::new([0x11_u8; 64]);
let abm: [u8; 8] = [0xFF; 8];
let (mut sender, _) = auth_pair(&key, 16, Some(&abm));
let prefix = b"\x01\x02\x03\x04\x05";
let result = sender.sign(prefix, b"short-abm-test");
assert_matches!(result, Err(SaOperationError::AbmTooShort { abm_len: 8, required: 11 }));
}
fn authenc_ctr_pair<'a>(
key: &'a ConstKey<BIT128>,
sn_window: u16,
abm: Option<&'a [u8]>,
) -> (AuthEncCtrSA<'a>, AuthEncCtrSA<'a>) {
let sa =
|| SecurityAssociation::new_authenc_ctr(AesGcm::default(), 0x4404, key, sn_window, abm);
(sa(), sa())
}
#[test]
fn authenc_ctr_seal_open() {
let key = ConstKey::<BIT128>::new([0x77_u8; 16]);
let (mut sender, mut receiver) = authenc_ctr_pair(&key, 16, None);
let prefix = b"ctr-prefix";
let plain = b"ctr-authenc-payload";
let mut cipher = vec![0_u8; plain.len()];
let (hdr, trlr) = sender.seal_ctr(prefix, plain, &mut cipher).unwrap();
assert_eq!(hdr.spi, 0x4404);
let (recovered_len, verify) = receiver.open_ctr(prefix, &mut cipher, hdr, trlr).unwrap();
assert_matches!(verify, VerifyMacResult::Ok);
assert_eq!(&cipher[..recovered_len], plain);
}
#[test]
fn authenc_ctr_iv_increments() {
let key = ConstKey::<BIT128>::new([0x77_u8; 16]);
let (mut sender, _) = authenc_ctr_pair(&key, 16, None);
let mut cipher1 = vec![0_u8; 8];
let (hdr1, _) = sender.seal_ctr(b"", b"msg-one!", &mut cipher1).unwrap();
let mut cipher2 = vec![0_u8; 8];
let (hdr2, _) = sender.seal_ctr(b"", b"msg-two!", &mut cipher2).unwrap();
assert_ne!(hdr1.iv, hdr2.iv);
let mut buf = [0u8; 8];
buf.copy_from_slice(&hdr1.iv[4..]);
assert_eq!(u64::from_be_bytes(buf), 1);
}
#[test]
fn authenc_ctr_rejects_replay() {
let key = ConstKey::<BIT128>::new([0x77_u8; 16]);
let (mut sender, mut receiver) = authenc_ctr_pair(&key, 4, None);
let plain = b"ctr-replay";
let mut cipher = vec![0_u8; plain.len()];
let (hdr1, trlr1) = sender.seal_ctr(b"", plain, &mut cipher).unwrap();
let (_, verify) = receiver.open_ctr(b"", &mut cipher, hdr1.clone(), trlr1).unwrap();
assert_matches!(verify, VerifyMacResult::Ok);
let mut cipher2 = vec![0_u8; plain.len()];
let (_, trlr2) = sender.seal_ctr(b"", plain, &mut cipher2).unwrap();
let replayed_hdr = SecurityHeader::<AuthEncCtrFmt> {
spi: 0x4404,
iv: hdr1.iv,
sn: Array::default(),
pad_len: 0,
};
let result = receiver.open_ctr(b"", &mut cipher2, replayed_hdr, trlr2);
assert!(result.is_err());
}
#[test]
fn authenc_ctr_rejects_tampered() {
let key = ConstKey::<BIT128>::new([0x77_u8; 16]);
let (mut sender, mut receiver) = authenc_ctr_pair(&key, 16, None);
let mut cipher = vec![0_u8; 10];
let (hdr, trlr) = sender.seal_ctr(b"", b"tamper-ctr", &mut cipher).unwrap();
cipher[0] ^= 0xFF;
let (_, verify) = receiver.open_ctr(b"", &mut cipher, hdr, trlr).unwrap();
assert_matches!(verify, VerifyMacResult::InvalidMac);
}
#[test]
fn authenc_ctr_sliding_window() {
let key = ConstKey::<BIT128>::new([0x77_u8; 16]);
let (mut sender, mut receiver) = authenc_ctr_pair(&key, 4, None);
for _ in 0..10 {
let mut cipher = vec![0_u8; 7];
let (hdr, trlr) = sender.seal_ctr(b"", b"sliding", &mut cipher).unwrap();
let (_, verify) = receiver.open_ctr(b"", &mut cipher, hdr, trlr).unwrap();
assert_matches!(verify, VerifyMacResult::Ok);
}
}
}