From 3849a2466208a741165385f20150bddad59d2b8c Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 22 Jan 2021 23:36:47 +0100 Subject: Start implementing Qt olm binding --- CMakeLists.txt | 3 +- lib/olm/qolmaccount.cpp | 173 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/olm/qolmaccount.h | 89 +++++++++++++++++++++++++ 3 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 lib/olm/qolmaccount.cpp create mode 100644 lib/olm/qolmaccount.h 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 $ ) 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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifdef Quotient_E2EE_ENABLED +#include "qolmaccount.h" +#include +#include +#include +#include + +QMap OneTimeKeys::curve25519() const +{ + return keys[QStringLiteral("curve25519")]; +} + +std::optional> 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(mode)) { + return ""; + } + return std::get(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::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(account); +} + +std::variant 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 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 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 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::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 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(); + 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 +// +// SPDX-License-Identifier: LGPL-2.1-or-later +#pragma once + +#include +#include +#include +#include +#include +#include "olm/olm.h" + +struct OlmAccount; + +struct Unencrypted {}; +struct Encrypted { + QByteArray key; +}; + +using PicklingMode = std::variant; + +template struct overloaded : Ts... { using Ts::operator()...; }; +template overloaded(Ts...) -> overloaded; + +struct IdentityKeys +{ + QByteArray curve25519; + QByteArray ed25519; +}; + +//! Struct representing the the one-time keys. +struct OneTimeKeys +{ + QMap> keys; + + //! Get the HashMap containing the curve25519 one-time keys. + QMap curve25519() const; + + //! Get a reference to the hashmap corresponding to given key type. + std::optional> 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 here. + static std::optional create(); + static std::variant unpickle(QByteArray picked, PicklingMode mode); + + //! Serialises an OlmAccount to encrypted Base64. + std::variant pickle(PicklingMode mode); + std::variant identityKeys(); + + //! Returns the signature of the supplied message. + std::variant 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 generateOneTimeKeys(size_t numberOfKeys) const; + + //! Gets the OlmAccount's one time keys formatted as JSON. + std::variant oneTimeKeys() const; + + // HACK do not use directly + QOlmAccount(OlmAccount *account); +private: + OlmAccount *m_account; +}; -- cgit v1.2.3