From c864a6b446e55da20ea60a84926d8556fcc861db Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Mon, 5 Mar 2018 23:42:57 -0800 Subject: [PATCH] ircd::m: Move keys related into modules/key; keys into node's room; remaining keys.cc into m.cc. --- include/ircd/m/keys.h | 27 +-- ircd/Makefile.am | 1 - ircd/m/keys.cc | 524 ------------------------------------------ ircd/m/m.cc | 229 +++++++++++++++++- modules/Makefile.am | 2 + modules/key/keys.cc | 447 +++++++++++++++++++++++++++++++++++ 6 files changed, 686 insertions(+), 544 deletions(-) delete mode 100644 ircd/m/keys.cc create mode 100644 modules/key/keys.cc diff --git a/include/ircd/m/keys.h b/include/ircd/m/keys.h index 56ff148b3..472a4191d 100644 --- a/include/ircd/m/keys.h +++ b/include/ircd/m/keys.h @@ -10,8 +10,6 @@ #pragma once #define HAVE_IRCD_M_KEYS_H -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wsubobject-linkage" namespace ircd::m { @@ -29,6 +27,8 @@ namespace ircd::m::self extern std::string tls_cert_der_sha256_b64; } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsubobject-linkage" /// Contains the public keys and proof of identity for a remote server. /// /// A user who wishes to verify a signature from a remote server must have @@ -62,23 +62,20 @@ struct ircd::m::keys { struct init; - using key_closure = std::function; // remember to unquote()!!! - using keys_closure = std::function; - - static m::room room; - - static bool get_local(const string_view &server_name, const keys_closure &); - static bool verify(const keys &) noexcept; - static void set(const keys &); - public: - static void get(const string_view &server_name, const string_view &key_id, const string_view &query_server, const keys_closure &); + using closure = std::function; + using key_closure = std::function; // remember to unquote()!!! + + static void get(const string_view &server_name, const string_view &key_id, const string_view &query_server, const closure &); + + static void get(const string_view &server_name, const closure &); + static void get(const string_view &server_name, const string_view &key_id, const closure &); static void get(const string_view &server_name, const string_view &key_id, const key_closure &); - static void get(const string_view &server_name, const keys_closure &); using super_type::tuple; using super_type::operator=; }; +#pragma GCC diagnostic pop struct ircd::m::keys::init { @@ -88,10 +85,6 @@ struct ircd::m::keys::init void signing(); public: - void bootstrap(); - init(const json::object &config); ~init() noexcept; }; - -#pragma GCC diagnostic pop diff --git a/ircd/Makefile.am b/ircd/Makefile.am index e8b5c19db..3c01fe4f1 100644 --- a/ircd/Makefile.am +++ b/ircd/Makefile.am @@ -107,7 +107,6 @@ libircd_la_SOURCES = \ m/filter.cc \ m/request.cc \ m/v1.cc \ - m/keys.cc \ m/m.cc \ m/vm.cc \ ircd.cc \ diff --git a/ircd/m/keys.cc b/ircd/m/keys.cc deleted file mode 100644 index 7e5787304..000000000 --- a/ircd/m/keys.cc +++ /dev/null @@ -1,524 +0,0 @@ -// Matrix Construct -// -// Copyright (C) Matrix Construct Developers, Authors & Contributors -// Copyright (C) 2016-2018 Jason Volk -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice is present in all copies. The -// full license for this software is available in the LICENSE file. - -#include - -const ircd::m::room::id::buf -keys_room_id -{ - "keys", ircd::my_host() -}; - -/// The keys room is where the public key data for each server is stored as -/// state indexed by the server name. -ircd::m::room -ircd::m::keys::room -{ - keys_room_id -}; - -ircd::ed25519::sk -ircd::m::self::secret_key -{}; - -ircd::ed25519::pk -ircd::m::self::public_key -{}; - -std::string -ircd::m::self::public_key_b64 -{}; - -std::string -ircd::m::self::public_key_id -{}; - -std::string -ircd::m::self::tls_cert_der -{}; - -std::string -ircd::m::self::tls_cert_der_sha256_b64 -{}; - -// -// init -// - -ircd::m::keys::init::init(const json::object &config) -:config{config} -{ - certificate(); - signing(); -} - -ircd::m::keys::init::~init() -noexcept -{ -} - -void -ircd::m::keys::init::certificate() -{ - const std::string cert_file - { - unquote(conf.at("tls_certificate_path")) - }; - - if(!fs::exists(cert_file)) - throw fs::error("Failed to find SSL certificate @ `%s'", cert_file); - - const auto cert_pem - { - fs::read(cert_file) - }; - - const unique_buffer der_buf - { - 8_KiB - }; - - const auto cert_der - { - openssl::cert2d(der_buf, cert_pem) - }; - - const fixed_buffer hash - { - sha256{cert_der} - }; - - self::tls_cert_der_sha256_b64 = - { - b64encode_unpadded(hash) - }; - - static char print_buf[8_KiB]; - log.info("Certificate `%s' (PEM: %zu bytes; DER: %zu bytes) sha256b64: %s :%s", - cert_file, - cert_pem.size(), - ircd::size(cert_der), - self::tls_cert_der_sha256_b64, - openssl::print_subject(print_buf, cert_pem)); -} - -void -ircd::m::keys::init::signing() -{ - const std::string sk_file - { - unquote(config.get("signing_key_path", "construct.sk")) - }; - - if(fs::exists(sk_file)) - log.info("Using ed25519 secret key @ `%s'", sk_file); - else - log.notice("Creating new ed25519 secret key @ `%s'", sk_file); - - self::secret_key = ed25519::sk - { - sk_file, &self::public_key - }; - - self::public_key_b64 = b64encode_unpadded(self::public_key); - const fixed_buffer hash - { - sha256{self::public_key} - }; - - const auto public_key_hash_b58 - { - b58encode(hash) - }; - - static const auto trunc_size{8}; - self::public_key_id = fmt::snstringf - { - BUFSIZE, "ed25519:%s", trunc(public_key_hash_b58, trunc_size) - }; - - log.info("Current key is '%s' and the public key is: %s", - self::public_key_id, - self::public_key_b64); -} - -void -ircd::m::keys::init::bootstrap() -{ - create(keys::room, me.user_id); - send(keys::room, me.user_id, "m.room.name", "", - { - { "name", "Key Room" } - }); - - const json::strung verify_keys - { - json::members - {{ - string_view{self::public_key_id}, - { - { "key", self::public_key_b64 } - } - }} - }; - - keys my_key; - json::get<"verify_keys"_>(my_key) = verify_keys; - json::get<"server_name"_>(my_key) = my_host(); - json::get<"old_verify_keys"_>(my_key) = "{}"; - json::get<"valid_until_ts"_>(my_key) = ircd::time() + duration_cast(hours(2160)).count(); - - const json::members tlsfps - { - { "sha256", self::tls_cert_der_sha256_b64 } - }; - - const json::value tlsfp[1] - { - { tlsfps } - }; - - const json::strung tls_fingerprints{json::value - { - tlsfp, 1 - }}; - - json::get<"tls_fingerprints"_>(my_key) = tls_fingerprints; - - const auto presig - { - json::strung(my_key) - }; - - const ed25519::sig sig - { - self::secret_key.sign(const_buffer{presig}) - }; - - static char signature[256]; - const json::strung signatures{json::members - { - { my_host(), json::members - { - { string_view{self::public_key_id}, b64encode_unpadded(signature, sig) } - }} - }}; - - json::get<"signatures"_>(my_key) = signatures; - keys::set(my_key); -} - -// -// keys -// - -void -ircd::m::keys::get(const string_view &server_name, - const string_view &key_id, - const key_closure &closure) -try -{ - get(server_name, [&key_id, &closure](const keys &keys) - { - const json::object vks - { - at<"verify_keys"_>(keys) - }; - - const json::object vkk - { - vks.at(key_id) - }; - - // The key is not unquote() because some types of keys may be - // more complex than just a string one day; think: RLWE. - const string_view &key - { - vkk.at("key") - }; - - closure(key); - }); -} -catch(const json::not_found &e) -{ - throw m::NOT_FOUND - { - "Failed to find key '%s' for '%s': %s", - key_id, - server_name, - e.what() - }; -} - -void -ircd::m::keys::get(const string_view &server_name, - const keys_closure &closure) -{ - assert(!server_name.empty()); - - if(get_local(server_name, closure)) - return; - - if(server_name == my_host()) - throw m::NOT_FOUND - { - "keys for '%s' (that's myself) not found", server_name - }; - - log.debug("Keys for %s not cached; querying network...", - server_name); - - char url[1024]; const auto url_len - { - fmt::snprintf(url, sizeof(url), "_matrix/key/v2/server/") - }; - - //TODO: XXX - const unique_buffer buffer - { - 8192 - }; - - ircd::parse::buffer pb{mutable_buffer{buffer}}; - m::request request{"GET", url, {}, {}}; - m::session session{server_name}; - const json::object response - { - session(pb, request) - }; - - const m::keys &keys - { - response - }; - - if(!verify(keys)) - throw m::error - { - http::UNAUTHORIZED, "M_INVALID_SIGNATURE", - "Failed to verify keys for '%s'", server_name - }; - - log.debug("Verified keys from '%s'", - server_name); - - set(keys); - closure(keys); -} - -void -ircd::m::keys::get(const string_view &server_name, - const string_view &key_id, - const string_view &query_server, - const keys_closure &closure) -try -{ - assert(!server_name.empty()); - assert(!query_server.empty()); - - char key_id_buf[1024]; - char server_name_buf[1024]; - char url[1024]; const auto url_len - { - fmt::snprintf(url, sizeof(url), "_matrix/key/v2/query/%s/%s/", - url::encode(server_name, server_name_buf), - url::encode(key_id, key_id_buf)) - }; - - //TODO: XXX - const unique_buffer buffer - { - 8192 - }; - - // Make request and receive response synchronously. - // This ircd::ctx will block here fetching. - ircd::parse::buffer pb{mutable_buffer{buffer}}; - m::request request{"GET", url, {}, {}}; - m::session session{server_name}; - const json::object response - { - session(pb, request) - }; - - const json::array &keys - { - response.at("server_keys") - }; - - log::debug("Fetched %zu candidate keys seeking '%s' for '%s' from '%s' (%s)", - keys.count(), - empty(key_id)? "*" : key_id, - server_name, - query_server, - string(net::remote(session.server))); - - bool ret{false}; - for(auto it(begin(keys)); it != end(keys); ++it) - { - const m::keys &keys{*it}; - const auto &_server_name - { - at<"server_name"_>(keys) - }; - - if(!verify(keys)) - throw m::error - { - http::UNAUTHORIZED, "M_INVALID_SIGNATURE", - "Failed to verify keys for '%s' from '%s'", - _server_name, - query_server - }; - - log.debug("Verified keys for '%s' from '%s'", - _server_name, - query_server); - - set(keys); - const json::object vks{json::get<"verify_keys"_>(keys)}; - if(_server_name == server_name) - { - closure(keys); - ret = true; - } - } - - if(!ret) - throw m::NOT_FOUND - { - "Failed to get any keys for '%s' from '%s' (got %zu total keys otherwise)", - server_name, - query_server, - keys.count() - }; -} -catch(const json::not_found &e) -{ - throw m::NOT_FOUND - { - "Failed to find key '%s' for '%s' when querying '%s': %s", - key_id, - server_name, - query_server, - e.what() - }; -} - -bool -ircd::m::keys::get_local(const string_view &server_name, - const keys_closure &closure) -{ - static const string_view type{"ircd.key"}; - return m::keys::room.get(std::nothrow, type, server_name, [&closure] - (const m::event &event) - { - closure(json::get<"content"_>(event)); - }); -} - -void -ircd::m::keys::set(const keys &keys) -{ - const auto &server_name - { - unquote(at<"server_name"_>(keys)) - }; - - const auto &state_key - { - server_name - }; - - const m::user::id::buf sender - { - "ircd", server_name - }; - - const json::strung derp - { - keys - }; - - static const string_view type{"ircd.key"}; - send(keys::room, sender, type, state_key, json::object{derp}); -} - -/// Verify this key data (with itself). -bool -ircd::m::keys::verify(const keys &keys) -noexcept try -{ - const auto &valid_until_ts - { - at<"valid_until_ts"_>(keys) - }; - - if(valid_until_ts < ircd::time()) - throw ircd::error("Key was valid until %s", timestr(valid_until_ts)); - - const json::object &verify_keys - { - at<"verify_keys"_>(keys) - }; - - const string_view &key_id - { - begin(verify_keys)->first - }; - - const json::object &key - { - begin(verify_keys)->second - }; - - const ed25519::pk pk - { - [&key](auto &pk) - { - b64decode(pk, unquote(key.at("key"))); - } - }; - - const json::object &signatures - { - at<"signatures"_>(keys) - }; - - const string_view &server_name - { - unquote(at<"server_name"_>(keys)) - }; - - const json::object &server_signatures - { - signatures.at(server_name) - }; - - const ed25519::sig sig{[&server_signatures, &key_id](auto &sig) - { - b64decode(sig, unquote(server_signatures.at(key_id))); - }}; - - ///TODO: XXX - m::keys copy{keys}; - at<"signatures"_>(copy) = string_view{}; - const json::strung preimage{copy}; - return pk.verify(const_buffer{preimage}, sig); -} -catch(const std::exception &e) -{ - log.error("key verification for '%s' failed: %s", - json::get<"server_name"_>(keys, ""_sv), - e.what()); - - return false; -} diff --git a/ircd/m/m.cc b/ircd/m/m.cc index a4a122b41..70e54e46c 100644 --- a/ircd/m/m.cc +++ b/ircd/m/m.cc @@ -250,8 +250,6 @@ ircd::m::init::bootstrap() { { "name", "User Tokens" } }); - - _keys.bootstrap(); } bool @@ -299,6 +297,233 @@ ircd::m::leave_ircd_room() presence::set(me, "offline", me_offline_status_msg); } +/////////////////////////////////////////////////////////////////////////////// +// +// m/keys.h +// + +ircd::ed25519::sk +ircd::m::self::secret_key +{}; + +ircd::ed25519::pk +ircd::m::self::public_key +{}; + +std::string +ircd::m::self::public_key_b64 +{}; + +std::string +ircd::m::self::public_key_id +{}; + +std::string +ircd::m::self::tls_cert_der +{}; + +std::string +ircd::m::self::tls_cert_der_sha256_b64 +{}; + +void +ircd::m::keys::get(const string_view &server_name, + const string_view &key_id, + const key_closure &closure) +try +{ + get(server_name, [&key_id, &closure](const keys &keys) + { + const json::object vks + { + at<"verify_keys"_>(keys) + }; + + const json::object vkk + { + vks.at(key_id) + }; + + const string_view &key + { + vkk.at("key") + }; + + closure(key); + }); +} +catch(const json::not_found &e) +{ + throw m::NOT_FOUND + { + "Failed to find key '%s' for '%s': %s", + key_id, + server_name, + e.what() + }; +} + +void +ircd::m::keys::get(const string_view &server_name, + const string_view &key_id, + const closure &closure) +{ + get(server_name, [&key_id, &closure](const keys &keys) + { + closure(keys); + }); +} + +void +ircd::m::keys::get(const string_view &server_name, + const closure &closure_) +{ + using prototype = void (const string_view &, const closure &); + + static import function + { + "key_keys", "get__keys" + }; + + return function(server_name, closure_); +} + +void +ircd::m::keys::get(const string_view &server_name, + const string_view &key_id, + const string_view &query_server, + const closure &closure_) +{ + using prototype = void (const string_view &, const string_view &, const string_view &, const closure &); + + static import function + { + "key_keys", "query__keys" + }; + + return function(server_name, key_id, query_server, closure_); +} + +// +// init +// + +ircd::m::keys::init::init(const json::object &config) +:config{config} +{ + certificate(); + signing(); +} + +ircd::m::keys::init::~init() +noexcept +{ +} + +void +ircd::m::keys::init::certificate() +{ + const std::string private_key_file + { + unquote(config.at("tls_private_key_path")) + }; + + const std::string public_key_file + { + unquote(config.get("tls_public_key_path", private_key_file + ".pub")) + }; + + if(!fs::exists(private_key_file)) + { + log::warning("Failed to find certificate private key @ `%s'; creating...", private_key_file); + openssl::genrsa(private_key_file, public_key_file); + } + + const std::string cert_file + { + unquote(config.at("tls_certificate_path")) + }; + + if(!fs::exists(cert_file)) + throw fs::error("Failed to find SSL certificate @ `%s'", cert_file); + + const auto cert_pem + { + fs::read(cert_file) + }; + + const unique_buffer der_buf + { + 8_KiB + }; + + const auto cert_der + { + openssl::cert2d(der_buf, cert_pem) + }; + + const fixed_buffer hash + { + sha256{cert_der} + }; + + self::tls_cert_der_sha256_b64 = + { + b64encode_unpadded(hash) + }; + + log.info("Certificate `%s' :PEM %zu bytes; DER %zu bytes; sha256b64 %s", + cert_file, + cert_pem.size(), + ircd::size(cert_der), + self::tls_cert_der_sha256_b64); + + thread_local char print_buf[8_KiB]; + log.info("Certificate `%s' :%s", + cert_file, + openssl::print_subject(print_buf, cert_pem)); +} + +void +ircd::m::keys::init::signing() +{ + const std::string sk_file + { + unquote(config.get("signing_key_path", "construct.sk")) + }; + + if(fs::exists(sk_file)) + log.info("Using ed25519 secret key @ `%s'", sk_file); + else + log.notice("Creating new ed25519 secret key @ `%s'", sk_file); + + self::secret_key = ed25519::sk + { + sk_file, &self::public_key + }; + + self::public_key_b64 = b64encode_unpadded(self::public_key); + const fixed_buffer hash + { + sha256{self::public_key} + }; + + const auto public_key_hash_b58 + { + b58encode(hash) + }; + + static const auto trunc_size{8}; + self::public_key_id = fmt::snstringf + { + BUFSIZE, "ed25519:%s", trunc(public_key_hash_b58, trunc_size) + }; + + log.info("Current key is '%s' and the public key is: %s", + self::public_key_id, + self::public_key_b64); +} + /////////////////////////////////////////////////////////////////////////////// // // m/presence.h diff --git a/modules/Makefile.am b/modules/Makefile.am index 8f1317876..ce32f980b 100644 --- a/modules/Makefile.am +++ b/modules/Makefile.am @@ -207,10 +207,12 @@ client_module_LTLIBRARIES += \ key_moduledir = @moduledir@ +key_key_keys_la_SOURCES = key/keys.cc key_key_server_la_SOURCES = key/server.cc key_key_query_la_SOURCES = key/query.cc key_module_LTLIBRARIES = \ + key/key_keys.la \ key/key_server.la \ key/key_query.la \ ### diff --git a/modules/key/keys.cc b/modules/key/keys.cc new file mode 100644 index 000000000..5906145e2 --- /dev/null +++ b/modules/key/keys.cc @@ -0,0 +1,447 @@ +// Matrix Construct +// +// Copyright (C) Matrix Construct Developers, Authors & Contributors +// Copyright (C) 2016-2018 Jason Volk +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice is present in all copies. The +// full license for this software is available in the LICENSE file. + +using namespace ircd::m; +using namespace ircd; + +static bool verify(const m::keys &) noexcept; +static bool get_local(const string_view &server_name, const m::keys::closure &); +static event::id::buf set_keys(const m::keys &); + +mapi::header +IRCD_MODULE +{ + "2.3 Retrieving Server Keys" +}; + +extern "C" void +get__keys(const string_view &server_name, + const m::keys::closure &closure) +{ + assert(!server_name.empty()); + + if(get_local(server_name, closure)) + return; + + if(server_name == my_host()) + throw m::NOT_FOUND + { + "keys for '%s' (that's myself) not found", server_name + }; + + m::log.debug("Keys for %s not cached; querying network...", + server_name); + + //TODO: XXX + const unique_buffer buffer + { + 32_KiB + }; + + const mutable_buffer out_head + { + data(buffer), 8_KiB + }; + + using buffer::size; + const mutable_buffer in + { + data(out_head) + size(out_head), size(buffer) - size(out_head) + }; + + m::request request + { + my_host(), server_name, "GET", "/_matrix/key/v2/server/", {} + }; + + const auto request_head + { + request(out_head) + }; + + const net::hostport host + { + server_name + }; + + server::request tag + { + host, { request_head }, { in } + }; + + //TODO: conf + if(tag.wait(seconds(20)) == ctx::future_status::timeout) + { + cancel(tag); + throw m::error + { + http::REQUEST_TIMEOUT, "M_TIMEOUT", + "Failed to fetch keys for '%s' in time", + server_name + }; + } + + const http::code status + { + tag.get() + }; + + const json::object response + { + tag.in.content + }; + + const m::keys &keys + { + response + }; + + if(!verify(keys)) + throw m::error + { + http::UNAUTHORIZED, "M_INVALID_SIGNATURE", + "Failed to verify keys for '%s'", + server_name + }; + + m::log.debug("Verified keys from '%s'", + server_name); + + set_keys(keys); + closure(keys); +} + +extern "C" void +query__keys(const string_view &server_name, + const string_view &key_id, + const string_view &query_server, + const m::keys::closure &closure) +try +{ + assert(!server_name.empty()); + assert(!query_server.empty()); + + thread_local char url_buf[2_KiB]; + thread_local char key_id_buf[1_KiB]; + thread_local char server_name_buf[1_KiB]; + const string_view url{fmt::sprintf + { + url_buf, "/_matrix/key/v2/query/%s/%s/", + url::encode(server_name, server_name_buf), + url::encode(key_id, key_id_buf) + }}; + + // This buffer will hold the HTTP request and response + //TODO: XXX + const unique_buffer buffer + { + 32_KiB + }; + + m::request request + { + my_host(), server_name, "GET", url, {} + }; + + // Generates the HTTP request head into the front of buffer + const string_view head + { + request(buffer) + }; + + // Partition the remainder of buffer for the response data + using buffer::size; + assert(size(head) < size(buffer) / 2); + const mutable_buffer in + { + data(buffer) + size(head), size(buffer) - size(head) + }; + + // The request to the remote is transmitted + server::request tag + { + server_name, { head }, { in } + }; + + if(tag.wait(seconds(30)) == ctx::future_status::timeout) + throw m::error + { + http::REQUEST_TIMEOUT, "M_TIMEOUT", + "Failed to fetch keys for '%s' from '%s' in time", + server_name, + query_server + }; + + // The response from the remote is received + const auto code + { + tag.get() + }; + + // The content received is here + const json::object response + { + tag.in.content + }; + + // This parses the content for our key. + const json::array &keys + { + response.at("server_keys") + }; + + m::log.debug("Fetched %zu candidate keys seeking '%s' for '%s' from '%s'", + keys.count(), + empty(key_id)? "*" : key_id, + server_name, + query_server); + + bool ret{false}; + for(auto it(begin(keys)); it != end(keys); ++it) + { + const m::keys &keys{*it}; + const auto &_server_name + { + at<"server_name"_>(keys) + }; + + if(!verify(keys)) + throw m::error + { + http::UNAUTHORIZED, "M_INVALID_SIGNATURE", + "Failed to verify keys for '%s' from '%s'", + _server_name, + query_server + }; + + m::log.debug("Verified keys for '%s' from '%s'", + _server_name, + query_server); + + set_keys(keys); + const json::object vks{json::get<"verify_keys"_>(keys)}; + if(_server_name == server_name) + { + closure(keys); + ret = true; + } + } + + if(!ret) + throw m::NOT_FOUND + { + "Failed to get any keys for '%s' from '%s' (got %zu total keys otherwise)", + server_name, + query_server, + keys.count() + }; +} +catch(const json::not_found &e) +{ + throw m::NOT_FOUND + { + "Failed to find key '%s' for '%s' when querying '%s': %s", + key_id, + server_name, + query_server, + e.what() + }; +} + +event::id::buf +set_keys(const m::keys &keys) +{ + const auto &server_name + { + unquote(at<"server_name"_>(keys)) + }; + + const m::node::id::buf node_id + { + "", server_name + }; + + const m::node::room node_room + { + node_id + }; + + if(!exists(node_room.room_id)) + create(node_room, m::me.user_id); + + const json::strung derp + { + keys + }; + + return send(node_room, m::me.user_id, "ircd.keys", "", json::object{derp}); +} + +bool +get_local(const string_view &server_name, + const m::keys::closure &closure) +{ + const node::id::buf node_id + { + "", server_name + }; + + const m::node::room node_room + { + node_id + }; + + return node_room.get(std::nothrow, "ircd.keys", "", [&closure] + (const m::event &event) + { + closure(json::get<"content"_>(event)); + }); +} + +bool +verify(const m::keys &keys) +noexcept try +{ + const auto &valid_until_ts + { + at<"valid_until_ts"_>(keys) + }; + + if(valid_until_ts < ircd::time()) + throw ircd::error("Key was valid until %s", timestr(valid_until_ts)); + + const json::object &verify_keys + { + at<"verify_keys"_>(keys) + }; + + const string_view &key_id + { + begin(verify_keys)->first + }; + + const json::object &key + { + begin(verify_keys)->second + }; + + const ed25519::pk pk + { + [&key](auto &pk) + { + b64decode(pk, unquote(key.at("key"))); + } + }; + + const json::object &signatures + { + at<"signatures"_>(keys) + }; + + const string_view &server_name + { + unquote(at<"server_name"_>(keys)) + }; + + const json::object &server_signatures + { + signatures.at(server_name) + }; + + const ed25519::sig sig{[&server_signatures, &key_id](auto &sig) + { + b64decode(sig, unquote(server_signatures.at(key_id))); + }}; + + ///TODO: XXX + m::keys copy{keys}; + at<"signatures"_>(copy) = string_view{}; + const json::strung preimage{copy}; + return pk.verify(const_buffer{preimage}, sig); +} +catch(const std::exception &e) +{ + m::log.error("key verification for '%s' failed: %s", + json::get<"server_name"_>(keys, ""_sv), + e.what()); + + return false; +} + +static void +create_my_key(const m::event &) +{ + const json::strung verify_keys + { + json::members + {{ + string_view{self::public_key_id}, + { + { "key", self::public_key_b64 } + } + }} + }; + + m::keys my_key; + json::get<"verify_keys"_>(my_key) = verify_keys; + json::get<"server_name"_>(my_key) = my_host(); + json::get<"old_verify_keys"_>(my_key) = "{}"; + json::get<"valid_until_ts"_>(my_key) = ircd::time() + duration_cast(hours(2160)).count(); + + const json::members tlsfps + { + { "sha256", self::tls_cert_der_sha256_b64 } + }; + + const json::value tlsfp[1] + { + { tlsfps } + }; + + const json::strung tls_fingerprints{json::value + { + tlsfp, 1 + }}; + + json::get<"tls_fingerprints"_>(my_key) = tls_fingerprints; + + const auto presig + { + json::strung(my_key) + }; + + const ed25519::sig sig + { + self::secret_key.sign(const_buffer{presig}) + }; + + static char signature[256]; + const json::strung signatures{json::members + { + { my_host(), json::members + { + { string_view{self::public_key_id}, b64encode_unpadded(signature, sig) } + }} + }}; + + json::get<"signatures"_>(my_key) = signatures; + set_keys(my_key); +} + +const m::hook +create_my_key_hook +{ + create_my_key, + { + { "_site", "vm notify" }, + { "room_id", m::my_node.room_id() }, + { "type", "m.room.create" }, + } +};