From 21ae4eca4c06e500ec04a52ad42772bf8e8e9b6f Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Tue, 31 May 2022 18:58:27 +0200 Subject: Tweak QOlmAccount and data structures around This is mainly to plug the definition of a string-to-variant map for one-time keys (see https://spec.matrix.org/v1.2/client-server-api/#key-algorithms) into the CS API generated code (see the "shortcut OneTimeKeys" commit for gtad.yaml); but along with it came considerable streamlining of code in qolmaccount.cpp. Using std::variant to store that map also warranted converters.h to gain support for that type (even wider than toJson() that is already in dev - a non-trivial merge from dev is in order). --- autotests/testolmaccount.cpp | 18 +++----- lib/converters.h | 20 ++++++++ lib/e2ee/e2ee.h | 10 ++-- lib/e2ee/qolmaccount.cpp | 108 ++++++++++++++++--------------------------- lib/e2ee/qolmaccount.h | 9 ++-- 5 files changed, 73 insertions(+), 92 deletions(-) diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index e31ff6d3..c85718dd 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -198,7 +198,7 @@ void TestOlmAccount::uploadIdentityKey() QVERIFY(idKeys.curve25519.size() > 10); - OneTimeKeys unused; + UnsignedOneTimeKeys unused; auto request = olmAccount->createUploadKeyRequest(unused); connect(request, &BaseJob::result, this, [request, conn] { QCOMPARE(request->oneTimeKeyCounts().size(), 0); @@ -221,7 +221,7 @@ void TestOlmAccount::uploadOneTimeKeys() auto oneTimeKeys = olmAccount->oneTimeKeys(); - QHash oneTimeKeysHash; + OneTimeKeys oneTimeKeysHash; const auto curve = oneTimeKeys.curve25519(); for (const auto &[keyId, key] : asKeyValueRange(curve)) { oneTimeKeysHash["curve25519:"+keyId] = key; @@ -247,12 +247,10 @@ void TestOlmAccount::uploadSignedOneTimeKeys() QCOMPARE(nKeys, 5); auto oneTimeKeys = olmAccount->oneTimeKeys(); - QHash oneTimeKeysHash; + OneTimeKeys oneTimeKeysHash; const auto signedKey = olmAccount->signOneTimeKeys(oneTimeKeys); for (const auto &[keyId, key] : asKeyValueRange(signedKey)) { - QVariant var; - var.setValue(key); - oneTimeKeysHash[keyId] = var; + oneTimeKeysHash[keyId] = key; } auto request = new UploadKeysJob(none, oneTimeKeysHash); connect(request, &BaseJob::result, this, [request, nKeys, conn] { @@ -410,11 +408,9 @@ void TestOlmAccount::claimKeys() // The key is the one bob sent. const auto& oneTimeKey = job->oneTimeKeys().value(userId).value(deviceId); - QVERIFY(oneTimeKey.canConvert()); - - const auto varMap = oneTimeKey.toMap(); - QVERIFY(std::any_of(varMap.constKeyValueBegin(), - varMap.constKeyValueEnd(), [](const auto& kv) { + QVERIFY(std::any_of(oneTimeKey.constKeyValueBegin(), + oneTimeKey.constKeyValueEnd(), + [](const auto& kv) { return kv.first.startsWith( SignedCurve25519Key); })); diff --git a/lib/converters.h b/lib/converters.h index 5e3becb8..64a5cfb6 100644 --- a/lib/converters.h +++ b/lib/converters.h @@ -224,6 +224,26 @@ struct QUOTIENT_API JsonConverter { static QVariant load(const QJsonValue& jv); }; +template +inline QJsonValue toJson(const std::variant& v) +{ + // std::visit requires all overloads to return the same type - and + // QJsonValue is a perfect candidate for that same type (assuming that + // variants never occur on the top level in Matrix API) + return std::visit( + [](const auto& value) { return QJsonValue { toJson(value) }; }, v); +} + +template +struct QUOTIENT_API JsonConverter> { + static std::variant load(const QJsonValue& jv) + { + if (jv.isString()) + return fromJson(jv); + return fromJson(jv); + } +}; + template struct JsonConverter> { static QJsonValue dump(const Omittable& from) diff --git a/lib/e2ee/e2ee.h b/lib/e2ee/e2ee.h index 8e433d60..f97eb27a 100644 --- a/lib/e2ee/e2ee.h +++ b/lib/e2ee/e2ee.h @@ -70,15 +70,12 @@ struct IdentityKeys }; //! Struct representing the one-time keys. -struct QUOTIENT_API OneTimeKeys +struct QUOTIENT_API UnsignedOneTimeKeys { QHash> keys; //! Get the HashMap containing the curve25519 one-time keys. - QHash curve25519() const; - - //! Get a reference to the hashmap corresponding to given key type. -// std::optional> get(QString keyType) const; + QHash curve25519() const { return keys[Curve25519Key]; } }; //! Struct representing the signed one-time keys. @@ -93,7 +90,6 @@ public: QHash> signatures; }; - template <> struct JsonObjectConverter { static void fillFrom(const QJsonObject& jo, SignedOneTimeKey& result) @@ -109,6 +105,8 @@ struct JsonObjectConverter { } }; +using OneTimeKeys = QHash>; + template class asKeyValueRange { diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp index 72dddafb..4cfc6151 100644 --- a/lib/e2ee/qolmaccount.cpp +++ b/lib/e2ee/qolmaccount.cpp @@ -17,19 +17,6 @@ using namespace Quotient; -QHash OneTimeKeys::curve25519() const -{ - return keys[Curve25519Key]; -} - -//std::optional> OneTimeKeys::get(QString keyType) const -//{ -// if (!keys.contains(keyType)) { -// return std::nullopt; -// } -// return keys[keyType]; -//} - // Convert olm error to enum QOlmError lastError(OlmAccount *account) { return fromString(olm_account_last_error(account)); @@ -122,20 +109,15 @@ QByteArray QOlmAccount::sign(const QJsonObject &message) const QByteArray QOlmAccount::signIdentityKeys() const { const auto keys = identityKeys(); - QJsonObject body - { - {"algorithms", QJsonArray{"m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"}}, - {"user_id", m_userId}, - {"device_id", m_deviceId}, - {"keys", - QJsonObject{ - {QStringLiteral("curve25519:") + m_deviceId, QString::fromUtf8(keys.curve25519)}, - {QStringLiteral("ed25519:") + m_deviceId, QString::fromUtf8(keys.ed25519)} - } - } - }; - return sign(QJsonDocument(body).toJson(QJsonDocument::Compact)); - + return sign(QJsonObject { + { "algorithms", QJsonArray { "m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2" } }, + { "user_id", m_userId }, + { "device_id", m_deviceId }, + { "keys", QJsonObject { { QStringLiteral("curve25519:") + m_deviceId, + QString::fromUtf8(keys.curve25519) }, + { QStringLiteral("ed25519:") + m_deviceId, + QString::fromUtf8(keys.ed25519) } } } }); } size_t QOlmAccount::maxNumberOfOneTimeKeys() const @@ -145,9 +127,13 @@ size_t QOlmAccount::maxNumberOfOneTimeKeys() const size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) { - const size_t randomLength = olm_account_generate_one_time_keys_random_length(m_account, numberOfKeys); + const size_t randomLength = + olm_account_generate_one_time_keys_random_length(m_account, + numberOfKeys); QByteArray randomBuffer = getRandom(randomLength); - const auto error = olm_account_generate_one_time_keys(m_account, numberOfKeys, randomBuffer.data(), randomLength); + const auto error = + olm_account_generate_one_time_keys(m_account, numberOfKeys, + randomBuffer.data(), randomLength); if (error == olm_error()) { throw lastError(m_account); @@ -156,7 +142,7 @@ size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) return error; } -OneTimeKeys QOlmAccount::oneTimeKeys() const +UnsignedOneTimeKeys QOlmAccount::oneTimeKeys() const { const size_t oneTimeKeyLength = olm_account_one_time_keys_length(m_account); QByteArray oneTimeKeysBuffer(oneTimeKeyLength, '0'); @@ -166,34 +152,25 @@ OneTimeKeys QOlmAccount::oneTimeKeys() const throw lastError(m_account); } const auto json = QJsonDocument::fromJson(oneTimeKeysBuffer).object(); - OneTimeKeys oneTimeKeys; + UnsignedOneTimeKeys oneTimeKeys; fromJson(json, oneTimeKeys.keys); return oneTimeKeys; } -QHash QOlmAccount::signOneTimeKeys(const OneTimeKeys &keys) const +OneTimeKeys QOlmAccount::signOneTimeKeys(const UnsignedOneTimeKeys &keys) const { - QHash signedOneTimeKeys; - for (const auto &keyid : keys.curve25519().keys()) { - const auto oneTimeKey = keys.curve25519()[keyid]; - QByteArray sign = signOneTimeKey(oneTimeKey); - signedOneTimeKeys["signed_curve25519:" + keyid] = signedOneTimeKey(oneTimeKey.toUtf8(), sign); - } + OneTimeKeys signedOneTimeKeys; + const auto& curveKeys = keys.curve25519(); + for (const auto& [keyId, key] : asKeyValueRange(curveKeys)) + signedOneTimeKeys["signed_curve25519:" % keyId] = + signedOneTimeKey(key.toUtf8(), sign(QJsonObject{{"key", key}})); return signedOneTimeKeys; } -SignedOneTimeKey QOlmAccount::signedOneTimeKey(const QByteArray &key, const QString &signature) const +SignedOneTimeKey QOlmAccount::signedOneTimeKey(const QByteArray& key, + const QString& signature) const { - SignedOneTimeKey sign{}; - sign.key = key; - sign.signatures = {{m_userId, {{"ed25519:" + m_deviceId, signature}}}}; - return sign; -} - -QByteArray QOlmAccount::signOneTimeKey(const QString &key) const -{ - QJsonDocument j(QJsonObject{{"key", key}}); - return sign(j.toJson(QJsonDocument::Compact)); + return { key, { { m_userId, { { "ed25519:" + m_deviceId, signature } } } } }; } std::optional QOlmAccount::removeOneTimeKeys( @@ -227,39 +204,32 @@ DeviceKeys QOlmAccount::deviceKeys() const return deviceKeys; } -UploadKeysJob *QOlmAccount::createUploadKeyRequest(const OneTimeKeys &oneTimeKeys) +UploadKeysJob* QOlmAccount::createUploadKeyRequest( + const UnsignedOneTimeKeys& oneTimeKeys) const { - auto keys = deviceKeys(); - - if (oneTimeKeys.curve25519().isEmpty()) { - return new UploadKeysJob(keys); - } - - // Sign & append the one time keys. - auto temp = signOneTimeKeys(oneTimeKeys); - QHash oneTimeKeysSigned; - for (const auto &[keyId, key] : asKeyValueRange(temp)) { - oneTimeKeysSigned[keyId] = QVariant::fromValue(toJson(key)); - } - - return new UploadKeysJob(keys, oneTimeKeysSigned); + return new UploadKeysJob(deviceKeys(), signOneTimeKeys(oneTimeKeys)); } -QOlmExpected QOlmAccount::createInboundSession(const QOlmMessage &preKeyMessage) +QOlmExpected QOlmAccount::createInboundSession( + const QOlmMessage& preKeyMessage) { Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); return QOlmSession::createInboundSession(this, preKeyMessage); } -QOlmExpected QOlmAccount::createInboundSessionFrom(const QByteArray &theirIdentityKey, const QOlmMessage &preKeyMessage) +QOlmExpected QOlmAccount::createInboundSessionFrom( + const QByteArray& theirIdentityKey, const QOlmMessage& preKeyMessage) { Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey); - return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, preKeyMessage); + return QOlmSession::createInboundSessionFrom(this, theirIdentityKey, + preKeyMessage); } -QOlmExpected QOlmAccount::createOutboundSession(const QByteArray &theirIdentityKey, const QByteArray &theirOneTimeKey) +QOlmExpected QOlmAccount::createOutboundSession( + const QByteArray& theirIdentityKey, const QByteArray& theirOneTimeKey) { - return QOlmSession::createOutboundSession(this, theirIdentityKey, theirOneTimeKey); + return QOlmSession::createOutboundSession(this, theirIdentityKey, + theirOneTimeKey); } void QOlmAccount::markKeysAsPublished() diff --git a/lib/e2ee/qolmaccount.h b/lib/e2ee/qolmaccount.h index ee2aa69d..23fe58dd 100644 --- a/lib/e2ee/qolmaccount.h +++ b/lib/e2ee/qolmaccount.h @@ -59,17 +59,14 @@ public: size_t generateOneTimeKeys(size_t numberOfKeys); //! Gets the OlmAccount's one time keys formatted as JSON. - OneTimeKeys oneTimeKeys() const; + UnsignedOneTimeKeys oneTimeKeys() const; //! Sign all one time keys. - QHash signOneTimeKeys(const OneTimeKeys &keys) const; - - //! Sign one time key. - QByteArray signOneTimeKey(const QString &key) const; + OneTimeKeys signOneTimeKeys(const UnsignedOneTimeKeys &keys) const; SignedOneTimeKey signedOneTimeKey(const QByteArray &key, const QString &signature) const; - UploadKeysJob *createUploadKeyRequest(const OneTimeKeys &oneTimeKeys); + UploadKeysJob* createUploadKeyRequest(const UnsignedOneTimeKeys& oneTimeKeys) const; DeviceKeys deviceKeys() const; -- cgit v1.2.3