Skip to content
Snippets Groups Projects
Commit 3328fded authored by Konstantinos Sideris's avatar Konstantinos Sideris
Browse files

Add methods to encrypt/decrypt exported session keys

parent 1a539721
No related branches found
No related tags found
No related merge requests found
FILES=`find lib include tests examples -type f -type f \( -iname "*.cpp" -o -iname "*.hpp" \)`
SYNAPSE_IMAGE="avhost/docker-matrix:v0.33.3"
SYNAPSE_IMAGE="avhost/docker-matrix:v0.33.4"
DEPS_BUILD_DIR=.deps
DEPS_SOURCE_DIR=deps
......
......@@ -226,6 +226,21 @@ matches_inbound_session_from(OlmSession *session,
const std::string &id_key,
const std::string &one_time_key_message);
std::string
encrypt_exported_sessions(const mtx::crypto::ExportedSessionKeys &keys, std::string pass);
mtx::crypto::ExportedSessionKeys
decrypt_exported_sessions(const std::string &data, std::string pass);
std::string
base642bin(const std::string &b64);
std::string
bin2base64(const std::string &b64);
BinaryBuf
derive_key(const std::string &pass, const BinaryBuf &salt);
//! Verify a signature object as obtained from the response of /keys/query endpoint
bool
verify_identity_signature(nlohmann::json obj, const DeviceId &device_id, const UserId &user_id);
......
......@@ -10,8 +10,26 @@ STRONG_TYPE(RoomId, std::string)
namespace mtx {
namespace crypto {
static constexpr const char *ED25519 = "ed25519";
static constexpr const char *CURVE25519 = "curve25519";
constexpr auto ED25519 = "ed25519";
constexpr auto CURVE25519 = "curve25519";
constexpr auto MEGOLM_ALGO = "m.megolm.v1.aes-sha2";
struct ExportedSession
{
std::map<std::string, std::string> sender_claimed_keys; // currently unused.
std::vector<std::string> forwarding_curve25519_key_chain; // currently unused.
std::string algorithm = MEGOLM_ALGO;
std::string room_id;
std::string sender_key;
std::string session_id;
std::string session_key;
};
struct ExportedSessionKeys
{
std::vector<ExportedSession> sessions;
};
struct IdentityKeys
{
......@@ -19,6 +37,58 @@ struct IdentityKeys
std::string ed25519;
};
struct OneTimeKeys
{
using KeyId = std::string;
using EncodedKey = std::string;
std::map<KeyId, EncodedKey> curve25519;
};
inline void
to_json(nlohmann::json &obj, const ExportedSession &s)
{
obj["sender_claimed_keys"] = s.sender_claimed_keys;
obj["forwarding_curve25519_key_chain"] = s.forwarding_curve25519_key_chain;
obj["algorithm"] = s.algorithm;
obj["room_id"] = s.room_id;
obj["sender_key"] = s.sender_key;
obj["session_id"] = s.session_id;
obj["session_key"] = s.session_key;
}
inline void
from_json(const nlohmann::json &obj, ExportedSession &s)
{
s.room_id = obj.at("room_id").get<std::string>();
s.sender_key = obj.at("sender_key").get<std::string>();
s.session_id = obj.at("session_id").get<std::string>();
s.session_key = obj.at("session_key").get<std::string>();
using ClaimedKeys = std::map<std::string, std::string>;
using KeyChain = std::vector<std::string>;
if (obj.find("sender_claimed_keys") != obj.end())
s.sender_claimed_keys = obj.at("sender_claimed_keys").get<ClaimedKeys>();
if (obj.find("forwarding_curve25519_key_chain") != obj.end())
s.forwarding_curve25519_key_chain =
obj.at("forwarding_curve25519_key_chain").get<KeyChain>();
}
inline void
to_json(nlohmann::json &obj, const ExportedSessionKeys &keys)
{
obj["sessions"] = keys.sessions;
}
inline void
from_json(const nlohmann::json &obj, ExportedSessionKeys &keys)
{
keys.sessions = obj.at("sessions").get<std::vector<ExportedSession>>();
}
inline void
to_json(nlohmann::json &obj, const IdentityKeys &keys)
{
......@@ -33,24 +103,17 @@ from_json(const nlohmann::json &obj, IdentityKeys &keys)
keys.curve25519 = obj.at(CURVE25519).get<std::string>();
}
struct OneTimeKeys
{
using KeyId = std::string;
using EncodedKey = std::string;
std::map<KeyId, EncodedKey> curve25519;
};
inline void
to_json(nlohmann::json &obj, const OneTimeKeys &keys)
{
obj["curve25519"] = keys.curve25519;
obj[CURVE25519] = keys.curve25519;
}
inline void
from_json(const nlohmann::json &obj, OneTimeKeys &keys)
{
keys.curve25519 = obj.at("curve25519").get<std::map<std::string, std::string>>();
}
}
keys.curve25519 = obj.at(CURVE25519).get<std::map<std::string, std::string>>();
}
} // namespace crypto
} // namespace mtx
#include <iostream>
#include "mtxclient/crypto/client.hpp"
#include "mtxclient/crypto/types.hpp"
using json = nlohmann::json;
using namespace mtx::crypto;
......@@ -23,7 +24,7 @@ OlmClient::restore_account(const std::string &saved_data, const std::string &key
account_ = unpickle<AccountObject>(saved_data, key);
}
IdentityKeys
mtx::crypto::IdentityKeys
OlmClient::identity_keys() const
{
auto tmp_buf = create_buffer(olm_account_identity_keys_length(account_.get()));
......@@ -80,7 +81,7 @@ OlmClient::generate_one_time_keys(std::size_t number_of_keys)
return ret;
}
OneTimeKeys
mtx::crypto::OneTimeKeys
OlmClient::one_time_keys()
{
auto buf = create_buffer(olm_account_one_time_keys_length(account_.get()));
......@@ -497,3 +498,130 @@ mtx::crypto::verify_identity_signature(nlohmann::json obj,
return false;
}
std::string
mtx::crypto::encrypt_exported_sessions(const mtx::crypto::ExportedSessionKeys &keys,
std::string pass)
{
const auto plaintext = json(keys).dump();
const auto msg_len = plaintext.size();
const auto ciphertext_len = crypto_secretbox_MACBYTES + msg_len;
auto nonce = create_buffer(crypto_secretbox_NONCEBYTES);
auto ciphertext = create_buffer(ciphertext_len);
auto salt = create_buffer(crypto_pwhash_SALTBYTES);
auto key = derive_key(pass, salt);
crypto_secretbox_easy(reinterpret_cast<unsigned char *>(ciphertext.data()),
reinterpret_cast<const unsigned char *>(plaintext.data()),
msg_len,
nonce.data(),
reinterpret_cast<const unsigned char *>(key.data()));
// Format of the output buffer: (nonce + salt + ciphertext)
BinaryBuf output{nonce};
output.insert(
output.end(), std::make_move_iterator(salt.begin()), std::make_move_iterator(salt.end()));
output.insert(output.end(),
std::make_move_iterator(ciphertext.begin()),
std::make_move_iterator(ciphertext.end()));
return std::string(output.begin(), output.end());
}
mtx::crypto::ExportedSessionKeys
mtx::crypto::decrypt_exported_sessions(const std::string &data, std::string pass)
{
if (data.size() <
crypto_secretbox_MACBYTES + crypto_secretbox_NONCEBYTES + crypto_pwhash_SALTBYTES)
throw std::runtime_error{"decrypt_exported_sessions ciphertext too small"};
const auto nonce_start = data.begin();
const auto nonce_end = nonce_start + crypto_secretbox_NONCEBYTES;
auto nonce = BinaryBuf(nonce_start, nonce_end);
const auto salt_end = nonce_end + crypto_pwhash_SALTBYTES;
auto salt = BinaryBuf(nonce_end, salt_end);
auto ciphertext = BinaryBuf(salt_end, data.end());
auto decrypted = create_buffer(ciphertext.size() - crypto_secretbox_MACBYTES);
auto key = derive_key(pass, salt);
if (crypto_secretbox_open_easy(decrypted.data(),
reinterpret_cast<const unsigned char *>(ciphertext.data()),
ciphertext.size(),
nonce.data(),
reinterpret_cast<const unsigned char *>(key.data())) != 0)
throw std::runtime_error{"crypto_secretbox_open_easy: failed to decrypt"};
return json::parse(std::string(decrypted.begin(), decrypted.end()));
}
std::string
mtx::crypto::base642bin(const std::string &b64)
{
std::size_t bin_maxlen = b64.size();
std::size_t bin_len;
const char *max_end;
auto ciphertext = create_buffer(bin_maxlen);
const int rc = sodium_base642bin(reinterpret_cast<unsigned char *>(ciphertext.data()),
ciphertext.size(),
b64.data(),
b64.size(),
nullptr,
&bin_len,
&max_end,
sodium_base64_VARIANT_ORIGINAL);
if (rc != 0)
throw std::runtime_error{"base642bin failed"};
if (bin_len != bin_maxlen)
ciphertext.resize(bin_len);
return std::string(std::make_move_iterator(ciphertext.begin()),
std::make_move_iterator(ciphertext.end()));
}
std::string
mtx::crypto::bin2base64(const std::string &bin)
{
auto base64buf =
create_buffer(sodium_base64_encoded_len(bin.size(), sodium_base64_VARIANT_ORIGINAL));
sodium_bin2base64(reinterpret_cast<char *>(base64buf.data()),
base64buf.size(),
reinterpret_cast<const unsigned char *>(bin.data()),
bin.size(),
sodium_base64_VARIANT_ORIGINAL);
// Removing the null byte.
return std::string(base64buf.begin(), base64buf.end() - 1);
}
BinaryBuf
mtx::crypto::derive_key(const std::string &pass, const BinaryBuf &salt)
{
if (salt.size() != crypto_pwhash_SALTBYTES)
throw std::runtime_error{"derive_key: invalid buffer size for salt"};
auto key = create_buffer(crypto_secretbox_KEYBYTES);
// Derive a key from the user provided password.
if (crypto_pwhash(key.data(),
key.size(),
pass.data(),
pass.size(),
salt.data(),
crypto_pwhash_OPSLIMIT_INTERACTIVE,
crypto_pwhash_MEMLIMIT_INTERACTIVE,
crypto_pwhash_ALG_DEFAULT) != 0) {
throw std::runtime_error{"crypto_pwhash: out of memory"};
}
return key;
}
#include "mtxclient/crypto/types.hpp"
namespace mtx {
namespace crypto {
void
to_json(nlohmann::json &obj, const ExportedSession &s)
{
obj["sender_claimed_keys"] = s.sender_claimed_keys;
obj["forwarding_curve25519_key_chain"] = s.forwarding_curve25519_key_chain;
obj["algorithm"] = s.algorithm;
obj["room_id"] = s.room_id;
obj["sender_key"] = s.sender_key;
obj["session_id"] = s.session_id;
obj["session_key"] = s.session_key;
}
void
from_json(const nlohmann::json &obj, ExportedSession &s)
{
s.room_id = obj.at("room_id").get<std::string>();
s.sender_key = obj.at("sender_key").get<std::string>();
s.session_id = obj.at("session_id").get<std::string>();
s.session_key = obj.at("session_key").get<std::string>();
using ClaimedKeys = std::map<std::string, std::string>;
using KeyChain = std::vector<std::string>;
if (obj.find("sender_claimed_keys") != obj.end())
s.sender_claimed_keys = obj.at("sender_claimed_keys").get<ClaimedKeys>();
if (obj.find("forwarding_curve25519_key_chain") != obj.end())
s.forwarding_curve25519_key_chain =
obj.at("forwarding_curve25519_key_chain").get<KeyChain>();
}
void
to_json(nlohmann::json &obj, const ExportedSessionKeys &keys)
{
obj["sessions"] = keys.sessions;
}
void
from_json(const nlohmann::json &obj, ExportedSessionKeys &keys)
{
keys.sessions = obj.at("sessions").get<std::vector<ExportedSession>>();
}
void
to_json(nlohmann::json &obj, const IdentityKeys &keys)
{
obj[ED25519] = keys.ed25519;
obj[CURVE25519] = keys.curve25519;
}
void
from_json(const nlohmann::json &obj, IdentityKeys &keys)
{
keys.ed25519 = obj.at(ED25519).get<std::string>();
keys.curve25519 = obj.at(CURVE25519).get<std::string>();
}
void
to_json(nlohmann::json &obj, const OneTimeKeys &keys)
{
obj[CURVE25519] = keys.curve25519;
}
void
from_json(const nlohmann::json &obj, OneTimeKeys &keys)
{
keys.curve25519 = obj.at(CURVE25519).get<std::map<std::string, std::string>>();
}
} // namespace crypto
} // namespace mtx
......@@ -6,6 +6,7 @@
#include <gtest/gtest.h>
#include "mtxclient/crypto/client.hpp"
#include "mtxclient/crypto/types.hpp"
#include "mtxclient/http/client.hpp"
#include "mtx/requests.hpp"
......@@ -1035,6 +1036,41 @@ TEST(Encryption, PickleMegolmSessions)
EXPECT_EQ(std::string((char *)plaintext.data.data(), plaintext.data.size()), SECRET);
}
TEST(Base64, EncodingDecoding)
{
std::string random_str =
"+7TE+9qmFWHPnrBLd03MtoXsRlhYaQt2tLBg4kZJI+NFcXVxqNUI1S3c97eV8aVgSj1/"
"eo8PsnRNO29c2TgPLXvah2GDl90ehHjzH/"
"vMBJKPdqyE31ch7NYBgvLBVoesrRyDoIYDlbRhHiRDTmLKMC55WN1YvDJu2Pvg3WxZiANobk"
"0EPzHABqOYLaYiVxFrdko7mm8pDZXlatys+dvLv9Zf6lxfd/5MPK1C52m/UhnrZ3shS/"
"XBzxRfBikZQjl7C9IMo7l170ffipN8QHb5LmZlj4V41DUJHCU=";
EXPECT_EQ(base642bin(bin2base64(random_str)), random_str);
EXPECT_EQ(bin2base64(base642bin(random_str)), random_str);
}
TEST(ExportSessions, EncryptDecrypt)
{
constexpr auto PASS = "secret_passphrase";
ExportedSession s1;
s1.room_id = "!room_id:example.org";
s1.session_id = "sid";
s1.session_key = "skey";
ExportedSessionKeys keys;
keys.sessions = {s1, s1, s1};
std::string ciphertext = mtx::crypto::encrypt_exported_sessions(keys, PASS);
EXPECT_TRUE(ciphertext.size() > 0);
auto encoded = bin2base64(ciphertext);
auto decoded = base642bin(encoded);
auto restored_keys = mtx::crypto::decrypt_exported_sessions(decoded, PASS);
EXPECT_EQ(json(keys).dump(), json(restored_keys).dump());
}
TEST(Encryption, DISABLED_HandleRoomKeyEvent) {}
TEST(Encryption, DISABLED_HandleRoomKeyRequestEvent) {}
TEST(Encryption, DISABLED_HandleNewDevices) {}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment