Completed
Pull Request — master (#3)
by Marcel
02:23
created

Authenticator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
namespace UMA\Psr\Http\Message\HMAC;
4
5
use Psr\Http\Message\MessageInterface;
6
use Psr\Http\Message\RequestInterface;
7
use UMA\Psr\Http\Message\Serializer\MessageSerializer;
8
9
class Authenticator
10
{
11
    /**
12
     * @param MessageInterface $message
13
     * @param string           $secret
14
     *
15
     * @return MessageInterface The signed message.
16
     *
17
     * @throws \InvalidArgumentException When $message is an implementation of
18
     *                                   MessageInterface that cannot be
19
     *                                   serialized and thus neither signed.
20
     */
21 70
    public function sign(MessageInterface $message, $secret)
22
    {
23 70
        $preSignedMessage = $message->withHeader(
24 70
            Specification::SIGN_HEADER,
25 70
            $this->getSignedHeadersString($message)
26 70
        );
27
28 70
        $serialization = MessageSerializer::serialize($preSignedMessage);
29
30 70
        return $preSignedMessage->withHeader(
31 70
            Specification::AUTH_HEADER,
32 70
            Specification::AUTH_PREFIX.' '.$this->doHMACSignature($serialization, $secret)
33 70
        );
34
    }
35
36
    /**
37
     * @param MessageInterface $message
38
     * @param string           $secret
39
     *
40
     * @return bool Signature verification outcome.
41
     *
42
     * @throws \InvalidArgumentException When $message is an implementation of
43
     *                                   MessageInterface that cannot be
44
     *                                   serialized and thus neither verified.
45
     */
46 72
    public function verify(MessageInterface $message, $secret)
47
    {
48 72
        if (0 === preg_match(
49 72
            '#^'.Specification::AUTH_PREFIX.' ([+/0-9A-Za-z]{43}=)$#',
50 72
            $message->getHeaderLine(Specification::AUTH_HEADER), $matches)
51 72
        ) {
52 2
            return false;
53
        }
54
55 70
        $signedHeaders = array_filter(explode(',', $message->getHeaderLine(Specification::SIGN_HEADER)));
56
57 70
        foreach ($message->getHeaders() as $name => $value) {
58 70
            if (!in_array(mb_strtolower($name), $signedHeaders)) {
59 70
                $message = $message->withoutHeader($name);
60 70
            }
61 70
        }
62
63 70
        $clientSideSignature = $matches[1];
64
65 70
        $serverSideSignature = $this->doHMACSignature(
66 70
            MessageSerializer::serialize($message), $secret
67 70
        );
68
69 70
        return hash_equals($serverSideSignature, $clientSideSignature);
70
    }
71
72
    /**
73
     * @param string $data The string to be signed
74
     * @param string $key  The secret with which to sign it
75
     *
76
     * @return string A base64-encoded SHA256 hash (so it is guaranteed to be 44 bytes long)
77
     */
78 70
    protected function doHMACSignature($data, $key)
79
    {
80 70
        return base64_encode(hash_hmac(Specification::HASH_ALGORITHM, $data, $key, true));
81
    }
82
83
    /**
84
     * @param MessageInterface $message
85
     *
86
     * @return string
87
     */
88 70
    private function getSignedHeadersString(MessageInterface $message)
89
    {
90 70
        $headers = array_keys(array_change_key_case($message->getHeaders(), CASE_LOWER));
91 70
        array_push($headers, mb_strtolower(Specification::SIGN_HEADER));
92
93
        // Some of the tested RequestInterface implementations do not include
94
        // the Host header in $message->getHeaders(), so it is explicitly set when needed
95 70
        if ($message instanceof RequestInterface && !in_array('host', $headers)) {
96 12
            array_push($headers, 'host');
97 12
        }
98
99
        // There is no guarantee about the order of the headers returned by
100
        // $message->getHeaders(), so they are explicitly sorted in order
101
        // to produce the exact same string regardless of the underlying implementation
102 70
        sort($headers);
103
104 70
        return implode(',', $headers);
105
    }
106
}
107