111 lines
3.7 KiB
PHP
111 lines
3.7 KiB
PHP
<?php
|
|
/**
|
|
* Copyright 2010-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
|
* You may not use this file except in compliance with the License.
|
|
* A copy of the License is located at
|
|
*
|
|
* http://aws.amazon.com/apache2.0
|
|
*
|
|
* or in the "license" file accompanying this file. This file is distributed
|
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
|
* express or implied. See the License for the specific language governing
|
|
* permissions and limitations under the License.
|
|
*/
|
|
require_once(W3TC_LIB_DIR . '/SNS/services/MessageValidator/sns-exceptions.php');
|
|
|
|
/**
|
|
* This class uses openssl to verify SNS messages to ensure that they were sent by AWS.
|
|
*/
|
|
class MessageValidator {
|
|
|
|
private $hostPattern
|
|
= '/^sns\.[a-zA-Z0-9\-]{3,}\.amazonaws\.com(\.cn)?$/';
|
|
|
|
/**
|
|
* Constructs the Message Validator object and ensures that openssl is installed
|
|
*
|
|
* @throws Exception If openssl is not installed
|
|
*/
|
|
public function __construct()
|
|
{
|
|
if (!extension_loaded('openssl')) {
|
|
throw new Exception('The openssl extension is required to use the SNS Message '
|
|
. 'Validator. Please install this extension in order to use this feature.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validates a message from SNS to ensure that it was delivered by AWS
|
|
*
|
|
* @param Message $message The message to validate
|
|
*
|
|
* @throws CannotGetPublicKeyFromCertificateException If the certificate cannot be retrieved
|
|
* @throws CertificateFromUnrecognizedSourceException If the certificate's source cannot be verified
|
|
* @throws InvalidMessageSignatureException If the message's signature is invalid
|
|
*/
|
|
public function validate($message) {
|
|
// Get the cert's URL and ensure it is from AWS
|
|
$certUrl = $message->get('SigningCertURL');
|
|
$this->validateUrl($certUrl);
|
|
|
|
// Get the cert itself and extract the public key
|
|
$response = wp_remote_get($certUrl);
|
|
if (is_wp_error($response))
|
|
throw new CannotGetPublicKeyFromCertificateException('Could not retrieve certificate from ' . $certUrl);
|
|
|
|
$certificate = wp_remote_retrieve_body($response);
|
|
$publicKey = openssl_get_publickey($certificate);
|
|
if (!$publicKey) {
|
|
throw new CannotGetPublicKeyFromCertificateException('Could not extract public key from ' . $certUrl);
|
|
}
|
|
|
|
// Verify the signature of the message
|
|
$stringToSign = $message->getStringToSign();
|
|
$incomingSignature = base64_decode($message->get('Signature'));
|
|
if (0 !== openssl_verify($stringToSign, $incomingSignature, $publicKey, OPENSSL_ALGO_SHA1)) {
|
|
throw new InvalidMessageSignatureException('The message did not match the signature ' . "\n" . $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.'
|
|
);
|
|
}
|
|
}
|
|
/**
|
|
* 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)
|
|
{
|
|
try {
|
|
$this->validate($message);
|
|
return true;
|
|
} catch (SnsMessageValidatorException $e) {
|
|
$error = $e->getMessage();
|
|
return false;
|
|
}
|
|
}
|
|
}
|