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