From 40668bec3f7311eb300777497c00b17b95c6f5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Est=C3=A9vez?= Date: Thu, 25 Jan 2024 11:18:04 +0100 Subject: [PATCH] potentially fetch key from previous chain for Slow MAC When validating the MACSEQ to process Slow MAC tags with the current TESLA key, the MACSEQ needs to be checked against an older key. If there has been a chain renewal, this older key can belong to the previous chain. This adds some logic to the key storage to keep track of when the chain for a key starts being applicable. This is used to fetch the key from the other chain if the key GST needed for the Slow MAC MACSEQ verification is before the start of applicability of the current chain. The algorithm included here is not completely foolproof, and for instance doesn't handle cases when the current chain is changing between 3 different chain IDs, but it is good enough for the typical case of chain renewal in which there is only a single chain ID change in a long period of time. --- src/gst.rs | 11 ++++++ src/osnma.rs | 110 ++++++++++++++++++++++++++++++++++++++++----------- src/tesla.rs | 6 +-- 3 files changed, 99 insertions(+), 28 deletions(-) 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..1ea3371 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 { @@ -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), @@ -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(key.gst_subframe()), + _ => 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(key.gst_subframe()), + _ => 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.