1
0
Fork 1

Known pubkey fallback, exclude on key collisions

pull/26/head
gravel 1 year ago
parent 8d4815f33d
commit c45ce974ac
Signed by: gravel
SSH Key Fingerprint: SHA256:p4HP49CCk4YQMkJpWJ09L8peEPQWjERtdCRAFxPfbOY

@ -218,6 +218,13 @@
*/
private array $room_hints = [];
/**
* @var bool $merge_error
*
* Flag specifying whether the server is invalidated as a result of merging.
*/
private bool $merge_error = false;
private function __construct() {}
/**
@ -266,8 +273,10 @@
/**
* Absorbs extra info from another instance of the same server.
* @param CommunityServer $server
*
* @return True if successful, false in case of mistmatch.
*/
private function merge_from($server) {
private function merge_from($server): bool {
// Merge room hint information.
$this->room_hints = [
...$this->room_hints,
@ -275,8 +284,24 @@
];
// Merge public key information.
if (!$this->has_pubkey() && $server->has_pubkey()) {
$this->pubkey = $server->pubkey;
// In case of error, set the `merge_error` flag.
if (!$this->set_pubkey($server->pubkey)) {
if ($this->merge_error) {
return false;
}
$base_url = $this->base_url;
$other_base_url = $server->base_url;
$pubkey_old = $this->pubkey;
$pubkey_new = $server->pubkey;
log_error(
"Key collision for $base_url:" .
"Have $pubkey_old, fetched $pubkey_new" .
"from server $other_base_url"
);
$this->merge_error = true;
return false;
}
// Prefer HTTPS URLs over HTTP.
@ -288,14 +313,27 @@
if (filter_var($this->get_hostname(), FILTER_VALIDATE_IP)) {
$this->base_url = $this->get_scheme() . "://" . $server->get_hostname();
}
return true;
}
/**
* Re-introduces the server to a consistent state after merging.
* Re-introduces the servers to a consistent state after merging.
* @param CommunityServer[] $servers
* @return CommunityServer[]
*/
private function merge_consistency() {
private static function ensure_merge_consistency(array $servers) {
// Exclude servers with merge errors.
$servers = array_filter($servers, function(\CommunityServer $server) {
return !$server->merge_error;
});
// Remove duplicate room hints; does not require sorting.
$this->room_hints = array_unique($this->room_hints);
foreach ($servers as $server) {
$server->room_hints = array_unique($server->room_hints);
}
return $servers;
}
/**
@ -336,7 +374,7 @@
CommunityServer::merge_by($servers, "get_hostname");
foreach ($servers as $server) $server->merge_consistency();
$servers = CommunityServer::ensure_merge_consistency($servers);
return $servers;
}
@ -351,7 +389,7 @@
CommunityServer::merge_by($servers, "get_pubkey");
foreach ($servers as $server) $server->merge_consistency();
$servers = CommunityServer::ensure_merge_consistency($servers);
return $servers;
}
@ -400,6 +438,7 @@
foreach ($join_urls as $join_url) {
$server = new CommunityServer();
// Call must succeed with no default public key.
$server->initialize_from_url($join_url);
$servers[] = $server;
@ -465,8 +504,7 @@
// Synchronous for-loop for now.
foreach ($servers as $server) {
if (!($server->fetch_rooms())) continue;
// Accept failures to fetch pubkey if already known.
if (!$server->fetch_or_has_pubkey()) continue;
if (!($server->fetch_pubkey())) continue;
$reachable_servers[] = $server;
}
@ -515,28 +553,44 @@
return $this->pubkey;
}
function set_pubkey($pubkey) {
/**
* Attempts to set the server public key.
* @param string $pubkey SOGS public key.
* @return bool True if successful, false in case of mismatch.
*/
function set_pubkey(string $pubkey): bool {
if ($this->has_pubkey() && $this->pubkey != $pubkey) {
$base_url = $this->base_url;
throw new ValueError("Pubkey mismatch for $base_url");
return false;
}
$this->pubkey = $pubkey;
return true;
}
function set_pubkey_from_url($join_url) {
$url_components = parse_url($join_url);
parse_str($url_components['query'], $query_components);
$this->pubkey = $query_components['public_key'];
/**
* Attempts to read the server public key from a join URL.
* @param string $join_url Join URL for any of the server's rooms.
* @return bool True if successful, false in case of mismatch.
*/
function set_pubkey_from_url(string $join_url): bool {
return $this->set_pubkey(url_get_pubkey($join_url));
}
/**
* Sets base url, pubkey, and room hint from join URL.
* Learns server info from a room's join URL.
* The base URL and public key are saved,
* and the room token is added as a fallback for room polling.
* @param string $join_url Room join URL to initialize with.
* @return bool True if successful, false in case of public key mismatch.
*/
function initialize_from_url($join_url) {
function initialize_from_url($join_url): bool {
if (!$this->set_pubkey_from_url($join_url)) {
return false;
}
$this->base_url = url_get_base($join_url);
$this->set_pubkey_from_url($join_url);
$this->room_hints[] = url_get_token($join_url);
return true;
}
/**
@ -641,21 +695,9 @@
}
/**
* Fetch the server's SOGS public key if absent.
* @return bool True if pubkey is present or has been fetched, false otherwise.
*/
function fetch_or_has_pubkey(): bool {
$base_url = $this->base_url;
// Do not use 'or' here; I learned the hard way.
$result = $this->fetch_pubkey() || $this->has_pubkey();
if (!$result) log_warning("Failed to fetch pubkey for $base_url.");
return $result;
}
/**
* Attempt to fetch own public key by parsing SOGS HTML preview.
* Attempt to fetch server public key by parsing SOGS HTML preview.
*
* @return bool True if successful, false otherwise.
* @return bool True iff no key conflict has arised and we have a pubkey.
*/
function fetch_pubkey() {
if (empty($this->rooms)) {
@ -672,17 +714,29 @@
if (!$room_view) {
log_debug("Failed to fetch room preview from $preview_url.");
return false;
return $has_pubkey;
}
$links = parse_join_links($room_view);
$link = $links[0];
if (!isset($links[0])) {
if (!isset($link)) {
log_debug("Could not locate join link in preview at $preview_url.");
return false;
return $has_pubkey;
}
$this->set_pubkey_from_url($links[0]);
if (!$this->set_pubkey_from_url($link)) {
// More information needs to be logged for errors
// in case of lack of context due to lower verbosity.
$base_url = $this->base_url;
$pubkey_old = $this->pubkey;
$pubkey_new = url_get_pubkey($link);
log_error(
"Key collision for $base_url:" .
"Have $pubkey_old, fetched $pubkey_new from $preview_url"
);
return false;
}
return true;
}

@ -189,6 +189,16 @@
return str_replace("/", "", $token);
}
/**
* Extracts the server public key from a join URL.
* @param string $join_url Join URL for Session Community.
* @return string SOGS public key
*/
function url_get_pubkey(string $join_url) {
$url_components = parse_url($join_url);
parse_str($url_components['query'], $query_components);
return $query_components['public_key'];
}
/**
* Extracts join links that match $REGEX_JOIN_LINK.