Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85712071
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
80 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/base/types.hpp b/src/base/types.hpp
index 8c22d1e..0f83bc8 100644
--- a/src/base/types.hpp
+++ b/src/base/types.hpp
@@ -1,219 +1,232 @@
/*
* Copyright (C) 2020 Tusooa Zhu
*
* 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 "libkazv-config.hpp"
#include <string>
#include <variant>
#include <nlohmann/json.hpp>
#include <immer/array.hpp>
#include <immer/flex_vector.hpp>
#include <immer/map.hpp>
#include <boost/hana/type.hpp>
#include <lager/util.hpp>
#include "jsonwrap.hpp"
#include "event.hpp"
#include "descendent.hpp"
namespace Kazv
{
using Bytes = std::string;
enum Status : bool
{
FAIL,
SUCC,
};
namespace detail
{
auto hasEmptyMethod = boost::hana::is_valid(
[](auto &&x) -> decltype((void)x.empty()) {});
template<class U>
struct AddToJsonIfNeededT
{
template<class T>
static void call(json &j, std::string name, T &&arg) {
if constexpr (detail::hasEmptyMethod(arg)) {
if (! arg.empty()) {
j[name] = std::forward<T>(arg);
}
} else {
j[name] = std::forward<T>(arg);
}
}
};
template<class U>
struct AddToJsonIfNeededT<std::optional<U>>
{
template<class T>
static void call(json &j, std::string name, T &&arg) {
if (arg.has_value()) {
j[name] = std::forward<T>(arg).value();
}
}
};
}
template<class T>
inline void addToJsonIfNeeded(json &j, std::string name, T &&arg)
{
detail::AddToJsonIfNeededT<std::decay_t<T>>::call(j, name, std::forward<T>(arg));
};
// Provide a non-destructive way to add the map
// to json.
template<class MapT,
// disallow json object here
std::enable_if_t<!std::is_same_v<std::decay_t<MapT>, json>
&& !std::is_same_v<std::decay_t<MapT>, JsonWrap>, int> = 0>
inline void addPropertyMapToJson(json &j, MapT &&arg)
{
for (auto kv : std::forward<MapT>(arg)) {
auto [k, v] = kv;
j[k] = v;
}
};
inline void addPropertyMapToJson(json &j, const json &arg)
{
for (auto kv : arg.items()) {
auto [k, v] = kv;
j[k] = v;
}
};
using EventList = immer::flex_vector<Event>;
using namespace std::string_literals;
struct Null {};
using Variant = std::variant<std::string, JsonWrap, Null>;
namespace detail
{
struct DefaultValT
{
template<class T>
constexpr operator T() const {
return T();
}
};
}
constexpr detail::DefaultValT DEFVAL;
enum RoomMembership
{
Invite, Join, Leave
};
namespace detail
{
// emulates declval() but returns lvalue reference
template<class T>
typename std::add_lvalue_reference<T>::type declref() noexcept;
}
}
namespace nlohmann {
template <class T, class V>
struct adl_serializer<immer::map<T, V>> {
static void to_json(json& j, immer::map<T, V> map) {
- for (auto [k, v] : map) {
- j[k] = v;
+ if constexpr (std::is_same_v<T, std::string>) {
+ j = json::object();
+ for (auto [k, v] : map) {
+ j[k] = v;
+ }
+ } else {
+ j = json::array();
+ for (auto [k, v] : map) {
+ j.push_back(k);
+ j.push_back(v);
+ }
}
}
static void from_json(const json& j, immer::map<T, V> &m) {
immer::map<T, V> ret;
- if (j.is_object()) {
+ if constexpr (std::is_same_v<T, std::string>) {
for (const auto &[k, v] : j.items()) {
ret = std::move(ret).set(k, v);
}
+ } else {
+ for (std::size_t i = 0; i < j.size(); i += 2) {
+ ret = std::move(ret).set(j[i], j[i+1]);
+ }
}
m = ret;
}
};
template <class T>
struct adl_serializer<immer::array<T>> {
static void to_json(json& j, immer::array<T> arr) {
for (auto i : arr) {
j.push_back(json(i));
}
}
static void from_json(const json& j, immer::array<T> &a) {
immer::array<T> ret;
if (j.is_array()) {
for (const auto &i : j) {
ret = std::move(ret).push_back(i);
}
}
a = ret;
}
};
template <class T>
struct adl_serializer<immer::flex_vector<T>> {
static void to_json(json& j, immer::flex_vector<T> arr) {
for (auto i : arr) {
j.push_back(json(i));
}
}
static void from_json(const json& j, immer::flex_vector<T> &a) {
immer::flex_vector<T> ret;
if (j.is_array()) {
for (const auto &i : j) {
ret = std::move(ret).push_back(i.get<T>());
}
}
a = ret;
}
};
template <>
struct adl_serializer<Kazv::Variant> {
static void to_json(json& j, const Kazv::Variant &var) {
std::visit(lager::visitor{
[&j](std::string i) { j = i; },
[&j](Kazv::JsonWrap i) { j = i; },
[&j](Kazv::Null) { j = nullptr; }
}, var);
}
static void from_json(const json& j, Kazv::Variant &var) {
if (j.is_string()) {
var = j.get<std::string>();
} else if (j.is_null()) {
var = Kazv::Null{};
} else { // is object
var = Kazv::Variant(Kazv::JsonWrap(j));
}
}
};
}
diff --git a/src/crypto/crypto-p.hpp b/src/crypto/crypto-p.hpp
index 68e3038..97d50fd 100644
--- a/src/crypto/crypto-p.hpp
+++ b/src/crypto/crypto-p.hpp
@@ -1,79 +1,79 @@
/*
* 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 <libkazv-config.hpp>
#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<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);
+ std::string pickle() const;
+ void unpickle(std::string 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-util.hpp b/src/crypto/crypto-util.hpp
index f6d35fb..1fd2bac 100644
--- a/src/crypto/crypto-util.hpp
+++ b/src/crypto/crypto-util.hpp
@@ -1,101 +1,119 @@
/*
- * Copyright (C) 2020 Tusooa Zhu <tusooa@vista.aero>
+ * Copyright (C) 2021 Tusooa Zhu <tusooa@kazv.moe>
*
* 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 <libkazv-config.hpp>
#include <string>
#include <random>
#include <algorithm>
#include <vector>
+#include <nlohmann/json.hpp>
+
#include <boost/container_hash/hash.hpp>
namespace Kazv
{
using ByteArray = std::vector<unsigned char>;
struct KeyOfGroupSession
{
std::string roomId;
std::string senderKey;
std::string sessionId;
};
+ inline void from_json(const nlohmann::json &j, KeyOfGroupSession &k)
+ {
+ k.roomId = j.at("roomId");
+ k.senderKey = j.at("senderKey");
+ k.sessionId = j.at("sessionId");
+ }
+
+ inline void to_json(nlohmann::json &j, const KeyOfGroupSession &k)
+ {
+ j = nlohmann::json::object({
+ {"roomId", k.roomId},
+ {"senderKey", k.senderKey},
+ {"sessionId", k.sessionId},
+ });
+ }
+
inline bool operator==(KeyOfGroupSession a, KeyOfGroupSession b)
{
return a.roomId == b.roomId
&& a.senderKey == b.senderKey
&& a.sessionId == b.sessionId;
}
struct KeyOfOutboundSession
{
std::string userId;
std::string deviceId;
};
inline bool operator==(KeyOfOutboundSession a, KeyOfOutboundSession b)
{
return a.userId == b.userId
&& a.deviceId == b.deviceId;
};
[[nodiscard]] inline ByteArray genRandom(int len)
{
auto rd = std::random_device{};
auto ret = ByteArray(len, '\0');
std::generate(ret.begin(), ret.end(), [&] { return rd(); });
return ret;
}
namespace CryptoConstants
{
inline const std::string ed25519{"ed25519"};
inline const std::string curve25519{"curve25519"};
inline const std::string signedCurve25519{"signed_curve25519"};
inline const std::string olmAlgo{"m.olm.v1.curve25519-aes-sha2"};
inline const std::string megOlmAlgo{"m.megolm.v1.aes-sha2"};
}
}
namespace std
{
template<> struct hash<Kazv::KeyOfGroupSession>
{
std::size_t operator()(const Kazv::KeyOfGroupSession & k) const noexcept {
std::size_t seed = 0;
boost::hash_combine(seed, k.roomId);
boost::hash_combine(seed, k.senderKey);
boost::hash_combine(seed, k.sessionId);
return seed;
}
};
template<> struct hash<Kazv::KeyOfOutboundSession>
{
std::size_t operator()(const Kazv::KeyOfOutboundSession & k) const noexcept {
std::size_t seed = 0;
boost::hash_combine(seed, k.userId);
boost::hash_combine(seed, k.deviceId);
return seed;
}
};
}
diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp
index a3bbbad..dcc1a59 100644
--- a/src/crypto/crypto.cpp
+++ b/src/crypto/crypto.cpp
@@ -1,525 +1,552 @@
/*
- * Copyright (C) 2020 Tusooa Zhu <tusooa@vista.aero>
+ * Copyright (C) 2020-2021 Tusooa Zhu <tusooa@kazv.moe>
*
* 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 <libkazv-config.hpp>
#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 <types.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)
, inboundGroupSessions(that.inboundGroupSessions)
, outboundGroupSessions(that.outboundGroupSessions)
, utilityData(olm_utility_size(), '\0')
, utility(olm_utility(utilityData.data()))
{
unpickle(that.pickle());
}
- ByteArray CryptoPrivate::pickle() const
+ std::string CryptoPrivate::pickle() const
{
auto key = ByteArray(3, 'x');
- auto pickleData = ByteArray(olm_pickle_account_length(account), '\0');
+ auto pickleData = std::string(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)
+ void CryptoPrivate::unpickle(std::string 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 theirCurve25519IdentityKey)
{
try {
auto &session = m_d->knownSessions.at(theirCurve25519IdentityKey);
auto [type, body] = session.encrypt(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},
{"sender_key", curve25519IdentityKey()},
{"ciphertext", ciphertext},
{"session_id", session.sessionId()},
};
}
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(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(
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;
}
void Crypto::createOutboundSession(std::string theirIdentityKey,
std::string theirOneTimeKey)
{
auto session = Session(OutboundSessionTag{},
m_d->account,
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->pickle();
+ auto j = nlohmann::json::object({
+ {"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)
+ {
+ const auto &pickledData = j.at("account").template get<std::string>();
+ m_d->unpickle(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/crypto/crypto.hpp b/src/crypto/crypto.hpp
index 078216e..b6e8eb9 100644
--- a/src/crypto/crypto.hpp
+++ b/src/crypto/crypto.hpp
@@ -1,134 +1,153 @@
/*
- * Copyright (C) 2020-2021 Tusooa Zhu <tusooa@vista.aero>
+ * Copyright (C) 2020-2021 Tusooa Zhu <tusooa@kazv.moe>
*
* 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 <libkazv-config.hpp>
#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 theirCurve25519IdentityKey);
/// returns the content template with everything but deviceId
/// eventJson should contain type, room_id and content
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;
/// 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(
immer::map<std::string, immer::map<std::string /* deviceId */,
std::string /* curve25519IdentityKey */>> keyMap) const;
void createOutboundSession(std::string theirIdentityKey,
std::string theirOneTimeKey);
+ template<class Archive>
+ void save(Archive & ar, const unsigned int /* version */) const {
+ ar << toJson().dump();
+ }
+
+ template<class Archive>
+ void load(Archive &ar, const unsigned int /* version */) {
+ std::string j;
+ ar >> j;
+ loadJson(nlohmann::json::parse(std::move(j)));
+ }
+
+ BOOST_SERIALIZATION_SPLIT_MEMBER()
+
private:
+ nlohmann::json toJson() const;
+ void loadJson(const nlohmann::json &j);
+
friend class Session;
friend class SessionPrivate;
std::unique_ptr<CryptoPrivate> m_d;
};
}
+
+BOOST_CLASS_VERSION(Kazv::Crypto, 0)
diff --git a/src/crypto/inbound-group-session-p.hpp b/src/crypto/inbound-group-session-p.hpp
index b27efcd..36ce94d 100644
--- a/src/crypto/inbound-group-session-p.hpp
+++ b/src/crypto/inbound-group-session-p.hpp
@@ -1,71 +1,84 @@
/*
- * Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
+ * Copyright (C) 2021 Tusooa Zhu <tusooa@kazv.moe>
*
* 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 <libkazv-config.hpp>
#include "inbound-group-session.hpp"
#include <olm/olm.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;
ByteArray sessionData;
OlmInboundGroupSession *session;
std::string ed25519Key;
bool valid{false};
immer::map<std::uint32_t /* index */, KeyOfDecryptedEvent> decryptedEvents;
std::size_t checkError(std::size_t code) const;
std::string error() const;
- ByteArray pickle() const;
- bool unpickle(ByteArray pickleData);
+ std::string pickle() const;
+ bool unpickle(std::string pickleData);
};
}
diff --git a/src/crypto/inbound-group-session.cpp b/src/crypto/inbound-group-session.cpp
index ca5d40f..eafe729 100644
--- a/src/crypto/inbound-group-session.cpp
+++ b/src/crypto/inbound-group-session.cpp
@@ -1,176 +1,200 @@
/*
- * Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
+ * Copyright (C) 2021 Tusooa Zhu <tusooa@kazv.moe>
*
* 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 <libkazv-config.hpp>
#include "inbound-group-session-p.hpp"
+#include <types.hpp>
+
#include <debug.hpp>
namespace Kazv
{
std::size_t InboundGroupSessionPrivate::checkError(std::size_t code) const
{
if (code == olm_error()) {
kzo.crypto.warn() << "Olm inbound group session error: "
<< olm_inbound_group_session_last_error(session) << std::endl;
}
return code;
}
std::string InboundGroupSessionPrivate::error() const
{
return olm_inbound_group_session_last_error(session);
}
InboundGroupSessionPrivate::InboundGroupSessionPrivate()
: sessionData(olm_inbound_group_session_size(), '\0')
, session(olm_inbound_group_session(sessionData.data()))
{
}
InboundGroupSessionPrivate::InboundGroupSessionPrivate(std::string sessionKey, std::string ed25519Key)
: InboundGroupSessionPrivate()
{
this->ed25519Key = ed25519Key;
auto keyBuf = ByteArray(sessionKey.begin(), sessionKey.end());
auto res = checkError(olm_init_inbound_group_session(session, keyBuf.data(), keyBuf.size()));
if (res != olm_error()) {
valid = true;
}
}
InboundGroupSessionPrivate::InboundGroupSessionPrivate(const InboundGroupSessionPrivate &that)
: InboundGroupSessionPrivate()
{
ed25519Key = that.ed25519Key;
valid = unpickle(that.pickle());
+ decryptedEvents = that.decryptedEvents;
}
- ByteArray InboundGroupSessionPrivate::pickle() const
+ std::string InboundGroupSessionPrivate::pickle() const
{
- auto pickleData = ByteArray(olm_pickle_inbound_group_session_length(session), '\0');
+ auto pickleData = std::string(olm_pickle_inbound_group_session_length(session), '\0');
auto key = ByteArray(3, 'x');
checkError(olm_pickle_inbound_group_session(session,
key.data(), key.size(),
pickleData.data(), pickleData.size()));
return pickleData;
}
- bool InboundGroupSessionPrivate::unpickle(ByteArray pickleData)
+ bool InboundGroupSessionPrivate::unpickle(std::string pickleData)
{
auto key = ByteArray(3, 'x');
auto res = checkError(olm_unpickle_inbound_group_session(
session,
key.data(), key.size(),
pickleData.data(), pickleData.size()));
return res != olm_error();
}
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;
}
MaybeString InboundGroupSession::decrypt(std::string message, std::string eventId, std::int_fast64_t originServerTs)
{
ByteArray msgBuffer(message.begin(), message.end());
ByteArray msgBuffer2 = msgBuffer;
auto size = m_d->checkError(olm_group_decrypt_max_plaintext_length(
m_d->session,
msgBuffer.data(), msgBuffer.size()));
if (size == olm_error()) {
return NotBut(m_d->error());
}
auto plainText = ByteArray(size, '\0');
std::uint32_t messageIndex;
auto actualSize = m_d->checkError(olm_group_decrypt(
m_d->session,
msgBuffer2.data(), msgBuffer2.size(),
plainText.data(), plainText.size(),
&messageIndex));
if (actualSize == olm_error()) {
return NotBut(m_d->error());
}
// 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.begin() + actualSize);
}
std::string InboundGroupSession::ed25519Key() const
{
return m_d->ed25519Key;
}
+
+ void to_json(nlohmann::json &j, const InboundGroupSession &s)
+ {
+ j = nlohmann::json::object();
+ 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();
+ }
+ }
+
+ 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) {
+ s.m_d->valid = s.m_d->unpickle(j.at("session"));
+ }
+ }
}
diff --git a/src/crypto/inbound-group-session.hpp b/src/crypto/inbound-group-session.hpp
index 02d5013..537410b 100644
--- a/src/crypto/inbound-group-session.hpp
+++ b/src/crypto/inbound-group-session.hpp
@@ -1,54 +1,56 @@
/*
* 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 <libkazv-config.hpp>
#include <memory>
#include <maybe.hpp>
#include <event.hpp>
#include "crypto-util.hpp"
namespace Kazv
{
struct InboundGroupSessionPrivate;
class InboundGroupSession
{
public:
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;
std::string ed25519Key() 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/crypto/outbound-group-session-p.hpp b/src/crypto/outbound-group-session-p.hpp
index b29eeee..0cbe78c 100644
--- a/src/crypto/outbound-group-session-p.hpp
+++ b/src/crypto/outbound-group-session-p.hpp
@@ -1,56 +1,56 @@
/*
* 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 <libkazv-config.hpp>
#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 pickle() const;
+ bool unpickle(std::string pickleData);
std::string sessionKey();
};
}
diff --git a/src/crypto/outbound-group-session.cpp b/src/crypto/outbound-group-session.cpp
index 1fc6a6a..67c1fb3 100644
--- a/src/crypto/outbound-group-session.cpp
+++ b/src/crypto/outbound-group-session.cpp
@@ -1,179 +1,201 @@
/*
- * Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
+ * Copyright (C) 2021 Tusooa Zhu <tusooa@kazv.moe>
*
* 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 <libkazv-config.hpp>
#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
+ std::string OutboundGroupSessionPrivate::pickle() const
{
- auto pickleData = ByteArray(olm_pickle_outbound_group_session_length(session), '\0');
+ auto pickleData = std::string(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)
+ bool OutboundGroupSessionPrivate::unpickle(std::string 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 OutboundGroupSessionPrivate::sessionKey()
{
auto size = olm_outbound_group_session_key_length(session);
auto keyBuf = ByteArray(size, '\0');
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;
}
+
+ void to_json(nlohmann::json &j, const OutboundGroupSession &s)
+ {
+ j = nlohmann::json::object();
+ j["valid"] = s.m_d->valid;
+ j["creationTime"] = s.m_d->creationTime;
+ j["initialSessionKey"] = s.m_d->initialSessionKey;
+ if (s.m_d->valid) {
+ j["session"] = s.m_d->pickle();
+ }
+ }
+
+ void from_json(const nlohmann::json &j, OutboundGroupSession &s)
+ {
+ s.m_d->valid = j.at("valid");
+ s.m_d->creationTime = j.at("creationTime");
+ s.m_d->initialSessionKey = j.at("initialSessionKey");
+ if (s.m_d->valid) {
+ s.m_d->valid = s.m_d->unpickle(j.at("session"));
+ }
+ }
+
}
diff --git a/src/crypto/outbound-group-session.hpp b/src/crypto/outbound-group-session.hpp
index a7f7a80..d942dcc 100644
--- a/src/crypto/outbound-group-session.hpp
+++ b/src/crypto/outbound-group-session.hpp
@@ -1,59 +1,61 @@
/*
- * Copyright (C) 2021 Tusooa Zhu <tusooa@vista.aero>
+ * Copyright (C) 2021 Tusooa Zhu <tusooa@kazv.moe>
*
* 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 <libkazv-config.hpp>
#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:
+ friend void to_json(nlohmann::json &j, const OutboundGroupSession &s);
+ friend void from_json(const nlohmann::json &j, OutboundGroupSession &s);
std::unique_ptr<OutboundGroupSessionPrivate> m_d;
};
}
diff --git a/src/crypto/session-p.hpp b/src/crypto/session-p.hpp
index b16f98d..75e6dfb 100644
--- a/src/crypto/session-p.hpp
+++ b/src/crypto/session-p.hpp
@@ -1,55 +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 <libkazv-config.hpp>
#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};
- ByteArray pickle() const;
- bool unpickle(ByteArray data);
+ std::string pickle() const;
+ bool unpickle(std::string 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 e5c748f..96df816 100644
--- a/src/crypto/session.cpp
+++ b/src/crypto/session.cpp
@@ -1,225 +1,241 @@
/*
* 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 <libkazv-config.hpp>
#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()
{
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()
{
valid = unpickle(that.pickle());
}
- ByteArray SessionPrivate::pickle() const
+ std::string SessionPrivate::pickle() const
{
- auto pickleData = ByteArray(olm_pickle_session_length(session), '\0');
+ auto pickleData = std::string(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)
+ bool SessionPrivate::unpickle(std::string 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, "" };
}
+
+ void to_json(nlohmann::json &j, const Session &s)
+ {
+ j = nlohmann::json::object({
+ {"valid", s.m_d->valid},
+ {"data", s.m_d->valid ? s.m_d->pickle() : std::string()}
+ });
+ }
+
+ void from_json(const nlohmann::json &j, Session &s)
+ {
+ if (j.at("valid").template get<bool>()) {
+ s.m_d->valid = s.m_d->unpickle(j.at("data"));
+ }
+ }
+
}
diff --git a/src/crypto/session.hpp b/src/crypto/session.hpp
index 38c391e..d009f2b 100644
--- a/src/crypto/session.hpp
+++ b/src/crypto/session.hpp
@@ -1,75 +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 <libkazv-config.hpp>
#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();
bool matches(std::string message);
bool valid() const;
MaybeString decrypt(int type, std::string message);
std::pair<int /* type */, std::string /* message */> encrypt(std::string plainText);
private:
friend class Crypto;
friend class CryptoPrivate;
+
+ friend void to_json(nlohmann::json &j, const Session &s);
+ friend void from_json(const nlohmann::json &j, Session &s);
std::unique_ptr<SessionPrivate> m_d;
};
}
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index fa4b1a6..a9b788e 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,41 +1,42 @@
include(CTest)
set(KAZVTEST_RESPATH ${CMAKE_CURRENT_SOURCE_DIR}/resources)
configure_file(kazvtest-respath.hpp.in kazvtest-respath.hpp)
add_executable(kazvtest
testmain.cpp
basejobtest.cpp
event-test.cpp
cursorutiltest.cpp
+ base/serialization-test.cpp
+ base/types-test.cpp
client/client-test-util.cpp
client/sync-test.cpp
client/content-test.cpp
client/paginate-test.cpp
client/util-test.cpp
- base/serialization-test.cpp
kazvjobtest.cpp
event-emitter-test.cpp
crypto-test.cpp
promise-test.cpp
store-test.cpp
file-desc-test.cpp
)
target_include_directories(
kazvtest
PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(kazvtest
PRIVATE Catch2::Catch2
PRIVATE kazv
PRIVATE kazveventemitter
PRIVATE kazvjob
PRIVATE nlohmann_json::nlohmann_json
PRIVATE immer
PRIVATE lager
PRIVATE zug)
diff --git a/src/tests/base/types-test.cpp b/src/tests/base/types-test.cpp
new file mode 100644
index 0000000..ed85281
--- /dev/null
+++ b/src/tests/base/types-test.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 Tusooa Zhu <tusooa@kazv.moe>
+ *
+ * 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 <libkazv-config.hpp>
+
+#include <catch2/catch.hpp>
+
+#include <types.hpp>
+
+using namespace Kazv;
+
+TEST_CASE("immer::map<std::string, X> should convert to json object", "[base][types]")
+{
+ immer::map<std::string, int> m;
+ json j = m;
+
+ REQUIRE(j.is_object());
+}
+
+TEST_CASE("immer::map<non-std::string, X> should convert to json array", "[base][types]")
+{
+ immer::map<int, int> m;
+ json j = m;
+
+ REQUIRE(j.is_array());
+}
diff --git a/src/tests/crypto-test.cpp b/src/tests/crypto-test.cpp
index f5a3dd9..248dbcd 100644
--- a/src/tests/crypto-test.cpp
+++ b/src/tests/crypto-test.cpp
@@ -1,110 +1,174 @@
/*
- * Copyright (C) 2020 Tusooa Zhu <tusooa@vista.aero>
+ * Copyright (C) 2020-2021 Tusooa Zhu <tusooa@kazv.moe>
*
* 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 <libkazv-config.hpp>
#include <catch2/catch.hpp>
+#include <sstream>
+
+#include <boost/archive/text_iarchive.hpp>
+#include <boost/archive/text_oarchive.hpp>
+
#include <crypto/crypto.hpp>
+
using namespace Kazv;
using namespace Kazv::CryptoConstants;
+using IAr = boost::archive::text_iarchive;
+using OAr = boost::archive::text_oarchive;
+
+template<class T>
+static void serializeDup(const T &in, T &out)
+{
+ std::stringstream stream;
+
+ {
+ auto ar = OAr(stream);
+ ar << in;
+ }
+
+ {
+ auto ar = IAr(stream);
+ ar >> out;
+ }
+}
+
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("Crypto should be serializable", "[crypto]")
+{
+ Crypto crypto;
+ crypto.genOneTimeKeys(1);
+ auto oneTimeKeys = crypto.unpublishedOneTimeKeys();
+
+ Crypto cryptoClone;
+
+ serializeDup(crypto, cryptoClone);
+
+ REQUIRE(crypto.ed25519IdentityKey() == cryptoClone.ed25519IdentityKey());
+ REQUIRE(crypto.curve25519IdentityKey() == cryptoClone.curve25519IdentityKey());
+
+ auto oneTimeKeys2 = cryptoClone.unpublishedOneTimeKeys();
+
+ REQUIRE(oneTimeKeys == oneTimeKeys2);
+ REQUIRE(crypto.numUnpublishedOneTimeKeys() == cryptoClone.numUnpublishedOneTimeKeys());
+}
+
+TEST_CASE("Serialize Crypto with an OutboundGroupSession", "[crypto]")
+{
+ Crypto crypto;
+
+ std::string roomId = "!example:example.org";
+ auto desc = MegOlmSessionRotateDesc{500000 /* ms */, 100 /* messages */};
+
+ crypto.rotateMegOlmSession(roomId);
+
+ Crypto cryptoClone;
+
+ serializeDup(crypto, cryptoClone);
+
+ REQUIRE(! cryptoClone.rotateMegOlmSessionIfNeeded(roomId, desc).has_value());
+}
+
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()));
+ Crypto aClone{a};
+
auto devices = a.devicesMissingOutboundSessionKey(devMap);
+ auto devicesAClone = aClone.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);
+ REQUIRE(devicesAClone == expected);
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Sep 19, 9:14 AM (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1769230
Default Alt Text
(80 KB)
Attached To
Mode
rL libkazv
Attached
Detach File
Event Timeline
Log In to Comment