PHP Classes

File: application/modules/extensions/aws/Aws/Sns/MessageValidator.php

Recommend this page to a friend!
  Classes of Tran Tuan   Pretty PHP S3 Files Manager   application/modules/extensions/aws/Aws/Sns/MessageValidator.php   Download  
File: application/modules/extensions/aws/Aws/Sns/MessageValidator.php
Role: Application script
Content type: text/plain
Description: Application script
Class: Pretty PHP S3 Files Manager
Web based interface to manage files in Amazon S3
Author: By
Last change:
Date: 8 years ago
Size: 4,998 bytes
 

Contents

Class file image Download
<?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';

   
/**
     * @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)?$/';

   
/**
     * 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 $certContent`
     * @param string $hostNamePattern
     */
   
public function __construct(
        callable
$certClient = null,
       
$hostNamePattern = ''
   
) {
       
$this->certClient = $certClient ?: 'file_get_contents';
       
$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)
    {
       
// Get the certificate.
       
$this->validateUrl($message['SigningCertURL']);
       
$certificate = call_user_func($this->certClient, $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']);
        if (!
openssl_verify($content, $signature, $key, OPENSSL_ALGO_SHA1)) {
            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) {
            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.'
           
);
        }
    }
}