diff options
Diffstat (limited to 'lib/events')
-rw-r--r-- | lib/events/encryptedevent.cpp | 31 | ||||
-rw-r--r-- | lib/events/encryptedevent.h | 17 | ||||
-rw-r--r-- | lib/events/encryptedfile.cpp | 92 | ||||
-rw-r--r-- | lib/events/encryptedfile.h | 44 | ||||
-rw-r--r-- | lib/events/encryptionevent.cpp | 2 | ||||
-rw-r--r-- | lib/events/eventcontent.cpp | 1 | ||||
-rw-r--r-- | lib/events/keyverificationevent.cpp | 164 | ||||
-rw-r--r-- | lib/events/keyverificationevent.h | 161 | ||||
-rw-r--r-- | lib/events/roomevent.cpp | 15 | ||||
-rw-r--r-- | lib/events/roomevent.h | 10 |
10 files changed, 490 insertions, 47 deletions
diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp index 0290f973..9d07a35f 100644 --- a/lib/events/encryptedevent.cpp +++ b/lib/events/encryptedevent.cpp @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-2.1-or-later #include "encryptedevent.h" +#include "roommessageevent.h" +#include "events/eventloader.h" using namespace Quotient; @@ -30,3 +32,32 @@ EncryptedEvent::EncryptedEvent(const QJsonObject& obj) { qCDebug(E2EE) << "Encrypted event from" << senderId(); } + +QString EncryptedEvent::algorithm() const +{ + const auto algo = contentPart<QString>(AlgorithmKeyL); + if (!isSupportedAlgorithm(algo)) + qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo + << "is not supported"; + + return algo; +} + +RoomEventPtr EncryptedEvent::createDecrypted(const QString &decrypted) const +{ + auto eventObject = QJsonDocument::fromJson(decrypted.toUtf8()).object(); + eventObject["event_id"] = id(); + eventObject["sender"] = senderId(); + eventObject["origin_server_ts"] = originTimestamp().toMSecsSinceEpoch(); + if (const auto relatesToJson = contentPart("m.relates_to"_ls); !relatesToJson.isUndefined()) { + auto content = eventObject["content"].toObject(); + content["m.relates_to"] = relatesToJson.toObject(); + eventObject["content"] = content; + } + if (const auto redactsJson = unsignedPart("redacts"_ls); !redactsJson.isUndefined()) { + auto unsign = eventObject["unsigned"].toObject(); + unsign["redacts"] = redactsJson.toString(); + eventObject["unsigned"] = unsign; + } + return loadEvent<RoomEvent>(eventObject); +} diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h index 81343a29..72efffd4 100644 --- a/lib/events/encryptedevent.h +++ b/lib/events/encryptedevent.h @@ -3,7 +3,7 @@ #pragma once -#include "e2ee.h" +#include "e2ee/e2ee.h" #include "roomevent.h" namespace Quotient { @@ -39,28 +39,23 @@ public: const QString& deviceId, const QString& sessionId); explicit EncryptedEvent(const QJsonObject& obj); - QString algorithm() const - { - QString algo = contentPart<QString>(AlgorithmKeyL); - if (!SupportedAlgorithms.contains(algo)) { - qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo - << "is not supported"; - } - return algo; - } + QString algorithm() const; QByteArray ciphertext() const { return contentPart<QString>(CiphertextKeyL).toLatin1(); } QJsonObject ciphertext(const QString& identityKey) const { - return contentPart<QJsonObject>(CiphertextKeyL).value(identityKey).toObject(); + return contentPart<QJsonObject>(CiphertextKeyL) + .value(identityKey) + .toObject(); } QString senderKey() const { return contentPart<QString>(SenderKeyKeyL); } /* device_id and session_id are required with Megolm */ QString deviceId() const { return contentPart<QString>(DeviceIdKeyL); } QString sessionId() const { return contentPart<QString>(SessionIdKeyL); } + RoomEventPtr createDecrypted(const QString &decrypted) const; }; REGISTER_EVENT_TYPE(EncryptedEvent) diff --git a/lib/events/encryptedfile.cpp b/lib/events/encryptedfile.cpp new file mode 100644 index 00000000..d4a517bd --- /dev/null +++ b/lib/events/encryptedfile.cpp @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org> +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "encryptedfile.h" +#include "logging.h" + +#ifdef Quotient_E2EE_ENABLED +#include <openssl/evp.h> +#include <QtCore/QCryptographicHash> +#endif + +using namespace Quotient; + +QByteArray EncryptedFile::decryptFile(const QByteArray& ciphertext) const +{ +#ifdef Quotient_E2EE_ENABLED + auto _key = key.k; + const auto keyBytes = QByteArray::fromBase64( + _key.replace(u'_', u'/').replace(u'-', u'+').toLatin1()); + const auto sha256 = QByteArray::fromBase64(hashes["sha256"].toLatin1()); + if (sha256 + != QCryptographicHash::hash(ciphertext, QCryptographicHash::Sha256)) { + qCWarning(E2EE) << "Hash verification failed for file"; + return {}; + } + { + int length; + auto* ctx = EVP_CIPHER_CTX_new(); + QByteArray plaintext(ciphertext.size() + EVP_CIPHER_CTX_block_size(ctx) + - 1, + '\0'); + EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), nullptr, + reinterpret_cast<const unsigned char*>( + keyBytes.data()), + reinterpret_cast<const unsigned char*>( + QByteArray::fromBase64(iv.toLatin1()).data())); + EVP_DecryptUpdate( + ctx, reinterpret_cast<unsigned char*>(plaintext.data()), &length, + reinterpret_cast<const unsigned char*>(ciphertext.data()), + ciphertext.size()); + EVP_DecryptFinal_ex(ctx, + reinterpret_cast<unsigned char*>(plaintext.data()) + + length, + &length); + EVP_CIPHER_CTX_free(ctx); + return plaintext; + } +#else + qWarning(MAIN) << "This build of libQuotient doesn't support E2EE, " + "cannot decrypt the file"; + return ciphertext; +#endif +} + +void JsonObjectConverter<EncryptedFile>::dumpTo(QJsonObject& jo, + const EncryptedFile& pod) +{ + addParam<>(jo, QStringLiteral("url"), pod.url); + addParam<>(jo, QStringLiteral("key"), pod.key); + addParam<>(jo, QStringLiteral("iv"), pod.iv); + addParam<>(jo, QStringLiteral("hashes"), pod.hashes); + addParam<>(jo, QStringLiteral("v"), pod.v); +} + +void JsonObjectConverter<EncryptedFile>::fillFrom(const QJsonObject& jo, + EncryptedFile& pod) +{ + fromJson(jo.value("url"_ls), pod.url); + fromJson(jo.value("key"_ls), pod.key); + fromJson(jo.value("iv"_ls), pod.iv); + fromJson(jo.value("hashes"_ls), pod.hashes); + fromJson(jo.value("v"_ls), pod.v); +} + +void JsonObjectConverter<JWK>::dumpTo(QJsonObject &jo, const JWK &pod) +{ + addParam<>(jo, QStringLiteral("kty"), pod.kty); + addParam<>(jo, QStringLiteral("key_ops"), pod.keyOps); + addParam<>(jo, QStringLiteral("alg"), pod.alg); + addParam<>(jo, QStringLiteral("k"), pod.k); + addParam<>(jo, QStringLiteral("ext"), pod.ext); +} + +void JsonObjectConverter<JWK>::fillFrom(const QJsonObject &jo, JWK &pod) +{ + fromJson(jo.value("kty"_ls), pod.kty); + fromJson(jo.value("key_ops"_ls), pod.keyOps); + fromJson(jo.value("alg"_ls), pod.alg); + fromJson(jo.value("k"_ls), pod.k); + fromJson(jo.value("ext"_ls), pod.ext); +} diff --git a/lib/events/encryptedfile.h b/lib/events/encryptedfile.h index 24ac9de1..0558563f 100644 --- a/lib/events/encryptedfile.h +++ b/lib/events/encryptedfile.h @@ -29,7 +29,7 @@ public: bool ext; }; -struct EncryptedFile +struct QUOTIENT_API EncryptedFile { Q_GADGET Q_PROPERTY(QUrl url MEMBER url CONSTANT) @@ -44,45 +44,19 @@ public: QString iv; QHash<QString, QString> hashes; QString v; + + QByteArray decryptFile(const QByteArray &ciphertext) const; }; template <> -struct JsonObjectConverter<EncryptedFile> { - static void dumpTo(QJsonObject& jo, const EncryptedFile& pod) - { - addParam<>(jo, QStringLiteral("url"), pod.url); - addParam<>(jo, QStringLiteral("key"), pod.key); - addParam<>(jo, QStringLiteral("iv"), pod.iv); - addParam<>(jo, QStringLiteral("hashes"), pod.hashes); - addParam<>(jo, QStringLiteral("v"), pod.v); - } - static void fillFrom(const QJsonObject& jo, EncryptedFile& pod) - { - fromJson(jo.value("url"_ls), pod.url); - fromJson(jo.value("key"_ls), pod.key); - fromJson(jo.value("iv"_ls), pod.iv); - fromJson(jo.value("hashes"_ls), pod.hashes); - fromJson(jo.value("v"_ls), pod.v); - } +struct QUOTIENT_API JsonObjectConverter<EncryptedFile> { + static void dumpTo(QJsonObject& jo, const EncryptedFile& pod); + static void fillFrom(const QJsonObject& jo, EncryptedFile& pod); }; template <> -struct JsonObjectConverter<JWK> { - static void dumpTo(QJsonObject& jo, const JWK& pod) - { - addParam<>(jo, QStringLiteral("kty"), pod.kty); - addParam<>(jo, QStringLiteral("key_ops"), pod.keyOps); - addParam<>(jo, QStringLiteral("alg"), pod.alg); - addParam<>(jo, QStringLiteral("k"), pod.k); - addParam<>(jo, QStringLiteral("ext"), pod.ext); - } - static void fillFrom(const QJsonObject& jo, JWK& pod) - { - fromJson(jo.value("kty"_ls), pod.kty); - fromJson(jo.value("key_ops"_ls), pod.keyOps); - fromJson(jo.value("alg"_ls), pod.alg); - fromJson(jo.value("k"_ls), pod.k); - fromJson(jo.value("ext"_ls), pod.ext); - } +struct QUOTIENT_API JsonObjectConverter<JWK> { + static void dumpTo(QJsonObject& jo, const JWK& pod); + static void fillFrom(const QJsonObject& jo, JWK& pod); }; } // namespace Quotient diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index aa05a96e..6272c668 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -4,7 +4,7 @@ #include "encryptionevent.h" -#include "e2ee.h" +#include "e2ee/e2ee.h" #include <array> diff --git a/lib/events/eventcontent.cpp b/lib/events/eventcontent.cpp index 4ce130a6..9d7edf20 100644 --- a/lib/events/eventcontent.cpp +++ b/lib/events/eventcontent.cpp @@ -74,6 +74,7 @@ void FileInfo::fillInfoJson(QJsonObject* infoJson) const infoJson->insert(QStringLiteral("size"), payloadSize); if (mimeType.isValid()) infoJson->insert(QStringLiteral("mimetype"), mimeType.name()); + //TODO add encryptedfile } ImageInfo::ImageInfo(const QFileInfo& fi, QSize imageSize) diff --git a/lib/events/keyverificationevent.cpp b/lib/events/keyverificationevent.cpp new file mode 100644 index 00000000..4803955d --- /dev/null +++ b/lib/events/keyverificationevent.cpp @@ -0,0 +1,164 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org> +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "keyverificationevent.h" + +using namespace Quotient; + +KeyVerificationRequestEvent::KeyVerificationRequestEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationRequestEvent::fromDevice() const +{ + return contentPart<QString>("from_device"_ls); +} + +QString KeyVerificationRequestEvent::transactionId() const +{ + return contentPart<QString>("transaction_id"_ls); +} + +QStringList KeyVerificationRequestEvent::methods() const +{ + return contentPart<QStringList>("methods"_ls); +} + +uint64_t KeyVerificationRequestEvent::timestamp() const +{ + return contentPart<double>("timestamp"_ls); +} + +KeyVerificationStartEvent::KeyVerificationStartEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationStartEvent::fromDevice() const +{ + return contentPart<QString>("from_device"_ls); +} + +QString KeyVerificationStartEvent::transactionId() const +{ + return contentPart<QString>("transaction_id"_ls); +} + +QString KeyVerificationStartEvent::method() const +{ + return contentPart<QString>("method"_ls); +} + +Omittable<QString> KeyVerificationStartEvent::nextMethod() const +{ + return contentPart<Omittable<QString>>("method_ls"); +} + +QStringList KeyVerificationStartEvent::keyAgreementProtocols() const +{ + Q_ASSERT(method() == QStringLiteral("m.sas.v1")); + return contentPart<QStringList>("key_agreement_protocols"_ls); +} + +QStringList KeyVerificationStartEvent::hashes() const +{ + Q_ASSERT(method() == QStringLiteral("m.sas.v1")); + return contentPart<QStringList>("hashes"_ls); + +} + +QStringList KeyVerificationStartEvent::messageAuthenticationCodes() const +{ + Q_ASSERT(method() == QStringLiteral("m.sas.v1")); + return contentPart<QStringList>("message_authentication_codes"_ls); +} + +QString KeyVerificationStartEvent::shortAuthenticationString() const +{ + return contentPart<QString>("short_authentification_string"_ls); +} + +KeyVerificationAcceptEvent::KeyVerificationAcceptEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationAcceptEvent::transactionId() const +{ + return contentPart<QString>("transaction_id"_ls); +} + +QString KeyVerificationAcceptEvent::method() const +{ + return contentPart<QString>("method"_ls); +} + +QString KeyVerificationAcceptEvent::keyAgreementProtocol() const +{ + return contentPart<QString>("key_agreement_protocol"_ls); +} + +QString KeyVerificationAcceptEvent::hashData() const +{ + return contentPart<QString>("hash"_ls); +} + +QStringList KeyVerificationAcceptEvent::shortAuthenticationString() const +{ + return contentPart<QStringList>("short_authentification_string"_ls); +} + +QString KeyVerificationAcceptEvent::commitement() const +{ + return contentPart<QString>("commitment"_ls); +} + +KeyVerificationCancelEvent::KeyVerificationCancelEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationCancelEvent::transactionId() const +{ + return contentPart<QString>("transaction_id"_ls); +} + +QString KeyVerificationCancelEvent::reason() const +{ + return contentPart<QString>("reason"_ls); +} + +QString KeyVerificationCancelEvent::code() const +{ + return contentPart<QString>("code"_ls); +} + +KeyVerificationKeyEvent::KeyVerificationKeyEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationKeyEvent::transactionId() const +{ + return contentPart<QString>("transaction_id"_ls); +} + +QString KeyVerificationKeyEvent::key() const +{ + return contentPart<QString>("key"_ls); +} + +KeyVerificationMacEvent::KeyVerificationMacEvent(const QJsonObject &obj) + : Event(typeId(), obj) +{} + +QString KeyVerificationMacEvent::transactionId() const +{ + return contentPart<QString>("transaction_id"_ls); +} + +QString KeyVerificationMacEvent::keys() const +{ + return contentPart<QString>("keys"_ls); +} + +QHash<QString, QString> KeyVerificationMacEvent::mac() const +{ + return contentPart<QHash<QString, QString>>("mac"_ls); +} diff --git a/lib/events/keyverificationevent.h b/lib/events/keyverificationevent.h new file mode 100644 index 00000000..497e56a2 --- /dev/null +++ b/lib/events/keyverificationevent.h @@ -0,0 +1,161 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org> +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "event.h" + +namespace Quotient { + +/// Requests a key verification with another user's devices. +/// Typically sent as a to-device event. +class QUOTIENT_API KeyVerificationRequestEvent : public Event { +public: + DEFINE_EVENT_TYPEID("m.key.verification.request", KeyVerificationRequestEvent) + + explicit KeyVerificationRequestEvent(const QJsonObject& obj); + + /// The device ID which is initiating the request. + QString fromDevice() const; + + /// An opaque identifier for the verification request. Must + /// be unique with respect to the devices involved. + QString transactionId() const; + + /// The verification methods supported by the sender. + QStringList methods() const; + + /// The POSIX timestamp in milliseconds for when the request was + /// made. If the request is in the future by more than 5 minutes or + /// more than 10 minutes in the past, the message should be ignored + /// by the receiver. + uint64_t timestamp() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationRequestEvent) + +/// Begins a key verification process. +class QUOTIENT_API KeyVerificationStartEvent : public Event { +public: + DEFINE_EVENT_TYPEID("m.key.verification.start", KeyVerificationStartEvent) + + explicit KeyVerificationStartEvent(const QJsonObject &obj); + + /// The device ID which is initiating the process. + QString fromDevice() const; + + /// An opaque identifier for the verification request. Must + /// be unique with respect to the devices involved. + QString transactionId() const; + + /// The verification method to use. + QString method() const; + + /// Optional method to use to verify the other user's key with. + Omittable<QString> nextMethod() const; + + // SAS.V1 methods + + /// The key agreement protocols the sending device understands. + /// \note Only exist if method is m.sas.v1 + QStringList keyAgreementProtocols() const; + + /// The hash methods the sending device understands. + /// \note Only exist if method is m.sas.v1 + QStringList hashes() const; + + /// The message authentication codes that the sending device understands. + /// \note Only exist if method is m.sas.v1 + QStringList messageAuthenticationCodes() const; + + /// The SAS methods the sending device (and the sending device's + /// user) understands. + /// \note Only exist if method is m.sas.v1 + QString shortAuthenticationString() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationStartEvent) + +/// Accepts a previously sent m.key.verification.start message. +/// Typically sent as a to-device event. +class QUOTIENT_API KeyVerificationAcceptEvent : public Event { +public: + DEFINE_EVENT_TYPEID("m.key.verification.accept", KeyVerificationAcceptEvent) + + explicit KeyVerificationAcceptEvent(const QJsonObject& obj); + + /// An opaque identifier for the verification process. + QString transactionId() const; + + /// The verification method to use. Must be 'm.sas.v1'. + QString method() const; + + /// The key agreement protocol the device is choosing to use, out of + /// the options in the m.key.verification.start message. + QString keyAgreementProtocol() const; + + /// The hash method the device is choosing to use, out of the + /// options in the m.key.verification.start message. + QString hashData() const; + + /// The message authentication code the device is choosing to use, out + /// of the options in the m.key.verification.start message. + QString messageAuthenticationCode() const; + + /// The SAS methods both devices involved in the verification process understand. + QStringList shortAuthenticationString() const; + + /// The hash (encoded as unpadded base64) of the concatenation of the + /// device's ephemeral public key (encoded as unpadded base64) and the + /// canonical JSON representation of the m.key.verification.start message. + QString commitement() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationAcceptEvent) + +class QUOTIENT_API KeyVerificationCancelEvent : public Event { +public: + DEFINE_EVENT_TYPEID("m.key.verification.cancel", KeyVerificationCancelEvent) + + explicit KeyVerificationCancelEvent(const QJsonObject &obj); + + /// An opaque identifier for the verification process. + QString transactionId() const; + + /// A human readable description of the code. The client should only + /// rely on this string if it does not understand the code. + QString reason() const; + + /// The error code for why the process/request was cancelled by the user. + QString code() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationCancelEvent) + +/// Sends the ephemeral public key for a device to the partner device. +/// Typically sent as a to-device event. +class KeyVerificationKeyEvent : public Event { +public: + DEFINE_EVENT_TYPEID("m.key.verification.key", KeyVerificationKeyEvent) + + explicit KeyVerificationKeyEvent(const QJsonObject &obj); + + /// An opaque identifier for the verification process. + QString transactionId() const; + + /// The device's ephemeral public key, encoded as unpadded base64. + QString key() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationKeyEvent) + +/// Sends the MAC of a device's key to the partner device. +class QUOTIENT_API KeyVerificationMacEvent : public Event { +public: + DEFINE_EVENT_TYPEID("m.key.verification.mac", KeyVerificationMacEvent) + + explicit KeyVerificationMacEvent(const QJsonObject &obj); + + /// An opaque identifier for the verification process. + QString transactionId() const; + + /// The device's ephemeral public key, encoded as unpadded base64. + QString keys() const; + + QHash<QString, QString> mac() const; +}; +REGISTER_EVENT_TYPE(KeyVerificationMacEvent) +} // namespace Quotient diff --git a/lib/events/roomevent.cpp b/lib/events/roomevent.cpp index 3502e3f7..2f482871 100644 --- a/lib/events/roomevent.cpp +++ b/lib/events/roomevent.cpp @@ -122,3 +122,18 @@ CallEventBase::CallEventBase(Event::Type type, const QJsonObject& json) if (callId().isEmpty()) qCWarning(EVENTS) << id() << "is a call event with an empty call id"; } + +#ifdef Quotient_E2EE_ENABLED +void RoomEvent::setOriginalEvent(event_ptr_tt<RoomEvent>&& originalEvent) +{ + _originalEvent = std::move(originalEvent); +} + +const QJsonObject RoomEvent::encryptedJson() const +{ + if(!_originalEvent) { + return {}; + } + return _originalEvent->fullJson(); +} +#endif diff --git a/lib/events/roomevent.h b/lib/events/roomevent.h index dcee1170..c4b0131a 100644 --- a/lib/events/roomevent.h +++ b/lib/events/roomevent.h @@ -60,11 +60,21 @@ public: //! callback for that in RoomEvent. void addId(const QString& newId); +#ifdef Quotient_E2EE_ENABLED + void setOriginalEvent(event_ptr_tt<RoomEvent>&& originalEvent); + const RoomEvent* originalEvent() { return _originalEvent.get(); } + const QJsonObject encryptedJson() const; +#endif + protected: void dumpTo(QDebug dbg) const override; private: event_ptr_tt<RedactionEvent> _redactedBecause; + +#ifdef Quotient_E2EE_ENABLED + event_ptr_tt<RoomEvent> _originalEvent; +#endif }; using RoomEventPtr = event_ptr_tt<RoomEvent>; using RoomEvents = EventsArray<RoomEvent>; |