- Add VfsFile: Send supertrait for Mutex compatibility - Fix SmbServerCommand: struct → Subcommand enum with Start variant - Fix tracing_subscriber::init() → try_init() to avoid panic when logger already initialized - Fix CLI subcommand name: smb-server → smb-start (flatten naming) - Add #[command(name = "smb-start")] for CLI disambiguation - Fix unused variable warnings (smb_fs.rs, smb_server_backend.rs) - Remove unused VfsFile imports (webdav.rs, scp_handler.rs) - Integration test: Docker smbclient verified (list, upload, read)
1632 lines
56 KiB
Rust
1632 lines
56 KiB
Rust
//! Kerberos ASN.1/DER message encoding and decoding.
|
|
//!
|
|
//! Hand-rolled ASN.1/DER for the specific Kerberos message structures needed
|
|
//! by an SMB2 client. Follows the same pattern as `spnego.rs`.
|
|
//!
|
|
//! References:
|
|
//! - RFC 4120: The Kerberos Network Authentication Service (V5)
|
|
//! - MS-KILE: Kerberos Protocol Extensions
|
|
|
|
use crate::auth::der::{der_tlv, parse_der_tlv};
|
|
use crate::auth::kerberos::crypto::EncryptionType;
|
|
use crate::Error;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ASN.1 tag constants
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const TAG_INTEGER: u8 = 0x02;
|
|
const TAG_BIT_STRING: u8 = 0x03;
|
|
const TAG_OCTET_STRING: u8 = 0x04;
|
|
const TAG_GENERAL_STRING: u8 = 0x1b;
|
|
const TAG_GENERALIZED_TIME: u8 = 0x18;
|
|
const TAG_SEQUENCE: u8 = 0x30;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Core types
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Kerberos principal name.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct PrincipalName {
|
|
/// Name type: KRB_NT_PRINCIPAL=1, KRB_NT_SRV_INST=2, etc.
|
|
pub name_type: i32,
|
|
/// Name components: for example, `["user"]` or `["cifs", "server.domain.com"]`.
|
|
pub name_string: Vec<String>,
|
|
}
|
|
|
|
/// Kerberos ticket (opaque to the client: we don't decrypt it).
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct Ticket {
|
|
/// Ticket version number (always 5).
|
|
pub tkt_vno: i32,
|
|
/// Realm of the ticket.
|
|
pub realm: String,
|
|
/// Service principal name.
|
|
pub sname: PrincipalName,
|
|
/// Encrypted part (opaque).
|
|
pub enc_part: EncryptedData,
|
|
/// Raw DER bytes of the ticket as received from the KDC.
|
|
/// Used to pass the ticket through to the AP-REQ verbatim,
|
|
/// avoiding re-encoding which could corrupt the encrypted data.
|
|
pub raw_bytes: Option<Vec<u8>>,
|
|
}
|
|
|
|
/// Generic encrypted data envelope.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct EncryptedData {
|
|
/// Encryption type identifier.
|
|
pub etype: i32,
|
|
/// Key version number (optional).
|
|
pub kvno: Option<i32>,
|
|
/// Ciphertext bytes.
|
|
pub cipher: Vec<u8>,
|
|
}
|
|
|
|
/// Pre-authentication data element.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct PaData {
|
|
/// Pre-authentication data type.
|
|
pub padata_type: i32,
|
|
/// Pre-authentication data value.
|
|
pub padata_value: Vec<u8>,
|
|
}
|
|
|
|
/// Parsed KDC-REP (AS-REP or TGS-REP).
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct KdcRep {
|
|
/// Message type: 11 = AS-REP, 13 = TGS-REP.
|
|
pub msg_type: i32,
|
|
/// Client realm.
|
|
pub crealm: String,
|
|
/// Client principal name.
|
|
pub cname: PrincipalName,
|
|
/// Ticket.
|
|
pub ticket: Ticket,
|
|
/// Encrypted part (to be decrypted by the client).
|
|
pub enc_part: EncryptedData,
|
|
}
|
|
|
|
/// Parsed decrypted EncKDCRepPart.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct EncKdcRepPart {
|
|
/// Session key.
|
|
pub key: EncryptionKey,
|
|
/// Nonce from the request.
|
|
pub nonce: u32,
|
|
/// Ticket flags as a bit field.
|
|
pub flags: u32,
|
|
/// Authentication time.
|
|
pub authtime: String,
|
|
/// Ticket end time.
|
|
pub endtime: String,
|
|
/// Service realm.
|
|
pub srealm: String,
|
|
/// Service principal name.
|
|
pub sname: PrincipalName,
|
|
}
|
|
|
|
/// Encryption key (keytype + key value).
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct EncryptionKey {
|
|
/// Key type (etype number).
|
|
pub keytype: i32,
|
|
/// Key value bytes.
|
|
pub keyvalue: Vec<u8>,
|
|
}
|
|
|
|
/// Parsed AP-REP (`APPLICATION [15]`).
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct ApRep {
|
|
/// Encrypted part (to be decrypted with the session key or subkey).
|
|
pub enc_part: EncryptedData,
|
|
}
|
|
|
|
/// Parsed decrypted EncAPRepPart.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct EncApRepPart {
|
|
/// Optional sub-session key from the server. If present, this overrides
|
|
/// the client's subkey as the session key for the application (SMB).
|
|
pub subkey: Option<EncryptionKey>,
|
|
/// Optional sequence number.
|
|
pub seq_number: Option<u32>,
|
|
}
|
|
|
|
/// Parsed KRB-ERROR message.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct KrbError {
|
|
/// Error code.
|
|
pub error_code: i32,
|
|
/// Client realm (optional).
|
|
pub crealm: Option<String>,
|
|
/// Server realm.
|
|
pub realm: String,
|
|
/// Server principal name.
|
|
pub sname: PrincipalName,
|
|
/// Error text (optional).
|
|
pub e_text: Option<String>,
|
|
/// Error data (optional).
|
|
pub e_data: Option<Vec<u8>>,
|
|
}
|
|
|
|
// Core DER encoding/decoding helpers (der_length, der_tlv, parse_der_length,
|
|
// parse_der_tlv) are in `crate::auth::der`. Imported at the top.
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// DER encoding helpers (Kerberos-specific)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Encode a context-specific constructed tag: `[tag_num]`.
|
|
fn der_context(tag_num: u8, data: &[u8]) -> Vec<u8> {
|
|
der_tlv(0xa0 | tag_num, data)
|
|
}
|
|
|
|
/// Encode an APPLICATION constructed tag: `[APPLICATION tag_num]`.
|
|
fn der_application(tag_num: u8, data: &[u8]) -> Vec<u8> {
|
|
der_tlv(0x60 | tag_num, data)
|
|
}
|
|
|
|
/// Encode an ASN.1 INTEGER (signed, big-endian, minimal bytes).
|
|
fn der_integer(val: i32) -> Vec<u8> {
|
|
let bytes = val.to_be_bytes();
|
|
// Find the first significant byte, keeping sign correct.
|
|
let mut start = 0;
|
|
if val >= 0 {
|
|
// Skip leading 0x00 bytes, but keep one if the next byte has the high bit set.
|
|
while start < 3 && bytes[start] == 0x00 && bytes[start + 1] & 0x80 == 0 {
|
|
start += 1;
|
|
}
|
|
} else {
|
|
// Skip leading 0xff bytes, but keep one if the next byte doesn't have the high bit set.
|
|
while start < 3 && bytes[start] == 0xff && bytes[start + 1] & 0x80 != 0 {
|
|
start += 1;
|
|
}
|
|
}
|
|
der_tlv(TAG_INTEGER, &bytes[start..])
|
|
}
|
|
|
|
/// Encode an unsigned 32-bit value as ASN.1 INTEGER.
|
|
fn der_integer_u32(val: u32) -> Vec<u8> {
|
|
// Treat as i64 to handle the full u32 range without sign issues.
|
|
let val64 = val as i64;
|
|
let bytes = val64.to_be_bytes();
|
|
let mut start = 0;
|
|
while start < 7 && bytes[start] == 0x00 && bytes[start + 1] & 0x80 == 0 {
|
|
start += 1;
|
|
}
|
|
der_tlv(TAG_INTEGER, &bytes[start..])
|
|
}
|
|
|
|
/// Encode a DER OCTET STRING.
|
|
fn der_octet_string(data: &[u8]) -> Vec<u8> {
|
|
der_tlv(TAG_OCTET_STRING, data)
|
|
}
|
|
|
|
/// Encode a DER GeneralString.
|
|
fn der_general_string(s: &str) -> Vec<u8> {
|
|
der_tlv(TAG_GENERAL_STRING, s.as_bytes())
|
|
}
|
|
|
|
/// Encode a DER GeneralizedTime (for example, `"20260408120000Z"`).
|
|
fn der_generalized_time(time: &str) -> Vec<u8> {
|
|
der_tlv(TAG_GENERALIZED_TIME, time.as_bytes())
|
|
}
|
|
|
|
/// Encode a DER BIT STRING. `bits` is the raw bytes; `unused` is the number
|
|
/// of unused bits in the last byte (usually 0 for 32-bit flags).
|
|
fn der_bit_string(bits: &[u8], unused: u8) -> Vec<u8> {
|
|
let mut data = vec![unused];
|
|
data.extend_from_slice(bits);
|
|
der_tlv(TAG_BIT_STRING, &data)
|
|
}
|
|
|
|
/// Encode a DER SEQUENCE from pre-encoded items.
|
|
fn der_sequence(items: &[&[u8]]) -> Vec<u8> {
|
|
let mut contents = Vec::new();
|
|
for item in items {
|
|
contents.extend_from_slice(item);
|
|
}
|
|
der_tlv(TAG_SEQUENCE, &contents)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// DER parsing helpers (Kerberos-specific)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Parse all TLV elements in a SEQUENCE body, returning `(tag, value)` pairs.
|
|
fn parse_sequence_fields(data: &[u8]) -> Result<Vec<(u8, Vec<u8>)>, Error> {
|
|
let mut fields = Vec::new();
|
|
let mut pos = 0;
|
|
while pos < data.len() {
|
|
let (tag, value, consumed) = parse_der_tlv(&data[pos..])?;
|
|
fields.push((tag, value.to_vec()));
|
|
pos += consumed;
|
|
}
|
|
Ok(fields)
|
|
}
|
|
|
|
/// Parse a DER INTEGER value (already unwrapped from TLV), returning i32.
|
|
fn parse_integer_value(data: &[u8]) -> Result<i32, Error> {
|
|
if data.is_empty() {
|
|
return Err(Error::invalid_data("Kerberos: empty INTEGER"));
|
|
}
|
|
// Sign-extend from arbitrary-length big-endian.
|
|
let negative = data[0] & 0x80 != 0;
|
|
let mut val: i64 = if negative { -1 } else { 0 };
|
|
for &b in data {
|
|
val = (val << 8) | (b as i64);
|
|
}
|
|
Ok(val as i32)
|
|
}
|
|
|
|
/// Parse a DER INTEGER TLV, returning i32.
|
|
fn parse_der_integer(data: &[u8]) -> Result<i32, Error> {
|
|
let (tag, value, _) = parse_der_tlv(data)?;
|
|
if tag != TAG_INTEGER {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected INTEGER (0x02), got 0x{tag:02x}"
|
|
)));
|
|
}
|
|
parse_integer_value(value)
|
|
}
|
|
|
|
/// Parse a DER INTEGER TLV, returning u32.
|
|
fn parse_der_integer_u32(data: &[u8]) -> Result<u32, Error> {
|
|
let val = parse_der_integer(data)?;
|
|
Ok(val as u32)
|
|
}
|
|
|
|
/// Parse a DER OCTET STRING TLV, returning the raw bytes.
|
|
fn parse_der_octet_string(data: &[u8]) -> Result<Vec<u8>, Error> {
|
|
let (tag, value, _) = parse_der_tlv(data)?;
|
|
if tag != TAG_OCTET_STRING {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected OCTET STRING (0x04), got 0x{tag:02x}"
|
|
)));
|
|
}
|
|
Ok(value.to_vec())
|
|
}
|
|
|
|
/// Parse a DER GeneralString TLV, returning the string.
|
|
fn parse_der_general_string(data: &[u8]) -> Result<String, Error> {
|
|
let (tag, value, _) = parse_der_tlv(data)?;
|
|
if tag != TAG_GENERAL_STRING {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected GeneralString (0x1b), got 0x{tag:02x}"
|
|
)));
|
|
}
|
|
String::from_utf8(value.to_vec())
|
|
.map_err(|_| Error::invalid_data("Kerberos: invalid UTF-8 in GeneralString"))
|
|
}
|
|
|
|
/// Parse a DER GeneralizedTime TLV, returning the time string.
|
|
fn parse_der_generalized_time(data: &[u8]) -> Result<String, Error> {
|
|
let (tag, value, _) = parse_der_tlv(data)?;
|
|
if tag != TAG_GENERALIZED_TIME {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected GeneralizedTime (0x18), got 0x{tag:02x}"
|
|
)));
|
|
}
|
|
String::from_utf8(value.to_vec())
|
|
.map_err(|_| Error::invalid_data("Kerberos: invalid UTF-8 in GeneralizedTime"))
|
|
}
|
|
|
|
/// Parse a DER BIT STRING TLV, returning the raw bit bytes (without the
|
|
/// unused-bits prefix byte) and the number of unused bits.
|
|
fn parse_der_bit_string(data: &[u8]) -> Result<(Vec<u8>, u8), Error> {
|
|
let (tag, value, _) = parse_der_tlv(data)?;
|
|
if tag != TAG_BIT_STRING {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected BIT STRING (0x03), got 0x{tag:02x}"
|
|
)));
|
|
}
|
|
if value.is_empty() {
|
|
return Err(Error::invalid_data("Kerberos: empty BIT STRING"));
|
|
}
|
|
let unused = value[0];
|
|
Ok((value[1..].to_vec(), unused))
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Encoding compound types
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Encode a PrincipalName as DER.
|
|
fn encode_principal_name(name: &PrincipalName) -> Vec<u8> {
|
|
// PrincipalName ::= SEQUENCE {
|
|
// name-type [0] Int32,
|
|
// name-string [1] SEQUENCE OF KerberosString (GeneralString)
|
|
// }
|
|
let name_type = der_context(0, &der_integer(name.name_type));
|
|
let name_strings: Vec<Vec<u8>> = name
|
|
.name_string
|
|
.iter()
|
|
.map(|s| der_general_string(s))
|
|
.collect();
|
|
let name_refs: Vec<&[u8]> = name_strings.iter().map(|v| v.as_slice()).collect();
|
|
let name_seq = der_sequence(&name_refs);
|
|
let name_string = der_context(1, &name_seq);
|
|
der_sequence(&[&name_type, &name_string])
|
|
}
|
|
|
|
/// Encode an EncryptedData as DER.
|
|
fn encode_encrypted_data(ed: &EncryptedData) -> Vec<u8> {
|
|
// EncryptedData ::= SEQUENCE {
|
|
// etype [0] Int32,
|
|
// kvno [1] UInt32 OPTIONAL,
|
|
// cipher [2] OCTET STRING
|
|
// }
|
|
let etype = der_context(0, &der_integer(ed.etype));
|
|
let cipher = der_context(2, &der_octet_string(&ed.cipher));
|
|
if let Some(kvno) = ed.kvno {
|
|
let kvno_enc = der_context(1, &der_integer(kvno));
|
|
der_sequence(&[&etype, &kvno_enc, &cipher])
|
|
} else {
|
|
der_sequence(&[&etype, &cipher])
|
|
}
|
|
}
|
|
|
|
/// Encode a Ticket as DER (`APPLICATION [1]`).
|
|
fn encode_ticket(ticket: &Ticket) -> Vec<u8> {
|
|
// Ticket ::= [APPLICATION 1] SEQUENCE {
|
|
// tkt-vno [0] INTEGER (5),
|
|
// realm [1] Realm (GeneralString),
|
|
// sname [2] PrincipalName,
|
|
// enc-part [3] EncryptedData
|
|
// }
|
|
let tkt_vno = der_context(0, &der_integer(ticket.tkt_vno));
|
|
let realm = der_context(1, &der_general_string(&ticket.realm));
|
|
let sname = der_context(2, &encode_principal_name(&ticket.sname));
|
|
let enc_part = der_context(3, &encode_encrypted_data(&ticket.enc_part));
|
|
let seq = der_sequence(&[&tkt_vno, &realm, &sname, &enc_part]);
|
|
der_application(1, &seq)
|
|
}
|
|
|
|
/// Encode a PaData as DER.
|
|
fn encode_pa_data(pa: &PaData) -> Vec<u8> {
|
|
// PA-DATA ::= SEQUENCE {
|
|
// padata-type [1] Int32,
|
|
// padata-value [2] OCTET STRING
|
|
// }
|
|
let padata_type = der_context(1, &der_integer(pa.padata_type));
|
|
let padata_value = der_context(2, &der_octet_string(&pa.padata_value));
|
|
der_sequence(&[&padata_type, &padata_value])
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Parsing compound types
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Parse a PrincipalName from DER bytes.
|
|
fn parse_principal_name(data: &[u8]) -> Result<PrincipalName, Error> {
|
|
let (tag, seq_data, _) = parse_der_tlv(data)?;
|
|
if tag != TAG_SEQUENCE {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected SEQUENCE for PrincipalName, got 0x{tag:02x}"
|
|
)));
|
|
}
|
|
let fields = parse_sequence_fields(seq_data)?;
|
|
let mut name_type = None;
|
|
let mut name_string = Vec::new();
|
|
for (ftag, fvalue) in &fields {
|
|
match ftag {
|
|
0xa0 => name_type = Some(parse_der_integer(fvalue)?),
|
|
0xa1 => {
|
|
// SEQUENCE OF GeneralString
|
|
let (stag, sdata, _) = parse_der_tlv(fvalue)?;
|
|
if stag != TAG_SEQUENCE {
|
|
return Err(Error::invalid_data(
|
|
"Kerberos: expected SEQUENCE for name-string",
|
|
));
|
|
}
|
|
let mut pos = 0;
|
|
while pos < sdata.len() {
|
|
let (_, sv, consumed) = parse_der_tlv(&sdata[pos..])?;
|
|
name_string.push(String::from_utf8(sv.to_vec()).map_err(|_| {
|
|
Error::invalid_data("Kerberos: invalid UTF-8 in name-string")
|
|
})?);
|
|
pos += consumed;
|
|
}
|
|
}
|
|
_ => {} // ignore unknown fields
|
|
}
|
|
}
|
|
Ok(PrincipalName {
|
|
name_type: name_type
|
|
.ok_or_else(|| Error::invalid_data("Kerberos: missing name-type in PrincipalName"))?,
|
|
name_string,
|
|
})
|
|
}
|
|
|
|
/// Parse an EncryptedData from DER bytes.
|
|
fn parse_encrypted_data(data: &[u8]) -> Result<EncryptedData, Error> {
|
|
let (tag, seq_data, _) = parse_der_tlv(data)?;
|
|
if tag != TAG_SEQUENCE {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected SEQUENCE for EncryptedData, got 0x{tag:02x}"
|
|
)));
|
|
}
|
|
let fields = parse_sequence_fields(seq_data)?;
|
|
let mut etype = None;
|
|
let mut kvno = None;
|
|
let mut cipher = None;
|
|
for (ftag, fvalue) in &fields {
|
|
match ftag {
|
|
0xa0 => etype = Some(parse_der_integer(fvalue)?),
|
|
0xa1 => kvno = Some(parse_der_integer(fvalue)?),
|
|
0xa2 => cipher = Some(parse_der_octet_string(fvalue)?),
|
|
_ => {}
|
|
}
|
|
}
|
|
Ok(EncryptedData {
|
|
etype: etype
|
|
.ok_or_else(|| Error::invalid_data("Kerberos: missing etype in EncryptedData"))?,
|
|
kvno,
|
|
cipher: cipher
|
|
.ok_or_else(|| Error::invalid_data("Kerberos: missing cipher in EncryptedData"))?,
|
|
})
|
|
}
|
|
|
|
/// Parse a Ticket from DER bytes (expects `APPLICATION [1]` wrapper).
|
|
///
|
|
/// Stores the raw DER bytes so the ticket can be passed through to the
|
|
/// AP-REQ verbatim. Re-encoding the ticket from parsed fields can produce
|
|
/// different DER bytes (e.g., different length encoding, field order), which
|
|
/// corrupts the encrypted data and causes the server to fail decryption.
|
|
pub fn parse_ticket(data: &[u8]) -> Result<Ticket, Error> {
|
|
let (tag, inner, total_consumed) = parse_der_tlv(data)?;
|
|
// APPLICATION [1] = 0x61
|
|
if tag != 0x61 {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected APPLICATION [1] (0x61) for Ticket, got 0x{tag:02x}"
|
|
)));
|
|
}
|
|
|
|
// Store raw bytes for verbatim pass-through.
|
|
let raw_bytes = data[..total_consumed].to_vec();
|
|
|
|
let (seq_tag, seq_data, _) = parse_der_tlv(inner)?;
|
|
if seq_tag != TAG_SEQUENCE {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected SEQUENCE in Ticket, got 0x{seq_tag:02x}"
|
|
)));
|
|
}
|
|
let fields = parse_sequence_fields(seq_data)?;
|
|
let mut tkt_vno = None;
|
|
let mut realm = None;
|
|
let mut sname = None;
|
|
let mut enc_part = None;
|
|
for (ftag, fvalue) in &fields {
|
|
match ftag {
|
|
0xa0 => tkt_vno = Some(parse_der_integer(fvalue)?),
|
|
0xa1 => realm = Some(parse_der_general_string(fvalue)?),
|
|
0xa2 => sname = Some(parse_principal_name(fvalue)?),
|
|
0xa3 => enc_part = Some(parse_encrypted_data(fvalue)?),
|
|
_ => {}
|
|
}
|
|
}
|
|
Ok(Ticket {
|
|
tkt_vno: tkt_vno
|
|
.ok_or_else(|| Error::invalid_data("Kerberos: missing tkt-vno in Ticket"))?,
|
|
realm: realm.ok_or_else(|| Error::invalid_data("Kerberos: missing realm in Ticket"))?,
|
|
sname: sname.ok_or_else(|| Error::invalid_data("Kerberos: missing sname in Ticket"))?,
|
|
enc_part: enc_part
|
|
.ok_or_else(|| Error::invalid_data("Kerberos: missing enc-part in Ticket"))?,
|
|
raw_bytes: Some(raw_bytes),
|
|
})
|
|
}
|
|
|
|
/// Parse an EncryptionKey from DER bytes.
|
|
fn parse_encryption_key(data: &[u8]) -> Result<EncryptionKey, Error> {
|
|
let (tag, seq_data, _) = parse_der_tlv(data)?;
|
|
if tag != TAG_SEQUENCE {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected SEQUENCE for EncryptionKey, got 0x{tag:02x}"
|
|
)));
|
|
}
|
|
let fields = parse_sequence_fields(seq_data)?;
|
|
let mut keytype = None;
|
|
let mut keyvalue = None;
|
|
for (ftag, fvalue) in &fields {
|
|
match ftag {
|
|
0xa0 => keytype = Some(parse_der_integer(fvalue)?),
|
|
0xa1 => keyvalue = Some(parse_der_octet_string(fvalue)?),
|
|
_ => {}
|
|
}
|
|
}
|
|
Ok(EncryptionKey {
|
|
keytype: keytype
|
|
.ok_or_else(|| Error::invalid_data("Kerberos: missing keytype in EncryptionKey"))?,
|
|
keyvalue: keyvalue
|
|
.ok_or_else(|| Error::invalid_data("Kerberos: missing keyvalue in EncryptionKey"))?,
|
|
})
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Public API: encoding
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Encode a KRB_AS_REQ message (`APPLICATION [10]`).
|
|
pub fn encode_as_req(
|
|
cname: &PrincipalName,
|
|
realm: &str,
|
|
sname: &PrincipalName,
|
|
nonce: u32,
|
|
etypes: &[EncryptionType],
|
|
padata: &[PaData],
|
|
) -> Vec<u8> {
|
|
encode_kdc_req(10, Some(cname), realm, sname, nonce, etypes, padata)
|
|
}
|
|
|
|
/// Encode the KDC-REQ-BODY for a TGS-REQ.
|
|
///
|
|
/// Returns the DER-encoded body, which is needed for computing the
|
|
/// checksum in the Authenticator (per RFC 4120 section 7.2.2).
|
|
pub fn encode_tgs_req_body(
|
|
realm: &str,
|
|
sname: &PrincipalName,
|
|
nonce: u32,
|
|
etypes: &[EncryptionType],
|
|
) -> Vec<u8> {
|
|
encode_kdc_req_body(None, realm, sname, nonce, etypes)
|
|
}
|
|
|
|
/// Encode a KRB_TGS_REQ message (`APPLICATION [12]`).
|
|
///
|
|
/// The `tgt_ap_req` is an AP-REQ wrapping the TGT, placed in PA-TGS-REQ (padata type 1).
|
|
/// The `req_body` must be the same bytes returned by `encode_tgs_req_body` (used for
|
|
/// the Authenticator checksum).
|
|
pub fn encode_tgs_req(
|
|
realm: &str,
|
|
sname: &PrincipalName,
|
|
nonce: u32,
|
|
etypes: &[EncryptionType],
|
|
tgt_ap_req: &[u8],
|
|
) -> Vec<u8> {
|
|
let padata = [PaData {
|
|
padata_type: 1, // PA-TGS-REQ
|
|
padata_value: tgt_ap_req.to_vec(),
|
|
}];
|
|
encode_kdc_req(12, None, realm, sname, nonce, etypes, &padata)
|
|
}
|
|
|
|
/// Encode a KRB_AP_REQ message (`APPLICATION [14]`).
|
|
///
|
|
/// When `mutual_required` is true, sets the mutual-required bit (bit 2) in
|
|
/// AP-OPTIONS, requesting the server to prove its identity via an AP-REP.
|
|
pub fn encode_ap_req(
|
|
ticket: &Ticket,
|
|
encrypted_authenticator: &EncryptedData,
|
|
mutual_required: bool,
|
|
) -> Vec<u8> {
|
|
// AP-REQ ::= [APPLICATION 14] SEQUENCE {
|
|
// pvno [0] INTEGER (5),
|
|
// msg-type [1] INTEGER (14),
|
|
// ap-options [2] APOptions (BIT STRING, 32 bits),
|
|
// ticket [3] Ticket,
|
|
// authenticator [4] EncryptedData
|
|
// }
|
|
let pvno = der_context(0, &der_integer(5));
|
|
let msg_type = der_context(1, &der_integer(14));
|
|
// AP-OPTIONS: bit 2 = mutual-required (0x20 in the first byte).
|
|
let opts_byte0 = if mutual_required { 0x20 } else { 0x00 };
|
|
let ap_options = der_context(2, &der_bit_string(&[opts_byte0, 0x00, 0x00, 0x00], 0));
|
|
// Use raw ticket bytes if available (preserves exact DER encoding from KDC).
|
|
// Re-encoding can produce different bytes and corrupt the encrypted ticket.
|
|
let ticket_raw = ticket
|
|
.raw_bytes
|
|
.as_ref()
|
|
.map(|b| der_context(3, b))
|
|
.unwrap_or_else(|| der_context(3, &encode_ticket(ticket)));
|
|
let authenticator = der_context(4, &encode_encrypted_data(encrypted_authenticator));
|
|
let seq = der_sequence(&[&pvno, &msg_type, &ap_options, &ticket_raw, &authenticator]);
|
|
der_application(14, &seq)
|
|
}
|
|
|
|
/// Encode an Authenticator (`APPLICATION [2]`), to be encrypted before embedding in AP-REQ.
|
|
///
|
|
/// The optional `cksum` parameter adds a checksum field `[3]`, used in TGS-REQ
|
|
/// to authenticate the KDC-REQ-BODY (RFC 4120 section 7.2.2).
|
|
pub fn encode_authenticator(
|
|
crealm: &str,
|
|
cname: &PrincipalName,
|
|
ctime: &str,
|
|
cusec: u32,
|
|
subkey: Option<(&[u8], i32)>,
|
|
seq_number: Option<u32>,
|
|
cksum: Option<(&[u8], i32)>,
|
|
) -> Vec<u8> {
|
|
// Authenticator ::= [APPLICATION 2] SEQUENCE {
|
|
// authenticator-vno [0] INTEGER (5),
|
|
// crealm [1] Realm (GeneralString),
|
|
// cname [2] PrincipalName,
|
|
// cksum [3] Checksum OPTIONAL,
|
|
// cusec [4] Microseconds (INTEGER),
|
|
// ctime [5] KerberosTime (GeneralizedTime),
|
|
// subkey [6] EncryptionKey OPTIONAL,
|
|
// seq-number [7] UInt32 OPTIONAL,
|
|
// }
|
|
let auth_vno = der_context(0, &der_integer(5));
|
|
let crealm_enc = der_context(1, &der_general_string(crealm));
|
|
let cname_enc = der_context(2, &encode_principal_name(cname));
|
|
|
|
let mut items: Vec<Vec<u8>> = vec![auth_vno, crealm_enc, cname_enc];
|
|
|
|
if let Some((checksum_data, checksum_type)) = cksum {
|
|
// Checksum ::= SEQUENCE { cksumtype [0] Int32, checksum [1] OCTET STRING }
|
|
let cktype = der_context(0, &der_integer(checksum_type));
|
|
let ckval = der_context(1, &der_octet_string(checksum_data));
|
|
let ck = der_sequence(&[&cktype, &ckval]);
|
|
items.push(der_context(3, &ck));
|
|
}
|
|
|
|
let cusec_enc = der_context(4, &der_integer_u32(cusec));
|
|
let ctime_enc = der_context(5, &der_generalized_time(ctime));
|
|
items.push(cusec_enc);
|
|
items.push(ctime_enc);
|
|
|
|
if let Some((key_value, key_type)) = subkey {
|
|
// EncryptionKey ::= SEQUENCE { keytype [0], keyvalue [1] }
|
|
let kt = der_context(0, &der_integer(key_type));
|
|
let kv = der_context(1, &der_octet_string(key_value));
|
|
let ek = der_sequence(&[&kt, &kv]);
|
|
items.push(der_context(6, &ek));
|
|
}
|
|
|
|
if let Some(seq) = seq_number {
|
|
items.push(der_context(7, &der_integer_u32(seq)));
|
|
}
|
|
|
|
let item_refs: Vec<&[u8]> = items.iter().map(|v| v.as_slice()).collect();
|
|
let seq = der_sequence(&item_refs);
|
|
der_application(2, &seq)
|
|
}
|
|
|
|
/// Encode a PA-ENC-TIMESTAMP pre-authentication data (the plaintext to be encrypted).
|
|
///
|
|
/// Returns the DER encoding of `PA-ENC-TS-ENC ::= SEQUENCE { patimestamp [0] GeneralizedTime, pausec [1] Microseconds }`.
|
|
pub fn encode_pa_enc_timestamp(ctime: &str, cusec: u32) -> Vec<u8> {
|
|
let patimestamp = der_context(0, &der_generalized_time(ctime));
|
|
let pausec = der_context(1, &der_integer_u32(cusec));
|
|
der_sequence(&[&patimestamp, &pausec])
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Public API: parsing
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Parse a KRB_AS_REP (`APPLICATION [11]`) or KRB_TGS_REP (`APPLICATION [13]`) message.
|
|
pub fn parse_kdc_rep(data: &[u8]) -> Result<KdcRep, Error> {
|
|
let (tag, inner, _) = parse_der_tlv(data)?;
|
|
// APPLICATION [11] = 0x6b, APPLICATION [13] = 0x6d
|
|
let expected_msg_type = match tag {
|
|
0x6b => 11,
|
|
0x6d => 13,
|
|
_ => {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected APPLICATION [11] or [13] for KDC-REP, got 0x{tag:02x}"
|
|
)));
|
|
}
|
|
};
|
|
|
|
let (seq_tag, seq_data, _) = parse_der_tlv(inner)?;
|
|
if seq_tag != TAG_SEQUENCE {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected SEQUENCE in KDC-REP, got 0x{seq_tag:02x}"
|
|
)));
|
|
}
|
|
let fields = parse_sequence_fields(seq_data)?;
|
|
let mut msg_type = None;
|
|
let mut crealm = None;
|
|
let mut cname = None;
|
|
let mut ticket = None;
|
|
let mut enc_part = None;
|
|
|
|
for (ftag, fvalue) in &fields {
|
|
match ftag {
|
|
// RFC 4120 section 5.4.2: KDC-REP fields
|
|
0xa0 => {
|
|
// pvno [0] — skip validation
|
|
}
|
|
0xa1 => msg_type = Some(parse_der_integer(fvalue)?),
|
|
// [2] padata — skip
|
|
0xa3 => crealm = Some(parse_der_general_string(fvalue)?),
|
|
0xa4 => cname = Some(parse_principal_name(fvalue)?),
|
|
0xa5 => ticket = Some(parse_ticket(fvalue)?),
|
|
0xa6 => enc_part = Some(parse_encrypted_data(fvalue)?),
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
let msg_type =
|
|
msg_type.ok_or_else(|| Error::invalid_data("Kerberos: missing msg-type in KDC-REP"))?;
|
|
if msg_type != expected_msg_type {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: KDC-REP msg-type mismatch: tag says {expected_msg_type}, field says {msg_type}"
|
|
)));
|
|
}
|
|
|
|
Ok(KdcRep {
|
|
msg_type,
|
|
crealm: crealm.ok_or_else(|| Error::invalid_data("Kerberos: missing crealm in KDC-REP"))?,
|
|
cname: cname.ok_or_else(|| Error::invalid_data("Kerberos: missing cname in KDC-REP"))?,
|
|
ticket: ticket.ok_or_else(|| Error::invalid_data("Kerberos: missing ticket in KDC-REP"))?,
|
|
enc_part: enc_part
|
|
.ok_or_else(|| Error::invalid_data("Kerberos: missing enc-part in KDC-REP"))?,
|
|
})
|
|
}
|
|
|
|
/// Parse the decrypted EncKDCRepPart.
|
|
///
|
|
/// This can be wrapped in `APPLICATION [25]` (EncASRepPart) or `APPLICATION [26]` (EncTGSRepPart),
|
|
/// or may appear as a bare SEQUENCE (some implementations).
|
|
pub fn parse_enc_kdc_rep_part(data: &[u8]) -> Result<EncKdcRepPart, Error> {
|
|
let (tag, inner, _) = parse_der_tlv(data)?;
|
|
|
|
// APPLICATION [25] = 0x79, APPLICATION [26] = 0x7a, or bare SEQUENCE
|
|
let seq_data = if tag == 0x79 || tag == 0x7a {
|
|
let (seq_tag, sd, _) = parse_der_tlv(inner)?;
|
|
if seq_tag != TAG_SEQUENCE {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected SEQUENCE in EncKDCRepPart, got 0x{seq_tag:02x}"
|
|
)));
|
|
}
|
|
sd
|
|
} else if tag == TAG_SEQUENCE {
|
|
inner
|
|
} else {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected APPLICATION [25/26] or SEQUENCE for EncKDCRepPart, got 0x{tag:02x}"
|
|
)));
|
|
};
|
|
|
|
let fields = parse_sequence_fields(seq_data)?;
|
|
let mut key = None;
|
|
let mut nonce = None;
|
|
let mut flags = None;
|
|
let mut authtime = None;
|
|
let mut endtime = None;
|
|
let mut srealm = None;
|
|
let mut sname = None;
|
|
|
|
for (ftag, fvalue) in &fields {
|
|
match ftag {
|
|
0xa0 => key = Some(parse_encryption_key(fvalue)?),
|
|
// [1] last-req — skip
|
|
0xa2 => nonce = Some(parse_der_integer_u32(fvalue)?),
|
|
// [3] key-expiration — skip
|
|
0xa4 => {
|
|
let (bits, _unused) = parse_der_bit_string(fvalue)?;
|
|
if bits.len() >= 4 {
|
|
flags = Some(u32::from_be_bytes([bits[0], bits[1], bits[2], bits[3]]));
|
|
}
|
|
}
|
|
0xa5 => authtime = Some(parse_der_generalized_time(fvalue)?),
|
|
// [6] starttime — skip
|
|
0xa7 => endtime = Some(parse_der_generalized_time(fvalue)?),
|
|
// [8] renew-till — skip
|
|
0xa9 => srealm = Some(parse_der_general_string(fvalue)?),
|
|
0xaa => sname = Some(parse_principal_name(fvalue)?),
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
Ok(EncKdcRepPart {
|
|
key: key.ok_or_else(|| Error::invalid_data("Kerberos: missing key in EncKDCRepPart"))?,
|
|
nonce: nonce
|
|
.ok_or_else(|| Error::invalid_data("Kerberos: missing nonce in EncKDCRepPart"))?,
|
|
flags: flags.unwrap_or(0),
|
|
authtime: authtime
|
|
.ok_or_else(|| Error::invalid_data("Kerberos: missing authtime in EncKDCRepPart"))?,
|
|
endtime: endtime
|
|
.ok_or_else(|| Error::invalid_data("Kerberos: missing endtime in EncKDCRepPart"))?,
|
|
srealm: srealm
|
|
.ok_or_else(|| Error::invalid_data("Kerberos: missing srealm in EncKDCRepPart"))?,
|
|
sname: sname
|
|
.ok_or_else(|| Error::invalid_data("Kerberos: missing sname in EncKDCRepPart"))?,
|
|
})
|
|
}
|
|
|
|
/// Parse a KRB-ERROR message (`APPLICATION [30]`).
|
|
pub fn parse_krb_error(data: &[u8]) -> Result<KrbError, Error> {
|
|
let (tag, inner, _) = parse_der_tlv(data)?;
|
|
// APPLICATION [30] = 0x7e
|
|
if tag != 0x7e {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected APPLICATION [30] (0x7e) for KRB-ERROR, got 0x{tag:02x}"
|
|
)));
|
|
}
|
|
let (seq_tag, seq_data, _) = parse_der_tlv(inner)?;
|
|
if seq_tag != TAG_SEQUENCE {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected SEQUENCE in KRB-ERROR, got 0x{seq_tag:02x}"
|
|
)));
|
|
}
|
|
let fields = parse_sequence_fields(seq_data)?;
|
|
|
|
let mut error_code = None;
|
|
let mut crealm = None;
|
|
let mut realm = None;
|
|
let mut sname = None;
|
|
let mut e_text = None;
|
|
let mut e_data = None;
|
|
|
|
for (ftag, fvalue) in &fields {
|
|
match ftag {
|
|
// [0] pvno — skip
|
|
// [1] msg-type — skip
|
|
// [2] ctime — skip
|
|
// [3] cusec — skip
|
|
// [4] stime — skip
|
|
// [5] susec — skip
|
|
0xa6 => error_code = Some(parse_der_integer(fvalue)?),
|
|
0xa7 => crealm = Some(parse_der_general_string(fvalue)?),
|
|
0xa8 => {
|
|
// cname — skip (we don't need it in the error struct, but parse to validate)
|
|
}
|
|
0xa9 => realm = Some(parse_der_general_string(fvalue)?),
|
|
0xaa => sname = Some(parse_principal_name(fvalue)?),
|
|
0xab => e_text = Some(parse_der_general_string(fvalue)?),
|
|
0xac => e_data = Some(parse_der_octet_string(fvalue)?),
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
Ok(KrbError {
|
|
error_code: error_code
|
|
.ok_or_else(|| Error::invalid_data("Kerberos: missing error-code in KRB-ERROR"))?,
|
|
crealm,
|
|
realm: realm.ok_or_else(|| Error::invalid_data("Kerberos: missing realm in KRB-ERROR"))?,
|
|
sname: sname.ok_or_else(|| Error::invalid_data("Kerberos: missing sname in KRB-ERROR"))?,
|
|
e_text,
|
|
e_data,
|
|
})
|
|
}
|
|
|
|
/// Unwrap a GSS-API token: `APPLICATION [0] { OID, inner-data }`.
|
|
///
|
|
/// Returns the inner data after the OID as a `Vec<u8>`.
|
|
pub fn parse_gss_api_wrapper(data: &[u8]) -> Result<(Vec<u8>, Vec<u8>, usize), Error> {
|
|
let (tag, inner, total) = parse_der_tlv(data)?;
|
|
if tag != 0x60 {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected GSS-API wrapper (0x60), got 0x{tag:02x}"
|
|
)));
|
|
}
|
|
// Skip the OID TLV.
|
|
let (_oid_tag, oid_data, oid_consumed) = parse_der_tlv(inner)?;
|
|
let oid = oid_data.to_vec();
|
|
let rest = inner[oid_consumed..].to_vec();
|
|
Ok((oid, rest, total))
|
|
}
|
|
|
|
/// Parse a KRB_AP_REP message (`APPLICATION [15]`).
|
|
///
|
|
/// Handles both bare AP-REP and GSS-API wrapped tokens (`APPLICATION [0]`
|
|
/// containing an OID followed by the AP-REP).
|
|
pub fn parse_ap_rep(data: &[u8]) -> Result<ApRep, Error> {
|
|
let (tag, inner, _) = parse_der_tlv(data)?;
|
|
|
|
// If wrapped in GSS-API APPLICATION [0], unwrap first.
|
|
let inner = if tag == 0x60 {
|
|
// APPLICATION [0] { OID, AP-REP }
|
|
// Skip the OID TLV to get to the AP-REP.
|
|
let (_oid_tag, _oid_data, oid_consumed) = parse_der_tlv(inner)?;
|
|
let ap_rep_data = &inner[oid_consumed..];
|
|
let (ap_tag, ap_inner, _) = parse_der_tlv(ap_rep_data)?;
|
|
if ap_tag != 0x6f {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected AP-REP (0x6f) inside GSS wrapper, got 0x{ap_tag:02x}"
|
|
)));
|
|
}
|
|
ap_inner
|
|
} else if tag == 0x6f {
|
|
inner
|
|
} else {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected APPLICATION [15] (0x6f) or GSS wrapper (0x60) for AP-REP, got 0x{tag:02x}"
|
|
)));
|
|
};
|
|
let (seq_tag, seq_data, _) = parse_der_tlv(inner)?;
|
|
if seq_tag != TAG_SEQUENCE {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected SEQUENCE in AP-REP, got 0x{seq_tag:02x}"
|
|
)));
|
|
}
|
|
let fields = parse_sequence_fields(seq_data)?;
|
|
|
|
let mut enc_part = None;
|
|
for (ftag, fvalue) in &fields {
|
|
// [0] pvno — skip, [1] msg-type — skip
|
|
if ftag == &0xa2 {
|
|
enc_part = Some(parse_encrypted_data(fvalue)?);
|
|
}
|
|
}
|
|
|
|
Ok(ApRep {
|
|
enc_part: enc_part
|
|
.ok_or_else(|| Error::invalid_data("Kerberos: missing enc-part in AP-REP"))?,
|
|
})
|
|
}
|
|
|
|
/// Parse the decrypted EncAPRepPart (`APPLICATION [27]`).
|
|
pub fn parse_enc_ap_rep_part(data: &[u8]) -> Result<EncApRepPart, Error> {
|
|
let (tag, inner, _) = parse_der_tlv(data)?;
|
|
// APPLICATION [27] = 0x7b, or bare SEQUENCE
|
|
let seq_data = match tag {
|
|
0x7b => {
|
|
let (seq_tag, seq_data, _) = parse_der_tlv(inner)?;
|
|
if seq_tag != TAG_SEQUENCE {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected SEQUENCE in EncAPRepPart, got 0x{seq_tag:02x}"
|
|
)));
|
|
}
|
|
seq_data
|
|
}
|
|
TAG_SEQUENCE => inner,
|
|
_ => {
|
|
return Err(Error::invalid_data(format!(
|
|
"Kerberos: expected APPLICATION [27] or SEQUENCE for EncAPRepPart, got 0x{tag:02x}"
|
|
)));
|
|
}
|
|
};
|
|
|
|
let fields = parse_sequence_fields(seq_data)?;
|
|
|
|
let mut subkey = None;
|
|
let mut seq_number = None;
|
|
for (ftag, fvalue) in &fields {
|
|
match ftag {
|
|
// [0] ctime — skip
|
|
// [1] cusec — skip
|
|
0xa2 => subkey = Some(parse_encryption_key(fvalue)?),
|
|
0xa3 => seq_number = Some(parse_der_integer_u32(fvalue)?),
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
Ok(EncApRepPart { subkey, seq_number })
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Internal: KDC-REQ encoding (shared by AS-REQ and TGS-REQ)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Encode just the KDC-REQ-BODY portion of a KDC-REQ.
|
|
fn encode_kdc_req_body(
|
|
cname: Option<&PrincipalName>,
|
|
realm: &str,
|
|
sname: &PrincipalName,
|
|
nonce: u32,
|
|
etypes: &[EncryptionType],
|
|
) -> Vec<u8> {
|
|
let kdc_options = der_context(0, &der_bit_string(&[0x40, 0x81, 0x00, 0x10], 0));
|
|
let mut body_items: Vec<Vec<u8>> = vec![kdc_options];
|
|
|
|
if let Some(cn) = cname {
|
|
body_items.push(der_context(1, &encode_principal_name(cn)));
|
|
}
|
|
body_items.push(der_context(2, &der_general_string(realm)));
|
|
body_items.push(der_context(3, &encode_principal_name(sname)));
|
|
// till: set far in the future
|
|
body_items.push(der_context(5, &der_generalized_time("20370913024805Z")));
|
|
body_items.push(der_context(7, &der_integer_u32(nonce)));
|
|
|
|
// etype: SEQUENCE OF INTEGER
|
|
let etype_ints: Vec<Vec<u8>> = etypes.iter().map(|e| der_integer(*e as i32)).collect();
|
|
let etype_refs: Vec<&[u8]> = etype_ints.iter().map(|v| v.as_slice()).collect();
|
|
let etype_seq = der_sequence(&etype_refs);
|
|
body_items.push(der_context(8, &etype_seq));
|
|
|
|
let body_refs: Vec<&[u8]> = body_items.iter().map(|v| v.as_slice()).collect();
|
|
der_sequence(&body_refs)
|
|
}
|
|
|
|
fn encode_kdc_req(
|
|
msg_type_val: i32,
|
|
cname: Option<&PrincipalName>,
|
|
realm: &str,
|
|
sname: &PrincipalName,
|
|
nonce: u32,
|
|
etypes: &[EncryptionType],
|
|
padata: &[PaData],
|
|
) -> Vec<u8> {
|
|
let req_body = encode_kdc_req_body(cname, realm, sname, nonce, etypes);
|
|
|
|
// KDC-REQ
|
|
let pvno = der_context(1, &der_integer(5));
|
|
let msg_type = der_context(2, &der_integer(msg_type_val));
|
|
|
|
let mut kdc_req_items: Vec<Vec<u8>> = vec![pvno, msg_type];
|
|
|
|
if !padata.is_empty() {
|
|
let pa_items: Vec<Vec<u8>> = padata.iter().map(encode_pa_data).collect();
|
|
let pa_refs: Vec<&[u8]> = pa_items.iter().map(|v| v.as_slice()).collect();
|
|
let pa_seq = der_sequence(&pa_refs);
|
|
kdc_req_items.push(der_context(3, &pa_seq));
|
|
}
|
|
|
|
kdc_req_items.push(der_context(4, &req_body));
|
|
|
|
let kdc_req_refs: Vec<&[u8]> = kdc_req_items.iter().map(|v| v.as_slice()).collect();
|
|
let kdc_req_seq = der_sequence(&kdc_req_refs);
|
|
|
|
// APPLICATION tag for the message type
|
|
let app_tag = match msg_type_val {
|
|
10 => 10, // AS-REQ
|
|
12 => 12, // TGS-REQ
|
|
_ => msg_type_val as u8,
|
|
};
|
|
der_application(app_tag, &kdc_req_seq)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
// -----------------------------------------------------------------------
|
|
// DER helper tests
|
|
// -----------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn test_der_integer_positive() {
|
|
// 5 should encode as 02 01 05
|
|
let encoded = der_integer(5);
|
|
assert_eq!(encoded, vec![0x02, 0x01, 0x05]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_der_integer_zero() {
|
|
// 0 should encode as 02 01 00
|
|
let encoded = der_integer(0);
|
|
assert_eq!(encoded, vec![0x02, 0x01, 0x00]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_der_integer_negative() {
|
|
// -1 should encode as 02 01 ff
|
|
let encoded = der_integer(-1);
|
|
assert_eq!(encoded, vec![0x02, 0x01, 0xff]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_der_integer_128() {
|
|
// 128 needs leading 0x00: 02 02 00 80
|
|
let encoded = der_integer(128);
|
|
assert_eq!(encoded, vec![0x02, 0x02, 0x00, 0x80]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_der_integer_256() {
|
|
// 256 = 0x0100: 02 02 01 00
|
|
let encoded = der_integer(256);
|
|
assert_eq!(encoded, vec![0x02, 0x02, 0x01, 0x00]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_der_integer_large_positive() {
|
|
// 65536 = 0x10000: 02 03 01 00 00
|
|
let encoded = der_integer(65536);
|
|
assert_eq!(encoded, vec![0x02, 0x03, 0x01, 0x00, 0x00]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_der_integer_u32_max() {
|
|
// u32::MAX = 0xFFFFFFFF: needs 02 05 00 ff ff ff ff
|
|
let encoded = der_integer_u32(u32::MAX);
|
|
assert_eq!(encoded, vec![0x02, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_der_generalized_time() {
|
|
let encoded = der_generalized_time("20260408120000Z");
|
|
assert_eq!(encoded[0], TAG_GENERALIZED_TIME);
|
|
assert_eq!(encoded[1], 15); // length
|
|
assert_eq!(&encoded[2..], b"20260408120000Z");
|
|
}
|
|
|
|
#[test]
|
|
fn test_der_bit_string_32bit_flags() {
|
|
let encoded = der_bit_string(&[0x40, 0x81, 0x00, 0x10], 0);
|
|
assert_eq!(encoded[0], TAG_BIT_STRING);
|
|
assert_eq!(encoded[1], 5); // 1 unused-bits byte + 4 bytes
|
|
assert_eq!(encoded[2], 0); // 0 unused bits
|
|
assert_eq!(&encoded[3..], &[0x40, 0x81, 0x00, 0x10]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_der_general_string() {
|
|
let encoded = der_general_string("EXAMPLE.COM");
|
|
assert_eq!(encoded[0], TAG_GENERAL_STRING);
|
|
assert_eq!(encoded[1], 11);
|
|
assert_eq!(&encoded[2..], b"EXAMPLE.COM");
|
|
}
|
|
|
|
// DER primitive tests (der_length, der_tlv, parse_der_length, parse_der_tlv)
|
|
// live in `auth::der::tests`.
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Parse helper tests (Kerberos-specific)
|
|
// -----------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn test_parse_der_integer_roundtrip() {
|
|
for val in [0, 1, 5, 127, 128, 255, 256, 1000, -1, -128, -129] {
|
|
let encoded = der_integer(val);
|
|
let parsed = parse_der_integer(&encoded).unwrap();
|
|
assert_eq!(parsed, val, "roundtrip failed for {val}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_der_octet_string_roundtrip() {
|
|
let data = vec![0x01, 0x02, 0x03, 0xff];
|
|
let encoded = der_octet_string(&data);
|
|
let parsed = parse_der_octet_string(&encoded).unwrap();
|
|
assert_eq!(parsed, data);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_der_general_string_roundtrip() {
|
|
let encoded = der_general_string("EXAMPLE.COM");
|
|
let parsed = parse_der_general_string(&encoded).unwrap();
|
|
assert_eq!(parsed, "EXAMPLE.COM");
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_der_generalized_time_roundtrip() {
|
|
let encoded = der_generalized_time("20260408120000Z");
|
|
let parsed = parse_der_generalized_time(&encoded).unwrap();
|
|
assert_eq!(parsed, "20260408120000Z");
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_der_bit_string_roundtrip() {
|
|
let bits = vec![0x40, 0x81, 0x00, 0x10];
|
|
let encoded = der_bit_string(&bits, 0);
|
|
let (parsed_bits, unused) = parse_der_bit_string(&encoded).unwrap();
|
|
assert_eq!(parsed_bits, bits);
|
|
assert_eq!(unused, 0);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Encoding tests
|
|
// -----------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn test_encode_as_req_application_tag() {
|
|
let cname = PrincipalName {
|
|
name_type: 1,
|
|
name_string: vec!["user".to_string()],
|
|
};
|
|
let sname = PrincipalName {
|
|
name_type: 2,
|
|
name_string: vec!["krbtgt".to_string(), "EXAMPLE.COM".to_string()],
|
|
};
|
|
let encoded = encode_as_req(
|
|
&cname,
|
|
"EXAMPLE.COM",
|
|
&sname,
|
|
12345,
|
|
&[EncryptionType::Aes256CtsHmacSha196],
|
|
&[],
|
|
);
|
|
// APPLICATION [10] = 0x6a
|
|
assert_eq!(encoded[0], 0x6a, "AS-REQ must start with APPLICATION [10]");
|
|
}
|
|
|
|
#[test]
|
|
fn test_encode_as_req_contains_pvno_and_msg_type() {
|
|
let cname = PrincipalName {
|
|
name_type: 1,
|
|
name_string: vec!["user".to_string()],
|
|
};
|
|
let sname = PrincipalName {
|
|
name_type: 2,
|
|
name_string: vec!["krbtgt".to_string(), "EXAMPLE.COM".to_string()],
|
|
};
|
|
let encoded = encode_as_req(
|
|
&cname,
|
|
"EXAMPLE.COM",
|
|
&sname,
|
|
12345,
|
|
&[EncryptionType::Aes256CtsHmacSha196],
|
|
&[],
|
|
);
|
|
// Should contain pvno=5 somewhere: a1 03 02 01 05
|
|
let pvno_pattern = [0xa1, 0x03, 0x02, 0x01, 0x05];
|
|
assert!(
|
|
contains_subsequence(&encoded, &pvno_pattern),
|
|
"AS-REQ must contain pvno=5"
|
|
);
|
|
// Should contain msg-type=10: a2 03 02 01 0a
|
|
let msg_type_pattern = [0xa2, 0x03, 0x02, 0x01, 0x0a];
|
|
assert!(
|
|
contains_subsequence(&encoded, &msg_type_pattern),
|
|
"AS-REQ must contain msg-type=10"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_encode_tgs_req_application_tag() {
|
|
let sname = PrincipalName {
|
|
name_type: 2,
|
|
name_string: vec!["cifs".to_string(), "server.example.com".to_string()],
|
|
};
|
|
let fake_ap_req = vec![0x6e, 0x03, 0x01, 0x02, 0x03];
|
|
let encoded = encode_tgs_req(
|
|
"EXAMPLE.COM",
|
|
&sname,
|
|
54321,
|
|
&[EncryptionType::Aes256CtsHmacSha196],
|
|
&fake_ap_req,
|
|
);
|
|
// APPLICATION [12] = 0x6c
|
|
assert_eq!(encoded[0], 0x6c, "TGS-REQ must start with APPLICATION [12]");
|
|
}
|
|
|
|
#[test]
|
|
fn test_encode_tgs_req_contains_msg_type_12() {
|
|
let sname = PrincipalName {
|
|
name_type: 2,
|
|
name_string: vec!["cifs".to_string(), "server.example.com".to_string()],
|
|
};
|
|
let fake_ap_req = vec![0x6e, 0x03, 0x01, 0x02, 0x03];
|
|
let encoded = encode_tgs_req(
|
|
"EXAMPLE.COM",
|
|
&sname,
|
|
54321,
|
|
&[EncryptionType::Aes256CtsHmacSha196],
|
|
&fake_ap_req,
|
|
);
|
|
// msg-type=12: a2 03 02 01 0c
|
|
let msg_type_pattern = [0xa2, 0x03, 0x02, 0x01, 0x0c];
|
|
assert!(
|
|
contains_subsequence(&encoded, &msg_type_pattern),
|
|
"TGS-REQ must contain msg-type=12"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_encode_ap_req_application_tag() {
|
|
let ticket = make_test_ticket();
|
|
let auth = EncryptedData {
|
|
etype: 18,
|
|
kvno: None,
|
|
cipher: vec![0xaa, 0xbb],
|
|
};
|
|
let encoded = encode_ap_req(&ticket, &auth, false);
|
|
// APPLICATION [14] = 0x6e
|
|
assert_eq!(encoded[0], 0x6e, "AP-REQ must start with APPLICATION [14]");
|
|
}
|
|
|
|
#[test]
|
|
fn test_encode_ap_req_contains_pvno_and_msg_type() {
|
|
let ticket = make_test_ticket();
|
|
let auth = EncryptedData {
|
|
etype: 18,
|
|
kvno: None,
|
|
cipher: vec![0xaa, 0xbb],
|
|
};
|
|
let encoded = encode_ap_req(&ticket, &auth, false);
|
|
// pvno=5: a0 03 02 01 05
|
|
let pvno_pattern = [0xa0, 0x03, 0x02, 0x01, 0x05];
|
|
assert!(
|
|
contains_subsequence(&encoded, &pvno_pattern),
|
|
"AP-REQ must contain pvno=5"
|
|
);
|
|
// msg-type=14: a1 03 02 01 0e
|
|
let msg_type_pattern = [0xa1, 0x03, 0x02, 0x01, 0x0e];
|
|
assert!(
|
|
contains_subsequence(&encoded, &msg_type_pattern),
|
|
"AP-REQ must contain msg-type=14"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_encode_authenticator_application_tag() {
|
|
let cname = PrincipalName {
|
|
name_type: 1,
|
|
name_string: vec!["user".to_string()],
|
|
};
|
|
let encoded = encode_authenticator(
|
|
"EXAMPLE.COM",
|
|
&cname,
|
|
"20260408120000Z",
|
|
123456,
|
|
None,
|
|
None,
|
|
None,
|
|
);
|
|
// APPLICATION [2] = 0x62
|
|
assert_eq!(
|
|
encoded[0], 0x62,
|
|
"Authenticator must start with APPLICATION [2]"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_encode_authenticator_with_subkey() {
|
|
let cname = PrincipalName {
|
|
name_type: 1,
|
|
name_string: vec!["user".to_string()],
|
|
};
|
|
let subkey_value = vec![0x01; 32];
|
|
let encoded = encode_authenticator(
|
|
"EXAMPLE.COM",
|
|
&cname,
|
|
"20260408120000Z",
|
|
0,
|
|
Some((&subkey_value, 18)),
|
|
Some(42),
|
|
None,
|
|
);
|
|
assert_eq!(encoded[0], 0x62);
|
|
// Should contain the subkey context tag [6] = 0xa6
|
|
assert!(
|
|
contains_subsequence(&encoded, &[0xa6]),
|
|
"Authenticator with subkey must contain [6]"
|
|
);
|
|
// Should contain seq-number context tag [7] = 0xa7
|
|
assert!(
|
|
contains_subsequence(&encoded, &[0xa7]),
|
|
"Authenticator with seq-number must contain [7]"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_encode_pa_enc_timestamp() {
|
|
let encoded = encode_pa_enc_timestamp("20260408120000Z", 123456);
|
|
// Should be a SEQUENCE starting with 0x30
|
|
assert_eq!(encoded[0], TAG_SEQUENCE);
|
|
// Should contain [0] with GeneralizedTime
|
|
assert!(contains_subsequence(&encoded, &[0xa0]));
|
|
// Should contain [1] with INTEGER
|
|
assert!(contains_subsequence(&encoded, &[0xa1]));
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Parsing tests
|
|
// -----------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn test_parse_kdc_rep_as_rep() {
|
|
let rep_bytes = build_test_kdc_rep(11);
|
|
let rep = parse_kdc_rep(&rep_bytes).unwrap();
|
|
assert_eq!(rep.msg_type, 11);
|
|
assert_eq!(rep.crealm, "EXAMPLE.COM");
|
|
assert_eq!(rep.cname.name_type, 1);
|
|
assert_eq!(rep.cname.name_string, vec!["user"]);
|
|
assert_eq!(rep.ticket.realm, "EXAMPLE.COM");
|
|
assert_eq!(rep.enc_part.etype, 18);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_kdc_rep_tgs_rep() {
|
|
let rep_bytes = build_test_kdc_rep(13);
|
|
let rep = parse_kdc_rep(&rep_bytes).unwrap();
|
|
assert_eq!(rep.msg_type, 13);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_krb_error() {
|
|
let err_bytes = build_test_krb_error(25); // KDC_ERR_PREAUTH_REQUIRED
|
|
let err = parse_krb_error(&err_bytes).unwrap();
|
|
assert_eq!(err.error_code, 25);
|
|
assert_eq!(err.realm, "EXAMPLE.COM");
|
|
assert_eq!(err.sname.name_type, 2);
|
|
assert_eq!(err.sname.name_string, vec!["krbtgt", "EXAMPLE.COM"]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_ticket_roundtrip() {
|
|
let ticket = make_test_ticket();
|
|
let encoded = encode_ticket(&ticket);
|
|
let parsed = parse_ticket(&encoded).unwrap();
|
|
assert_eq!(parsed.tkt_vno, 5);
|
|
assert_eq!(parsed.realm, "EXAMPLE.COM");
|
|
assert_eq!(parsed.sname.name_type, 2);
|
|
assert_eq!(parsed.sname.name_string, vec!["krbtgt", "EXAMPLE.COM"]);
|
|
assert_eq!(parsed.enc_part.etype, 18);
|
|
assert_eq!(parsed.enc_part.cipher, vec![0xde, 0xad, 0xbe, 0xef]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_enc_kdc_rep_part() {
|
|
let part_bytes = build_test_enc_kdc_rep_part();
|
|
let part = parse_enc_kdc_rep_part(&part_bytes).unwrap();
|
|
assert_eq!(part.key.keytype, 18);
|
|
assert_eq!(part.key.keyvalue, vec![0x01; 32]);
|
|
assert_eq!(part.nonce, 12345);
|
|
assert_eq!(part.authtime, "20260408120000Z");
|
|
assert_eq!(part.endtime, "20260409120000Z");
|
|
assert_eq!(part.srealm, "EXAMPLE.COM");
|
|
assert_eq!(part.sname.name_type, 2);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Roundtrip tests
|
|
// -----------------------------------------------------------------------
|
|
|
|
#[test]
|
|
fn test_principal_name_roundtrip() {
|
|
let name = PrincipalName {
|
|
name_type: 2,
|
|
name_string: vec!["cifs".to_string(), "server.example.com".to_string()],
|
|
};
|
|
let encoded = encode_principal_name(&name);
|
|
let parsed = parse_principal_name(&encoded).unwrap();
|
|
assert_eq!(parsed, name);
|
|
}
|
|
|
|
#[test]
|
|
fn test_encrypted_data_roundtrip() {
|
|
let ed = EncryptedData {
|
|
etype: 17,
|
|
kvno: Some(3),
|
|
cipher: vec![0x01, 0x02, 0x03, 0x04],
|
|
};
|
|
let encoded = encode_encrypted_data(&ed);
|
|
let parsed = parse_encrypted_data(&encoded).unwrap();
|
|
assert_eq!(parsed, ed);
|
|
}
|
|
|
|
#[test]
|
|
fn test_encrypted_data_no_kvno_roundtrip() {
|
|
let ed = EncryptedData {
|
|
etype: 23,
|
|
kvno: None,
|
|
cipher: vec![0xff; 16],
|
|
};
|
|
let encoded = encode_encrypted_data(&ed);
|
|
let parsed = parse_encrypted_data(&encoded).unwrap();
|
|
assert_eq!(parsed, ed);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ticket_roundtrip() {
|
|
let ticket = make_test_ticket();
|
|
let encoded = encode_ticket(&ticket);
|
|
let parsed = parse_ticket(&encoded).unwrap();
|
|
// Compare fields (raw_bytes differs: None vs Some).
|
|
assert_eq!(parsed.tkt_vno, ticket.tkt_vno);
|
|
assert_eq!(parsed.realm, ticket.realm);
|
|
assert_eq!(parsed.sname, ticket.sname);
|
|
assert_eq!(parsed.enc_part, ticket.enc_part);
|
|
// Parsed ticket should have raw_bytes.
|
|
assert!(parsed.raw_bytes.is_some());
|
|
assert_eq!(parsed.raw_bytes.as_ref().unwrap(), &encoded);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Test helpers
|
|
// -----------------------------------------------------------------------
|
|
|
|
fn contains_subsequence(haystack: &[u8], needle: &[u8]) -> bool {
|
|
haystack
|
|
.windows(needle.len())
|
|
.any(|window| window == needle)
|
|
}
|
|
|
|
fn make_test_ticket() -> Ticket {
|
|
Ticket {
|
|
tkt_vno: 5,
|
|
realm: "EXAMPLE.COM".to_string(),
|
|
sname: PrincipalName {
|
|
name_type: 2,
|
|
name_string: vec!["krbtgt".to_string(), "EXAMPLE.COM".to_string()],
|
|
},
|
|
enc_part: EncryptedData {
|
|
etype: 18,
|
|
kvno: Some(2),
|
|
cipher: vec![0xde, 0xad, 0xbe, 0xef],
|
|
},
|
|
raw_bytes: None,
|
|
}
|
|
}
|
|
|
|
/// Build a test KDC-REP (AS-REP or TGS-REP) in DER.
|
|
fn build_test_kdc_rep(msg_type_val: i32) -> Vec<u8> {
|
|
// RFC 4120 section 5.4.2: KDC-REP fields start at [0]
|
|
let pvno = der_context(0, &der_integer(5));
|
|
let msg_type = der_context(1, &der_integer(msg_type_val));
|
|
let crealm = der_context(3, &der_general_string("EXAMPLE.COM"));
|
|
|
|
let cname_inner = encode_principal_name(&PrincipalName {
|
|
name_type: 1,
|
|
name_string: vec!["user".to_string()],
|
|
});
|
|
let cname = der_context(4, &cname_inner);
|
|
|
|
let ticket = der_context(5, &encode_ticket(&make_test_ticket()));
|
|
|
|
let enc_part_inner = encode_encrypted_data(&EncryptedData {
|
|
etype: 18,
|
|
kvno: Some(1),
|
|
cipher: vec![0xca, 0xfe],
|
|
});
|
|
let enc_part = der_context(6, &enc_part_inner);
|
|
|
|
let seq = der_sequence(&[&pvno, &msg_type, &crealm, &cname, &ticket, &enc_part]);
|
|
|
|
let app_tag = match msg_type_val {
|
|
11 => 11, // AS-REP
|
|
13 => 13, // TGS-REP
|
|
_ => panic!("unexpected msg_type_val"),
|
|
};
|
|
der_application(app_tag, &seq)
|
|
}
|
|
|
|
/// Build a test KRB-ERROR in DER.
|
|
fn build_test_krb_error(error_code_val: i32) -> Vec<u8> {
|
|
let pvno = der_context(0, &der_integer(5));
|
|
let msg_type = der_context(1, &der_integer(30));
|
|
let stime = der_context(4, &der_generalized_time("20260408120000Z"));
|
|
let susec = der_context(5, &der_integer(0));
|
|
let error_code = der_context(6, &der_integer(error_code_val));
|
|
let realm = der_context(9, &der_general_string("EXAMPLE.COM"));
|
|
let sname_inner = encode_principal_name(&PrincipalName {
|
|
name_type: 2,
|
|
name_string: vec!["krbtgt".to_string(), "EXAMPLE.COM".to_string()],
|
|
});
|
|
let sname = der_context(10, &sname_inner);
|
|
|
|
let seq = der_sequence(&[
|
|
&pvno,
|
|
&msg_type,
|
|
&stime,
|
|
&susec,
|
|
&error_code,
|
|
&realm,
|
|
&sname,
|
|
]);
|
|
// APPLICATION [30] = 0x7e
|
|
der_application(30, &seq)
|
|
}
|
|
|
|
/// Build a test EncKDCRepPart in DER.
|
|
fn build_test_enc_kdc_rep_part() -> Vec<u8> {
|
|
// key [0]: EncryptionKey { keytype=18, keyvalue=0x01*32 }
|
|
let kt = der_context(0, &der_integer(18));
|
|
let kv = der_context(1, &der_octet_string(&[0x01; 32]));
|
|
let key_seq = der_sequence(&[&kt, &kv]);
|
|
let key = der_context(0, &key_seq);
|
|
|
|
// last-req [1]: minimal (empty sequence)
|
|
let last_req = der_context(1, &der_sequence(&[]));
|
|
|
|
// nonce [2]
|
|
let nonce = der_context(2, &der_integer_u32(12345));
|
|
|
|
// flags [4]: BIT STRING
|
|
let flags = der_context(4, &der_bit_string(&[0x50, 0x80, 0x00, 0x00], 0));
|
|
|
|
// authtime [5]
|
|
let authtime = der_context(5, &der_generalized_time("20260408120000Z"));
|
|
|
|
// endtime [7]
|
|
let endtime = der_context(7, &der_generalized_time("20260409120000Z"));
|
|
|
|
// srealm [9]
|
|
let srealm = der_context(9, &der_general_string("EXAMPLE.COM"));
|
|
|
|
// sname [10]
|
|
let sname_inner = encode_principal_name(&PrincipalName {
|
|
name_type: 2,
|
|
name_string: vec!["krbtgt".to_string(), "EXAMPLE.COM".to_string()],
|
|
});
|
|
let sname = der_context(10, &sname_inner);
|
|
|
|
let seq = der_sequence(&[
|
|
&key, &last_req, &nonce, &flags, &authtime, &endtime, &srealm, &sname,
|
|
]);
|
|
|
|
// Wrap in APPLICATION [25] (EncASRepPart)
|
|
der_application(25, &seq)
|
|
}
|
|
}
|