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 QReadLocker locker(&_lock);
67 if (!_enabled) {
68 return {0, QString()};
69 }
70 return {_signing.timestamp, _keyHint};
71}
72
73bool SigningChannel::setAcceptUnsignedCallback(mavlink_accept_unsigned_t callback)
74{
75 QWriteLocker locker(&_lock);
76 if (!_enabled) {
77 return false;
78 }
79 _signing.accept_unsigned_callback = callback;
80 return true;
81}
82
83bool SigningChannel::setSignOutgoing(bool signOutgoing)
84{
85 QWriteLocker locker(&_lock);
86 if (!_enabled) {
87 return false;
88 }
89 if (signOutgoing) {
90 _signing.flags |= MAVLINK_SIGNING_FLAG_SIGN_OUTGOING;
91 } else {
92 _signing.flags &= static_cast<uint8_t>(~MAVLINK_SIGNING_FLAG_SIGN_OUTGOING);
93 }
94 return true;
95}
96
98{
99 QReadLocker locker(&_lock);
100 return _enabled;
101}
102
104{
105 QReadLocker locker(&_lock);
106 return static_cast<int>(_streams.num_signing_streams);
107}
108
110{
111 QReadLocker locker(&_lock);
112 return _keyHint;
113}
114
115void SigningChannel::setKeyHint(const QString& name)
116{
117 QWriteLocker locker(&_lock);
118 _keyHint = name;
119}
120
122{
123 return _autoDetectSuspended.load(std::memory_order_acquire);
124}
125
127{
128 QReadLocker locker(&_lock);
129 return !_detectCooldown.hasExpired();
130}
131
133{
134 QWriteLocker locker(&_lock);
135 _detectCooldown.setRemainingTime(kDetectCooldownMs);
136}
137
139{
140 QWriteLocker locker(&_lock);
141 _detectCooldown.setRemainingTime(0);
142}
143
145{
146 QWriteLocker locker(&_lock);
147 _keyHint.clear();
148}
149
151{
152 // _autoDetectSuspended atomic read outside lock; only keyHint/cooldown pair must be coherent.
154 snap.autoDetectSuspended = _autoDetectSuspended.load(std::memory_order_acquire);
155 {
156 QReadLocker locker(&_lock);
157 snap.keyHint = _keyHint;
158 snap.inCooldown = !_detectCooldown.hasExpired();
159 }
160 return snap;
161}
162
164{
165 QWriteLocker locker(&_lock);
166 const mavlink_status_t* const status = mavlink_get_channel_status(channel);
167 const mavlink_signing_status_t current =
168 (status && status->signing) ? status->signing->last_status : MAVLINK_SIGNING_STATUS_NONE;
169 if (current == _lastTransitionStatus) {
170 return false;
171 }
172 _lastTransitionStatus = current;
173 return true;
174}
mavlink_channel_t
mavlink_status_t * mavlink_get_channel_status(uint8_t chan)
Definition QGCMAVLink.cc:53
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Owns MAVLink signing state for one channel: signing/streams structs, key hint, and RW lock.
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.
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().