//! 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, } /// 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>, } /// 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, /// Ciphertext bytes. pub cipher: Vec, } /// 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, } /// 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, } /// 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, /// Optional sequence number. pub seq_number: Option, } /// Parsed KRB-ERROR message. #[derive(Debug, Clone, PartialEq, Eq)] pub struct KrbError { /// Error code. pub error_code: i32, /// Client realm (optional). pub crealm: Option, /// Server realm. pub realm: String, /// Server principal name. pub sname: PrincipalName, /// Error text (optional). pub e_text: Option, /// Error data (optional). pub e_data: Option>, } // 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 { der_tlv(0xa0 | tag_num, data) } /// Encode an APPLICATION constructed tag: `[APPLICATION tag_num]`. fn der_application(tag_num: u8, data: &[u8]) -> Vec { der_tlv(0x60 | tag_num, data) } /// Encode an ASN.1 INTEGER (signed, big-endian, minimal bytes). fn der_integer(val: i32) -> Vec { 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 { // 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 { der_tlv(TAG_OCTET_STRING, data) } /// Encode a DER GeneralString. fn der_general_string(s: &str) -> Vec { der_tlv(TAG_GENERAL_STRING, s.as_bytes()) } /// Encode a DER GeneralizedTime (for example, `"20260408120000Z"`). fn der_generalized_time(time: &str) -> Vec { 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 { 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 { 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)>, 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 { 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 { 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 { 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, 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 { 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 { 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), 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 { // 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> = 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 { // 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 { // 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 { // 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { // 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, cksum: Option<(&[u8], i32)>, ) -> Vec { // 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![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 { 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 { 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 { 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 { 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`. pub fn parse_gss_api_wrapper(data: &[u8]) -> Result<(Vec, Vec, 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 { 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 { 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 { let kdc_options = der_context(0, &der_bit_string(&[0x40, 0x81, 0x00, 0x10], 0)); let mut body_items: Vec> = 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> = 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 { 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![pvno, msg_type]; if !padata.is_empty() { let pa_items: Vec> = 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 { // 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 { 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 { // 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) } }