⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/net/kerberos/
protocol.rs

1//! Kerberos v5 Protocol (RFC 4120)
2//!
3//! Implements Kerberos message types, principal names, encryption types,
4//! and the client-side AS-REQ/AS-REP and TGS-REQ/TGS-REP flows. All
5//! messages are encoded using `crate::net::asn1` for ASN.1/BER serialization.
6//!
7//! # Key Derivation
8//!
9//! Provides string2key stubs for AES-256-CTS-HMAC-SHA1-96 (etype 18) using
10//! PBKDF2-HMAC-SHA1 with 4096 iterations. Full AES-CTS encryption is stubbed
11//! pending a complete AES implementation.
12
13#![allow(dead_code)]
14
15#[cfg(feature = "alloc")]
16extern crate alloc;
17
18#[cfg(feature = "alloc")]
19use alloc::{string::String, vec, vec::Vec};
20
21use crate::{
22    error::KernelError,
23    net::asn1::{encode_application, encode_context_specific, AsnEncoder, AsnValue},
24};
25
26// ---------------------------------------------------------------------------
27// Kerberos Constants
28// ---------------------------------------------------------------------------
29
30/// Default Kerberos port
31pub const KDC_PORT: u16 = 88;
32
33/// Kerberos protocol version
34const KRB5_VERSION: i64 = 5;
35
36/// Ticket version
37const TKT_VERSION: i64 = 5;
38
39// ---------------------------------------------------------------------------
40// Message Types
41// ---------------------------------------------------------------------------
42
43/// Kerberos message types
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45#[repr(u8)]
46pub enum KerberosMsgType {
47    /// AS-REQ (10)
48    AsReq = 10,
49    /// AS-REP (11)
50    AsRep = 11,
51    /// TGS-REQ (12)
52    TgsReq = 12,
53    /// TGS-REP (13)
54    TgsRep = 13,
55    /// AP-REQ (14)
56    ApReq = 14,
57    /// AP-REP (15)
58    ApRep = 15,
59    /// KRB-ERROR (30)
60    Error = 30,
61}
62
63impl KerberosMsgType {
64    /// Create from an integer.
65    fn from_i64(v: i64) -> Option<Self> {
66        match v {
67            10 => Some(KerberosMsgType::AsReq),
68            11 => Some(KerberosMsgType::AsRep),
69            12 => Some(KerberosMsgType::TgsReq),
70            13 => Some(KerberosMsgType::TgsRep),
71            14 => Some(KerberosMsgType::ApReq),
72            15 => Some(KerberosMsgType::ApRep),
73            30 => Some(KerberosMsgType::Error),
74            _ => None,
75        }
76    }
77}
78
79// ---------------------------------------------------------------------------
80// Name Types
81// ---------------------------------------------------------------------------
82
83/// Kerberos name types
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85#[repr(u32)]
86pub enum NameType {
87    /// Principal name (user)
88    Principal = 1,
89    /// Service and instance (service/hostname)
90    SrvInst = 2,
91    /// Service and host (HTTP/host@realm)
92    SrvHst = 3,
93}
94
95// ---------------------------------------------------------------------------
96// Encryption Types
97// ---------------------------------------------------------------------------
98
99/// Kerberos encryption types
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101#[repr(i32)]
102pub enum EncryptionType {
103    /// DES3-CBC-SHA1 (legacy)
104    Des3CbcSha1 = 16,
105    /// AES128-CTS-HMAC-SHA1-96
106    Aes128CtsHmacSha1 = 17,
107    /// AES256-CTS-HMAC-SHA1-96 (preferred)
108    Aes256CtsHmacSha1 = 18,
109}
110
111impl EncryptionType {
112    /// Key size in bytes for this encryption type.
113    pub fn key_size(&self) -> usize {
114        match self {
115            EncryptionType::Des3CbcSha1 => 24,
116            EncryptionType::Aes128CtsHmacSha1 => 16,
117            EncryptionType::Aes256CtsHmacSha1 => 32,
118        }
119    }
120
121    /// Create from integer etype value.
122    pub(crate) fn from_i64(v: i64) -> Option<Self> {
123        match v {
124            16 => Some(EncryptionType::Des3CbcSha1),
125            17 => Some(EncryptionType::Aes128CtsHmacSha1),
126            18 => Some(EncryptionType::Aes256CtsHmacSha1),
127            _ => None,
128        }
129    }
130}
131
132// ---------------------------------------------------------------------------
133// Principal Name
134// ---------------------------------------------------------------------------
135
136/// Kerberos principal name.
137///
138/// A principal has a name type and one or more name components.
139/// For example, `krbtgt/EXAMPLE.COM` has type SrvInst and components
140/// `["krbtgt", "EXAMPLE.COM"]`.
141#[cfg(feature = "alloc")]
142#[derive(Debug, Clone, PartialEq, Eq)]
143pub struct PrincipalName {
144    /// Name type (NT_PRINCIPAL, NT_SRV_INST, etc.)
145    pub name_type: NameType,
146    /// Name string components
147    pub name_string: Vec<String>,
148}
149
150#[cfg(feature = "alloc")]
151impl PrincipalName {
152    /// Create a simple principal (type NT_PRINCIPAL, single component).
153    pub fn new_principal(name: &str) -> Self {
154        Self {
155            name_type: NameType::Principal,
156            name_string: vec![String::from(name)],
157        }
158    }
159
160    /// Create a service principal (type NT_SRV_INST, two components).
161    pub fn new_service(service: &str, instance: &str) -> Self {
162        Self {
163            name_type: NameType::SrvInst,
164            name_string: vec![String::from(service), String::from(instance)],
165        }
166    }
167
168    /// Create the krbtgt principal for a realm.
169    pub fn krbtgt(realm: &str) -> Self {
170        Self::new_service("krbtgt", realm)
171    }
172
173    /// Encode as ASN.1 SEQUENCE { name-type, name-string }.
174    pub fn encode(&self) -> Vec<u8> {
175        let name_type = AsnEncoder::encode(&AsnValue::Integer(self.name_type as i64));
176        let name_type_ctx = encode_context_specific(0, true, &name_type);
177
178        let name_strings: Vec<AsnValue> = self
179            .name_string
180            .iter()
181            .map(|s| AsnValue::OctetString(s.as_bytes().to_vec()))
182            .collect();
183        let name_seq = AsnEncoder::encode(&AsnValue::Sequence(name_strings));
184        let name_seq_ctx = encode_context_specific(1, true, &name_seq);
185
186        let mut content = Vec::new();
187        content.extend_from_slice(&name_type_ctx);
188        content.extend_from_slice(&name_seq_ctx);
189        AsnEncoder::encode(&AsnValue::Sequence(vec![AsnValue::OctetString(content)]))
190    }
191
192    /// Display as "component1/component2" format.
193    pub fn to_text(&self) -> String {
194        let mut s = String::new();
195        for (i, part) in self.name_string.iter().enumerate() {
196            if i > 0 {
197                s.push('/');
198            }
199            s.push_str(part);
200        }
201        s
202    }
203}
204
205// ---------------------------------------------------------------------------
206// Kerberos Time
207// ---------------------------------------------------------------------------
208
209/// Kerberos timestamp (seconds since epoch, integer-only).
210///
211/// Stored as a u64 for compatibility with the kernel timer subsystem.
212/// Kerberos GeneralizedTime format is "YYYYMMDDHHMMSSZ".
213#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
214pub struct KerberosTime {
215    /// Seconds since Unix epoch
216    pub timestamp: u64,
217}
218
219impl KerberosTime {
220    /// Create from a Unix timestamp.
221    pub const fn from_timestamp(ts: u64) -> Self {
222        Self { timestamp: ts }
223    }
224
225    /// Get the current time from the kernel timer.
226    pub fn now() -> Self {
227        Self {
228            timestamp: crate::arch::timer::get_timestamp_secs(),
229        }
230    }
231
232    /// Check if this time has passed.
233    pub fn has_expired(&self) -> bool {
234        let now = crate::arch::timer::get_timestamp_secs();
235        now >= self.timestamp
236    }
237
238    /// Encode as ASN.1 GeneralizedTime string.
239    ///
240    /// Uses a simplified format: "YYYYMMDDHHMMSSZ".
241    /// This is an approximation since we derive date from epoch seconds
242    /// using integer math only.
243    #[cfg(feature = "alloc")]
244    pub fn encode_generalized_time(&self) -> Vec<u8> {
245        // Simplified: encode as an integer timestamp wrapped in context tag
246        AsnEncoder::encode(&AsnValue::OctetString(
247            self.timestamp.to_be_bytes().to_vec(),
248        ))
249    }
250}
251
252// KerberosTime Default: timestamp 0 (epoch)
253
254// ---------------------------------------------------------------------------
255// Encrypted Data
256// ---------------------------------------------------------------------------
257
258/// Encrypted data container (EncryptedData in RFC 4120).
259#[cfg(feature = "alloc")]
260#[derive(Debug, Clone, PartialEq, Eq)]
261pub struct EncryptedData {
262    /// Encryption type
263    pub etype: EncryptionType,
264    /// Key version number (optional)
265    pub kvno: Option<u32>,
266    /// Cipher text
267    pub cipher: Vec<u8>,
268}
269
270#[cfg(feature = "alloc")]
271impl EncryptedData {
272    /// Create new encrypted data.
273    pub fn new(etype: EncryptionType, kvno: Option<u32>, cipher: Vec<u8>) -> Self {
274        Self {
275            etype,
276            kvno,
277            cipher,
278        }
279    }
280
281    /// Encode as ASN.1 SEQUENCE.
282    pub fn encode(&self) -> Vec<u8> {
283        let etype = encode_context_specific(
284            0,
285            true,
286            &AsnEncoder::encode(&AsnValue::Integer(self.etype as i64)),
287        );
288
289        let mut content = Vec::new();
290        content.extend_from_slice(&etype);
291
292        if let Some(kvno) = self.kvno {
293            let kvno_enc = encode_context_specific(
294                1,
295                true,
296                &AsnEncoder::encode(&AsnValue::Integer(kvno as i64)),
297            );
298            content.extend_from_slice(&kvno_enc);
299        }
300
301        let cipher = encode_context_specific(
302            2,
303            true,
304            &AsnEncoder::encode(&AsnValue::OctetString(self.cipher.clone())),
305        );
306        content.extend_from_slice(&cipher);
307
308        AsnEncoder::encode(&AsnValue::Sequence(vec![AsnValue::OctetString(content)]))
309    }
310}
311
312// ---------------------------------------------------------------------------
313// Ticket
314// ---------------------------------------------------------------------------
315
316/// Kerberos Ticket.
317#[cfg(feature = "alloc")]
318#[derive(Debug, Clone, PartialEq, Eq)]
319pub struct Ticket {
320    /// Ticket version (always 5)
321    pub tkt_vno: i64,
322    /// Realm
323    pub realm: String,
324    /// Server principal name
325    pub sname: PrincipalName,
326    /// Encrypted part
327    pub enc_part: EncryptedData,
328}
329
330#[cfg(feature = "alloc")]
331impl Ticket {
332    /// Create a new ticket.
333    pub fn new(realm: &str, sname: PrincipalName, enc_part: EncryptedData) -> Self {
334        Self {
335            tkt_vno: TKT_VERSION,
336            realm: String::from(realm),
337            sname,
338            enc_part,
339        }
340    }
341
342    /// Encode as ASN.1 [APPLICATION 1] SEQUENCE.
343    pub fn encode(&self) -> Vec<u8> {
344        let tkt_vno = encode_context_specific(
345            0,
346            true,
347            &AsnEncoder::encode(&AsnValue::Integer(self.tkt_vno)),
348        );
349        let realm = encode_context_specific(
350            1,
351            true,
352            &AsnEncoder::encode(&AsnValue::OctetString(self.realm.as_bytes().to_vec())),
353        );
354        let sname = encode_context_specific(2, true, &self.sname.encode());
355        let enc_part = encode_context_specific(3, true, &self.enc_part.encode());
356
357        let mut content = Vec::new();
358        content.extend_from_slice(&tkt_vno);
359        content.extend_from_slice(&realm);
360        content.extend_from_slice(&sname);
361        content.extend_from_slice(&enc_part);
362
363        encode_application(1, true, &content)
364    }
365}
366
367// ---------------------------------------------------------------------------
368// KDC Request Body
369// ---------------------------------------------------------------------------
370
371/// KDC request body (KDC-REQ-BODY).
372#[cfg(feature = "alloc")]
373#[derive(Debug, Clone)]
374pub struct KdcReqBody {
375    /// KDC options flags
376    pub kdc_options: u32,
377    /// Client principal name (optional, present in AS-REQ)
378    pub cname: Option<PrincipalName>,
379    /// Realm
380    pub realm: String,
381    /// Server principal name
382    pub sname: Option<PrincipalName>,
383    /// Requested start time
384    pub from: Option<KerberosTime>,
385    /// Requested end time
386    pub till: KerberosTime,
387    /// Requested renewal time
388    pub rtime: Option<KerberosTime>,
389    /// Random nonce for replay protection
390    pub nonce: u32,
391    /// Requested encryption types (in preference order)
392    pub etype: Vec<EncryptionType>,
393}
394
395#[cfg(feature = "alloc")]
396impl KdcReqBody {
397    /// Encode as ASN.1 SEQUENCE.
398    pub fn encode(&self) -> Vec<u8> {
399        let mut content = Vec::new();
400
401        // kdc-options [0] KDCOptions (BIT STRING)
402        let options_bytes = self.kdc_options.to_be_bytes();
403        let options = encode_context_specific(
404            0,
405            true,
406            &AsnEncoder::encode(&AsnValue::BitString(options_bytes.to_vec(), 0)),
407        );
408        content.extend_from_slice(&options);
409
410        // cname [1] PrincipalName OPTIONAL
411        if let Some(ref cname) = self.cname {
412            let cname_enc = encode_context_specific(1, true, &cname.encode());
413            content.extend_from_slice(&cname_enc);
414        }
415
416        // realm [2] Realm
417        let realm = encode_context_specific(
418            2,
419            true,
420            &AsnEncoder::encode(&AsnValue::OctetString(self.realm.as_bytes().to_vec())),
421        );
422        content.extend_from_slice(&realm);
423
424        // sname [3] PrincipalName OPTIONAL
425        if let Some(ref sname) = self.sname {
426            let sname_enc = encode_context_specific(3, true, &sname.encode());
427            content.extend_from_slice(&sname_enc);
428        }
429
430        // till [5] KerberosTime
431        let till = encode_context_specific(5, true, &self.till.encode_generalized_time());
432        content.extend_from_slice(&till);
433
434        // nonce [7] UInt32
435        let nonce = encode_context_specific(
436            7,
437            true,
438            &AsnEncoder::encode(&AsnValue::Integer(self.nonce as i64)),
439        );
440        content.extend_from_slice(&nonce);
441
442        // etype [8] SEQUENCE OF Int32
443        let etypes: Vec<AsnValue> = self
444            .etype
445            .iter()
446            .map(|e| AsnValue::Integer(*e as i64))
447            .collect();
448        let etype_enc =
449            encode_context_specific(8, true, &AsnEncoder::encode(&AsnValue::Sequence(etypes)));
450        content.extend_from_slice(&etype_enc);
451
452        AsnEncoder::encode(&AsnValue::Sequence(vec![AsnValue::OctetString(content)]))
453    }
454}
455
456// ---------------------------------------------------------------------------
457// KDC Reply Parts
458// ---------------------------------------------------------------------------
459
460/// Encrypted part of a KDC reply (EncKDCRepPart).
461#[cfg(feature = "alloc")]
462#[derive(Debug, Clone)]
463pub struct EncKdcRepPart {
464    /// Session key
465    pub session_key: Vec<u8>,
466    /// Session key encryption type
467    pub session_key_etype: EncryptionType,
468    /// Nonce (must match request)
469    pub nonce: u32,
470    /// Ticket flags
471    pub flags: u32,
472    /// Authentication time
473    pub authtime: KerberosTime,
474    /// Start time
475    pub starttime: Option<KerberosTime>,
476    /// End time
477    pub endtime: KerberosTime,
478    /// Renewal end time
479    pub renew_till: Option<KerberosTime>,
480    /// Server realm
481    pub srealm: String,
482    /// Server principal name
483    pub sname: PrincipalName,
484}
485
486// ---------------------------------------------------------------------------
487// Kerberos Client
488// ---------------------------------------------------------------------------
489
490/// Kerberos v5 client.
491///
492/// Handles AS-REQ (initial authentication) and TGS-REQ (service ticket
493/// request) flows.
494#[cfg(feature = "alloc")]
495pub struct KerberosClient {
496    /// Client principal
497    pub client_principal: PrincipalName,
498    /// Realm
499    pub realm: String,
500    /// Long-term key derived from password
501    key: Vec<u8>,
502    /// TGT (obtained via AS-REQ)
503    tgt: Option<Ticket>,
504    /// TGT session key
505    session_key: Option<Vec<u8>>,
506    /// TGT expiration
507    tgt_expiry: KerberosTime,
508}
509
510#[cfg(feature = "alloc")]
511impl KerberosClient {
512    /// Create a new Kerberos client.
513    ///
514    /// Derives a long-term key from the password using string2key.
515    pub fn new(username: &str, realm: &str, password: &str) -> Self {
516        let client_principal = PrincipalName::new_principal(username);
517        let key = Self::derive_key(password, realm, username);
518
519        Self {
520            client_principal,
521            realm: String::from(realm),
522            key,
523            tgt: None,
524            session_key: None,
525            tgt_expiry: KerberosTime::default(),
526        }
527    }
528
529    /// Whether the client has a valid (non-expired) TGT.
530    pub fn has_valid_tgt(&self) -> bool {
531        self.tgt.is_some() && !self.tgt_expiry.has_expired()
532    }
533
534    /// Get the TGT, if present.
535    pub fn tgt(&self) -> Option<&Ticket> {
536        self.tgt.as_ref()
537    }
538
539    /// Get the session key, if present.
540    pub fn session_key(&self) -> Option<&[u8]> {
541        self.session_key.as_deref()
542    }
543
544    // -----------------------------------------------------------------------
545    // Key Derivation
546    // -----------------------------------------------------------------------
547
548    /// Derive a key from a password using string2key for AES-256.
549    ///
550    /// Per RFC 3962: string2key(password) = random2key(PBKDF2(password, salt,
551    /// iteration_count, key_size))
552    ///
553    /// Salt for Kerberos AES = realm + principal_name
554    ///
555    /// This is a simplified implementation using HMAC-SHA256 as the PRF
556    /// instead of HMAC-SHA1 (the full implementation would use HMAC-SHA1
557    /// per RFC 3962 Section 4).
558    fn derive_key(password: &str, realm: &str, username: &str) -> Vec<u8> {
559        // Salt = realm || username (per RFC 3962)
560        let mut salt = Vec::with_capacity(realm.len() + username.len());
561        salt.extend_from_slice(realm.as_bytes());
562        salt.extend_from_slice(username.as_bytes());
563
564        // PBKDF2-HMAC-SHA256, 4096 iterations, 32-byte output
565        Self::pbkdf2_derive(password.as_bytes(), &salt, 4096)
566    }
567
568    /// PBKDF2 key derivation (simplified, integer-only).
569    ///
570    /// Uses HMAC-SHA256 as the PRF. Returns 32 bytes.
571    fn pbkdf2_derive(password: &[u8], salt: &[u8], iterations: u32) -> Vec<u8> {
572        use crate::crypto::hash::sha256;
573
574        // HMAC-SHA256(key, message) -- simplified inline implementation
575        let hmac = |key: &[u8], msg: &[u8]| -> [u8; 32] {
576            const BLOCK_SIZE: usize = 64;
577            const IPAD: u8 = 0x36;
578            const OPAD: u8 = 0x5c;
579
580            // If key > block size, hash it
581            let key_bytes: [u8; 32];
582            let actual_key = if key.len() > BLOCK_SIZE {
583                key_bytes = *sha256(key).as_bytes();
584                &key_bytes[..]
585            } else {
586                key
587            };
588
589            let mut padded_key = [0u8; BLOCK_SIZE];
590            padded_key[..actual_key.len()].copy_from_slice(actual_key);
591
592            // Inner: SHA256((key XOR ipad) || message)
593            let mut inner = [0u8; 192];
594            for (i, byte) in padded_key.iter().enumerate() {
595                inner[i] = byte ^ IPAD;
596            }
597            let inner_len = BLOCK_SIZE + msg.len().min(128);
598            let copy_len = msg.len().min(128);
599            inner[BLOCK_SIZE..BLOCK_SIZE + copy_len].copy_from_slice(&msg[..copy_len]);
600            let inner_hash = sha256(&inner[..inner_len]);
601
602            // Outer: SHA256((key XOR opad) || inner_hash)
603            let mut outer = [0u8; BLOCK_SIZE + 32];
604            for (i, byte) in padded_key.iter().enumerate() {
605                outer[i] = byte ^ OPAD;
606            }
607            outer[BLOCK_SIZE..BLOCK_SIZE + 32].copy_from_slice(inner_hash.as_bytes());
608            *sha256(&outer[..BLOCK_SIZE + 32]).as_bytes()
609        };
610
611        // PBKDF2 for block 1 (we only need one 32-byte block)
612        // U1 = HMAC(password, salt || INT(1))
613        let mut salt_counter = Vec::with_capacity(salt.len() + 4);
614        salt_counter.extend_from_slice(salt);
615        salt_counter.extend_from_slice(&1u32.to_be_bytes());
616
617        let u1 = hmac(password, &salt_counter);
618        let mut result = u1;
619        let mut prev = u1;
620
621        for _ in 1..iterations {
622            let u_next = hmac(password, &prev);
623            for (r, u) in result.iter_mut().zip(u_next.iter()) {
624                *r ^= u;
625            }
626            prev = u_next;
627        }
628
629        result.to_vec()
630    }
631
632    // -----------------------------------------------------------------------
633    // AS-REQ / AS-REP
634    // -----------------------------------------------------------------------
635
636    /// Build an AS-REQ message to request a TGT.
637    ///
638    /// Returns BER-encoded bytes ready to send to the KDC.
639    pub fn request_tgt(&mut self) -> Vec<u8> {
640        let nonce = self.generate_nonce();
641
642        let body = KdcReqBody {
643            kdc_options: 0x4000_0000, // forwardable
644            cname: Some(self.client_principal.clone()),
645            realm: self.realm.clone(),
646            sname: Some(PrincipalName::krbtgt(&self.realm)),
647            from: None,
648            till: KerberosTime::from_timestamp(
649                crate::arch::timer::get_timestamp_secs().saturating_add(36000),
650            ), // 10 hours
651            rtime: None,
652            nonce,
653            etype: vec![
654                EncryptionType::Aes256CtsHmacSha1,
655                EncryptionType::Aes128CtsHmacSha1,
656            ],
657        };
658
659        self.encode_as_req(&body)
660    }
661
662    /// Encode an AS-REQ message.
663    ///
664    /// AS-REQ ::= [APPLICATION 10] KDC-REQ
665    /// KDC-REQ ::= SEQUENCE { pvno, msg-type, padata, req-body }
666    fn encode_as_req(&self, body: &KdcReqBody) -> Vec<u8> {
667        let mut content = Vec::new();
668
669        // pvno [1] INTEGER (5)
670        let pvno = encode_context_specific(
671            1,
672            true,
673            &AsnEncoder::encode(&AsnValue::Integer(KRB5_VERSION)),
674        );
675        content.extend_from_slice(&pvno);
676
677        // msg-type [2] INTEGER (10)
678        let msg_type = encode_context_specific(
679            2,
680            true,
681            &AsnEncoder::encode(&AsnValue::Integer(KerberosMsgType::AsReq as i64)),
682        );
683        content.extend_from_slice(&msg_type);
684
685        // padata [3] SEQUENCE OF PA-DATA OPTIONAL (empty for now)
686        let padata = encode_context_specific(
687            3,
688            true,
689            &AsnEncoder::encode(&AsnValue::Sequence(Vec::new())),
690        );
691        content.extend_from_slice(&padata);
692
693        // req-body [4] KDC-REQ-BODY
694        let body_enc = encode_context_specific(4, true, &body.encode());
695        content.extend_from_slice(&body_enc);
696
697        encode_application(KerberosMsgType::AsReq as u8, true, &content)
698    }
699
700    /// Parse an AS-REP message.
701    ///
702    /// Extracts the TGT and encrypted part. The caller must decrypt the
703    /// encrypted part using the client's long-term key to obtain the
704    /// session key.
705    pub fn parse_as_rep(&mut self, _data: &[u8]) -> Result<AsRepParts, KernelError> {
706        // In a full implementation, we would:
707        // 1. Decode the APPLICATION 11 envelope
708        // 2. Extract the ticket from [5]
709        // 3. Extract the enc-part from [6]
710        // 4. Decrypt enc-part using self.key
711        // 5. Extract session key and TGT expiration
712        //
713        // For now, return a stub indicating the structure is understood.
714        Err(KernelError::NotImplemented {
715            feature: "kerberos_as_rep_parse",
716        })
717    }
718
719    /// Store a TGT obtained from an AS-REP.
720    pub fn store_tgt(&mut self, ticket: Ticket, session_key: Vec<u8>, expiry: KerberosTime) {
721        self.tgt = Some(ticket);
722        self.session_key = Some(session_key);
723        self.tgt_expiry = expiry;
724    }
725
726    // -----------------------------------------------------------------------
727    // TGS-REQ / TGS-REP
728    // -----------------------------------------------------------------------
729
730    /// Build a TGS-REQ message to request a service ticket.
731    ///
732    /// Requires a valid TGT (obtained via `request_tgt` + `parse_as_rep`).
733    pub fn request_service_ticket(
734        &mut self,
735        service: &str,
736        hostname: &str,
737    ) -> Result<Vec<u8>, KernelError> {
738        if !self.has_valid_tgt() {
739            return Err(KernelError::InvalidState {
740                expected: "valid TGT",
741                actual: "no TGT or expired",
742            });
743        }
744
745        let nonce = self.generate_nonce();
746        let sname = PrincipalName::new_service(service, hostname);
747
748        let body = KdcReqBody {
749            kdc_options: 0x4000_0000,
750            cname: None, // Not included in TGS-REQ
751            realm: self.realm.clone(),
752            sname: Some(sname),
753            from: None,
754            till: KerberosTime::from_timestamp(
755                crate::arch::timer::get_timestamp_secs().saturating_add(36000),
756            ),
757            rtime: None,
758            nonce,
759            etype: vec![
760                EncryptionType::Aes256CtsHmacSha1,
761                EncryptionType::Aes128CtsHmacSha1,
762            ],
763        };
764
765        Ok(self.encode_tgs_req(&body))
766    }
767
768    /// Encode a TGS-REQ message.
769    ///
770    /// TGS-REQ ::= [APPLICATION 12] KDC-REQ
771    fn encode_tgs_req(&self, body: &KdcReqBody) -> Vec<u8> {
772        let mut content = Vec::new();
773
774        // pvno [1]
775        let pvno = encode_context_specific(
776            1,
777            true,
778            &AsnEncoder::encode(&AsnValue::Integer(KRB5_VERSION)),
779        );
780        content.extend_from_slice(&pvno);
781
782        // msg-type [2]
783        let msg_type = encode_context_specific(
784            2,
785            true,
786            &AsnEncoder::encode(&AsnValue::Integer(KerberosMsgType::TgsReq as i64)),
787        );
788        content.extend_from_slice(&msg_type);
789
790        // padata [3] -- would contain AP-REQ with TGT
791        // For a complete implementation, encode the TGT as PA-TGS-REQ here
792        if let Some(ref tgt) = self.tgt {
793            let tgt_enc = tgt.encode();
794            let pa_tgs_req = AsnEncoder::encode(&AsnValue::Sequence(vec![
795                AsnValue::Integer(1), // PA-TGS-REQ type
796                AsnValue::OctetString(tgt_enc),
797            ]));
798            let padata = encode_context_specific(
799                3,
800                true,
801                &AsnEncoder::encode(&AsnValue::Sequence(vec![AsnValue::OctetString(pa_tgs_req)])),
802            );
803            content.extend_from_slice(&padata);
804        }
805
806        // req-body [4]
807        let body_enc = encode_context_specific(4, true, &body.encode());
808        content.extend_from_slice(&body_enc);
809
810        encode_application(KerberosMsgType::TgsReq as u8, true, &content)
811    }
812
813    /// Parse a TGS-REP message.
814    pub fn parse_tgs_rep(&mut self, _data: &[u8]) -> Result<TgsRepParts, KernelError> {
815        Err(KernelError::NotImplemented {
816            feature: "kerberos_tgs_rep_parse",
817        })
818    }
819
820    // -----------------------------------------------------------------------
821    // Helpers
822    // -----------------------------------------------------------------------
823
824    /// Generate a pseudo-random nonce using the kernel timer.
825    fn generate_nonce(&self) -> u32 {
826        // Use timestamp-based nonce (sufficient for stub; real implementation
827        // would use CSPRNG)
828        let ts = crate::arch::timer::get_timestamp_secs();
829        (ts & 0xFFFF_FFFF) as u32
830    }
831
832    /// Get the client's derived key.
833    pub fn key(&self) -> &[u8] {
834        &self.key
835    }
836
837    /// Get the realm.
838    pub fn realm(&self) -> &str {
839        &self.realm
840    }
841}
842
843// ---------------------------------------------------------------------------
844// Reply parts (for parse_as_rep / parse_tgs_rep return types)
845// ---------------------------------------------------------------------------
846
847/// Parsed AS-REP components.
848#[cfg(feature = "alloc")]
849#[derive(Debug)]
850pub struct AsRepParts {
851    /// The TGT ticket
852    pub ticket: Ticket,
853    /// The encrypted KDC reply part
854    pub enc_part: EncryptedData,
855}
856
857/// Parsed TGS-REP components.
858#[cfg(feature = "alloc")]
859#[derive(Debug)]
860pub struct TgsRepParts {
861    /// The service ticket
862    pub ticket: Ticket,
863    /// The encrypted KDC reply part
864    pub enc_part: EncryptedData,
865}
866
867// ---------------------------------------------------------------------------
868// AES-CTS Key Derivation Stubs
869// ---------------------------------------------------------------------------
870
871/// Derive a usage-specific key (dk) from a base key.
872///
873/// Per RFC 3961: dk(base_key, usage) = DK(base_key, usage_constant)
874/// This is a stub that returns a truncated HMAC of the base key.
875#[cfg(feature = "alloc")]
876pub fn derive_usage_key(base_key: &[u8], usage: u32) -> Vec<u8> {
877    use crate::crypto::hash::sha256;
878
879    let mut input = Vec::with_capacity(base_key.len() + 4);
880    input.extend_from_slice(base_key);
881    input.extend_from_slice(&usage.to_be_bytes());
882
883    let hash = sha256(&input);
884    hash.as_bytes().to_vec()
885}
886
887/// Convert random bytes to a key (random-to-key).
888///
889/// For AES, this is the identity function: the random bytes ARE the key.
890#[cfg(feature = "alloc")]
891pub fn random_to_key(random_bytes: &[u8], etype: EncryptionType) -> Vec<u8> {
892    let key_size = etype.key_size();
893    if random_bytes.len() >= key_size {
894        random_bytes[..key_size].to_vec()
895    } else {
896        let mut key = random_bytes.to_vec();
897        key.resize(key_size, 0);
898        key
899    }
900}
901
902// ---------------------------------------------------------------------------
903// Tests
904// ---------------------------------------------------------------------------
905
906#[cfg(test)]
907mod tests {
908    use super::*;
909
910    #[test]
911    fn test_principal_name_simple() {
912        let p = PrincipalName::new_principal("alice");
913        assert_eq!(p.name_type, NameType::Principal);
914        assert_eq!(p.name_string.len(), 1);
915        assert_eq!(p.name_string[0], "alice");
916    }
917
918    #[test]
919    fn test_principal_name_service() {
920        let p = PrincipalName::new_service("HTTP", "www.example.com");
921        assert_eq!(p.name_type, NameType::SrvInst);
922        assert_eq!(p.name_string.len(), 2);
923        assert_eq!(p.to_text(), "HTTP/www.example.com");
924    }
925
926    #[test]
927    fn test_principal_krbtgt() {
928        let p = PrincipalName::krbtgt("EXAMPLE.COM");
929        assert_eq!(p.name_string[0], "krbtgt");
930        assert_eq!(p.name_string[1], "EXAMPLE.COM");
931    }
932
933    #[test]
934    fn test_kerberos_time() {
935        let t = KerberosTime::from_timestamp(1000);
936        assert_eq!(t.timestamp, 1000);
937    }
938
939    #[test]
940    fn test_encryption_type_key_size() {
941        assert_eq!(EncryptionType::Aes256CtsHmacSha1.key_size(), 32);
942        assert_eq!(EncryptionType::Aes128CtsHmacSha1.key_size(), 16);
943        assert_eq!(EncryptionType::Des3CbcSha1.key_size(), 24);
944    }
945
946    #[test]
947    fn test_encrypted_data_encode() {
948        let ed = EncryptedData::new(EncryptionType::Aes256CtsHmacSha1, Some(1), vec![0xDE, 0xAD]);
949        let encoded = ed.encode();
950        assert!(!encoded.is_empty());
951    }
952
953    #[test]
954    fn test_ticket_encode() {
955        let ticket = Ticket::new(
956            "EXAMPLE.COM",
957            PrincipalName::krbtgt("EXAMPLE.COM"),
958            EncryptedData::new(EncryptionType::Aes256CtsHmacSha1, None, vec![0x01]),
959        );
960        let encoded = ticket.encode();
961        assert!(!encoded.is_empty());
962    }
963
964    #[test]
965    fn test_kerberos_client_creation() {
966        let client = KerberosClient::new("alice", "EXAMPLE.COM", "password");
967        assert_eq!(client.realm(), "EXAMPLE.COM");
968        assert!(!client.has_valid_tgt());
969        assert_eq!(client.key().len(), 32); // AES-256 key
970    }
971
972    #[test]
973    fn test_derive_key_deterministic() {
974        let k1 = KerberosClient::derive_key("password", "EXAMPLE.COM", "alice");
975        let k2 = KerberosClient::derive_key("password", "EXAMPLE.COM", "alice");
976        assert_eq!(k1, k2);
977    }
978
979    #[test]
980    fn test_derive_key_different_inputs() {
981        let k1 = KerberosClient::derive_key("password", "EXAMPLE.COM", "alice");
982        let k2 = KerberosClient::derive_key("password", "EXAMPLE.COM", "bob");
983        assert_ne!(k1, k2);
984    }
985
986    #[test]
987    fn test_request_tgt_produces_bytes() {
988        let mut client = KerberosClient::new("alice", "EXAMPLE.COM", "password");
989        let req = client.request_tgt();
990        assert!(!req.is_empty());
991    }
992
993    #[test]
994    fn test_request_service_ticket_requires_tgt() {
995        let mut client = KerberosClient::new("alice", "EXAMPLE.COM", "password");
996        let result = client.request_service_ticket("HTTP", "www.example.com");
997        assert!(result.is_err());
998    }
999
1000    #[test]
1001    fn test_msg_type_from_i64() {
1002        assert_eq!(KerberosMsgType::from_i64(10), Some(KerberosMsgType::AsReq));
1003        assert_eq!(KerberosMsgType::from_i64(99), None);
1004    }
1005
1006    #[test]
1007    fn test_derive_usage_key() {
1008        let base = vec![0x42u8; 32];
1009        let dk = derive_usage_key(&base, 1);
1010        assert_eq!(dk.len(), 32);
1011
1012        // Different usages produce different keys
1013        let dk2 = derive_usage_key(&base, 2);
1014        assert_ne!(dk, dk2);
1015    }
1016}