| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 | 
							- <?php
 
- namespace Aws\Sns;
 
- use Aws\Sns\Exception\InvalidSnsMessageException;
 
- /**
 
-  * Uses openssl to verify SNS messages to ensure that they were sent by AWS.
 
-  */
 
- class MessageValidator
 
- {
 
-     const SIGNATURE_VERSION_1 = '1';
 
-     const SIGNATURE_VERSION_2 = '2';
 
-     /**
 
-      * @var callable Callable used to download the certificate content.
 
-      */
 
-     private $certClient;
 
-     /** @var string */
 
-     private $hostPattern;
 
-     /**
 
-      * @var string  A pattern that will match all regional SNS endpoints, e.g.:
 
-      *                  - sns.<region>.amazonaws.com        (AWS)
 
-      *                  - sns.us-gov-west-1.amazonaws.com   (AWS GovCloud)
 
-      *                  - sns.cn-north-1.amazonaws.com.cn   (AWS China)
 
-      */
 
-     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;
 
-     }
 
-     /**
 
-      * Constructs the Message Validator object and ensures that openssl is
 
-      * installed.
 
-      *
 
-      * @param callable $certClient Callable used to download the certificate.
 
-      *                             Should have the following function signature:
 
-      *                             `function (string $certUrl) : string|false $certContent`
 
-      * @param string $hostNamePattern
 
-      */
 
-     public function __construct(
 
-         callable $certClient = null,
 
-         $hostNamePattern = ''
 
-     ) {
 
-         $this->certClient = $certClient ?: function($certUrl) {
 
-             return @ file_get_contents($certUrl);
 
-         };
 
-         $this->hostPattern = $hostNamePattern ?: self::$defaultHostPattern;
 
-     }
 
-     /**
 
-      * Validates a message from SNS to ensure that it was delivered by AWS.
 
-      *
 
-      * @param Message $message Message to validate.
 
-      *
 
-      * @throws InvalidSnsMessageException If the cert cannot be retrieved or its
 
-      *                                    source verified, or the message
 
-      *                                    signature is invalid.
 
-      */
 
-     public function validate(Message $message)
 
-     {
 
-         if (self::isLambdaStyle($message)) {
 
-             $message = self::convertLambdaMessage($message);
 
-         }
 
-         // Get the certificate.
 
-         $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']}\"."
 
-             );
 
-         }
 
-         // Extract the public key.
 
-         $key = openssl_get_publickey($certificate);
 
-         if (!$key) {
 
-             throw new InvalidSnsMessageException(
 
-                 'Cannot get the public key from the certificate.'
 
-             );
 
-         }
 
-         // Verify the signature of the message.
 
-         $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.'
 
-             );
 
-         }
 
-     }
 
-     /**
 
-      * 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.
 
-      *
 
-      * @param Message $message The message to validate
 
-      *
 
-      * @return bool
 
-      */
 
-     public function isValid(Message $message)
 
-     {
 
-         try {
 
-             $this->validate($message);
 
-             return true;
 
-         } catch (InvalidSnsMessageException $e) {
 
-             return false;
 
-         }
 
-     }
 
-     /**
 
-      * Builds string-to-sign according to the SNS message spec.
 
-      *
 
-      * @param Message $message Message for which to build the string-to-sign.
 
-      *
 
-      * @return string
 
-      * @link http://docs.aws.amazon.com/sns/latest/gsg/SendMessageToHttp.verify.signature.html
 
-      */
 
-     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;
 
-     }
 
-     /**
 
-      * Ensures that the URL of the certificate is one belonging to AWS, and not
 
-      * just something from the amazonaws domain, which could include S3 buckets.
 
-      *
 
-      * @param string $url Certificate URL
 
-      *
 
-      * @throws InvalidSnsMessageException if the cert url is invalid.
 
-      */
 
-     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.'
 
-             );
 
-         }
 
-     }
 
- }
 
 
  |