Skip to content

Commit cf4c08c

Browse files
committed
Add signature version check
1 parent 03ca320 commit cf4c08c

File tree

2 files changed

+76
-31
lines changed

2 files changed

+76
-31
lines changed

src/MessageValidator.php

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
*/
99
class MessageValidator
1010
{
11+
const SUPPORTED_SIGNATURE_VERSION = '1';
12+
1113
/**
1214
* @var callable
1315
*/
@@ -45,7 +47,8 @@ public function __construct(callable $remoteFileReader = null)
4547
*/
4648
public function validate(Message $message)
4749
{
48-
// Get and validate the URL for the certificate.
50+
$this->validateSignatureVersion($message->get('SignatureVersion'));
51+
4952
$certUrl = $message->get('SigningCertURL');
5053
$this->validateUrl($certUrl);
5154

@@ -106,8 +109,18 @@ private function validateUrl($url)
106109
|| substr($url, -4) !== '.pem'
107110
|| !preg_match($hostPattern, $parsed['host'])
108111
) {
109-
throw new MessageValidatorException('The certificate is located '
110-
. 'on an invalid domain.');
112+
throw new MessageValidatorException(
113+
'The certificate is located on an invalid domain.'
114+
);
115+
}
116+
}
117+
118+
private function validateSignatureVersion($version)
119+
{
120+
if ($version !== self::SUPPORTED_SIGNATURE_VERSION) {
121+
throw new MessageValidatorException(
122+
"Only v1 signatures can be validated; v{$version} provided"
123+
);
111124
}
112125
}
113126
}

tests/MessageValidatorTest.php

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,29 @@
33
namespace Aws\Sns;
44

55
/**
6-
* @covers \Aws\Sns\MessageValidator
6+
* @covers MessageValidator
77
*/
88
class MessageValidatorTest extends \PHPUnit_Framework_TestCase
99
{
1010
const VALID_CERT_URL = "https://sns.foo.amazonaws.com/bar.pem";
1111

12-
protected function setUp()
12+
private static $pKey;
13+
14+
private static $certificate;
15+
16+
public static function setUpBeforeClass()
17+
{
18+
self::$pKey = openssl_pkey_new();
19+
$csr = openssl_csr_new([], self::$pKey);
20+
$x509 = openssl_csr_sign($csr, null, self::$pKey, 1);
21+
22+
openssl_x509_export($x509, self::$certificate);
23+
openssl_x509_free($x509);
24+
}
25+
26+
public static function tearDownAfterClass()
1327
{
14-
if (!extension_loaded('openssl')) {
15-
$this->markTestSkipped('The OpenSSL extension is required to run '
16-
. 'the tests for MessageValidator.');
17-
}
28+
openssl_pkey_free(self::$pKey);
1829
}
1930

2031
public function testIsValidReturnsFalseOnFailedValidation()
@@ -24,6 +35,19 @@ public function testIsValidReturnsFalseOnFailedValidation()
2435
$this->assertFalse($validator->isValid($message));
2536
}
2637

38+
/**
39+
* @expectedException \Aws\Sns\MessageValidatorException
40+
* @expectedExceptionMessage Only v1 signatures can be validated; v2 provided
41+
*/
42+
public function testValidateFailsWhenSignatureVersionIsInvalid()
43+
{
44+
$validator = new MessageValidator();
45+
$message = new Message([
46+
'SignatureVersion' => '2',
47+
]);
48+
$validator->validate($message);
49+
}
50+
2751
/**
2852
* @expectedException \Aws\Sns\MessageValidatorException
2953
* @expectedExceptionMessage The certificate is located on an invalid domain.
@@ -32,7 +56,8 @@ public function testValidateFailsWhenCertUrlInvalid()
3256
{
3357
$validator = new MessageValidator();
3458
$message = new Message([
35-
'SigningCertURL' => 'https://foo.amazonaws.com/bar'
59+
'SigningCertURL' => 'https://foo.amazonaws.com/bar',
60+
'SignatureVersion' => '1',
3661
]);
3762
$validator->validate($message);
3863
}
@@ -43,9 +68,10 @@ public function testValidateFailsWhenCertUrlInvalid()
4368
*/
4469
public function testValidateFailsWhenCannotDeterminePublicKey()
4570
{
46-
$validator = new MessageValidator($this->getMockClient(''));
71+
$validator = new MessageValidator($this->getMockHttpClient(''));
4772
$message = new Message([
48-
'SigningCertURL' => self::VALID_CERT_URL
73+
'SigningCertURL' => self::VALID_CERT_URL,
74+
'SignatureVersion' => '1',
4975
]);
5076
$validator->validate($message);
5177
}
@@ -57,13 +83,14 @@ public function testValidateFailsWhenCannotDeterminePublicKey()
5783
public function testValidateFailsWhenMessageIsInvalid()
5884
{
5985
// Get the signature for some dummy data
60-
list($signature, $certificate) = $this->getSignature('foo');
86+
$signature = $this->getSignature('foo');
6187
// Create the validator with a mock HTTP client that will respond with
6288
// the certificate
63-
$validator = new MessageValidator($this->getMockClient($certificate));
89+
$validator = new MessageValidator($this->getMockCertServerClient());
6490
$message = new Message([
6591
'SigningCertURL' => self::VALID_CERT_URL,
6692
'Signature' => $signature,
93+
'SignatureVersion' => '1',
6794
]);
6895
$validator->validate($message);
6996
}
@@ -79,43 +106,48 @@ public function testValidateSucceedsWhenMessageIsValid()
79106
'Type' => 'Notification',
80107
'SigningCertURL' => self::VALID_CERT_URL,
81108
'Signature' => ' ',
109+
'SignatureVersion' => '1',
82110
]);
83111

84112
// Get the signature for a real message
85-
list($signature, $certificate) = $this->getSignature($message->getStringToSign());
113+
$signature = $this->getSignature($message->getStringToSign());
86114
$ref = new \ReflectionProperty($message, 'data');
87115
$ref->setAccessible(true);
88-
$ref->setValue($message, ['Signature' => $signature] + $ref->getValue($message));
116+
$ref->setValue(
117+
$message,
118+
['Signature' => $signature] + $ref->getValue($message)
119+
);
89120

90121
// Create the validator with a mock HTTP client that will respond with
91122
// the certificate
92-
$validator = new MessageValidator($this->getMockClient($certificate));
123+
$validator = new MessageValidator($this->getMockCertServerClient());
93124

94125
// The message should validate
95126
$this->assertTrue($validator->isValid($message));
96127
}
97128

98-
protected function getMockClient($responseBody)
129+
protected function getMockHttpClient($responseBody)
99130
{
100-
return static function () use ($responseBody) {
131+
return function () use ($responseBody) {
101132
return $responseBody;
102133
};
103134
}
104135

136+
protected function getMockCertServerClient()
137+
{
138+
return function ($url) {
139+
if ($url !== self::VALID_CERT_URL) {
140+
return '';
141+
}
142+
143+
return self::$certificate;
144+
};
145+
}
146+
105147
protected function getSignature($stringToSign)
106148
{
107-
// Generate a new Certificate Signing Request and public/private keypair
108-
$csr = openssl_csr_new(array(), $keypair);
109-
// Create the self-signed certificate
110-
$x509 = openssl_csr_sign($csr, null, $keypair, 1);
111-
openssl_x509_export($x509, $certificate);
112-
// Create the signature
113-
$privateKey = openssl_get_privatekey($keypair);
114-
openssl_sign($stringToSign, $signature, $privateKey);
115-
// Free the openssl resources used
116-
openssl_pkey_free($keypair);
117-
openssl_x509_free($x509);
149+
openssl_sign($stringToSign, $signature, self::$pKey);
118150

119-
return [base64_encode($signature), $certificate];
151+
return base64_encode($signature);
120152
}
121153
}

0 commit comments

Comments
 (0)