Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F140432
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
84 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/client/actions/encryption.cpp b/src/client/actions/encryption.cpp
index fba2c49..65335f2 100644
--- a/src/client/actions/encryption.cpp
+++ b/src/client/actions/encryption.cpp
@@ -1,535 +1,550 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#include <zug/transducer/filter.hpp>
#include <zug/transducer/cat.hpp>
#include "encryption.hpp"
#include <debug.hpp>
#include "cursorutil.hpp"
namespace Kazv
{
using namespace CryptoConstants;
static json convertSignature(const ClientModel &m, std::string signature)
{
auto j = json::object();
j[m.userId] = json::object();
j[m.userId][ed25519 + ":" + m.deviceId] = signature;
return j;
}
ClientResult updateClient(ClientModel m, UploadIdentityKeysAction)
{
if (! m.crypto) {
kzo.client.warn() << "Client::crypto is invalid, ignoring it." << std::endl;
return { std::move(m), lager::noop };
}
auto &crypto = m.crypto.value();
auto keys =
immer::map<std::string, std::string>{}
.set(ed25519 + ":" + m.deviceId, crypto.ed25519IdentityKey())
.set(curve25519 + ":" + m.deviceId, crypto.curve25519IdentityKey());
DeviceKeys k {
m.userId,
m.deviceId,
{olmAlgo, megOlmAlgo},
keys,
{} // signatures to be added soon
};
auto j = json(k);
auto sig = crypto.sign(j);
k.signatures = convertSignature(m, sig);
auto job = m.job<UploadKeysJob>()
.make(k)
.withData(json{{"is", "identityKeys"}});
kzo.client.dbg() << "Uploading identity keys" << std::endl;
m.addJob(std::move(job));
return { std::move(m), lager::noop };
}
ClientResult updateClient(ClientModel m, GenerateAndUploadOneTimeKeysAction)
{
if (! m.crypto) {
kzo.client.warn() << "Client::crypto is invalid, ignoring it." << std::endl;
return { std::move(m), lager::noop };
}
auto &crypto = m.crypto.value();
// Keep half of max supported number of keys
int numUploadedKeys = crypto.uploadedOneTimeKeysCount(signedCurve25519);
int numKeysNeeded = crypto.maxNumberOfOneTimeKeys() / 2
- numUploadedKeys;
// Subtract the number of existing one-time keys, in case
// the previous upload was not successful.
int numKeysToGenerate = numKeysNeeded - crypto.numUnpublishedOneTimeKeys();
kzo.client.dbg() << "Generating one-time keys..." << std::endl;
kzo.client.dbg() << "Number needed: " << numKeysNeeded << std::endl;
if (numKeysNeeded <= 0) { // we have enough already
kzo.client.dbg() << "We have enough one-time keys. Ignoring this." << std::endl;
return { std::move(m), lager::noop };
}
if (numKeysToGenerate > 0) {
crypto.genOneTimeKeys(numKeysToGenerate);
}
kzo.client.dbg() << "Generating done." << std::endl;
auto keys = crypto.unpublishedOneTimeKeys();
auto cv25519Keys = keys.at(curve25519);
immer::map<std::string, Variant> oneTimeKeys;
for (auto [id, keyStr] : cv25519Keys.items()) {
json keyObject = json::object();
keyObject["key"] = keyStr;
keyObject["signatures"] = convertSignature(m, crypto.sign(keyObject));
oneTimeKeys = std::move(oneTimeKeys).set(signedCurve25519 + ":" + id, JsonWrap(keyObject));
}
auto job = m.job<UploadKeysJob>()
.make(
std::nullopt, // deviceKeys
oneTimeKeys)
.withData(json{{"is", "oneTimeKeys"}});
kzo.client.dbg() << "Uploading one time keys" << std::endl;
m.addJob(std::move(job));
return { std::move(m), lager::noop };
};
ClientResult processResponse(ClientModel m, UploadKeysResponse r)
{
if (! m.crypto) {
kzo.client.warn() << "Client::crypto is invalid, ignoring it." << std::endl;
return { std::move(m), lager::noop };
}
auto &crypto = m.crypto.value();
auto is = r.dataStr("is");
if (is == "identityKeys") {
if (! r.success()) {
kzo.client.dbg() << "Uploading identity keys failed" << std::endl;
m.addTrigger(UploadIdentityKeysFailed{r.errorCode(), r.errorMessage()});
return { std::move(m), lager::noop };
}
kzo.client.dbg() << "Uploading identity keys successful" << std::endl;
m.addTrigger(UploadIdentityKeysSuccessful{});
m.identityKeysUploaded = true;
} else {
if (! r.success()) {
kzo.client.dbg() << "Uploading one-time keys failed" << std::endl;
m.addTrigger(UploadOneTimeKeysFailed{r.errorCode(), r.errorMessage()});
return { std::move(m), lager::noop };
}
kzo.client.dbg() << "Uploading one-time keys successful" << std::endl;
m.addTrigger(UploadOneTimeKeysSuccessful{});
crypto.markOneTimeKeysAsPublished();
}
crypto.setUploadedOneTimeKeysCount(r.oneTimeKeyCounts());
return { std::move(m), lager::noop };
}
static JsonWrap cannotDecryptEvent(std::string reason)
{
return json{
{"type", "m.room.message"},
{"content", {
{"msgtype","moe.kazv.mxc.cannot.decrypt"},
{"body", "**This message cannot be decrypted due to " + reason + ".**"}}}};
}
static bool verifyEvent(ClientModel &m, Event e, const json &plainJson)
{
auto crypto = m.crypto.value();
bool valid = true;
try {
std::string senderCurve25519Key = e.originalJson().get()
.at("content").at("sender_key");
auto deviceInfoOpt = m.deviceLists.findByCurve25519Key(e.sender(), senderCurve25519Key);
if (! deviceInfoOpt) {
kzo.client.dbg() << "Device key " << senderCurve25519Key
<< " unknown, thus invalid" << std::endl;
valid = false;
}
auto deviceInfo = deviceInfoOpt.value();
std::string algo = e.originalJson().get().at("content").at("algorithm");
if (algo == olmAlgo) {
if (! (plainJson.at("sender") == e.sender())) {
kzo.client.dbg() << "Sender does not match, thus invalid" << std::endl;
valid = false;
}
if (! (plainJson.at("recipient") == m.userId)) {
kzo.client.dbg() << "Recipient does not match, thus invalid" << std::endl;
valid = false;
}
if (! (plainJson.at("recipient_keys").at(ed25519) == crypto.ed25519IdentityKey())) {
kzo.client.dbg() << "Recipient key does not match, thus invalid" << std::endl;
valid = false;
}
auto thisEd25519Key = plainJson.at("keys").at(ed25519).get<std::string>();
if (thisEd25519Key != deviceInfo.ed25519Key) {
kzo.client.dbg() << "Sender ed25519 key does not match, thus invalid" << std::endl;
valid = false;
}
} else if (algo == megOlmAlgo) {
if (! (plainJson.at("room_id").get<std::string>() ==
e.originalJson().get().at("room_id").get<std::string>())) {
kzo.client.dbg() << "Room id does not match, thus invalid" << std::endl;
valid = false;
}
if (e.originalJson().get().at("content").at("device_id").get<std::string>()
!= deviceInfo.deviceId) {
kzo.client.dbg() << "Device id does not match, thus invalid" << std::endl;
valid = false;
}
auto actualEd25519Key = crypto.getInboundGroupSessionEd25519KeyFromEvent(e.originalJson().get());
if ((! actualEd25519Key)
|| deviceInfo.ed25519Key != actualEd25519Key.value()) {
kzo.client.dbg() << "sender ed25519 key does not match, thus invalid" << std::endl;
kzo.client.dbg() << "From group session: "
<< (actualEd25519Key ? actualEd25519Key.value() : "<none>") << std::endl;
kzo.client.dbg() << "From device info: " << deviceInfo.ed25519Key << std::endl;
valid = false;
}
} else {
kzo.client.dbg() << "Unknown algorithm, thus invalid" << std::endl;
valid = false;
}
} catch (const std::exception &) {
kzo.client.dbg() << "json format is not correct, thus invalid" << std::endl;
valid = false;
}
return valid;
}
static Event decryptEvent(ClientModel &m, Event e)
{
// no need for decryption
if (e.decrypted() || (! e.encrypted())) {
return e;
}
auto &crypto = m.crypto.value();
kzo.client.dbg() << "About to decrypt event: "
<< e.originalJson().get().dump() << std::endl;
auto maybePlainText = crypto.decrypt(e.originalJson().get());
if (! maybePlainText) {
kzo.client.dbg() << "Cannot decrypt: " << maybePlainText.reason() << std::endl;
return e.setDecryptedJson(cannotDecryptEvent(maybePlainText.reason()), Event::NotDecrypted);
} else {
kzo.client.dbg() << "Decrypted message: " << maybePlainText.value() << std::endl;
auto plainJson = json::parse(maybePlainText.value());
auto valid = verifyEvent(m, e, plainJson);
if (valid) {
kzo.client.dbg() << "The decrypted event is valid." << std::endl;
}
return valid
? e.setDecryptedJson(plainJson, Event::Decrypted)
: e.setDecryptedJson(cannotDecryptEvent("invalid event"), Event::NotDecrypted);
}
}
ClientModel tryDecryptEvents(ClientModel m)
{
if (! m.crypto) {
kzo.client.dbg() << "We have no encryption enabled--ignoring decryption request" << std::endl;
return m;
}
auto &crypto = m.crypto.value();
kzo.client.dbg() << "Trying to decrypt events..." << std::endl;
auto decryptFunc = [&](auto e) { return decryptEvent(m, e); };
auto takeOutRoomKeyEvents =
[&](auto e) {
if (e.type() != "m.room_key") {
// Leave it as it is
return true;
}
auto content = e.content();
std::string roomId = content.get().at("room_id");
std::string sessionId = content.get().at("session_id");
std::string sessionKey = content.get().at("session_key");
std::string senderKey = e.originalJson().get().at("content").at("sender_key");
auto k = KeyOfGroupSession{roomId, senderKey, sessionId};
std::string ed25519Key = e.decryptedJson().get().at("keys").at(ed25519);
if (crypto.createInboundGroupSession(k, sessionKey, ed25519Key)) {
return false; // such that this event is removed
}
return true;
};
m.toDevice = intoImmer(
EventList{},
zug::map(decryptFunc)
| zug::filter(takeOutRoomKeyEvents),
std::move(m.toDevice));
auto decryptEventInRoom =
[&](auto id, auto room) {
if (! room.encrypted) {
return;
} else {
auto messages = room.messages;
room.messages = merge(
room.messages,
intoImmer(
EventList{},
zug::filter([](auto n) {
auto e = n.second;
return e.encrypted();
})
| zug::map([=](auto n) {
auto event = n.second;
return decryptFunc(event);
}),
std::move(messages)),
keyOfTimeline);
m.roomList.rooms = std::move(m.roomList.rooms).set(id, room);
}
};
auto rooms = m.roomList.rooms;
for (auto [id, room]: rooms) {
decryptEventInRoom(id, room);
}
return m;
}
ClientResult updateClient(ClientModel m, QueryKeysAction a)
{
if (! m.crypto) {
kzo.client.dbg() << "We have no encryption enabled--ignoring this" << std::endl;
return { std::move(m), lager::noop };
}
immer::map<std::string, immer::array<std::string>> deviceKeys;
auto encryptedUsers = m.deviceLists.outdatedUsers();
if (encryptedUsers.empty()) {
kzo.client.dbg() << "Keys are up-to-date." << std::endl;
return { std::move(m), lager::noop };
}
kzo.client.dbg() << "We need to query keys for: " << std::endl;
for (auto userId: encryptedUsers) {
kzo.client.dbg() << userId << std::endl;
deviceKeys = std::move(deviceKeys).set(userId, {});
}
kzo.client.dbg() << "^" << std::endl;
auto job = m.job<QueryKeysJob>()
.make(std::move(deviceKeys),
std::nullopt, // timeout
a.isInitialSync ? std::nullopt : m.syncToken
);
m.addJob(std::move(job));
return { std::move(m), lager::noop };
}
ClientResult processResponse(ClientModel m, QueryKeysResponse r)
{
if (! m.crypto) {
kzo.client.dbg() << "We have no encryption enabled--ignoring this" << std::endl;
return { std::move(m), lager::noop };
}
if (! r.success()) {
kzo.client.dbg() << "query keys failed" << std::endl;
return { std::move(m), lager::noop };
}
kzo.client.dbg() << "Received a query key response" << std::endl;
auto &crypto = m.crypto.value();
auto usersMap = r.deviceKeys();
for (auto [userId, deviceMap] : usersMap) {
for (auto [deviceId, deviceInfo] : deviceMap) {
kzo.client.dbg() << "Key for " << userId
<< "/" << deviceId
<< ": " << json(deviceInfo).dump()
<< std::endl;
m.deviceLists.addDevice(userId, deviceId, deviceInfo, crypto);
}
m.deviceLists.markUpToDate(userId);
}
return { std::move(m), lager::noop };
}
ClientResult updateClient(ClientModel m, ClaimKeysAndSendSessionKeyAction a)
{
if (! m.crypto) {
kzo.client.dbg() << "We have no encryption enabled--ignoring this" << std::endl;
return { std::move(m), lager::noop };
}
auto &c = m.crypto.value();
kzo.client.dbg() << "claim keys for: " << json(a.devicesToSend).dump() << std::endl;
- auto devicesToClaimKeys = c.devicesMissingOutboundSessionKey(a.devicesToSend);
+ auto keyMap = immer::map<std::string, immer::map<std::string /* deviceId */,
+ std::string /* curve25519IdentityKey */>>{};
+
+
+ for (auto [userId, devices] : a.devicesToSend) {
+ auto deviceToKey = immer::map<std::string, std::string>{};
+ for (auto deviceId : devices) {
+ auto infoOpt = m.deviceLists.get(userId, deviceId);
+ if (infoOpt) {
+ deviceToKey = std::move(deviceToKey)
+ .set(deviceId, infoOpt.value().curve25519Key);
+ }
+ }
+ keyMap = std::move(keyMap).set(userId, deviceToKey);
+ }
+
+ auto devicesToClaimKeys = c.devicesMissingOutboundSessionKey(keyMap);
auto oneTimeKeys = immer::map<std::string, immer::map<std::string, std::string>>{};
for (auto [userId, devices] : devicesToClaimKeys) {
auto devKeys = immer::map<std::string, std::string>{};
for (auto deviceId: devices) {
devKeys = std::move(devKeys).set(deviceId, signedCurve25519);
}
oneTimeKeys = std::move(oneTimeKeys).set(userId, devKeys);
}
auto job = m.job<ClaimKeysJob>()
.make(std::move(oneTimeKeys))
.withData(json{
{"roomId", a.roomId},
{"sessionId", a.sessionId},
{"sessionKey", a.sessionKey},
{"devicesToSend", a.devicesToSend}
});
m.addJob(std::move(job));
return { std::move(m), lager::noop };
}
ClientResult processResponse(ClientModel m, ClaimKeysResponse r)
{
if (! m.crypto) {
kzo.client.dbg() << "We have no encryption enabled--ignoring this" << std::endl;
return { std::move(m), lager::noop };
}
if (! r.success()) {
kzo.client.dbg() << "claim keys failed" << std::endl;
m.addTrigger(ClaimKeysFailed{r.errorCode(), r.errorMessage()});
return { std::move(m), lager::noop };
}
kzo.client.dbg() << "claim keys successful" << std::endl;
auto &c = m.crypto.value();
auto roomId = r.dataStr("roomId");
auto sessionKey = r.dataStr("sessionKey");
auto sessionId = r.dataStr("sessionId");
auto devicesToSend =
immer::map<std::string, immer::flex_vector<std::string>>(r.dataJson("devicesToSend"));
if (! r.success()) {
kzo.client.dbg() << "claiming keys failed" << std::endl;
return { std::move(m), lager::noop };
}
// create outbound sessions for those devices
auto oneTimeKeys = r.oneTimeKeys();
for (auto [userId, deviceMap] : oneTimeKeys) {
for (auto [deviceId, keyVar] : deviceMap) {
if (std::holds_alternative<JsonWrap>(keyVar)) {
auto keys = std::get<JsonWrap>(keyVar).get();
for (auto [keyId, key] : keys.items()) {
auto deviceInfoOpt = m.deviceLists.get(userId, deviceId);
if (deviceInfoOpt) {
auto deviceInfo = deviceInfoOpt.value();
kzo.client.dbg() << "Verifying key for " << userId
<< "/" << deviceId
<< key.dump()
<< " with ed25519 key "
<< deviceInfo.ed25519Key << std::endl;
auto verified = c.verify(key, userId, deviceId, deviceInfo.ed25519Key);
kzo.client.dbg() << (verified ? "passed" : "did not pass") << std::endl;
if (verified && key.contains("key")) {
auto theirOneTimeKey = key.at("key");
kzo.client.dbg() << "creating outbound session for it" << std::endl;
- c.createOutboundSession(userId, deviceId,
- deviceInfo.curve25519Key, theirOneTimeKey);
+ c.createOutboundSession(deviceInfo.curve25519Key, theirOneTimeKey);
kzo.client.dbg() << "done" << std::endl;
}
}
}
}
}
}
auto eventJson = json{
{"content", {{"algorithm", megOlmAlgo},
{"room_id", roomId},
{"session_id", sessionId},
{"session_key", sessionKey}}},
{"type", "m.room_key"}
};
kzo.client.dbg() << "the original key event: " << eventJson.dump() << std::endl;
// send the actual key, encrypted
auto event = m.olmEncrypt(Event(JsonWrap(eventJson)), devicesToSend);
kzo.client.dbg() << "encrypted key event: " << event.originalJson().get().dump() << std::endl;
m.addTrigger(ClaimKeysSuccessful{event, devicesToSend});
return { std::move(m), lager::noop };
}
}
diff --git a/src/client/client-model.cpp b/src/client/client-model.cpp
index 3c3121d..345542f 100644
--- a/src/client/client-model.cpp
+++ b/src/client/client-model.cpp
@@ -1,257 +1,260 @@
/*
* Copyright (C) 2020 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#include <lager/util.hpp>
#include <lager/context.hpp>
#include <functional>
#include <zug/transducer/filter.hpp>
#include <immer/flex_vector_transient.hpp>
#include "debug.hpp"
#include "client-model.hpp"
#include "actions/states.hpp"
#include "actions/auth.hpp"
#include "actions/membership.hpp"
#include "actions/paginate.hpp"
#include "actions/send.hpp"
#include "actions/states.hpp"
#include "actions/sync.hpp"
#include "actions/ephemeral.hpp"
#include "actions/content.hpp"
#include "actions/encryption.hpp"
namespace Kazv
{
auto ClientModel::update(ClientModel m, Action a) -> Result
{
auto oldDeviceLists = m.deviceLists;
auto [newClient, effect] = lager::match(std::move(a))(
[&](Error::Action a) -> Result {
m.error = Error::update(m.error, a);
return {std::move(m), lager::noop};
},
[&](RoomListAction a) -> Result {
m.roomList = RoomListModel::update(std::move(m.roomList), a);
return {std::move(m), lager::noop};
},
[&](ResubmitJobAction a) -> Result {
m.addJob(std::move(a.job));
return { std::move(m), lager::noop };
},
[&](auto a) -> decltype(updateClient(m, a)) {
return updateClient(m, a);
},
#define RESPONSE_FOR(_jobId) \
if (r.jobId() == #_jobId) { \
return processResponse(m, _jobId##Response{std::move(r)}); \
}
[&](ProcessResponseAction a) -> Result {
auto r = std::move(a.response);
// auth
RESPONSE_FOR(Login);
// paginate
RESPONSE_FOR(GetRoomEvents);
// sync
RESPONSE_FOR(Sync);
RESPONSE_FOR(DefineFilter);
// membership
RESPONSE_FOR(CreateRoom);
RESPONSE_FOR(InviteUser);
RESPONSE_FOR(JoinRoomById);
RESPONSE_FOR(JoinRoom);
RESPONSE_FOR(LeaveRoom);
RESPONSE_FOR(ForgetRoom);
// send
RESPONSE_FOR(SendMessage);
RESPONSE_FOR(SendToDevice);
// states
RESPONSE_FOR(GetRoomState);
RESPONSE_FOR(SetRoomStateWithKey);
RESPONSE_FOR(GetRoomStateWithKey);
// ephemeral
RESPONSE_FOR(SetTyping);
RESPONSE_FOR(PostReceipt);
RESPONSE_FOR(SetReadMarker);
// content
RESPONSE_FOR(UploadContent);
RESPONSE_FOR(GetContent);
RESPONSE_FOR(GetContentThumbnail);
// encryption
RESPONSE_FOR(UploadKeys);
RESPONSE_FOR(QueryKeys);
RESPONSE_FOR(ClaimKeys);
m.addTrigger(UnrecognizedResponse{std::move(r)});
return { std::move(m), lager::noop };
}
#undef RESPONSE_FOR
);
// Rotate megolm keys for rooms whose users' device list has changed
auto changedUsers = newClient.deviceLists.diff(oldDeviceLists);
if (! changedUsers.empty()) {
for (auto [roomId, room] : newClient.roomList.rooms) {
auto it = std::find_if(changedUsers.begin(), changedUsers.end(),
[=](auto userId) { return room.hasUser(userId); });
if (it != changedUsers.end()) {
newClient.roomList.rooms =
std::move(newClient.roomList.rooms)
.update(roomId, [](auto room) {
room.shouldRotateSessionKey = true;
return room;
});
}
}
}
return { std::move(newClient), std::move(effect) };
}
std::pair<Event, std::optional<std::string>> ClientModel::megOlmEncrypt(Event e, std::string roomId)
{
if (!crypto) {
kzo.client.dbg() << "We do not have e2ee, so do not encrypt events" << std::endl;
return { e, std::nullopt };
}
if (e.encrypted()) {
kzo.client.dbg() << "The event is already encrypted. Ignoring it." << std::endl;
return { e, std::nullopt };
}
auto &c = crypto.value();
auto j = e.originalJson().get();
auto r = roomList[roomId];
if (! r.encrypted) {
kzo.client.dbg() << "The room " << roomId
<< " is not encrypted, so do not encrypt events" << std::endl;
return { e, std::nullopt };
}
auto desc = r.sessionRotateDesc();
+ auto keyOpt = std::optional<std::string>{};
if (r.shouldRotateSessionKey) {
kzo.client.dbg() << "We should rotate this session." << std::endl;
- desc = MegOlmSessionRotateDesc{}; // 0ms and 0 msgs, so guaranteed to rotate
+ keyOpt = c.rotateMegOlmSession(roomId);
+ } else {
+ keyOpt = c.rotateMegOlmSessionIfNeeded(roomId, desc);
}
// we no longer need to rotate session
// until next time a device change happens
roomList.rooms = std::move(roomList.rooms)
.update(roomId, [](auto r) { r.shouldRotateSessionKey = false; return r; });
// so that Crypto::encryptMegOlm() can find room id
j["room_id"] = roomId;
- auto [content, keyOpt] = c.encryptMegOlm(j, desc);
+ auto content = c.encryptMegOlm(j);
j["type"] = "m.room.encrypted";
j["content"] = std::move(content);
j["content"]["device_id"] = deviceId;
kzo.client.dbg() << "Encrypted json is " << j.dump() << std::endl;
kzo.client.dbg() << "Session key is " << (keyOpt ? keyOpt.value() : "<not rotated>") << std::endl;
return { Event(JsonWrap(j)), keyOpt };
}
Event ClientModel::olmEncrypt(Event e, immer::map<std::string, immer::flex_vector<std::string>> userIdToDeviceIdMap)
{
if (!crypto) {
kzo.client.dbg() << "We do not have e2ee, so do not encrypt events" << std::endl;
return e;
}
if (e.encrypted()) {
kzo.client.dbg() << "The event is already encrypted. Ignoring it." << std::endl;
return e;
}
auto &c = crypto.value();
auto origJson = e.originalJson().get();
auto encJson = json::object();
encJson["content"] = json{
{"algorithm", CryptoConstants::olmAlgo},
{"ciphertext", json::object()},
{"sender_key", c.curve25519IdentityKey()},
};
encJson["type"] = "m.room.encrypted";
for (auto [userId, devices] : userIdToDeviceIdMap) {
for (auto dev : devices) {
auto devInfoOpt = deviceLists.get(userId, dev);
if (! devInfoOpt) {
continue;
}
auto devInfo = devInfoOpt.value();
auto jsonForThisDevice = origJson;
jsonForThisDevice["sender"] = this->userId;
jsonForThisDevice["recipient"] = userId;
jsonForThisDevice["recipient_keys"] = json{
{CryptoConstants::ed25519, devInfo.ed25519Key}
};
jsonForThisDevice["keys"] = json{
{CryptoConstants::ed25519, c.ed25519IdentityKey()}
};
encJson["content"]["ciphertext"]
- .merge_patch(c.encryptOlm(jsonForThisDevice, userId, dev));
+ .merge_patch(c.encryptOlm(jsonForThisDevice, devInfo.curve25519Key));
}
}
return Event(JsonWrap(encJson));
}
immer::flex_vector<std::string /* deviceId */> ClientModel::devicesToSendKeys(std::string userId) const
{
auto trustLevelNeeded = DeviceTrustLevel::Unseen;
// XXX: preliminary approach
auto shouldSendP = [=](auto deviceInfo, auto /* deviceMap */) {
return deviceInfo.trustLevel >= trustLevelNeeded;
};
auto devices = deviceLists.devicesFor(userId);
return intoImmer(
immer::flex_vector<std::string>{},
zug::filter([=](auto n) {
auto [id, dev] = n;
return shouldSendP(dev, devices);
})
| zug::map([=](auto n) {
return n.first;
}),
devices);
}
}
diff --git a/src/crypto/crypto-p.hpp b/src/crypto/crypto-p.hpp
index 52c0a2c..56746b1 100644
--- a/src/crypto/crypto-p.hpp
+++ b/src/crypto/crypto-p.hpp
@@ -1,79 +1,78 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <olm/olm.h>
#include <unordered_map>
#include "crypto.hpp"
#include "crypto-util.hpp"
#include "session.hpp"
#include "inbound-group-session.hpp"
#include "outbound-group-session.hpp"
namespace Kazv
{
using SessionList = std::vector<Session>;
struct CryptoPrivate
{
CryptoPrivate();
CryptoPrivate(const CryptoPrivate &that);
~CryptoPrivate();
ByteArray accountData;
OlmAccount *account;
immer::map<std::string /* algorithm */, int> uploadedOneTimeKeysCount;
int numUnpublishedKeys{0};
std::unordered_map<std::string /* theirCurve25519IdentityKey */, Session> knownSessions;
- std::unordered_map<KeyOfOutboundSession, Session> outboundSessions;
std::unordered_map<KeyOfGroupSession, InboundGroupSession> inboundGroupSessions;
std::unordered_map<std::string /* roomId */, OutboundGroupSession> outboundGroupSessions;
ByteArray utilityData;
OlmUtility *utility;
std::size_t checkUtilError(std::size_t code) const;
ByteArray pickle() const;
void unpickle(ByteArray data);
ByteArray identityKeys();
std::string ed25519IdentityKey();
std::string curve25519IdentityKey();
std::size_t checkError(std::size_t code) const;
MaybeString decryptOlm(nlohmann::json content);
// Here we need the full event for eventId and originServerTs
MaybeString decryptMegOlm(nlohmann::json eventJson);
/// returns whether the session is successfully established
bool createInboundSession(std::string theirCurve25519IdentityKey,
std::string message);
bool createInboundGroupSession(KeyOfGroupSession k, std::string sessionKey, std::string ed25519Key);
bool reuseOrCreateOutboundGroupSession(std::string roomId, MegOlmSessionRotateDesc desc);
};
}
diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp
index 983145e..6488221 100644
--- a/src/crypto/crypto.cpp
+++ b/src/crypto/crypto.cpp
@@ -1,506 +1,523 @@
/*
* Copyright (C) 2020 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#include <vector>
#include <zug/transducer/filter.hpp>
#include <olm/olm.h>
#include <nlohmann/json.hpp>
#include <debug.hpp>
#include <event.hpp>
#include <cursorutil.hpp>
#include "crypto-p.hpp"
#include "session-p.hpp"
#include "crypto-util.hpp"
#include "time-util.hpp"
namespace Kazv
{
using namespace CryptoConstants;
CryptoPrivate::CryptoPrivate()
: accountData(olm_account_size(), 0)
, account(olm_account(accountData.data()))
, utilityData(olm_utility_size(), '\0')
, utility(olm_utility(utilityData.data()))
{
auto randLen = olm_create_account_random_length(account);
auto randomData = genRandom(randLen);
checkError(olm_create_account(account, randomData.data(), randLen));
}
CryptoPrivate::~CryptoPrivate()
{
olm_clear_account(account);
}
CryptoPrivate::CryptoPrivate(const CryptoPrivate &that)
: accountData(olm_account_size(), 0)
, account(olm_account(accountData.data()))
, uploadedOneTimeKeysCount(that.uploadedOneTimeKeysCount)
, numUnpublishedKeys(that.numUnpublishedKeys)
, knownSessions(that.knownSessions)
- , outboundSessions(that.outboundSessions)
, inboundGroupSessions(that.inboundGroupSessions)
, outboundGroupSessions(that.outboundGroupSessions)
, utilityData(olm_utility_size(), '\0')
, utility(olm_utility(utilityData.data()))
{
unpickle(that.pickle());
}
ByteArray CryptoPrivate::pickle() const
{
auto key = ByteArray(3, 'x');
auto pickleData = ByteArray(olm_pickle_account_length(account), '\0');
checkError(olm_pickle_account(account, key.data(), key.size(),
pickleData.data(), pickleData.size()));
return pickleData;
}
void CryptoPrivate::unpickle(ByteArray pickleData)
{
auto key = ByteArray(3, 'x');
checkError(olm_unpickle_account(account, key.data(), key.size(),
pickleData.data(), pickleData.size()));
}
std::size_t CryptoPrivate::checkError(std::size_t code) const
{
if (code == olm_error()) {
kzo.crypto.warn() << "Olm error: " << olm_account_last_error(account) << std::endl;
}
return code;
}
std::size_t CryptoPrivate::checkUtilError(std::size_t code) const
{
if (code == olm_error()) {
kzo.crypto.warn() << "Olm utility error: " << olm_utility_last_error(utility) << std::endl;
}
return code;
}
MaybeString CryptoPrivate::decryptOlm(nlohmann::json content)
{
auto theirCurve25519IdentityKey = content.at("sender_key").get<std::string>();
auto ourCurve25519IdentityKey = curve25519IdentityKey();
if (! content.at("ciphertext").contains(ourCurve25519IdentityKey)) {
return NotBut("Message not intended for us");
}
auto type = content.at("ciphertext").at(ourCurve25519IdentityKey).at("type").get<int>();
auto body = content.at("ciphertext").at(ourCurve25519IdentityKey).at("body").get<std::string>();
auto hasKnownSession = knownSessions.find(theirCurve25519IdentityKey) != knownSessions.end();
if (type == 0) { // pre-key message
bool shouldCreateNewSession =
// there is no possible session
(! hasKnownSession)
// the possible session does not match this message
|| (! knownSessions.at(theirCurve25519IdentityKey).matches(body));
if (shouldCreateNewSession) {
auto created = createInboundSession(theirCurve25519IdentityKey, body);
if (! created) { // cannot create session, thus cannot decrypt
return NotBut("Cannot create session");
}
}
auto &session = knownSessions.at(theirCurve25519IdentityKey);
return session.decrypt(type, body);
} else {
if (! hasKnownSession) {
return NotBut("No available session");
}
auto &session = knownSessions.at(theirCurve25519IdentityKey);
return session.decrypt(type, body);
}
}
MaybeString CryptoPrivate::decryptMegOlm(nlohmann::json eventJson)
{
auto content = eventJson.at("content");
auto senderKey = content.at("sender_key").get<std::string>();
auto sessionId = content.at("session_id").get<std::string>();
auto roomId = eventJson.at("room_id").get<std::string>();
auto k = KeyOfGroupSession{roomId, senderKey, sessionId};
if (inboundGroupSessions.find(k) == inboundGroupSessions.end()) {
return NotBut("We do not have the keys for this");
} else {
auto msg = content.at("ciphertext").get<std::string>();
auto eventId = eventJson.at("event_id").get<std::string>();
auto originServerTs = eventJson.at("origin_server_ts").get<Timestamp>();
auto &session = inboundGroupSessions.at(k);
return session.decrypt(msg, eventId, originServerTs);
}
}
bool CryptoPrivate::createInboundSession(std::string theirCurve25519IdentityKey,
std::string message)
{
auto s = Session(InboundSessionTag{}, account,
theirCurve25519IdentityKey, message);
if (s.valid()) {
checkError(olm_remove_one_time_keys(account, s.m_d->session));
knownSessions.insert_or_assign(theirCurve25519IdentityKey, std::move(s));
return true;
}
return false;
}
bool CryptoPrivate::reuseOrCreateOutboundGroupSession(std::string roomId, MegOlmSessionRotateDesc desc)
{
auto it = outboundGroupSessions.find(roomId);
bool valid = true;
if (it == outboundGroupSessions.end()) {
valid = false;
} else {
auto &session = it->second;
if (currentTimeMs() - session.creationTimeMs() >= desc.ms) {
valid = false;
} else if (session.messageIndex() >= desc.messages) {
valid = false;
}
}
if (! valid) {
outboundGroupSessions.insert_or_assign(roomId, OutboundGroupSession());
auto &session = outboundGroupSessions.at(roomId);
auto sessionId = session.sessionId();
auto sessionKey = session.sessionKey();
auto senderKey = curve25519IdentityKey();
auto k = KeyOfGroupSession{roomId, senderKey, sessionId};
if (! createInboundGroupSession(k, sessionKey, ed25519IdentityKey())) {
kzo.client.warn() << "Create inbound group session from outbound group session failed. We may not be able to read our own messages." << std::endl;
}
}
return valid;
}
Crypto::Crypto()
: m_d(new CryptoPrivate{})
{
}
Crypto::~Crypto() = default;
Crypto::Crypto(const Crypto &that)
: m_d(new CryptoPrivate(*that.m_d))
{
}
Crypto::Crypto(Crypto &&that)
: m_d(std::move(that.m_d))
{
}
Crypto &Crypto::operator=(const Crypto &that)
{
m_d.reset(new CryptoPrivate(*that.m_d));
return *this;
}
Crypto &Crypto::operator=(Crypto &&that)
{
m_d = std::move(that.m_d);
return *this;
}
ByteArray CryptoPrivate::identityKeys()
{
auto ret = ByteArray(olm_account_identity_keys_length(account), '\0');
checkError(olm_account_identity_keys(account, ret.data(), ret.size()));
return ret;
}
std::string CryptoPrivate::ed25519IdentityKey()
{
auto keys = identityKeys();
auto keyStr = std::string(keys.begin(), keys.end());
auto keyJson = nlohmann::json::parse(keyStr);
return keyJson.at(ed25519);
}
std::string CryptoPrivate::curve25519IdentityKey()
{
auto keys = identityKeys();
auto keyStr = std::string(keys.begin(), keys.end());
auto keyJson = nlohmann::json::parse(keyStr);
return keyJson.at(curve25519);
}
std::string Crypto::ed25519IdentityKey()
{
return m_d->ed25519IdentityKey();
}
std::string Crypto::curve25519IdentityKey()
{
return m_d->curve25519IdentityKey();
}
std::string Crypto::sign(nlohmann::json j)
{
j.erase("signatures");
j.erase("unsigned");
auto str = j.dump();
auto ret = ByteArray(olm_account_signature_length(m_d->account), '\0');
kzo.crypto.dbg() << "We are about to sign: " << str << std::endl;
m_d->checkError(olm_account_sign(m_d->account,
str.data(), str.size(),
ret.data(), ret.size()));
return std::string{ret.begin(), ret.end()};
}
void Crypto::setUploadedOneTimeKeysCount(immer::map<std::string /* algorithm */, int> uploadedOneTimeKeysCount)
{
m_d->uploadedOneTimeKeysCount = uploadedOneTimeKeysCount;
}
int Crypto::maxNumberOfOneTimeKeys()
{
return olm_account_max_number_of_one_time_keys(m_d->account);
}
void Crypto::genOneTimeKeys(int num)
{
auto random = genRandom(olm_account_generate_one_time_keys_random_length(m_d->account, num));
auto res = m_d->checkError(
olm_account_generate_one_time_keys(
m_d->account,
num,
random.data(), random.size()));
if (res != olm_error()) {
m_d->numUnpublishedKeys += num;
}
}
nlohmann::json Crypto::unpublishedOneTimeKeys()
{
auto keys = ByteArray(olm_account_one_time_keys_length(m_d->account), '\0');
m_d->checkError(olm_account_one_time_keys(m_d->account, keys.data(), keys.size()));
return nlohmann::json::parse(std::string(keys.begin(), keys.end()));
}
void Crypto::markOneTimeKeysAsPublished()
{
auto ret = m_d->checkError(olm_account_mark_keys_as_published(m_d->account));
if (ret != olm_error()) {
m_d->numUnpublishedKeys = 0;
}
}
int Crypto::numUnpublishedOneTimeKeys() const
{
return m_d->numUnpublishedKeys;
}
int Crypto::uploadedOneTimeKeysCount(std::string algorithm) const
{
return m_d->uploadedOneTimeKeysCount[algorithm];
}
MaybeString Crypto::decrypt(nlohmann::json eventJson)
{
auto content = eventJson.at("content");
auto algo = content.at("algorithm").get<std::string>();
if (algo == olmAlgo) {
return m_d->decryptOlm(std::move(content));
} else if (algo == megOlmAlgo) {
return m_d->decryptMegOlm(eventJson);
}
return NotBut("Algorithm " + algo + " not supported");
}
bool Crypto::createInboundGroupSession(KeyOfGroupSession k, std::string sessionKey, std::string ed25519Key)
{
return m_d->createInboundGroupSession(std::move(k), std::move(sessionKey), std::move(ed25519Key));
}
bool CryptoPrivate::createInboundGroupSession(KeyOfGroupSession k, std::string sessionKey, std::string ed25519Key)
{
auto session = InboundGroupSession(sessionKey, ed25519Key);
if (session.valid()) {
inboundGroupSessions.insert_or_assign(k, std::move(session));
return true;
}
return false;
}
bool Crypto::verify(nlohmann::json object, std::string userId, std::string deviceId, std::string ed25519Key)
{
if (! object.contains("signatures")) {
return false;
}
std::string signature;
try {
signature = object.at("signatures").at(userId).at(ed25519 + ":" + deviceId);
} catch(const std::exception &) {
return false;
}
object.erase("signatures");
object.erase("unsigned");
auto message = object.dump();
auto res = m_d->checkUtilError(
olm_ed25519_verify(m_d->utility,
ed25519Key.c_str(), ed25519Key.size(),
message.c_str(), message.size(),
signature.data(), signature.size()));
return res != olm_error();
}
MaybeString Crypto::getInboundGroupSessionEd25519KeyFromEvent(const nlohmann::json &eventJson) const
{
auto content = eventJson.at("content");
auto senderKey = content.at("sender_key").get<std::string>();
auto sessionId = content.at("session_id").get<std::string>();
auto roomId = eventJson.at("room_id").get<std::string>();
auto k = KeyOfGroupSession{roomId, senderKey, sessionId};
if (m_d->inboundGroupSessions.find(k) == m_d->inboundGroupSessions.end()) {
return NotBut("We do not have the keys for this");
} else {
auto &session = m_d->inboundGroupSessions.at(k);
return session.ed25519Key();
}
}
- nlohmann::json Crypto::encryptOlm(nlohmann::json eventJson, std::string userId, std::string deviceId)
+ nlohmann::json Crypto::encryptOlm(nlohmann::json eventJson, std::string theirCurve25519IdentityKey)
{
try {
- auto &session = m_d->outboundSessions.at(KeyOfOutboundSession{userId, deviceId});
+ auto &session = m_d->knownSessions.at(theirCurve25519IdentityKey);
auto [type, body] = session.encrypt(eventJson.dump());
return nlohmann::json{
{
- session.theirIdentityKey(), {
+ theirCurve25519IdentityKey, {
{"type", type},
{"body", body}
}
}
};
} catch (const std::exception &) {
return nlohmann::json::object();
}
}
- std::pair<nlohmann::json, std::optional<std::string>>
- Crypto::encryptMegOlm(nlohmann::json eventJson, MegOlmSessionRotateDesc desc)
+ nlohmann::json Crypto::encryptMegOlm(nlohmann::json eventJson)
{
auto roomId = eventJson.at("room_id").get<std::string>();
auto content = eventJson.at("content");
auto type = eventJson.at("type").get<std::string>();
auto jsonToEncrypt = nlohmann::json::object();
jsonToEncrypt["room_id"] = roomId;
jsonToEncrypt["content"] = std::move(content);
jsonToEncrypt["type"] = type;
auto textToEncrypt = std::move(jsonToEncrypt).dump();
- auto reused = m_d->reuseOrCreateOutboundGroupSession(roomId, desc);
auto &session = m_d->outboundGroupSessions.at(roomId);
- // sessionKey() returns the key for the next message to be decrypted
- // so we need to call it before encrypt()
- auto sessionKey = session.sessionKey();
auto ciphertext = session.encrypt(std::move(textToEncrypt));
- return {
+ return
json{
{"algorithm", CryptoConstants::megOlmAlgo},
{"sender_key", curve25519IdentityKey()},
{"ciphertext", ciphertext},
{"session_id", session.sessionId()},
- },
- reused ? std::nullopt : std::optional<std::string>(sessionKey)
- };
+ };
}
- void Crypto::forceRotateMegOlmSession(std::string roomId)
+ std::string Crypto::rotateMegOlmSession(std::string roomId)
{
// just let the session expire 0ms after creation and
// we will have a new one
- m_d->reuseOrCreateOutboundGroupSession(std::move(roomId), MegOlmSessionRotateDesc());
+ m_d->reuseOrCreateOutboundGroupSession(roomId, MegOlmSessionRotateDesc());
+ return outboundGroupSessionCurrentKey(roomId);
+ }
+
+ std::optional<std::string> Crypto::rotateMegOlmSessionIfNeeded(std::string roomId, MegOlmSessionRotateDesc desc)
+ {
+ auto oldSessionValid = m_d->reuseOrCreateOutboundGroupSession(roomId, std::move(desc));
+ return oldSessionValid ? std::nullopt : std::optional(outboundGroupSessionCurrentKey(roomId));
+ }
+
+ std::string Crypto::outboundGroupSessionInitialKey(std::string roomId)
+ {
+ auto &session = m_d->outboundGroupSessions.at(roomId);
+ return session.initialSessionKey();
+ }
+
+ std::string Crypto::outboundGroupSessionCurrentKey(std::string roomId)
+ {
+ auto &session = m_d->outboundGroupSessions.at(roomId);
+ return session.sessionKey();
}
- auto Crypto::devicesMissingOutboundSessionKey(UserIdToDeviceIdMap userIdToDeviceIdMap) const -> UserIdToDeviceIdMap
+ auto Crypto::devicesMissingOutboundSessionKey(
+ immer::map<std::string, immer::map<std::string /* deviceId */,
+ std::string /* curve25519IdentityKey */>> keyMap) const -> UserIdToDeviceIdMap
{
auto ret = UserIdToDeviceIdMap{};
- for (auto [userId, devices] : userIdToDeviceIdMap) {
+ for (auto [userId, devices] : keyMap) {
auto unknownDevices =
intoImmer(immer::flex_vector<std::string>{},
- zug::filter([=](auto deviceId) {
- auto k = KeyOfOutboundSession{userId, deviceId};
- return m_d->outboundSessions.find(k) == m_d->outboundSessions.end();
- }),
+ zug::filter([=](auto kv) {
+ auto [deviceId, theirCurve25519IdentityKey] = kv;
+ return m_d->knownSessions.find(theirCurve25519IdentityKey)
+ == m_d->knownSessions.end();
+ })
+ | zug::map([=](auto kv) {
+ auto [_, key] = kv;
+ return key;
+ }),
devices);
if (! unknownDevices.empty()) {
ret = std::move(ret).set(userId, std::move(unknownDevices));
}
}
return ret;
}
- void Crypto::createOutboundSession(std::string userId, std::string deviceId, std::string theirIdentityKey,
+ void Crypto::createOutboundSession(std::string theirIdentityKey,
std::string theirOneTimeKey)
{
- // TODO
auto session = Session(OutboundSessionTag{},
m_d->account,
theirIdentityKey,
theirOneTimeKey);
if (session.valid()) {
- m_d->outboundSessions.insert_or_assign(KeyOfOutboundSession{userId, deviceId},
- std::move(session));
+ m_d->knownSessions.insert_or_assign(theirIdentityKey,
+ std::move(session));
}
}
}
diff --git a/src/crypto/crypto.hpp b/src/crypto/crypto.hpp
index 92e9242..90eba24 100644
--- a/src/crypto/crypto.hpp
+++ b/src/crypto/crypto.hpp
@@ -1,125 +1,133 @@
/*
* Copyright (C) 2020-2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <memory>
#include <nlohmann/json.hpp>
#include <immer/map.hpp>
#include <immer/flex_vector.hpp>
#include <maybe.hpp>
#include "crypto-util.hpp"
#include "time-util.hpp"
namespace Kazv
{
class Session;
struct MegOlmSessionRotateDesc
{
Timestamp ms{};
int messages{};
};
struct CryptoPrivate;
class Crypto
{
public:
explicit Crypto();
Crypto(const Crypto &that);
Crypto(Crypto &&that);
Crypto &operator=(const Crypto &that);
Crypto &operator=(Crypto &&that);
~Crypto();
std::string ed25519IdentityKey();
std::string curve25519IdentityKey();
std::string sign(nlohmann::json j);
void setUploadedOneTimeKeysCount(immer::map<std::string /* algorithm */, int> uploadedOneTimeKeysCount);
int uploadedOneTimeKeysCount(std::string algorithm) const;
int maxNumberOfOneTimeKeys();
void genOneTimeKeys(int num);
/**
* According to olm.h, this returns an object like
*
* {
* curve25519: {
* "AAAAAA": "wo76WcYtb0Vk/pBOdmduiGJ0wIEjW4IBMbbQn7aSnTo",
* "AAAAAB": "LRvjo46L1X2vx69sS9QNFD29HWulxrmW11Up5AfAjgU"
* }
* }
*/
nlohmann::json unpublishedOneTimeKeys();
int numUnpublishedOneTimeKeys() const;
void markOneTimeKeysAsPublished();
/// Returns decrypted message if we can decrypt it
/// otherwise returns the error
MaybeString decrypt(nlohmann::json eventJson);
/** returns a json object that looks like
* {
* "<their identity key>": {
* "type": <number>,
* "body": "<body>"
* }
* }
*/
- nlohmann::json encryptOlm(nlohmann::json eventJson, std::string userId, std::string deviceId);
+ nlohmann::json encryptOlm(nlohmann::json eventJson, std::string theirCurve25519IdentityKey);
/// returns the content template with everything but deviceId
/// eventJson should contain type, room_id and content
- /// if the session is rotated, also returns the session key
- std::pair<nlohmann::json, std::optional<std::string>>
- encryptMegOlm(nlohmann::json eventJson, MegOlmSessionRotateDesc desc);
+ nlohmann::json encryptMegOlm(nlohmann::json eventJson);
bool createInboundGroupSession(KeyOfGroupSession k, std::string sessionKey, std::string ed25519Key);
+ std::string outboundGroupSessionInitialKey(std::string roomId);
+
+ std::string outboundGroupSessionCurrentKey(std::string roomId);
+
/// Check whether the signature of userId/deviceId is valid in object
bool verify(nlohmann::json object, std::string userId, std::string deviceId, std::string ed25519Key);
MaybeString getInboundGroupSessionEd25519KeyFromEvent(const nlohmann::json &eventJson) const;
- void forceRotateMegOlmSession(std::string roomId);
+ /// Returns the new session key
+ std::string rotateMegOlmSession(std::string roomId);
+
+ /// Returns the new session key only if it is rotated
+ std::optional<std::string> rotateMegOlmSessionIfNeeded(std::string roomId, MegOlmSessionRotateDesc desc);
using UserIdToDeviceIdMap = immer::map<std::string, immer::flex_vector<std::string>>;
- UserIdToDeviceIdMap devicesMissingOutboundSessionKey(UserIdToDeviceIdMap userIdToDeviceIdMap) const;
+ UserIdToDeviceIdMap devicesMissingOutboundSessionKey(
+ immer::map<std::string, immer::map<std::string /* deviceId */,
+ std::string /* curve25519IdentityKey */>> keyMap) const;
- void createOutboundSession(std::string userId, std::string deviceId, std::string theirIdentityKey,
+ void createOutboundSession(std::string theirIdentityKey,
std::string theirOneTimeKey);
private:
friend class Session;
friend class SessionPrivate;
std::unique_ptr<CryptoPrivate> m_d;
};
}
diff --git a/src/crypto/outbound-group-session-p.hpp b/src/crypto/outbound-group-session-p.hpp
index 66d11cb..cc94399 100644
--- a/src/crypto/outbound-group-session-p.hpp
+++ b/src/crypto/outbound-group-session-p.hpp
@@ -1,50 +1,55 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "outbound-group-session.hpp"
#include <olm/olm.h>
#include <immer/map.hpp>
namespace Kazv
{
struct OutboundGroupSessionPrivate
{
OutboundGroupSessionPrivate();
OutboundGroupSessionPrivate(const OutboundGroupSessionPrivate &that);
~OutboundGroupSessionPrivate() = default;
ByteArray sessionData;
OlmOutboundGroupSession *session;
bool valid{false};
Timestamp creationTime;
+ std::string initialSessionKey;
+
+
std::size_t checkError(std::size_t code) const;
std::string error() const;
ByteArray pickle() const;
bool unpickle(ByteArray pickleData);
+
+ std::string sessionKey();
};
}
diff --git a/src/crypto/outbound-group-session.cpp b/src/crypto/outbound-group-session.cpp
index 52c2df9..ea6a2c0 100644
--- a/src/crypto/outbound-group-session.cpp
+++ b/src/crypto/outbound-group-session.cpp
@@ -1,165 +1,177 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#include "outbound-group-session-p.hpp"
#include <debug.hpp>
#include "time-util.hpp"
namespace Kazv
{
std::size_t OutboundGroupSessionPrivate::checkError(std::size_t code) const
{
if (code == olm_error()) {
kzo.crypto.warn() << "Olm outbound group session error: "
<< olm_outbound_group_session_last_error(session) << std::endl;
}
return code;
}
std::string OutboundGroupSessionPrivate::error() const
{
return olm_outbound_group_session_last_error(session);
}
OutboundGroupSessionPrivate::OutboundGroupSessionPrivate()
: sessionData(olm_outbound_group_session_size(), '\0')
, session(olm_outbound_group_session(sessionData.data()))
{
auto randomData = genRandom(olm_init_outbound_group_session_random_length(session));
auto res = checkError(olm_init_outbound_group_session(session, randomData.data(), randomData.size()));
if (res != olm_error()) {
valid = true;
+ initialSessionKey = sessionKey();
}
creationTime = currentTimeMs();
}
OutboundGroupSessionPrivate::OutboundGroupSessionPrivate(const OutboundGroupSessionPrivate &that)
: sessionData(olm_outbound_group_session_size(), '\0')
, session(olm_outbound_group_session(sessionData.data()))
, creationTime(that.creationTime)
+ , initialSessionKey(that.initialSessionKey)
{
valid = unpickle(that.pickle());
}
ByteArray OutboundGroupSessionPrivate::pickle() const
{
auto pickleData = ByteArray(olm_pickle_outbound_group_session_length(session), '\0');
auto key = ByteArray(3, 'x');
checkError(olm_pickle_outbound_group_session(session,
key.data(), key.size(),
pickleData.data(), pickleData.size()));
return pickleData;
}
bool OutboundGroupSessionPrivate::unpickle(ByteArray pickleData)
{
auto key = ByteArray(3, 'x');
auto res = checkError(olm_unpickle_outbound_group_session(
session,
key.data(), key.size(),
pickleData.data(), pickleData.size()));
return res != olm_error();
}
OutboundGroupSession::OutboundGroupSession()
: m_d(new OutboundGroupSessionPrivate)
{
}
OutboundGroupSession::~OutboundGroupSession() = default;
OutboundGroupSession::OutboundGroupSession(const OutboundGroupSession &that)
: m_d(new OutboundGroupSessionPrivate(*that.m_d))
{
}
OutboundGroupSession::OutboundGroupSession(OutboundGroupSession &&that)
: m_d(std::move(that.m_d))
{
}
OutboundGroupSession &OutboundGroupSession::operator=(const OutboundGroupSession &that)
{
m_d.reset(new OutboundGroupSessionPrivate(*that.m_d));
return *this;
}
OutboundGroupSession &OutboundGroupSession::operator=(OutboundGroupSession &&that)
{
m_d = std::move(that.m_d);
return *this;
}
bool OutboundGroupSession::valid() const
{
return m_d && m_d->valid;
}
std::string OutboundGroupSession::encrypt(std::string plainText)
{
auto plain = ByteArray(plainText.begin(), plainText.end());
auto size = olm_group_encrypt_message_length(m_d->session, plainText.size());
auto encrypted = ByteArray(size, '\0');
auto actualSize = m_d->checkError(olm_group_encrypt(
m_d->session,
plain.data(), plain.size(),
encrypted.data(), encrypted.size()));
return std::string(encrypted.begin(), encrypted.begin() + actualSize);
}
- std::string OutboundGroupSession::sessionKey()
+ std::string OutboundGroupSessionPrivate::sessionKey()
{
- auto size = olm_outbound_group_session_key_length(m_d->session);
+ auto size = olm_outbound_group_session_key_length(session);
auto keyBuf = ByteArray(size, '\0');
- auto actualSize = m_d->checkError(
- olm_outbound_group_session_key(m_d->session, keyBuf.data(), keyBuf.size()));
+ auto actualSize = checkError(
+ olm_outbound_group_session_key(session, keyBuf.data(), keyBuf.size()));
return std::string(keyBuf.begin(), keyBuf.begin() + actualSize);
}
+ std::string OutboundGroupSession::sessionKey()
+ {
+ return m_d->sessionKey();
+ }
+
+ std::string OutboundGroupSession::initialSessionKey() const
+ {
+ return m_d->initialSessionKey;
+ }
+
std::string OutboundGroupSession::sessionId()
{
auto size = olm_outbound_group_session_id_length(m_d->session);
auto idBuf = ByteArray(size, '\0');
auto actualSize = m_d->checkError(
olm_outbound_group_session_id(m_d->session, idBuf.data(), idBuf.size()));
return std::string(idBuf.begin(), idBuf.begin() + actualSize);
}
int OutboundGroupSession::messageIndex()
{
return olm_outbound_group_session_message_index(m_d->session);
}
Timestamp OutboundGroupSession::creationTimeMs() const
{
return m_d->creationTime;
}
}
diff --git a/src/crypto/outbound-group-session.hpp b/src/crypto/outbound-group-session.hpp
index 115d708..dc49c4c 100644
--- a/src/crypto/outbound-group-session.hpp
+++ b/src/crypto/outbound-group-session.hpp
@@ -1,57 +1,58 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <memory>
#include <maybe.hpp>
#include <event.hpp>
#include "crypto-util.hpp"
namespace Kazv
{
struct OutboundGroupSessionPrivate;
class OutboundGroupSession
{
public:
explicit OutboundGroupSession();
OutboundGroupSession(const OutboundGroupSession &that);
OutboundGroupSession(OutboundGroupSession &&that);
OutboundGroupSession &operator=(const OutboundGroupSession &that);
OutboundGroupSession &operator=(OutboundGroupSession &&that);
~OutboundGroupSession();
std::string encrypt(std::string plainText);
bool valid() const;
std::string sessionKey();
+ std::string initialSessionKey() const;
std::string sessionId();
int messageIndex();
Timestamp creationTimeMs() const;
private:
std::unique_ptr<OutboundGroupSessionPrivate> m_d;
};
}
diff --git a/src/crypto/session-p.hpp b/src/crypto/session-p.hpp
index e355963..1117ef7 100644
--- a/src/crypto/session-p.hpp
+++ b/src/crypto/session-p.hpp
@@ -1,57 +1,54 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "session.hpp"
namespace Kazv
{
struct SessionPrivate
{
SessionPrivate();
SessionPrivate(OutboundSessionTag,
OlmAccount *acc,
std::string theirIdentityKey,
std::string theirOneTimeKey);
SessionPrivate(InboundSessionTag,
OlmAccount *acc,
std::string theirIdentityKey,
std::string message);
SessionPrivate(const SessionPrivate &that);
~SessionPrivate() = default;
ByteArray sessionData;
OlmSession *session{0};
bool valid{false};
- /// only for outbound sessions
- std::string theirIdentityKey;
-
ByteArray pickle() const;
bool unpickle(ByteArray data);
std::size_t checkError(std::size_t code) const;
std::string error() const { return olm_session_last_error(session); }
};
}
diff --git a/src/crypto/session.cpp b/src/crypto/session.cpp
index 958538f..16dfca7 100644
--- a/src/crypto/session.cpp
+++ b/src/crypto/session.cpp
@@ -1,231 +1,223 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#include <olm/olm.h>
#include <debug.hpp>
#include "crypto-p.hpp"
#include "session-p.hpp"
namespace Kazv
{
std::size_t SessionPrivate::checkError(std::size_t code) const
{
if (code == olm_error()) {
kzo.crypto.warn() << "Olm session error: " << olm_session_last_error(session) << std::endl;
}
return code;
}
SessionPrivate::SessionPrivate()
: sessionData(olm_session_size(), '\0')
, session(olm_session(sessionData.data()))
{
}
SessionPrivate::SessionPrivate(OutboundSessionTag,
OlmAccount *acc,
std::string theirIdentityKey,
std::string theirOneTimeKey)
: SessionPrivate()
{
- this->theirIdentityKey = theirIdentityKey;
-
auto random = genRandom(olm_create_outbound_session_random_length(session));
auto res = checkError(olm_create_outbound_session(
session,
acc,
theirIdentityKey.c_str(), theirIdentityKey.size(),
theirOneTimeKey.c_str(), theirOneTimeKey.size(),
random.data(), random.size()));
if (res != olm_error()) {
valid = true;
}
}
SessionPrivate::SessionPrivate(InboundSessionTag,
OlmAccount *acc,
std::string theirIdentityKey,
std::string message)
: SessionPrivate()
{
auto res = checkError(olm_create_inbound_session_from(
session,
acc,
theirIdentityKey.c_str(), theirIdentityKey.size(),
message.data(), message.size()));
if (res != olm_error()) {
valid = true;
}
}
SessionPrivate::SessionPrivate(const SessionPrivate &that)
: SessionPrivate()
{
- theirIdentityKey = that.theirIdentityKey;
valid = unpickle(that.pickle());
}
ByteArray SessionPrivate::pickle() const
{
auto pickleData = ByteArray(olm_pickle_session_length(session), '\0');
auto key = ByteArray(3, 'x');
checkError(olm_pickle_session(session,
key.data(), key.size(),
pickleData.data(), pickleData.size()));
return pickleData;
}
bool SessionPrivate::unpickle(ByteArray pickleData)
{
auto key = ByteArray(3, 'x');
auto res = checkError(olm_unpickle_session(
session,
key.data(), key.size(),
pickleData.data(), pickleData.size()));
return res != olm_error();
}
Session::Session()
: m_d(new SessionPrivate)
{
}
Session::Session(OutboundSessionTag,
OlmAccount *acc,
std::string theirIdentityKey,
std::string theirOneTimeKey)
: m_d(new SessionPrivate{
OutboundSessionTag{},
acc,
theirIdentityKey,
theirOneTimeKey})
{
}
Session::Session(InboundSessionTag,
OlmAccount *acc,
std::string theirIdentityKey,
std::string theirOneTimeKey)
: m_d(new SessionPrivate{
InboundSessionTag{},
acc,
theirIdentityKey,
theirOneTimeKey})
{
}
Session::~Session() = default;
Session::Session(const Session &that)
: m_d(new SessionPrivate(*that.m_d))
{
}
Session::Session(Session &&that)
: m_d(std::move(that.m_d))
{
}
Session &Session::operator=(const Session &that)
{
m_d.reset(new SessionPrivate(*that.m_d));
return *this;
}
Session &Session::operator=(Session &&that)
{
m_d = std::move(that.m_d);
return *this;
}
bool Session::matches(std::string message)
{
auto res = m_d->checkError(
olm_matches_inbound_session(m_d->session, message.data(), message.size()));
// if match, returns 1
return res == 1;
}
bool Session::valid() const
{
// maybe a moved-from state, so check m_d first
return m_d && m_d->valid;
}
MaybeString Session::decrypt(int type, std::string message)
{
auto msgBuffer = ByteArray(message.begin(), message.end());
auto size = m_d->checkError(olm_decrypt_max_plaintext_length(
m_d->session, type, msgBuffer.data(), msgBuffer.size()));
if (size == olm_error()) {
return NotBut(m_d->error());
}
auto plainTextBuffer = ByteArray(size, '\0');
auto actualSize = m_d->checkError(
olm_decrypt(m_d->session, type,
message.data(), message.size(),
plainTextBuffer.data(), plainTextBuffer.size()));
if (actualSize == olm_error()) {
return NotBut(m_d->error());
}
return std::string(plainTextBuffer.begin(), plainTextBuffer.begin() + actualSize);
}
std::pair<int, std::string> Session::encrypt(std::string plainText)
{
auto randomData = genRandom(olm_encrypt_random_length(m_d->session));
auto type = m_d->checkError(olm_encrypt_message_type(m_d->session));
auto size = m_d->checkError(olm_encrypt_message_length(m_d->session, plainText.size()));
auto buf = ByteArray(size, '\0');
auto actualSize = m_d->checkError(
olm_encrypt(m_d->session, plainText.c_str(), plainText.size(),
randomData.data(), randomData.size(),
buf.data(), buf.size()));
if (actualSize != olm_error()) {
return { type, std::string(buf.begin(), buf.begin() + actualSize) };
}
return { -1, "" };
}
-
- std::string Session::theirIdentityKey() const
- {
- return m_d->theirIdentityKey;
- }
}
diff --git a/src/crypto/session.hpp b/src/crypto/session.hpp
index f74499d..087952e 100644
--- a/src/crypto/session.hpp
+++ b/src/crypto/session.hpp
@@ -1,80 +1,74 @@
/*
* Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <memory>
#include <tuple>
#include <olm/olm.h>
#include <maybe.hpp>
#include "crypto-util.hpp"
namespace Kazv
{
class Crypto;
struct InboundSessionTag {};
struct OutboundSessionTag {};
struct SessionPrivate;
class Session
{
// Creates an outbound session
explicit Session(OutboundSessionTag,
OlmAccount *acc,
std::string theirIdentityKey,
std::string theirOneTimeKey);
// Creates an inbound session
explicit Session(InboundSessionTag,
OlmAccount *acc,
std::string theirIdentityKey,
std::string message);
public:
explicit Session();
Session(const Session &that);
Session(Session &&that);
Session &operator=(const Session &that);
Session &operator=(Session &&that);
~Session();
- /// only for inbound sessions
bool matches(std::string message);
bool valid() const;
- /// only for inbound sessions
MaybeString decrypt(int type, std::string message);
- /// only for outbound sessions
std::pair<int /* type */, std::string /* message */> encrypt(std::string plainText);
- /// only for outbound sessions
- std::string theirIdentityKey() const;
-
private:
friend class Crypto;
friend class CryptoPrivate;
std::unique_ptr<SessionPrivate> m_d;
};
}
diff --git a/src/tests/crypto-test.cpp b/src/tests/crypto-test.cpp
index c6c247e..e4ed418 100644
--- a/src/tests/crypto-test.cpp
+++ b/src/tests/crypto-test.cpp
@@ -1,54 +1,108 @@
/*
* Copyright (C) 2020 Tusooa Zhu <tusooa@vista.aero>
*
* This file is part of libkazv.
*
* libkazv is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* libkazv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with libkazv. If not, see <https://www.gnu.org/licenses/>.
*/
#include <catch2/catch.hpp>
#include <crypto/crypto.hpp>
using namespace Kazv;
+using namespace Kazv::CryptoConstants;
TEST_CASE("Crypto should be copyable", "[crypto]")
{
Crypto crypto;
crypto.genOneTimeKeys(1);
auto oneTimeKeys = crypto.unpublishedOneTimeKeys();
Crypto cryptoClone(crypto);
REQUIRE(crypto.ed25519IdentityKey() == cryptoClone.ed25519IdentityKey());
REQUIRE(crypto.curve25519IdentityKey() == cryptoClone.curve25519IdentityKey());
auto oneTimeKeys2 = cryptoClone.unpublishedOneTimeKeys();
REQUIRE(oneTimeKeys == oneTimeKeys2);
REQUIRE(crypto.numUnpublishedOneTimeKeys() == cryptoClone.numUnpublishedOneTimeKeys());
}
TEST_CASE("Generating and publishing keys should work", "[crypto]")
{
Crypto crypto;
crypto.genOneTimeKeys(1);
REQUIRE(crypto.numUnpublishedOneTimeKeys() == 1);
crypto.genOneTimeKeys(1);
REQUIRE(crypto.numUnpublishedOneTimeKeys() == 2);
crypto.markOneTimeKeysAsPublished();
REQUIRE(crypto.numUnpublishedOneTimeKeys() == 0);
}
+
+TEST_CASE("Should reuse existing inbound session to encrypt", "[crypto]")
+{
+ Crypto a;
+ Crypto b;
+
+ a.genOneTimeKeys(1);
+
+ // Get A publish the key and send to B
+ auto k = a.unpublishedOneTimeKeys();
+ a.markOneTimeKeysAsPublished();
+
+ auto oneTimeKey = std::string{};
+
+ for (auto [id, key] : k[curve25519].items()) {
+ oneTimeKey = key;
+ }
+
+ auto aIdKey = a.curve25519IdentityKey();
+ b.createOutboundSession(aIdKey, oneTimeKey);
+
+ auto origJson = json{{"test", "mew"}};
+ auto encryptedMsg = b.encryptOlm(origJson, aIdKey);
+ auto encJson = json{
+ {"content",
+ {
+ {"algorithm", olmAlgo},
+ {"ciphertext", encryptedMsg},
+ {"sender_key", b.curve25519IdentityKey()}
+ }
+ }
+ };
+
+ auto decryptedOpt = a.decrypt(encJson);
+
+ REQUIRE(decryptedOpt);
+
+ auto decryptedJson = json::parse(decryptedOpt.value());
+
+ REQUIRE(decryptedJson == origJson);
+
+ using StrMap = immer::map<std::string, std::string>;
+ auto devMap = immer::map<std::string, StrMap>()
+ .set("b", StrMap().set("dev", b.curve25519IdentityKey()));
+
+ auto devices = a.devicesMissingOutboundSessionKey(devMap);
+
+ // No device should be missing an olm session, as A has received an
+ // inbound olm session before.
+ auto expected = immer::map<std::string, immer::flex_vector<std::string>>();
+
+ REQUIRE(devices == expected);
+}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 8:53 PM (1 d, 21 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55445
Default Alt Text
(84 KB)
Attached To
Mode
rL libkazv
Attached
Detach File
Event Timeline
Log In to Comment