Completed
Push — master ( 434e09...694844 )
by Marcel
26s
created

Authenticator::sign()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 14
rs 9.4285
cc 1
eloc 8
nc 1
nop 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
     * @var string
13
     */
14
    private $secret;
15
16
    /**
17
     * Authenticator constructor.
18
     *
19
     * @param string $secret
20
     */
21
    public function __construct($secret)
22
    {
23
        $this->secret = $secret;
24
    }
25
26
    /**
27
     * @param MessageInterface $message
28
     *
29
     * @return MessageInterface The signed message.
30
     *
31
     * @throws \InvalidArgumentException When $message is an implementation of
32
     *                                   MessageInterface that cannot be
33
     *                                   serialized and thus neither signed.
34
     */
35
    public function sign(MessageInterface $message)
36
    {
37
        $preSignedMessage = $message->withHeader(
38
            Specification::SIGN_HEADER,
39
            $this->getSignedHeadersString($message)
40
        );
41
42
        $serialization = MessageSerializer::serialize($preSignedMessage);
43
44
        return $preSignedMessage->withHeader(
45
            Specification::AUTH_HEADER,
46
            Specification::AUTH_PREFIX.' '.Specification::doHMACSignature($serialization, $this->secret)
47
        );
48
    }
49
50
    /**
51
     * @param MessageInterface $message
52
     *
53
     * @return bool Signature verification outcome.
54
     *
55
     * @throws \InvalidArgumentException When $message is an implementation of
56
     *                                   MessageInterface that cannot be
57
     *                                   serialized and thus neither verified.
58
     */
59
    public function verify(MessageInterface $message)
60
    {
61
        if (empty($authHeader = $message->getHeaderLine(Specification::AUTH_HEADER))) {
62
            return false;
63
        }
64
65
        if (0 === preg_match('#^'.Specification::AUTH_PREFIX.' ([+/0-9A-Za-z]{43}=)$#', $authHeader, $matches)) {
66
            return false;
67
        }
68
69
        $clientSideSignature = $matches[1];
70
71
        $serverSideSignature = Specification::doHMACSignature(
72
            MessageSerializer::serialize($message->withoutHeader(Specification::AUTH_HEADER)),
73
            $this->secret
74
        );
75
76
        return hash_equals($serverSideSignature, $clientSideSignature);
77
    }
78
79
    /**
80
     * @param MessageInterface $message
81
     *
82
     * @return string
83
     */
84
    private function getSignedHeadersString(MessageInterface $message)
85
    {
86
        $headers = array_keys($message->getHeaders());
87
        array_push($headers, Specification::SIGN_HEADER);
88
89
        // Some of the tested RequestInterface implementations do not include
90
        // the Host header in $message->getHeaders(), so it is explicitly set when needed
91
        if ($message instanceof RequestInterface && !in_array('Host', $headers)) {
92
            array_push($headers, 'Host');
93
        }
94
95
        // There is no guarantee about the order of the headers returned by
96
        // $message->getHeaders(), so they are explicitly sorted in order
97
        // to produce the exact same string regardless of the underlying implementation
98
        sort($headers);
99
100
        return implode(',', $headers);
101
    }
102
}
103