use crate::{Result, RpcError};
pub const TOK_ID_WRAP: u16 = 0x0504;
pub const WRAP_HEADER_LEN: usize = 16;
pub const AES_SHA1_CHECKSUM_LEN: usize = 12;
pub const AES_SHA1_AUTH_VALUE_LEN: usize = WRAP_HEADER_LEN + AES_SHA1_CHECKSUM_LEN;
pub mod wrap_flags {
pub const SENT_BY_ACCEPTOR: u8 = 0x01;
pub const SEALED: u8 = 0x02;
pub const ACCEPTOR_SUBKEY: u8 = 0x04;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WrapToken {
pub flags: u8,
pub ec: u16,
pub rrc: u16,
pub snd_seq: u64,
}
impl WrapToken {
pub fn sealed(sent_by_acceptor: bool, acceptor_subkey: bool, snd_seq: u64) -> Self {
let mut flags = wrap_flags::SEALED;
if sent_by_acceptor {
flags |= wrap_flags::SENT_BY_ACCEPTOR;
}
if acceptor_subkey {
flags |= wrap_flags::ACCEPTOR_SUBKEY;
}
WrapToken {
flags,
ec: 0,
rrc: 0,
snd_seq,
}
}
pub fn encode(&self) -> [u8; WRAP_HEADER_LEN] {
let mut out = [0u8; WRAP_HEADER_LEN];
out[0..2].copy_from_slice(&TOK_ID_WRAP.to_be_bytes());
out[2] = self.flags;
out[3] = 0xFF; out[4..6].copy_from_slice(&self.ec.to_be_bytes());
out[6..8].copy_from_slice(&self.rrc.to_be_bytes());
out[8..16].copy_from_slice(&self.snd_seq.to_be_bytes());
out
}
pub fn decode(buf: &[u8]) -> Result<Self> {
if buf.len() < WRAP_HEADER_LEN {
return Err(RpcError::Underrun {
need: WRAP_HEADER_LEN,
pos: buf.len(),
});
}
let tok_id = u16::from_be_bytes([buf[0], buf[1]]);
if tok_id != TOK_ID_WRAP {
return Err(RpcError::Protocol(format!(
"WRAP token TOK_ID {tok_id:#06x} != 0x0504"
)));
}
let flags = buf[2];
if flags & 0xF8 != 0 {
return Err(RpcError::Protocol(format!(
"WRAP token reserved flag bits set: {flags:#04x}"
)));
}
if buf[3] != 0xFF {
return Err(RpcError::Protocol(format!(
"WRAP token filler {:#04x} != 0xFF",
buf[3]
)));
}
Ok(WrapToken {
flags,
ec: u16::from_be_bytes([buf[4], buf[5]]),
rrc: u16::from_be_bytes([buf[6], buf[7]]),
snd_seq: u64::from_be_bytes(buf[8..16].try_into().unwrap()),
})
}
pub fn is_sealed(&self) -> bool {
self.flags & wrap_flags::SEALED != 0
}
pub fn is_from_acceptor(&self) -> bool {
self.flags & wrap_flags::SENT_BY_ACCEPTOR != 0
}
pub fn uses_acceptor_subkey(&self) -> bool {
self.flags & wrap_flags::ACCEPTOR_SUBKEY != 0
}
}
pub trait KrbSealer {
fn seal_pdu(&mut self, sign_over: &[u8], stub: &[u8]) -> (Vec<u8>, Vec<u8>);
fn unseal_pdu(
&mut self,
pdu_no_auth: &[u8],
stub_off: usize,
stub_len: usize,
auth_value: &[u8],
) -> Result<Vec<u8>>;
fn auth_value_len(&self) -> usize;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wrap_token_roundtrip() {
let t = WrapToken::sealed(false, true, 0x0102_0304_0506_0708);
let enc = t.encode();
assert_eq!(&enc[0..2], &[0x05, 0x04]); assert_eq!(enc[2], wrap_flags::SEALED | wrap_flags::ACCEPTOR_SUBKEY);
assert_eq!(enc[3], 0xFF); assert_eq!(&enc[8..16], &0x0102_0304_0506_0708u64.to_be_bytes());
let dec = WrapToken::decode(&enc).unwrap();
assert_eq!(dec, t);
}
#[test]
fn wrap_token_server_ack_direction() {
let t = WrapToken::sealed(true, false, 0);
let enc = t.encode();
assert_eq!(enc[2], wrap_flags::SEALED | wrap_flags::SENT_BY_ACCEPTOR);
let dec = WrapToken::decode(&enc).unwrap();
assert!(dec.is_from_acceptor());
assert!(dec.is_sealed());
assert!(!dec.uses_acceptor_subkey());
}
#[test]
fn hostile_short_header_rejected() {
let err = WrapToken::decode(&[0xFFu8; 15]).unwrap_err();
match err {
RpcError::Underrun { need, pos } => {
assert_eq!(need, WRAP_HEADER_LEN);
assert_eq!(pos, 15);
}
other => panic!("expected Underrun, got {other:?}"),
}
}
#[test]
fn hostile_wrong_tok_id_rejected() {
let mut buf = [0u8; WRAP_HEADER_LEN];
buf[0..2].copy_from_slice(&0x0404u16.to_be_bytes());
buf[3] = 0xFF;
match WrapToken::decode(&buf).unwrap_err() {
RpcError::Protocol(m) => assert!(m.contains("TOK_ID")),
other => panic!("expected Protocol, got {other:?}"),
}
}
#[test]
fn hostile_reserved_flag_bits_rejected() {
let mut buf = [0u8; WRAP_HEADER_LEN];
buf[0..2].copy_from_slice(&TOK_ID_WRAP.to_be_bytes());
buf[2] = 0xF8; buf[3] = 0xFF;
match WrapToken::decode(&buf).unwrap_err() {
RpcError::Protocol(m) => assert!(m.contains("reserved flag")),
other => panic!("expected Protocol, got {other:?}"),
}
}
#[test]
fn hostile_bad_filler_rejected() {
let mut buf = [0u8; WRAP_HEADER_LEN];
buf[0..2].copy_from_slice(&TOK_ID_WRAP.to_be_bytes());
buf[2] = wrap_flags::SEALED;
buf[3] = 0x00;
match WrapToken::decode(&buf).unwrap_err() {
RpcError::Protocol(m) => assert!(m.contains("filler")),
other => panic!("expected Protocol, got {other:?}"),
}
}
#[test]
fn max_rrc_field_decodes_without_alloc() {
let mut buf = [0u8; WRAP_HEADER_LEN];
buf[0..2].copy_from_slice(&TOK_ID_WRAP.to_be_bytes());
buf[2] = wrap_flags::SEALED;
buf[3] = 0xFF;
buf[6..8].copy_from_slice(&u16::MAX.to_be_bytes());
let t = WrapToken::decode(&buf).unwrap();
assert_eq!(t.rrc, u16::MAX);
}
struct XorSealer {
key: u8,
client_seq: u64,
server_seq: u64,
}
impl KrbSealer for XorSealer {
fn seal_pdu(&mut self, _sign_over: &[u8], stub: &[u8]) -> (Vec<u8>, Vec<u8>) {
let sealed: Vec<u8> = stub.iter().map(|b| b ^ self.key).collect();
let mut av = WrapToken::sealed(false, false, self.client_seq)
.encode()
.to_vec();
av.extend_from_slice(&[0xABu8; AES_SHA1_CHECKSUM_LEN]); self.client_seq = self.client_seq.wrapping_add(1);
(sealed, av)
}
fn unseal_pdu(
&mut self,
pdu_no_auth: &[u8],
stub_off: usize,
stub_len: usize,
auth_value: &[u8],
) -> Result<Vec<u8>> {
if auth_value.len() != AES_SHA1_AUTH_VALUE_LEN {
return Err(RpcError::Protocol(format!(
"auth_value length {} != {AES_SHA1_AUTH_VALUE_LEN}",
auth_value.len()
)));
}
let tok = WrapToken::decode(&auth_value[..WRAP_HEADER_LEN])?;
if tok.snd_seq != self.server_seq {
return Err(RpcError::Protocol(format!(
"WRAP seq {} != expected {}",
tok.snd_seq, self.server_seq
)));
}
let sealed =
pdu_no_auth
.get(stub_off..stub_off + stub_len)
.ok_or(RpcError::Underrun {
need: stub_off + stub_len,
pos: pdu_no_auth.len(),
})?;
let plain: Vec<u8> = sealed.iter().map(|b| b ^ self.key).collect();
self.server_seq = self.server_seq.wrapping_add(1);
Ok(plain)
}
fn auth_value_len(&self) -> usize {
AES_SHA1_AUTH_VALUE_LEN
}
}
#[test]
fn trait_roundtrip_via_mock_sealer() {
let mut client = XorSealer {
key: 0x5A,
client_seq: 0,
server_seq: 0,
};
let mut server = XorSealer {
key: 0x5A,
client_seq: 0,
server_seq: 0,
};
let stub = b"NDR-marshaled-request-stub-payload".to_vec();
let sign_over = b"pdu-header + body + sec_trailer".to_vec();
let (sealed, av) = client.seal_pdu(&sign_over, &stub);
assert_eq!(sealed.len(), stub.len());
assert_eq!(av.len(), AES_SHA1_AUTH_VALUE_LEN);
let mut pdu_no_auth = sign_over.clone();
let stub_off = pdu_no_auth.len();
pdu_no_auth.extend_from_slice(&sealed);
let out = server
.unseal_pdu(&pdu_no_auth, stub_off, stub.len(), &av)
.unwrap();
assert_eq!(out, stub);
}
#[test]
fn trait_rejects_wrong_auth_value_length() {
let mut sealer = XorSealer {
key: 0,
client_seq: 0,
server_seq: 0,
};
let err = sealer.unseal_pdu(&[0u8; 40], 8, 16, &[0u8; 4]).unwrap_err();
match err {
RpcError::Protocol(m) => assert!(m.contains("length")),
other => panic!("expected Protocol, got {other:?}"),
}
}
}