galileo_osnma/bitfields.rs
1//! Message bit fields.
2//!
3//! This module contains structures that give acccess to each of the fields in
4//! the messages used by OSNMA. As a general rule, the structures are a wrapper
5//! over a `&[u8]` or `&[u8; N]`.
6
7pub use crate::tesla::NmaHeader;
8use crate::tesla::{AdkdCheckError, Key, MacseqCheckError};
9use crate::types::{
10 BitSlice, MACK_MESSAGE_BYTES, MERKLE_TREE_NODE_BYTES, MackMessage, MerkleTreeNode, Towh,
11};
12use crate::validation::{NotValidated, Validated};
13use crate::{Gst, Svn, Wn};
14use bitvec::prelude::*;
15use core::fmt;
16use ecdsa::{PrimeCurve, Signature, SignatureSize};
17use sha2::{Digest, Sha256};
18use signature::Verifier;
19
20/// Status of the NMA chain.
21///
22/// This represents the values of the NMAS field of the [`NmaHeader`]
23/// as defined in Section 3.1.1 of the
24/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
25#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
26pub enum NmaStatus {
27 /// Reserved value (NMAS = 0),
28 Reserved,
29 /// Test (NMAS = 1),
30 Test,
31 /// Operational (NMAS = 2).
32 Operational,
33 /// Don't use (NMAS = 3).
34 DontUse,
35}
36
37/// Chain and Public Key status.
38///
39/// This represents the valus of the CPKS field of the [`NmaHeader`]
40/// as defined in Section 3.1.3 of the
41/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
42#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
43pub enum ChainAndPubkeyStatus {
44 /// Reserved value (CPKS = 0).
45 Reserved,
46 /// Nominal (CPKS = 1).
47 Nominal,
48 /// End of chain (EOC) (CPKS = 2).
49 EndOfChain,
50 /// Chain revoked (CREV) (CPKS = 3).
51 ChainRevoked,
52 /// New public key (NPK) (CPKS = 4).
53 NewPublicKey,
54 /// Public key revoked (PKREV) (CPKS = 5).
55 PublicKeyRevoked,
56 /// New Merkle tree (NMT) (CPKS = 6).
57 NewMerkleTree,
58 /// Alert Message (AM) (CPKS = 7)
59 AlertMessage,
60}
61
62/// DSM header.
63///
64/// The DSM header found in the second byte of an HKROOT message.
65/// See Figure 5 in the
66/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
67#[derive(Copy, Clone, Eq, PartialEq, Hash)]
68pub struct DsmHeader<'a>(
69 /// Reference to an array containing the 1-byte header data.
70 pub &'a [u8; 1],
71);
72
73/// Type of the DSM message.
74///
75/// This is derived from the DSM ID field according to Section 3.2.1.1 in the
76/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
77#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
78pub enum DsmType {
79 /// DSM-KROOT.
80 ///
81 /// This message is used to transmit the TESLA root key. It corresponds to
82 /// DSM IDs 0 to 11.
83 Kroot,
84 /// DSM-PKR.
85 ///
86 /// This message is used to transmit a new ECDSA public key. It corresponds
87 /// to DSM IDs 12 to 15.
88 Pkr,
89}
90
91impl DsmHeader<'_> {
92 fn bits(&self) -> &BitSlice {
93 BitSlice::from_slice(self.0)
94 }
95
96 /// Gives the value of the DSM ID field.
97 pub fn dsm_id(&self) -> u8 {
98 self.bits()[..4].load_be()
99 }
100
101 /// Gives the value of the DSM block ID field.
102 pub fn dsm_block_id(&self) -> u8 {
103 self.bits()[4..8].load_be()
104 }
105
106 /// Gives the type of DSM message, according to the DSM ID field.
107 pub fn dsm_type(&self) -> DsmType {
108 if self.dsm_id() >= 12 {
109 DsmType::Pkr
110 } else {
111 DsmType::Kroot
112 }
113 }
114}
115
116impl fmt::Debug for DsmHeader<'_> {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 f.debug_struct("DsmHeader")
119 .field("dsm_id", &self.dsm_id())
120 .field("dsm_block_id", &self.dsm_block_id())
121 .finish()
122 }
123}
124
125/// DSM-PKR message.
126///
127/// The DSM-PKR message, as defined in Figure 6 of the
128/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
129#[derive(Copy, Clone, Eq, PartialEq, Hash)]
130pub struct DsmPkr<'a>(
131 /// Reference to a slice containing the DSM-PKR message data.
132 ///
133 /// # Panics
134 ///
135 /// This slice should be long enough to contain the full DSM-PKR
136 /// message. Otherwise the methods of `DsmPkr` may panic.
137 pub &'a [u8],
138);
139
140/// New Public Key Type (NPKT).
141///
142/// This represents the values of the New Public Key Type (NPKT) field in the
143/// DSM-PKR message. See Table 5 in the
144/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
145#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
146pub enum NewPublicKeyType {
147 /// An ECDSA key, as defined by the enum [`EcdsaFunction`].
148 EcdsaKey(EcdsaFunction),
149 /// OSNMA Alert Message (OAM).
150 OsnmaAlertMessage,
151 /// Reserved value.
152 Reserved,
153}
154
155impl DsmPkr<'_> {
156 fn bits(&self) -> &BitSlice {
157 BitSlice::from_slice(self.0)
158 }
159
160 /// Gives the number of DSM-PKR blocks.
161 ///
162 /// The number is computed according to the value of the NB_DP field and
163 /// Table 3 in the
164 /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
165 ///
166 /// If the NB_DP field contains a reserved value, `None` is returned.
167 pub fn number_of_blocks(&self) -> Option<usize> {
168 let v = self.bits()[..4].load_be::<u8>();
169 match v {
170 7..=10 => Some(usize::from(v) + 6),
171 _ => None, // reserved value
172 }
173 }
174
175 /// Gives the value of the Message ID (MID) field.
176 pub fn message_id(&self) -> u8 {
177 self.bits()[4..8].load_be::<u8>()
178 }
179
180 /// Gives the value of an interemediate tree node.
181 ///
182 /// The DSM-PKR contains 4 256-bit intermediate tree nodes. This returns the
183 /// 256-bit slice corresponding to the intermediate tree node in position
184 /// `node_number` (where `node_number` can be 0, 1, 2, or 3).
185 ///
186 /// # Panics
187 ///
188 /// This function panics if `node` number is not 0, 1, 2, or 3.
189 ///
190 pub fn intermediate_tree_node(&self, node_number: usize) -> &MerkleTreeNode {
191 assert!(node_number < 4);
192 (&self.0[1 + node_number * MERKLE_TREE_NODE_BYTES
193 ..1 + (node_number + 1) * MERKLE_TREE_NODE_BYTES])
194 .try_into()
195 .unwrap()
196 }
197
198 /// Gives the value of the New Public Key Type (NPKT) field.
199 pub fn new_public_key_type(&self) -> NewPublicKeyType {
200 match self.bits()[1032..1036].load_be::<u8>() {
201 1 => NewPublicKeyType::EcdsaKey(EcdsaFunction::P256Sha256),
202 3 => NewPublicKeyType::EcdsaKey(EcdsaFunction::P521Sha512),
203 4 => NewPublicKeyType::OsnmaAlertMessage,
204 _ => NewPublicKeyType::Reserved,
205 }
206 }
207
208 /// Gives the value of the New Public Key ID (NPKID) field.
209 pub fn new_public_key_id(&self) -> u8 {
210 self.bits()[1036..1040].load_be::<u8>()
211 }
212
213 /// Gives the size of the New Public Key field in bytes.
214 ///
215 /// The size is computed according to the value of the NPKT field and Table 6 in the
216 /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
217 /// If the NPKT field contains a reserved value, `None` is returned.
218 pub fn key_size(&self) -> Option<usize> {
219 match self.new_public_key_type() {
220 NewPublicKeyType::EcdsaKey(EcdsaFunction::P256Sha256) => Some(264 / 8),
221 NewPublicKeyType::EcdsaKey(EcdsaFunction::P521Sha512) => Some(536 / 8),
222 NewPublicKeyType::OsnmaAlertMessage => {
223 self.number_of_blocks().map(|n| n * (104 / 8) - 1040 / 8)
224 }
225 NewPublicKeyType::Reserved => None,
226 }
227 }
228
229 /// Gives a slice containing the New Public Key field.
230 ///
231 /// If the size of the New Public Key field cannot be determined because
232 /// some other fields contain reserved values, `None` is returned.
233 pub fn new_public_key(&self) -> Option<&[u8]> {
234 self.key_size().map(|s| &self.0[1040 / 8..1040 / 8 + s])
235 }
236
237 /// Gives a slice containing the padding field.
238 ///
239 /// If the size of the New Public Key field cannot be determined because
240 /// some other fields contain reserved values, `None` is returned.
241 pub fn padding(&self) -> Option<&[u8]> {
242 if let (Some(ks), Some(nb)) = (self.key_size(), self.number_of_blocks()) {
243 Some(&self.0[1040 / 8 + ks..nb * 104 / 8])
244 } else {
245 None
246 }
247 }
248
249 /// Gives the Merkle tree leaf corresponding to this message.
250 ///
251 /// The tree leaf is defined in Section 6.2 of the
252 /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
253 ///
254 /// If the size of the New Public Key field cannot be determined because
255 /// some other fields contain reserved values, `None` is returned.
256 pub fn merkle_tree_leaf(&self) -> Option<&[u8]> {
257 self.key_size().map(|s| &self.0[1032 / 8..1040 / 8 + s])
258 }
259
260 /// Checks the contents of the padding field.
261 /// The contents are checked according to Eq. 4 in the
262 /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
263 ///
264 /// If the contents are correct, this returns `true`. Otherwise, this
265 /// returns `false`. If `self.padding()` returns `None`, then this function
266 /// returns `false`.
267 pub fn check_padding(&self, merkle_tree_root: &MerkleTreeNode) -> bool {
268 let Some(padding) = self.padding() else {
269 return false;
270 };
271 if padding.is_empty() {
272 // This happens for OSNMA Alert Messages: The padding is empty and
273 // does not need to be checked.
274 return true;
275 }
276 let mut hash = Sha256::new();
277 hash.update(merkle_tree_root);
278 // merkle_tree_leaf should not panic, because self.padding() is not None
279 hash.update(self.merkle_tree_leaf().unwrap());
280 let hash = hash.finalize();
281 let truncated = &hash[..padding.len()];
282 truncated == padding
283 }
284}
285
286impl fmt::Debug for DsmPkr<'_> {
287 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288 f.debug_struct("DsmPkr")
289 .field("number_of_blocks", &self.number_of_blocks())
290 .field("message_id", &self.message_id())
291 .field("intermediate_tree_node_0", &self.intermediate_tree_node(0))
292 .field("intermediate_tree_node_1", &self.intermediate_tree_node(1))
293 .field("intermediate_tree_node_2", &self.intermediate_tree_node(2))
294 .field("intermediate_tree_node_3", &self.intermediate_tree_node(3))
295 .field("new_public_key_type", &self.new_public_key_type())
296 .field("new_public_key_id", &self.new_public_key_id())
297 .field("new_public_key", &self.new_public_key())
298 .field("padding", &self.padding())
299 .finish()
300 }
301}
302
303/// DSM-KROOT message.
304///
305/// The DSM-KROOT message, as defined in Figure 7 of the
306/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
307#[derive(Copy, Clone, Eq, PartialEq, Hash)]
308pub struct DsmKroot<'a>(
309 /// Reference to a slice containing the DSM-KROOT message data.
310 ///
311 /// # Panics
312 ///
313 /// This slice should be long enough to contain the full DSM-KROOT
314 /// message. Otherwise the methods of `DsmKroot` may panic.
315 pub &'a [u8],
316);
317
318/// Hash function.
319///
320/// This represents the values of the Hash Function (HF) field of the DSM-KROOT
321/// message. See Table 8 in the
322/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
323#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
324pub enum HashFunction {
325 /// SHA-256 (HF = 0).
326 Sha256,
327 /// SHA3-256 (HF = 2).
328 Sha3_256,
329 /// Reserved value (HF = 1, 3).
330 Reserved,
331}
332
333/// MAC function.
334///
335/// This represents the values of the MAC Function (MF) field of the DSM-KROOT
336/// message. See Table 9 in the
337/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
338#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
339pub enum MacFunction {
340 /// HMAC-SHA-256 (MF = 0).
341 HmacSha256,
342 /// CMAC-AES (MF = 1).
343 CmacAes,
344 /// Reserved value (MF = 2, 3).
345 Reserved,
346}
347
348/// ECDSA function.
349///
350/// This represents the key types available for ECDSA signatures. See Table 15
351/// in the
352/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
353#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
354pub enum EcdsaFunction {
355 /// ECDSA P-256/SHA-256.
356 P256Sha256,
357 /// ECDSA P-521/SHA-512
358 P521Sha512,
359}
360
361impl DsmKroot<'_> {
362 fn bits(&self) -> &BitSlice {
363 BitSlice::from_slice(self.0)
364 }
365
366 /// Gives the number of DSM-KROOT blocks.
367 ///
368 /// The number is computed according to the value of the NB_DK field and
369 /// Table 7 in the
370 /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
371 ///
372 /// If the NB_DK field contains a reserved value, `None` is returned.
373 pub fn number_of_blocks(&self) -> Option<usize> {
374 let v = self.bits()[..4].load_be::<u8>();
375 match v {
376 1..=8 => Some(usize::from(v) + 6),
377 _ => None, // reserved value
378 }
379 }
380
381 /// Gives the value of the PKID (public key ID) field.
382 pub fn public_key_id(&self) -> u8 {
383 self.bits()[4..8].load_be::<u8>()
384 }
385
386 /// Gives the value of the CIDKR (KROOT chain ID) field.
387 pub fn kroot_chain_id(&self) -> u8 {
388 self.bits()[8..10].load_be::<u8>()
389 }
390
391 /// Gives the value of the hash function field.
392 pub fn hash_function(&self) -> HashFunction {
393 match self.bits()[12..14].load_be::<u8>() {
394 0 => HashFunction::Sha256,
395 2 => HashFunction::Sha3_256,
396 _ => HashFunction::Reserved,
397 }
398 }
399
400 /// Gives the value of the MAC function field.
401 pub fn mac_function(&self) -> MacFunction {
402 match self.bits()[14..16].load_be::<u8>() {
403 0 => MacFunction::HmacSha256,
404 1 => MacFunction::CmacAes,
405 _ => MacFunction::Reserved,
406 }
407 }
408
409 /// Gives the TESLA key size in bits.
410 ///
411 /// The size is computed according to the value of the KS field and
412 /// Table 10 in the
413 /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
414 ///
415 /// If the KS field contains a reserved value, `None` is returned.
416 pub fn key_size(&self) -> Option<usize> {
417 // note that all the key sizes are a multiple of 8 bits
418 let size = match self.bits()[16..20].load_be::<u8>() {
419 0 => Some(96),
420 1 => Some(104),
421 2 => Some(112),
422 3 => Some(120),
423 4 => Some(128),
424 5 => Some(160),
425 6 => Some(192),
426 7 => Some(224),
427 8 => Some(256),
428 _ => None,
429 };
430 if let Some(s) = size {
431 debug_assert!(s % 8 == 0);
432 }
433 size
434 }
435
436 /// Gives the MAC tag size in bits.
437 ///
438 /// The size is computed according to the value of the TS field and
439 /// Table 11 in the
440 /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
441 ///
442 /// If the TS field contains a reserved value, `None` is returned.
443 pub fn tag_size(&self) -> Option<usize> {
444 match self.bits()[20..24].load_be::<u8>() {
445 5 => Some(20),
446 6 => Some(24),
447 7 => Some(28),
448 8 => Some(32),
449 9 => Some(40),
450 _ => None,
451 }
452 }
453
454 /// Gives the value of the MACLT (MAC look-up table) field.
455 pub fn mac_lookup_table(&self) -> u8 {
456 self.bits()[24..32].load_be()
457 }
458
459 /// Gives the KROOT week number.
460 ///
461 /// This is the value of the WNK field.
462 pub fn kroot_wn(&self) -> Wn {
463 self.bits()[36..48].load_be()
464 }
465
466 /// Gives the KROOT time of week in hours.
467 ///
468 /// This is the value of the TOWHK field.
469 pub fn kroot_towh(&self) -> Towh {
470 self.bits()[48..56].load_be()
471 }
472
473 /// Gives the value of the random pattern alpha.
474 ///
475 /// The random pattern alpha is a 48-bit value. Here it is given in a `u64`.
476 pub fn alpha(&self) -> u64 {
477 self.bits()[56..104].load_be()
478 }
479
480 /// Returns a slice reference to the KROOT in the DSM-KROOT message.
481 ///
482 /// This is the contents of the KROOT field. The length of the returned slice
483 /// depends on the TESLA key size.
484 ///
485 /// # Panics
486 ///
487 /// Panics if the key size field in the DSM-KROOT message contains a reserved
488 /// value.
489 pub fn kroot(&self) -> &[u8] {
490 let size = self
491 .key_size()
492 .expect("attempted to extract kroot of DSM with reserved key size");
493 let size_bytes = size / 8;
494 &self.0[13..13 + size_bytes]
495 }
496
497 /// Returns the ECDSA function used by this DSM-KROOT message.
498 ///
499 /// The ECDSA function is guessed from the size of the ECDSA signature
500 /// in the message.
501 ///
502 /// # Panics
503 ///
504 /// Panics if the ECDSA function cannot be guessed because the size of
505 /// the signature is neither 512 bits (for P-256) nor 1056 bits (for P-521).
506 pub fn ecdsa_function(&self) -> EcdsaFunction {
507 // Although the ICD is not clear about this, we can guess the
508 // ECDSA function in use from the size of the DSM-KROOT
509 let total_len = self.0.len();
510 let fixed_len = 13;
511 let kroot_len = self.kroot().len();
512 let remaining_len = total_len - fixed_len - kroot_len;
513 let b = 13; // block size
514 let p256_bytes = 64; // 512 bits
515 let p521_bytes = 132; // 1056 bits
516 let p256_padding = (b - (kroot_len + p256_bytes) % b) % b;
517 let p521_padding = (b - (kroot_len + p521_bytes) % b) % b;
518 if remaining_len == p256_bytes + p256_padding {
519 EcdsaFunction::P256Sha256
520 } else if remaining_len == p521_bytes + p521_padding {
521 EcdsaFunction::P521Sha512
522 } else {
523 panic!(
524 "failed to guess ECDSA function with DSM-KROOT total len = {total_len}\
525 and kroot len = {kroot_len}"
526 );
527 }
528 }
529
530 /// Returns a slice reference to the ECDSA signature in the DSM-KROOT message.
531 ///
532 /// This is the contents of the digital signature (DS) field. The length of
533 /// the returned slice depend on the ECDSA function in use.
534 ///
535 /// # Panics
536 ///
537 /// Panics if the ECDSA function cannot be guessed because the size of
538 /// the signature is neither 512 bits (for P-256) nor 1056 bits (for P-521).
539 pub fn digital_signature(&self) -> &[u8] {
540 let size = match self.ecdsa_function() {
541 EcdsaFunction::P256Sha256 => 64,
542 EcdsaFunction::P521Sha512 => 132,
543 };
544 let start = 13 + self.kroot().len();
545 &self.0[start..start + size]
546 }
547
548 /// Gives the contents of the DSM-KROOT padding (P_DK) field.
549 pub fn padding(&self) -> &[u8] {
550 let start = 13 + self.kroot().len() + self.digital_signature().len();
551 &self.0[start..]
552 }
553
554 // message for digital signature verification
555 fn signature_message(&self, nma_header: NmaHeader<NotValidated>) -> ([u8; 209], usize) {
556 let mut m = [0; 209];
557 m[0] = nma_header.data();
558 let end = 13 + self.kroot().len();
559 // we skip the NB_DK and PKID fields in self.0
560 m[1..end].copy_from_slice(&self.0[1..end]);
561 (m, end)
562 }
563
564 /// Checks the contents of the padding field.
565 ///
566 /// The contents are checked according to Eq. 7 in the
567 /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
568 ///
569 /// If the contents are correct, this returns `true`. Otherwise, this
570 /// returns `false`.
571 pub fn check_padding(&self, nma_header: NmaHeader<NotValidated>) -> bool {
572 let (message, size) = self.signature_message(nma_header);
573 let message = &message[..size];
574 let mut hash = Sha256::new();
575 hash.update(message);
576 hash.update(self.digital_signature());
577 let hash = hash.finalize();
578 let padding = self.padding();
579 let truncated = &hash[..padding.len()];
580 truncated == padding
581 }
582
583 /// Checks the P256 ECDSA signature.
584 ///
585 /// This verifies that the P256 ECDSA signature of the DSM-KROOT message is
586 /// correct. The algorithm in Section 6.3 of the
587 /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
588 /// is followed.
589 ///
590 /// # Panics
591 ///
592 /// Panics if the DSM-KROOT message does not use a P256 ECDSA signature.
593 ///
594 pub fn check_signature_p256(
595 &self,
596 nma_header: NmaHeader<NotValidated>,
597 pubkey: &p256::ecdsa::VerifyingKey,
598 ) -> bool {
599 assert_eq!(self.ecdsa_function(), EcdsaFunction::P256Sha256);
600 self.check_signature(nma_header, pubkey)
601 }
602
603 /// Checks the P512 ECDSA signature.
604 ///
605 /// This verifies that the P512 ECDSA signature of the DSM-KROOT message is
606 /// correct. The algorithm in Section 6.3 of the
607 /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
608 /// is followed.
609 ///
610 /// # Panics
611 ///
612 /// Panics if the DSM-KROOT message does not use a P512 ECDSA signature.
613 ///
614 #[cfg(feature = "p521")]
615 pub fn check_signature_p521(
616 &self,
617 nma_header: NmaHeader<NotValidated>,
618 pubkey: &p521::ecdsa::VerifyingKey,
619 ) -> bool {
620 assert_eq!(self.ecdsa_function(), EcdsaFunction::P521Sha512);
621 self.check_signature(nma_header, pubkey)
622 }
623
624 // Generic function to check the ECDSA signature. This works for either:
625 //
626 // - VK = p256::ecdsa::VerifyingKey, C = p256::NistP256
627 // - VK = p512::ecdsa::VerifyingKey, C = p521::NistP521
628 //
629 // The function can also be called with other type parameters, but it doesn't
630 // make sense to do so.
631 //
632 // # Panics
633 //
634 // The function panics if the ECDSA signature cannot be serialized, which
635 // can happen if the chosen type parameters do not match the signature
636 // length in the DSM-KROOT message.
637 fn check_signature<VK, C>(&self, nma_header: NmaHeader<NotValidated>, pubkey: &VK) -> bool
638 where
639 VK: Verifier<Signature<C>>,
640 C: PrimeCurve,
641 SignatureSize<C>: crypto_common::generic_array::ArrayLength<u8>,
642 {
643 let (message, size) = self.signature_message(nma_header);
644 let message = &message[..size];
645 let signature = Signature::from_bytes(self.digital_signature().into())
646 .expect("error serializing ECDSA signature");
647 pubkey.verify(message, &signature).is_ok()
648 }
649}
650
651impl fmt::Debug for DsmKroot<'_> {
652 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
653 f.debug_struct("DsmKroot")
654 .field("number_of_blocks", &self.number_of_blocks())
655 .field("public_key_id", &self.public_key_id())
656 .field("kroot_chain_id", &self.kroot_chain_id())
657 .field("hash_function", &self.hash_function())
658 .field("mac_function", &self.mac_function())
659 .field("key_size", &self.key_size())
660 .field("tag_size", &self.tag_size())
661 .field("mac_loopkup_table", &self.mac_lookup_table())
662 .field("kroot_wn", &self.kroot_wn())
663 .field("kroot_towh", &self.kroot_towh())
664 .field("alpha", &self.alpha())
665 .field("kroot", &self.kroot())
666 .field("digital_signature", &self.digital_signature())
667 .field("padding", &self.padding())
668 .finish()
669 }
670}
671
672/// MACK message.
673///
674/// The MACK message, as defined in Figure 8 of the
675/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
676///
677/// This is one of the few structs in [bitfields](crate::bitfields) that is not
678/// a simple wrapper around a slice. The reason is that to interpret the MACK
679/// message, it is necessary to know the key and tag sizes, so `Mack` holds
680/// these values as well.
681///
682/// The `V` type parameter is used to indicate the validation status of the MACK
683/// message. Validation of a MACK message corresponds to checking its MACSEQ
684/// field and that its ADKDs match the corresponding look-up table. See
685/// [validation](crate::validation) for a description of validation type
686/// parameters.
687#[derive(Copy, Clone, Eq, PartialEq, Hash)]
688pub struct Mack<'a, V> {
689 data: &'a BitSlice,
690 key_size: usize,
691 tag_size: usize,
692 _validated: V,
693}
694
695impl Mack<'_, NotValidated> {
696 /// Constructs a new MACK message.
697 ///
698 /// The `data` should be a reference to an array containing the 60 bytes of
699 /// the MACK message. The `key_size` in bits and `tag_size` in bits should
700 /// be taken from the parameters of the current TESLA chain. The MACK
701 /// message is marked as [`NotValidated`].
702 pub fn new(data: &MackMessage, key_size: usize, tag_size: usize) -> Mack<'_, NotValidated> {
703 Mack {
704 data: BitSlice::from_slice(data),
705 key_size,
706 tag_size,
707 _validated: NotValidated {},
708 }
709 }
710}
711
712impl<V> Mack<'_, V> {
713 /// Gives the key size in bits corresponding to the MACK message.
714 ///
715 /// This returns the value that has been given in [`Mack::new`].
716 pub fn key_size(&self) -> usize {
717 self.key_size
718 }
719
720 /// Gives the key size in bits corresponding to the MACK message.
721 ///
722 /// This returns the value that has been given in [`Mack::new`].
723 pub fn tag_size(&self) -> usize {
724 self.tag_size
725 }
726
727 /// Gives the tag0 field contained in the MACK header of the MACK message.
728 ///
729 /// See Figure 9 in the
730 /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
731 pub fn tag0(&self) -> &BitSlice {
732 &self.data[..self.tag_size()]
733 }
734
735 /// Gives the value of the MACSEQ field contained in the MACK header of the MACK message.
736 ///
737 /// See Figure 9 in the
738 /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
739 /// The MACSEQ is a 12-bit integer, which is returned as a `u16`.
740 pub fn macseq(&self) -> u16 {
741 let macseq_size = 12;
742 self.data[self.tag_size()..self.tag_size() + macseq_size].load_be::<u16>()
743 }
744
745 /// Gives the value of the COP field contained in the MACK header of the MACK message.
746 ///
747 /// See Figure 9 in the
748 /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
749 /// The COP is a 4-bit integer, which is returned as a `u8`.
750 pub fn cop(&self) -> u8 {
751 let macseq_size = 12;
752 let cop_offset = self.tag_size() + macseq_size;
753 let cop_size = 4;
754 self.data[cop_offset..cop_offset + cop_size].load_be::<u8>()
755 }
756
757 /// Returns the number of tags in the MACK message.
758 ///
759 /// The number of tags is computed according to the tag size.
760 pub fn num_tags(&self) -> usize {
761 (8 * MACK_MESSAGE_BYTES - self.key_size()) / (self.tag_size() + 16)
762 }
763
764 /// Gives the Key field of the MACK message.
765 ///
766 /// This fields contains a TESLA key. See Figure 8 in the
767 /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
768 pub fn key(&self) -> &BitSlice {
769 let start = (self.tag_size() + 16) * self.num_tags();
770 &self.data[start..start + self.key_size()]
771 }
772}
773
774/// MACK validation error
775///
776/// This enum lists the possible errors that can happen when a MACK message
777/// validation using [`Mack::validate`] is attempted.
778#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
779pub enum MackValidationError {
780 /// The MACSEQ field could not be verified.
781 ///
782 /// The MACSEQ field is checked using the algorithm in Section 6.6 of the
783 /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
784 MacseqError(MacseqCheckError),
785 /// One of the ADKD fields is not correct.
786 WrongAdkd {
787 /// The index of the first tag whose ADKD is not correct.
788 tag_index: usize,
789 /// The reason why the ADKD field is not correct.
790 error: AdkdCheckError,
791 },
792}
793
794impl fmt::Display for MackValidationError {
795 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
796 match self {
797 MackValidationError::MacseqError(err) => err.fmt(f),
798 MackValidationError::WrongAdkd { tag_index, error } => {
799 write!(f, "incorrect ADKD field at tag {tag_index} ({error})")
800 }
801 }
802 }
803}
804
805impl From<MacseqCheckError> for MackValidationError {
806 fn from(value: MacseqCheckError) -> MackValidationError {
807 MackValidationError::MacseqError(value)
808 }
809}
810
811#[cfg(feature = "std")]
812impl std::error::Error for MackValidationError {
813 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
814 match self {
815 MackValidationError::MacseqError(err) => Some(err),
816 MackValidationError::WrongAdkd { error, .. } => Some(error),
817 }
818 }
819}
820
821impl<V: Clone> Mack<'_, V> {
822 /// Gives an object representing one of the Tag-Info sections in the MACK message.
823 ///
824 /// The Tag-Info section is defined in Figure 11 of the
825 /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
826 /// The parameter `n` corresponds to the index of the Tag-Info in the MACK
827 /// message. The first Tag-Info has `n = 1`, since `n = 0` would correspond
828 /// to the Tag0 field, which does not have an associated info field and is
829 /// obtained with [`Mack::tag0`].
830 ///
831 /// The validation status of the Tag-Info is inherited from the validation
832 /// status of the MACK message. There is no way to validate Tag-Info
833 /// sections once they have been separated from the MACK message. If a
834 /// validated Tag-Info is needed, the whole MACK message should be validated
835 /// first using [`Mack::validate`] before calling [`Mack::tag_and_info`].
836 ///
837 /// # Panics
838 ///
839 /// Panics if `n` is not between 1 and `self.num_tags() - 1`.
840 pub fn tag_and_info(&self, n: usize) -> TagAndInfo<'_, V> {
841 assert!(0 < n && n < self.num_tags());
842 let size = self.tag_size() + 16;
843 TagAndInfo {
844 data: &self.data[size * n..size * (n + 1)],
845 _validated: self._validated.clone(),
846 }
847 }
848}
849
850impl<'a, V: Clone> Mack<'a, V> {
851 /// Try to validate the MACK message.
852 ///
853 /// Given the TESLA `key` transmitted on the next subframe, this will
854 /// attempt to validate the MACSEQ field and the ADKD fields of the MACK
855 /// message. The MACSEQ field is checked using the algorithm in Section 6.6
856 /// of the
857 /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
858 /// The sequence of ADKD fields is checked against the MAC look-up table
859 /// using the chain parameters held by the TESLA key.
860 ///
861 /// The parameter `prna` should be the SVN of the satellite that transmitted
862 /// this MACK message, and `gst_mack` corresponds to the GST at the start of
863 /// the subframe in which the MACK message was transmitted. The `maclt`
864 /// parameter indicates the active MAC Look-up Table id. It is used to
865 /// determine which tags are flexible.
866 ///
867 /// If the validation is successful, this returns a copy of `self` with the
868 /// validation type parameter `V` set to `Validated`. Otherwise, an error
869 /// indicating which check was not satisfied is returned.
870 pub fn validate(
871 &self,
872 key: &Key<Validated>,
873 prna: Svn,
874 gst_mack: Gst,
875 ) -> Result<Mack<'a, Validated>, MackValidationError> {
876 key.validate_macseq(self, prna, gst_mack)?;
877
878 for j in 1..self.num_tags() {
879 let tag = self.tag_and_info(j);
880 if let Err(e) = key.chain().validate_adkd(j, tag, prna, gst_mack) {
881 return Err(MackValidationError::WrongAdkd {
882 tag_index: j,
883 error: e,
884 });
885 }
886 }
887 Ok(Mack {
888 data: self.data,
889 key_size: self.key_size,
890 tag_size: self.tag_size,
891 _validated: Validated {},
892 })
893 }
894}
895
896impl<V: fmt::Debug + Clone> fmt::Debug for Mack<'_, V> {
897 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
898 let mut dbg = f.debug_struct("Mack");
899 dbg.field("tag0", &self.tag0())
900 .field("macseq", &self.macseq());
901 for tag in 1..self.num_tags() {
902 dbg.field("tag", &self.tag_and_info(tag));
903 }
904 dbg.field("key", &self.key())
905 .field("_validated", &self._validated)
906 .finish()
907 }
908}
909
910/// Tag-Info section.
911///
912/// The Tag-Info section is defined in Figure 11 of the
913/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
914/// A Tag-Info field is obtained from a MACK message with [`Mack::tag_and_info`].
915#[derive(Copy, Clone, Eq, PartialEq, Hash)]
916pub struct TagAndInfo<'a, V> {
917 data: &'a BitSlice,
918 _validated: V,
919}
920
921/// PRND (PRN of the satellite transmitting the authenticated data).
922///
923/// This represents the values of the PRND field in a Tag-Info section, as
924/// described in Table 12 in the
925/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
926#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
927pub enum Prnd {
928 /// Galileo SVID (PRND = 1 - 36).
929 GalileoSvid(
930 /// The Galileo SVID value (between 1 and 36).
931 u8,
932 ),
933 /// Galileo constellation-related information (PRND = 255).
934 GalileoConstellation,
935 /// Reserved value (any other value of the PRND field).
936 Reserved,
937}
938
939impl TryFrom<Prnd> for u8 {
940 type Error = ();
941 fn try_from(value: Prnd) -> Result<u8, ()> {
942 match value {
943 Prnd::GalileoSvid(svid) => Ok(svid),
944 Prnd::GalileoConstellation => Ok(255),
945 Prnd::Reserved => Err(()),
946 }
947 }
948}
949
950/// ADKD (Authentication Data and Key Delay).
951///
952/// Represents the values of the ADKD (Authentication Data and Key Delay) field,
953/// as defined in Table 14 in the
954/// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf).
955#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
956pub enum Adkd {
957 /// Galileo I/NAV ephemeris, clock and status (ADKD = 0).
958 InavCed,
959 /// Galileo I/NAV timing parameters (ADKD = 4).
960 InavTiming,
961 /// Slow MAC. Galileo I/NAV ephemeris, clock and status (ADKD = 12).
962 SlowMac,
963 /// Reserved value (any other ADKD value).
964 Reserved,
965}
966
967impl<V> TagAndInfo<'_, V> {
968 /// Gives the tag field.
969 pub fn tag(&self) -> &BitSlice {
970 &self.data[..self.data.len() - 16]
971 }
972
973 /// Returns the tag-info section as a [`BitSlice`].
974 ///
975 /// The methods below return individual fields of the tag-info section.
976 pub fn tag_info(&self) -> &BitSlice {
977 &self.data[self.data.len() - 16..]
978 }
979
980 /// Gives the value of the PRND field in the Tag-Info section.
981 pub fn prnd(&self) -> Prnd {
982 let len = self.data.len();
983 match self.data[len - 16..len - 8].load_be::<u8>() {
984 n @ 1..=36 => Prnd::GalileoSvid(n),
985 255 => Prnd::GalileoConstellation,
986 _ => Prnd::Reserved,
987 }
988 }
989
990 /// Gives the value of the ADKD field in the Tag-Info section.
991 pub fn adkd(&self) -> Adkd {
992 let len = self.data.len();
993 match self.data[len - 8..len - 4].load_be::<u8>() {
994 0 => Adkd::InavCed,
995 4 => Adkd::InavTiming,
996 12 => Adkd::SlowMac,
997 _ => Adkd::Reserved,
998 }
999 }
1000
1001 /// Gives the value of the COP field in the Tag-Info section.
1002 ///
1003 /// The COP is a 4-bit integer, which is returned as a `u8`.
1004 pub fn cop(&self) -> u8 {
1005 let len = self.data.len();
1006 self.data[len - 4..].load_be::<u8>()
1007 }
1008}
1009
1010impl<V: fmt::Debug> fmt::Debug for TagAndInfo<'_, V> {
1011 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1012 f.debug_struct("TagAndInfo")
1013 .field("tag", &self.tag())
1014 .field("prnd", &self.prnd())
1015 .field("adkd", &self.adkd())
1016 .field("_validated", &self._validated)
1017 .finish()
1018 }
1019}
1020
1021#[cfg(test)]
1022mod test {
1023 use super::*;
1024 use hex_literal::hex;
1025
1026 #[test]
1027 fn nma_header() {
1028 // NMA header broadcast on 2022-03-07
1029 let nma_header = NmaHeader::new(0x52);
1030 assert_eq!(nma_header.nma_status(), NmaStatus::Test);
1031 assert_eq!(nma_header.chain_id(), 1);
1032 assert_eq!(
1033 nma_header.chain_and_pubkey_status(),
1034 ChainAndPubkeyStatus::Nominal
1035 );
1036 }
1037
1038 #[test]
1039 fn dsm_header() {
1040 let header = [0x17];
1041 let dsm_header = DsmHeader(&header);
1042 assert_eq!(dsm_header.dsm_id(), 1);
1043 assert_eq!(dsm_header.dsm_block_id(), 7);
1044 assert_eq!(dsm_header.dsm_type(), DsmType::Kroot);
1045 }
1046
1047 #[test]
1048 fn dsm_pkr() {
1049 // DSM-PKR broadcast on 2023-12-12 12:00 UTC
1050 let dsm = hex!(
1051 "
1052 70 01 63 1b dc ed 79 d4 31 7b c2 87 0e e3 89 5b
1053 d5 9c f2 b6 ea 51 6f ab bf df 1d 73 96 26 14 6f
1054 fe 31 6f a9 28 5f 5a 1e 44 04 24 13 bd af 18 aa
1055 3c f6 84 72 33 97 d7 b8 32 5a ec a1 eb ca 9f 0f
1056 64 99 05 42 4c be 48 2a 1a 32 b0 10 64 f8 5d 0c
1057 36 df 03 8e 52 ce 12 8e 7e c5 f3 23 e1 65 b1 82
1058 a7 15 37 bd b0 10 97 2e b4 a3 b9 0b aa cd 14 94
1059 1e f4 0d a2 cb 2b 82 d3 78 b3 15 c0 08 de ce fd
1060 8e 11 03 74 a9 25 cf a0 ff 18 05 e5 c5 a5 8f db
1061 a3 1b f0 14 5d 5b 5b e2 f0 62 d3 f8 bb 2e e9 8f
1062 0f 6d b0 e8 23 c5 e7 5e 78"
1063 );
1064 let dsm = DsmPkr(&dsm);
1065 assert_eq!(dsm.number_of_blocks(), Some(13));
1066 assert_eq!(dsm.message_id(), 0);
1067 assert_eq!(
1068 dsm.intermediate_tree_node(0),
1069 &hex!(
1070 "01 63 1b dc ed 79 d4 31 7b c2 87 0e e3 89 5b d5
1071 9c f2 b6 ea 51 6f ab bf df 1d 73 96 26 14 6f fe"
1072 )
1073 );
1074 let itn1 = hex!(
1075 "31 6f a9 28 5f 5a 1e 44 04 24 13 bd af 18 aa 3c
1076 f6 84 72 33 97 d7 b8 32 5a ec a1 eb ca 9f 0f 64"
1077 );
1078 assert_eq!(dsm.intermediate_tree_node(1), &itn1);
1079 let itn2 = hex!(
1080 "99 05 42 4c be 48 2a 1a 32 b0 10 64 f8 5d 0c 36
1081 df 03 8e 52 ce 12 8e 7e c5 f3 23 e1 65 b1 82 a7"
1082 );
1083 assert_eq!(dsm.intermediate_tree_node(2), &itn2);
1084 let itn3 = hex!(
1085 "15 37 bd b0 10 97 2e b4 a3 b9 0b aa cd 14 94 1e
1086 f4 0d a2 cb 2b 82 d3 78 b3 15 c0 08 de ce fd 8e"
1087 );
1088 assert_eq!(dsm.intermediate_tree_node(3), &itn3);
1089 assert_eq!(
1090 dsm.new_public_key_type(),
1091 NewPublicKeyType::EcdsaKey(EcdsaFunction::P256Sha256)
1092 );
1093 assert_eq!(dsm.new_public_key_id(), 1);
1094 assert_eq!(
1095 dsm.new_public_key(),
1096 Some(
1097 &hex!(
1098 "03 74 a9 25 cf a0 ff 18 05 e5 c5 a5 8f db a3 1b
1099 f0 14 5d 5b 5b e2 f0 62 d3 f8 bb 2e e9 8f 0f 6d b0"
1100 )[..]
1101 )
1102 );
1103 assert_eq!(dsm.padding(), Some(&hex!("e8 23 c5 e7 5e 78")[..]));
1104 // Obtained from OSNMA_MerkleTree_20231213105954_PKID_1.xml
1105 let merkle_tree_root =
1106 hex!("0E63F552C8021709043C239032EFFE941BF22C8389032F5F2701E0FBC80148B8");
1107 assert!(dsm.check_padding(&merkle_tree_root));
1108
1109 // DSM-PKR broadcast on 2023-12-15 00:00 UTC
1110 let dsm = hex!(
1111 "
1112 71 e5 53 0a 33 d5 cb 60 c9 50 16 b8 ae c7 45 93
1113 db cd f2 71 1d 39 9e a2 48 69 17 3c a2 29 37 9a
1114 15 31 6f a9 28 5f 5a 1e 44 04 24 13 bd af 18 aa
1115 3c f6 84 72 33 97 d7 b8 32 5a ec a1 eb ca 9f 0f
1116 64 99 05 42 4c be 48 2a 1a 32 b0 10 64 f8 5d 0c
1117 36 df 03 8e 52 ce 12 8e 7e c5 f3 23 e1 65 b1 82
1118 a7 15 37 bd b0 10 97 2e b4 a3 b9 0b aa cd 14 94
1119 1e f4 0d a2 cb 2b 82 d3 78 b3 15 c0 08 de ce fd
1120 8e 12 03 35 78 e5 c7 11 a9 c3 bd dd 1c a4 ee 85
1121 f7 c5 1b 36 78 97 cb 40 b8 85 68 a0 c8 97 da 30
1122 ef b7 c3 24 e0 22 2c 90 80"
1123 );
1124 let dsm = DsmPkr(&dsm);
1125 assert_eq!(dsm.number_of_blocks(), Some(13));
1126 assert_eq!(dsm.message_id(), 1);
1127 assert_eq!(
1128 dsm.intermediate_tree_node(0),
1129 &hex!(
1130 "e5 53 0a 33 d5 cb 60 c9 50 16 b8 ae c7 45 93 db
1131 cd f2 71 1d 39 9e a2 48 69 17 3c a2 29 37 9a 15"
1132 )
1133 );
1134 assert_eq!(dsm.intermediate_tree_node(1), &itn1);
1135 assert_eq!(dsm.intermediate_tree_node(2), &itn2);
1136 assert_eq!(dsm.intermediate_tree_node(3), &itn3);
1137 assert_eq!(
1138 dsm.new_public_key_type(),
1139 NewPublicKeyType::EcdsaKey(EcdsaFunction::P256Sha256)
1140 );
1141 assert_eq!(dsm.new_public_key_id(), 2);
1142 assert_eq!(
1143 dsm.new_public_key(),
1144 Some(
1145 &hex!(
1146 "03 35 78 e5 c7 11 a9 c3 bd dd 1c a4 ee 85 f7 c5
1147 1b 36 78 97 cb 40 b8 85 68 a0 c8 97 da 30 ef b7 c3"
1148 )[..]
1149 )
1150 );
1151 assert_eq!(dsm.padding(), Some(&hex!("24 e0 22 2c 90 80")[..]));
1152 assert!(dsm.check_padding(&merkle_tree_root));
1153 }
1154
1155 #[test]
1156 fn dsm_kroot() {
1157 // DSM-KROOT broadcast on 2022-03-07 9:00 UTC
1158 let dsm = hex!(
1159 "
1160 22 50 49 21 04 98 21 25 d3 96 4d a3 a2 84 1e 1d
1161 e4 d4 58 c0 e9 84 24 76 e0 04 66 6c f3 79 58 de
1162 28 51 97 a2 63 53 f1 a4 c6 6d 7e 3d 29 18 53 ba
1163 5a 13 c9 c3 48 4a 26 77 70 11 2a 13 38 3e a5 2d
1164 3a 01 9d 5b 6e 1d d1 87 b9 45 3c df 06 ca 7f 34
1165 ea 14 97 52 5a af 18 f1 f9 f1 fc cb 12 29 89 77
1166 35 c0 21 b0 41 73 93 b5"
1167 );
1168 let dsm = DsmKroot(&dsm);
1169 assert_eq!(dsm.number_of_blocks(), Some(8));
1170 assert_eq!(dsm.public_key_id(), 2);
1171 assert_eq!(dsm.kroot_chain_id(), 1);
1172 assert_eq!(dsm.hash_function(), HashFunction::Sha256);
1173 assert_eq!(dsm.mac_function(), MacFunction::HmacSha256);
1174 assert_eq!(dsm.key_size(), Some(128));
1175 assert_eq!(dsm.tag_size(), Some(40));
1176 assert_eq!(dsm.mac_lookup_table(), 0x21);
1177 assert_eq!(dsm.kroot_wn(), 0x498);
1178 assert_eq!(dsm.kroot_towh(), 0x21);
1179 assert_eq!(dsm.alpha(), 0x25d3964da3a2);
1180 assert_eq!(
1181 dsm.kroot(),
1182 hex!("84 1e 1d e4 d4 58 c0 e9 84 24 76 e0 04 66 6c f3")
1183 );
1184 assert_eq!(dsm.ecdsa_function(), EcdsaFunction::P256Sha256);
1185 assert_eq!(
1186 dsm.digital_signature(),
1187 hex!(
1188 "79 58 de 28 51 97 a2 63 53 f1 a4 c6 6d 7e 3d 29
1189 18 53 ba 5a 13 c9 c3 48 4a 26 77 70 11 2a 13 38
1190 3e a5 2d 3a 01 9d 5b 6e 1d d1 87 b9 45 3c df 06
1191 ca 7f 34 ea 14 97 52 5a af 18 f1 f9 f1 fc cb 12"
1192 )
1193 );
1194 assert_eq!(dsm.padding(), hex!("29 89 77 35 c0 21 b0 41 73 93 b5"));
1195 let nma_header = NmaHeader::new(0x52);
1196 assert!(dsm.check_padding(nma_header));
1197 }
1198
1199 #[test]
1200 fn mack() {
1201 // MACK broadcast on 2022-03-07 9:00 UTC
1202 let mack = hex!(
1203 "
1204 11 55 d3 71 f2 1f 30 a8 e4 ec e0 c0 1b 07 6d 17
1205 7d 64 03 12 05 d4 02 7e 77 13 15 c0 4c ca 1c 16
1206 99 1a 05 48 91 07 a7 f7 0e c5 42 b4 19 da 6a da
1207 1c 0a 3d 6f 56 a5 e5 dc 59 a7 00 00"
1208 );
1209 let key_size = 128;
1210 let tag_size = 40;
1211 let mack = Mack::new(&mack, key_size, tag_size);
1212 assert_eq!(mack.key_size(), key_size);
1213 assert_eq!(mack.tag_size(), tag_size);
1214 assert_eq!(mack.tag0(), BitSlice::from_slice(&hex!("11 55 d3 71 f2")));
1215 assert_eq!(mack.macseq(), 0x1f3);
1216 assert_eq!(mack.num_tags(), 6);
1217 assert_eq!(
1218 mack.tag_and_info(1).tag(),
1219 BitSlice::from_slice(&hex!("a8 e4 ec e0 c0"))
1220 );
1221 assert_eq!(mack.tag_and_info(1).prnd(), Prnd::GalileoSvid(0x1b));
1222 assert_eq!(mack.tag_and_info(1).adkd(), Adkd::InavCed);
1223 assert_eq!(
1224 mack.tag_and_info(2).tag(),
1225 BitSlice::from_slice(&hex!("6d 17 7d 64 03"))
1226 );
1227 assert_eq!(mack.tag_and_info(2).prnd(), Prnd::GalileoSvid(0x12));
1228 assert_eq!(mack.tag_and_info(2).adkd(), Adkd::InavCed);
1229 assert_eq!(
1230 mack.tag_and_info(3).tag(),
1231 BitSlice::from_slice(&hex!("d4 02 7e 77 13"))
1232 );
1233 assert_eq!(mack.tag_and_info(3).prnd(), Prnd::GalileoSvid(0x15));
1234 assert_eq!(mack.tag_and_info(3).adkd(), Adkd::SlowMac);
1235 assert_eq!(
1236 mack.tag_and_info(4).tag(),
1237 BitSlice::from_slice(&hex!("4c ca 1c 16 99"))
1238 );
1239 assert_eq!(mack.tag_and_info(4).prnd(), Prnd::GalileoSvid(0x1a));
1240 assert_eq!(mack.tag_and_info(4).adkd(), Adkd::InavCed);
1241 assert_eq!(
1242 mack.tag_and_info(5).tag(),
1243 BitSlice::from_slice(&hex!("48 91 07 a7 f7"))
1244 );
1245 assert_eq!(mack.tag_and_info(5).prnd(), Prnd::GalileoSvid(0x0e));
1246 assert_eq!(mack.tag_and_info(5).adkd(), Adkd::SlowMac);
1247 assert_eq!(
1248 mack.key(),
1249 BitSlice::from_slice(&hex!("42 b4 19 da 6a da 1c 0a 3d 6f 56 a5 e5 dc 59 a7"))
1250 );
1251 }
1252}