Fix http signatures signing of psr7 requests

This commit is contained in:
Jeremy Dormitzer 2019-01-17 14:47:30 -05:00
parent 634db57684
commit 4c5b667175
4 changed files with 152 additions and 15 deletions

View File

@ -28,7 +28,8 @@
"phpseclib/phpseclib": "^2.0",
"psr/http-message": "^1.0",
"symfony/http-kernel": "^4.2",
"symfony/psr-http-message-bridge": "^1.1"
"symfony/psr-http-message-bridge": "^1.1",
"zendframework/zend-diactoros": "^1.8"
},
"require-dev": {
"phpunit/dbunit": "^4.0",

118
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "fa1affc2b91bce0c5e025b09069a9fc6",
"content-hash": "4fdd0e94241c59ddd2c9763881167989",
"packages": [
{
"name": "doctrine/annotations",
@ -1900,6 +1900,70 @@
"psr-7"
],
"time": "2018-08-30T16:28:28+00:00"
},
{
"name": "zendframework/zend-diactoros",
"version": "1.8.6",
"source": {
"type": "git",
"url": "https://github.com/zendframework/zend-diactoros.git",
"reference": "20da13beba0dde8fb648be3cc19765732790f46e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/20da13beba0dde8fb648be3cc19765732790f46e",
"reference": "20da13beba0dde8fb648be3cc19765732790f46e",
"shasum": ""
},
"require": {
"php": "^5.6 || ^7.0",
"psr/http-message": "^1.0"
},
"provide": {
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"ext-dom": "*",
"ext-libxml": "*",
"php-http/psr7-integration-tests": "dev-master",
"phpunit/phpunit": "^5.7.16 || ^6.0.8 || ^7.2.7",
"zendframework/zend-coding-standard": "~1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.8.x-dev",
"dev-develop": "1.9.x-dev",
"dev-release-2.0": "2.0.x-dev"
}
},
"autoload": {
"files": [
"src/functions/create_uploaded_file.php",
"src/functions/marshal_headers_from_sapi.php",
"src/functions/marshal_method_from_sapi.php",
"src/functions/marshal_protocol_version_from_sapi.php",
"src/functions/marshal_uri_from_sapi.php",
"src/functions/normalize_server.php",
"src/functions/normalize_uploaded_files.php",
"src/functions/parse_cookie_header.php"
],
"psr-4": {
"Zend\\Diactoros\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-2-Clause"
],
"description": "PSR HTTP Message implementations",
"homepage": "https://github.com/zendframework/zend-diactoros",
"keywords": [
"http",
"psr",
"psr-7"
],
"time": "2018-09-05T19:29:37+00:00"
}
],
"packages-dev": [
@ -2657,6 +2721,58 @@
],
"time": "2018-12-12T07:20:32+00:00"
},
{
"name": "psr/http-factory",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-factory.git",
"reference": "378bfe27931ecc54ff824a20d6f6bfc303bbd04c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/378bfe27931ecc54ff824a20d6f6bfc303bbd04c",
"reference": "378bfe27931ecc54ff824a20d6f6bfc303bbd04c",
"shasum": ""
},
"require": {
"php": ">=7.0.0",
"psr/http-message": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interfaces for PSR-7 HTTP message factories",
"keywords": [
"factory",
"http",
"message",
"psr",
"psr-17",
"psr-7",
"request",
"response"
],
"time": "2018-07-30T21:54:04+00:00"
},
{
"name": "sebastian/code-unit-reverse-lookup",
"version": "1.0.1",

View File

@ -5,7 +5,7 @@ use DateTime;
use ActivityPub\Utils\DateTimeProvider;
use ActivityPub\Utils\SimpleDateTimeProvider;
use Psr\Http\Message\RequestInterface;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\HeaderUtils;
@ -29,9 +29,9 @@ class HttpSignatureService
private $dateTimeProvider;
/**
* @var HttpFoundationFactory
* @var DiactorosFactory
*/
private $httpFoundationFactory;
private $psr7Factory;
/**
* Constructs a new HttpSignatureService
@ -45,7 +45,7 @@ class HttpSignatureService
$dateTimeProvider = new SimpleDateTimeProvider();
}
$this->dateTimeProvider = $dateTimeProvider;
$this->httpFoundationFactory = new HttpFoundationFactory();
$this->psr7Factory = new DiactorosFactory();
}
/**
@ -58,10 +58,9 @@ class HttpSignatureService
* (default ['(request-target)', 'host', 'date'])
* @return string The Signature header value
*/
public function sign( RequestInterface $psrRequest, string $privateKey,
public function sign( RequestInterface $request, string $privateKey,
string $keyId, $headers = self::DEFAULT_HEADERS )
{
$request = $this->httpFoundationFactory->createRequest( $psrRequest );
$headers = array_map( 'strtolower', $headers );
$signingString = $this->getSigningString( $request, $headers );
$keypair = RsaKeypair::fromPrivateKey( $privateKey );
@ -111,7 +110,8 @@ class HttpSignatureService
$targetHeaders = $params['headers'];
}
$signingString = $this->getSigningString( $request, $targetHeaders );
$psrRequest = $this->psr7Factory->createRequest( $request );
$signingString = $this->getSigningString( $psrRequest, $targetHeaders );
$signature = base64_decode( $params['signature'] );
// TODO handle different algorithms here, checking the 'algorithm' param and the key headers
$keypair = RsaKeypair::fromPublicKey( $publicKey );
@ -125,18 +125,22 @@ class HttpSignatureService
* @param array $headers The headers to use to generate the signing string
* @return string The signing string
*/
private function getSigningString( Request $request, $headers )
private function getSigningString( RequestInterface $request, $headers )
{
$signingComponents = array();
foreach ( $headers as $header ) {
$component = "${header}: ";
if ( $header == '(request-target)' ) {
$method = strtolower( $request->getMethod());
$path = $request->getRequestUri();
$path = $request->getUri()->getPath();
$query = $request->getUri()->getQuery();
if ( ! empty( $query ) ) {
$path = "$path?$query";
}
$component = $component . $method . ' ' . $path;
} else {
// TODO handle 'digest' specially here too
$values = $request->headers->get( $header, null, false );
$values = $request->getHeader( $header );
$component = $component . implode( ', ', $values );
}
$signingComponents[] = $component;

View File

@ -4,6 +4,7 @@ namespace ActivityPub\Test\Crypto;
use DateTime;
use ActivityPub\Crypto\HttpSignatureService;
use ActivityPub\Test\TestUtils\TestDateTimeProvider;
use GuzzleHttp\Psr7\Request as PsrRequest;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
@ -44,7 +45,7 @@ G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
$this->httpSignatureService = new HttpSignatureService( $dateTimeProvider );
}
private static function getRequest()
private static function getSymfonyRequest()
{
$request = Request::create(
'https://example.com/foo?param=value&pet=dog',
@ -65,6 +66,21 @@ G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
return $request;
}
private static function getPsrRequest()
{
$headers = array(
'Host' => 'example.com',
'Content-Type' => 'application/json',
'Digest' => 'SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=',
'Content-Length' => 18,
'Date' => 'Sun, 05 Jan 2014 21:31:40 GMT'
);
$body = '{"hello": "world"}';
return new PsrRequest(
'POST', 'https://example.com/foo?param=value&pet=dog', $headers, $body
);
}
public function testItVerifies()
{
$testCases = array(
@ -164,7 +180,7 @@ G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
) );
$this->httpSignatureService = new HttpSignatureService( $dateTimeProvider );
}
$request = self::getRequest();
$request = self::getSymfonyRequest();
foreach ( $testCase['headers'] as $header => $value ) {
$request->headers->set( $header, $value );
}
@ -198,7 +214,7 @@ G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
),
);
foreach ( $testCases as $testCase ) {
$request = self::getRequest();
$request = self::getPsrRequest();
if ( array_key_exists( 'headers', $testCase ) ) {
$actual = $this->httpSignatureService->sign(
$request, self::PRIVATE_KEY, $testCase['keyId'], $testCase['headers']