<?php
namespace Stripe\StripeTaxForWooCommerce\SDK\lib;
abstract class WebhookSignature {
const EXPECTED_SCHEME = 'v1';
/**
* Verifies the signature header sent by Stripe\StripeTaxForWooCommerce\SDK\lib. Throws an
* Exception\SignatureVerificationException exception if the verification fails for
* any reason.
*
* @param string $payload the payload sent by Stripe\StripeTaxForWooCommerce\SDK\lib
* @param string $header the contents of the signature header sent by
* Stripe\StripeTaxForWooCommerce\SDK\lib
* @param string $secret secret used to generate the signature
* @param int $tolerance maximum difference allowed between the header's
* timestamp and the current time
*
* @throws Exception\SignatureVerificationException if the verification fails
*
* @return bool
*/
public static function verifyHeader( $payload, $header, $secret, $tolerance = null ) {
// Extract timestamp and signatures from header
$timestamp = self::getTimestamp( $header );
$signatures = self::getSignatures( $header, self::EXPECTED_SCHEME );
if ( -1 === $timestamp ) {
throw Exception\SignatureVerificationException::factory(
'Unable to extract timestamp and signatures from header',
$payload,
$header
);
}
if ( empty( $signatures ) ) {
throw Exception\SignatureVerificationException::factory(
'No signatures found with expected scheme',
$payload,
$header
);
}
// Check if expected signature is found in list of signatures from
// header
$signedPayload = "{$timestamp}.{$payload}";
$expectedSignature = self::computeSignature( $signedPayload, $secret );
$signatureFound = false;
foreach ( $signatures as $signature ) {
if ( Util\Util::secureCompare( $expectedSignature, $signature ) ) {
$signatureFound = true;
break;
}
}
if ( ! $signatureFound ) {
throw Exception\SignatureVerificationException::factory(
'No signatures found matching the expected signature for payload',
$payload,
$header
);
}
// Check if timestamp is within tolerance
if ( ( $tolerance > 0 ) && ( \abs( \time() - $timestamp ) > $tolerance ) ) {
throw Exception\SignatureVerificationException::factory(
'Timestamp outside the tolerance zone',
$payload,
$header
);
}
return true;
}
/**
* Extracts the timestamp in a signature header.
*
* @param string $header the signature header
*
* @return int the timestamp contained in the header, or -1 if no valid
* timestamp is found
*/
private static function getTimestamp( $header ) {
$items = \explode( ',', $header );
foreach ( $items as $item ) {
$itemParts = \explode( '=', $item, 2 );
if ( 't' === $itemParts[0] ) {
if ( ! \is_numeric( $itemParts[1] ) ) {
return -1;
}
return (int) ( $itemParts[1] );
}
}
return -1;
}
/**
* Extracts the signatures matching a given scheme in a signature header.
*
* @param string $header the signature header
* @param string $scheme the signature scheme to look for
*
* @return array the list of signatures matching the provided scheme
*/
private static function getSignatures( $header, $scheme ) {
$signatures = array();
$items = \explode( ',', $header );
foreach ( $items as $item ) {
$itemParts = \explode( '=', $item, 2 );
if ( \trim( $itemParts[0] ) === $scheme ) {
$signatures[] = $itemParts[1];
}
}
return $signatures;
}
/**
* Computes the signature for a given payload and secret.
*
* The current scheme used by Stripe\StripeTaxForWooCommerce\SDK\lib ("v1") is HMAC/SHA-256.
*
* @param string $payload the payload to sign
* @param string $secret the secret used to generate the signature
*
* @return string the signature as a string
*/
private static function computeSignature( $payload, $secret ) {
return \hash_hmac( 'sha256', $payload, $secret );
}
}