aboutsummaryrefslogtreecommitdiff
path: root/lib/e2ee/qolmaccount.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/e2ee/qolmaccount.cpp')
-rw-r--r--lib/e2ee/qolmaccount.cpp275
1 files changed, 275 insertions, 0 deletions
diff --git a/lib/e2ee/qolmaccount.cpp b/lib/e2ee/qolmaccount.cpp
new file mode 100644
index 00000000..345ab16b
--- /dev/null
+++ b/lib/e2ee/qolmaccount.cpp
@@ -0,0 +1,275 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#include "qolmaccount.h"
+
+#include "connection.h"
+#include "e2ee/qolmsession.h"
+#include "e2ee/qolmutility.h"
+#include "e2ee/qolmutils.h"
+
+#include "csapi/keys.h"
+
+#include <QtCore/QRandomGenerator>
+
+#include <olm/olm.h>
+
+using namespace Quotient;
+
+// Convert olm error to enum
+OlmErrorCode QOlmAccount::lastErrorCode() const {
+ return olm_account_last_error_code(m_account);
+}
+
+const char* QOlmAccount::lastError() const
+{
+ return olm_account_last_error(m_account);
+}
+
+QOlmAccount::QOlmAccount(QStringView userId, QStringView deviceId,
+ QObject* parent)
+ : QObject(parent)
+ , m_userId(userId.toString())
+ , m_deviceId(deviceId.toString())
+{}
+
+QOlmAccount::~QOlmAccount()
+{
+ olm_clear_account(m_account);
+ delete[](reinterpret_cast<uint8_t *>(m_account));
+}
+
+void QOlmAccount::createNewAccount()
+{
+ m_account = olm_account(new uint8_t[olm_account_size()]);
+ if (const auto randomLength = olm_create_account_random_length(m_account);
+ olm_create_account(m_account, RandomBuffer(randomLength), randomLength)
+ == olm_error())
+ QOLM_INTERNAL_ERROR("Failed to create a new account");
+
+ emit needsSave();
+}
+
+OlmErrorCode QOlmAccount::unpickle(QByteArray&& pickled,
+ const PicklingMode& mode)
+{
+ m_account = olm_account(new uint8_t[olm_account_size()]);
+ if (const auto key = toKey(mode);
+ olm_unpickle_account(m_account, key.data(), key.length(),
+ pickled.data(), pickled.size())
+ == olm_error()) {
+ // Probably log the user out since we have no way of getting to the keys
+ return lastErrorCode();
+ }
+ return OLM_SUCCESS;
+}
+
+QByteArray QOlmAccount::pickle(const PicklingMode &mode)
+{
+ const QByteArray key = toKey(mode);
+ const size_t pickleLength = olm_pickle_account_length(m_account);
+ QByteArray pickleBuffer(pickleLength, '\0');
+ if (olm_pickle_account(m_account, key.data(), key.length(),
+ pickleBuffer.data(), pickleLength)
+ == olm_error())
+ QOLM_INTERNAL_ERROR(qPrintable("Failed to pickle Olm account "
+ + accountId()));
+
+ return pickleBuffer;
+}
+
+IdentityKeys QOlmAccount::identityKeys() const
+{
+ const size_t keyLength = olm_account_identity_keys_length(m_account);
+ QByteArray keyBuffer(keyLength, '\0');
+ if (olm_account_identity_keys(m_account, keyBuffer.data(), keyLength)
+ == olm_error()) {
+ QOLM_INTERNAL_ERROR(
+ qPrintable("Failed to get " % accountId() % " identity keys"));
+ }
+ const auto key = QJsonDocument::fromJson(keyBuffer).object();
+ return IdentityKeys {
+ key.value(QStringLiteral("curve25519")).toString().toUtf8(),
+ key.value(QStringLiteral("ed25519")).toString().toUtf8()
+ };
+}
+
+QByteArray QOlmAccount::sign(const QByteArray &message) const
+{
+ QByteArray signatureBuffer(olm_account_signature_length(m_account), '\0');
+
+ if (olm_account_sign(m_account, message.data(), message.length(),
+ signatureBuffer.data(), signatureBuffer.length())
+ == olm_error())
+ QOLM_INTERNAL_ERROR("Failed to sign a message");
+
+ return signatureBuffer;
+}
+
+QByteArray QOlmAccount::sign(const QJsonObject &message) const
+{
+ return sign(QJsonDocument(message).toJson(QJsonDocument::Compact));
+}
+
+QByteArray QOlmAccount::signIdentityKeys() const
+{
+ const auto keys = identityKeys();
+ 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
+{
+ return olm_account_max_number_of_one_time_keys(m_account);
+}
+
+size_t QOlmAccount::generateOneTimeKeys(size_t numberOfKeys)
+{
+ const auto randomLength =
+ olm_account_generate_one_time_keys_random_length(m_account,
+ numberOfKeys);
+ const auto result = olm_account_generate_one_time_keys(
+ m_account, numberOfKeys, RandomBuffer(randomLength), randomLength);
+
+ if (result == olm_error())
+ QOLM_INTERNAL_ERROR(qPrintable(
+ "Failed to generate one-time keys for account " + accountId()));
+
+ emit needsSave();
+ return result;
+}
+
+UnsignedOneTimeKeys QOlmAccount::oneTimeKeys() const
+{
+ const auto oneTimeKeyLength = olm_account_one_time_keys_length(m_account);
+ QByteArray oneTimeKeysBuffer(static_cast<int>(oneTimeKeyLength), '\0');
+
+ if (olm_account_one_time_keys(m_account, oneTimeKeysBuffer.data(),
+ oneTimeKeyLength)
+ == olm_error())
+ QOLM_INTERNAL_ERROR(qPrintable(
+ "Failed to obtain one-time keys for account" % accountId()));
+
+ const auto json = QJsonDocument::fromJson(oneTimeKeysBuffer).object();
+ UnsignedOneTimeKeys oneTimeKeys;
+ fromJson(json, oneTimeKeys.keys);
+ return oneTimeKeys;
+}
+
+OneTimeKeys QOlmAccount::signOneTimeKeys(const UnsignedOneTimeKeys &keys) const
+{
+ OneTimeKeys signedOneTimeKeys;
+ for (const auto& curveKeys = keys.curve25519();
+ const auto& [keyId, key] : asKeyValueRange(curveKeys))
+ signedOneTimeKeys.insert("signed_curve25519:" % keyId,
+ SignedOneTimeKey {
+ key, m_userId, m_deviceId,
+ sign(QJsonObject { { "key", key } }) });
+ return signedOneTimeKeys;
+}
+
+OlmErrorCode QOlmAccount::removeOneTimeKeys(const QOlmSession& session)
+{
+ if (olm_remove_one_time_keys(m_account, session.raw()) == olm_error()) {
+ qWarning(E2EE).nospace()
+ << "Failed to remove one-time keys for session "
+ << session.sessionId() << ": " << lastError();
+ return lastErrorCode();
+ }
+ emit needsSave();
+ return OLM_SUCCESS;
+}
+
+OlmAccount* QOlmAccount::data() { return m_account; }
+
+DeviceKeys QOlmAccount::deviceKeys() const
+{
+ static QStringList Algorithms(SupportedAlgorithms.cbegin(),
+ SupportedAlgorithms.cend());
+
+ const auto idKeys = identityKeys();
+ return DeviceKeys{
+ .userId = m_userId,
+ .deviceId = m_deviceId,
+ .algorithms = Algorithms,
+ .keys{ { "curve25519:" + m_deviceId, idKeys.curve25519 },
+ { "ed25519:" + m_deviceId, idKeys.ed25519 } },
+ .signatures{
+ { m_userId, { { "ed25519:" + m_deviceId, signIdentityKeys() } } } }
+ };
+}
+
+UploadKeysJob* QOlmAccount::createUploadKeyRequest(
+ const UnsignedOneTimeKeys& oneTimeKeys) const
+{
+ return new UploadKeysJob(deviceKeys(), signOneTimeKeys(oneTimeKeys));
+}
+
+QOlmExpected<QOlmSessionPtr> QOlmAccount::createInboundSession(
+ const QOlmMessage& preKeyMessage)
+{
+ Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey);
+ return QOlmSession::createInboundSession(this, preKeyMessage);
+}
+
+QOlmExpected<QOlmSessionPtr> QOlmAccount::createInboundSessionFrom(
+ const QByteArray& theirIdentityKey, const QOlmMessage& preKeyMessage)
+{
+ Q_ASSERT(preKeyMessage.type() == QOlmMessage::PreKey);
+ return QOlmSession::createInboundSessionFrom(this, theirIdentityKey,
+ preKeyMessage);
+}
+
+QOlmExpected<QOlmSessionPtr> QOlmAccount::createOutboundSession(
+ const QByteArray& theirIdentityKey, const QByteArray& theirOneTimeKey)
+{
+ return QOlmSession::createOutboundSession(this, theirIdentityKey,
+ theirOneTimeKey);
+}
+
+void QOlmAccount::markKeysAsPublished()
+{
+ olm_account_mark_keys_as_published(m_account);
+ emit needsSave();
+}
+
+bool Quotient::verifyIdentitySignature(const DeviceKeys& deviceKeys,
+ const QString& deviceId,
+ const QString& userId)
+{
+ const auto signKeyId = "ed25519:" + deviceId;
+ const auto signingKey = deviceKeys.keys[signKeyId];
+ const auto signature = deviceKeys.signatures[userId][signKeyId];
+
+ return ed25519VerifySignature(signingKey, toJson(deviceKeys), signature);
+}
+
+bool Quotient::ed25519VerifySignature(const QString& signingKey,
+ const QJsonObject& obj,
+ const QString& signature)
+{
+ if (signature.isEmpty())
+ return false;
+
+ QJsonObject obj1 = obj;
+
+ obj1.remove("unsigned");
+ obj1.remove("signatures");
+
+ auto canonicalJson = QJsonDocument(obj1).toJson(QJsonDocument::Compact);
+
+ QByteArray signingKeyBuf = signingKey.toUtf8();
+ QOlmUtility utility;
+ auto signatureBuf = signature.toUtf8();
+ return utility.ed25519Verify(signingKeyBuf, canonicalJson, signatureBuf);
+}
+
+QString QOlmAccount::accountId() const { return m_userId % '/' % m_deviceId; }