Test Failed
Push — master ( 3dd85e...34f16b )
by Devin
04:34 queued 10s
created

WebhookSignature::verifyHeader()   B

Complexity

Conditions 8
Paths 11

Size

Total Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

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