Skip to content

Commit

Permalink
Standartize error messages and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
tt1991 committed Nov 16, 2015
1 parent 1635f3a commit b84f9ef
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 93 deletions.
74 changes: 27 additions & 47 deletions src/Escher.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ public function authenticate($keyDB, array $serverVars = null, $requestBody = nu
$authElements->validateMandatorySignedHeaders($this->dateHeaderKey);
$authElements->validateHashAlgo();
$authElements->validateDates($helper, $this->clockSkew);
$authElements->validateHost($helper);
$authElements->validateCredentials($this->credentialScope);
$authElements->validateSignature($helper, $this, $keyDB, $vendorKey);
return $authElements->getAccessKeyId();
Expand Down Expand Up @@ -350,7 +349,7 @@ public function getAuthElements($vendorKey, $algoPrefix)
} else if($this->getRequestMethod() === 'GET' && isset($queryParams[$this->paramKey($vendorKey, 'Signature')])) {
return EscherAuthElements::parseFromQuery($headerList, $queryParams, $vendorKey, $algoPrefix);
}
throw new EscherException('Request has not been signed.');
throw new EscherException('Escher authentication is missing');
}

public function getTimeStamp()
Expand Down Expand Up @@ -483,7 +482,7 @@ public static function parseFromHeaders(array $headerList, $authHeaderKey, $date
$host = self::checkHost($headerList);

if (!isset($headerList[strtolower($dateHeaderKey)])) {
throw new EscherException('The '.$dateHeaderKey.' header is missing');
throw new EscherException('The '.strtolower($dateHeaderKey).' header is missing');
}

if (strtolower($dateHeaderKey) !== 'date') {
Expand All @@ -492,12 +491,9 @@ public static function parseFromHeaders(array $headerList, $authHeaderKey, $date
try {
$dateTime = new DateTime($headerList[strtolower($dateHeaderKey)], new DateTimeZone('GMT'));
} catch (Exception $ex) {
throw new EscherException('Invalid date format');
throw new EscherException('Invalid date header, expected format is: Wed, 04 Nov 2015 09:20:22 GMT');
}
}
if (!$dateTime) {
throw new EscherException('Invalid date format');
}
return new EscherAuthElements($elementParts, $accessKeyId, $shortDate, $credentialScope, $dateTime, $host, true);
}

Expand All @@ -509,40 +505,40 @@ public static function parseFromHeaders(array $headerList, $authHeaderKey, $date
*/
public static function parseAuthHeader($headerContent, $algoPrefix)
{
$parts = explode(' ', $headerContent);
if (count($parts) !== 4) {
$pattern = '/^' . $algoPrefix . '-HMAC-([A-Z0-9\,]+)(.*)' .
'Credential=([A-Za-z0-9\/\-_]+),(.*)' .
'SignedHeaders=([A-Za-z\-;]+),(.*)' .
'Signature=([0-9a-f]+)$/';

if (!preg_match($pattern, $headerContent, $matches)) {
throw new EscherException('Could not parse auth header');
}
return array(
'Algorithm' => self::match(self::algoPattern($algoPrefix), $parts[0]),
'Credentials' => self::match('Credential=([A-Za-z0-9\/\-_]+),', $parts[1]),
'SignedHeaders' => self::match('SignedHeaders=([A-Za-z\-;]+),', $parts[2]),
'Signature' => self::match('Signature=([0-9a-f]+)', $parts[3]),
'Algorithm' => $matches[1],
'Credentials' => $matches[3],
'SignedHeaders' => $matches[5],
'Signature' => $matches[7],
);
}

private static function match($pattern, $part)
{
if (!preg_match("/^$pattern$/", $part, $matches)) {
throw new EscherException('Could not parse auth header');
}
return $matches[1];
}

public static function parseFromQuery($headerList, $queryParams, $vendorKey, $algoPrefix)
{
$elementParts = array();
$paramKey = self::checkParam($queryParams, $vendorKey, 'Algorithm');
$elementParts['Algorithm'] = self::match(self::algoPattern($algoPrefix), $queryParams[$paramKey]);

$pattern = '/^' . $algoPrefix . '-HMAC-([A-Z0-9\,]+)$/';
if (!preg_match($pattern, $queryParams[$paramKey], $matches))
{
throw new EscherException('invalid ' . $paramKey . ' query key format');
}
$elementParts['Algorithm'] = $matches[1];

foreach (self::basicQueryParamKeys() as $paramId) {
$paramKey = self::checkParam($queryParams, $vendorKey, $paramId);
$elementParts[$paramId] = $queryParams[$paramKey];
}
list($accessKeyId, $shortDate, $credentialScope) = explode('/', $elementParts['Credentials'], 3);
$dateTime = EscherUtils::parseLongDate($elementParts['Date']);
if (!$dateTime) {
throw new EscherException('Invalid date format');
}
return new EscherAuthElements($elementParts, $accessKeyId, $shortDate, $credentialScope, $dateTime, self::checkHost($headerList), false);
}

Expand All @@ -557,15 +553,6 @@ private static function basicQueryParamKeys()
);
}

/**
* @param $algoPrefix
* @return string
*/
private static function algoPattern($algoPrefix)
{
return $algoPrefix . '-HMAC-([A-Z0-9\,]+)';
}

/**
* @param $queryParams
* @param $vendorKey
Expand All @@ -577,7 +564,7 @@ private static function checkParam($queryParams, $vendorKey, $paramId)
{
$paramKey = 'X-' . $vendorKey . '-' . $paramId;
if (!isset($queryParams[$paramKey])) {
throw new EscherException('Missing query parameter: ' . $paramKey);
throw new EscherException('Query key is missing: ' . $paramKey);
}
return $paramKey;
}
Expand All @@ -594,25 +581,18 @@ public function validateDates(EscherRequestHelper $helper, $clockSkew)
{
$shortDate = $this->dateTime->format('Ymd');
if ($shortDate !== $this->getShortDate()) {
throw new EscherException('The authorization header\'s shortDate does not match with the request date');
throw new EscherException('Invalid date in authorization header, it should equal with header');
}

if (!$this->isInAcceptableInterval($helper->getTimeStamp(), EscherUtils::getTimeStampOfDateTime($this->dateTime), $clockSkew)) {
throw new EscherException('The request date is not within the accepted time range');
}
}

public function validateHost(EscherRequestHelper $helper)
{
if($helper->getServerHost() !== $this->getHost()) {
throw new EscherException('The Host header does not match: ' . $this->getHost() . ' != ' . $helper->getServerHost());
}
}

public function validateCredentials($credentialScope)
{
if (!$this->checkCredentials($credentialScope)) {
throw new EscherException('The credential scope is invalid');
throw new EscherException('Invalid Credential Scope');
}
}

Expand Down Expand Up @@ -654,7 +634,7 @@ public function validateHashAlgo()
{
if(!in_array(strtoupper($this->getAlgorithm()), array('SHA256','SHA512')))
{
throw new EscherException('Only SHA256 and SHA512 hash algorithms are allowed.');
throw new EscherException('Invalid hash algorithm, only SHA256 and SHA512 are allowed');
}
}

Expand All @@ -669,7 +649,7 @@ public function validateMandatorySignedHeaders($dateHeaderKey)
throw new EscherException('The host header is not signed');
}
if ($this->isFromHeaders && !in_array(strtolower($dateHeaderKey), $signedHeaders)) {
throw new EscherException('The date header is not signed');
throw new EscherException('The ' . strtolower($dateHeaderKey) . ' header is not signed');
}
}

Expand Down Expand Up @@ -912,7 +892,7 @@ class EscherUtils
public static function parseLongDate($dateString)
{
if (!preg_match('/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z$/', $dateString)) {
throw new EscherException('Invalid date format');
throw new EscherException('Invalid date header, expected format is: 20151104T092022Z');
}
if (!self::advancedDateTimeFunctionsAvailable()) {
return new DateTime($dateString, new DateTimeZone('GMT'));
Expand Down
50 changes: 4 additions & 46 deletions test/unit/AuthenticateRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,14 @@ public function itShouldFailToValidateInvalidRequests($tamperedKey, $tamperedVal
public function requestTamperingProvider()
{
return array(
'wrong date' => array('HTTP_X_EMS_DATE', 'INVALIDDATE', 'Invalid date format'),
'wrong date' => array('HTTP_X_EMS_DATE', 'INVALIDDATE', 'Invalid date header, expected format is: 20151104T092022Z'),
'wrong request time' => array('REQUEST_TIME', '20110909T113600Z', 'The request date is not within the accepted time range'),
'wrong host' => array('HTTP_HOST', 'example.com', 'The Host header does not match: example.com != iam.amazonaws.com'),
'wrong auth header' => array('HTTP_X_EMS_AUTH', 'Malformed auth header', 'Could not parse auth header'),
'tampered signature' => array('HTTP_X_EMS_AUTH', 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 'The signatures do not match'),
'wrong hash algo' => array('HTTP_X_EMS_AUTH', 'EMS-HMAC-SHA123 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', 'Only SHA256 and SHA512 hash algorithms are allowed'),
'wrong hash algo' => array('HTTP_X_EMS_AUTH', 'EMS-HMAC-SHA123 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', 'Invalid hash algorithm, only SHA256 and SHA512 are allowed'),
'host not signed' => array('HTTP_X_EMS_AUTH', 'EMS-HMAC-SHA123 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', 'The host header is not signed'),
'date not signed' => array('HTTP_X_EMS_AUTH', 'EMS-HMAC-SHA123 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', 'The date header is not signed'),
'invalid credential' => array('HTTP_X_EMS_AUTH', 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-2/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', 'The credential scope is invalid'),
'date not signed' => array('HTTP_X_EMS_AUTH', 'EMS-HMAC-SHA123 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', 'The x-ems-date header is not signed'),
'invalid credential' => array('HTTP_X_EMS_AUTH', 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-2/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', 'Invalid Credential Scope'),
'invalid Escher key' => array('HTTP_X_EMS_AUTH', 'EMS-HMAC-SHA256 Credential=FOOBAR/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd', 'Invalid Escher key'),
);
}
Expand Down Expand Up @@ -172,47 +171,6 @@ public function itShouldFailToValidateInvalidQueryStrings()
$this->createEscher('us-east-1/host/aws4_request')->authenticate($keyDB, $serverVars, '');
}

/**
* @test
* @dataProvider invalidPortProvider
*/
public function itShouldFailToAuthenticateWrongPort($httpHost, $serverName, $serverPort, $https)
{
$serverVars = array(
'HTTP_X_EMS_DATE' => '20110909T233600Z',
'HTTP_X_EMS_AUTH' => 'EMS-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-ems-date, Signature=f36c21c6e16a71a6e8dc56673ad6354aeef49c577a22fd58a190b5fcf8891dbd',
'REQUEST_TIME' => $this->strtotime('20110909T233600Z'),
'REQUEST_METHOD' => 'POST',
'HTTP_HOST' => $httpHost,
'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8',
'REQUEST_URI' => '/',
'HTTPS' => $https,
'SERVER_PORT' => $serverPort,
'SERVER_NAME' => $serverName,
);
$keyDB = array('AKIDEXAMPLE' => 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY');
try
{
$this->createEscher('us-east-1/iam/aws4_request')
->authenticate($keyDB, $serverVars, 'Action=ListUsers&Version=2010-05-08');
$this->fail('Should fail to validate!');
}
catch (EscherException $e)
{
$this->assertStringStartsWith('The Host header does not match', $e->getMessage());
}
}

public function invalidPortProvider()
{
return array (
'server on default http port, request to something else' => array('iam.amazonaws.com:123', 'iam.amazonaws.com', '80', ''),
'server on default https port, request to something else' => array('iam.amazonaws.com:123', 'iam.amazonaws.com', '443', 'on'),
'server on default http port, request to default https port' => array('iam.amazonaws.com:443', 'iam.amazonaws.com', '80', ''),
'server on default https port, request to default http port' => array('iam.amazonaws.com:80', 'iam.amazonaws.com', '443', 'on')
);
}

private function strtotime($dateString)
{
return EscherUtils::parseLongDate($dateString)->format('U');
Expand Down

0 comments on commit b84f9ef

Please sign in to comment.