diff options
Diffstat (limited to 'vendor/macgirvin')
4 files changed, 129 insertions, 55 deletions
diff --git a/vendor/macgirvin/http-message-signer/README.md b/vendor/macgirvin/http-message-signer/README.md index 3e111f860..cd1aab1aa 100644 --- a/vendor/macgirvin/http-message-signer/README.md +++ b/vendor/macgirvin/http-message-signer/README.md @@ -48,6 +48,7 @@ To sign a message, install the composer package guzzlehttp/psr7 and create an in ```php use HttpSignature\HttpMessageSigner; +use HttpSignature\UnProcessableSignatureException; use GuzzleHttp\Psr7\Request; $request = new Request( @@ -71,9 +72,18 @@ $signer = (new HttpMessageSigner()) ->setTag('fediverse') // optional app profile name ->setSignatureId('sig1') // optional, default is sig1 - -$request = $signer->signRequest('("@method" "@path" "host" "date")', $request); -$isValid = $signer->verifyRequest($request); +try { + $request = $signer->signRequest('("@method" "@path" "host" "date")', $request); +} +catch (UnProcessableSignatureException $exception) { + $whatHappened = $exception->getMessage(); +} +try { + $isValid = $signer->verifyRequest($request); +} catch (UnProcessableSignatureException $exception) { + $isValid = false; + $whatHappened = $exception->getMessage(); +} ``` See full examples in `/tests`. @@ -95,12 +105,11 @@ and may include modifier parameters. These are represented as '("@query-param";name="foo" "header2";sf "header3" ...)' ``` -Parameters beginning with '@' are components derived from the HTTP request but may not be represented in the headers. Please review RFC9421 for precise definitions. - +Field names beginning with '@' are components derived from the HTTP request but may not be represented in the headers. Please review RFC9421 for precise definitions. Using the 'sf' parameter on a component will treat a signature component as a Structured Field when normalising the string. -However, parsing Structured Fields by adding the 'sf' parameter is likely to fail unless you know what `type` it is. A built-in table contains the type definition for a number of known stuctured header types. This list is probably incomplete. A method `addStructuredFieldTypes()` is available to add the type information so it can be successfullly parsed. This takes an array with key of the lowercase header name and a value; which is one of 'list', 'innerlist', 'parameters, 'dictionary', 'item'. If the header name is in the list and the 'sf' modifier is used, the header will be parsed as the Structured Field type indicated. +However, parsing Structured Fields by adding the 'sf' parameter is likely to fail unless you know what `type` it is. A built-in table contains the type definition for a number of known stuctured header types. This list is probably incomplete. A method `addStructuredFieldTypes()` is available to add the type information so it can be successfully parsed. This takes an array with key of the lowercase header name and a value; which is one of 'list', 'innerlist', 'parameters, 'dictionary', 'item'. If the header name is in the list and the 'sf' modifier is used, the header will be parsed as the Structured Field type indicated. If a Structured Field is declared as type 'dictionary'; it is suitable for use with the RFC9421 `key` parameter. Using this parameter will fail if the Structured Field type is unknown or has not been registered. diff --git a/vendor/macgirvin/http-message-signer/composer.json b/vendor/macgirvin/http-message-signer/composer.json index 8c02de2c6..dfb735938 100644 --- a/vendor/macgirvin/http-message-signer/composer.json +++ b/vendor/macgirvin/http-message-signer/composer.json @@ -7,7 +7,9 @@ "psr/http-message": "^2.0", "guzzlehttp/psr7": "^2.0", "bakame/http-structured-fields": "^2.0", - "ext-openssl": "*" + "ext-openssl": "*", + "phpseclib/phpseclib": "~3.0", + "phpseclib/phpseclib2_compat": "^1.0" }, "require-dev": { "phpunit/phpunit": "^10.0" diff --git a/vendor/macgirvin/http-message-signer/src/HttpMessageSigner.php b/vendor/macgirvin/http-message-signer/src/HttpMessageSigner.php index 977bed4cd..31c481f22 100644 --- a/vendor/macgirvin/http-message-signer/src/HttpMessageSigner.php +++ b/vendor/macgirvin/http-message-signer/src/HttpMessageSigner.php @@ -2,6 +2,7 @@ namespace HttpSignature; +use Bakame\Http\StructuredFields\Dictionary; use Bakame\Http\StructuredFields\InnerList; use Bakame\Http\StructuredFields\Item; use Bakame\Http\StructuredFields\OuterList; @@ -9,7 +10,7 @@ use Bakame\Http\StructuredFields\Parameters; use Psr\Http\Message\MessageInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; -use Bakame\Http\StructuredFields\Dictionary; +use phpseclib\Crypt\RSA; class HttpMessageSigner { @@ -45,6 +46,13 @@ class HttpMessageSigner /* PSR-7 interface to signing function */ + /** + * @param string $coveredFields + * @param MessageInterface $interface + * @param RequestInterface|null $originalRequest + * @return MessageInterface + * @throws UnProcessableSignatureException + */ public function signRequest(string $coveredFields, MessageInterface $interface, RequestInterface $originalRequest = null): MessageInterface { @@ -66,6 +74,13 @@ class HttpMessageSigner } /* PSR-7 verify interface and also check body digest if included */ + /** + * @param MessageInterface $interface + * @param RequestInterface|null $originalRequest + * @return bool + * + * @throws UnProcessableSignatureException + */ public function verifyRequest(MessageInterface $interface, RequestInterface $originalRequest = null): bool { @@ -136,10 +151,10 @@ class HttpMessageSigner foreach ($indices as $index) { $member = $dict->getByIndex($index); if (!$member) { - throw new \Exception('Index ' . $index . ' not found'); + throw new UnProcessableSignatureException('Index ' . $index . ' not found'); } if (in_array($member, $processedComponents, true)) { - throw new \Exception('Duplicate member found'); + throw new UnProcessableSignatureException('Duplicate member found'); } $processedComponents[] = $member; $signatureComponents[] = $this->canonicalizeComponent($member, $headers, $interface); @@ -186,10 +201,10 @@ class HttpMessageSigner foreach ($indices as $index) { $member = $dict->getByIndex($index); if (!$member) { - throw new \Exception('Index ' . $index . ' not found'); + throw new UnProcessableSignatureException('Index ' . $index . ' not found'); } if (in_array($member, $processedComponents, true)) { - throw new \Exception('Duplicate member found'); + throw new UnProcessableSignatureException('Duplicate member found'); } $processedComponents[] = $member; $signatureComponents[] = $this->canonicalizeComponent($member, $headers, $interface); @@ -239,7 +254,13 @@ class HttpMessageSigner $signatureComponents[$dictName][] = $this->canonicalizeComponent($member, $headers, $interface); } $parameters = $this->extractParameters($members); - + if ($parameters) { + foreach ($parameters as $key => $value) { + if (!in_array($key, ['created', 'expires', 'nonce', 'alg', 'keyid', 'tag'])) { + return false; + } + } + } if (isset($parameters['expires'])) { $expires = (int) $parameters['expires']; if ($expires < time()) { @@ -257,21 +278,6 @@ class HttpMessageSigner } $sigDict = $this->parseStructuredDict($headers['signature']); - if ($sigDict->isNotEmpty()) { - $indices = $sigDict->indices(); - foreach ($indices as $index) { - [$dictName, $members] = $sigDict->getByIndex($index); - if ($members instanceof Item) { - $signatures[$dictName] = $members->value(); - } - if ($members instanceof InnerList) { - $innerIndices = $members->indices(); - foreach ($innerIndices as $innerIndex) { - $signatures[$dictName][] = $members->getByIndex($innerIndex); - } - } - } - } foreach ($signatureComponents as $dictName => $dictComponents) { $namedSignatureComponents = $signatureComponents[$dictName]; @@ -283,7 +289,7 @@ class HttpMessageSigner } $decodedSig = base64_decode(trim($sigDict[$dictName]->__toString(), ':')); - return $this->verifySignature($signatureBase, $decodedSig, $params['alg'] ?? $this->algorithm); + return $this->verifySignature($signatureBase, $decodedSig, $parameters['alg'] ?? $this->algorithm); } return false; } @@ -308,12 +314,17 @@ class HttpMessageSigner $fieldName = $field->value(); $parameters = $this->extractParameters($field); if (isset($parameters['bs']) && isset($parameters['sf'])) { - throw new \Exception('Cannot use both bs and sf'); + throw new UnProcessableSignatureException('Cannot use both bs and sf'); } $whichRequest = $interface; - if (isset($parameters['req']) && $interface instanceof ResponseInterface) { - $whichRequest = $this->getOriginalRequest(); + if (isset($parameters['req'])) { + if ($interface instanceof ResponseInterface) { + $whichRequest = $this->getOriginalRequest(); + } + else { + throw new UnProcessableSignatureException('missing request for req parameter'); + } } $whichHeaders = $headers; @@ -353,12 +364,17 @@ class HttpMessageSigner private function getFieldValue($fieldName, MessageInterface $interface, $headers, $parameters ): array { + // The $interface has no single method to extract this, so build it from + // the avilable components. + $targetUri = $interface->getUri()->getScheme() . '://' . $interface->getUri()->getAuthority() + . $interface->getUri()->getPath() . $interface->getUri()->getQuery(); + $value = match ($fieldName) { '@signature-params' => ['', ''], '@method' => ['"@method"', strtoupper($interface->getMethod())], '@authority' => ['"@authority"', $interface->getUri()->getAuthority()], '@scheme' => ['"@scheme"', strtolower($interface->getUri()->getScheme())], - '@target-uri' => ['"target-uri"', $interface->getUri()->__toString()], + '@target-uri' => ['"@target-uri"', $targetUri], '@request-target' => ['"@request-target"', $interface->getRequestTarget()], '@path' => ['"@path"', $interface->getUri()->getPath()], '@query' => ['"@query"', $interface->getUri()->getQuery()], @@ -412,7 +428,7 @@ class HttpMessageSigner return ['"_' . $fieldName . '_"', $queryParams[$fieldName] ? '"' . $queryParams[$fieldName] . '"' : '']; } } - throw new \Exception('Query string named parameter not set'); + throw new UnProcessableSignatureException('Query string named parameter not set'); } private function applyStructuredField(string $name, string $fieldValue): string @@ -423,11 +439,11 @@ class HttpMessageSigner $field = OuterList::fromHttpValue($fieldValue); break; case 'innerlist': - $field = InnerList::fromHttpValue($fieldValue); - break; + $field = InnerList::fromHttpValue($fieldValue); + break; case 'parameters': - $field = Parameters::fromHttpValue($fieldValue); - break; + $field = Parameters::fromHttpValue($fieldValue); + break; case 'dictionary': $field = Dictionary::fromHttpValue($fieldValue); break; @@ -455,7 +471,7 @@ class HttpMessageSigner break; } if (!$field) { - return ''; + throw new UnProcessableSignatureException('Unknown or unregistered structured field type'); } return $field->toHttpValue(); } @@ -472,31 +488,29 @@ class HttpMessageSigner return ''; } - private function applyByteSequence(string $fieldValue): string - { - return $fieldValue; - } - - private function applyTrailer(string $fieldValue): string - { - return $fieldValue; - } - private function createSignature(string $data): string { return match ($this->algorithm) { + 'rsa-v1_5-sha256' => $this->rsaSign($data), + 'rsa-v1_5-sha512' => $this->rsa512Sign($data), 'rsa-sha256' => $this->rsaSign($data), + 'rsa-pss-sha512' => $this->pssSign($data), 'ed25519' => $this->ed25519Sign($data), 'hmac-sha256' => base64_encode(hash_hmac('sha256', $data, $this->privateKey, true)), - default => throw new \RuntimeException("Unsupported algorithm: $this->algorithm") + default => throw new UnProcessableSignatureException("Unsupported algorithm: $this->algorithm") }; } private function verifySignature(string $data, string $signature, string $alg): bool { return match ($alg) { - 'rsa-sha256' => openssl_verify($data, $signature, $this->publicKey, - OPENSSL_ALGO_SHA256) === 1, + 'rsa-v1_5-sha256' => openssl_verify($data, $signature, $this->publicKey, + OPENSSL_ALGO_SHA256) === 1, + 'rsa-v1_5-sha512' => openssl_verify($data, $signature, $this->publicKey, + OPENSSL_ALGO_SHA512) === 1, + 'rsa-sha256' => openssl_verify($data, $signature, $this->publicKey, + OPENSSL_ALGO_SHA256) === 1, + 'rsa-pss-sha512' => $this->pssVerify($data, $signature), 'ed25519' => openssl_verify($data, $signature, $this->publicKey, "Ed25519") === 1, 'hmac-sha256' => hash_equals( base64_encode(hash_hmac('sha256', $data, $this->privateKey, true)), @@ -511,19 +525,60 @@ class HttpMessageSigner private function rsaSign(string $data): string { if (!openssl_sign($data, $signature, $this->privateKey, OPENSSL_ALGO_SHA256)) { - throw new \RuntimeException("RSA signing failed"); + throw new UnProcessableSignatureException("RSA signing failed"); } return base64_encode($signature); } + private function rsa512Sign(string $data): string + { + if (!openssl_sign($data, $signature, $this->privateKey, OPENSSL_ALGO_SHA512)) { + throw new UnProcessableSignatureException("RSA signing failed"); + } + return base64_encode($signature); + } + + private function pssSign(string $data): string + { + $rsa = new RSA(); + if ($rsa->loadKey($this->privateKey) !== true) { + throw new UnprocessableSignatureException("PSS loadkey failure"); + }; + $rsa->setHash('sha512'); + $rsa->setMGFHash('sha512'); + $rsa->setSignatureMode(RSA::SIGNATURE_PSS); + try { + $signatureBytes = $rsa->sign($data); + } catch (\Exception $exception) { + throw new UnprocessableSignatureException($exception->getMessage()); + } + return base64_encode($signatureBytes); + } private function ed25519Sign(string $data): string { if (!openssl_sign($data, $signature, $this->privateKey, "Ed25519")) { - throw new \RuntimeException("Ed25519 signing failed"); + throw new UnProcessableSignatureException("Ed25519 signing failed"); } return base64_encode($signature); } + private function pssVerify(string $data, $signature): bool + { + $rsa = new RSA(); + if (!$rsa->loadKey($this->publicKey)) { + throw new UnprocessableSignatureException("PSS loadkey failure"); + }; + $rsa->setHash('sha512'); + $rsa->setMGFHash('sha512'); + $rsa->setSignatureMode(RSA::SIGNATURE_PSS); + try { + $verified = $rsa->verify($data, $signature); + } catch (\Exception $exception) { + $verified = false; + } + return $verified; + } + /* parse a structed dict */ private function parseStructuredDict(string $headerValue) @@ -567,7 +622,7 @@ class HttpMessageSigner } } if (!isset($algorithmHeaderString)) { - throw new \RuntimeException("Unsupported algorithm: $algorithm"); + throw new UnProcessableSignatureException("Unsupported digest algorithm: $algorithm"); } $digest = hash($algorithm, $body, true); /** diff --git a/vendor/macgirvin/http-message-signer/src/UnProcessableSignatureException.php b/vendor/macgirvin/http-message-signer/src/UnProcessableSignatureException.php new file mode 100644 index 000000000..b3dacd8eb --- /dev/null +++ b/vendor/macgirvin/http-message-signer/src/UnProcessableSignatureException.php @@ -0,0 +1,8 @@ +<?php + +namespace HttpSignature; + +class UnProcessableSignatureException extends \Exception +{ + +}
\ No newline at end of file |