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}