Skip to content

Commit

Permalink
tesla: remove chain status from Chain
Browse files Browse the repository at this point in the history
This removes the chain status from the Chain. Since the chain status
can change from don't use to operational/test at any moment but the
Chain is associated with a key and propagated to the next TESLA key
after validation, it does not make sense to store the status in the
Chain.
  • Loading branch information
daniestevez committed Jan 16, 2024
1 parent a988893 commit 6472f48
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 63 deletions.
31 changes: 26 additions & 5 deletions src/navmessage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! the [`Osnma`](crate::Osnma) black box, but it can also be used directly
//! if finer control is needed.
use crate::bitfields::{Adkd, Mack};
use crate::bitfields::{Adkd, Mack, NmaStatus};
use crate::storage::StaticStorage;
use crate::tesla::Key;
use crate::types::{BitSlice, InavBand, InavWord};
Expand Down Expand Up @@ -310,7 +310,10 @@ impl<S: StaticStorage> CollectNavMessage<S> {
/// `prna` is the authenticating PRN, which is the SVN that has transmitted
/// the MACK message. The `gst_mack` parameter should be the GST
/// corresponding to the start of the subframe when the MACK message was
/// transmitted.
/// transmitted. The `nma_status` parameter should be the value of the NMA
/// status field in the current NMA header. The NMA header does not need to
/// be validated using the DSM-KROOT, since a forged or incorrect NMA header
/// will simply make tag validation fail.
///
/// This function ignores the ADKD=12 (Slow MAC) tags in the MACK message,
/// since they do not correspond to `key`.
Expand All @@ -320,6 +323,7 @@ impl<S: StaticStorage> CollectNavMessage<S> {
key: &Key<Validated>,
prna: Svn,
gst_mack: Gst,
nma_status: NmaStatus,
) {
log::info!("{} tag0 at {:?} COP = {}", prna, gst_mack, mack.cop());
let gst_navmessage = gst_mack.add_seconds(-30);
Expand All @@ -336,6 +340,7 @@ impl<S: StaticStorage> CollectNavMessage<S> {
u8::from(prna),
prna,
0,
nma_status,
&navdata,
self.ced_and_status_iter_authbits_mut(),
);
Expand Down Expand Up @@ -378,6 +383,7 @@ impl<S: StaticStorage> CollectNavMessage<S> {
prnd,
prna,
j,
nma_status,
&navdata,
self.ced_and_status_iter_authbits_mut(),
);
Expand All @@ -402,6 +408,7 @@ impl<S: StaticStorage> CollectNavMessage<S> {
prnd,
prna,
j,
nma_status,
&navdata,
self.timing_parameters_iter_authbits_mut(),
);
Expand Down Expand Up @@ -432,7 +439,10 @@ impl<S: StaticStorage> CollectNavMessage<S> {
/// message). The `prna` is the authenticating PRN, which is the SVN that
/// has transmitted the MACK message. The `gst_mack` parameter should be the
/// GST corresponding to the start of the subframe when the MACK message was
/// transmitted.
/// transmitted. The `nma_status` parameter should be the value of the NMA
/// status field in the current NMA header. The NMA header does not need to
/// be validated using the DSM-KROOT, since a forged or incorrect NMA header
/// will simply make tag validation fail.
///
/// This function ignores all the other tags in the MACK message, since they
/// do not correspond to `key`.
Expand All @@ -442,6 +452,7 @@ impl<S: StaticStorage> CollectNavMessage<S> {
key: &Key<Validated>,
prna: Svn,
gst_mack: Gst,
nma_status: NmaStatus,
) {
let gst_navmessage = gst_mack.add_seconds(-30);
for j in 1..mack.num_tags() {
Expand Down Expand Up @@ -476,6 +487,7 @@ impl<S: StaticStorage> CollectNavMessage<S> {
prnd,
prna,
j,
nma_status,
&navdata,
self.ced_and_status_iter_authbits_mut(),
);
Expand All @@ -493,13 +505,22 @@ impl<S: StaticStorage> CollectNavMessage<S> {
prnd: u8,
prna: Svn,
tag_idx: usize,
nma_status: NmaStatus,
navdata: &dyn AuthBits,
to_add_authbits: impl Iterator<Item = &'a mut dyn AuthBits>,
) -> bool {
let ctr = (tag_idx + 1).try_into().unwrap();
let ret = match tag_idx {
0 => key.validate_tag0(tag, gst_tag, prna, navdata.message_bits()),
_ => key.validate_tag(tag, gst_tag, prnd, prna, ctr, navdata.message_bits()),
0 => key.validate_tag0(tag, gst_tag, prna, nma_status, navdata.message_bits()),
_ => key.validate_tag(
tag,
gst_tag,
prnd,
prna,
ctr,
nma_status,
navdata.message_bits(),
),
};
if ret {
log::info!(
Expand Down
19 changes: 12 additions & 7 deletions src/osnma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ impl<S: StaticStorage> OsnmaDsm<S> {
self.data.process_dsm(dsm, nma_header);
}

self.data.validate_key(mack, gst);
self.data.validate_key(mack, gst, nma_header.nma_status());
}
}

Expand Down Expand Up @@ -362,7 +362,7 @@ impl<S: StaticStorage> OsnmaData<S> {
}
}

fn validate_key(&mut self, mack: &MackMessage, gst: Gst) {
fn validate_key(&mut self, mack: &MackMessage, gst: Gst, nma_status: NmaStatus) {
let Some(current_key) = self.key.current_key() else {
log::info!("no valid TESLA key for the chain in force. unable to validate MACK key");
return;
Expand Down Expand Up @@ -394,7 +394,7 @@ impl<S: StaticStorage> OsnmaData<S> {
new_valid_key,
current_key
);
self.process_tags(&new_valid_key);
self.process_tags(&new_valid_key, nma_status);
self.key.store_key(new_valid_key);
}
Err(e) => log::error!(
Expand All @@ -408,7 +408,7 @@ impl<S: StaticStorage> OsnmaData<S> {
}
}

fn process_tags(&mut self, current_key: &Key<Validated>) {
fn process_tags(&mut self, current_key: &Key<Validated>, nma_status: NmaStatus) {
if self.dont_use {
return;
}
Expand All @@ -428,7 +428,7 @@ impl<S: StaticStorage> OsnmaData<S> {
);
if let Some(mack) = Self::validate_mack(mack, current_key, svn, gst_mack) {
self.navmessage
.process_mack(mack, current_key, svn, gst_mack);
.process_mack(mack, current_key, svn, gst_mack, nma_status);
};
}
}
Expand All @@ -445,8 +445,13 @@ impl<S: StaticStorage> OsnmaData<S> {
// 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);
self.navmessage.process_mack_slowmac(
mack,
current_key,
svn,
gst_slowmac,
nma_status,
);
}
}
}
Expand Down
76 changes: 25 additions & 51 deletions src/tesla.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ const MAX_KEY_BYTES: usize = 32;
/// constructed from a DSK-KROOT message using [`Chain::from_dsm_kroot`].
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Chain {
status: ChainStatus,
id: u8,
// TODO: decide if CPKS needs to be included here (and how)
hash_function: HashFunction,
mac_function: MacFunction,
key_size_bytes: usize,
Expand All @@ -44,22 +42,6 @@ pub struct Chain {
alpha: u64,
}

/// Chain status.
///
/// This gives the chain status for a valid TESLA chain. This roughly
/// corresponds to the NMA status [`NmaStatus`],
/// but "don't use" and "reserved" are not considered valid statuses for a TESLA
/// chain.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum ChainStatus {
/// Test (corresponds to NMAS = 1).
Test,
/// Operational (corresponds to NMAS = 2).
Operational,
/// Don't use (corresponds to NMAS = 3).
DontUse,
}

/// Hash function.
///
/// This gives the hash function used by the TESLA chain. Its values correspond
Expand Down Expand Up @@ -89,21 +71,9 @@ pub enum MacFunction {
impl Chain {
/// Extract the chain parameters from a DSM-KROOT message.
///
/// The corresponding NMA header needs to be given, in order to extract the
/// [`ChainStatus`] from the NMA status field.
///
/// If all the values in the DSM-KROOT message are acceptable a `Chain` is
/// returned. Otherwise, this returns an error indicating the problem.
pub fn from_dsm_kroot(
nma_header: NmaHeader<NotValidated>,
dsm_kroot: DsmKroot,
) -> Result<Chain, ChainError> {
let status = match nma_header.nma_status() {
NmaStatus::Test => ChainStatus::Test,
NmaStatus::Operational => ChainStatus::Operational,
NmaStatus::DontUse => ChainStatus::DontUse,
NmaStatus::Reserved => return Err(ChainError::ReservedField),
};
pub fn from_dsm_kroot(dsm_kroot: DsmKroot) -> Result<Chain, ChainError> {
let hash_function = match dsm_kroot.hash_function() {
bitfields::HashFunction::Sha256 => HashFunction::Sha256,
bitfields::HashFunction::Sha3_256 => HashFunction::Sha3_256,
Expand All @@ -123,8 +93,7 @@ impl Chain {
};
let tag_size_bits = dsm_kroot.tag_size().ok_or(ChainError::ReservedField)?;
Ok(Chain {
status,
id: nma_header.chain_id(),
id: dsm_kroot.kroot_chain_id(),
hash_function,
mac_function,
key_size_bytes,
Expand All @@ -134,11 +103,6 @@ impl Chain {
})
}

/// Gives the status of the TESLA chain.
pub fn chain_status(&self) -> ChainStatus {
self.status
}

/// Gives the chain ID of the TESLA chain.
pub fn chain_id(&self) -> u8 {
self.id
Expand Down Expand Up @@ -629,8 +593,8 @@ impl Key<Validated> {
dsm_kroot: DsmKroot,
pubkey: &PublicKey<Validated>,
) -> Result<(Key<Validated>, NmaHeader<Validated>), KrootValidationError> {
let chain = Chain::from_dsm_kroot(nma_header, dsm_kroot)
.map_err(KrootValidationError::WrongDsmKrootChain)?;
let chain =
Chain::from_dsm_kroot(dsm_kroot).map_err(KrootValidationError::WrongDsmKrootChain)?;
if !dsm_kroot.check_padding(nma_header) {
return Err(KrootValidationError::WrongDsmKrootPadding);
}
Expand Down Expand Up @@ -806,7 +770,10 @@ impl Key<Validated> {
/// according to Section 6.7 in the ICD. The `ctr` parameter is the index of
/// the tag, where the first tag in a MACK message has `ctr = 1`. Note that
/// [`Key::validate_tag0`] should be used to validate the tag0 in a MACK
/// message instead of this function.
/// message instead of this function. The `nma_status` parameter should be
/// the value of the NMA status field in the current NMA header. The NMA
/// header does not need to be validated using the DSM-KROOT, since a forged
/// or incorrect NMA header will simply make tag validation fail.
///
/// Note that the navigation data `navdata` must correspond to the previous
/// subframe of the tag, and the key `self` must correspond to the next
Expand All @@ -817,18 +784,20 @@ impl Key<Validated> {
///
/// This returns `true` if the validation was succesful. Otherwise, it
/// returns `false`.
#[allow(clippy::too_many_arguments)]
pub fn validate_tag(
&self,
tag: &BitSlice,
tag_gst: Gst,
prnd: u8,
prna: Svn,
ctr: u8,
nma_status: NmaStatus,
navdata: &BitSlice,
) -> bool {
let mut mac = self.mac_digest();
mac.update(&[prnd]);
self.update_common_tag_message(&mut mac, tag_gst, prna, ctr, navdata);
self.update_common_tag_message(&mut mac, tag_gst, prna, ctr, nma_status, navdata);
self.check_common(mac, tag)
}

Expand All @@ -840,7 +809,11 @@ impl Key<Validated> {
///
/// The `tag_gst` parameter should give the GST at the start of the subframe
/// when the `tag` was transmitted. The `prna` parameter corresponds to the
/// SVN of the satellite that transmitted the tag0.
/// SVN of the satellite that transmitted the tag0. The `nma_status`
/// parameter should be the value of the NMA status field in the current NMA
/// header. The NMA header does not need to be validated using the
/// DSM-KROOT, since a forged or incorrect NMA header will simply make tag
/// validation fail.
///
/// Note that the navigation data `navdata` must correspond to the previous
/// subframe of the tag0, and the key `self` must correspond to the next
Expand All @@ -853,10 +826,11 @@ impl Key<Validated> {
tag0: &BitSlice,
tag_gst: Gst,
prna: Svn,
nma_status: NmaStatus,
navdata: &BitSlice,
) -> bool {
let mut mac = self.mac_digest();
self.update_common_tag_message(&mut mac, tag_gst, prna, 1, navdata);
self.update_common_tag_message(&mut mac, tag_gst, prna, 1, nma_status, navdata);
self.check_common(mac, tag0)
}

Expand All @@ -871,6 +845,7 @@ impl Key<Validated> {
gst: Gst,
prna: Svn,
ctr: u8,
nma_status: NmaStatus,
navdata: &BitSlice,
) {
// This is large enough to fit all the message for ADKD=0 and 12
Expand All @@ -884,10 +859,11 @@ impl Key<Validated> {
buffer[5] = ctr;
let remaining_bits = BitSlice::from_slice_mut(&mut buffer[6..]);
const STATUS_BITS: usize = 2;
remaining_bits[..STATUS_BITS].store_be(match self.chain.status {
ChainStatus::Test => 1,
ChainStatus::Operational => 2,
ChainStatus::DontUse => 3,
remaining_bits[..STATUS_BITS].store_be(match nma_status {
NmaStatus::Reserved => 0,
NmaStatus::Test => 1,
NmaStatus::Operational => 2,
NmaStatus::DontUse => 3,
});
remaining_bits[STATUS_BITS..STATUS_BITS + navdata.len()].copy_from_bitslice(navdata);
let message_bytes = FIXED_SIZE + (STATUS_BITS + navdata.len() + 7) / 8;
Expand Down Expand Up @@ -1000,7 +976,6 @@ mod test {
fn test_chain() -> Chain {
// Active chain on 2022-03-07 ~09:00 UTC
Chain {
status: ChainStatus::Test,
id: 1,
hash_function: HashFunction::Sha256,
mac_function: MacFunction::HmacSha256,
Expand All @@ -1014,7 +989,6 @@ mod test {
fn test_chain_2023() -> Chain {
// Active chain on 2023-12-12 ~10:00 UTC
Chain {
status: ChainStatus::Test,
id: 0,
hash_function: HashFunction::Sha256,
mac_function: MacFunction::HmacSha256,
Expand Down Expand Up @@ -1082,7 +1056,7 @@ mod test {
01 7f fd 87 d0 fe 85 ee 31 ff f6 20 0c 68 0b fe
48 00 50 14 00"
))[..549];
assert!(key.validate_tag0(tag0, tag0_gst, prna, navdata_adkd0));
assert!(key.validate_tag0(tag0, tag0_gst, prna, NmaStatus::Test, navdata_adkd0));
}

fn test_mack() -> Mack<'static, NotValidated> {
Expand Down

0 comments on commit 6472f48

Please sign in to comment.