From 6c87fbd6fdc1ab2c267b30ef1f7ffd77ef057a07 Mon Sep 17 00:00:00 2001 From: Nicolas Werner <nicolas.werner@hotmail.de> Date: Fri, 8 Oct 2021 14:47:12 +0200 Subject: [PATCH] Add online key backup bootstrapping --- include/mtx/responses/common.hpp | 9 ++++ include/mtxclient/http/client.hpp | 4 ++ lib/crypto/client.cpp | 21 +++++++++ lib/http/client.cpp | 10 +++++ lib/structs/responses/common.cpp | 6 +++ tests/crypto.cpp | 25 +++++++++++ tests/e2ee.cpp | 73 +++++++++++++++++++++++++++++-- 7 files changed, 145 insertions(+), 3 deletions(-) diff --git a/include/mtx/responses/common.hpp b/include/mtx/responses/common.hpp index 753991bc3..fdf7b3951 100644 --- a/include/mtx/responses/common.hpp +++ b/include/mtx/responses/common.hpp @@ -57,6 +57,15 @@ struct FilterId void from_json(const nlohmann::json &obj, FilterId &response); +//! A new room version as returned by the room_keys/version API +struct Version +{ + //! Required: The backup version. This is an opaque string. + std::string version; +}; + +void +from_json(const nlohmann::json &obj, Version &response); //! Different helper for parsing responses. namespace utils { //! Multiple account_data events. diff --git a/include/mtxclient/http/client.hpp b/include/mtxclient/http/client.hpp index 4a4dba843..9c25ac2c5 100644 --- a/include/mtxclient/http/client.hpp +++ b/include/mtxclient/http/client.hpp @@ -58,6 +58,7 @@ struct CreateRoom; struct EventId; struct RoomId; struct FilterId; +struct Version; struct GroupId; struct GroupProfile; struct JoinedGroups; @@ -623,6 +624,9 @@ public: void update_backup_version(const std::string &version, const mtx::responses::backup::BackupVersion &data, ErrCallback cb); + void post_backup_version(const std::string &algorithm, + const std::string &auth_data, + Callback<mtx::responses::Version> cb); void room_keys(const std::string &version, Callback<mtx::responses::backup::KeysBackup> cb); void room_keys(const std::string &version, diff --git a/lib/crypto/client.cpp b/lib/crypto/client.cpp index fe55f43ec..0511179e7 100644 --- a/lib/crypto/client.cpp +++ b/lib/crypto/client.cpp @@ -259,6 +259,27 @@ OlmClient::create_crosssigning_keys() return setup; } +std::optional<OlmClient::OnlineKeyBackupSetup> +OlmClient::create_online_key_backup(const std::string &masterKey) +{ + OnlineKeyBackupSetup setup{}; + + auto key = create_buffer(olm_pk_private_key_length()); + setup.privateKey = key; + + json auth_data; + auth_data["public_key"] = bin2base64_unpadded(CURVE25519_public_key_from_private(key)); + auto master = PkSigning::from_seed(masterKey); + + auto sig = master.sign(auth_data.dump()); + auth_data["signatures"][user_id_]["ed25519:" + master.public_key()] = sig; + + setup.backupVersion.auth_data = auth_data.dump(); + setup.backupVersion.algorithm = "m.megolm_backup.v1.curve25519-aes-sha2"; + + return setup; +} + std::optional<OlmClient::SSSSSetup> OlmClient::create_ssss_key(const std::string &password) { diff --git a/lib/http/client.cpp b/lib/http/client.cpp index 13731ea37..d03ffb0f0 100644 --- a/lib/http/client.cpp +++ b/lib/http/client.cpp @@ -1193,6 +1193,16 @@ Client::update_backup_version(const std::string &version, put<mtx::responses::backup::BackupVersion>( "/client/r0/room_keys/version/" + mtx::client::utils::url_encode(version), data, cb); } + +void +Client::post_backup_version(const std::string &algorithm, + const std::string &auth_data, + Callback<mtx::responses::Version> cb) +{ + nlohmann::json req = {{"algorithm", algorithm}, + {"auth_data", nlohmann::json::parse(auth_data)}}; + post<nlohmann::json, mtx::responses::Version>("/client/r0/room_keys/version", req, cb); +} void Client::room_keys(const std::string &version, Callback<mtx::responses::backup::KeysBackup> cb) { diff --git a/lib/structs/responses/common.cpp b/lib/structs/responses/common.cpp index 49a4e107f..366815f7d 100644 --- a/lib/structs/responses/common.cpp +++ b/lib/structs/responses/common.cpp @@ -53,6 +53,12 @@ from_json(const nlohmann::json &obj, FilterId &response) response.filter_id = obj.at("filter_id"); } +void +from_json(const nlohmann::json &obj, Version &response) +{ + response.version = obj.at("version"); +} + namespace utils { void diff --git a/tests/crypto.cpp b/tests/crypto.cpp index 6420bb060..b47923b3c 100644 --- a/tests/crypto.cpp +++ b/tests/crypto.cpp @@ -395,3 +395,28 @@ TEST(SecretStorage, CreateSecretKey) EXPECT_EQ(mtx::crypto::key_from_passphrase("some passphrase", ssss2->keyDescription), ssss2->privateKey); } + +TEST(SecretStorage, CreateOnlineKeyBackup) +{ + mtx::crypto::OlmClient account; + account.create_new_account(); + + auto cross = account.create_crosssigning_keys(); + ASSERT_TRUE(cross.has_value()); + + auto okb = account.create_online_key_backup(cross->private_master_key); + ASSERT_TRUE(okb.has_value()); + + mtx::responses::backup::SessionData s; + s.algorithm = mtx::crypto::MEGOLM_ALGO; + s.sender_key = "abc"; + s.session_key = "cde"; + + auto enc1 = + mtx::crypto::encrypt_session(s, json::parse(okb->backupVersion.auth_data)["public_key"]); + EXPECT_FALSE(enc1.ciphertext.empty()); + + auto enc2 = mtx::crypto::encrypt_session( + s, mtx::crypto::CURVE25519_public_key_from_private(okb->privateKey)); + EXPECT_FALSE(enc2.ciphertext.empty()); +} diff --git a/tests/e2ee.cpp b/tests/e2ee.cpp index c06e2d7a9..ae2201827 100644 --- a/tests/e2ee.cpp +++ b/tests/e2ee.cpp @@ -495,7 +495,6 @@ TEST(Encryption, UploadCrossSigningKeys) EXPECT_EQ(res.one_time_key_counts.size(), 0); }); - atomic<bool> done = false; auto xsign_keys = olm_account->create_crosssigning_keys(); ASSERT_TRUE(xsign_keys.has_value()); mtx::requests::DeviceSigningUpload u; @@ -519,9 +518,77 @@ TEST(Encryption, UploadCrossSigningKeys) auth.content = pass; h.next(auth); }), - [&done](RequestErr e) { - done = true; + [](RequestErr e) { check_error(e); }); + + alice->close(); +} + +TEST(Encryption, UploadOnlineBackup) +{ + auto alice = make_test_client(); + auto olm_account = std::make_shared<mtx::crypto::OlmClient>(); + + EXPECT_THROW(olm_account->identity_keys(), olm_exception); + + olm_account->create_new_account(); + + alice->login( + "alice", "secret", [](const mtx::responses::Login &, RequestErr err) { check_error(err); }); + + while (alice->access_token().empty()) + sleep(); + + olm_account->set_user_id(alice->user_id().to_string()); + olm_account->set_device_id(alice->device_id()); + + auto id_keys = olm_account->identity_keys(); + + ASSERT_TRUE(id_keys.curve25519.size() > 10); + ASSERT_TRUE(id_keys.curve25519.size() > 10); + + mtx::crypto::OneTimeKeys unused; + auto request = olm_account->create_upload_keys_request(unused); + + // Make the request with the signed identity keys. + alice->upload_keys(request, [](const mtx::responses::UploadKeys &res, RequestErr err) { + check_error(err); + EXPECT_EQ(res.one_time_key_counts.size(), 0); + }); + + auto xsign_keys = olm_account->create_crosssigning_keys(); + ASSERT_TRUE(xsign_keys.has_value()); + mtx::requests::DeviceSigningUpload u; + u.master_key = xsign_keys->master_key; + u.user_signing_key = xsign_keys->user_signing_key; + u.self_signing_key = xsign_keys->self_signing_key; + alice->device_signing_upload( + u, + mtx::http::UIAHandler([](const mtx::http::UIAHandler &h, + const mtx::user_interactive::Unauthorized &unauthorized) { + ASSERT_EQ(unauthorized.flows.size(), 1); + ASSERT_EQ(unauthorized.flows[0].stages.size(), 1); + ASSERT_EQ(unauthorized.flows[0].stages[0], mtx::user_interactive::auth_types::password); + + mtx::user_interactive::Auth auth; + auth.session = unauthorized.session; + mtx::user_interactive::auth::Password pass{}; + pass.password = "secret"; + pass.identifier_user = "alice"; + pass.identifier_type = mtx::user_interactive::auth::Password::IdType::UserId; + auth.content = pass; + h.next(auth); + }), + [xsign_keys, olm_account, alice](RequestErr e) { check_error(e); + + auto bk = olm_account->create_online_key_backup(xsign_keys->private_master_key); + alice->post_backup_version(bk->backupVersion.algorithm, + bk->backupVersion.auth_data, + [](const mtx::responses::Version &v, RequestErr e) { + check_error(e); + + EXPECT_FALSE(v.version.empty()); + }); }); alice->close(); -- GitLab