QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
MAVLinkSigning.cc
Go to the documentation of this file.
1#include "MAVLinkSigning.h"
2
3#include <QtCore/QCryptographicHash>
4#include <QtCore/QDateTime>
5#include <algorithm>
6
7#include "MAVLinkLib.h"
9
10QGC_LOGGING_CATEGORY(MAVLinkSigningLog, "MAVLink.MAVLinkSigning")
11
12namespace {
13
14const mavlink_signing_t* _channelSigningPtr(mavlink_channel_t channel)
15{
16 const mavlink_status_t* const status = mavlink_get_channel_status(channel);
17 return status ? status->signing : nullptr;
18}
19
20mavlink_signing_status_t _lastSigningStatus(mavlink_channel_t channel)
21{
22 const mavlink_signing_t* const signing = _channelSigningPtr(channel);
23 return signing ? signing->last_status : MAVLINK_SIGNING_STATUS_NONE;
24}
25
26} // namespace
27
28namespace MAVLinkSigning {
29
30std::optional<SigningKey> makeSigningKey(QByteArrayView bytes)
31{
32 if (bytes.size() != kSigningKeySize) {
33 return std::nullopt;
34 }
35 SigningKey key;
36 memcpy(key.data(), bytes.constData(), kSigningKeySize);
37 return key;
38}
39
40bool secureConnectionAcceptUnsignedCallback(const mavlink_status_t* status, uint32_t message_id)
41{
42 Q_UNUSED(status)
43 return (message_id == MAVLINK_MSG_ID_RADIO_STATUS);
44}
45
46bool insecureConnectionAcceptUnsignedCallback(const mavlink_status_t* status, uint32_t message_id)
47{
48 Q_UNUSED(status)
49 Q_UNUSED(message_id)
50 return true;
51}
52
53namespace {
54
55bool pendingAcceptUnsignedCallback(const mavlink_status_t* status, uint32_t message_id)
56{
57 Q_UNUSED(status)
58 // RADIO_STATUS + HEARTBEAT + STATUSTEXT — observe enable/disable confirmation without leaking data messages.
59 switch (message_id) {
60 case MAVLINK_MSG_ID_RADIO_STATUS:
61 case MAVLINK_MSG_ID_HEARTBEAT:
62 case MAVLINK_MSG_ID_STATUSTEXT:
63 return true;
64 default:
65 return false;
66 }
67}
68
69} // namespace
70
71mavlink_accept_unsigned_t callbackForPolicy(UnsignedAcceptancePolicy policy)
72{
73 switch (policy) {
77 return pendingAcceptUnsignedCallback;
78 }
80}
81
82void createSetupSigning(mavlink_channel_t channel, mavlink_system_t target_system, QByteArrayView keyBytes,
83 mavlink_setup_signing_t& setup_signing)
84{
85 setup_signing = {};
86 setup_signing.target_system = target_system.sysid;
87 setup_signing.target_component = target_system.compid;
88
89 if (!keyBytes.isEmpty() && keyBytes.size() >= static_cast<qsizetype>(sizeof(setup_signing.secret_key))) {
90 // PX4 stores initial_timestamp verbatim (no bump on restart); guarantee we never seed below wall-clock.
91 const mavlink_signing_t* const signing = _channelSigningPtr(channel);
92 const uint64_t channelTs = signing ? signing->timestamp : 0;
93 setup_signing.initial_timestamp = std::max(currentSigningTimestampTicks(), channelTs);
94 memcpy(setup_signing.secret_key, keyBytes.constData(), sizeof(setup_signing.secret_key));
95 }
96}
97
98bool encodeSetupSigning(mavlink_channel_t channel, uint8_t srcSysId, uint8_t srcCompId, mavlink_system_t target_system,
99 QByteArrayView keyBytes, mavlink_message_t& message)
100{
101 if (!mavlink_get_channel_status(channel)) {
102 return false;
103 }
105 createSetupSigning(channel, target_system, keyBytes, payload);
106 (void)mavlink_msg_setup_signing_encode_chan(srcSysId, srcCompId, channel, &message, &payload);
107 return true;
108}
109
111{
112 return (message.incompat_flags & MAVLINK_IFLAG_SIGNED) != 0;
113}
114
115void setMessageSigned(mavlink_message_t& message, bool isSigned)
116{
117 if (isSigned) {
118 message.incompat_flags |= MAVLINK_IFLAG_SIGNED;
119 } else {
120 message.incompat_flags &= static_cast<uint8_t>(~MAVLINK_IFLAG_SIGNED);
121 }
122}
123
124QByteArray serializeUnsignedCopy(const mavlink_message_t& message)
125{
126 mavlink_message_t copy = message;
127
128 if (copy.magic == MAVLINK_STX) {
129 copy.incompat_flags &= static_cast<uint8_t>(~MAVLINK_IFLAG_SIGNED);
130
131 // Replicates mavlink_finalize_message_buffer; assert fails loudly if libmavlink header layout drifts.
132 static_assert(MAVLINK_CORE_HEADER_LEN == 9, "MAVLink2 core header layout changed — update CRC recomputation");
133 uint8_t header[MAVLINK_CORE_HEADER_LEN];
134 header[0] = copy.len;
135 header[1] = copy.incompat_flags;
136 header[2] = copy.compat_flags;
137 header[3] = copy.seq;
138 header[4] = copy.sysid;
139 header[5] = copy.compid;
140 header[6] = static_cast<uint8_t>(copy.msgid & 0xFF);
141 header[7] = static_cast<uint8_t>((copy.msgid >> 8) & 0xFF);
142 header[8] = static_cast<uint8_t>((copy.msgid >> 16) & 0xFF);
143
144 uint16_t checksum = crc_calculate(header, MAVLINK_CORE_HEADER_LEN);
145 crc_accumulate_buffer(&checksum, _MAV_PAYLOAD(&copy), copy.len);
146 crc_accumulate(mavlink_get_crc_extra(&copy), &checksum);
147
148 copy.checksum = checksum;
149 mavlink_ck_a(&copy) = static_cast<uint8_t>(checksum & 0xFF);
150 mavlink_ck_b(&copy) = static_cast<uint8_t>(checksum >> 8);
151 }
152
153 QByteArray buf(MAVLINK_MAX_PACKET_LEN, Qt::Uninitialized);
154 const uint16_t len = mavlink_msg_to_send_buffer(reinterpret_cast<uint8_t*>(buf.data()), &copy);
155 buf.resize(len);
156 return buf;
157}
158
159namespace {
160
164void _computeSignatureHash(QByteArrayView key, const mavlink_message_t& message, uchar (&hashBuf)[kSigningKeySize])
165{
166 const uint8_t* header = reinterpret_cast<const uint8_t*>(&message.magic);
167 const char* payload = _MAV_PAYLOAD(&message);
168 const uint8_t* sig = message.signature;
169 const uint8_t crc[2] = {static_cast<uint8_t>(message.checksum & 0xFF), static_cast<uint8_t>(message.checksum >> 8)};
170
171 const QByteArrayView parts[] = {
172 key.first(kSigningKeySize),
173 QByteArrayView(reinterpret_cast<const char*>(header), MAVLINK_NUM_HEADER_BYTES),
174 QByteArrayView(payload, message.len),
175 QByteArrayView(reinterpret_cast<const char*>(crc), sizeof(crc)),
176 QByteArrayView(reinterpret_cast<const char*>(sig), kSignaturePrefixBytes),
177 };
178 (void) QCryptographicHash::hashInto(QSpan<uchar>(hashBuf), QSpan<const QByteArrayView>(parts),
179 QCryptographicHash::Sha256);
180}
181
182} // namespace
183
184bool verifySignature(QByteArrayView key, const mavlink_message_t& message)
185{
186 if (key.size() < kSigningKeySize) {
187 return false;
188 }
189
190 uchar hashBuf[kSigningKeySize];
191 _computeSignatureHash(key, message, hashBuf);
192
193 return memcmp(hashBuf, message.signature + kSignaturePrefixBytes, kSignatureHashBytes) == 0;
194}
195
196void signMessage(QByteArrayView key, uint8_t linkId, uint64_t timestamp, mavlink_message_t& message)
197{
198 if (key.size() < kSigningKeySize) {
199 return;
200 }
201
202 // Populate link_id + 48-bit little-endian timestamp before hashing — they are part of the signed bytes.
203 static constexpr int kTimestampBytes = kSignaturePrefixBytes - 1; // link_id(1) + timestamp(6) = prefix(7)
204 message.signature[0] = linkId;
205 for (int i = 0; i < kTimestampBytes; ++i) {
206 message.signature[1 + i] = static_cast<uint8_t>((timestamp >> (8 * i)) & 0xFF);
207 }
208
209 uchar hashBuf[kSigningKeySize];
210 _computeSignatureHash(key, message, hashBuf);
211 memcpy(message.signature + kSignaturePrefixBytes, hashBuf, kSignatureHashBytes);
212
213 setMessageSigned(message, true);
214}
215
216bool verifySignature(const SigningKey& key, const mavlink_message_t& message)
217{
218 return verifySignature(QByteArrayView(reinterpret_cast<const char*>(key.data()), key.size()), message);
219}
220
222{
223 const mavlink_signing_t* const signing = _channelSigningPtr(channel);
224 if (!signing) {
225 qCWarning(MAVLinkSigningLog) << "checkSigningLinkId: no signing struct on channel" << channel;
226 return false;
227 }
228 return (signing->link_id == static_cast<mavlink_channel_t>(message.signature[0]));
229}
230
232{
233 switch (_lastSigningStatus(channel)) {
234 case MAVLINK_SIGNING_STATUS_OK:
235 return QStringLiteral("OK");
236 case MAVLINK_SIGNING_STATUS_BAD_SIGNATURE:
237 return QStringLiteral("Bad Signature");
238 case MAVLINK_SIGNING_STATUS_NO_STREAMS:
239 return QStringLiteral("No Streams");
240 case MAVLINK_SIGNING_STATUS_TOO_MANY_STREAMS:
241 return QStringLiteral("Too Many Streams");
242 case MAVLINK_SIGNING_STATUS_OLD_TIMESTAMP:
243 return QStringLiteral("Stale Timestamp");
244 case MAVLINK_SIGNING_STATUS_REPLAY:
245 return QStringLiteral("Replay Detected");
246 case MAVLINK_SIGNING_STATUS_NONE:
247 default:
248 return QString();
249 }
250}
251
253{
254 const mavlink_status_t* const status = mavlink_get_channel_status(channel);
255 if (!status || !status->signing_streams) {
256 return 0;
257 }
258 return status->signing_streams->num_signing_streams;
259}
260
262{
263 switch (_lastSigningStatus(channel)) {
264 case MAVLINK_SIGNING_STATUS_BAD_SIGNATURE:
265 qCWarning(MAVLinkSigningLog) << "Channel" << channel << "signing failure: bad signature (key mismatch)";
266 break;
267 case MAVLINK_SIGNING_STATUS_NO_STREAMS:
268 qCWarning(MAVLinkSigningLog) << "Channel" << channel << "signing failure: no signing streams table";
269 break;
270 case MAVLINK_SIGNING_STATUS_TOO_MANY_STREAMS:
271 qCWarning(MAVLinkSigningLog) << "Channel" << channel << "signing failure: stream table full (>"
272 << MAVLINK_MAX_SIGNING_STREAMS << "streams)";
273 break;
274 case MAVLINK_SIGNING_STATUS_OLD_TIMESTAMP:
275 qCWarning(MAVLinkSigningLog) << "Channel" << channel
276 << "signing failure: new stream with stale timestamp (>"
277 << MAVLINK_SIGNING_TIMESTAMP_LIMIT << "s old)";
278 break;
279 case MAVLINK_SIGNING_STATUS_REPLAY:
280 qCWarning(MAVLinkSigningLog) << "Channel" << channel
281 << "signing failure: replay detected (repeated/old timestamp)";
282 break;
283 case MAVLINK_SIGNING_STATUS_OK:
284 case MAVLINK_SIGNING_STATUS_NONE:
285 default:
286 break;
287 }
288}
289
290} // namespace MAVLinkSigning
mavlink_channel_t
#define MAVLINK_MAX_SIGNING_STREAMS
mavlink_status_t * mavlink_get_channel_status(uint8_t chan)
Definition QGCMAVLink.cc:53
struct __mavlink_setup_signing_t mavlink_setup_signing_t
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
QByteArray serializeUnsignedCopy(const mavlink_message_t &message)
static constexpr int kSignatureHashBytes
bool checkSigningLinkId(mavlink_channel_t channel, const mavlink_message_t &message)
bool encodeSetupSigning(mavlink_channel_t channel, uint8_t srcSysId, uint8_t srcCompId, mavlink_system_t target_system, QByteArrayView keyBytes, mavlink_message_t &message)
std::array< uint8_t, kSigningKeySize > SigningKey
std::array avoids QByteArray COW detach so secureZero() actually wipes the bytes.
QString signingStatusString(mavlink_channel_t channel)
void signMessage(QByteArrayView key, uint8_t linkId, uint64_t timestamp, mavlink_message_t &message)
bool verifySignature(QByteArrayView key, const mavlink_message_t &message)
Verify a key against a signed message's signature.
uint64_t currentSigningTimestampTicks()
Current signing timestamp in 10µs ticks since 2015-01-01.
void createSetupSigning(mavlink_channel_t channel, mavlink_system_t target_system, QByteArrayView keyBytes, mavlink_setup_signing_t &setup_signing)
Build a SETUP_SIGNING payload. Empty keyBytes produces a disable payload (zero key,...
bool secureConnectionAcceptUnsignedCallback(const mavlink_status_t *status, uint32_t message_id)
std::optional< SigningKey > makeSigningKey(QByteArrayView bytes)
Build a SigningKey from arbitrary bytes. Returns nullopt if input is the wrong size.
bool isMessageSigned(const mavlink_message_t &message)
Returns true if the message has a MAVLink2 signature.
mavlink_accept_unsigned_t callbackForPolicy(UnsignedAcceptancePolicy policy)
Maps a high-level policy to the underlying libmavlink callback.
void logSigningFailure(mavlink_channel_t channel)
static constexpr int kSigningKeySize
void setMessageSigned(mavlink_message_t &message, bool isSigned)
Set or clear the MAVLink2 signature incompatibility flag on a message.
static constexpr int kSignaturePrefixBytes
bool insecureConnectionAcceptUnsignedCallback(const mavlink_status_t *status, uint32_t message_id)
int signingStreamCount(mavlink_channel_t channel)