aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt3
-rw-r--r--lib/olm/qolmaccount.cpp173
-rw-r--r--lib/olm/qolmaccount.h89
3 files changed, 264 insertions, 1 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index aa3b9c98..b4d4ef8b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -171,6 +171,7 @@ list(APPEND lib_SRCS
lib/jobs/syncjob.cpp
lib/jobs/mediathumbnailjob.cpp
lib/jobs/downloadfilejob.cpp
+ lib/olm/qolmaccount.cpp
)
# Configure API files generation
@@ -317,7 +318,7 @@ target_include_directories(${PROJECT_NAME} PUBLIC
$<INSTALL_INTERFACE:${${PROJECT_NAME}_INSTALL_INCLUDEDIR}>
)
if (${PROJECT_NAME}_ENABLE_E2EE)
- target_link_libraries(${PROJECT_NAME} QtOlm)
+ target_link_libraries(${PROJECT_NAME} Olm::Olm QtOlm)
set(FIND_DEPS "find_dependency(QtOlm)") # For QuotientConfig.cmake.in
endif()
target_link_libraries(${PROJECT_NAME} ${Qt}::Core ${Qt}::Network ${Qt}::Gui)
diff --git a/lib/olm/qolmaccount.cpp b/lib/olm/qolmaccount.cpp
new file mode 100644
index 00000000..673f613b
--- /dev/null
+++ b/lib/olm/qolmaccount.cpp
@@ -0,0 +1,173 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#ifdef Quotient_E2EE_ENABLED
+#include "qolmaccount.h"
+#include <QJsonObject>
+#include <QJsonDocument>
+#include <QDebug>
+#include <iostream>
+
+QMap<QString, QString> OneTimeKeys::curve25519() const
+{
+ return keys[QStringLiteral("curve25519")];
+}
+
+std::optional<QMap<QString, QString>> OneTimeKeys::get(QString keyType) const
+{
+ if (!keys.contains(keyType)) {
+ return std::nullopt;
+ }
+ return keys[keyType];
+}
+
+// Convert PicklingMode to key
+QByteArray toKey(PicklingMode mode)
+{
+ if (std::holds_alternative<Unencrypted>(mode)) {
+ return "";
+ }
+ return std::get<Encrypted>(mode).key;
+}
+
+bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs)
+{
+ return lhs.curve25519 == rhs.curve25519 &&& lhs.ed25519 == rhs.ed25519;
+}
+
+// Conver olm error to enum
+QOlmAccount::OlmAccountError lastError(OlmAccount *account) {
+ const std::string error_raw = olm_account_last_error(account);
+
+ if (error_raw.compare("BAD_ACCOUNT_KEY")) {
+ return QOlmAccount::OlmAccountError::BadAccountKey;
+ } else if (error_raw.compare("BAD_MESSAGE_KEY_ID")) {
+ return QOlmAccount::OlmAccountError::BadMessageKeyId;
+ } else if (error_raw.compare("INVALID_BASE64")) {
+ return QOlmAccount::OlmAccountError::InvalidBase64;
+ } else if (error_raw.compare("NOT_ENOUGH_RANDOM")) {
+ return QOlmAccount::OlmAccountError::NotEnoughRandom;
+ } else if (error_raw.compare("OUTPUT_BUFFER_TOO_SMALL")) {
+ return QOlmAccount::OlmAccountError::OutputBufferTooSmall;
+ } else {
+ return QOlmAccount::OlmAccountError::Unknown;
+ }
+}
+
+QByteArray getRandom(size_t bufferSize)
+{
+ QByteArray buffer(bufferSize, '0');
+ std::generate(buffer.begin(), buffer.end(), std::rand);
+ return buffer;
+}
+
+QOlmAccount::QOlmAccount(OlmAccount *account)
+ : m_account(account)
+{}
+
+std::optional<QOlmAccount> QOlmAccount::create()
+{
+ auto account = olm_account(new uint8_t[olm_account_size()]);
+ size_t randomSize = olm_create_account_random_length(account);
+ QByteArray randomData = getRandom(randomSize);
+ const auto error = olm_create_account(account, randomData.data(), randomSize);
+ if (error == olm_error()) {
+ return std::nullopt;
+ }
+ return std::make_optional<QOlmAccount>(account);
+}
+
+std::variant<QOlmAccount, QOlmAccount::OlmAccountError> QOlmAccount::unpickle(QByteArray pickled, PicklingMode mode)
+{
+ auto account = olm_account(new uint8_t[olm_account_size()]);
+ const QByteArray key = toKey(mode);
+ const auto error = olm_unpickle_account(account, key.data(), key.length(), pickled.data(), pickled.size());
+ if (error == olm_error()) {
+ return lastError(account);
+ }
+ return QOlmAccount(account);
+}
+
+std::variant<QByteArray, QOlmAccount::OlmAccountError> QOlmAccount::pickle(PicklingMode mode)
+{
+ const QByteArray key = toKey(mode);
+ const size_t pickleLength = olm_pickle_account_length(m_account);
+ QByteArray pickleBuffer(pickleLength, '0');
+ const auto error = olm_pickle_account(m_account, key.data(),
+ key.length(), pickleBuffer.data(), pickleLength);
+ if (error == olm_error()) {
+ return lastError(m_account);
+ }
+ return pickleBuffer;
+}
+
+std::variant<IdentityKeys, QOlmAccount::OlmAccountError> QOlmAccount::identityKeys()
+{
+ const size_t keyLength = olm_account_identity_keys_length(m_account);
+ QByteArray keyBuffer(keyLength, '0');
+ const auto error = olm_account_identity_keys(m_account, keyBuffer.data(), keyLength);
+ if (error == olm_error()) {
+ return lastError(m_account);
+ }
+ const QJsonObject key = QJsonDocument::fromJson(keyBuffer).object();
+ return IdentityKeys {
+ key.value(QStringLiteral("curve25519")).toString().toUtf8(),
+ key.value(QStringLiteral("ed25519")).toString().toUtf8()
+ };
+}
+
+std::variant<QString, QOlmAccount::OlmAccountError> QOlmAccount::sign(QString message) const
+{
+ const size_t signatureLength = olm_account_signature_length(m_account);
+ QByteArray signatureBuffer(signatureLength, '0');
+ const auto error = olm_account_sign(m_account, message.data(), message.length(),
+ signatureBuffer.data(), signatureLength);
+
+ if (error == olm_error()) {
+ return lastError(m_account);
+ }
+ return QString::fromUtf8(signatureBuffer);
+}
+
+size_t QOlmAccount::maxNumberOfOneTimeKeys() const
+{
+ return olm_account_max_number_of_one_time_keys(m_account);
+}
+
+std::optional<QOlmAccount::OlmAccountError> QOlmAccount::generateOneTimeKeys(size_t numberOfKeys) const
+{
+ const size_t randomLen = olm_account_generate_one_time_keys_random_length(m_account, numberOfKeys);
+ QByteArray randomBuffer = getRandom(randomLen);
+ const auto error = olm_account_generate_one_time_keys(m_account, numberOfKeys, randomBuffer.data(), randomLen);
+
+ if (error == olm_error()) {
+ return lastError(m_account);
+ }
+ return std::nullopt;
+}
+
+std::variant<OneTimeKeys, QOlmAccount::OlmAccountError> QOlmAccount::oneTimeKeys() const
+{
+ const size_t oneTimeKeyLength = olm_account_one_time_keys_length(m_account);
+ QByteArray oneTimeKeysBuffer(oneTimeKeyLength, '0');
+
+ const auto error = olm_account_one_time_keys(m_account, oneTimeKeysBuffer.data(), oneTimeKeyLength);
+ if (error == olm_error()) {
+ return lastError(m_account);
+ }
+ const auto json = QJsonDocument::fromJson(oneTimeKeysBuffer).object();
+ OneTimeKeys oneTimeKeys;
+
+ for (const QJsonValue &key1 : json.keys()) {
+ auto oneTimeKeyObject = json[key1.toString()].toObject();
+ auto keyMap = QMap<QString, QString>();
+ for (const QString &key2 : oneTimeKeyObject.keys()) {
+ keyMap[key2] = oneTimeKeyObject[key2].toString();
+ }
+ oneTimeKeys.keys[key1.toString()] = keyMap;
+ }
+ return oneTimeKeys;
+}
+
+#endif
diff --git a/lib/olm/qolmaccount.h b/lib/olm/qolmaccount.h
new file mode 100644
index 00000000..219d7e48
--- /dev/null
+++ b/lib/olm/qolmaccount.h
@@ -0,0 +1,89 @@
+// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
+//
+// SPDX-License-Identifier: LGPL-2.1-or-later
+#pragma once
+
+#include <QObject>
+#include <QMap>
+#include <optional>
+#include <string>
+#include <variant>
+#include "olm/olm.h"
+
+struct OlmAccount;
+
+struct Unencrypted {};
+struct Encrypted {
+ QByteArray key;
+};
+
+using PicklingMode = std::variant<Unencrypted, Encrypted>;
+
+template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
+template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
+
+struct IdentityKeys
+{
+ QByteArray curve25519;
+ QByteArray ed25519;
+};
+
+//! Struct representing the the one-time keys.
+struct OneTimeKeys
+{
+ QMap<QString, QMap<QString, QString>> keys;
+
+ //! Get the HashMap containing the curve25519 one-time keys.
+ QMap<QString, QString> curve25519() const;
+
+ //! Get a reference to the hashmap corresponding to given key type.
+ std::optional<QMap<QString, QString>> get(QString keyType) const;
+};
+
+bool operator==(const IdentityKeys& lhs, const IdentityKeys& rhs);
+
+//! An olm account manages all cryptographic keys used on a device.
+//! \code{.cpp}
+//! const auto olmAccount = new QOlmAccount(this);
+//! \endcode
+class QOlmAccount
+{
+public:
+ enum OlmAccountError {
+ BadAccountKey,
+ BadMessageKeyId,
+ InvalidBase64,
+ NotEnoughRandom,
+ OutputBufferTooSmall,
+ Unknown,
+ };
+
+ //! Creates a new instance of OlmAccount. During the instantiation
+ //! the Ed25519 fingerprint key pair and the Curve25519 identity key
+ //! pair are generated. For more information see <a
+ //! href="https://matrix.org/docs/guides/e2e_implementation.html#keys-used-in-end-to-end-encryption">here</a>.
+ static std::optional<QOlmAccount> create();
+ static std::variant<QOlmAccount, OlmAccountError> unpickle(QByteArray picked, PicklingMode mode);
+
+ //! Serialises an OlmAccount to encrypted Base64.
+ std::variant<QByteArray, OlmAccountError> pickle(PicklingMode mode);
+ std::variant<IdentityKeys, OlmAccountError> identityKeys();
+
+ //! Returns the signature of the supplied message.
+ std::variant<QString, OlmAccountError> sign(QString message) const;
+
+ //! Maximum number of one time keys that this OlmAccount can
+ //! currently hold.
+ size_t maxNumberOfOneTimeKeys() const;
+
+ //! Generates the supplied number of one time keys.
+ std::optional<OlmAccountError> generateOneTimeKeys(size_t numberOfKeys) const;
+
+ //! Gets the OlmAccount's one time keys formatted as JSON.
+ std::variant<OneTimeKeys, OlmAccountError> oneTimeKeys() const;
+
+ // HACK do not use directly
+ QOlmAccount(OlmAccount *account);
+private:
+ OlmAccount *m_account;
+};