Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85595865
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
31 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp
index 96420d6..6e2ab81 100644
--- a/src/crypto/crypto.cpp
+++ b/src/crypto/crypto.cpp
@@ -1,674 +1,674 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020-2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <vector>
#include <zug/transducer/filter.hpp>
#include <vodozemac.h>
#include <nlohmann/json.hpp>
#include <debug.hpp>
#include <event.hpp>
#include <cursorutil.hpp>
#include <types.hpp>
#include <validator.hpp>
#include "crypto-p.hpp"
#include "session-p.hpp"
#include "crypto-util-p.hpp"
#include "crypto-util.hpp"
#include "time-util.hpp"
namespace Kazv
{
using namespace CryptoConstants;
CryptoPrivate::CryptoPrivate()
: account(std::nullopt)
, valid(false)
{
}
CryptoPrivate::CryptoPrivate(RandomTag, [[maybe_unused]] RandomData data)
: account(std::nullopt)
, valid(true)
{
account = vodozemac::olm::new_account();
}
CryptoPrivate::~CryptoPrivate()
{
}
CryptoPrivate::CryptoPrivate(const CryptoPrivate &that)
: account(std::nullopt)
, uploadedOneTimeKeysCount(that.uploadedOneTimeKeysCount)
, numUnpublishedKeys(that.numUnpublishedKeys)
, knownSessions(that.knownSessions)
, inboundGroupSessions(that.inboundGroupSessions)
, outboundGroupSessions(that.outboundGroupSessions)
{
if (that.valid) {
valid = unpickle(that.pickle());
}
}
std::string CryptoPrivate::pickle() const
{
auto pickleData = account.value()->pickle(VODOZEMAC_PICKLE_KEY);
return static_cast<std::string>(pickleData);
}
bool CryptoPrivate::unpickle(std::string pickleData)
{
account = checkVodozemacError([&]() {
return vodozemac::olm::account_from_pickle(
rust::Str(pickleData),
VODOZEMAC_PICKLE_KEY
);
});
return account.has_value();
}
bool CryptoPrivate::unpickleFromLibolm(std::string pickleData)
{
account = checkVodozemacError([&]() {
return vodozemac::olm::account_from_libolm_pickle(
rust::Str(pickleData),
rust::Slice<const unsigned char>(OLM_PICKLE_KEY.data(), OLM_PICKLE_KEY.size())
);
});
return account.has_value();
}
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.m_d->takeFirstDecrypted();
}
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 sessionId = content.at("session_id").get<std::string>();
auto roomId = eventJson.at("room_id").get<std::string>();
auto k = KeyOfGroupSession{roomId, 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{}, *this,
theirCurve25519IdentityKey, message);
if (s.valid()) {
knownSessions.insert_or_assign(theirCurve25519IdentityKey, std::move(s));
return true;
}
return false;
}
bool CryptoPrivate::reuseOrCreateOutboundGroupSession(
RandomData random, Timestamp timeMs,
std::string roomId, std::optional<MegOlmSessionRotateDesc> desc)
{
bool valid = true;
if (! desc.has_value()) { // force rotate
valid = false;
} else {
auto it = outboundGroupSessions.find(roomId);
if (it == outboundGroupSessions.end()) {
valid = false;
} else {
auto &session = it->second;
if (timeMs - session.creationTimeMs() >= desc.value().ms) {
valid = false;
} else if (session.messageIndex() >= desc.value().messages) {
valid = false;
}
}
}
if (! valid) {
outboundGroupSessions.insert_or_assign(roomId, OutboundGroupSession(RandomTag{}, random, timeMs));
auto &session = outboundGroupSessions.at(roomId);
auto sessionId = session.sessionId();
auto sessionKey = session.sessionKey();
auto k = KeyOfGroupSession{roomId, 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;
}
std::size_t Crypto::constructRandomSize()
{
return 0;
}
Crypto::Crypto()
: m_d(new CryptoPrivate{})
{
}
Crypto::Crypto(RandomTag, RandomData data)
: m_d(new CryptoPrivate(RandomTag{}, std::move(data)))
{
}
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;
}
bool Crypto::operator==(const Crypto &that) const
{
return this->m_d == that.m_d;
}
bool Crypto::valid() const
{
return m_d->valid;
}
std::string CryptoPrivate::ed25519IdentityKey() const
{
auto key = account.value()->ed25519_key()->to_base64();
return static_cast<std::string>(key);
}
std::string CryptoPrivate::curve25519IdentityKey() const
{
auto key = account.value()->curve25519_key()->to_base64();
return static_cast<std::string>(key);
}
std::string Crypto::ed25519IdentityKey() const
{
return m_d->ed25519IdentityKey();
}
std::string Crypto::curve25519IdentityKey() const
{
return m_d->curve25519IdentityKey();
}
std::string Crypto::sign(nlohmann::json j)
{
j.erase("signatures");
j.erase("unsigned");
auto str = j.dump();
auto signature = checkVodozemacError([&]() {
return m_d->account.value()->sign(str);
});
if (!signature.has_value()) {
return "";
}
return static_cast<std::string>(signature.value()->to_base64());
}
void Crypto::setUploadedOneTimeKeysCount(immer::map<std::string /* algorithm */, int> uploadedOneTimeKeysCount)
{
m_d->uploadedOneTimeKeysCount = uploadedOneTimeKeysCount;
}
std::size_t Crypto::maxNumberOfOneTimeKeys() const
{
return m_d->account.value()->max_number_of_one_time_keys();
}
std::size_t Crypto::genOneTimeKeysRandomSize(int num)
{
return 0;
}
void Crypto::genOneTimeKeysWithRandom(RandomData random, int num)
{
assert(random.size() >= genOneTimeKeysRandomSize(num));
m_d->account.value()->generate_one_time_keys(num);
m_d->numUnpublishedKeys += num;
}
nlohmann::json Crypto::unpublishedOneTimeKeys() const
{
auto keys = m_d->account.value()->one_time_keys();
auto ret = nlohmann::json{
{curve25519, nlohmann::json::object()},
};
for (const auto &k : keys) {
auto keyId = static_cast<std::string>(k.key_id);
auto key = static_cast<std::string>(k.key->to_base64());
ret[curve25519][keyId] = key;
}
return ret;
}
void Crypto::markOneTimeKeysAsPublished()
{
m_d->account.value()->mark_keys_as_published();
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.contains("algorithm") ? content.at("algorithm").template get<std::string>() : 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));
}
std::size_t Crypto::importInboundGroupSessions(const nlohmann::json &keys)
{
if (!keys.is_array()) {
return 0;
}
auto validateStr = identValidate(&nlohmann::json::is_string);
std::size_t count = 0;
for (const auto &data : keys) {
if (!data.is_object()) {
continue;
}
auto key = nlohmann::json::object();
if (!(cast(key, data, "algorithm", identValidate([](const auto &j) {
- return j == "m.megolm.v1.aes-sha2";
+ return j == megOlmAlgo;
})) && cast(key, data, "room_id", validateStr)
- && cast(key, data, "sender_key", validateStr)
- && cast(key, data, "session_id", validateStr)
&& cast(key, data, "session_key", validateStr)
+ && cast(key, data, "session_id", validateStr)
+ && cast(key, data, "/sender_claimed_keys/ed25519"_json_pointer, validateStr)
)) {
continue;
}
auto keyOfGroupSession = KeyOfGroupSession{
key["room_id"].template get<std::string>(),
key["session_id"].template get<std::string>(),
};
if (createInboundGroupSession(
keyOfGroupSession,
key["session_key"].template get<std::string>(),
- key["sender_key"].template get<std::string>()
+ key["sender_claimed_keys"]["ed25519"].template get<std::string>()
)) {
++count;
}
}
return count;
}
bool Crypto::hasInboundGroupSession(KeyOfGroupSession k) const
{
return m_d->inboundGroupSessions.find(k) != m_d->inboundGroupSessions.end();
}
bool CryptoPrivate::createInboundGroupSession(KeyOfGroupSession k, std::string sessionKey, std::string ed25519Key)
{
auto session = InboundGroupSession(sessionKey, ed25519Key);
if (!session.valid()) {
kzo.crypto.warn() << "Invalid session key for: " << k.roomId << ", " << k.sessionId << std::endl;
return false;
}
auto currentSessionIt = inboundGroupSessions.find(k);
if (currentSessionIt == inboundGroupSessions.end()) {
// the session is new, insert it
inboundGroupSessions.insert({k, std::move(session)});
return true;
}
// the session already exists, do some check
auto ¤tSession = currentSessionIt->second;
if (currentSession.ed25519Key() != ed25519Key) {
return false;
}
return currentSession.merge(session);
}
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 = checkVodozemacError([&]() {
auto key = vodozemac::types::ed25519_key_from_base64(ed25519Key);
auto sig = vodozemac::types::ed25519_signature_from_base64(signature);
key->verify(message, *sig);
// It throws if the signature cannot be verified
return true;
});
return res.has_value() && res.value();
}
MaybeString Crypto::getInboundGroupSessionEd25519KeyFromEvent(const nlohmann::json &eventJson) const
{
auto content = eventJson.at("content");
auto sessionId = content.at("session_id").get<std::string>();
auto roomId = eventJson.at("room_id").get<std::string>();
auto k = KeyOfGroupSession{roomId, 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();
}
}
std::size_t Crypto::encryptOlmRandomSize(std::string /* theirCurve25519IdentityKey */) const
{
// HACK: To prevent a possible race condition where we call
// encryptedOlmRandomSize() -> randomGenerator.generateRange() ~> [encryptOlm()]
//
// Here, encryptOlm() must be called in the reducer,
// as it changes the status of
// Crypto (so we should not risk any infomation lose in Crypto).
// Also, for the reducer to be pure, we may not call generateRange() in the reducer,
// as *that* is not a pure function. This means it can only be called in an effect,
// or .then() continuation. This means, the sequence from encryptedOlmRandomSize()
// to encryptOlm() can never be atomic. That is, there may be other encryptOlm()
// calls within, and that may increase the random data needed, and as a result,
// we will not have enough random data.
//
// According to the olm headers:
// https://gitlab.matrix.org/matrix-org/olm/-/blob/master/include/olm/ratchet.hh
// The maximum random size needed to encrypt is 32. We use this to ensure we
// will always have enough random data fot the encryption.
return encryptOlmMaxRandomSize();
}
std::size_t Crypto::encryptOlmMaxRandomSize()
{
return 0;
}
nlohmann::json Crypto::encryptOlmWithRandom(
RandomData random, nlohmann::json eventJson, std::string theirCurve25519IdentityKey)
{
assert(random.size() >= encryptOlmRandomSize(theirCurve25519IdentityKey));
try {
auto &session = m_d->knownSessions.at(theirCurve25519IdentityKey);
auto [type, body] = session.encryptWithRandom(random, eventJson.dump());
return nlohmann::json{
{
theirCurve25519IdentityKey, {
{"type", type},
{"body", body}
}
}
};
} catch (const std::exception &) {
return nlohmann::json::object();
}
}
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 &session = m_d->outboundGroupSessions.at(roomId);
auto ciphertext = session.encrypt(std::move(textToEncrypt));
return
json{
{"algorithm", CryptoConstants::megOlmAlgo},
// NOTE: we might stop sending sender_key in the future
// as per the Matrix spec
{"sender_key", curve25519IdentityKey()},
{"ciphertext", ciphertext},
{"session_id", session.sessionId()},
};
}
std::size_t Crypto::rotateMegOlmSessionRandomSize()
{
return OutboundGroupSession::constructRandomSize();
}
std::string Crypto::rotateMegOlmSessionWithRandom(RandomData random, Timestamp timeMs, std::string roomId)
{
m_d->reuseOrCreateOutboundGroupSession(
random, timeMs,
roomId, std::nullopt);
return outboundGroupSessionCurrentKey(roomId);
}
std::optional<std::string> Crypto::rotateMegOlmSessionWithRandomIfNeeded(
RandomData random, Timestamp timeMs,
std::string roomId, MegOlmSessionRotateDesc desc)
{
auto oldSessionValid = m_d->reuseOrCreateOutboundGroupSession(random, timeMs, 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(
immer::map<std::string, immer::map<std::string /* deviceId */,
std::string /* curve25519IdentityKey */>> keyMap) const -> UserIdToDeviceIdMap
{
auto ret = UserIdToDeviceIdMap{};
for (auto [userId, devices] : keyMap) {
auto unknownDevices =
intoImmer(immer::flex_vector<std::string>{},
zug::filter([=](auto kv) {
auto [deviceId, theirCurve25519IdentityKey] = kv;
return m_d->knownSessions.find(theirCurve25519IdentityKey)
== m_d->knownSessions.end();
})
| zug::map([=](auto kv) {
auto [deviceId, key] = kv;
return deviceId;
}),
devices);
if (! unknownDevices.empty()) {
ret = std::move(ret).set(userId, std::move(unknownDevices));
}
}
return ret;
}
std::size_t Crypto::createOutboundSessionRandomSize()
{
return Session::constructOutboundRandomSize();
}
void Crypto::createOutboundSessionWithRandom(
RandomData random,
std::string theirIdentityKey,
std::string theirOneTimeKey)
{
assert(random.size() >= createOutboundSessionRandomSize());
auto session = Session(OutboundSessionTag{},
RandomTag{},
random,
*m_d,
theirIdentityKey,
theirOneTimeKey);
if (session.valid()) {
m_d->knownSessions.insert_or_assign(theirIdentityKey,
std::move(session));
}
}
nlohmann::json Crypto::toJson() const
{
std::string pickledData = m_d->valid ? m_d->pickle() : std::string();
auto j = nlohmann::json::object({
{"valid", m_d->valid},
{"version", 1},
{"account", std::move(pickledData)},
{"uploadedOneTimeKeysCount", m_d->uploadedOneTimeKeysCount},
{"numUnpublishedKeys", m_d->numUnpublishedKeys},
{"knownSessions", nlohmann::json(m_d->knownSessions)},
{"inboundGroupSessions", nlohmann::json(m_d->inboundGroupSessions)},
{"outboundGroupSessions", nlohmann::json(m_d->outboundGroupSessions)},
});
return j;
}
void Crypto::loadJson(const nlohmann::json &j)
{
m_d->valid = j.contains("valid") ? j["valid"].template get<bool>() : true;
const auto &pickledData = j.at("account").template get<std::string>();
if (m_d->valid) {
if (j.contains("version") && j["version"] == 1) {
m_d->valid = m_d->unpickle(pickledData);
} else {
m_d->valid = m_d->unpickleFromLibolm(pickledData);
}
}
m_d->uploadedOneTimeKeysCount = j.at("uploadedOneTimeKeysCount");
m_d->numUnpublishedKeys = j.at("numUnpublishedKeys");
m_d->knownSessions = j.at("knownSessions").template get<decltype(m_d->knownSessions)>();
m_d->inboundGroupSessions = j.at("inboundGroupSessions").template get<decltype(m_d->inboundGroupSessions)>();
m_d->outboundGroupSessions = j.at("outboundGroupSessions").template get<decltype(m_d->outboundGroupSessions)>();
}
}
diff --git a/src/tests/crypto/key-export-test.cpp b/src/tests/crypto/key-export-test.cpp
index 6f4ff7b..c89e85e 100644
--- a/src/tests/crypto/key-export-test.cpp
+++ b/src/tests/crypto/key-export-test.cpp
@@ -1,189 +1,200 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2025 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <catch2/catch_test_macros.hpp>
#include <key-export.hpp>
#include <crypto.hpp>
+#include <crypto-util.hpp>
using namespace Kazv;
static const std::string password = "test";
static const std::string salt = "\x5e\x91\xe3\x75\xcc\x7b\xaf\x04\x91\xfe\xe3\x07\x89\x58\x1c\xb8";
static const std::size_t iterations = 1000000;
static const ByteArray iv(16, 0);
static const ByteArray expEncryptionKey = {5, 60, 69, 180, 233, 146, 45, 161, 158, 115, 248, 156, 6, 242, 138, 45, 103, 156, 226, 255, 176, 37, 30, 182, 138, 0, 240, 157, 217, 116, 141, 128};
static const ByteArray expHmacKey = {182, 206, 253, 28, 253, 171, 93, 193, 200, 217, 248, 152, 204, 174, 180, 159, 155, 28, 176, 208, 169, 99, 170, 121, 180, 4, 129, 245, 19, 47, 75, 182};
static const std::string backupFile = R"(-----BEGIN MEGOLM SESSION DATA-----
AV6R43XMe68Ekf7jB4lYHLgAAAAAAAAAAAAAAAAAAAAAAA9CQG628kByLP7LApTtbvhnpgFnUUJ
+tRMkpw4zcGoTOJya9/lawfRWKjd8LZeuHKdNLkEhfIAE16Xmqv+uU8oEASPxjLDOMjsgBKLMRx
/iwUR7Aoe4wjuwEcdEEOW+T6ffjUz5LmEJcI14qZ1wXUPk1pnNmz+4nX8+a9UxgEpAN7vsmilwz
P4PXNubhvGsqtZpy44pP6Td0alYgwVfTXqWB1KokMjuQE+2q6/Jb6U/z5D5nv8ArcJL04cD0U6r
ySsRWI9Jra2OcKFQxgLeVpRAiP6/sRyl9k1n6eiSOfmGkZ+qnvOfZsQh7Wupgh6zRe8LNEtrYZh
FpSaCE+0U8I5hZJrWNBDFfHg+rtzB4BEk0YwpD3rVcWEsk8kKqHmEulEqIXckd1SbSG7y7H1ADB
7mjAY7qWetMizPXD+I8MDUnU1TF3Jv3CIZfZY7BHh2WukmiORlpN4H5s/Wwq2oCIk7qXhCHFvaF
uj+XytIz6TmkEVZfXK9zqUCwCU+VYSGl9GVAezO8CZ6aEJes95yYqRxfADdJG2Vtd0oXwrpR0xV
1GO+0JJ3xKicVX6U77iMtJbL1Lge32QvbAcv8o6mcaW28xeeYPrccMIRa3vLtuSDDqKC79S9bIP
2U5F+MHn+5dqMeXcG9K2hS91gsBQMAvX6
-----END MEGOLM SESSION DATA-----)";
static const std::string nonJsonFile = R"(-----BEGIN MEGOLM SESSION DATA-----
AV6R43XMe68Ekf7jB4lYHLgAAAAAAAAAAAAAAAAAAAAAAA9CQE628kByLP7LApTtbvhnpgFnUUJ
+tRMkpw4zcGoTOJya9/lawfRWKjd8LZeuHKdNLkEhfIAE16Xmqv+uU8oEASPxjLDOMjsgBKLMRx
/iwUR7Aoe4wjuwEcdEEOW+T6ffjUz5LmEJcI14qZ1wXUPk1pnNmz+4nX8+a9UxgEpAN7vsmilwz
P4PXNubhvGsqtZpy44pP6Td0alYgwVfTXqWB1KokMjuQE+2q6/Jb6U/z5D5nv8ArcJL04cD0U6r
ySsRWI9Jra2OcKFQxgLeVpRAiP6/sRyl9k1n6eiSOfmGkZ+qnvOfZsQh7Wupgh6zRe8LNEtrYZh
FpSaCE+0U8I5hZJrWNBDFfHg+rtzB4BEk0YwpD3rVcWEsk8kKqHmEulEqIXckd1SbSG7y7H1ADB
7mjAY7qWetMizPXD+I8MDUnU1TF3Jv3CIZfZY7BHh2WukmiORlpN4H5s/Wwq2oCIk7qXhCHFvaF
uj+XytIz6TmkEVZfXK9zqUCwCU+VYSGl9GVAezO8CZ6aEJes95yYqRxfADdJG2Vtd0oXwrpR0xV
1GO+0JJ3xKicVX6U77iMtJbL1Lge32QvbAcv8o6mcaW28xeeYPrccMIRa3vLtuSDDrgHHUiZDSE
kuZ4FJ70pzD12mQoT/0L5yVCaKP8ibtRd
-----END MEGOLM SESSION DATA-----)";
static const nlohmann::json expectedJson = R"([{
"algorithm": "m.megolm.v1.aes-sha2",
"forwarding_curve25519_key_chain": [
"hPQNcabIABgGnx3/ACv/jmMmiQHoeFfuLB17tzWp6Hw"
],
"room_id": "!Cuyf34gef24t:localhost",
"sender_claimed_keys": {
"ed25519": "aj40p+aw64yPIdsxoog8jhPu9i7l7NcFRecuOQblE3Y"
},
"sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU",
"session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ",
"session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8Llf..."
}])"_json;
TEST_CASE("deriveKeyExportKey", "[crypto][key-export]")
{
auto [encryptionKey, hmacKey] = deriveKeyExportKey(password, ByteArray(salt.begin(), salt.end()), iterations);
REQUIRE(encryptionKey == expEncryptionKey);
REQUIRE(hmacKey == expHmacKey);
}
TEST_CASE("decryptKeyExport", "[crypto][key-export]")
{
auto res = decryptKeyExport(backupFile, password);
REQUIRE(res.has_value());
REQUIRE(res.value() == expectedJson);
}
TEST_CASE("decryptKeyExport error handling", "[crypto][key-export]")
{
WHEN("no header")
{
auto res = decryptKeyExport(backupFile.substr(1), password);
REQUIRE(!res.has_value());
REQUIRE(res.reason() == DecryptKeyExportErrorCodes::FILE_MALFORMED);
}
WHEN("no footer")
{
auto res = decryptKeyExport(backupFile.substr(0, backupFile.size() - 1), password);
REQUIRE(!res.has_value());
REQUIRE(res.reason() == DecryptKeyExportErrorCodes::FILE_MALFORMED);
}
WHEN("file too small")
{
auto res = decryptKeyExport(R"(-----BEGIN MEGOLM SESSION DATA-----
AV6R43XMe68Ekf7jB4lYHLgAAAAAAAAAAAA
-----END MEGOLM SESSION DATA-----)", password);
REQUIRE(!res.has_value());
REQUIRE(res.reason() == DecryptKeyExportErrorCodes::FILE_MALFORMED);
}
WHEN("version is not 1")
{
auto modifiedBackupFile = backupFile;
modifiedBackupFile[37] = '1';
auto res = decryptKeyExport(modifiedBackupFile, password);
REQUIRE(!res.has_value());
REQUIRE(res.reason() == DecryptKeyExportErrorCodes::VERSION_UNSUPPORTED);
}
WHEN("does not pass HMAC")
{
auto modifiedBackupFile = backupFile;
modifiedBackupFile[400] = '1';
auto res = decryptKeyExport(modifiedBackupFile, password);
REQUIRE(!res.has_value());
REQUIRE(res.reason() == DecryptKeyExportErrorCodes::HMAC_FAILED);
}
WHEN("decrypted content is not json")
{
auto res = decryptKeyExport(nonJsonFile, password);
REQUIRE(!res.has_value());
REQUIRE(res.reason() == DecryptKeyExportErrorCodes::NOT_JSON);
}
}
TEST_CASE("Crypto::importInboundGroupSessions()")
{
Crypto a(RandomTag{}, genRandomData(Crypto::constructRandomSize()));
std::string roomId = "!room:example.org";
auto sessionKey = a.rotateMegOlmSessionWithRandom(
genRandomData(a.rotateMegOlmSessionRandomSize()),
1000,
roomId
);
// encrypt to get the session id
auto plainText = R"({
"content": {},
"type": "m.room.message",
"room_id": "!room:example.org"
})"_json;
auto encryptedContent = a.encryptMegOlm(plainText);
auto sessionId = encryptedContent["session_id"].template get<std::string>();
auto exportJson = nlohmann::json{
{
{"algorithm", "m.megolm.v1.aes-sha2"},
- {"forwarding_curve25519_key_chain", {}},
+ {"forwarding_curve25519_key_chain", nlohmann::json::array()},
{"room_id", roomId},
{"sender_claimed_keys", {
{"ed25519", a.ed25519IdentityKey()},
}},
{"sender_key", a.curve25519IdentityKey()},
{"session_id", sessionId},
{"session_key", sessionKey},
},
};
Crypto b(RandomTag{}, genRandomData(Crypto::constructRandomSize()));
WHEN("importing one")
{
auto res = b.importInboundGroupSessions(exportJson);
REQUIRE(res == 1);
+ auto encryptedEvent = nlohmann::json{
+ {"content", encryptedContent},
+ {"algorithm", CryptoConstants::megOlmAlgo},
+ {"room_id", roomId},
+ {"event_id", "!abc"},
+ {"origin_server_ts", 1000},
+ };
+ auto decrypted = b.decrypt(encryptedEvent);
+ REQUIRE(decrypted.has_value());
+ REQUIRE(nlohmann::json::parse(decrypted.value()) == plainText);
}
WHEN("importing empty array")
{
auto res = b.importInboundGroupSessions(nlohmann::json::array());
REQUIRE(res == 0);
}
WHEN("importing non-array")
{
auto res = b.importInboundGroupSessions(nlohmann::json(nullptr));
REQUIRE(res == 0);
}
WHEN("importing one valid and other invalid")
{
exportJson.push_back("a");
auto invalidKey = exportJson[0];
invalidKey["session_id"] = 1;
exportJson.insert(exportJson.begin(), invalidKey);
auto res = b.importInboundGroupSessions(exportJson);
REQUIRE(res == 1);
}
WHEN("importing bad session key")
{
exportJson[0]["session_key"] = "bad_session_key";
auto res = b.importInboundGroupSessions(exportJson);
REQUIRE(res == 0);
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Jul 20, 9:42 AM (1 d, 22 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1695496
Default Alt Text
(31 KB)
Attached To
Mode
rL libkazv
Attached
Detach File
Event Timeline
Log In to Comment