1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
use crate::bitfields::{DsmHeader, DsmKroot, Mack, NmaHeader};
use crate::dsm::CollectDsm;
use crate::mack::MackStorage;
use crate::navmessage::{CollectNavMessage, NavMessageData};
use crate::storage::StaticStorage;
use crate::subframe::CollectSubframe;
use crate::tesla::Key;
use crate::types::{HkrootMessage, InavWord, MackMessage, OsnmaDataMessage};
use crate::validation::{NotValidated, Validated};
use crate::{Gst, Svn};

use core::cmp::Ordering;
use p256::ecdsa::VerifyingKey;

/// OSNMA "black box" processing.
///
/// The [`Osnma`] struct gives a way to process OSNMA data using a "black box"
/// approach. INAV words and OSNMA data retrieved from the E1B and E5b signals
/// is fed by the user into `Osnma`, and at any point the user can request
/// `Osnma` to give the most recent authenticated navigation data (provided that
/// it is available).
///
/// # Examples
///
/// ```
/// use galileo_osnma::{Gst, Osnma, Svn};
/// use galileo_osnma::storage::FullStorage;
/// use p256::ecdsa::VerifyingKey;
///
/// // Typically, the ECDSA public key should be obtained from
/// // a file. Here a statically defined dummy key is used for
/// // the sake of the example.
/// let pubkey = [3, 154, 36, 205, 5, 122, 110, 166, 187, 238, 33,
///               117, 116, 91, 202, 57, 34, 72, 200, 202, 10, 169,
///               253, 225, 1, 233, 82, 99, 133, 255, 241, 114, 218];
/// let pubkey = VerifyingKey::from_sec1_bytes(&pubkey).unwrap();
///
/// // Create OSNMA black box using full storage (36 satellites and
/// // large enough history for Slow MAC)
/// let only_slowmac = false; // process "fast" MAC as well as Slow MAC
/// let mut osnma = Osnma::<FullStorage>::from_pubkey(pubkey, only_slowmac);
///
/// // Feed some INAV and OSNMA data. Data full of zeros is used here.
/// let svn = Svn::try_from(12).unwrap(); // E12
/// let gst = Gst::new(1177, 175767); // WN 1177, TOW 175767
/// let inav = [0; 16];
/// let osnma_data = [0; 5];
/// osnma.feed_inav(&inav, svn, gst);
/// osnma.feed_osnma(&osnma_data, svn, gst);
///
/// // Try to retrieve authenticated data
/// // ADKD=0 and 12, CED and health status for a satellite
/// let ced = osnma.get_ced_and_status(svn);
/// // ADKD=4, Galileo constellation timing parameters
/// let timing = osnma.get_timing_parameters();
/// ```
///
/// # Storage size
///
/// The size of the internal storage used to hold navigation data and MACK
/// messages is defined by the [`StaticStorage`] type parameter `S`. See the
/// [storage](crate::storage) module for a description of how the storage size
/// is defined.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Osnma<S: StaticStorage> {
    subframe: CollectSubframe,
    data: OsnmaDsm<S>,
}

// These structures exist only in order to avoid double mutable
// borrows of Osnma because we take references from CollectSubframe
// and CollectDsm
#[derive(Debug, Clone, PartialEq, Eq)]
struct OsnmaDsm<S: StaticStorage> {
    dsm: CollectDsm,
    data: OsnmaData<S>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
struct OsnmaData<S: StaticStorage> {
    navmessage: CollectNavMessage<S>,
    mack: MackStorage<S>,
    pubkey: VerifyingKey,
    key: Option<Key<Validated>>,
    only_slowmac: bool,
}

impl<S: StaticStorage> Osnma<S> {
    /// Constructs a new OSNMA black box using an ECDSA P-256 public key.
    ///
    /// The OSNMA black box will hold the public key `pubkey` and use it to
    /// try to authenticate the TESLA root key. The public key cannot be changed
    /// after construction.
    ///
    /// If `only_slowmac` is `true`, only ADKD=12 (Slow MAC) will be processed.
    /// This should be used by receivers which have a larger time uncertainty.
    /// (See Annex 3 in the
    /// [OSNMA Receiver Guidelines](https://www.gsc-europa.eu/sites/default/files/sites/all/files/Galileo_OSNMA_Receiver_Guidelines_for_Test_Phase_v1.0.pdf)).
    pub fn from_pubkey(pubkey: VerifyingKey, only_slowmac: bool) -> Osnma<S> {
        Osnma {
            subframe: CollectSubframe::new(),
            data: OsnmaDsm {
                dsm: CollectDsm::new(),
                data: OsnmaData {
                    navmessage: CollectNavMessage::new(),
                    mack: MackStorage::new(),
                    pubkey,
                    key: None,
                    only_slowmac,
                },
            },
        }
    }

    /// Feed an INAV word into the OSNMA black box.
    ///
    /// The black box will store the navigation data in the INAV word for later
    /// usage.
    ///
    /// The `svn` parameter corresponds to the SVN of the satellite transmitting
    /// the INAV word. This should be obtained from the PRN used for tracking.
    ///
    /// The `gst` parameter gives the GST at the start of the INAV page transmission.
    pub fn feed_inav(&mut self, word: &InavWord, svn: Svn, gst: Gst) {
        self.data.data.navmessage.feed(word, svn, gst);
    }

    /// Feed the OSNMA data message from an INAV page into the OSNMA black box.
    ///
    /// The black box will store the data and potentially trigger any new
    /// cryptographic checks that this data makes possible.
    ///
    /// The `svn` parameter corresponds to the SVN of the satellite transmitting
    /// the INAV word. This should be obtained from the PRN used for tracking.
    ///
    /// The `gst` parameter gives the GST at the start of the INAV page transmission.
    pub fn feed_osnma(&mut self, osnma: &OsnmaDataMessage, svn: Svn, gst: Gst) {
        if osnma.iter().all(|&x| x == 0) {
            // No OSNMA data
            return;
        }
        if let Some((hkroot, mack, subframe_gst)) = self.subframe.feed(osnma, svn, gst) {
            self.data.process_subframe(hkroot, mack, svn, subframe_gst);
        }
    }

    /// Try to get authenticated CED and health status data for a satellite.
    ///
    /// This will try to retrieve the most recent authenticated CED and health
    /// status data (ADKD=0 and 12) for the satellite with SVN `svn` that is
    /// available in the OSNMA storage. If the storage does not contain any
    /// authenticated CED and health status data for this SVN, this returns
    /// `None`.
    pub fn get_ced_and_status(&self, svn: Svn) -> Option<NavMessageData> {
        self.data.data.navmessage.get_ced_and_status(svn)
    }

    /// Try to get authenticated timing parameters for the Galileo constellation.
    ///
    /// This will try to retrieve the most Galileo constellation timing
    /// parameters data (ADKD=4) `svn` that is available in the OSNMA
    /// storage. If the storage does not contain any authenticated timing
    /// parameters data, this returns `None`.
    pub fn get_timing_parameters(&self) -> Option<NavMessageData> {
        self.data.data.navmessage.get_timing_parameters()
    }
}

impl<S: StaticStorage> OsnmaDsm<S> {
    fn process_subframe(&mut self, hkroot: &HkrootMessage, mack: &MackMessage, svn: Svn, gst: Gst) {
        self.data.mack.store(mack, svn, gst);

        let nma_header = &hkroot[..1].try_into().unwrap();
        let nma_header = NmaHeader(nma_header);
        let dsm_header = &hkroot[1..2].try_into().unwrap();
        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.validate_key(mack, gst);
    }
}

impl<S: StaticStorage> OsnmaData<S> {
    fn process_dsm(&mut self, dsm: &[u8], nma_header: NmaHeader) {
        // TODO: handle DSM-PKR
        let dsm_kroot = DsmKroot(dsm);
        match Key::from_dsm_kroot(nma_header, dsm_kroot, &self.pubkey) {
            Ok(key) => {
                log::info!("verified KROOT");
                if self.key.is_none() {
                    self.key = Some(key);
                    log::info!("initializing TESLA key to {:?}", key);
                }
            }
            Err(e) => log::error!("could not verify KROOT: {:?}", e),
        }
    }

    fn validate_key(&mut self, mack: &MackMessage, gst: Gst) {
        let current_key = match self.key {
            Some(k) => k,
            None => {
                log::info!("no valid TESLA key yet. unable to validate MACK key");
                return;
            }
        };
        let mack = Mack::new(
            mack,
            current_key.chain().key_size_bits(),
            current_key.chain().tag_size_bits(),
        );
        let new_key = Key::from_bitslice(mack.key(), gst, current_key.chain());
        match current_key.gst_subframe().cmp(&new_key.gst_subframe()) {
            Ordering::Equal => {
                // we already have this key; nothing to do
            }
            Ordering::Greater => {
                log::warn!(
                    "got a key in MACK which is older than our current valid key\
                            MACK key = {:?}, current valid key = {:?}",
                    new_key,
                    current_key
                );
            }
            Ordering::Less => {
                // attempt to validate the new key
                match current_key.validate_key(&new_key) {
                    Ok(new_valid_key) => {
                        log::info!(
                            "new TESLA key {:?} successfully validated by {:?}",
                            new_valid_key,
                            current_key
                        );
                        self.key = Some(new_valid_key);
                        self.process_tags();
                    }
                    Err(e) => log::error!(
                        "could not validate TESLA key {:?} using {:?}: {:?}",
                        new_key,
                        current_key,
                        e
                    ),
                }
            }
        }
    }

    fn process_tags(&mut self) {
        let current_key = match self.key {
            Some(k) => k,
            None => {
                log::info!("no valid TESLA key yet. unable to validate MACK tags");
                return;
            }
        };
        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);
        for svn in Svn::iter() {
            if !self.only_slowmac {
                if let Some(mack) = self.mack.get(svn, gst_mack) {
                    let mack = Mack::new(
                        mack,
                        current_key.chain().key_size_bits(),
                        current_key.chain().tag_size_bits(),
                    );
                    if let Some(mack) = Self::validate_mack(mack, &current_key, svn, gst_mack) {
                        self.navmessage
                            .process_mack(mack, &current_key, svn, gst_mack);
                    };
                }
            }

            // 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(mack, &current_key, svn, gst_slowmac);
                }
            }
        }
    }

    fn validate_mack<'a>(
        mack: Mack<'a, NotValidated>,
        key: &Key<Validated>,
        prna: Svn,
        gst_mack: Gst,
    ) -> Option<Mack<'a, Validated>> {
        match mack.validate(key, prna, gst_mack) {
            Err(e) => {
                log::error!("error validating MACK {:?}: {:?}", mack, e);
                None
            }
            Ok(m) => Some(m),
        }
    }
}