diff options
author | Kitsune Ral <Kitsune-Ral@users.sf.net> | 2019-07-31 17:33:31 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-07-31 17:33:31 +0900 |
commit | f5083ee71e6fad9f28c4b835899f3ad574b426f1 (patch) | |
tree | 32039492751db53d89188a403f112e6c3e068af4 /lib | |
parent | 0df1cdcf40fd639f039f0c0e7165c8c40f6efd79 (diff) | |
parent | 2737dc00334ad3a56c1b311435dbe84453ee389e (diff) | |
download | libquotient-f5083ee71e6fad9f28c4b835899f3ad574b426f1.tar.gz libquotient-f5083ee71e6fad9f28c4b835899f3ad574b426f1.zip |
Merge pull request #335 from quotient-im/aa13q-e2ee-encrypted-msg
E2EE: introduce EncryptedEvent
Diffstat (limited to 'lib')
-rw-r--r-- | lib/connection.cpp | 5 | ||||
-rw-r--r-- | lib/connection.h | 5 | ||||
-rw-r--r-- | lib/e2ee.h | 26 | ||||
-rw-r--r-- | lib/encryptionmanager.cpp | 31 | ||||
-rw-r--r-- | lib/encryptionmanager.h | 6 | ||||
-rw-r--r-- | lib/events/encryptedevent.cpp | 29 | ||||
-rw-r--r-- | lib/events/encryptedevent.h | 66 | ||||
-rw-r--r-- | lib/events/encryptionevent.cpp | 15 | ||||
-rw-r--r-- | lib/events/event.h | 1 | ||||
-rw-r--r-- | lib/events/roommessageevent.cpp | 7 | ||||
-rw-r--r-- | lib/room.cpp | 87 | ||||
-rw-r--r-- | lib/room.h | 6 |
12 files changed, 257 insertions, 27 deletions
diff --git a/lib/connection.cpp b/lib/connection.cpp index 1bd2e32e..b9ab5147 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -979,6 +979,11 @@ QByteArray Connection::accessToken() const return d->data->accessToken(); } +QtOlm::Account* Connection::olmAccount() const +{ + return d->encryptionManager->account(); +} + SyncJob* Connection::syncJob() const { return d->syncJob; diff --git a/lib/connection.h b/lib/connection.h index 11499a6e..199803d7 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -30,6 +30,10 @@ #include <functional> +namespace QtOlm { + class Account; +} + namespace QMatrixClient { class Room; @@ -264,6 +268,7 @@ namespace QMatrixClient QString userId() const; QString deviceId() const; QByteArray accessToken() const; + QtOlm::Account* olmAccount() const; Q_INVOKABLE SyncJob* syncJob() const; Q_INVOKABLE int millisToReconnect() const; diff --git a/lib/e2ee.h b/lib/e2ee.h new file mode 100644 index 00000000..4a42809d --- /dev/null +++ b/lib/e2ee.h @@ -0,0 +1,26 @@ +#pragma once + +#include <QtCore/QStringList> + +namespace QMatrixClient +{ + static const auto CiphertextKeyL = "ciphertext"_ls; + static const auto SenderKeyKeyL = "sender_key"_ls; + static const auto DeviceIdKeyL = "device_id"_ls; + static const auto SessionIdKeyL = "session_id"_ls; + + static const auto AlgorithmKeyL = "algorithm"_ls; + static const auto RotationPeriodMsKeyL = "rotation_period_ms"_ls; + static const auto RotationPeriodMsgsKeyL = "rotation_period_msgs"_ls; + + static const auto AlgorithmKey = QStringLiteral("algorithm"); + static const auto RotationPeriodMsKey = QStringLiteral("rotation_period_ms"); + static const auto RotationPeriodMsgsKey = QStringLiteral("rotation_period_msgs"); + + static const auto Ed25519Key = QStringLiteral("ed25519"); + static const auto Curve25519Key = QStringLiteral("curve25519"); + static const auto SignedCurve25519Key = QStringLiteral("signed_curve25519"); + static const auto OlmV1Curve25519AesSha2AlgoKey = QStringLiteral("m.olm.v1.curve25519-aes-sha2"); + static const auto MegolmV1AesSha2AlgoKey = QStringLiteral("m.megolm.v1.aes-sha2"); + static const QStringList SupportedAlgorithms = { OlmV1Curve25519AesSha2AlgoKey, MegolmV1AesSha2AlgoKey }; +} // namespace QMatrixClient diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 50db9889..3533d791 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -8,18 +8,12 @@ #include "csapi/keys.h" #include "connection.h" +#include "e2ee.h" using namespace QMatrixClient; using namespace QtOlm; using std::move; -static const auto ed25519Name = QStringLiteral("ed25519"); -static const auto Curve25519Name = QStringLiteral("curve25519"); -static const auto SignedCurve25519Name = QStringLiteral("signed_curve25519"); -static const auto OlmV1Curve25519AesSha2AlgoName = QStringLiteral("m.olm.v1.curve25519-aes-sha2"); -static const auto MegolmV1AesSha2AlgoName = QStringLiteral("m.megolm.v1.aes-sha2"); -static const QStringList SupportedAlgorithms = { OlmV1Curve25519AesSha2AlgoName, MegolmV1AesSha2AlgoName }; - class EncryptionManager::Private { public: @@ -49,8 +43,8 @@ class EncryptionManager::Private targetKeysNumber = olmAccount->maxOneTimeKeys(); // 2 // see note below targetOneTimeKeyCounts = { - {SignedCurve25519Name, qRound(signedKeysProportion * targetKeysNumber)}, - {Curve25519Name, qRound((1-signedKeysProportion) * targetKeysNumber)} + {SignedCurve25519Key, qRound(signedKeysProportion * targetKeysNumber)}, + {Curve25519Key, qRound((1-signedKeysProportion) * targetKeysNumber)} }; } ~Private() = default; @@ -104,11 +98,11 @@ void EncryptionManager::uploadIdentityKeys(Connection* connection) */ { { - Curve25519Name + QStringLiteral(":") + connection->deviceId(), + Curve25519Key + QStringLiteral(":") + connection->deviceId(), d->olmAccount->curve25519IdentityKey() }, { - ed25519Name + QStringLiteral(":") + connection->deviceId(), + Ed25519Key + QStringLiteral(":") + connection->deviceId(), d->olmAccount->ed25519IdentityKey() } }, @@ -133,7 +127,7 @@ void EncryptionManager::uploadIdentityKeys(Connection* connection) connection->userId(), { { - ed25519Name + QStringLiteral(":") + connection->deviceId(), + Ed25519Key + QStringLiteral(":") + connection->deviceId(), d->olmAccount->sign(deviceKeysJsonObject) } } @@ -158,8 +152,8 @@ void EncryptionManager::uploadOneTimeKeys(Connection* connection, bool forceUpda } - int signedKeysToUploadCount = d->oneTimeKeysToUploadCounts.value(SignedCurve25519Name, 0); - int unsignedKeysToUploadCount = d->oneTimeKeysToUploadCounts.value(Curve25519Name, 0); + int signedKeysToUploadCount = d->oneTimeKeysToUploadCounts.value(SignedCurve25519Key, 0); + int unsignedKeysToUploadCount = d->oneTimeKeysToUploadCounts.value(Curve25519Key, 0); d->olmAccount->generateOneTimeKeys(signedKeysToUploadCount + unsignedKeysToUploadCount); @@ -179,11 +173,11 @@ void EncryptionManager::uploadOneTimeKeys(Connection* connection, bool forceUpda {QStringLiteral("key"), it.value().toString()} }; key = d->olmAccount->sign(message); - keyType = SignedCurve25519Name; + keyType = SignedCurve25519Key; } else { key = it.value(); - keyType = Curve25519Name; + keyType = Curve25519Key; } ++oneTimeKeysCounter; oneTimeKeys.insert(QString("%1:%2").arg(keyType).arg(keyId), key); @@ -200,6 +194,11 @@ QByteArray EncryptionManager::olmAccountPickle() return d->olmAccount->pickle(); // TODO: passphrase even with qtkeychain? } +QtOlm::Account* EncryptionManager::account() const +{ + return d->olmAccount.data(); +} + void EncryptionManager::Private::updateKeysToUpload() { for (auto it = targetOneTimeKeyCounts.cbegin(); it != targetOneTimeKeyCounts.cend(); ++it) diff --git a/lib/encryptionmanager.h b/lib/encryptionmanager.h index 40fe7383..0225969d 100644 --- a/lib/encryptionmanager.h +++ b/lib/encryptionmanager.h @@ -4,6 +4,10 @@ #include <memory> #include <QtCore/QObject> +namespace QtOlm { + class Account; +} + namespace QMatrixClient { class Connection; @@ -23,6 +27,8 @@ namespace QMatrixClient void uploadOneTimeKeys(Connection* connection, bool forceUpdate = false); QByteArray olmAccountPickle(); + QtOlm::Account* account() const; + private: class Private; std::unique_ptr<Private> d; diff --git a/lib/events/encryptedevent.cpp b/lib/events/encryptedevent.cpp new file mode 100644 index 00000000..6942738a --- /dev/null +++ b/lib/events/encryptedevent.cpp @@ -0,0 +1,29 @@ +#include "encryptedevent.h" +#include "room.h" + +using namespace QMatrixClient; +using namespace QtOlm; + +EncryptedEvent::EncryptedEvent(const QJsonObject &ciphertext, const QString &senderKey) + : RoomEvent(typeId(), matrixTypeId(), + { { AlgorithmKeyL , OlmV1Curve25519AesSha2AlgoKey }, + { CiphertextKeyL , ciphertext }, + { SenderKeyKeyL, senderKey } + }) +{ } + +EncryptedEvent::EncryptedEvent(QByteArray ciphertext, const QString &senderKey, const QString& deviceId, const QString& sessionId) + : RoomEvent(typeId(), matrixTypeId(), + { { AlgorithmKeyL , MegolmV1AesSha2AlgoKey }, + { CiphertextKeyL , QString(ciphertext) }, + { DeviceIdKeyL, deviceId }, + { SenderKeyKeyL, senderKey }, + { SessionIdKeyL, sessionId }, + }) +{ } + +EncryptedEvent::EncryptedEvent(const QJsonObject &obj) + : RoomEvent(typeId(), obj) +{ + qCDebug(EVENTS) << "Encrypted event" << id(); +} diff --git a/lib/events/encryptedevent.h b/lib/events/encryptedevent.h new file mode 100644 index 00000000..2f9e4422 --- /dev/null +++ b/lib/events/encryptedevent.h @@ -0,0 +1,66 @@ +#pragma once + +#include "roomevent.h" +#include "e2ee.h" + +namespace QMatrixClient +{ + class Room; + /* + * While the specification states: + * + * "This event type is used when sending encrypted events. + * It can be used either within a room + * (in which case it will have all of the Room Event fields), + * or as a to-device event." + * "The encrypted payload can contain any message event." + * https://matrix.org/docs/spec/client_server/latest#id493 + * + * -- for most of the cases the message event is the room message event. + * And even for the to-device events the context is for the room. + * + * So, to simplify integration to the timeline, EncryptedEvent is a RoomEvent inheritor. + * Strictly speaking though, it's not always a RoomEvent, but an Event in general. + * It's possible, because RoomEvent interface is similar to Event's one + * and doesn't add new restrictions, just provides additional features. + */ + class EncryptedEvent : public RoomEvent + { + Q_GADGET + public: + DEFINE_EVENT_TYPEID("m.room.encrypted", EncryptedEvent) + + /* In case with Olm, the encrypted content of the event is + * a map from the recipient Curve25519 identity key to ciphertext information */ + explicit EncryptedEvent(const QJsonObject& ciphertext, + const QString& senderKey); + /* In case with Megolm, device_id and session_id are required */ + explicit EncryptedEvent(QByteArray ciphertext, + const QString& senderKey, + const QString& deviceId, + const QString& sessionId); + explicit EncryptedEvent(const QJsonObject& obj); + + QString algorithm() const + { + QString algo = content<QString>(AlgorithmKeyL); + if (!SupportedAlgorithms.contains(algo)) { + qWarning(MAIN) << "The EncryptedEvent's algorithm" << algo + << "is not supported"; + } + return algo; + } + QByteArray ciphertext() const { return content<QString>(CiphertextKeyL).toLatin1(); } + QJsonObject ciphertext(const QString& identityKey) const + { + return content<QJsonObject>(CiphertextKeyL).value(identityKey).toObject(); + } + QString senderKey() const { return content<QString>(SenderKeyKeyL); } + + /* device_id and session_id are required with Megolm */ + QString deviceId() const { return content<QString>(DeviceIdKeyL); } + QString sessionId() const { return content<QString>(SessionIdKeyL); } + }; + REGISTER_EVENT_TYPE(EncryptedEvent) + +} // namespace QMatrixClient diff --git a/lib/events/encryptionevent.cpp b/lib/events/encryptionevent.cpp index b8e2b575..ee6c92b1 100644 --- a/lib/events/encryptionevent.cpp +++ b/lib/events/encryptionevent.cpp @@ -7,11 +7,12 @@ #include "converters.h" #include "logging.h" +#include "e2ee.h" #include <array> static const std::array<QString, 1> encryptionStrings = { { - QStringLiteral("m.megolm.v1.aes-sha2") + QMatrixClient::MegolmV1AesSha2AlgoKey } }; namespace QMatrixClient { @@ -36,9 +37,9 @@ using namespace QMatrixClient; EncryptionEventContent::EncryptionEventContent(const QJsonObject& json) : encryption(fromJson<EncryptionType>(json["algorithm"_ls])) - , algorithm(sanitized(json["algorithm"_ls].toString())) - , rotationPeriodMs(json["rotation_period_ms"_ls].toInt(604800000)) - , rotationPeriodMsgs(json["rotation_period_msgs"_ls].toInt(100)) + , algorithm(sanitized(json[AlgorithmKeyL].toString())) + , rotationPeriodMs(json[RotationPeriodMsKeyL].toInt(604800000)) + , rotationPeriodMsgs(json[RotationPeriodMsgsKeyL].toInt(100)) { } void EncryptionEventContent::fillJson(QJsonObject* o) const @@ -47,7 +48,7 @@ void EncryptionEventContent::fillJson(QJsonObject* o) const Q_ASSERT_X(encryption != EncryptionType::Undefined, __FUNCTION__, "The key 'algorithm' must be explicit in EncryptionEventContent"); if (encryption != EncryptionType::Undefined) - o->insert(QStringLiteral("algorithm"), algorithm); - o->insert(QStringLiteral("rotation_period_ms"), rotationPeriodMs); - o->insert(QStringLiteral("rotation_period_msgs"), rotationPeriodMsgs); + o->insert(AlgorithmKey, algorithm); + o->insert(RotationPeriodMsKey, rotationPeriodMs); + o->insert(RotationPeriodMsgsKey, rotationPeriodMsgs); } diff --git a/lib/events/event.h b/lib/events/event.h index b3a58806..6f28c4fa 100644 --- a/lib/events/event.h +++ b/lib/events/event.h @@ -62,6 +62,7 @@ namespace QMatrixClient static const auto UnsignedKey = QStringLiteral("unsigned"); static const auto StateKeyKey = QStringLiteral("state_key"); static const auto TypeKeyL = "type"_ls; + static const auto BodyKeyL = "body"_ls; static const auto ContentKeyL = "content"_ls; static const auto EventIdKeyL = "event_id"_ls; static const auto UnsignedKeyL = "unsigned"_ls; diff --git a/lib/events/roommessageevent.cpp b/lib/events/roommessageevent.cpp index 8f4e0ebc..ec18e962 100644 --- a/lib/events/roommessageevent.cpp +++ b/lib/events/roommessageevent.cpp @@ -32,7 +32,6 @@ using MsgType = RoomMessageEvent::MsgType; static const auto RelatesToKey = "m.relates_to"_ls; static const auto MsgTypeKey = "msgtype"_ls; -static const auto BodyKey = "body"_ls; static const auto FormattedBodyKey = "formatted_body"_ls; static const auto TextTypeKey = "m.text"; @@ -159,7 +158,7 @@ RoomMessageEvent::RoomMessageEvent(const QJsonObject& obj) if (isRedacted()) return; const QJsonObject content = contentJson(); - if ( content.contains(MsgTypeKey) && content.contains(BodyKey) ) + if ( content.contains(MsgTypeKey) && content.contains(BodyKeyL) ) { auto msgtype = content[MsgTypeKey].toString(); bool msgTypeFound = false; @@ -196,7 +195,7 @@ QString RoomMessageEvent::rawMsgtype() const QString RoomMessageEvent::plainBody() const { - return contentJson()[BodyKey].toString(); + return contentJson()[BodyKeyL].toString(); } QMimeType RoomMessageEvent::mimeType() const @@ -267,7 +266,7 @@ TextContent::TextContent(const QJsonObject& json) // Falling back to plain text, as there's no standard way to describe // rich text in messages. mimeType = PlainTextMimeType; - body = json[BodyKey].toString(); + body = json[BodyKeyL].toString(); } const auto replyJson = json[RelatesToKey].toObject() .value(RelatesTo::ReplyTypeId()).toObject(); diff --git a/lib/room.cpp b/lib/room.cpp index cb368d9e..0402ce67 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -52,6 +52,12 @@ #include "converters.h" #include "syncdata.h" +#include "e2ee.h" + +#include <session.h> // QtOlm +#include <groupsession.h> // QtOlm +#include <message.h> // QtOlm + #include <QtCore/QHash> #include <QtCore/QStringBuilder> // for efficient string concats (operator%) #include <QtCore/QPointer> @@ -1187,6 +1193,87 @@ bool Room::usesEncryption() const return !d->getCurrentState<EncryptionEvent>()->algorithm().isEmpty(); } +const RoomEvent* Room::decryptMessage(EncryptedEvent *encryptedEvent) const +{ + if (encryptedEvent->algorithm() == OlmV1Curve25519AesSha2AlgoKey) { + QString identityKey = connection()->olmAccount()->curve25519IdentityKey(); + QJsonObject personalCipherObject = encryptedEvent->ciphertext(identityKey); + if (personalCipherObject.isEmpty()) { + qCDebug(EVENTS) << "Encrypted event is not for the current device"; + return nullptr; + } + return makeEvent<RoomMessageEvent>(decryptMessage(personalCipherObject, encryptedEvent->senderKey().toLatin1())).get(); + } + if (encryptedEvent->algorithm() == MegolmV1AesSha2AlgoKey) { + return makeEvent<RoomMessageEvent>(decryptMessage(encryptedEvent->ciphertext(), encryptedEvent->senderKey(), encryptedEvent->deviceId(), encryptedEvent->sessionId())).get(); + } + return nullptr; +} + +const QString Room::decryptMessage(QJsonObject personalCipherObject, QByteArray senderKey) const +{ + QString decrypted; + + using namespace QtOlm; + // TODO: new objects to private fields: + InboundSession* session; + + int type = personalCipherObject.value(TypeKeyL).toInt(-1); + QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); + + PreKeyMessage* preKeyMessage = new PreKeyMessage(body); + session = new InboundSession(connection()->olmAccount(), preKeyMessage, senderKey); + if (type == 0) { + if (!session->matches(preKeyMessage, senderKey)) + { + connection()->olmAccount()->removeOneTimeKeys(session); + } + try + { + decrypted = session->decrypt(preKeyMessage); + } + catch(std::runtime_error& e) + { + qWarning(EVENTS) << "Decrypt failed:" << e.what(); + } + } + else if (type == 1) + { + Message* message = new Message(body); + if (!session->matches(preKeyMessage, senderKey)) + { + qWarning(EVENTS) << "Invalid encrypted message"; + } + try + { + decrypted = session->decrypt(message); + } + catch(std::runtime_error& e) + { + qWarning(EVENTS) << "Decrypt failed:" << e.what(); + } + } + + return decrypted; +} + +const QString Room::sessionKey(const QString& senderKey, const QString& deviceId, const QString& sessionId) const +{ + // TODO: handling an m.room_key event + return ""; +} + +const QString Room::decryptMessage(QByteArray cipher, const QString& senderKey, const QString& deviceId, const QString& sessionId) const +{ + QString decrypted; + using namespace QtOlm; + InboundGroupSession* groupSession; + groupSession = new InboundGroupSession(sessionKey(senderKey, deviceId, sessionId).toLatin1()); + groupSession->decrypt(cipher); + // TODO: avoid replay attacks + return decrypted; +} + int Room::joinedCount() const { return d->summary.joinedMemberCount.omitted() @@ -24,8 +24,10 @@ #include "events/accountdataevents.h" #include "eventitem.h" #include "joinstate.h" +#include "events/encryptedevent.h" #include <QtGui/QImage> +#include <QtCore/QJsonObject> #include <memory> #include <deque> @@ -179,6 +181,10 @@ namespace QMatrixClient int memberCount() const; int timelineSize() const; bool usesEncryption() const; + const RoomEvent *decryptMessage(EncryptedEvent* encryptedEvent) const; + const QString decryptMessage(QJsonObject personalCipherObject, QByteArray senderKey) const; + const QString sessionKey(const QString &senderKey, const QString &deviceId, const QString &sessionId) const; + const QString decryptMessage(QByteArray cipher, const QString& senderKey, const QString& deviceId, const QString& sessionId) const; int joinedCount() const; int invitedCount() const; int totalMemberCount() const; |