diff options
-rw-r--r-- | CMakeLists.txt | 3 | ||||
-rw-r--r-- | lib/olm/qolmaccount.cpp | 173 | ||||
-rw-r--r-- | lib/olm/qolmaccount.h | 89 |
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; +}; |