use crate::error::{Error, SecureSessionError};
use crate::packet::{Address, ControlByte, CtrlFlags, PacketBuilder, ParsedPacket, Scb, ScsType};
use crate::secure::session::{Secure, Session};
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
AcuToPd,
PdToAcu,
}
impl Direction {
pub const fn scs_for(self, encrypt: bool) -> ScsType {
match (self, encrypt) {
(Self::AcuToPd, false) => ScsType::Scs15,
(Self::AcuToPd, true) => ScsType::Scs17,
(Self::PdToAcu, false) => ScsType::Scs16,
(Self::PdToAcu, true) => ScsType::Scs18,
}
}
}
pub fn seal(
session: &mut Session<Secure>,
addr: Address,
sqn: crate::packet::Sqn,
direction: Direction,
encrypt: bool,
code: u8,
data: &[u8],
) -> Result<Vec<u8>, Error> {
let ty = direction.scs_for(encrypt);
let scb = Scb::new(ty, []);
let payload: Vec<u8> = if encrypt {
session.seal_data(data)
} else {
data.to_vec()
};
let builder = PacketBuilder {
addr,
ctrl: ControlByte::new(sqn, CtrlFlags::USE_CRC | CtrlFlags::HAS_SCB),
scb: Some(scb),
code,
data: payload,
};
builder.encode_with_mac(|bytes| {
let full = session.mac(bytes);
let mut tag = [0u8; 4];
tag.copy_from_slice(&full[..4]);
tag
})
}
pub fn unseal(
session: &mut Session<Secure>,
parsed: &ParsedPacket<'_>,
raw: &[u8],
) -> Result<Vec<u8>, Error> {
let scb = parsed
.scb
.ok_or(Error::SecureSession(SecureSessionError::NotSecure))?;
if !scb.ty.has_mac() {
return Err(Error::SecureSession(SecureSessionError::NotSecure));
}
let trailer_len = if parsed.ctrl.use_crc() { 2 } else { 1 };
let mac_offset = raw
.len()
.checked_sub(trailer_len + crate::packet::MAC_LEN)
.ok_or(Error::ShortMac)?;
let mac_bytes = parsed.mac.ok_or(Error::ShortMac)?;
let plaintext = if scb.ty.is_encrypted() {
session.open_data(parsed.data).map_err(Error::from)?
} else {
parsed.data.to_vec()
};
session
.verify(&raw[..mac_offset], &mac_bytes)
.map_err(Error::from)?;
Ok(plaintext)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packet::Sqn;
use crate::reply::CCrypt;
use crate::secure::SCBK_D;
use crate::secure::crypto::{SessionKeys, client_cryptogram};
use crate::secure::session::{Disconnected, Session};
fn handshake_pair() -> (Session<Secure>, Session<Secure>) {
let acu = Session::<Disconnected>::new(SCBK_D);
let rnd_a = [0xAAu8; 8];
let acu = acu.challenge(rnd_a);
let rnd_b = [0xBBu8; 8];
let cuid = [0xCCu8; 8];
let keys = SessionKeys::derive(&SCBK_D, &rnd_a);
let cc = client_cryptogram(&keys.s_enc, &rnd_a, &rnd_b);
let acu = acu
.receive_ccrypt(&CCrypt {
cuid,
rnd_b,
client_cryptogram: cc,
})
.unwrap();
let rmac = acu.initial_rmac();
let acu = acu.confirm_rmac_i(&rmac).unwrap();
let pd = Session::<Disconnected>::new(SCBK_D).challenge(rnd_a);
let pd = pd
.receive_ccrypt(&CCrypt {
cuid,
rnd_b,
client_cryptogram: cc,
})
.unwrap();
let pd_rmac = pd.initial_rmac();
let pd = pd.confirm_rmac_i(&pd_rmac).unwrap();
assert_eq!(rmac, pd_rmac);
(acu, pd)
}
#[test]
fn seal_then_unseal_mac_only() {
let (mut acu, mut pd) = handshake_pair();
let bytes = seal(
&mut acu,
Address::pd(0x05).unwrap(),
Sqn::new(1).unwrap(),
Direction::AcuToPd,
false,
0x60, &[],
)
.unwrap();
let (parsed, _used) = ParsedPacket::parse(&bytes).unwrap();
let plain = unseal(&mut pd, &parsed, &bytes).unwrap();
assert!(plain.is_empty());
}
#[test]
fn seal_then_unseal_encrypted() {
let (mut acu, mut pd) = handshake_pair();
let payload = b"sensitive command data";
let bytes = seal(
&mut acu,
Address::pd(0x05).unwrap(),
Sqn::new(2).unwrap(),
Direction::AcuToPd,
true,
0x6E, payload,
)
.unwrap();
let (parsed, _used) = ParsedPacket::parse(&bytes).unwrap();
assert_eq!(parsed.data.len() % 16, 0);
assert_ne!(parsed.data, payload);
let plain = unseal(&mut pd, &parsed, &bytes).unwrap();
assert_eq!(plain, payload);
}
#[test]
fn tampered_mac_rejected() {
let (mut acu, mut pd) = handshake_pair();
let mut bytes = seal(
&mut acu,
Address::pd(0x05).unwrap(),
Sqn::new(1).unwrap(),
Direction::AcuToPd,
false, 0x60,
&[],
)
.unwrap();
let n = bytes.len();
let mac_byte = n - 2 - 4; bytes[mac_byte] ^= 0x01;
let crc = crate::packet::crc16(&bytes[..n - 2]);
bytes[n - 2..].copy_from_slice(&crc.to_le_bytes());
let (parsed, _used) = ParsedPacket::parse(&bytes).unwrap();
let err = unseal(&mut pd, &parsed, &bytes).unwrap_err();
assert!(matches!(err, Error::SecureSession(_)));
}
}