.amazonaws.com (AWS) * - sns.us-gov-west-1.amazonaws.com (AWS GovCloud) * - sns.cn-north-1.amazonaws.com.cn (AWS China) */ private $defaultHostPattern = '/^sns\.[a-zA-Z0-9\-]{3,}\.amazonaws\.com(\.cn)?$/'; /** * IHttpRequestFactory for creating http requests * */ private $_httpRequestFactory = null; /** * Create a new instance of the openssl implementation of * verify signature * * @param string expectedCnName for Amazon cert * @param IHttpRequestFactory httpRequestFactory factory to create http requests * * @return void */ public function __construct($expectedCnName, $httpRequestFactory) { $this->_expectedCnName = $expectedCnName; $this->_httpRequestFactory = $httpRequestFactory; } /** * Verify that the signature is correct for the given data and * public key * * @param string $data data to validate * @param string $signature decoded signature to compare against * @param string $certificatePath path to certificate, can be file or url * * @throws OffAmazonPaymentsNotifications_InvalidMessageException if there * is an error * with the call * * @return bool true if valid */ public function verifySignatureIsCorrect($data, $signature, $certificatePath) { $cert = $this->_getCertificateFromCertifcatePath($certificatePath); $certificate = new Certificate($cert); return $this->verifySignatureIsCorrectFromCertificate($data, $signature, $certificate); } /** * Verify that the signature is correct for the given data and * public key * * @param string $data data to validate * @param string $signature decoded signature to compare against * @param string $certificate certificate object defined in Certificate.php * * @throws OffAmazonPaymentsNotifications_InvalidMessageException if there * is an error * with the call * * @return bool true if valid */ public function verifySignatureIsCorrectFromCertificate($data, $signature, $certificate) { $certKey = openssl_get_publickey($certificate->getCertificate()); if ($certKey === False) { throw new OffAmazonPaymentsNotifications_InvalidMessageException( "Unable to extract public key from cert"); } try { $certSubject = $certificate->getSubject(); } catch (Exception $ex) { throw new OffAmazonPaymentsNotifications_InvalidMessageException( "Unable to verify certificate - error with the certificate subject", null, $ex ); } $this->_verifyCertificateSubject($certSubject); $result = -1; try { $result = openssl_verify($data, $signature, $certKey, OPENSSL_ALGO_SHA1); } catch (Exception $ex) { throw new OffAmazonPaymentsNotifications_InvalidMessageException( "Unable to verify signature - error with the verification algorithm", null, $ex ); } return ($result > 0); } /** * Verify that certificate is issued by Amazon * * @param array $certificateSubject certificate subject array * * @throws OffAmazonPaymentsNotifications_InvalidMessageException * * @return void */ private function _verifyCertificateSubject($certificateSubject) { if ( strcmp($certificateSubject["CN"], $this->_expectedCnName) ) { throw new OffAmazonPaymentsNotifications_InvalidMessageException( "Unable to verify certificate issued by Amazon - error with certificate subject" ); } } /** * Request the signing certificate from the given path, in order to * get the public key * * @param string $certificatePath certificate path to retreive * * @throws OffAmazonPaymentsNotifications_InvalidMessageException * * @return void */ private function _getCertificateFromCertifcatePath($certificatePath) { $this->_validateUrl($certificatePath); try { return $this->_httpRequestFactory->createGetRequest($certificatePath)->execute(); } catch (OffAmazonPayments_HttpException $ex) { throw new OffAmazonPaymentsNotifications_InvalidMessageException( "Error with signature validation - unable to request signing certificate at " . $certificatePath . " - underlying exception of " . $ex->getMessage() ); } } /* 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->defaultHostPattern, $parsed['host']) ) { throw new OffAmazonPaymentsNotifications_InvalidMessageException( 'The certificate is located on an invalid domain.' ); } } }