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
20} // namespace
21
22namespace MAVLinkSigning {
23
24std::optional<SigningKey> makeSigningKey(QByteArrayView bytes)
25{
26 if (bytes.size() != kSigningKeySize) {
27 return std::nullopt;
28 }
29 SigningKey key;
30 memcpy(key.data(), bytes.constData(), kSigningKeySize);
31 return key;
32}
33
34bool secureConnectionAcceptUnsignedCallback(const mavlink_status_t* status, uint32_t message_id)
35{
36 Q_UNUSED(status)
37 return (message_id == MAVLINK_MSG_ID_RADIO_STATUS);
38}
39
40bool insecureConnectionAcceptUnsignedCallback(const mavlink_status_t* status, uint32_t message_id)
41{
42 Q_UNUSED(status)
43 Q_UNUSED(message_id)
44 return true;
45}
46
47namespace {
48
49bool pendingAcceptUnsignedCallback(const mavlink_status_t* status, uint32_t message_id)
50{
51 Q_UNUSED(status)
52 // RADIO_STATUS + HEARTBEAT + STATUSTEXT — observe enable/disable confirmation without leaking data messages.
53 switch (message_id) {
54 case MAVLINK_MSG_ID_RADIO_STATUS:
55 case MAVLINK_MSG_ID_HEARTBEAT:
56 case MAVLINK_MSG_ID_STATUSTEXT:
57 return true;
58 default:
59 return false;
60 }
61}
62
63} // namespace
64
65mavlink_accept_unsigned_t callbackForPolicy(UnsignedAcceptancePolicy policy)
66{
67 switch (policy) {
71 return pendingAcceptUnsignedCallback;
72 }
74}
75
76void createSetupSigning(mavlink_channel_t channel, mavlink_system_t target_system, QByteArrayView keyBytes,
77 mavlink_setup_signing_t& setup_signing)
78{
79 setup_signing = {};
80 setup_signing.target_system = target_system.sysid;
81 setup_signing.target_component = target_system.compid;
82
83 if (!keyBytes.isEmpty() && keyBytes.size() >= static_cast<qsizetype>(sizeof(setup_signing.secret_key))) {
84 // PX4 stores initial_timestamp verbatim (no bump on restart); guarantee we never seed below wall-clock.
85 const mavlink_signing_t* const signing = _channelSigningPtr(channel);
86 const uint64_t channelTs = signing ? signing->timestamp : 0;
87 setup_signing.initial_timestamp = std::max(currentSigningTimestampTicks(), channelTs);
88 memcpy(setup_signing.secret_key, keyBytes.constData(), sizeof(setup_signing.secret_key));
89 }
90}
91
92bool encodeSetupSigning(mavlink_channel_t channel, uint8_t srcSysId, uint8_t srcCompId, mavlink_system_t target_system,
93 QByteArrayView keyBytes, mavlink_message_t& message)
94{
95 if (!mavlink_get_channel_status(channel)) {
96 return false;
97 }
99 createSetupSigning(channel, target_system, keyBytes, payload);
100 (void)mavlink_msg_setup_signing_encode_chan(srcSysId, srcCompId, channel, &message, &payload);
101 return true;
102}
103
105{
106 return (message.incompat_flags & MAVLINK_IFLAG_SIGNED) != 0;
107}
108
109void setMessageSigned(mavlink_message_t& message, bool isSigned)
110{
111 if (isSigned) {
112 message.incompat_flags |= MAVLINK_IFLAG_SIGNED;
113 } else {
114 message.incompat_flags &= static_cast<uint8_t>(~MAVLINK_IFLAG_SIGNED);
115 }
116}
117
118QByteArray serializeUnsignedCopy(const mavlink_message_t& message)
119{
120 mavlink_message_t copy = message;
121
122 if (copy.magic == MAVLINK_STX) {
123 copy.incompat_flags &= static_cast<uint8_t>(~MAVLINK_IFLAG_SIGNED);
124
125 // Replicates mavlink_finalize_message_buffer; assert fails loudly if libmavlink header layout drifts.
126 static_assert(MAVLINK_CORE_HEADER_LEN == 9, "MAVLink2 core header layout changed — update CRC recomputation");
127 uint8_t header[MAVLINK_CORE_HEADER_LEN];
128 header[0] = copy.len;
129 header[1] = copy.incompat_flags;
130 header[2] = copy.compat_flags;
131 header[3] = copy.seq;
132 header[4] = copy.sysid;
133 header[5] = copy.compid;
134 header[6] = static_cast<uint8_t>(copy.msgid & 0xFF);
135 header[7] = static_cast<uint8_t>((copy.msgid >> 8) & 0xFF);
136 header[8] = static_cast<uint8_t>((copy.msgid >> 16) & 0xFF);
137
138 uint16_t checksum = crc_calculate(header, MAVLINK_CORE_HEADER_LEN);
139 crc_accumulate_buffer(&checksum, _MAV_PAYLOAD(&copy), copy.len);
140 crc_accumulate(mavlink_get_crc_extra(&copy), &checksum);
141
142 copy.checksum = checksum;
143 mavlink_ck_a(&copy) = static_cast<uint8_t>(checksum & 0xFF);
144 mavlink_ck_b(&copy) = static_cast<uint8_t>(checksum >> 8);
145 }
146
147 QByteArray buf(MAVLINK_MAX_PACKET_LEN, Qt::Uninitialized);
148 const uint16_t len = mavlink_msg_to_send_buffer(reinterpret_cast<uint8_t*>(buf.data()), &copy);
149 buf.resize(len);
150 return buf;
151}
152
153bool verifySignature(QByteArrayView key, const mavlink_message_t& message)
154{
155 if (key.size() < kSigningKeySize) {
156 return false;
157 }
158
159 // C lib signature: SHA-256(secret_key + header_bytes + payload + CRC + link_id + timestamp).
160 const uint8_t* header = reinterpret_cast<const uint8_t*>(&message.magic);
161 const char* payload = _MAV_PAYLOAD(&message);
162 const uint8_t* sig = message.signature;
163 const uint8_t crc[2] = {static_cast<uint8_t>(message.checksum & 0xFF), static_cast<uint8_t>(message.checksum >> 8)};
164
165 const QByteArrayView parts[] = {
166 key.first(kSigningKeySize),
167 QByteArrayView(reinterpret_cast<const char*>(header), MAVLINK_NUM_HEADER_BYTES),
168 QByteArrayView(payload, message.len),
169 QByteArrayView(reinterpret_cast<const char*>(crc), sizeof(crc)),
170 QByteArrayView(reinterpret_cast<const char*>(sig), kSignaturePrefixBytes),
171 };
172 uchar hashBuf[kSigningKeySize];
173 const auto hash = QCryptographicHash::hashInto(QSpan<uchar>(hashBuf), QSpan<const QByteArrayView>(parts),
174 QCryptographicHash::Sha256);
175
176 return hash.size() >= kSignatureHashBytes &&
177 memcmp(hash.constData(), sig + kSignaturePrefixBytes, kSignatureHashBytes) == 0;
178}
179
180bool verifySignature(const SigningKey& key, const mavlink_message_t& message)
181{
182 return verifySignature(QByteArrayView(reinterpret_cast<const char*>(key.data()), key.size()), message);
183}
184
185} // namespace MAVLinkSigning
186
187namespace MAVLinkSigning {
188
190{
191 const mavlink_signing_t* const signing = _channelSigningPtr(channel);
192 if (!signing) {
193 qCWarning(MAVLinkSigningLog) << "checkSigningLinkId: no signing struct on channel" << channel;
194 return false;
195 }
196 return (signing->link_id == static_cast<mavlink_channel_t>(message.signature[0]));
197}
198
200{
201 const mavlink_status_t* const status = mavlink_get_channel_status(channel);
202 const mavlink_signing_status_t last =
203 (status && status->signing) ? status->signing->last_status : MAVLINK_SIGNING_STATUS_NONE;
204 switch (last) {
205 case MAVLINK_SIGNING_STATUS_OK:
206 return QStringLiteral("OK");
207 case MAVLINK_SIGNING_STATUS_BAD_SIGNATURE:
208 return QStringLiteral("Bad Signature");
209 case MAVLINK_SIGNING_STATUS_NO_STREAMS:
210 return QStringLiteral("No Streams");
211 case MAVLINK_SIGNING_STATUS_TOO_MANY_STREAMS:
212 return QStringLiteral("Too Many Streams");
213 case MAVLINK_SIGNING_STATUS_OLD_TIMESTAMP:
214 return QStringLiteral("Stale Timestamp");
215 case MAVLINK_SIGNING_STATUS_REPLAY:
216 return QStringLiteral("Replay Detected");
217 case MAVLINK_SIGNING_STATUS_NONE:
218 default:
219 return QString();
220 }
221}
222
224{
225 const mavlink_status_t* const status = mavlink_get_channel_status(channel);
226 if (!status || !status->signing_streams) {
227 return 0;
228 }
229 return status->signing_streams->num_signing_streams;
230}
231
233{
234 const mavlink_status_t* const status = mavlink_get_channel_status(channel);
235 const mavlink_signing_status_t last =
236 (status && status->signing) ? status->signing->last_status : MAVLINK_SIGNING_STATUS_NONE;
237 switch (last) {
238 case MAVLINK_SIGNING_STATUS_BAD_SIGNATURE:
239 qCWarning(MAVLinkSigningLog) << "Channel" << channel << "signing failure: bad signature (key mismatch)";
240 break;
241 case MAVLINK_SIGNING_STATUS_NO_STREAMS:
242 qCWarning(MAVLinkSigningLog) << "Channel" << channel << "signing failure: no signing streams table";
243 break;
244 case MAVLINK_SIGNING_STATUS_TOO_MANY_STREAMS:
245 qCWarning(MAVLinkSigningLog) << "Channel" << channel << "signing failure: stream table full (>"
246 << MAVLINK_MAX_SIGNING_STREAMS << "streams)";
247 break;
248 case MAVLINK_SIGNING_STATUS_OLD_TIMESTAMP:
249 qCWarning(MAVLinkSigningLog) << "Channel" << channel
250 << "signing failure: new stream with stale timestamp (>"
251 << MAVLINK_SIGNING_TIMESTAMP_LIMIT << "s old)";
252 break;
253 case MAVLINK_SIGNING_STATUS_REPLAY:
254 qCWarning(MAVLinkSigningLog) << "Channel" << channel
255 << "signing failure: replay detected (repeated/old timestamp)";
256 break;
257 case MAVLINK_SIGNING_STATUS_OK:
258 case MAVLINK_SIGNING_STATUS_NONE:
259 default:
260 break;
261 }
262}
263
264} // 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)
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)