From a0457ece39f0e659091e13fe9a73f76447cb0caf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Est=C3=A9vez?= Date: Wed, 24 Jan 2024 10:43:59 +0100 Subject: [PATCH] add validation of Alert Message against Merkle tree --- src/merkle_tree.rs | 33 +++++++++++++++++++++++++++++++-- src/osnma.rs | 40 +++++++++++++++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/src/merkle_tree.rs b/src/merkle_tree.rs index 36cb2b7..2c33897 100644 --- a/src/merkle_tree.rs +++ b/src/merkle_tree.rs @@ -25,7 +25,7 @@ impl MerkleTree { MerkleTree { root } } - /// Validates a DSM-PKR against this Merkle tree. + /// Validates a DSM-PKR containing a public key against this Merkle tree. /// /// This function checks that the public key in the DSM-PKR message belongs /// to the Merkle tree by using the intermediate tree nodes in the DSM-PKR @@ -41,6 +41,32 @@ impl MerkleTree { if !matches!(dsm_pkr.new_public_key_type(), NewPublicKeyType::EcdsaKey(_)) { return Err(PkrError::NoPublicKey); } + self.validate(dsm_pkr)?; + Self::pubkey_from_pkr(dsm_pkr) + } + + /// Validates a DSM-PKR containing an Alert Message against this Merkle tree. + /// + /// This function checks that the public key in the DSM-PKR message belongs + /// to the Merkle tree by using the intermediate tree nodes in the DSM-PKR + /// and checking against the tree root stored in `self`. + /// + /// The validation algorithm is described in Section 6.2 of the + /// [OSNMA SIS ICD v1.1](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_SIS_ICD_v1.1.pdf). + /// + /// If validation is successful, the function returns `Ok(())`. Otherwise, + /// an error is returned. + pub fn validate_alert_message(&self, dsm_pkr: DsmPkr) -> Result<(), PkrError> { + if !matches!( + dsm_pkr.new_public_key_type(), + NewPublicKeyType::OsnmaAlertMessage + ) { + return Err(PkrError::NoPublicKey); + } + self.validate(dsm_pkr) + } + + fn validate(&self, dsm_pkr: DsmPkr) -> Result<(), PkrError> { let Some(leaf) = dsm_pkr.merkle_tree_leaf() else { return Err(PkrError::ReservedField); }; @@ -58,7 +84,7 @@ impl MerkleTree { id >>= 1; } if node == self.root { - Self::pubkey_from_pkr(dsm_pkr) + Ok(()) } else { Err(PkrError::Invalid) } @@ -117,6 +143,8 @@ pub enum PkrError { Invalid, /// The DSM-PKR does not contain a public key. NoPublicKey, + /// The DSM-PKR is not an Alert Message. + NotAlert, /// The DSM-PRK key is P-521, but P-521 support has not been enabled. #[cfg(not(feature = "p521"))] P521NotSupported, @@ -128,6 +156,7 @@ impl fmt::Display for PkrError { PkrError::ReservedField => "reserved value present in some field".fmt(f), PkrError::Invalid => "wrong calculated Merkle tree root".fmt(f), PkrError::NoPublicKey => "no public key in DSM-PKR".fmt(f), + PkrError::NotAlert => "the DSM-PKR is not an alert message".fmt(f), #[cfg(not(feature = "p521"))] PkrError::P521NotSupported => "P-521 support disabled".fmt(f), } diff --git a/src/osnma.rs b/src/osnma.rs index b4b12be..f437be3 100644 --- a/src/osnma.rs +++ b/src/osnma.rs @@ -1,5 +1,6 @@ use crate::bitfields::{ - ChainAndPubkeyStatus, DsmHeader, DsmKroot, DsmPkr, DsmType, Mack, NmaHeader, NmaStatus, + ChainAndPubkeyStatus, DsmHeader, DsmKroot, DsmPkr, DsmType, Mack, NewPublicKeyType, NmaHeader, + NmaStatus, }; use crate::dsm::{CollectDsm, Dsm}; use crate::mack::MackStorage; @@ -335,20 +336,35 @@ impl OsnmaData { log::warn!("CPKS is new Merkle tree"); } ChainAndPubkeyStatus::AlertMessage => { - log::warn!("CPKS is alert message; deleting all cryptographic material"); - self.merkle_tree = None; - self.pubkey = PubkeyStore::empty(); - self.key = KeyStore::empty(); + log::warn!("CPKS is alert message"); + self.alert_message_received(); } } } + fn alert_message_received(&mut self) { + log::warn!("received OSNMA Alert Message; deleting all cryptographic material"); + self.merkle_tree = None; + self.pubkey = PubkeyStore::empty(); + self.key = KeyStore::empty(); + } + fn set_dont_use(&mut self) { self.dont_use = true; self.navmessage.reset_authbits(); } fn process_dsm_pkr(&mut self, dsm_pkr: DsmPkr) { + match dsm_pkr.new_public_key_type() { + NewPublicKeyType::EcdsaKey(_) => self.process_dsm_pkr_npk(dsm_pkr), + NewPublicKeyType::OsnmaAlertMessage => self.process_dsm_pkr_alert_message(dsm_pkr), + NewPublicKeyType::Reserved => { + log::error!("reserved NPKT in DSM-PKR: {:?}", dsm_pkr); + } + } + } + + fn process_dsm_pkr_npk(&mut self, dsm_pkr: DsmPkr) { let Some(merkle_tree) = &self.merkle_tree else { log::error!("could not verify public key because Merkle tree is not loaded"); return; @@ -362,6 +378,20 @@ impl OsnmaData { } } + fn process_dsm_pkr_alert_message(&mut self, dsm_pkr: DsmPkr) { + let Some(merkle_tree) = &self.merkle_tree else { + log::error!("could not verify OSNMA Alert Message because Merkle tree is not loaded"); + return; + }; + match merkle_tree.validate_alert_message(dsm_pkr) { + Ok(()) => { + log::warn!("received valid OSNMA Alert Message in DSM-PKR: {dsm_pkr:?}"); + self.alert_message_received(); + } + Err(e) => log::error!("could not verify OSNMA Alert Message: {e:?}"), + } + } + 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");