diff --git a/src/gst.rs b/src/gst.rs index 1cd2668..ecb4ed2 100644 --- a/src/gst.rs +++ b/src/gst.rs @@ -142,4 +142,15 @@ impl Gst { pub fn is_subframe(&self) -> bool { self.tow % SECS_PER_SUBFRAME == 0 } + + /// Returns the difference in subframes between `other` and `self`. + /// + /// The returned value is equal to the number of GST seconds elapsed between + /// `self` and `other`, divided by 30. + pub fn subframes_difference(&self, other: Gst) -> i32 { + (i32::from(self.wn) - i32::from(other.wn)) + * i32::try_from(SECS_IN_WEEK / SECS_PER_SUBFRAME).unwrap() + + (i32::try_from(self.tow).unwrap() - i32::try_from(other.tow).unwrap()) + / i32::try_from(SECS_PER_SUBFRAME).unwrap() + } } diff --git a/src/osnma.rs b/src/osnma.rs index f437be3..ddb13e3 100644 --- a/src/osnma.rs +++ b/src/osnma.rs @@ -108,7 +108,17 @@ struct PubkeyStore { #[derive(Debug, Clone)] struct KeyStore { keys: [Option>; 2], - in_force: Option, + in_force: Option, +} + +#[derive(Debug, Clone)] +struct KeyInForce { + index: bool, // false for the 1st position, true for then 2nd position + + // This is None if we haven't seen the key entering through a chain renewal + // or revocation, in which case we don't know when the key starts being + // applicable (and don't care, since we don't have the previous key). + start_applicability: Option, } impl Osnma { @@ -237,7 +247,7 @@ impl OsnmaDsm { let dsm_header = DsmHeader(dsm_header); let dsm_block = &hkroot[2..].try_into().unwrap(); if let Some(dsm) = self.dsm.feed(dsm_header, dsm_block) { - self.data.process_dsm(dsm, nma_header); + self.data.process_dsm(dsm, nma_header, gst); } self.data.validate_key(mack, gst, nma_header.nma_status()); @@ -245,14 +255,14 @@ impl OsnmaDsm { } impl OsnmaData { - fn process_dsm(&mut self, dsm: Dsm, nma_header: NmaHeader) { + fn process_dsm(&mut self, dsm: Dsm, nma_header: NmaHeader, gst: Gst) { match dsm.dsm_type() { - DsmType::Kroot => self.process_dsm_kroot(DsmKroot(dsm.data()), nma_header), + DsmType::Kroot => self.process_dsm_kroot(DsmKroot(dsm.data()), nma_header, gst), DsmType::Pkr => self.process_dsm_pkr(DsmPkr(dsm.data())), } } - fn process_dsm_kroot(&mut self, dsm_kroot: DsmKroot, nma_header: NmaHeader) { + fn process_dsm_kroot(&mut self, dsm_kroot: DsmKroot, nma_header: NmaHeader, gst: Gst) { let pkid = dsm_kroot.public_key_id(); let Some(pubkey) = self.pubkey.applicable_pubkey(pkid) else { return; @@ -262,7 +272,7 @@ impl OsnmaData { log::info!("verified KROOT with public key id {pkid}"); log::info!("current NMA header: {nma_header:?}"); self.pubkey.make_pkid_current(pkid); - self.key.store_kroot(key, nma_header); + self.key.store_kroot(key, nma_header, gst); self.process_nma_header(nma_header, pkid); } Err(e) => log::error!("could not verify KROOT: {:?}", e), @@ -424,8 +434,8 @@ impl OsnmaData { new_valid_key, current_key ); - self.process_tags(&new_valid_key, nma_status); self.key.store_key(new_valid_key); + self.process_tags(&new_valid_key, nma_status); } Err(e) => log::error!( "could not validate TESLA key {:?} using {:?}: {:?}", @@ -445,9 +455,18 @@ impl OsnmaData { let gst_mack = current_key.gst_subframe().add_seconds(-30); let gst_slowmac = gst_mack.add_seconds(-300); - // Re-generate the key that was used for the MACSEQ of the - // Slow MAC MACK - let slowmac_key = current_key.derive(10); + // Try to re-generate the key that was used for the MACSEQ of the + // Slow MAC MACK. This key might be from a previous chain. + let gst_k_slowmac = current_key.gst_subframe().add_seconds(-300); + let slowmac_chain_key = self.key.key_past_chain(gst_k_slowmac); + let slowmac_key = slowmac_chain_key.and_then(|k| { + let derivations = k.gst_subframe().subframes_difference(gst_k_slowmac); + if derivations >= 0 { + Some(k.derive(derivations.try_into().unwrap())) + } else { + None + } + }); for svn in Svn::iter() { if !self.only_slowmac { if let Some(mack) = self.mack.get(svn, gst_mack) { @@ -466,22 +485,24 @@ impl OsnmaData { // Try to validate Slow MAC // This needs fetching a tag which is 300 seconds older than for // the other ADKDs - if let Some(mack) = self.mack.get(svn, gst_slowmac) { - let mack = Mack::new( - mack, - current_key.chain().key_size_bits(), - current_key.chain().tag_size_bits(), - ); - // Note that slowmac_key is used for validation of the MACK, while - // current_key is used for validation of the Slow MAC tags it contains. - if let Some(mack) = Self::validate_mack(mack, &slowmac_key, svn, gst_slowmac) { - self.navmessage.process_mack_slowmac( + if let Some(slowmac_key) = &slowmac_key { + if let Some(mack) = self.mack.get(svn, gst_slowmac) { + let mack = Mack::new( mack, - current_key, - svn, - gst_slowmac, - nma_status, + current_key.chain().key_size_bits(), + current_key.chain().tag_size_bits(), ); + // Note that slowmac_key is used for validation of the MACK, while + // current_key is used for validation of the Slow MAC tags it contains. + if let Some(mack) = Self::validate_mack(mack, slowmac_key, svn, gst_slowmac) { + self.navmessage.process_mack_slowmac( + mack, + current_key, + svn, + gst_slowmac, + nma_status, + ); + } } } } @@ -495,7 +516,13 @@ impl OsnmaData { ) -> Option> { match mack.validate(key, prna, gst_mack) { Err(e) => { - log::error!("error validating MACK {:?}: {:?}", mack, e); + log::error!( + "error validating {} {:?} MACK {:?}: {:?}", + prna, + gst_mack, + mack, + e + ); None } Ok(m) => Some(m), @@ -631,7 +658,7 @@ impl KeyStore { } } - fn store_kroot(&mut self, key: Key, nma_header: NmaHeader) { + fn store_kroot(&mut self, key: Key, nma_header: NmaHeader, gst: Gst) { let kid = key.chain().chain_id(); let cid = nma_header.chain_id(); match (&self.keys[0], &self.keys[1]) { @@ -664,8 +691,28 @@ impl KeyStore { } // update self.in_force match (&self.keys[0], &self.keys[1]) { - (Some(k), _) if k.chain().chain_id() == cid => self.in_force = Some(false), - (_, Some(k)) if k.chain().chain_id() == cid => self.in_force = Some(true), + (Some(k), _) if k.chain().chain_id() == cid => { + let index = false; + let start_applicability = match &self.in_force { + Some(in_force) if in_force.index != index => Some(gst), + _ => None, + }; + self.in_force = Some(KeyInForce { + index, + start_applicability, + }); + } + (_, Some(k)) if k.chain().chain_id() == cid => { + let index = true; + let start_applicability = match &self.in_force { + Some(in_force) if in_force.index != index => Some(gst), + _ => None, + }; + self.in_force = Some(KeyInForce { + index, + start_applicability, + }); + } _ => self.in_force = None, } } @@ -686,7 +733,24 @@ impl KeyStore { fn current_key(&self) -> Option<&Key> { self.in_force - .and_then(|v| self.keys[usize::from(v)].as_ref()) + .as_ref() + .and_then(|in_force| self.keys[usize::from(in_force.index)].as_ref()) + } + + // Similar to current_key but returns a key from the other chain if the + // requested GST is before the start of applicability of the current + // chain. This is used to get the key for MACK validation for Slow MAC. + fn key_past_chain(&self, gst: Gst) -> Option<&Key> { + self.in_force + .as_ref() + .and_then(|in_force| match in_force.start_applicability { + Some(gst0) if gst0 > gst => { + // Requested time is before the start of the applicability. + // Get the key from the other slot (if occupied). + self.keys[usize::from(!in_force.index)].as_ref() + } + _ => self.keys[usize::from(in_force.index)].as_ref(), + }) } fn revoke(&mut self, cid: u8) { diff --git a/src/tesla.rs b/src/tesla.rs index ae56128..92a9344 100644 --- a/src/tesla.rs +++ b/src/tesla.rs @@ -738,11 +738,7 @@ impl Key { if self.gst_subframe >= other.gst_subframe { return Err(ValidationError::DoesNotFollow); } - let derivations = i32::from(other.gst_subframe.wn() - self.gst_subframe.wn()) - * (7 * 24 * 3600 / 30) - + (i32::try_from(other.gst_subframe.tow()).unwrap() - - i32::try_from(self.gst_subframe.tow()).unwrap()) - / 30; + let derivations = other.gst_subframe.subframes_difference(self.gst_subframe); assert!(derivations >= 1); // Set an arbitrary limit to the number of derivations. // This is chosen to be slightly greater than 1 day.