mirror of
https://github.com/matrix-construct/construct
synced 2024-12-25 15:04:10 +01:00
ircd:Ⓜ️:fetch: Split fetch_check related into unit.
This commit is contained in:
parent
c32928981b
commit
ed5dfd0031
3 changed files with 309 additions and 277 deletions
|
@ -163,6 +163,7 @@ libircd_matrix_la_SOURCES += fed.cc
|
|||
libircd_matrix_la_SOURCES += fed_well_known.cc
|
||||
libircd_matrix_la_SOURCES += feds.cc
|
||||
libircd_matrix_la_SOURCES += fetch.cc
|
||||
libircd_matrix_la_SOURCES += fetch_check.cc
|
||||
libircd_matrix_la_SOURCES += gossip.cc
|
||||
libircd_matrix_la_SOURCES += groups.cc
|
||||
libircd_matrix_la_SOURCES += request.cc
|
||||
|
|
278
matrix/fetch.cc
278
matrix/fetch.cc
|
@ -30,8 +30,7 @@ namespace ircd::m::fetch
|
|||
extern log::log log;
|
||||
|
||||
static bool timedout(const request &, const system_point &now);
|
||||
static void _check_event(const request &, const m::event &);
|
||||
static void check_response(const request &, const json::object &);
|
||||
extern void check_response(const request &, const json::object &);
|
||||
static bool proffer_remote(request &, const string_view &);
|
||||
static bool select_remote(request &, const string_view &);
|
||||
static bool select_random_remote(request &);
|
||||
|
@ -788,281 +787,6 @@ ircd::m::fetch::finish(request &request)
|
|||
request.promise.set_value(std::move(res));
|
||||
}
|
||||
|
||||
namespace ircd::m::fetch
|
||||
{
|
||||
static void check_response__backfill(const request &, const json::object &);
|
||||
static void check_response__event(const request &, const json::object &);
|
||||
static void check_response__auth(const request &, const json::object &);
|
||||
|
||||
extern conf::item<bool> check_event_id;
|
||||
extern conf::item<bool> check_conforms;
|
||||
extern conf::item<bool> check_signature;
|
||||
extern conf::item<bool> check_hashes;
|
||||
extern conf::item<bool> check_authoritative_redaction;
|
||||
}
|
||||
|
||||
decltype(ircd::m::fetch::check_event_id)
|
||||
ircd::m::fetch::check_event_id
|
||||
{
|
||||
{ "name", "ircd.m.fetch.check.event_id" },
|
||||
{ "default", true },
|
||||
};
|
||||
|
||||
decltype(ircd::m::fetch::check_conforms)
|
||||
ircd::m::fetch::check_conforms
|
||||
{
|
||||
{ "name", "ircd.m.fetch.check.conforms" },
|
||||
{ "default", true },
|
||||
};
|
||||
|
||||
decltype(ircd::m::fetch::check_hashes)
|
||||
ircd::m::fetch::check_hashes
|
||||
{
|
||||
{ "name", "ircd.m.fetch.check.hashes" },
|
||||
{ "default", true },
|
||||
};
|
||||
|
||||
decltype(ircd::m::fetch::check_authoritative_redaction)
|
||||
ircd::m::fetch::check_authoritative_redaction
|
||||
{
|
||||
{ "name", "ircd.m.fetch.check.authoritative_redaction" },
|
||||
{ "default", true },
|
||||
};
|
||||
|
||||
decltype(ircd::m::fetch::check_signature)
|
||||
ircd::m::fetch::check_signature
|
||||
{
|
||||
{ "name", "ircd.m.fetch.check.signature" },
|
||||
{ "default", true },
|
||||
{ "description",
|
||||
|
||||
R"(
|
||||
false - Signatures of events will not be checked by the fetch unit (they
|
||||
are still checked normally during evaluation; this conf item does not
|
||||
disable event signature verification for the server).
|
||||
|
||||
true - Signatures of events will be checked by the fetch unit such that
|
||||
bogus responses allow the fetcher to try the next server. This check might
|
||||
not occur in all cases. It will only occur if the server has the public
|
||||
key already; fetch unit worker contexts cannot be blocked trying to obtain
|
||||
unknown keys from remote hosts.
|
||||
)"},
|
||||
};
|
||||
|
||||
void
|
||||
ircd::m::fetch::check_response(const request &request,
|
||||
const json::object &response)
|
||||
{
|
||||
switch(request.opts.op)
|
||||
{
|
||||
case op::backfill:
|
||||
return check_response__backfill(request, response);
|
||||
|
||||
case op::event:
|
||||
return check_response__event(request, response);
|
||||
|
||||
case op::auth:
|
||||
return check_response__auth(request, response);
|
||||
|
||||
case op::noop:
|
||||
return;
|
||||
}
|
||||
|
||||
ircd::panic
|
||||
{
|
||||
"Unchecked response; fetch op:%u",
|
||||
uint(request.opts.op),
|
||||
};
|
||||
}
|
||||
|
||||
void
|
||||
ircd::m::fetch::check_response__auth(const request &request,
|
||||
const json::object &response)
|
||||
{
|
||||
const json::array &auth_chain
|
||||
{
|
||||
response.at("auth_chain")
|
||||
};
|
||||
|
||||
for(const json::object auth_event : auth_chain)
|
||||
{
|
||||
m::event::id::buf event_id;
|
||||
const m::event event
|
||||
{
|
||||
event_id, auth_event
|
||||
};
|
||||
|
||||
_check_event(request, event);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ircd::m::fetch::check_response__event(const request &request,
|
||||
const json::object &response)
|
||||
{
|
||||
const json::array &pdus
|
||||
{
|
||||
response.at("pdus")
|
||||
};
|
||||
|
||||
const m::event event
|
||||
{
|
||||
pdus.at(0), request.opts.event_id
|
||||
};
|
||||
|
||||
_check_event(request, event);
|
||||
}
|
||||
|
||||
void
|
||||
ircd::m::fetch::check_response__backfill(const request &request,
|
||||
const json::object &response)
|
||||
{
|
||||
const json::array &pdus
|
||||
{
|
||||
response.at("pdus")
|
||||
};
|
||||
|
||||
for(const json::object event : pdus)
|
||||
{
|
||||
m::event::id::buf event_id;
|
||||
const m::event _event
|
||||
{
|
||||
event_id, event
|
||||
};
|
||||
|
||||
_check_event(request, _event);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ircd::m::fetch::_check_event(const request &request,
|
||||
const m::event &event)
|
||||
{
|
||||
if(unlikely(!request.promise))
|
||||
throw ctx::broken_promise
|
||||
{
|
||||
"Fetch response check interrupted."
|
||||
};
|
||||
|
||||
if(request.opts.check_event_id && check_event_id && !m::check_id(event))
|
||||
{
|
||||
event::id::buf buf;
|
||||
const m::event &claim
|
||||
{
|
||||
buf, event.source
|
||||
};
|
||||
|
||||
throw ircd::error
|
||||
{
|
||||
"event::id claim:%s != sought:%s",
|
||||
string_view{claim.event_id},
|
||||
string_view{request.opts.event_id},
|
||||
};
|
||||
}
|
||||
|
||||
if(request.opts.check_conforms && check_conforms)
|
||||
{
|
||||
m::event::conforms conforms
|
||||
{
|
||||
event
|
||||
};
|
||||
|
||||
const bool mismatch_hashes
|
||||
{
|
||||
check_hashes
|
||||
&& request.opts.check_hashes
|
||||
&& conforms.has(m::event::conforms::MISMATCH_HASHES)
|
||||
};
|
||||
|
||||
const bool authoritative_redaction
|
||||
{
|
||||
check_authoritative_redaction
|
||||
&& request.opts.authoritative_redaction
|
||||
&& mismatch_hashes
|
||||
&& json::get<"origin"_>(event) == request.origin
|
||||
};
|
||||
|
||||
if(mismatch_hashes && !authoritative_redaction)
|
||||
{
|
||||
const json::object _unsigned
|
||||
{
|
||||
event.source["unsigned"]
|
||||
};
|
||||
|
||||
const json::string redacted_by
|
||||
{
|
||||
_unsigned["redacted_by"]
|
||||
};
|
||||
|
||||
if(valid(id::EVENT, redacted_by))
|
||||
log::dwarning
|
||||
{
|
||||
log, "%s claims %s redacted by %s",
|
||||
request.origin,
|
||||
string_view{request.opts.event_id},
|
||||
redacted_by,
|
||||
};
|
||||
|
||||
//TODO: XXX
|
||||
}
|
||||
|
||||
if(authoritative_redaction || !mismatch_hashes)
|
||||
conforms.del(m::event::conforms::MISMATCH_HASHES);
|
||||
|
||||
thread_local char buf[128];
|
||||
const string_view failures
|
||||
{
|
||||
conforms.string(buf)
|
||||
};
|
||||
|
||||
assert(failures || conforms.clean());
|
||||
if(!conforms.clean())
|
||||
throw ircd::error
|
||||
{
|
||||
"Non-conforming event in response :%s",
|
||||
failures,
|
||||
};
|
||||
}
|
||||
|
||||
// only check signature for v1 events
|
||||
assert(request.promise);
|
||||
if(request.opts.check_signature && check_signature && request.opts.event_id.version() == "1")
|
||||
{
|
||||
const string_view &server
|
||||
{
|
||||
!json::get<"origin"_>(event)?
|
||||
user::id(at<"sender"_>(event)).host():
|
||||
string_view(json::get<"origin"_>(event))
|
||||
};
|
||||
|
||||
const json::object &signatures
|
||||
{
|
||||
at<"signatures"_>(event).at(server)
|
||||
};
|
||||
|
||||
const json::string &key_id
|
||||
{
|
||||
!signatures.empty()?
|
||||
signatures.begin()->first:
|
||||
string_view{},
|
||||
};
|
||||
|
||||
if(!key_id)
|
||||
throw ircd::error
|
||||
{
|
||||
"Cannot find any keys for '%s' in event.signatures",
|
||||
server,
|
||||
};
|
||||
|
||||
if(m::keys::cache::has(server, key_id))
|
||||
if(!verify(event, server))
|
||||
throw ircd::error
|
||||
{
|
||||
"Signature verification failed."
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ircd::m::fetch::timedout(const request &request,
|
||||
const system_point &now)
|
||||
|
|
307
matrix/fetch_check.cc
Normal file
307
matrix/fetch_check.cc
Normal file
|
@ -0,0 +1,307 @@
|
|||
// Matrix Construct
|
||||
//
|
||||
// Copyright (C) Matrix Construct Developers, Authors & Contributors
|
||||
// Copyright (C) 2016-2022 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.
|
||||
|
||||
namespace ircd::m::fetch
|
||||
{
|
||||
static void check_event_signature(const request &, const m::event &);
|
||||
static void check_event_conforms(const request &, const m::event &);
|
||||
static void check_event_id(const request &, const m::event &);
|
||||
static void check_event(const request &, const m::event &);
|
||||
static void check_response__backfill(const request &, const json::object &);
|
||||
static void check_response__event(const request &, const json::object &);
|
||||
static void check_response__auth(const request &, const json::object &);
|
||||
extern void check_response(const request &, const json::object &);
|
||||
|
||||
extern conf::item<bool> enable_check_event_id;
|
||||
extern conf::item<bool> enable_check_conforms;
|
||||
extern conf::item<bool> enable_check_signature;
|
||||
extern conf::item<bool> enable_check_hashes;
|
||||
extern conf::item<bool> enable_check_authoritative_redaction;
|
||||
}
|
||||
|
||||
decltype(ircd::m::fetch::enable_check_event_id)
|
||||
ircd::m::fetch::enable_check_event_id
|
||||
{
|
||||
{ "name", "ircd.m.fetch.check.event_id" },
|
||||
{ "default", true },
|
||||
};
|
||||
|
||||
decltype(ircd::m::fetch::enable_check_conforms)
|
||||
ircd::m::fetch::enable_check_conforms
|
||||
{
|
||||
{ "name", "ircd.m.fetch.check.conforms" },
|
||||
{ "default", true },
|
||||
};
|
||||
|
||||
decltype(ircd::m::fetch::enable_check_hashes)
|
||||
ircd::m::fetch::enable_check_hashes
|
||||
{
|
||||
{ "name", "ircd.m.fetch.check.hashes" },
|
||||
{ "default", true },
|
||||
};
|
||||
|
||||
decltype(ircd::m::fetch::enable_check_authoritative_redaction)
|
||||
ircd::m::fetch::enable_check_authoritative_redaction
|
||||
{
|
||||
{ "name", "ircd.m.fetch.check.authoritative_redaction" },
|
||||
{ "default", true },
|
||||
};
|
||||
|
||||
decltype(ircd::m::fetch::enable_check_signature)
|
||||
ircd::m::fetch::enable_check_signature
|
||||
{
|
||||
{ "name", "ircd.m.fetch.check.signature" },
|
||||
{ "default", true },
|
||||
{ "description",
|
||||
|
||||
R"(
|
||||
false - Signatures of events will not be checked by the fetch unit (they
|
||||
are still checked normally during evaluation; this conf item does not
|
||||
disable event signature verification for the server).
|
||||
|
||||
true - Signatures of events will be checked by the fetch unit such that
|
||||
bogus responses allow the fetcher to try the next server. This check might
|
||||
not occur in all cases. It will only occur if the server has the public
|
||||
key already; fetch unit worker contexts cannot be blocked trying to obtain
|
||||
unknown keys from remote hosts.
|
||||
)"},
|
||||
};
|
||||
|
||||
void
|
||||
ircd::m::fetch::check_response(const request &request,
|
||||
const json::object &response)
|
||||
{
|
||||
switch(request.opts.op)
|
||||
{
|
||||
case op::backfill:
|
||||
return check_response__backfill(request, response);
|
||||
|
||||
case op::event:
|
||||
return check_response__event(request, response);
|
||||
|
||||
case op::auth:
|
||||
return check_response__auth(request, response);
|
||||
|
||||
case op::noop:
|
||||
return;
|
||||
}
|
||||
|
||||
ircd::panic
|
||||
{
|
||||
"Unchecked response; fetch op:%u",
|
||||
uint(request.opts.op),
|
||||
};
|
||||
}
|
||||
|
||||
void
|
||||
ircd::m::fetch::check_response__auth(const request &request,
|
||||
const json::object &response)
|
||||
{
|
||||
const json::array &auth_chain
|
||||
{
|
||||
response.at("auth_chain")
|
||||
};
|
||||
|
||||
for(const json::object auth_event : auth_chain)
|
||||
{
|
||||
m::event::id::buf event_id;
|
||||
const m::event event
|
||||
{
|
||||
event_id, auth_event
|
||||
};
|
||||
|
||||
check_event(request, event);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ircd::m::fetch::check_response__event(const request &request,
|
||||
const json::object &response)
|
||||
{
|
||||
const json::array &pdus
|
||||
{
|
||||
response.at("pdus")
|
||||
};
|
||||
|
||||
const m::event event
|
||||
{
|
||||
pdus.at(0), request.opts.event_id
|
||||
};
|
||||
|
||||
check_event(request, event);
|
||||
}
|
||||
|
||||
void
|
||||
ircd::m::fetch::check_response__backfill(const request &request,
|
||||
const json::object &response)
|
||||
{
|
||||
const json::array &pdus
|
||||
{
|
||||
response.at("pdus")
|
||||
};
|
||||
|
||||
for(const json::object event : pdus)
|
||||
{
|
||||
m::event::id::buf event_id;
|
||||
const m::event _event
|
||||
{
|
||||
event_id, event
|
||||
};
|
||||
|
||||
check_event(request, _event);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ircd::m::fetch::check_event(const request &request,
|
||||
const m::event &event)
|
||||
{
|
||||
if(unlikely(!request.promise))
|
||||
throw ctx::broken_promise
|
||||
{
|
||||
"Fetch response check interrupted."
|
||||
};
|
||||
|
||||
if(request.opts.check_event_id && enable_check_event_id)
|
||||
check_event_id(request, event);
|
||||
|
||||
if(request.opts.check_conforms && enable_check_conforms)
|
||||
check_event_conforms(request, event);
|
||||
|
||||
// only check signature for v1 events
|
||||
assert(request.promise);
|
||||
if(request.opts.check_signature && enable_check_signature && request.opts.event_id.version() == "1")
|
||||
check_event_signature(request, event);
|
||||
}
|
||||
|
||||
void
|
||||
ircd::m::fetch::check_event_id(const request &request,
|
||||
const m::event &event)
|
||||
{
|
||||
if(likely(m::check_id(event)))
|
||||
return;
|
||||
|
||||
event::id::buf buf;
|
||||
const m::event &claim
|
||||
{
|
||||
buf, event.source
|
||||
};
|
||||
|
||||
throw ircd::error
|
||||
{
|
||||
"event::id claim:%s != sought:%s",
|
||||
string_view{claim.event_id},
|
||||
string_view{request.opts.event_id},
|
||||
};
|
||||
}
|
||||
|
||||
void
|
||||
ircd::m::fetch::check_event_conforms(const request &request,
|
||||
const m::event &event)
|
||||
{
|
||||
m::event::conforms conforms
|
||||
{
|
||||
event
|
||||
};
|
||||
|
||||
const bool mismatch_hashes
|
||||
{
|
||||
enable_check_hashes
|
||||
&& request.opts.check_hashes
|
||||
&& conforms.has(m::event::conforms::MISMATCH_HASHES)
|
||||
};
|
||||
|
||||
const bool authoritative_redaction
|
||||
{
|
||||
enable_check_authoritative_redaction
|
||||
&& request.opts.authoritative_redaction
|
||||
&& mismatch_hashes
|
||||
&& json::get<"origin"_>(event) == request.origin
|
||||
};
|
||||
|
||||
if(mismatch_hashes && !authoritative_redaction)
|
||||
{
|
||||
const json::object _unsigned
|
||||
{
|
||||
event.source["unsigned"]
|
||||
};
|
||||
|
||||
const json::string redacted_by
|
||||
{
|
||||
_unsigned["redacted_by"]
|
||||
};
|
||||
|
||||
if(valid(id::EVENT, redacted_by))
|
||||
log::dwarning
|
||||
{
|
||||
log, "%s claims %s redacted by %s",
|
||||
request.origin,
|
||||
string_view{request.opts.event_id},
|
||||
redacted_by,
|
||||
};
|
||||
|
||||
//TODO: XXX
|
||||
}
|
||||
|
||||
if(authoritative_redaction || !mismatch_hashes)
|
||||
conforms.del(m::event::conforms::MISMATCH_HASHES);
|
||||
|
||||
thread_local char buf[128];
|
||||
const string_view failures
|
||||
{
|
||||
conforms.string(buf)
|
||||
};
|
||||
|
||||
assert(failures || conforms.clean());
|
||||
if(!conforms.clean())
|
||||
throw ircd::error
|
||||
{
|
||||
"Non-conforming event in response :%s",
|
||||
failures,
|
||||
};
|
||||
}
|
||||
|
||||
void
|
||||
ircd::m::fetch::check_event_signature(const request &request,
|
||||
const m::event &event)
|
||||
{
|
||||
const string_view &server
|
||||
{
|
||||
!json::get<"origin"_>(event)?
|
||||
user::id(at<"sender"_>(event)).host():
|
||||
string_view(json::get<"origin"_>(event))
|
||||
};
|
||||
|
||||
const json::object &signatures
|
||||
{
|
||||
at<"signatures"_>(event).at(server)
|
||||
};
|
||||
|
||||
const json::string &key_id
|
||||
{
|
||||
!signatures.empty()?
|
||||
signatures.begin()->first:
|
||||
string_view{},
|
||||
};
|
||||
|
||||
if(!key_id)
|
||||
throw ircd::error
|
||||
{
|
||||
"Cannot find any keys for '%s' in event.signatures",
|
||||
server,
|
||||
};
|
||||
|
||||
if(m::keys::cache::has(server, key_id))
|
||||
if(!verify(event, server))
|
||||
throw ircd::error
|
||||
{
|
||||
"Signature verification failed."
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue