From 1eaab5e4107ef3a3ee23b7a990a0137f45138b45 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 15 Jan 2025 07:51:30 +0000 Subject: [PATCH 1/3] Issue 14692: Prevent loops with remote servers --- src/Util/HTTPSignature.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Util/HTTPSignature.php b/src/Util/HTTPSignature.php index a544119616..b4ecf32eb6 100644 --- a/src/Util/HTTPSignature.php +++ b/src/Util/HTTPSignature.php @@ -604,13 +604,14 @@ class HTTPSignature /** * Gets a signer from a given HTTP request * - * @param string $content - * @param array $http_headers + * @param string $content + * @param array $http_headers + * @param ?boolean $update true = always update, false = never update, null = update when not found or outdated * * @return string|null|false Signer * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - public static function getSigner(string $content, array $http_headers) + public static function getSigner(string $content, array $http_headers, bool $update = null) { if (empty($http_headers['HTTP_SIGNATURE'])) { DI::logger()->debug('No HTTP_SIGNATURE header'); @@ -700,7 +701,7 @@ class HTTPSignature return false; } - $key = self::fetchKey($sig_block['keyId'], $actor); + $key = self::fetchKey($sig_block['keyId'], $actor, $update); if (empty($key)) { DI::logger()->info('Empty key'); return false; @@ -802,17 +803,18 @@ class HTTPSignature /** * fetches a key for a given id and actor * - * @param string $id - * @param string $actor + * @param string $id + * @param string $actor + * @param ?boolean $update true = always update, false = never update, null = update when not found or outdated * * @return array with actor url and public key * @throws \Exception */ - private static function fetchKey(string $id, string $actor): array + private static function fetchKey(string $id, string $actor, bool $update = null): array { $url = (strpos($id, '#') ? substr($id, 0, strpos($id, '#')) : $id); - $profile = APContact::getByURL($url); + $profile = APContact::getByURL($url, $update); if (!empty($profile)) { DI::logger()->info('Taking key from id', ['id' => $id]); return ['url' => $url, 'pubkey' => $profile['pubkey'], 'type' => $profile['type']]; From bccf95a088838975295b7411508a6b0c8ce8d480 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 22 Jan 2025 20:26:05 +0000 Subject: [PATCH 2/3] Changes after review --- src/Util/HTTPSignature.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Util/HTTPSignature.php b/src/Util/HTTPSignature.php index b4ecf32eb6..ebaeab028c 100644 --- a/src/Util/HTTPSignature.php +++ b/src/Util/HTTPSignature.php @@ -604,14 +604,14 @@ class HTTPSignature /** * Gets a signer from a given HTTP request * - * @param string $content - * @param array $http_headers + * @param string $content Body of the request + * @param array $http_headers array containing the HTTP headers * @param ?boolean $update true = always update, false = never update, null = update when not found or outdated * * @return string|null|false Signer * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - public static function getSigner(string $content, array $http_headers, bool $update = null) + public static function getSigner(string $content, array $http_headers, ?bool $update = null) { if (empty($http_headers['HTTP_SIGNATURE'])) { DI::logger()->debug('No HTTP_SIGNATURE header'); @@ -803,14 +803,14 @@ class HTTPSignature /** * fetches a key for a given id and actor * - * @param string $id - * @param string $actor + * @param string $id keyId of the signature block + * @param string $actor Actor URI * @param ?boolean $update true = always update, false = never update, null = update when not found or outdated * * @return array with actor url and public key * @throws \Exception */ - private static function fetchKey(string $id, string $actor, bool $update = null): array + private static function fetchKey(string $id, string $actor, ?bool $update = null): array { $url = (strpos($id, '#') ? substr($id, 0, strpos($id, '#')) : $id); From 0f8786f8b18be97f1c7a92f5e72fcc7b5fcb1968 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 22 Jan 2025 20:31:14 +0000 Subject: [PATCH 3/3] Fix codestyle --- src/Util/HTTPSignature.php | 50 +++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/src/Util/HTTPSignature.php b/src/Util/HTTPSignature.php index ebaeab028c..bb5d8c6b5a 100644 --- a/src/Util/HTTPSignature.php +++ b/src/Util/HTTPSignature.php @@ -49,19 +49,21 @@ class HTTPSignature { $headers = null; $spoofable = false; - $result = [ - 'signer' => '', - 'header_signed' => false, - 'header_valid' => false + $result = [ + 'signer' => '', + 'header_signed' => false, + 'header_valid' => false ]; // Decide if $data arrived via controller submission or curl. $headers = []; + $headers['(request-target)'] = strtolower(DI::args()->getMethod()) . ' ' . $_SERVER['REQUEST_URI']; foreach ($_SERVER as $k => $v) { if (strpos($k, 'HTTP_') === 0) { $field = str_replace('_', '-', strtolower(substr($k, 5))); + $headers[$field] = $v; } } @@ -98,6 +100,7 @@ class HTTPSignature if ($key && function_exists($key)) { $result['signer'] = $sig_block['keyId']; + $key = $key($sig_block['keyId']); } @@ -136,7 +139,7 @@ class HTTPSignature $return_headers = $head; } - $alg = 'sha512'; + $alg = 'sha512'; $algorithm = 'rsa-sha512'; $x = self::sign($head, $prvkey, $alg); @@ -158,7 +161,7 @@ class HTTPSignature */ private static function sign(array $head, string $prvkey, string $alg = 'sha256'): array { - $ret = []; + $ret = []; $headers = ''; $fields = ''; @@ -220,10 +223,10 @@ class HTTPSignature } $return = [ - 'keyId' => $headers['keyId'] ?? '', + 'keyId' => $headers['keyId'] ?? '', 'algorithm' => $headers['algorithm'] ?? 'rsa-sha256', - 'created' => $headers['created'] ?? null, - 'expires' => $headers['expires'] ?? null, + 'created' => $headers['created'] ?? null, + 'expires' => $headers['expires'] ?? null, 'headers' => explode(' ', $headers['headers'] ?? ''), 'signature' => base64_decode(preg_replace('/\s+/', '', $headers['signature'] ?? '')), ]; @@ -268,17 +271,17 @@ class HTTPSignature $content = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); // Header data that is about to be signed. - $host = strtolower(parse_url($target, PHP_URL_HOST)); - $path = parse_url($target, PHP_URL_PATH); - $digest = 'SHA-256=' . base64_encode(hash('sha256', $content, true)); + $host = strtolower(parse_url($target, PHP_URL_HOST)); + $path = parse_url($target, PHP_URL_PATH); + $digest = 'SHA-256=' . base64_encode(hash('sha256', $content, true)); $content_length = strlen($content); - $date = DateTimeFormat::utcNow(DateTimeFormat::HTTP); + $date = DateTimeFormat::utcNow(DateTimeFormat::HTTP); $headers = [ - 'Date' => $date, + 'Date' => $date, 'Content-Length' => $content_length, - 'Digest' => $digest, - 'Host' => $host + 'Digest' => $digest, + 'Host' => $host ]; $signed_data = "(request-target): post " . $path . "\ndate: " . $date . "\ncontent-length: " . $content_length . "\ndigest: " . $digest . "\nhost: " . $host; @@ -289,7 +292,7 @@ class HTTPSignature $headers['Content-Type'] = 'application/activity+json'; - $postResult = DI::httpClient()->post($target, $content, $headers, DI::config()->get('system', 'curl_timeout'), HttpClientRequest::ACTIVITYPUB); + $postResult = DI::httpClient()->post($target, $content, $headers, DI::config()->get('system', 'curl_timeout'), HttpClientRequest::ACTIVITYPUB); $return_code = $postResult->getReturnCode(); DI::logger()->info('Transmit to ' . $target . ' returned ' . $return_code); @@ -318,10 +321,10 @@ class HTTPSignature return false; } - $activity = JsonLD::compact($data); - $type = JsonLD::fetchElement($activity, '@type'); + $activity = JsonLD::compact($data); + $type = JsonLD::fetchElement($activity, '@type'); $trust_source = true; - $object_data = Receiver::prepareObjectData($activity, $uid, true, $trust_source, $owner['url']); + $object_data = Receiver::prepareObjectData($activity, $uid, true, $trust_source, $owner['url']); if (empty($object_data)) { return false; } @@ -387,7 +390,7 @@ class HTTPSignature * @param int $gsid Server ID * @throws \Exception */ - static public function setInboxStatus(string $url, bool $success, bool $shared = false, int $gsid = null) + public static function setInboxStatus(string $url, bool $success, bool $shared = false, int $gsid = null) { $now = DateTimeFormat::utcNow(); @@ -434,7 +437,7 @@ class HTTPSignature $stamp1 = strtotime($status['success']); } - $stamp2 = strtotime($now); + $stamp2 = strtotime($now); $previous_stamp = strtotime($status['previous']); // Archive the inbox when there had been failures for five days. @@ -631,11 +634,13 @@ class HTTPSignature } $headers = []; + $headers['(request-target)'] = strtolower(DI::args()->getMethod()) . ' ' . parse_url($http_headers['REQUEST_URI'], PHP_URL_PATH); // First take every header foreach ($http_headers as $k => $v) { $field = str_replace('_', '-', strtolower($k)); + $headers[$field] = $v; } @@ -643,6 +648,7 @@ class HTTPSignature foreach ($http_headers as $k => $v) { if (strpos($k, 'HTTP_') === 0) { $field = str_replace('_', '-', strtolower(substr($k, 5))); + $headers[$field] = $v; } }