Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85711987
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
16 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Sep 19, 8:26 AM (1 d, 17 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1769190
Default Alt Text
(16 KB)
Attached To
Mode
rL libkazv
Attached
Detach File
Event Timeline
Log In to Comment