aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorKitsune Ral <Kitsune-Ral@users.sf.net>2019-07-31 17:33:31 +0900
committerGitHub <noreply@github.com>2019-07-31 17:33:31 +0900
commitf5083ee71e6fad9f28c4b835899f3ad574b426f1 (patch)
tree32039492751db53d89188a403f112e6c3e068af4 /lib
parent0df1cdcf40fd639f039f0c0e7165c8c40f6efd79 (diff)
parent2737dc00334ad3a56c1b311435dbe84453ee389e (diff)
downloadlibquotient-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.cpp5
-rw-r--r--lib/connection.h5
-rw-r--r--lib/e2ee.h26
-rw-r--r--lib/encryptionmanager.cpp31
-rw-r--r--lib/encryptionmanager.h6
-rw-r--r--lib/events/encryptedevent.cpp29
-rw-r--r--lib/events/encryptedevent.h66
-rw-r--r--lib/events/encryptionevent.cpp15
-rw-r--r--lib/events/event.h1
-rw-r--r--lib/events/roommessageevent.cpp7
-rw-r--r--lib/room.cpp87
-rw-r--r--lib/room.h6
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()
diff --git a/lib/room.h b/lib/room.h
index c79ca1e0..e09556b6 100644
--- a/lib/room.h
+++ b/lib/room.h
@@ -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;