Authorization::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 8
c 2
b 0
f 0
dl 0
loc 12
rs 10
cc 1
nc 1
nop 7
1
<?php declare(strict_types=1);
2
3
namespace ncryptf;
4
5
use DateTime;
6
use ncryptf\Signature;
7
use ncryptf\Token;
8
9
final class Authorization
10
{
11
    /**
12
     * The HMAC info parameter. Overwrite this class and redeclare to change.
13
     */
14
    const AUTH_INFO = 'HMAC|AuthenticationKey';
15
16
    /**
17
     * The HMAC algorithm. Overwrite this class and redeclare to change.
18
     */
19
    const HMAC_ALGO = 'sha256';
20
21
    /**
22
     * Token object containing the access token and initial key material
23
     *
24
     * @var Token
25
     */
26
    private $token;
27
28
    /**
29
     * 32 byte salt array
30
     *
31
     * @var string
32
     */
33
    private $salt;
34
35
    /**
36
     * RFC1123 representation of the date
37
     *
38
     * @var string
39
     */
40
    private $date;
41
42
    /**
43
     * The generated signature
44
     *
45
     * @var string
46
     */
47
    private $signature;
48
49
    /**
50
     * 32 byte HMAC
51
     *
52
     * @var string
53
     */
54
    private $hmac;
55
56
    /**
57
     * The header version to generate
58
     *
59
     * @var integer
60
     */
61
    private $version = 2;
62
63
    /**
64
     * Calculates the authorization header information
65
     *
66
     * @param string $httpMethod    The HTTP method
67
     * @param string $uri           The full URI with query string parameters
68
     * @param Token $token          A token object containing the ikm, access token, and other authentication attributes
69
     * @param DateTime $date        The date
70
     * @param array|string $payload Array representation of the payload
71
     * @param integer $version      The authorization version, by default this is 2
72
     * @param string $salt          An optional fixed salt value
73
     */
74
    public function __construct(string $httpMethod, string $uri, Token $token, DateTime $date, $payload = '', int $version = 2, string $salt = null)
75
    {
76
        $httpMethod = \strtoupper($httpMethod);
77
        $this->salt = $salt ?? \random_bytes(32);
78
        $this->signature = Signature::derive($httpMethod, $uri, $this->salt, $date, $payload, $version);
79
80
        $hkdf = hash_hkdf(static::HMAC_ALGO, $token->ikm, 0, static::AUTH_INFO, $this->salt);
81
82
        $this->hmac = \hash_hmac('sha256', $this->signature, \bin2hex($hkdf), true);
83
        $this->version = $version;
84
        $this->date = $date->format(\DateTime::RFC1123);
85
        $this->token = $token;
86
    }
87
88
    /**
89
     * Extracts the HMAC parameters from a header string
90
     *
91
     * @param string $hmacHeader
92
     * @return array|boolean
93
     */
94
    public static function extractParamsFromHeaderString(string $hmacHeader = null)
95
    {
96
        if ($hmacHeader !== null && preg_match('/^HMAC\s+(.*?)$/', $hmacHeader, $matches)) {
97
            if (\strpos($matches[1], ',') !== false) {
98
                $params = explode(',', trim($matches[1]));
99
100
                if (count($params) !== 3) {
101
                    return false;
102
                }
103
104
                return [
105
                    'access_token' => $params[0],
106
                    'hmac' => $params[1],
107
                    'salt' => $params[2],
108
                    'v' => 1,
109
                    'date' => null,
110
                ];
111
            } else {
112
                $params = \json_decode(\base64_decode($matches[1]), true);
113
114
                if (!isset($params['v']) ||
115
                    !isset($params['access_token']) ||
116
                    !isset($params['hmac']) ||
117
                    !isset($params['salt']) ||
118
                    !isset($params['v']) ||
119
                    !isset($params['date'])
120
                ) {
121
                    return false;
122
                }
123
124
                return $params;
125
            }
126
        }
127
128
        return false;
129
    }
130
131
    /**
132
     * Returns the signature string
133
     *
134
     * @return string
135
     */
136
    public function getSignatureString() : string
137
    {
138
        return $this->signature;
139
    }
140
141
    /**
142
     * Returns the generated HMAC
143
     *
144
     * @return string
145
     */
146
    public function getHMAC() : string
147
    {
148
        return $this->hmac;
149
    }
150
151
    /**
152
     * Returns the base64 encoded HMAC
153
     *
154
     * @return string
155
     */
156
    public function getEncodedHMAC() : string
157
    {
158
        return \base64_encode($this->hmac);
159
    }
160
161
    /**
162
     * Returns the base64 encoded salt
163
     *
164
     * @return string
165
     */
166
    public function getEncodedSalt() : string
167
    {
168
        return \base64_encode($this->salt);
169
    }
170
171
    /**
172
     * Returns an RFC1123 formatted date
173
     *
174
     * @return string
175
     */
176
    public function getDate() : string
177
    {
178
        return $this->date;
179
    }
180
181
    /**
182
     * Generates the versions HMAC header
183
     *
184
     * @return string
185
     */
186
    public function getHeader() : string
187
    {
188
        $salt = $this->getEncodedSalt();
189
        $hmac = $this->getEncodedHMAC();
190
191
        if ($this->version === 2) {
192
            $data = \base64_encode(\json_encode([
193
                'access_token' => $this->token->accessToken,
194
                'date' => $this->date,
195
                'hmac' => $hmac,
196
                'salt' => $salt,
197
                'v' => 2
198
            ]));
199
200
            return "HMAC {$data}";
201
        }
202
203
        // The version 1 HMAC is returned by default
204
        return "HMAC {$this->token->accessToken},{$hmac},{$salt}";
205
    }
206
207
    /**
208
     * Validates a provided HMAC against an auth object and a drift
209
     *
210
     * @param string $hmac  byte[] HMAC
211
     * @param self $auth    Authorization object
212
     * @param integer $driftAllowance
213
     * @return boolean
214
     */
215
    public function verify(string $hmac, self $auth, int $driftAllowance = 90) : bool
216
    {
217
        $drift = $this->getTimeDrift($auth->getDate());
218
        if ($drift === null || $drift >= $driftAllowance) {
219
            return false;
220
        }
221
222
        try {
223
            if (\sodium_memcmp($hmac, $auth->getHMAC()) === 0) {
224
                return true;
225
            }
226
        } catch (\SodiumException $e) {
227
            return false;
228
        }
229
230
        return false;
231
    }
232
233
    /**
234
     * Calculates the time difference between now and the provided date
235
     *
236
     * @param string $date
237
     * @return int|boolean
238
     */
239
    private function getTimeDrift(string $date) :? int
240
    {
241
        $now = new \DateTime();
242
        $now->format(\DateTime::RFC1123);
243
244
        try {
245
            $realDate = new DateTime($date);
246
        } catch (\Exception $e) {
247
            return null;
248
        }
249
250
        return (int)\abs($now->getTimestamp() - $realDate->getTimestamp());
251
    }
252
}
253