123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- <?php
- namespace Aws\Sns;
- use Aws\Sns\Exception\InvalidSnsMessageException;
- class MessageValidator
- {
- const SIGNATURE_VERSION_1 = '1';
- const SIGNATURE_VERSION_2 = '2';
-
- private $certClient;
-
- private $hostPattern;
-
- private static $defaultHostPattern
- = '/^sns\.[a-zA-Z0-9\-]{3,}\.amazonaws\.com(\.cn)?$/';
- private static function isLambdaStyle(Message $message)
- {
- return isset($message['SigningCertUrl']);
- }
- private static function convertLambdaMessage(Message $lambdaMessage)
- {
- $keyReplacements = [
- 'SigningCertUrl' => 'SigningCertURL',
- 'SubscribeUrl' => 'SubscribeURL',
- 'UnsubscribeUrl' => 'UnsubscribeURL',
- ];
- $message = clone $lambdaMessage;
- foreach ($keyReplacements as $lambdaKey => $canonicalKey) {
- if (isset($message[$lambdaKey])) {
- $message[$canonicalKey] = $message[$lambdaKey];
- unset($message[$lambdaKey]);
- }
- }
- return $message;
- }
-
- public function __construct(
- callable $certClient = null,
- $hostNamePattern = ''
- ) {
- $this->certClient = $certClient ?: function($certUrl) {
- return @ file_get_contents($certUrl);
- };
- $this->hostPattern = $hostNamePattern ?: self::$defaultHostPattern;
- }
-
- public function validate(Message $message)
- {
- if (self::isLambdaStyle($message)) {
- $message = self::convertLambdaMessage($message);
- }
-
- $this->validateUrl($message['SigningCertURL']);
- $certificate = call_user_func($this->certClient, $message['SigningCertURL']);
- if ($certificate === false) {
- throw new InvalidSnsMessageException(
- "Cannot get the certificate from \"{$message['SigningCertURL']}\"."
- );
- }
-
- $key = openssl_get_publickey($certificate);
- if (!$key) {
- throw new InvalidSnsMessageException(
- 'Cannot get the public key from the certificate.'
- );
- }
-
- $content = $this->getStringToSign($message);
- $signature = base64_decode($message['Signature']);
- $algo = ($message['SignatureVersion'] === self::SIGNATURE_VERSION_1 ? OPENSSL_ALGO_SHA1 : OPENSSL_ALGO_SHA256);
- if (openssl_verify($content, $signature, $key, $algo) !== 1) {
- throw new InvalidSnsMessageException(
- 'The message signature is invalid.'
- );
- }
- }
-
- public function isValid(Message $message)
- {
- try {
- $this->validate($message);
- return true;
- } catch (InvalidSnsMessageException $e) {
- return false;
- }
- }
-
- public function getStringToSign(Message $message)
- {
- static $signableKeys = [
- 'Message',
- 'MessageId',
- 'Subject',
- 'SubscribeURL',
- 'Timestamp',
- 'Token',
- 'TopicArn',
- 'Type',
- ];
- if ($message['SignatureVersion'] !== self::SIGNATURE_VERSION_1
- && $message['SignatureVersion'] !== self::SIGNATURE_VERSION_2) {
- throw new InvalidSnsMessageException(
- "The SignatureVersion \"{$message['SignatureVersion']}\" is not supported."
- );
- }
- $stringToSign = '';
- foreach ($signableKeys as $key) {
- if (isset($message[$key])) {
- $stringToSign .= "{$key}\n{$message[$key]}\n";
- }
- }
- return $stringToSign;
- }
-
- private function validateUrl($url)
- {
- $parsed = parse_url($url);
- if (empty($parsed['scheme'])
- || empty($parsed['host'])
- || $parsed['scheme'] !== 'https'
- || substr($url, -4) !== '.pem'
- || !preg_match($this->hostPattern, $parsed['host'])
- ) {
- throw new InvalidSnsMessageException(
- 'The certificate is located on an invalid domain.'
- );
- }
- }
- }
|