Skip to main content

mavlink_core/
signing.rs

1use crate::{MAVLinkV2MessageRaw, consts};
2
3use std::time::SystemTime;
4use std::{collections::HashMap, sync::Mutex};
5
6/// Configuration used for MAVLink 2 messages signing as defined in <https://mavlink.io/en/guide/message_signing.html>.
7///
8/// To use a [`SigningConfig`] for sending and reciving messages create a [`SigningData`] object using `SigningData::from_config`.
9///
10/// # Examples
11/// Creating `SigningData`:
12/// ```
13/// # use mavlink_core::{SigningData, SigningConfig};
14/// let config = SigningConfig::new([0u8; 32], 0, true, false);
15/// let sign_data = SigningData::from_config(config);
16/// ```
17///
18#[derive(Debug, Clone)]
19pub struct SigningConfig {
20    secret_key: [u8; 32],
21    link_id: u8,
22    pub(crate) sign_outgoing: bool,
23    pub(crate) allow_unsigned: bool,
24}
25
26// mutable state of signing per connection
27pub(crate) struct SigningState {
28    timestamp: u64,
29    stream_timestamps: HashMap<(u8, u8, u8), u64>,
30}
31
32/// MAVLink 2 message signing data
33///
34/// Contains a [`SigningConfig`] as well as a mutable state that is reused for all messages in a connection.  
35pub struct SigningData {
36    pub(crate) config: SigningConfig,
37    pub(crate) state: Mutex<SigningState>,
38}
39
40impl SigningConfig {
41    /// Creates a new signing configuration.
42    ///
43    /// If `sign_outgoing` is set messages send using this configuration will be signed.
44    /// If `allow_unsigned` is set, when receiving messages, all unsigned messages are accepted, this may also includes MAVLink 1 messages.
45    pub fn new(
46        secret_key: [u8; 32],
47        link_id: u8,
48        sign_outgoing: bool,
49        allow_unsigned: bool,
50    ) -> Self {
51        Self {
52            secret_key,
53            link_id,
54            sign_outgoing,
55            allow_unsigned,
56        }
57    }
58}
59
60impl SigningData {
61    /// Initializes signing data from a given [`SigningConfig`]
62    pub fn from_config(config: SigningConfig) -> Self {
63        Self {
64            config,
65            state: Mutex::new(SigningState {
66                timestamp: 0,
67                stream_timestamps: HashMap::new(),
68            }),
69        }
70    }
71
72    /// Verify the signature of a MAVLink 2 message.
73    ///
74    /// This respects the `allow_unsigned` parameter in [`SigningConfig`].
75    pub fn verify_signature(&self, message: &MAVLinkV2MessageRaw) -> bool {
76        if message.incompatibility_flags() & consts::v2::IFLAG_SIGNED > 0 {
77            // The code that holds the mutex lock is not expected to panic, therefore the expect is justified.
78            // The only issue that might cause a panic, presuming the opertions on the message buffer are sound,
79            // is the `SystemTime::now()` call in `get_current_timestamp()`.
80            let mut state = self
81                .state
82                .lock()
83                .expect("Code holding MutexGuard should not panic.");
84            state.timestamp = u64::max(state.timestamp, Self::get_current_timestamp());
85            let timestamp = message.signature_timestamp();
86            let src_system = message.system_id();
87            let src_component = message.component_id();
88            let stream_key = (message.signature_link_id(), src_system, src_component);
89            match state.stream_timestamps.get(&stream_key) {
90                Some(stream_timestamp) => {
91                    if timestamp <= *stream_timestamp {
92                        // reject old timestamp
93                        return false;
94                    }
95                }
96                None => {
97                    if timestamp + 60 * 1000 * 100 < state.timestamp {
98                        // bad new stream, more then a minute older the the last one
99                        return false;
100                    }
101                }
102            }
103
104            let mut signature_buffer = [0u8; consts::v2::SIGNATURE_VALUE_SIZE];
105            message.calculate_signature(&self.config.secret_key, &mut signature_buffer);
106            let result = signature_buffer == message.signature_value();
107            if result {
108                // if signature is valid update timestamps
109                state.stream_timestamps.insert(stream_key, timestamp);
110                state.timestamp = u64::max(state.timestamp, timestamp);
111            }
112            result
113        } else {
114            self.config.allow_unsigned
115        }
116    }
117
118    /// Sign a MAVLink 2 message if its incompatibility flag is set accordingly.
119    pub fn sign_message(&self, message: &mut MAVLinkV2MessageRaw) {
120        if message.incompatibility_flags() & consts::v2::IFLAG_SIGNED > 0 {
121            // The code that holds the mutex lock is not expected to panic, therefore the expect is justified.
122            // The only issue that might cause a panic, presuming the opertions on the message buffer are sound,
123            // is the `SystemTime::now()` call in `get_current_timestamp()`.
124            let mut state = self
125                .state
126                .lock()
127                .expect("Code holding MutexGuard should not panic.");
128            state.timestamp = u64::max(state.timestamp, Self::get_current_timestamp());
129            let ts_bytes = u64::to_le_bytes(state.timestamp);
130            message
131                .signature_timestamp_bytes_mut()
132                .copy_from_slice(&ts_bytes[0..consts::v2::SIGNATURE_TIMESTAMP_SIZE]);
133            *message.signature_link_id_mut() = self.config.link_id;
134
135            let mut signature_buffer = [0u8; consts::v2::SIGNATURE_VALUE_SIZE];
136            message.calculate_signature(&self.config.secret_key, &mut signature_buffer);
137
138            message
139                .signature_value_mut()
140                .copy_from_slice(&signature_buffer);
141            state.timestamp += 1;
142        }
143    }
144
145    fn get_current_timestamp() -> u64 {
146        // fallback to 0 if the system time appears to be before epoch
147        let now = SystemTime::now()
148            .duration_since(SystemTime::UNIX_EPOCH)
149            .map(|n| n.as_micros())
150            .unwrap_or(0);
151        // use 1st January 2015 GMT as offset, fallback to 0 if before that date, the used 48 bit of this will overflow in 2104
152        (now.saturating_sub(1420070400u128 * 1000000u128) / 10u128) as u64
153    }
154}