QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
SigningChannel.cc
Go to the documentation of this file.
1#include "SigningChannel.h"
2
3#include <algorithm>
4#include <cstring>
5
6#include "MAVLinkSigning.h"
8#include "SecureMemory.h"
9
10QGC_LOGGING_CATEGORY(SigningChannelLog, "MAVLink.SigningChannel")
11
12bool SigningChannel::init(mavlink_channel_t channel, QByteArrayView key, mavlink_accept_unsigned_t callback,
13 uint64_t persistedTimestamp, const QString& keyName, bool signOutgoing)
14{
15 if (!key.isEmpty() && !callback) {
16 qCWarning(SigningChannelLog) << "[ch" << channel << "] callback must be specified when enabling";
17 return false;
18 }
19
20 mavlink_status_t* const status = mavlink_get_channel_status(channel);
21 if (!status) {
22 qCWarning(SigningChannelLog) << "[ch" << channel << "] invalid channel (no MAVLink status)";
23 return false;
24 }
25
26 QWriteLocker locker(&_lock);
27
28 if (key.isEmpty()) {
29 // Detach C library before wiping our struct so framing won't read freed/zeroed pointers mid-update.
30 status->signing = nullptr;
31 status->signing_streams = nullptr;
32 QGC::secureZero(_signing.secret_key, sizeof(_signing.secret_key));
33 _signing.accept_unsigned_callback = nullptr;
34 _streams = {};
35 _keyHint.clear();
36 _enabled = false;
37 return true;
38 }
39
40 if (key.size() < static_cast<qsizetype>(sizeof(_signing.secret_key))) {
41 qCWarning(SigningChannelLog) << "[ch" << channel << "] key too short:" << key.size() << "bytes (need"
42 << sizeof(_signing.secret_key) << ")";
43 return false;
44 }
45
46 // Reset stream table — stale entries from a prior key/session would cause OLD_TIMESTAMP rejections.
47 _streams = {};
48
49 _signing.link_id = static_cast<uint8_t>(channel);
50 _signing.flags = signOutgoing ? MAVLINK_SIGNING_FLAG_SIGN_OUTGOING : 0;
51 _signing.accept_unsigned_callback = callback;
52 memcpy(_signing.secret_key, key.constData(), sizeof(_signing.secret_key));
53 // Persisted+bump is the real defense (wall clock is non-monotonic under NTP/DST/suspend).
54 _signing.timestamp = std::max(MAVLinkSigning::currentSigningTimestampTicks(),
55 persistedTimestamp + kPersistedTimestampSafetyBumpTicks);
56 _keyHint = keyName;
57
58 status->signing = &_signing;
59 status->signing_streams = &_streams;
60 _enabled = true;
61 return true;
62}
63
65{
66 QWriteLocker locker(&_lock);
67 if (!_enabled) {
68 return false;
69 }
71 if (now <= _signing.timestamp) {
72 return false;
73 }
74 _signing.timestamp = now;
75 return true;
76}
77
79{
80 QWriteLocker locker(&_lock);
81 if (!_enabled || !(_signing.flags & MAVLINK_SIGNING_FLAG_SIGN_OUTGOING)) {
82 return false;
83 }
84 // Keep monotonic AND current: libmavlink only post-increments per packet, so an idle/cached path otherwise drifts.
85 _signing.timestamp = std::max(_signing.timestamp, MAVLinkSigning::currentSigningTimestampTicks());
86 const QByteArrayView key(reinterpret_cast<const char*>(_signing.secret_key), sizeof(_signing.secret_key));
87 MAVLinkSigning::signMessage(key, _signing.link_id, _signing.timestamp, message);
88 // Match libmavlink's mavlink_sign_packet: post-increment so the next packet never reuses this timestamp.
89 ++_signing.timestamp;
90 return true;
91}
92
94{
95 QReadLocker locker(&_lock);
96 if (!_enabled) {
97 return {0, QString()};
98 }
99 return {_signing.timestamp, _keyHint};
100}
101
102bool SigningChannel::setAcceptUnsignedCallback(mavlink_accept_unsigned_t callback)
103{
104 QWriteLocker locker(&_lock);
105 if (!_enabled) {
106 return false;
107 }
108 _signing.accept_unsigned_callback = callback;
109 return true;
110}
111
112bool SigningChannel::setSignOutgoing(bool signOutgoing)
113{
114 QWriteLocker locker(&_lock);
115 if (!_enabled) {
116 return false;
117 }
118 if (signOutgoing) {
119 _signing.flags |= MAVLINK_SIGNING_FLAG_SIGN_OUTGOING;
120 } else {
121 _signing.flags &= static_cast<uint8_t>(~MAVLINK_SIGNING_FLAG_SIGN_OUTGOING);
122 }
123 return true;
124}
125
127{
128 QReadLocker locker(&_lock);
129 return _enabled;
130}
131
133{
134 QReadLocker locker(&_lock);
135 return static_cast<int>(_streams.num_signing_streams);
136}
137
139{
140 QReadLocker locker(&_lock);
141 return _keyHint;
142}
143
144void SigningChannel::setKeyHint(const QString& name)
145{
146 QWriteLocker locker(&_lock);
147 _keyHint = name;
148}
149
151{
152 return _autoDetectSuspended.load(std::memory_order_acquire);
153}
154
156{
157 QReadLocker locker(&_lock);
158 return !_detectCooldown.hasExpired();
159}
160
162{
163 QWriteLocker locker(&_lock);
164 _detectCooldown.setRemainingTime(kDetectCooldownMs);
165}
166
168{
169 QWriteLocker locker(&_lock);
170 _detectCooldown.setRemainingTime(0);
171}
172
174{
175 QWriteLocker locker(&_lock);
176 _keyHint.clear();
177}
178
180{
181 // _autoDetectSuspended atomic read outside lock; only keyHint/cooldown pair must be coherent.
183 snap.autoDetectSuspended = _autoDetectSuspended.load(std::memory_order_acquire);
184 {
185 QReadLocker locker(&_lock);
186 snap.keyHint = _keyHint;
187 snap.inCooldown = !_detectCooldown.hasExpired();
188 }
189 return snap;
190}
191
193{
194 QWriteLocker locker(&_lock);
195 const mavlink_status_t* const status = mavlink_get_channel_status(channel);
196 const mavlink_signing_status_t current =
197 (status && status->signing) ? status->signing->last_status : MAVLINK_SIGNING_STATUS_NONE;
198 if (current == _lastTransitionStatus) {
199 return false;
200 }
201 _lastTransitionStatus = current;
202 return true;
203}
mavlink_channel_t
mavlink_status_t * mavlink_get_channel_status(uint8_t chan)
Definition QGCMAVLink.cc:53
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Owns MAVLink signing state for one channel: signing/streams structs, key hint, and RW lock.
bool refreshOutgoingTimestamp()
bool signOutgoing(mavlink_message_t &message)
QString keyHint() const
void setKeyHint(const QString &name)
bool setAcceptUnsignedCallback(mavlink_accept_unsigned_t callback)
Swap the accept-unsigned callback without resetting the key. Returns false if signing isn't enabled.
MAVLinkSigning::DetectSnapshot detectSnapshot() const
Single-lock snapshot; 3 separate reads have TOCTOU window vs MockLink's thread.
int streamCount() const
bool isEnabled() const
bool isAutoDetectSuspended() const
While suspended, tryDetectKey is suppressed to block stale-key installs during pending enable.
bool consumeStatusTransition(mavlink_channel_t channel)
True if last_status changed since previous call; sole transition-detection source.
bool isInDetectCooldown() const
Throttles detect misses; HMAC per packet per key is expensive. Monotonic timer to avoid wall-clock sk...
static constexpr qint64 kDetectCooldownMs
TimestampSnapshot currentTimestampAndName() const
Returns current timestamp and active key name. Returns {0, ""} when signing is not enabled.
void signMessage(QByteArrayView key, uint8_t linkId, uint64_t timestamp, mavlink_message_t &message)
uint64_t currentSigningTimestampTicks()
Current signing timestamp in 10µs ticks since 2015-01-01.
void secureZero(void *data, size_t size)
Single-lock snapshot struct; fields populated by SigningChannel::detectSnapshot().