1use super::{Error, Result, be32, put_be32};
27
28const S_FIXED: u8 = 0;
29const V_FIXED: u8 = 1;
30const PT_KEYING_MATERIAL: u8 = 2;
31const SIGN_FIXED: u16 = 0x2029; const RESV1_FIXED: u8 = 0;
33const RESV2_FIXED: u8 = 0;
34const RESV3_FIXED: u16 = 0;
35
36pub const KM_KK_NO_SEK: u8 = 0b00;
40pub const KM_KK_EVEN: u8 = 0b01;
42pub const KM_KK_ODD: u8 = 0b10;
44pub const KM_KK_BOTH: u8 = 0b11;
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
50#[cfg_attr(feature = "serde", derive(serde::Serialize))]
51#[non_exhaustive]
52pub enum KmKeyFlag {
53 NoSek,
55 Even,
57 Odd,
59 Both,
61}
62
63impl KmKeyFlag {
64 pub fn from_bits(v: u8) -> Self {
66 match v & 0b11 {
67 KM_KK_EVEN => KmKeyFlag::Even,
68 KM_KK_ODD => KmKeyFlag::Odd,
69 KM_KK_BOTH => KmKeyFlag::Both,
70 _ => KmKeyFlag::NoSek,
71 }
72 }
73
74 pub fn to_bits(self) -> u8 {
76 match self {
77 KmKeyFlag::NoSek => KM_KK_NO_SEK,
78 KmKeyFlag::Even => KM_KK_EVEN,
79 KmKeyFlag::Odd => KM_KK_ODD,
80 KmKeyFlag::Both => KM_KK_BOTH,
81 }
82 }
83
84 pub fn key_count(self) -> u8 {
87 match self {
88 KmKeyFlag::NoSek => 0,
89 KmKeyFlag::Even | KmKeyFlag::Odd => 1,
90 KmKeyFlag::Both => 2,
91 }
92 }
93
94 pub fn name(&self) -> &'static str {
96 match self {
97 KmKeyFlag::NoSek => "no SEK",
98 KmKeyFlag::Even => "even key",
99 KmKeyFlag::Odd => "odd key",
100 KmKeyFlag::Both => "both keys",
101 }
102 }
103}
104
105broadcast_common::impl_spec_display!(KmKeyFlag);
106
107pub const CIPHER_NONE: u8 = 0;
109pub const CIPHER_AES_CTR: u8 = 2;
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
114#[cfg_attr(feature = "serde", derive(serde::Serialize))]
115#[non_exhaustive]
116pub enum Cipher {
117 None,
119 AesCtr,
121 Reserved(u8),
123}
124
125impl Cipher {
126 pub fn from_bits(v: u8) -> Self {
128 match v {
129 CIPHER_NONE => Cipher::None,
130 CIPHER_AES_CTR => Cipher::AesCtr,
131 other => Cipher::Reserved(other),
132 }
133 }
134
135 pub fn to_bits(self) -> u8 {
137 match self {
138 Cipher::None => CIPHER_NONE,
139 Cipher::AesCtr => CIPHER_AES_CTR,
140 Cipher::Reserved(v) => v,
141 }
142 }
143
144 pub fn name(&self) -> &'static str {
146 match self {
147 Cipher::None => "none / KEKI-indexed",
148 Cipher::AesCtr => "AES-CTR",
149 Cipher::Reserved(_) => "reserved",
150 }
151 }
152}
153
154broadcast_common::impl_spec_display!(Cipher, Reserved);
155
156pub const KM_AUTH_NONE: u8 = 0;
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
162#[cfg_attr(feature = "serde", derive(serde::Serialize))]
163#[non_exhaustive]
164pub enum KmAuth {
165 None,
167 Reserved(u8),
169}
170
171impl KmAuth {
172 pub fn from_bits(v: u8) -> Self {
174 match v {
175 KM_AUTH_NONE => KmAuth::None,
176 other => KmAuth::Reserved(other),
177 }
178 }
179
180 pub fn to_bits(self) -> u8 {
182 match self {
183 KmAuth::None => KM_AUTH_NONE,
184 KmAuth::Reserved(v) => v,
185 }
186 }
187
188 pub fn name(&self) -> &'static str {
190 match self {
191 KmAuth::None => "none / KEKI-indexed",
192 KmAuth::Reserved(_) => "reserved",
193 }
194 }
195}
196
197broadcast_common::impl_spec_display!(KmAuth, Reserved);
198
199pub const STREAM_ENCAP_UNSPECIFIED: u8 = 0;
201pub const STREAM_ENCAP_MPEG_TS_UDP: u8 = 1;
203pub const STREAM_ENCAP_MPEG_TS_SRT: u8 = 2;
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
208#[cfg_attr(feature = "serde", derive(serde::Serialize))]
209#[non_exhaustive]
210pub enum StreamEncapsulation {
211 Unspecified,
213 MpegTsUdp,
215 MpegTsSrt,
217 Reserved(u8),
219}
220
221impl StreamEncapsulation {
222 pub fn from_bits(v: u8) -> Self {
224 match v {
225 STREAM_ENCAP_UNSPECIFIED => StreamEncapsulation::Unspecified,
226 STREAM_ENCAP_MPEG_TS_UDP => StreamEncapsulation::MpegTsUdp,
227 STREAM_ENCAP_MPEG_TS_SRT => StreamEncapsulation::MpegTsSrt,
228 other => StreamEncapsulation::Reserved(other),
229 }
230 }
231
232 pub fn to_bits(self) -> u8 {
234 match self {
235 StreamEncapsulation::Unspecified => STREAM_ENCAP_UNSPECIFIED,
236 StreamEncapsulation::MpegTsUdp => STREAM_ENCAP_MPEG_TS_UDP,
237 StreamEncapsulation::MpegTsSrt => STREAM_ENCAP_MPEG_TS_SRT,
238 StreamEncapsulation::Reserved(v) => v,
239 }
240 }
241
242 pub fn name(&self) -> &'static str {
244 match self {
245 StreamEncapsulation::Unspecified => "unspecified / KEKI-indexed",
246 StreamEncapsulation::MpegTsUdp => "MPEG-TS/UDP",
247 StreamEncapsulation::MpegTsSrt => "MPEG-TS/SRT",
248 StreamEncapsulation::Reserved(_) => "reserved",
249 }
250 }
251}
252
253broadcast_common::impl_spec_display!(StreamEncapsulation, Reserved);
254
255#[derive(Debug, Clone, PartialEq, Eq, Hash)]
257#[cfg_attr(feature = "serde", derive(serde::Serialize))]
258pub struct KeyMaterial<'a> {
259 pub kk: KmKeyFlag,
261 pub keki: u32,
263 pub cipher: Cipher,
265 pub auth: KmAuth,
267 pub se: StreamEncapsulation,
269 pub salt: &'a [u8],
272 pub icv: [u8; 8],
274 pub x_sek: &'a [u8],
277 pub o_sek: Option<&'a [u8]>,
280}
281
282impl<'a> KeyMaterial<'a> {
283 pub fn parse(bytes: &'a [u8]) -> Result<Self> {
287 if bytes.len() < 16 {
288 return Err(Error::BufferTooShort {
289 need: 16,
290 have: bytes.len(),
291 what: "key material fixed header",
292 });
293 }
294 let word0 = be32(bytes, 0);
295 let s = (word0 >> 31) as u8;
296 let v = ((word0 >> 28) & 0b111) as u8;
297 let pt = ((word0 >> 24) & 0b1111) as u8;
298 let sign = ((word0 >> 8) & 0xFFFF) as u16;
299 let resv1 = ((word0 >> 2) & 0b0011_1111) as u8;
300 let kk_bits = (word0 & 0b11) as u8;
301
302 if s != S_FIXED {
303 return Err(Error::InvalidKeyMaterial {
304 field: "S",
305 reason: "must be 0",
306 });
307 }
308 if v != V_FIXED {
309 return Err(Error::InvalidKeyMaterial {
310 field: "V",
311 reason: "must be 1",
312 });
313 }
314 if pt != PT_KEYING_MATERIAL {
315 return Err(Error::InvalidKeyMaterial {
316 field: "PT",
317 reason: "must be 2 (Keying Material Message)",
318 });
319 }
320 if sign != SIGN_FIXED {
321 return Err(Error::InvalidKeyMaterial {
322 field: "Sign",
323 reason: "must be 0x2029 ('HAI' PnP Vendor ID)",
324 });
325 }
326 if resv1 != RESV1_FIXED {
327 return Err(Error::InvalidKeyMaterial {
328 field: "Resv1",
329 reason: "must be 0",
330 });
331 }
332 let kk = KmKeyFlag::from_bits(kk_bits);
333
334 let keki = be32(bytes, 4);
335
336 let word2 = be32(bytes, 8);
337 let cipher = Cipher::from_bits((word2 >> 24) as u8);
338 let auth = KmAuth::from_bits((word2 >> 16) as u8);
339 let se = StreamEncapsulation::from_bits((word2 >> 8) as u8);
340 let resv2 = (word2 & 0xFF) as u8;
341 if resv2 != RESV2_FIXED {
342 return Err(Error::InvalidKeyMaterial {
343 field: "Resv2",
344 reason: "must be 0",
345 });
346 }
347
348 let word3 = be32(bytes, 12);
349 let resv3 = (word3 >> 16) as u16;
350 let slen4 = ((word3 >> 8) & 0xFF) as usize;
351 let klen4 = (word3 & 0xFF) as usize;
352 if resv3 != RESV3_FIXED {
353 return Err(Error::InvalidKeyMaterial {
354 field: "Resv3",
355 reason: "must be 0",
356 });
357 }
358
359 let slen = slen4 * 4;
360 let klen = klen4 * 4;
361 if !matches!(klen, 16 | 24 | 32) {
362 return Err(Error::InvalidKeyMaterial {
363 field: "KLen",
364 reason: "must be 16, 24, or 32 bytes (AES-128/192/256)",
365 });
366 }
367
368 let mut offset = 16usize;
369 let salt = bytes
370 .get(offset..offset + slen)
371 .ok_or(Error::BufferTooShort {
372 need: offset + slen,
373 have: bytes.len(),
374 what: "key material salt",
375 })?;
376 offset += slen;
377
378 let n = usize::from(kk.key_count());
379 let wrap_len = 8 + n * klen;
380 let wrap = bytes
381 .get(offset..offset + wrap_len)
382 .ok_or(Error::BufferTooShort {
383 need: offset + wrap_len,
384 have: bytes.len(),
385 what: "key material wrap field",
386 })?;
387 offset += wrap_len;
388
389 if offset != bytes.len() {
390 return Err(Error::UnexpectedTrailingBytes {
391 what: "key material message",
392 extra: bytes.len() - offset,
393 });
394 }
395
396 let mut icv = [0u8; 8];
397 icv.copy_from_slice(&wrap[0..8]);
398 let (x_sek, o_sek) = if n >= 1 {
399 let x = &wrap[8..8 + klen];
400 let o = if n == 2 {
401 Some(&wrap[8 + klen..8 + 2 * klen])
402 } else {
403 None
404 };
405 (x, o)
406 } else {
407 (&wrap[8..8], None)
408 };
409
410 Ok(KeyMaterial {
411 kk,
412 keki,
413 cipher,
414 auth,
415 se,
416 salt,
417 icv,
418 x_sek,
419 o_sek,
420 })
421 }
422
423 pub fn serialized_len(&self) -> usize {
425 16 + self.salt.len() + 8 + self.x_sek.len() + self.o_sek.map_or(0, <[u8]>::len)
426 }
427
428 pub fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
430 let len = self.serialized_len();
431 if buf.len() < len {
432 return Err(Error::OutputBufferTooSmall {
433 need: len,
434 have: buf.len(),
435 });
436 }
437 if !self.salt.len().is_multiple_of(4) {
438 return Err(Error::InvalidKeyMaterial {
439 field: "Salt",
440 reason: "length must be a whole number of 4-byte words",
441 });
442 }
443 if !matches!(self.x_sek.len(), 16 | 24 | 32) {
444 return Err(Error::InvalidKeyMaterial {
445 field: "xSEK",
446 reason: "length must be 16, 24, or 32 bytes",
447 });
448 }
449 let expects_both = self.kk == KmKeyFlag::Both;
450 match (&self.o_sek, expects_both) {
451 (Some(o), true) if o.len() == self.x_sek.len() => {}
452 (None, false) => {}
453 _ => {
454 return Err(Error::InvalidKeyMaterial {
455 field: "KK/oSEK",
456 reason: "oSEK must be present with the same length as xSEK iff KK is Both",
457 });
458 }
459 }
460
461 let word0 = (u32::from(S_FIXED) << 31)
462 | (u32::from(V_FIXED) << 28)
463 | (u32::from(PT_KEYING_MATERIAL) << 24)
464 | (u32::from(SIGN_FIXED) << 8)
465 | (u32::from(RESV1_FIXED) << 2)
466 | u32::from(self.kk.to_bits());
467 put_be32(buf, 0, word0);
468 put_be32(buf, 4, self.keki);
469 let word2 = (u32::from(self.cipher.to_bits()) << 24)
470 | (u32::from(self.auth.to_bits()) << 16)
471 | (u32::from(self.se.to_bits()) << 8)
472 | u32::from(RESV2_FIXED);
473 put_be32(buf, 8, word2);
474 let slen4 = (self.salt.len() / 4) as u32;
475 let klen4 = (self.x_sek.len() / 4) as u32;
476 let word3 = (u32::from(RESV3_FIXED) << 16) | (slen4 << 8) | klen4;
477 put_be32(buf, 12, word3);
478
479 let mut off = 16;
480 buf[off..off + self.salt.len()].copy_from_slice(self.salt);
481 off += self.salt.len();
482 buf[off..off + 8].copy_from_slice(&self.icv);
483 off += 8;
484 buf[off..off + self.x_sek.len()].copy_from_slice(self.x_sek);
485 off += self.x_sek.len();
486 if let Some(o) = self.o_sek {
487 buf[off..off + o.len()].copy_from_slice(o);
488 off += o.len();
489 }
490 debug_assert_eq!(off, len);
491 Ok(len)
492 }
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498
499 fn sample_even_only() -> KeyMaterial<'static> {
500 KeyMaterial {
501 kk: KmKeyFlag::Even,
502 keki: 0,
503 cipher: Cipher::AesCtr,
504 auth: KmAuth::None,
505 se: StreamEncapsulation::MpegTsSrt,
506 salt: &[0xAA; 16],
507 icv: [1, 2, 3, 4, 5, 6, 7, 8],
508 x_sek: &[0xEE; 16],
509 o_sek: None,
510 }
511 }
512
513 #[test]
514 fn round_trips_hand_computed_bytes() {
515 let km = sample_even_only();
516 let mut buf = alloc::vec![0u8; km.serialized_len()];
517 let n = km.serialize_into(&mut buf).unwrap();
518 assert_eq!(n, 16 + 16 + 8 + 16);
519
520 let expected_word0 = (1u32 << 28) | (2u32 << 24) | (0x2029u32 << 8) | 0b01u32;
522 assert_eq!(&buf[0..4], &expected_word0.to_be_bytes());
523 assert_eq!(&buf[12..16], &[0, 0, 4, 4]);
525 assert_eq!(&buf[16..32], &[0xAAu8; 16][..]);
526 assert_eq!(&buf[32..40], &[1, 2, 3, 4, 5, 6, 7, 8]);
527 assert_eq!(&buf[40..56], &[0xEEu8; 16][..]);
528
529 assert_eq!(KeyMaterial::parse(&buf).unwrap(), km);
530 }
531
532 #[test]
533 fn both_keys_round_trip() {
534 let km = KeyMaterial {
535 kk: KmKeyFlag::Both,
536 keki: 7,
537 cipher: Cipher::AesCtr,
538 auth: KmAuth::None,
539 se: StreamEncapsulation::MpegTsSrt,
540 salt: &[0; 16],
541 icv: [0; 8],
542 x_sek: &[1; 24],
543 o_sek: Some(&[2; 24]),
544 };
545 let mut buf = alloc::vec![0u8; km.serialized_len()];
546 km.serialize_into(&mut buf).unwrap();
547 assert_eq!(KeyMaterial::parse(&buf).unwrap(), km);
548 }
549
550 #[test]
551 fn no_salt_round_trips() {
552 let km = KeyMaterial {
553 kk: KmKeyFlag::Odd,
554 keki: 0,
555 cipher: Cipher::None,
556 auth: KmAuth::None,
557 se: StreamEncapsulation::Unspecified,
558 salt: &[],
559 icv: [9; 8],
560 x_sek: &[3; 32],
561 o_sek: None,
562 };
563 let mut buf = alloc::vec![0u8; km.serialized_len()];
564 km.serialize_into(&mut buf).unwrap();
565 assert_eq!(KeyMaterial::parse(&buf).unwrap(), km);
566 }
567
568 #[test]
569 fn bad_signature_errs_without_panic() {
570 let km = sample_even_only();
571 let mut buf = alloc::vec![0u8; km.serialized_len()];
572 km.serialize_into(&mut buf).unwrap();
573 buf[1] = 0x00; assert!(matches!(
575 KeyMaterial::parse(&buf),
576 Err(Error::InvalidKeyMaterial { field: "Sign", .. })
577 ));
578 }
579
580 #[test]
581 fn inconsistent_kk_o_sek_errs() {
582 let mut km = sample_even_only();
583 km.o_sek = Some(&[0xEE; 16]); let mut buf = alloc::vec![0u8; 16 + 16 + 8 + 16 + 16];
585 assert!(matches!(
586 km.serialize_into(&mut buf),
587 Err(Error::InvalidKeyMaterial { .. })
588 ));
589 }
590}