From b7f285e4e24247fedb94f030356fa6f291f525cc Mon Sep 17 00:00:00 2001 From: Yassine Doghri <yassine@doghri.fr> Date: Sun, 30 Jan 2022 16:32:11 +0000 Subject: [PATCH] fix(http-signature): update SIGNATURE_PATTERN allowing signature keys to be sent in any order set algorithm key as optional and set defaults for both algorithm (rsa-sha256) and headers (date) keys --- modules/Fediverse/HttpSignature.php | 59 +++++++++++++++++++---------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/modules/Fediverse/HttpSignature.php b/modules/Fediverse/HttpSignature.php index 1bd6412517..b0f489182e 100644 --- a/modules/Fediverse/HttpSignature.php +++ b/modules/Fediverse/HttpSignature.php @@ -28,18 +28,14 @@ class HttpSignature /** * @var string */ - private const SIGNATURE_PATTERN = '/^ - keyId="(?P<keyId> - (https?:\/\/[\w\-\.]+[\w]+) - (:[\d]+)? - ([\w\-\.#\/@]+) - )", - algorithm="(?P<algorithm>[\w\-]+)", - (headers="\(request-target\) (?P<headers>[\w\\-\s]+)",)? - signature="(?P<signature>[\w+\/]+={0,2})" + private const SIGNATURE_PATTERN = '/ + (?=.*(keyId="(?P<keyId>https?:\/\/[\w\-\.]+[\w]+(:[\d]+)?[\w\-\.#\/@]+)")) + (?=.*(signature="(?P<signature>[\w+\/]+={0,2})")) + (?=.*(headers="\(request-target\)(?P<headers>[\w\\-\s]+)"))? + (?=.*(algorithm="(?P<algorithm>[\w\-]+)"))? /x'; - protected ?IncomingRequest $request = null; + protected IncomingRequest $request; public function __construct(IncomingRequest $request = null) { @@ -66,7 +62,8 @@ class HttpSignature $requestTime = Time::createFromFormat('D, d M Y H:i:s T', $dateHeader->getValue()); $diff = $requestTime->difference($currentTime); - if ($diff->getSeconds() > 3600) { + $diffSeconds = $diff->getSeconds(); + if ($diffSeconds > 3600 || $diffSeconds < 0) { throw new Exception('Request must be made within the last hour.'); } @@ -74,6 +71,7 @@ class HttpSignature if (! ($digestHeader = $this->request->header('digest'))) { throw new Exception('Request must include a digest header'); } + // compute body digest and compare with header digest $bodyDigest = hash('sha256', $this->request->getBody(), true); $digest = 'SHA-256=' . base64_encode($bodyDigest); @@ -94,7 +92,8 @@ class HttpSignature // set $keyId, $headers and $signature variables $keyId = $parts['keyId']; - $headers = $parts['headers']; + $algorithm = $parts['algorithm']; + $headers = $parts['headers'] ?? 'date'; $signature = $parts['signature']; // Fetch the public key linked from keyId @@ -102,19 +101,14 @@ class HttpSignature $actorResponse = $actorRequest->get(); $actor = json_decode($actorResponse->getBody(), false, 512, JSON_THROW_ON_ERROR); - $publicKeyPem = $actor->publicKey->publicKeyPem; + $publicKeyPem = (string) $actor->publicKey->publicKeyPem; // Create a comparison string from the plaintext headers we got // in the same order as was given in the signature header, $data = $this->getPlainText(explode(' ', trim($headers))); - // Verify that string using the public key and the original signature. - $rsa = new RSA(); - $rsa->setHash('sha256'); - $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1); - $rsa->loadKey($publicKeyPem); - - return $rsa->verify($data, base64_decode($signature, true)); + // Verify the data string using the public key and the original signature. + return $this->verifySignature($publicKeyPem, $data, $signature, $algorithm); } /** @@ -124,7 +118,7 @@ class HttpSignature */ private function splitSignature(string $signature): array | false { - if (! preg_match(self::SIGNATURE_PATTERN, $signature, $matches)) { + if (! preg_match(self::SIGNATURE_PATTERN, $signature, $matches, PREG_UNMATCHED_AS_NULL)) { // Signature pattern failed return false; } @@ -162,4 +156,27 @@ class HttpSignature return implode("\n", $strings); } + + /** + * Verifies the signature depending on the algorithm sent + */ + private function verifySignature( + string $publicKeyPem, + string $data, + string $signature, + string $algorithm = 'rsa-sha256' + ): bool { + if ($algorithm === 'rsa-sha512' || $algorithm === 'rsa-sha256') { + $hash = substr($algorithm, strpos($algorithm, '-') + 1); + $rsa = new RSA(); + $rsa->setHash($hash); + $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1); + $rsa->loadKey($publicKeyPem); + + return $rsa->verify($data, (string) base64_decode($signature, true)); + } + + // not implemented + return false; + } } -- GitLab