use crate::core::{
AsAuth, AsAuthEnc, AsEnc, AuthEncProvider, AuthEncSpec, AuthProvider, EmptyKey, EncProvider,
EncSpec, SDLSFrameFormat, SecurityAssociation, ServiceKind, ServiceProviderGeneric,
};
use core::cell::Cell;
use core::fmt::{Debug, Formatter};
use hybrid_array::ArraySize;
use typenum::{NonZero, U0, Zero};
use super::error::SaManagementError;
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SaState {
Provisioned,
Operational,
Retired,
}
pub struct ManagedSa<'a, S: ServiceProviderGeneric, F: SDLSFrameFormat, N: ArraySize> {
state: Cell<SaState>,
sa: SecurityAssociation<'a, S, F, N>,
}
impl<S: ServiceProviderGeneric, F: SDLSFrameFormat, N: ArraySize> Debug for ManagedSa<'_, S, F, N> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ManagedSa")
.field("state", &self.state.get())
.field("spi", &self.sa.spi())
.finish()
}
}
impl<'a, S: ServiceProviderGeneric, F: SDLSFrameFormat, N: ArraySize> ManagedSa<'a, S, F, N> {
#[inline]
#[must_use]
pub fn state(&self) -> SaState { self.state.get() }
#[inline]
#[must_use]
pub fn spi(&self) -> u16 { self.sa.spi() }
#[inline]
#[must_use]
pub const fn service_type(&self) -> ServiceKind { self.sa.service_type() }
#[inline]
#[must_use]
pub fn is_provisioned(&self) -> bool { matches!(self.state.get(), SaState::Provisioned) }
#[inline]
#[must_use]
pub fn is_operational(&self) -> bool { matches!(self.state.get(), SaState::Operational) }
#[inline]
#[must_use]
pub fn is_retired(&self) -> bool { matches!(self.state.get(), SaState::Retired) }
#[inline]
pub fn start(&self) -> Result<(), SaManagementError> {
if self.is_provisioned() {
self.state.set(SaState::Operational);
Ok(())
} else {
Err(SaManagementError::InvalidTransition {
from: self.state.get(),
to: SaState::Operational,
})
}
}
#[inline]
pub fn stop(&self) -> Result<(), SaManagementError> {
if self.is_operational() {
self.state.set(SaState::Retired);
Ok(())
} else {
Err(SaManagementError::InvalidTransition {
from: self.state.get(),
to: SaState::Retired,
})
}
}
#[inline]
pub fn get_operational(&self) -> Result<&SecurityAssociation<'a, S, F, N>, SaManagementError> {
if self.is_operational() { Ok(&self.sa) } else { Err(SaManagementError::NotOperational) }
}
#[inline]
pub fn get_operational_mut(
&mut self,
) -> Result<&mut SecurityAssociation<'a, S, F, N>, SaManagementError> {
if self.is_operational() {
Ok(&mut self.sa)
} else {
Err(SaManagementError::NotOperational)
}
}
#[inline]
#[must_use]
pub fn inner(&self) -> &SecurityAssociation<'a, S, F, N> { &self.sa }
#[inline]
pub fn inner_mut(&mut self) -> &mut SecurityAssociation<'a, S, F, N> { &mut self.sa }
}
impl<'a, S, F> ManagedSa<'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) -> Self {
Self {
state: Cell::new(SaState::Provisioned),
sa: SecurityAssociation::new_enc(service, spi, &EmptyKey),
}
}
}
impl<'a, S, F> ManagedSa<'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, sn_window: u16, abm: Option<&'a [u8]>) -> Self {
Self {
state: Cell::new(SaState::Provisioned),
sa: SecurityAssociation::new_auth(service, spi, &EmptyKey, sn_window, abm),
}
}
}
impl<'a, S, F> ManagedSa<'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, sn_window: u16, abm: Option<&'a [u8]>) -> Self {
Self {
state: Cell::new(SaState::Provisioned),
sa: SecurityAssociation::new_authenc(service, spi, &EmptyKey, sn_window, abm),
}
}
}
impl<'a, S, F> ManagedSa<'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, sn_window: u16, abm: Option<&'a [u8]>) -> Self {
Self {
state: Cell::new(SaState::Provisioned),
sa: SecurityAssociation::new_authenc_ctr(service, spi, &EmptyKey, sn_window, abm),
}
}
}
#[cfg(all(test, feature = "softcrypto"))]
mod tests {
use super::*;
use crate::core::{BIT128, Key};
#[test]
fn empty_key_returns_none() {
let k = EmptyKey;
assert_eq!(Key::<BIT128>::size(&k), 0);
assert_eq!(Key::<BIT128>::get(&k), None);
}
#[test]
fn new_sa_is_provisioned() {
let msa = make_test_enc_sa();
assert_eq!(msa.state(), SaState::Provisioned);
assert!(msa.is_provisioned());
assert!(!msa.is_operational());
assert!(!msa.is_retired());
}
#[test]
fn start_from_provisioned() {
let msa = make_test_enc_sa();
assert!(msa.start().is_ok());
assert_eq!(msa.state(), SaState::Operational);
}
#[test]
fn start_from_operational_fails() {
let msa = make_test_enc_sa();
msa.start().unwrap();
assert_eq!(
msa.start(),
Err(SaManagementError::InvalidTransition {
from: SaState::Operational,
to: SaState::Operational,
})
);
}
#[test]
fn start_from_retired_fails() {
let msa = make_test_enc_sa();
msa.start().unwrap();
msa.stop().unwrap();
assert_eq!(
msa.start(),
Err(SaManagementError::InvalidTransition {
from: SaState::Retired,
to: SaState::Operational
})
);
}
#[test]
fn stop_from_operational() {
let msa = make_test_enc_sa();
msa.start().unwrap();
assert!(msa.stop().is_ok());
assert_eq!(msa.state(), SaState::Retired);
}
#[test]
fn stop_from_provisioned_fails() {
let msa = make_test_enc_sa();
assert_eq!(
msa.stop(),
Err(SaManagementError::InvalidTransition {
from: SaState::Provisioned,
to: SaState::Retired
})
);
}
#[test]
fn stop_from_retired_fails() {
let msa = make_test_enc_sa();
msa.start().unwrap();
msa.stop().unwrap();
assert_eq!(
msa.stop(),
Err(SaManagementError::InvalidTransition {
from: SaState::Retired,
to: SaState::Retired
})
);
}
#[test]
fn get_operational_succeeds() {
let msa = make_test_enc_sa();
msa.start().unwrap();
assert!(msa.get_operational().is_ok());
assert_eq!(msa.get_operational().unwrap().spi(), 0x1001);
}
#[test]
fn get_operational_fails_provisioned() {
let msa = make_test_enc_sa();
assert_eq!(msa.get_operational().err(), Some(SaManagementError::NotOperational));
}
#[test]
fn get_operational_fails_retired() {
let msa = make_test_enc_sa();
msa.start().unwrap();
msa.stop().unwrap();
assert_eq!(msa.get_operational().err(), Some(SaManagementError::NotOperational));
}
#[test]
fn get_operational_mut_succeeds() {
let mut msa = make_test_enc_sa();
msa.start().unwrap();
assert!(msa.get_operational_mut().is_ok());
}
#[test]
fn inner_access_always_works() {
let msa = make_test_enc_sa();
assert_eq!(msa.inner().spi(), 0x1001);
msa.start().unwrap();
assert_eq!(msa.inner().spi(), 0x1001);
msa.stop().unwrap();
assert_eq!(msa.inner().spi(), 0x1001);
}
#[test]
fn spi_delegation() {
let msa = make_test_enc_sa();
assert_eq!(msa.spi(), 0x1001);
}
use crate::softcrypto::AesCbc;
use crate::testfmt::EncFmt;
use typenum::U16;
fn make_test_enc_sa()
-> ManagedSa<'static, AsEnc<AesCbc<aes::Aes128, cipher::block_padding::Pkcs7>>, EncFmt, U0>
{
ManagedSa::new_enc(AesCbc::default(), 0x1001)
}
#[test]
fn set_key_then_operate() {
use crate::core::ConstKey;
use hybrid_array::Array;
use std::boxed::Box;
let key: &'static ConstKey<BIT128> = Box::leak(Box::new(ConstKey::new([
0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf,
0x4f, 0x3c,
])));
let mut msa = make_test_enc_sa();
assert!(msa.get_operational().is_err());
msa.inner_mut().set_key(key);
msa.start().unwrap();
assert!(msa.is_operational());
let iv: Array<u8, U16> = [0u8; 16].into();
let plain = b"managed-sa-test!";
let mut cipher = std::vec![0u8; 32];
let (_, hdr) = msa.get_operational().unwrap().encrypt(iv, plain, &mut cipher).unwrap();
let written = plain.len() + usize::from(hdr.pad_len);
let mut buf = cipher[..written].to_vec();
let recovered = msa.get_operational().unwrap().decrypt(hdr, &mut buf).unwrap();
assert_eq!(&buf[..recovered], plain);
msa.stop().unwrap();
assert!(msa.get_operational().is_err());
}
}