Skip to content

Commit

Permalink
potentially fetch key from previous chain for Slow MAC
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
daniestevez committed Jan 25, 2024
1 parent dd785e6 commit 84d4ffe
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 28 deletions.
11 changes: 11 additions & 0 deletions src/gst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
110 changes: 87 additions & 23 deletions src/osnma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,17 @@ struct PubkeyStore {
#[derive(Debug, Clone)]
struct KeyStore {
keys: [Option<Key<Validated>>; 2],
in_force: Option<bool>,
in_force: Option<KeyInForce>,
}

#[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<Gst>,
}

impl<S: StaticStorage> Osnma<S> {
Expand Down Expand Up @@ -424,8 +434,8 @@ impl<S: StaticStorage> OsnmaData<S> {
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 {:?}: {:?}",
Expand All @@ -445,9 +455,18 @@ impl<S: StaticStorage> OsnmaData<S> {

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) {
Expand All @@ -466,22 +485,24 @@ impl<S: StaticStorage> OsnmaData<S> {
// 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,
);
}
}
}
}
Expand All @@ -495,7 +516,13 @@ impl<S: StaticStorage> OsnmaData<S> {
) -> Option<Mack<'a, Validated>> {
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),
Expand Down Expand Up @@ -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,
}
}
Expand All @@ -686,7 +733,24 @@ impl KeyStore {

fn current_key(&self) -> Option<&Key<Validated>> {
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<Validated>> {
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) {
Expand Down
6 changes: 1 addition & 5 deletions src/tesla.rs
Original file line number Diff line number Diff line change
Expand Up @@ -738,11 +738,7 @@ impl Key<Validated> {
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.
Expand Down

0 comments on commit 84d4ffe

Please sign in to comment.