Page MenuHomePhorge

No OneTemporary

Size
80 KB
Referenced Files
None
Subscribers
None
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

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)

Event Timeline