mirror of
https://github.com/matrix-construct/construct
synced 2024-11-29 10:12:39 +01:00
ircd:Ⓜ️ Move keys related into modules/key; keys into node's room; remaining keys.cc into m.cc.
This commit is contained in:
parent
0fe0d548c0
commit
c864a6b446
6 changed files with 686 additions and 544 deletions
|
@ -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<void (const string_view &)>; // remember to unquote()!!!
|
||||
using keys_closure = std::function<void (const keys &)>;
|
||||
|
||||
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<void (const keys &)>;
|
||||
using key_closure = std::function<void (const string_view &)>; // 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
|
||||
|
|
|
@ -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 \
|
||||
|
|
524
ircd/m/keys.cc
524
ircd/m/keys.cc
|
@ -1,524 +0,0 @@
|
|||
// Matrix Construct
|
||||
//
|
||||
// Copyright (C) Matrix Construct Developers, Authors & Contributors
|
||||
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
|
||||
//
|
||||
// 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 <ircd/m/m.h>
|
||||
|
||||
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<mutable_buffer> der_buf
|
||||
{
|
||||
8_KiB
|
||||
};
|
||||
|
||||
const auto cert_der
|
||||
{
|
||||
openssl::cert2d(der_buf, cert_pem)
|
||||
};
|
||||
|
||||
const fixed_buffer<const_buffer, crh::sha256::digest_size> 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<const_buffer, sha256::digest_size> 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<milliseconds>() + duration_cast<milliseconds>(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<mutable_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<mutable_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<milliseconds>())
|
||||
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, "<no server name>"_sv),
|
||||
e.what());
|
||||
|
||||
return false;
|
||||
}
|
229
ircd/m/m.cc
229
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<prototype> 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<prototype> 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<mutable_buffer> der_buf
|
||||
{
|
||||
8_KiB
|
||||
};
|
||||
|
||||
const auto cert_der
|
||||
{
|
||||
openssl::cert2d(der_buf, cert_pem)
|
||||
};
|
||||
|
||||
const fixed_buffer<const_buffer, crh::sha256::digest_size> 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<const_buffer, sha256::digest_size> 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
|
||||
|
|
|
@ -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 \
|
||||
###
|
||||
|
|
447
modules/key/keys.cc
Normal file
447
modules/key/keys.cc
Normal file
|
@ -0,0 +1,447 @@
|
|||
// Matrix Construct
|
||||
//
|
||||
// Copyright (C) Matrix Construct Developers, Authors & Contributors
|
||||
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
|
||||
//
|
||||
// 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<mutable_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<mutable_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<milliseconds>())
|
||||
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, "<no server name>"_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<milliseconds>() + duration_cast<milliseconds>(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" },
|
||||
}
|
||||
};
|
Loading…
Reference in a new issue