3#include <QtCore/QCoreApplication>
4#include <QtCore/QCryptographicHash>
5#include <QtCore/QRandomGenerator>
7#include <QtCore/QSettings>
8#include <QtCore/QTimer>
9#include <QtNetwork/QPasswordDigestor>
26 : QObject(parent), _name(name), _keyBytes(keyBytes)
28 qCDebug(MAVLinkSigningKeysLog) <<
"MAVLinkSigningKey ctor:" << _name;
34 qCDebug(MAVLinkSigningKeysLog) <<
"MAVLinkSigningKey dtor:" << _name;
41 return _mavlinkSigningKeysInstance();
46 qCDebug(MAVLinkSigningKeysLog) <<
"MAVLinkSigningKeys ctor";
55 for (
int i = 0; i < mvm->vehicles()->count(); ++i) {
56 _connectVehicle(mvm->vehicles()->value<
Vehicle*>(i));
59 _timestampFlushTimer =
new QTimer(
this);
60 _timestampFlushTimer->setInterval(kTimestampFlushIntervalMs);
62 _timestampFlushTimer->start();
65 if (
auto* app = QCoreApplication::instance()) {
72 qCDebug(MAVLinkSigningKeysLog) <<
"MAVLinkSigningKeys dtor";
78 for (
int i = 0; i < mvm->vehicles()->count(); ++i) {
79 const auto* vehicle = mvm->vehicles()->value<
Vehicle*>(i);
80 if (vehicle && vehicle->signingController() && vehicle->signingController()->signingStatus().keyName == name) {
87void MAVLinkSigningKeys::_connectVehicle(
Vehicle* vehicle)
96void MAVLinkSigningKeys::_disconnectVehicle(
Vehicle* vehicle)
107 if (index >= 0 && index < _keys->count()) {
115 const auto it = _keyIndex.constFind(name);
116 if (it == _keyIndex.constEnd()) {
119 return it.value()->keyBytes();
126 _keyIndex.insert(name, key);
130bool MAVLinkSigningKeys::_validateNewKey(
const QString& name)
const
132 if (name.isEmpty()) {
133 qCWarning(MAVLinkSigningKeysLog) <<
"Key name must not be empty";
136 if (_keys->
count() >= kMaxKeys) {
137 qCWarning(MAVLinkSigningKeysLog) <<
"Maximum key count reached:" << kMaxKeys;
140 if (_keyIndex.contains(name)) {
141 qCWarning(MAVLinkSigningKeysLog) <<
"Key with name already exists:" << name;
149 if (passphrase.size() < kMinPassphraseLength) {
150 qCWarning(MAVLinkSigningKeysLog) <<
"Passphrase must be at least" << kMinPassphraseLength <<
"characters";
153 if (!_validateNewKey(name)) {
158 const QByteArray salt(kPbkdf2Salt.constData(), kPbkdf2Salt.size());
159 QByteArray utf8 = passphrase.toUtf8();
160 QByteArray derived = QPasswordDigestor::deriveKeyPbkdf2(QCryptographicHash::Sha256, utf8, salt,
161 _effectivePbkdf2Iterations(), kSigningKeySize);
168 _insertKey(name, *key);
176 if (hexKey.isEmpty()) {
177 qCWarning(MAVLinkSigningKeysLog) <<
"Hex key must not be empty";
180 if (!_validateNewKey(name)) {
184 const QByteArray keyBytes = QByteArray::fromHex(hexKey.toLatin1());
187 qCWarning(MAVLinkSigningKeysLog) <<
"Raw key must be exactly 32 bytes (64 hex chars), got" << keyBytes.size();
191 _insertKey(name, *key);
200 std::array<quint32, 8> aligned{};
201 QRandomGenerator::system()->fillRange(aligned.data(), aligned.size());
202 const QByteArray bytes(
reinterpret_cast<const char*
>(aligned.data()),
sizeof(aligned));
203 return QString::fromLatin1(bytes.toHex());
212 QByteArray bytes(
reinterpret_cast<const char*
>(key->data()), key->size());
213 QByteArray hex = bytes.toHex();
214 QString result = QString::fromLatin1(hex);
223 const auto it = _keyIndex.constFind(name);
224 if (it == _keyIndex.constEnd()) {
229 if (
auto* removed = _keys->
removeOne(entry)) {
230 removed->deleteLater();
238 if (_keys->
count() == 0) {
248void MAVLinkSigningKeys::_save()
252 settings.beginGroup(kSettingsGroup);
253 const QStringList previousNames = settings.value(kManifestKey).toStringList();
255 QStringList manifestNames;
256 for (
int i = 0; i < _keys->
count(); ++i) {
258 const auto& bytes = key->
keyBytes();
259 QByteArray serialized(
reinterpret_cast<const char*
>(bytes.data()),
static_cast<qsizetype
>(bytes.size()));
260 settings.setValue(QString(
"%1/%2").arg(kKeySubgroup, key->name()), serialized);
262 manifestNames.append(key->name());
265 const QSet<QString> liveNameSet(manifestNames.constBegin(), manifestNames.constEnd());
266 for (
const QString& oldName : previousNames) {
267 if (!liveNameSet.contains(oldName)) {
268 settings.remove(QString(
"%1/%2").arg(kKeySubgroup, oldName));
269 settings.remove(QString(
"%1/%2").arg(kTimestampSubgroup, oldName));
273 settings.setValue(kManifestKey, manifestNames);
281 const auto it = _keyIndex.constFind(name);
282 return it == _keyIndex.constEnd() ? 0 : it.value()->lastTimestamp();
287 if (batch.isEmpty()) {
291 settings.beginGroup(kSettingsGroup);
292 bool wroteAnything =
false;
293 for (
auto it = batch.constBegin(); it != batch.constEnd(); ++it) {
294 const auto keyIt = _keyIndex.constFind(it.key());
295 if (keyIt == _keyIndex.constEnd()) {
299 if (it.value() <= keyIt.value()->lastTimestamp()) {
302 keyIt.value()->setLastTimestamp(it.value());
303 settings.setValue(QString(
"%1/%2").arg(kTimestampSubgroup, it.key()), QVariant::fromValue<quint64>(it.value()));
304 wroteAnything =
true;
322QHash<QString, uint64_t> MAVLinkSigningKeys::_snapshotAllTimestamps()
const
324 QHash<QString, uint64_t> batch;
326 for (
const auto& link : links) {
335 if (snap.keyName.isEmpty() || snap.timestamp == 0) {
338 auto& slot = batch[snap.keyName];
339 if (snap.timestamp > slot) {
358 if (snap.autoDetectSuspended || snap.inCooldown) {
365 const QString& hintName = snap.keyHint;
366 if (!hintName.isEmpty()) {
369 const QByteArrayView kv(
reinterpret_cast<const char*
>(hintKey->data()), hintKey->size());
372 qCDebug(MAVLinkSigningKeysLog) <<
"Auto-detected signing key" << hintName <<
"(cached hint)";
378 const int keyCount = _keys->
count();
379 for (
int i = 0; i < keyCount; ++i) {
380 const auto* entry =
keyAt(i);
381 if (!entry || entry->name() == hintName) {
384 const auto& keyBytes = entry->keyBytes();
386 const QByteArrayView kv(
reinterpret_cast<const char*
>(keyBytes.data()), keyBytes.size());
389 qCDebug(MAVLinkSigningKeysLog) <<
"Auto-detected signing key" << entry->name();
390 return entry->name();
399void MAVLinkSigningKeys::_load()
405 settings.beginGroup(kSettingsGroup);
406 const QStringList manifest = settings.value(kManifestKey).toStringList();
407 for (
const QString& name : manifest) {
408 QByteArray keyBytes = settings.value(QString(
"%1/%2").arg(kKeySubgroup, name)).toByteArray();
410 auto* inserted = _insertKey(name, *key);
411 const uint64_t ts = settings.value(QString(
"%1/%2").arg(kTimestampSubgroup, name)).toULongLong();
413 inserted->setLastTimestamp(ts);
415 }
else if (!keyBytes.isEmpty()) {
416 qCWarning(MAVLinkSigningKeysLog) <<
"Skipping malformed key entry:" << name;
Q_APPLICATION_STATIC(MAVLinkSigningKeys, _mavlinkSigningKeysInstance)
struct __mavlink_message mavlink_message_t
#define QGC_LOGGING_CATEGORY(name, categoryStr)
static LinkManager * instance()
QList< SharedLinkInterfacePtr > links()
A single named signing key entry.
const MAVLinkSigning::SigningKey & keyBytes() const
~MAVLinkSigningKey() override
Bag of named MAVLink signing keys; correct key per vehicle is auto-detected from incoming signed pack...
static MAVLinkSigningKeys * instance()
~MAVLinkSigningKeys() override
std::optional< MAVLinkSigning::SigningKey > keyBytesByName(const QString &name) const
Key bytes for the key with the given name, or nullopt if not found.
void flushAllTimestamps()
Walk every signing channel and persist its current timestamp under the active key's name.
MAVLinkSigningKey * keyAt(int index) const
Key entry at the given index, or nullptr if invalid.
void recordTimestamps(const QHash< QString, uint64_t > &batch)
Batch update with single QSettings + sync; per-entry monotonic guard still applies.
Q_INVOKABLE bool addRawKey(const QString &name, const QString &hexKey)
Q_INVOKABLE void removeAllKeys()
Used by tests and full reset.
MAVLinkSigningKeys(QObject *parent=nullptr)
void recordTimestamp(const QString &name, uint64_t ts)
Update in-memory + persisted last-timestamp for name. Monotonic — older values are dropped.
Q_INVOKABLE void removeKey(const QString &name)
QString tryDetectKey(SigningController *controller, const mavlink_message_t &message)
Try every stored key against message's signature; on match, configures channel and returns the key na...
Q_INVOKABLE bool addKey(const QString &name, const QString &passphrase)
Q_INVOKABLE bool isKeyInUse(const QString &name) const
True if any connected vehicle is using the key with the given name.
Q_INVOKABLE QString keyHexByName(const QString &name) const
Hex-encoded key bytes for export (empty if not found).
uint64_t lastTimestamp(const QString &name) const
Last persisted signing timestamp for name, or 0 if unknown / no entry.
static Q_INVOKABLE QString generateRandomHexKey()
Cryptographically random 64-char hex string (32 bytes).
static MultiVehicleManager * instance()
void vehicleAdded(Vehicle *vehicle)
void vehicleRemoved(Vehicle *vehicle)
void append(QObject *object)
Caller maintains responsibility for object ownership and deletion.
QObject * removeOne(const QObject *object) override final
int count() const override final
void clearAndDeleteContents() override final
Clears the list and calls deleteLater on each entry.
MAVLinkSigning::DetectSnapshot detectSnapshot() const
Single-lock snapshot; 3 separate reads have TOCTOU window vs MockLink's thread.
TimestampSnapshot currentTimestampAndName() const
Returns current timestamp and active key name. Returns {0, ""} when signing is not enabled.
Owns MAVLink signing state and the deferred-confirmation state machine for one LinkInterface.
void clearDetectCooldown()
bool initSigningImmediate(QByteArrayView key, MAVLinkSigning::UnsignedAcceptancePolicy policy, const QString &keyNameHint={})
Bypasses the FSM; used by tests and auto-detect. Non-empty keyNameHint seeds the persisted timestamp.
const SigningChannel & channel() const
void signingStatusChanged()
VehicleSigningController * signingController()
std::array< uint8_t, kSigningKeySize > SigningKey
std::array avoids QByteArray COW detach so secureZero() actually wipes the bytes.
bool verifySignature(QByteArrayView key, const mavlink_message_t &message)
Verify a key against a signed message's signature.
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.
void secureZero(void *data, size_t size)