Page MenuHomePhorge

No OneTemporary

Size
16 KB
Referenced Files
None
Subscribers
None
diff --git a/src/crypto/inbound-group-session-p.hpp b/src/crypto/inbound-group-session-p.hpp
index ecdd1b6..9c926e9 100644
--- a/src/crypto/inbound-group-session-p.hpp
+++ b/src/crypto/inbound-group-session-p.hpp
@@ -1,71 +1,72 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2021 Tusooa Zhu <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <libkazv-config.hpp>
#include "inbound-group-session.hpp"
#include <vodozemac.h>
#include <immer/map.hpp>
namespace Kazv
{
struct KeyOfDecryptedEvent
{
std::string eventId;
Timestamp originServerTs;
};
inline void to_json(nlohmann::json &j, const KeyOfDecryptedEvent &k)
{
j = nlohmann::json::object();
j["eventId"] = k.eventId;
j["originServerTs"] = k.originServerTs;
}
inline void from_json(const nlohmann::json &j, KeyOfDecryptedEvent &k)
{
k.eventId = j.at("eventId");
k.originServerTs = j.at("originServerTs");
}
inline bool operator==(KeyOfDecryptedEvent a, KeyOfDecryptedEvent b)
{
return a.eventId == b.eventId
&& a.originServerTs == b.originServerTs;
}
inline bool operator!=(KeyOfDecryptedEvent a, KeyOfDecryptedEvent b)
{
return !(a == b);
}
struct InboundGroupSessionPrivate
{
InboundGroupSessionPrivate();
InboundGroupSessionPrivate(std::string sessionKey, std::string ed25519Key);
InboundGroupSessionPrivate(const InboundGroupSessionPrivate &that);
~InboundGroupSessionPrivate() = default;
std::optional<rust::Box<vodozemac::megolm::InboundGroupSession>> session;
std::string ed25519Key;
bool valid{false};
+ bool isImported{false};
immer::map<std::uint32_t /* index */, KeyOfDecryptedEvent> decryptedEvents;
std::size_t checkError(std::size_t code) const;
std::string error() const;
std::string pickle() const;
bool unpickle(std::string pickleData);
bool unpickleFromLibolm(std::string pickleData);
};
}
diff --git a/src/crypto/inbound-group-session.cpp b/src/crypto/inbound-group-session.cpp
index 4b1bb50..6bd4217 100644
--- a/src/crypto/inbound-group-session.cpp
+++ b/src/crypto/inbound-group-session.cpp
@@ -1,190 +1,224 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2021-2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include "inbound-group-session-p.hpp"
#include "crypto-util-p.hpp"
#include <types.hpp>
#include <debug.hpp>
namespace Kazv
{
InboundGroupSessionPrivate::InboundGroupSessionPrivate()
: session(std::nullopt)
{
}
InboundGroupSessionPrivate::InboundGroupSessionPrivate(std::string sessionKey, std::string ed25519Key)
: InboundGroupSessionPrivate()
{
valid = false;
this->ed25519Key = ed25519Key;
auto keyRust = checkVodozemacError([&]() { return vodozemac::megolm::session_key_from_base64(rust::Str(sessionKey)); });
- if (!keyRust) {
+ if (keyRust) {
+ this->session = checkVodozemacError([&]() { return vodozemac::megolm::new_inbound_group_session(*(keyRust.value())); });
+ if (this->session.has_value()) {
+ valid = true;
+ }
return;
- }
-
- this->session = checkVodozemacError([&]() { return vodozemac::megolm::new_inbound_group_session(*(keyRust.value())); });
- if (this->session.has_value()) {
- valid = true;
+ } else {
+ // Try if this is the session export format
+ auto exportedKeyRust = checkVodozemacError([&]() {
+ return vodozemac::megolm::exported_session_key_from_base64(rust::Str(sessionKey));
+ });
+ if (!exportedKeyRust) {
+ return;
+ }
+ this->session = checkVodozemacError([&]() {
+ return vodozemac::megolm::import_inbound_group_session(*(exportedKeyRust.value()));
+ });
+ if (this->session.has_value()) {
+ isImported = true;
+ valid = true;
+ }
}
}
InboundGroupSessionPrivate::InboundGroupSessionPrivate(const InboundGroupSessionPrivate &that)
: InboundGroupSessionPrivate()
{
ed25519Key = that.ed25519Key;
if (that.valid) {
valid = unpickle(that.pickle());
}
+ isImported = that.isImported;
decryptedEvents = that.decryptedEvents;
}
std::string InboundGroupSessionPrivate::pickle() const
{
auto pickleData = this->session.value()->pickle(
VODOZEMAC_PICKLE_KEY);
return static_cast<std::string>(pickleData);
}
bool InboundGroupSessionPrivate::unpickle(std::string pickleData)
{
this->session = checkVodozemacError([&]() {
return vodozemac::megolm::inbound_group_session_from_pickle(pickleData, VODOZEMAC_PICKLE_KEY);
});
return this->session.has_value();
}
bool InboundGroupSessionPrivate::unpickleFromLibolm(std::string pickleData)
{
this->session = checkVodozemacError([&]() {
return vodozemac::megolm::inbound_group_session_from_libolm_pickle(pickleData, rust::Slice<const unsigned char>(OLM_PICKLE_KEY.data(), OLM_PICKLE_KEY.size()));
});
return this->session.has_value();
}
InboundGroupSession::InboundGroupSession()
: m_d(new InboundGroupSessionPrivate)
{
}
InboundGroupSession::InboundGroupSession(std::string sessionKey, std::string ed25519Key)
: m_d(new InboundGroupSessionPrivate(std::move(sessionKey), std::move(ed25519Key)))
{
}
InboundGroupSession::~InboundGroupSession() = default;
InboundGroupSession::InboundGroupSession(const InboundGroupSession &that)
: m_d(new InboundGroupSessionPrivate(*that.m_d))
{
}
InboundGroupSession::InboundGroupSession(InboundGroupSession &&that)
: m_d(std::move(that.m_d))
{
}
InboundGroupSession &InboundGroupSession::operator=(const InboundGroupSession &that)
{
m_d.reset(new InboundGroupSessionPrivate(*that.m_d));
return *this;
}
InboundGroupSession &InboundGroupSession::operator=(InboundGroupSession &&that)
{
m_d = std::move(that.m_d);
return *this;
}
bool InboundGroupSession::valid() const
{
return m_d && m_d->valid;
}
+ bool InboundGroupSession::isImported() const
+ {
+ return m_d->isImported;
+ }
+
MaybeString InboundGroupSession::decrypt(std::string message, std::string eventId, std::int_fast64_t originServerTs)
{
auto messageRust = checkVodozemacError([&]() { return vodozemac::megolm::megolm_message_from_base64(rust::Str(message)); });
if (!messageRust.has_value()) {
return NotBut(messageRust.reason());
}
auto decrypted = checkVodozemacError([&]() { return m_d->session.value()->decrypt(*(messageRust.value())); });
if (!decrypted.has_value()) {
return NotBut(decrypted.reason());
}
auto [plainText, messageIndex] = *decrypted;
// Check for possible replay attack
auto keyForThisMsg = KeyOfDecryptedEvent{eventId, originServerTs};
if (! m_d->decryptedEvents.find(messageIndex)) {
m_d->decryptedEvents = std::move(m_d->decryptedEvents)
.set(messageIndex, keyForThisMsg);
} else { // already decrypted in the past
auto key = m_d->decryptedEvents.at(messageIndex);
if (key != keyForThisMsg) {
return NotBut("This message has been decrypted in the past, but eventId or originServerTs does not match");
}
}
return std::string(plainText.begin(), plainText.end());
}
std::string InboundGroupSession::ed25519Key() const
{
return m_d->ed25519Key;
}
bool InboundGroupSession::merge(InboundGroupSession &that)
{
if (!valid() || !that.valid()) {
return false;
}
auto merged = checkVodozemacError([this, &that]() {
return m_d->session.value()->merge(*that.m_d->session.value());
});
if (!merged.has_value()) {
return false;
} else {
m_d->session = std::move(merged);
+ if (!m_d->isImported || !that.m_d->isImported) {
+ m_d->isImported = false;
+ }
return true;
}
}
+ std::string InboundGroupSession::toExportFormat() const
+ {
+ auto exported = m_d->session.value()->export_at(m_d->session.value()->first_known_index());
+ auto exportedStr = exported->to_base64();
+ return std::string(exportedStr);
+ }
+
void to_json(nlohmann::json &j, const InboundGroupSession &s)
{
j = nlohmann::json::object();
j["version"] = 1;
j["ed25519Key"] = s.m_d->ed25519Key;
j["valid"] = s.m_d->valid;
j["decryptedEvents"] = s.m_d->decryptedEvents;
if (s.m_d->valid) {
j["session"] = s.m_d->pickle();
+ j["isImported"] = s.m_d->isImported;
}
}
void from_json(const nlohmann::json &j, InboundGroupSession &s)
{
s.m_d->ed25519Key = j.at("ed25519Key");
s.m_d->valid = j.at("valid");
s.m_d->decryptedEvents = j.at("decryptedEvents");
if (s.m_d->valid) {
if (j.contains("version") && j["version"] == 1) { // vodozemac format
s.m_d->valid = s.m_d->unpickle(j.at("session"));
} else { // libolm format
s.m_d->valid = s.m_d->unpickleFromLibolm(j.at("session"));
}
+ if (j.contains("isImported")) {
+ s.m_d->isImported = j["isImported"].template get<bool>();
+ }
}
}
}
diff --git a/src/crypto/inbound-group-session.hpp b/src/crypto/inbound-group-session.hpp
index 394c680..701b913 100644
--- a/src/crypto/inbound-group-session.hpp
+++ b/src/crypto/inbound-group-session.hpp
@@ -1,51 +1,71 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2021 Tusooa Zhu <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <libkazv-config.hpp>
#include <memory>
#include <maybe.hpp>
#include <event.hpp>
#include "crypto-util.hpp"
namespace Kazv
{
struct InboundGroupSessionPrivate;
class InboundGroupSession
{
public:
+ struct ImportTag {};
explicit InboundGroupSession();
explicit InboundGroupSession(std::string sessionKey, std::string ed25519Key);
InboundGroupSession(const InboundGroupSession &that);
InboundGroupSession(InboundGroupSession &&that);
InboundGroupSession &operator=(const InboundGroupSession &that);
InboundGroupSession &operator=(InboundGroupSession &&that);
~InboundGroupSession();
MaybeString decrypt(std::string message, std::string eventId, Timestamp originServerTs);
bool valid() const;
+ /**
+ * Check whether this session is imported (from the session-export format).
+ *
+ * Precondition: valid() is true.
+ *
+ * @return true iff this session is imported.
+ */
+ bool isImported() const;
+
std::string ed25519Key() const;
/**
* Try to merge this session with another session.
*
* @param that The other session to merge with.
* @return true iff the two sessions are mergeable.
*/
bool merge(InboundGroupSession &that);
+
+ /**
+ * Export the session to session-export format.
+ *
+ * Precondition: valid() is true.
+ *
+ * @return The base64 encoded session-export format.
+ */
+ std::string toExportFormat() const;
+
private:
friend void to_json(nlohmann::json &j, const InboundGroupSession &s);
friend void from_json(const nlohmann::json &j, InboundGroupSession &s);
std::unique_ptr<InboundGroupSessionPrivate> m_d;
};
}
diff --git a/src/tests/crypto/inbound-group-session-test.cpp b/src/tests/crypto/inbound-group-session-test.cpp
index a4b5db7..eb9a6c6 100644
--- a/src/tests/crypto/inbound-group-session-test.cpp
+++ b/src/tests/crypto/inbound-group-session-test.cpp
@@ -1,84 +1,108 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <catch2/catch_test_macros.hpp>
#include <inbound-group-session.hpp>
#include <outbound-group-session.hpp>
#include <crypto.hpp>
#include "crypto-test-resource.hpp"
using namespace Kazv;
static const auto resource = cryptoDumpResource();
TEST_CASE("InboundGroupSession conversion from libolm to vodozemac")
{
auto sessionJson = resource["a"]["inboundGroupSessions"][0][1];
auto session = sessionJson.template get<InboundGroupSession>();
REQUIRE(session.valid());
+ REQUIRE(!session.isImported());
REQUIRE(session.ed25519Key() == sessionJson["ed25519Key"]);
auto encrypted = resource["megolmEncrypted"];
auto plainText = resource["megolmPlainText"];
auto a = Crypto();
a.loadJson(resource["a"]);
auto decrypted = a.decrypt(encrypted);
REQUIRE(decrypted.has_value());
auto decryptedJson = json::parse(decrypted.value());
REQUIRE(decryptedJson == plainText);
}
TEST_CASE("InboundGroupSession::from_json error handling")
{
auto sessionJson = resource["a"]["inboundGroupSessions"][0][1];
sessionJson["session"] = "AAAAAAAAAA";
auto session = sessionJson.template get<InboundGroupSession>();
REQUIRE(!session.valid());
}
TEST_CASE("InboundGroupSession::decrypt error handling")
{
auto sessionJson = resource["a"]["inboundGroupSessions"][0][1];
auto session = sessionJson.template get<InboundGroupSession>();
WHEN("message not decryptable") {
auto res = session.decrypt("AAAAAA", "$1", 1234);
REQUIRE(!res);
}
WHEN("message is not valid base64") {
auto res = session.decrypt("喵喵喵", "$1", 1234);
REQUIRE(!res);
}
WHEN("message is before the index") {
auto ogs = OutboundGroupSession(RandomTag{}, genRandomData(OutboundGroupSession::constructRandomSize()), 0);
auto encrypted1 = ogs.encrypt("text");
auto igs = InboundGroupSession(ogs.sessionKey(), "placeholder");
auto res = igs.decrypt(encrypted1, "$1", 1234);
REQUIRE(!res.has_value());
}
}
TEST_CASE("InboundGroupSession constructor error handling")
{
WHEN("key not valid") {
auto session = InboundGroupSession("AAAAAA", "ed25519Key");
REQUIRE(!session.valid());
}
WHEN("key is not valid base64") {
auto session = InboundGroupSession("喵喵喵", "ed25519Key");
REQUIRE(!session.valid());
}
}
TEST_CASE("invalid InboundGroupSession is copyable")
{
InboundGroupSession session("AAAAAA", "ed25519Key");
REQUIRE(!session.valid());
auto session2 = session;
REQUIRE(!session.valid());
}
+
+TEST_CASE("export and import InboundGroupSession", "[crypto]")
+{
+ auto ogs = OutboundGroupSession(RandomTag{}, genRandomData(OutboundGroupSession::constructRandomSize()), 0);
+ auto igs = InboundGroupSession(ogs.sessionKey(), "placeholder");
+ auto exported = igs.toExportFormat();
+ auto imported = InboundGroupSession(exported, "placeholder");
+ REQUIRE(imported.valid());
+ REQUIRE(imported.isImported());
+ WHEN("try to decrypt") {
+ auto encrypted1 = ogs.encrypt("text");
+ auto res = imported.decrypt(encrypted1, "$1", 1234);
+ REQUIRE(res.has_value());
+ REQUIRE(res.value() == "text");
+ }
+
+ WHEN("serialization") {
+ auto j = json(imported);
+ auto deserialized = j.template get<InboundGroupSession>();
+ REQUIRE(deserialized.valid());
+ REQUIRE(deserialized.isImported());
+ }
+}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Sep 19, 8:20 AM (1 d, 18 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1769190
Default Alt Text
(16 KB)

Event Timeline