use alloc::string::String;
use alloc::vec::Vec;
use zerodds_cdr::{BufferReader, BufferWriter, Endianness};
pub const INITIAL_CONTEXT_TOKEN_TAG: u8 = 0x60;
pub const GSSUP_OID_DER: &[u8] = &[0x06, 0x06, 0x67, 0x81, 0x02, 0x01, 0x01, 0x01];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GssupCredentialToken {
pub username: String,
pub password: String,
pub target_name: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GssupError {
Cdr(String),
Overflow,
}
impl GssupCredentialToken {
#[must_use]
pub fn new(username: String, password: String, target_name: Vec<u8>) -> Self {
Self {
username,
password,
target_name,
}
}
pub fn encode_encapsulation(&self, endianness: Endianness) -> Result<Vec<u8>, GssupError> {
let mut out = Vec::with_capacity(64);
out.push(match endianness {
Endianness::Big => 0,
Endianness::Little => 1,
});
let mut w = BufferWriter::new(endianness);
w.write_string(&self.username)
.map_err(|e| GssupError::Cdr(alloc::format!("{e:?}")))?;
w.write_string(&self.password)
.map_err(|e| GssupError::Cdr(alloc::format!("{e:?}")))?;
let n = u32::try_from(self.target_name.len()).map_err(|_| GssupError::Overflow)?;
w.write_u32(n)
.map_err(|e| GssupError::Cdr(alloc::format!("{e:?}")))?;
w.write_bytes(&self.target_name)
.map_err(|e| GssupError::Cdr(alloc::format!("{e:?}")))?;
out.extend_from_slice(w.as_bytes());
Ok(out)
}
pub fn decode_encapsulation(bytes: &[u8]) -> Result<Self, GssupError> {
if bytes.is_empty() {
return Err(GssupError::Cdr("empty encapsulation".into()));
}
let endianness = match bytes[0] {
0 => Endianness::Big,
1 => Endianness::Little,
_ => return Err(GssupError::Cdr("invalid endianness byte".into())),
};
let mut r = BufferReader::new(&bytes[1..], endianness);
let username = r
.read_string()
.map_err(|e| GssupError::Cdr(alloc::format!("{e:?}")))?;
let password = r
.read_string()
.map_err(|e| GssupError::Cdr(alloc::format!("{e:?}")))?;
let n = r
.read_u32()
.map_err(|e| GssupError::Cdr(alloc::format!("{e:?}")))? as usize;
let target_bytes = r
.read_bytes(n)
.map_err(|e| GssupError::Cdr(alloc::format!("{e:?}")))?;
Ok(Self {
username,
password,
target_name: target_bytes.to_vec(),
})
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn initial_context_token_tag_is_0x60() {
assert_eq!(INITIAL_CONTEXT_TOKEN_TAG, 0x60);
}
#[test]
fn gssup_oid_starts_with_der_tag() {
assert_eq!(GSSUP_OID_DER[0], 0x06);
assert_eq!(GSSUP_OID_DER[1], 0x06);
}
#[test]
fn token_round_trip_be() {
let t =
GssupCredentialToken::new("alice".into(), "swordfish".into(), b"REALM.LAB".to_vec());
let bytes = t.encode_encapsulation(Endianness::Big).unwrap();
assert_eq!(bytes[0], 0); let d = GssupCredentialToken::decode_encapsulation(&bytes).unwrap();
assert_eq!(d, t);
}
#[test]
fn token_round_trip_le() {
let t = GssupCredentialToken::new("bob".into(), "x".into(), b"R".to_vec());
let bytes = t.encode_encapsulation(Endianness::Little).unwrap();
assert_eq!(bytes[0], 1);
let d = GssupCredentialToken::decode_encapsulation(&bytes).unwrap();
assert_eq!(d, t);
}
#[test]
fn invalid_endianness_byte_is_diagnostic() {
let err =
GssupCredentialToken::decode_encapsulation(&[0xff, 0, 0, 0, 5, b'a']).unwrap_err();
assert!(matches!(err, GssupError::Cdr(_)));
}
}