1#![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
26pub const KDC_PORT: u16 = 88;
32
33const KRB5_VERSION: i64 = 5;
35
36const TKT_VERSION: i64 = 5;
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45#[repr(u8)]
46pub enum KerberosMsgType {
47 AsReq = 10,
49 AsRep = 11,
51 TgsReq = 12,
53 TgsRep = 13,
55 ApReq = 14,
57 ApRep = 15,
59 Error = 30,
61}
62
63impl KerberosMsgType {
64 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85#[repr(u32)]
86pub enum NameType {
87 Principal = 1,
89 SrvInst = 2,
91 SrvHst = 3,
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101#[repr(i32)]
102pub enum EncryptionType {
103 Des3CbcSha1 = 16,
105 Aes128CtsHmacSha1 = 17,
107 Aes256CtsHmacSha1 = 18,
109}
110
111impl EncryptionType {
112 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 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#[cfg(feature = "alloc")]
142#[derive(Debug, Clone, PartialEq, Eq)]
143pub struct PrincipalName {
144 pub name_type: NameType,
146 pub name_string: Vec<String>,
148}
149
150#[cfg(feature = "alloc")]
151impl PrincipalName {
152 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 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 pub fn krbtgt(realm: &str) -> Self {
170 Self::new_service("krbtgt", realm)
171 }
172
173 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 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#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
214pub struct KerberosTime {
215 pub timestamp: u64,
217}
218
219impl KerberosTime {
220 pub const fn from_timestamp(ts: u64) -> Self {
222 Self { timestamp: ts }
223 }
224
225 pub fn now() -> Self {
227 Self {
228 timestamp: crate::arch::timer::get_timestamp_secs(),
229 }
230 }
231
232 pub fn has_expired(&self) -> bool {
234 let now = crate::arch::timer::get_timestamp_secs();
235 now >= self.timestamp
236 }
237
238 #[cfg(feature = "alloc")]
244 pub fn encode_generalized_time(&self) -> Vec<u8> {
245 AsnEncoder::encode(&AsnValue::OctetString(
247 self.timestamp.to_be_bytes().to_vec(),
248 ))
249 }
250}
251
252#[cfg(feature = "alloc")]
260#[derive(Debug, Clone, PartialEq, Eq)]
261pub struct EncryptedData {
262 pub etype: EncryptionType,
264 pub kvno: Option<u32>,
266 pub cipher: Vec<u8>,
268}
269
270#[cfg(feature = "alloc")]
271impl EncryptedData {
272 pub fn new(etype: EncryptionType, kvno: Option<u32>, cipher: Vec<u8>) -> Self {
274 Self {
275 etype,
276 kvno,
277 cipher,
278 }
279 }
280
281 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#[cfg(feature = "alloc")]
318#[derive(Debug, Clone, PartialEq, Eq)]
319pub struct Ticket {
320 pub tkt_vno: i64,
322 pub realm: String,
324 pub sname: PrincipalName,
326 pub enc_part: EncryptedData,
328}
329
330#[cfg(feature = "alloc")]
331impl Ticket {
332 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 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#[cfg(feature = "alloc")]
373#[derive(Debug, Clone)]
374pub struct KdcReqBody {
375 pub kdc_options: u32,
377 pub cname: Option<PrincipalName>,
379 pub realm: String,
381 pub sname: Option<PrincipalName>,
383 pub from: Option<KerberosTime>,
385 pub till: KerberosTime,
387 pub rtime: Option<KerberosTime>,
389 pub nonce: u32,
391 pub etype: Vec<EncryptionType>,
393}
394
395#[cfg(feature = "alloc")]
396impl KdcReqBody {
397 pub fn encode(&self) -> Vec<u8> {
399 let mut content = Vec::new();
400
401 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 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 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 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 let till = encode_context_specific(5, true, &self.till.encode_generalized_time());
432 content.extend_from_slice(&till);
433
434 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 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#[cfg(feature = "alloc")]
462#[derive(Debug, Clone)]
463pub struct EncKdcRepPart {
464 pub session_key: Vec<u8>,
466 pub session_key_etype: EncryptionType,
468 pub nonce: u32,
470 pub flags: u32,
472 pub authtime: KerberosTime,
474 pub starttime: Option<KerberosTime>,
476 pub endtime: KerberosTime,
478 pub renew_till: Option<KerberosTime>,
480 pub srealm: String,
482 pub sname: PrincipalName,
484}
485
486#[cfg(feature = "alloc")]
495pub struct KerberosClient {
496 pub client_principal: PrincipalName,
498 pub realm: String,
500 key: Vec<u8>,
502 tgt: Option<Ticket>,
504 session_key: Option<Vec<u8>>,
506 tgt_expiry: KerberosTime,
508}
509
510#[cfg(feature = "alloc")]
511impl KerberosClient {
512 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 pub fn has_valid_tgt(&self) -> bool {
531 self.tgt.is_some() && !self.tgt_expiry.has_expired()
532 }
533
534 pub fn tgt(&self) -> Option<&Ticket> {
536 self.tgt.as_ref()
537 }
538
539 pub fn session_key(&self) -> Option<&[u8]> {
541 self.session_key.as_deref()
542 }
543
544 fn derive_key(password: &str, realm: &str, username: &str) -> Vec<u8> {
559 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 Self::pbkdf2_derive(password.as_bytes(), &salt, 4096)
566 }
567
568 fn pbkdf2_derive(password: &[u8], salt: &[u8], iterations: u32) -> Vec<u8> {
572 use crate::crypto::hash::sha256;
573
574 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 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 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 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 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 pub fn request_tgt(&mut self) -> Vec<u8> {
640 let nonce = self.generate_nonce();
641
642 let body = KdcReqBody {
643 kdc_options: 0x4000_0000, 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 ), 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 fn encode_as_req(&self, body: &KdcReqBody) -> Vec<u8> {
667 let mut content = Vec::new();
668
669 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 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 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 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 pub fn parse_as_rep(&mut self, _data: &[u8]) -> Result<AsRepParts, KernelError> {
706 Err(KernelError::NotImplemented {
715 feature: "kerberos_as_rep_parse",
716 })
717 }
718
719 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 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, 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 fn encode_tgs_req(&self, body: &KdcReqBody) -> Vec<u8> {
772 let mut content = Vec::new();
773
774 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 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 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), 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 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 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 fn generate_nonce(&self) -> u32 {
826 let ts = crate::arch::timer::get_timestamp_secs();
829 (ts & 0xFFFF_FFFF) as u32
830 }
831
832 pub fn key(&self) -> &[u8] {
834 &self.key
835 }
836
837 pub fn realm(&self) -> &str {
839 &self.realm
840 }
841}
842
843#[cfg(feature = "alloc")]
849#[derive(Debug)]
850pub struct AsRepParts {
851 pub ticket: Ticket,
853 pub enc_part: EncryptedData,
855}
856
857#[cfg(feature = "alloc")]
859#[derive(Debug)]
860pub struct TgsRepParts {
861 pub ticket: Ticket,
863 pub enc_part: EncryptedData,
865}
866
867#[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#[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#[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); }
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 let dk2 = derive_usage_key(&base, 2);
1014 assert_ne!(dk, dk2);
1015 }
1016}