fork
This commit is contained in:
commit
cefeda7908
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
$finder = Symfony\CS\Finder\DefaultFinder::create()
|
||||
->in(__DIR__ . "/src");
|
||||
|
||||
return Symfony\CS\Config\Config::create()
|
||||
->level(\Symfony\CS\FixerInterface::PSR2_LEVEL)
|
||||
->fixers([
|
||||
'unused_use',
|
||||
'remove_lines_between_uses',
|
||||
'remove_leading_slash_use',
|
||||
'ordered_use',
|
||||
'short_array_syntax',
|
||||
'whitespacy_lines',
|
||||
'ternary_spaces',
|
||||
'standardize_not_equal',
|
||||
'spaces_cast',
|
||||
'extra_empty_lines',
|
||||
])
|
||||
->finder($finder);
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2015 99designs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"name": "99designs/http-signatures",
|
||||
"description": "Sign and verify HTTP messages",
|
||||
"keywords": ["http", "https", "signing", "signed", "signature", "hmac"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paul Annesley",
|
||||
"email": "paul@99designs.com"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"HttpSignatures\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"HttpSignatures\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.5",
|
||||
"paragonie/random_compat": "^1.0|^2.0",
|
||||
"psr/http-message": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^1.11",
|
||||
"guzzlehttp/psr7": "^1.2",
|
||||
"phpunit/phpunit": "~4.8",
|
||||
"symfony/http-foundation": "~2.8|~3.0",
|
||||
"symfony/psr-http-message-bridge": "^1.0",
|
||||
"zendframework/zend-diactoros": "^1.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace HttpSignatures;
|
||||
|
||||
abstract class Algorithm
|
||||
{
|
||||
/**
|
||||
* @param string $name
|
||||
* @return HmacAlgorithm
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function create($name)
|
||||
{
|
||||
switch ($name) {
|
||||
case 'hmac-sha1':
|
||||
return new HmacAlgorithm('sha1');
|
||||
break;
|
||||
case 'hmac-sha256':
|
||||
return new HmacAlgorithm('sha256');
|
||||
break;
|
||||
default:
|
||||
throw new Exception("No algorithm named '$name'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace HttpSignatures;
|
||||
|
||||
interface AlgorithmInterface
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function name();
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
public function sign($key, $data);
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
namespace HttpSignatures;
|
||||
|
||||
class Context
|
||||
{
|
||||
/** @var array */
|
||||
private $headers;
|
||||
|
||||
/** @var KeyStoreInterface */
|
||||
private $keyStore;
|
||||
|
||||
/** @var array */
|
||||
private $keys;
|
||||
|
||||
/** @var string */
|
||||
private $signingKeyId;
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct($args)
|
||||
{
|
||||
if (isset($args['keys']) && isset($args['keyStore'])) {
|
||||
throw new Exception(__CLASS__.' accepts keys or keyStore but not both');
|
||||
} elseif (isset($args['keys'])) {
|
||||
// array of keyId => keySecret
|
||||
$this->keys = $args['keys'];
|
||||
} elseif (isset($args['keyStore'])) {
|
||||
$this->setKeyStore($args['keyStore']);
|
||||
}
|
||||
|
||||
// algorithm for signing; not necessary for verifying.
|
||||
if (isset($args['algorithm'])) {
|
||||
$this->algorithmName = $args['algorithm'];
|
||||
}
|
||||
|
||||
// headers list for signing; not necessary for verifying.
|
||||
if (isset($args['headers'])) {
|
||||
$this->headers = $args['headers'];
|
||||
}
|
||||
|
||||
// signingKeyId specifies the key used for signing messages.
|
||||
if (isset($args['signingKeyId'])) {
|
||||
$this->signingKeyId = $args['signingKeyId'];
|
||||
} elseif (isset($args['keys']) && count($args['keys']) === 1) {
|
||||
list($this->signingKeyId) = array_keys($args['keys']); // first key
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Signer
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function signer()
|
||||
{
|
||||
return new Signer(
|
||||
$this->signingKey(),
|
||||
$this->algorithm(),
|
||||
$this->headerList()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Verifier
|
||||
*/
|
||||
public function verifier()
|
||||
{
|
||||
return new Verifier($this->keyStore());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Key
|
||||
*
|
||||
* @throws Exception
|
||||
* @throws KeyStoreException
|
||||
*/
|
||||
private function signingKey()
|
||||
{
|
||||
if (isset($this->signingKeyId)) {
|
||||
return $this->keyStore()->fetch($this->signingKeyId);
|
||||
} else {
|
||||
throw new Exception('no implicit or specified signing key');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HmacAlgorithm
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function algorithm()
|
||||
{
|
||||
return Algorithm::create($this->algorithmName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HeaderList
|
||||
*/
|
||||
private function headerList()
|
||||
{
|
||||
return new HeaderList($this->headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return KeyStore
|
||||
*/
|
||||
private function keyStore()
|
||||
{
|
||||
if (empty($this->keyStore)) {
|
||||
$this->keyStore = new KeyStore($this->keys);
|
||||
}
|
||||
|
||||
return $this->keyStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param KeyStoreInterface $keyStore
|
||||
*/
|
||||
private function setKeyStore(KeyStoreInterface $keyStore)
|
||||
{
|
||||
$this->keyStore = $keyStore;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace HttpSignatures;
|
||||
|
||||
class Exception extends \Exception
|
||||
{
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace HttpSignatures;
|
||||
|
||||
class HeaderList
|
||||
{
|
||||
/** @var array */
|
||||
public $names;
|
||||
|
||||
/**
|
||||
* @param array $names
|
||||
*/
|
||||
public function __construct(array $names)
|
||||
{
|
||||
$this->names = array_map(
|
||||
[$this, 'normalize'],
|
||||
$names
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $string
|
||||
*
|
||||
* @return HeaderList
|
||||
*/
|
||||
public static function fromString($string)
|
||||
{
|
||||
return new static(explode(' ', $string));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function string()
|
||||
{
|
||||
return implode(' ', $this->names);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function normalize($name)
|
||||
{
|
||||
return strtolower($name);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace HttpSignatures;
|
||||
|
||||
class HmacAlgorithm implements AlgorithmInterface
|
||||
{
|
||||
/** @var string */
|
||||
private $digestName;
|
||||
|
||||
/**
|
||||
* @param string $digestName
|
||||
*/
|
||||
public function __construct($digestName)
|
||||
{
|
||||
$this->digestName = $digestName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function name()
|
||||
{
|
||||
return sprintf('hmac-%s', $this->digestName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param string $data
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function sign($key, $data)
|
||||
{
|
||||
return hash_hmac($this->digestName, $data, $key, true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace HttpSignatures;
|
||||
|
||||
class Key
|
||||
{
|
||||
/** @var string */
|
||||
public $id;
|
||||
|
||||
/** @var string */
|
||||
public $secret;
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param string $secret
|
||||
*/
|
||||
public function __construct($id, $secret)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->secret = $secret;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace HttpSignatures;
|
||||
|
||||
class KeyStore implements KeyStoreInterface
|
||||
{
|
||||
/** @var Key[] */
|
||||
private $keys;
|
||||
|
||||
/**
|
||||
* @param array $keys
|
||||
*/
|
||||
public function __construct($keys)
|
||||
{
|
||||
$this->keys = [];
|
||||
foreach ($keys as $id => $secret) {
|
||||
$this->keys[$id] = new Key($id, $secret);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $keyId
|
||||
*
|
||||
* @return Key
|
||||
*
|
||||
* @throws KeyStoreException
|
||||
*/
|
||||
public function fetch($keyId)
|
||||
{
|
||||
if (isset($this->keys[$keyId])) {
|
||||
return $this->keys[$keyId];
|
||||
} else {
|
||||
throw new KeyStoreException("Key '$keyId' not found");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace HttpSignatures;
|
||||
|
||||
class KeyStoreException extends Exception
|
||||
{
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace HttpSignatures;
|
||||
|
||||
interface KeyStoreInterface
|
||||
{
|
||||
/**
|
||||
* return the secret for the specified $keyId.
|
||||
*
|
||||
* @param string $keyId
|
||||
*
|
||||
* @return Key
|
||||
*/
|
||||
public function fetch($keyId);
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace HttpSignatures;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
class Signature
|
||||
{
|
||||
/** @var Key */
|
||||
private $key;
|
||||
|
||||
/** @var HmacAlgorithm */
|
||||
private $algorithm;
|
||||
|
||||
/** @var SigningString */
|
||||
private $signingString;
|
||||
|
||||
/**
|
||||
* @param RequestInterface $message
|
||||
* @param Key $key
|
||||
* @param AlgorithmInterface $algorithm
|
||||
* @param HeaderList $headerList
|
||||
*/
|
||||
public function __construct($message, Key $key, AlgorithmInterface $algorithm, HeaderList $headerList)
|
||||
{
|
||||
$this->key = $key;
|
||||
$this->algorithm = $algorithm;
|
||||
$this->signingString = new SigningString($headerList, $message);
|
||||
}
|
||||
|
||||
public function string()
|
||||
{
|
||||
return $this->algorithm->sign(
|
||||
$this->key->secret,
|
||||
$this->signingString->string()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace HttpSignatures;
|
||||
|
||||
class SignatureParameters
|
||||
{
|
||||
/**
|
||||
* @param Key $key
|
||||
* @param AlgorithmInterface $algorithm
|
||||
* @param HeaderList $headerList
|
||||
* @param Signature $signature
|
||||
*/
|
||||
public function __construct($key, $algorithm, $headerList, $signature)
|
||||
{
|
||||
$this->key = $key;
|
||||
$this->algorithm = $algorithm;
|
||||
$this->headerList = $headerList;
|
||||
$this->signature = $signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function string()
|
||||
{
|
||||
return implode(',', $this->parameterComponents());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function parameterComponents()
|
||||
{
|
||||
return [
|
||||
sprintf('keyId="%s"', $this->key->id),
|
||||
sprintf('algorithm="%s"', $this->algorithm->name()),
|
||||
sprintf('headers="%s"', $this->headerList->string()),
|
||||
sprintf('signature="%s"', $this->signatureBase64()),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function signatureBase64()
|
||||
{
|
||||
return base64_encode($this->signature->string());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
namespace HttpSignatures;
|
||||
|
||||
class SignatureParametersParser
|
||||
{
|
||||
/** @var string */
|
||||
private $input;
|
||||
|
||||
/**
|
||||
* @param string $input
|
||||
*/
|
||||
public function __construct($input)
|
||||
{
|
||||
$this->input = $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function parse()
|
||||
{
|
||||
$result = $this->pairsToAssociative(
|
||||
$this->arrayOfPairs()
|
||||
);
|
||||
$this->validate($result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $pairs
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function pairsToAssociative($pairs)
|
||||
{
|
||||
$result = [];
|
||||
foreach ($pairs as $pair) {
|
||||
$result[$pair[0]] = $pair[1];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function arrayOfPairs()
|
||||
{
|
||||
return array_map(
|
||||
[$this, 'pair'],
|
||||
$this->segments()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function segments()
|
||||
{
|
||||
return explode(',', $this->input);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $segment
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws SignatureParseException
|
||||
*/
|
||||
private function pair($segment)
|
||||
{
|
||||
$segmentPattern = '/\A(keyId|algorithm|headers|signature)="(.*)"\z/';
|
||||
$matches = [];
|
||||
$result = preg_match($segmentPattern, $segment, $matches);
|
||||
if ($result !== 1) {
|
||||
throw new SignatureParseException("Signature parameters segment '$segment' invalid");
|
||||
}
|
||||
array_shift($matches);
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $result
|
||||
*
|
||||
* @throws SignatureParseException
|
||||
*/
|
||||
private function validate($result)
|
||||
{
|
||||
$this->validateAllKeysArePresent($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $result
|
||||
*
|
||||
* @throws SignatureParseException
|
||||
*/
|
||||
private function validateAllKeysArePresent($result)
|
||||
{
|
||||
// Regexp in pair() ensures no unwanted keys exist.
|
||||
// Ensure that all wanted keys exist.
|
||||
$wanted = ['keyId', 'algorithm', 'headers', 'signature'];
|
||||
$missing = array_diff($wanted, array_keys($result));
|
||||
if (!empty($missing)) {
|
||||
$csv = implode(', ', $missing);
|
||||
throw new SignatureParseException("Missing keys $csv");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace HttpSignatures;
|
||||
|
||||
class SignatureParseException extends Exception
|
||||
{
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace HttpSignatures;
|
||||
|
||||
class SignedHeaderNotPresentException extends Exception
|
||||
{
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace HttpSignatures;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
class Signer
|
||||
{
|
||||
/** @var Key */
|
||||
private $key;
|
||||
|
||||
/** @var HmacAlgorithm */
|
||||
private $algorithm;
|
||||
|
||||
/** @var HeaderList */
|
||||
private $headerList;
|
||||
|
||||
/**
|
||||
* @param Key $key
|
||||
* @param HmacAlgorithm $algorithm
|
||||
* @param HeaderList $headerList
|
||||
*/
|
||||
public function __construct($key, $algorithm, $headerList)
|
||||
{
|
||||
$this->key = $key;
|
||||
$this->algorithm = $algorithm;
|
||||
$this->headerList = $headerList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestInterface $message
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public function sign($message)
|
||||
{
|
||||
$signatureParameters = $this->signatureParameters($message);
|
||||
$message = $message->withAddedHeader("Signature", $signatureParameters->string());
|
||||
$message = $message->withAddedHeader("Authorization", "Signature " . $signatureParameters->string());
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestInterface $message
|
||||
* @return SignatureParameters
|
||||
*/
|
||||
private function signatureParameters($message)
|
||||
{
|
||||
return new SignatureParameters(
|
||||
$this->key,
|
||||
$this->algorithm,
|
||||
$this->headerList,
|
||||
$this->signature($message)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestInterface $message
|
||||
* @return Signature
|
||||
*/
|
||||
private function signature($message)
|
||||
{
|
||||
return new Signature(
|
||||
$message,
|
||||
$this->key,
|
||||
$this->algorithm,
|
||||
$this->headerList
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
namespace HttpSignatures;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
class SigningString
|
||||
{
|
||||
/** @var HeaderList */
|
||||
private $headerList;
|
||||
|
||||
/** @var RequestInterface */
|
||||
private $message;
|
||||
|
||||
/**
|
||||
* @param HeaderList $headerList
|
||||
* @param RequestInterface $message
|
||||
*/
|
||||
public function __construct(HeaderList $headerList, $message)
|
||||
{
|
||||
$this->headerList = $headerList;
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function string()
|
||||
{
|
||||
return implode("\n", $this->lines());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function lines()
|
||||
{
|
||||
return array_map(
|
||||
[$this, 'line'],
|
||||
$this->headerList->names
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return string
|
||||
* @throws SignedHeaderNotPresentException
|
||||
*/
|
||||
private function line($name)
|
||||
{
|
||||
if ($name == '(request-target)') {
|
||||
return $this->requestTargetLine();
|
||||
} else {
|
||||
return sprintf('%s: %s', $name, $this->headerValue($name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return string
|
||||
* @throws SignedHeaderNotPresentException
|
||||
*/
|
||||
private function headerValue($name)
|
||||
{
|
||||
if ($this->message->hasHeader($name)) {
|
||||
$header = $this->message->getHeader($name);
|
||||
return end($header);
|
||||
} else {
|
||||
throw new SignedHeaderNotPresentException("Header '$name' not in message");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function requestTargetLine()
|
||||
{
|
||||
return sprintf(
|
||||
'(request-target): %s %s',
|
||||
strtolower($this->message->getMethod()),
|
||||
$this->message->getRequestTarget()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
<?php
|
||||
|
||||
namespace HttpSignatures;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
class Verification
|
||||
{
|
||||
/** @var RequestInterface */
|
||||
private $message;
|
||||
|
||||
/** @var KeyStoreInterface */
|
||||
private $keyStore;
|
||||
|
||||
/** @var array */
|
||||
private $_parameters;
|
||||
|
||||
/**
|
||||
* @param RequestInterface $message
|
||||
* @param KeyStoreInterface $keyStore
|
||||
*/
|
||||
public function __construct($message, KeyStoreInterface $keyStore)
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->keyStore = $keyStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid()
|
||||
{
|
||||
return $this->hasSignatureHeader() && $this->signatureMatches();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function signatureMatches()
|
||||
{
|
||||
try {
|
||||
$random = random_bytes(32);
|
||||
return hash_hmac('sha256', $this->expectedSignatureBase64(), $random, true) === hash_hmac('sha256', $this->providedSignatureBase64(), $random, true);
|
||||
} catch (SignatureParseException $e) {
|
||||
return false;
|
||||
} catch (KeyStoreException $e) {
|
||||
return false;
|
||||
} catch (SignedHeaderNotPresentException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function expectedSignatureBase64()
|
||||
{
|
||||
return base64_encode($this->expectedSignature()->string());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Signature
|
||||
*/
|
||||
private function expectedSignature()
|
||||
{
|
||||
return new Signature(
|
||||
$this->message,
|
||||
$this->key(),
|
||||
$this->algorithm(),
|
||||
$this->headerList()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function providedSignatureBase64()
|
||||
{
|
||||
return $this->parameter('signature');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Key
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function key()
|
||||
{
|
||||
return $this->keyStore->fetch($this->parameter('keyId'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HmacAlgorithm
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function algorithm()
|
||||
{
|
||||
return Algorithm::create($this->parameter('algorithm'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HeaderList
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function headerList()
|
||||
{
|
||||
return HeaderList::fromString($this->parameter('headers'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function parameter($name)
|
||||
{
|
||||
$parameters = $this->parameters();
|
||||
if (!isset($parameters[$name])) {
|
||||
throw new Exception("Signature parameters does not contain '$name'");
|
||||
}
|
||||
|
||||
return $parameters[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function parameters()
|
||||
{
|
||||
if (!isset($this->_parameters)) {
|
||||
$parser = new SignatureParametersParser($this->signatureHeader());
|
||||
$this->_parameters = $parser->parse();
|
||||
}
|
||||
|
||||
return $this->_parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function hasSignatureHeader()
|
||||
{
|
||||
return $this->message->hasHeader('Signature') || $this->message->hasHeader('Authorization');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function signatureHeader()
|
||||
{
|
||||
if ($signature = $this->fetchHeader('Signature')) {
|
||||
return $signature;
|
||||
} elseif ($authorization = $this->fetchHeader('Authorization')) {
|
||||
return substr($authorization, strlen('Signature '));
|
||||
} else {
|
||||
throw new Exception('HTTP message has no Signature or Authorization header');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function fetchHeader($name)
|
||||
{
|
||||
// grab the most recently set header.
|
||||
$header = $this->message->getHeader($name);
|
||||
return end($header);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace HttpSignatures;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
class Verifier
|
||||
{
|
||||
/** @var KeyStoreInterface */
|
||||
private $keyStore;
|
||||
|
||||
/**
|
||||
* @param KeyStoreInterface $keyStore
|
||||
*/
|
||||
public function __construct(KeyStoreInterface $keyStore)
|
||||
{
|
||||
$this->keyStore = $keyStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestInterface $message
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($message)
|
||||
{
|
||||
$verification = new Verification($message, $this->keyStore);
|
||||
|
||||
return $verification->isValid();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue