Completed
Push — master ( 556987...94c24e )
by ARCANEDEV
9s
created

WebhookSignature::getTimestamp()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 4
nop 1
dl 0
loc 16
ccs 9
cts 9
cp 1
crap 4
rs 9.2
c 0
b 0
f 0
1
<?php namespace Arcanedev\Stripe;
2
3
/**
4
 * Class     WebhookSignature
5
 *
6
 * @package  Arcanedev\Stripe
7
 * @author   ARCANEDEV <[email protected]>
8
 */
9
abstract class WebhookSignature
10
{
11
    /* -----------------------------------------------------------------
12
     |  Constants
13
     | -----------------------------------------------------------------
14
     */
15
16
    const EXPECTED_SCHEME = 'v1';
17
18
    /* -----------------------------------------------------------------
19
     |  Main Methods
20
     | -----------------------------------------------------------------
21
     */
22
23
    /**
24
     * Verifies the signature header sent by Stripe.
25
     * Throws a SignatureVerification exception if the verification fails for any reason.
26
     *
27
     * @param  string  $payload    The payload sent by Stripe.
28
     * @param  string  $header     The contents of the signature header sent by Stripe.
29
     * @param  string  $secret     Secret used to generate the signature.
30
     * @param  int     $tolerance  Maximum difference allowed between the header's timestamp and the current time
31
     *
32
     * @return bool
33
     *
34
     * @throws \Arcanedev\Stripe\Exceptions\SignatureVerificationException
35
     */
36 27
    public static function verifyHeader($payload, $header, $secret, $tolerance = null)
37
    {
38
        // Extract timestamp and signatures from header
39 27
        $timestamp  = self::getTimestamp($header);
40 27
        $signatures = self::getSignatures($header, self::EXPECTED_SCHEME);
41
42 27
        if ($timestamp == -1) {
43 6
            throw new Exceptions\SignatureVerificationException(
44 6
                'Unable to extract timestamp and signatures from header',
45 2
                    $header,
46
                    $payload
47 2
            );
48
        }
49
50 21
        if (empty($signatures)) {
51 3
            throw new Exceptions\SignatureVerificationException(
52 3
                "No signatures found with expected scheme", $header, $payload
53 1
            );
54
        }
55
56
        // Check if expected signature is found in list of signatures from header
57 18
        $signedPayload     = "$timestamp.$payload";
58 18
        $expectedSignature = self::computeSignature($signedPayload, $secret);
59 18
        $signatureFound    = false;
60
61 18
        foreach ($signatures as $signature) {
62 18
            if (Utilities\Util::secureCompare($expectedSignature, $signature)) {
63 15
                $signatureFound = true;
64 16
                break;
65
            }
66 6
        }
67
68 18
        if ( ! $signatureFound) {
69 3
            throw new Exceptions\SignatureVerificationException(
70 3
                'No signatures found matching the expected signature for payload',
71 1
                    $header,
72
                    $payload
73 1
            );
74
        }
75
76
        // Check if timestamp is within tolerance
77 15
        if (($tolerance > 0) && ((time() - $timestamp) > $tolerance)) {
78 3
            throw new Exceptions\SignatureVerificationException(
79 3
                'Timestamp outside the tolerance zone',
80 1
                    $header,
81
                    $payload
82 1
            );
83
        }
84
85 12
        return true;
86
    }
87
88
    /* -----------------------------------------------------------------
89
     |  Other Methods
90
     | -----------------------------------------------------------------
91
     */
92
93
    /**
94
     * Extracts the timestamp in a signature header.
95
     *
96
     * @param string $header the signature header
97
     * @return int the timestamp contained in the header, or -1 if no valid timestamp is found
98
     */
99 27
    private static function getTimestamp($header)
100
    {
101 27
        $items = explode(',', $header);
102
103 27
        foreach ($items as $item) {
104 27
            $itemParts = explode('=', $item, 2);
105
106 27
            if ($itemParts[0] == 't') {
107 21
                if ( ! is_numeric($itemParts[1])) return -1;
108
109 23
                return intval($itemParts[1]);
110
            }
111 2
        }
112
113 6
        return -1;
114
    }
115
116
    /**
117
     * Extracts the signatures matching a given scheme in a signature header.
118
     *
119
     * @param  string  $header  The signature header
120
     * @param  string  $scheme  The signature scheme to look for.
121
     *
122
     * @return array   The list of signatures matching the provided scheme.
123
     */
124 27
    private static function getSignatures($header, $scheme)
125
    {
126 27
        $signatures = [];
127 27
        $items      = explode(',', $header);
128
129 27
        foreach ($items as $item) {
130 27
            $itemParts = explode('=', $item, 2);
131
132 27
            if ($itemParts[0] == $scheme) {
133 21
                array_push($signatures, $itemParts[1]);
134 6
            }
135 9
        }
136
137 27
        return $signatures;
138
    }
139
140
    /**
141
     * Computes the signature for a given payload and secret.
142
     *
143
     * The current scheme used by Stripe ("v1") is HMAC/SHA-256.
144
     *
145
     * @param  string  $payload  The payload to sign.
146
     * @param  string  $secret   The secret used to generate the signature.
147
     *
148
     * @return string  The signature as a string.
149
     */
150 18
    private static function computeSignature($payload, $secret)
151
    {
152 18
        return hash_hmac('sha256', $payload, $secret);
153
    }
154
}
155