Skip to content

Commit

Permalink
Adding stricter validation of SNS SigningCertURL
Browse files Browse the repository at this point in the history
Updating the SNS MessageValidator to ensure that the SigningCertURL
is from a trusted source: the host is a known AWS host, the scheme
is http or https, and the file extension is ".pem".
  • Loading branch information
mtdowling committed Jul 31, 2014
1 parent bbdcd2d commit adc5fea
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 10 deletions.
17 changes: 14 additions & 3 deletions src/Aws/Sns/MessageValidator/MessageValidator.php
Expand Up @@ -64,9 +64,7 @@ public function validate(Message $message)
{
// Get the cert's URL and ensure it is from AWS
$certUrl = Url::factory($message->get('SigningCertURL'));
if ('.amazonaws.com' != substr($certUrl->getHost(), -14)) {
throw new CertificateFromUnrecognizedSourceException();
}
$this->validateUrl($certUrl);

// Get the cert itself and extract the public key
$certificate = $this->client->get((string) $certUrl)->send()->getBody();
Expand All @@ -83,6 +81,19 @@ public function validate(Message $message)
}
}

private function validateUrl(Url $url)
{
// The host must match the following pattern
$hostPattern = '/^sns\.[a-zA-Z0-9\-]{3,}\.amazonaws\.com(\.cn)?$/';

if ($url->getScheme() !== 'https' ||
substr($url, -4) !== '.pem' ||
!preg_match($hostPattern, $url->getHost())
) {
throw new CertificateFromUnrecognizedSourceException();
}
}

/**
* Determines if a message is valid and that is was delivered by AWS. This method does not throw exceptions and
* returns a simple boolean value.
Expand Down
47 changes: 40 additions & 7 deletions tests/Aws/Tests/Sns/MessageValidator/MessageValidatorTest.php
Expand Up @@ -16,9 +16,11 @@

namespace Aws\Tests\Sns\MessageValidator;

use Aws\Sns\MessageValidator\Exception\CertificateFromUnrecognizedSourceException;
use Aws\Sns\MessageValidator\Message;
use Aws\Sns\MessageValidator\MessageValidator;
use Guzzle\Common\Collection;
use Guzzle\Http\Url;
use Guzzle\Plugin\Mock\MockPlugin;
use Guzzle\Http\Message\Response;
use Guzzle\Http\Client;
Expand All @@ -42,13 +44,44 @@ public function testIsValidReturnsFalseOnFailedValidation()
$this->assertFalse($validator->isValid($message));
}

public function testValidateFailsWhenCertUrlDoesNotMatchAws()
public function urlProvider()
{
$this->setExpectedException('Aws\Sns\MessageValidator\Exception\CertificateFromUnrecognizedSourceException');
return array(
array('https://sns.us-east-1.amazonaws.com/key.pem', false),
array('https://sns.us-west-2.amazonaws.com/key.pem', false),
array('https://sns.us-west-2.amazonaws.com/key.pem', false),
array('https://sns.cn-north-1.amazonaws.com.cn/SimpleNotificationService-foo.pem', false),
array('https://sns.us-gov-west-1.amazonaws.com/abc.pem', false),
// Failure cases
array('http://sns.us-gov-west-1.amazonaws.com/abc.pem', true),
array('https://sns.us-gov-west-1.amazonaws.com/abc', true),
array('https://sns.amazonaws.com/abc', true),
array('https://s3.sns.amazonaws.com/abc.pem', true),
array('https://s3.amazonaws.com/sns/abc.pem', true),
array('https://sns.us-west-2.amazon.com/key.pem', true),
array('https://sns.s3.amazonaws.com/key.pem', true),
);
}

/**
* @dataProvider urlProvider
*/
public function testValidateFailsWhenCertUrlDoesNotMatchAws($url, $throws)
{
$validator = new MessageValidator();
$message = new Message(new Collection());
$validator->validate($message);
$m = new \ReflectionMethod($validator, 'validateUrl');
$m->setAccessible(true);

try {
$m->invoke($validator, Url::factory($url));
if ($throws) {
$this->fail('URL was invalid but did not fail the test');
}
} catch (CertificateFromUnrecognizedSourceException $e) {
if (!$throws) {
$this->fail('URL was supposed to be valid, but failed test');
}
}
}

public function testValidateFailsWhenCannotDeterminePublicKey()
Expand All @@ -59,7 +92,7 @@ public function testValidateFailsWhenCannotDeterminePublicKey()
$client = $this->getMockClient();
$validator = new MessageValidator($client);

$message = new Message(new Collection(array('SigningCertURL' => 'https://foo.amazonaws.com/bar')));
$message = new Message(new Collection(array('SigningCertURL' => 'https://sns.foo.amazonaws.com/bar.pem')));
$validator->validate($message);
}

Expand All @@ -75,7 +108,7 @@ public function testValidateFailsWhenMessageIsInvalid()
$validator = new MessageValidator($client);

$message = new Message(new Collection(array(
'SigningCertURL' => 'https://foo.amazonaws.com/bar',
'SigningCertURL' => 'https://sns.foo.amazonaws.com/bar.pem',
'Signature' => $signature,
)));
$validator->validate($message);
Expand All @@ -90,7 +123,7 @@ public function testValidateSucceedsWhenMessageIsValid()
'Timestamp' => time(),
'TopicArn' => 'baz',
'Type' => 'Notification',
'SigningCertURL' => 'https://foo.amazonaws.com/bar',
'SigningCertURL' => 'https://sns.us-west-2.amazonaws.com/bar.pem',
'Signature' => '',
));

Expand Down

0 comments on commit adc5fea

Please sign in to comment.