P256EncryptedMessage   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 227
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 18
eloc 46
c 1
b 0
f 0
dl 0
loc 227
ccs 56
cts 56
cp 1
rs 10

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getEncodedPrivateKey() 0 3 1
A getPrivateKey() 0 3 1
A getPseudoRandomKey() 0 5 1
A getSubscriberPublicKey() 0 3 1
A padPayload() 0 6 1
A hkdf() 0 7 1
A getSalt() 0 3 1
A getSharedSecret() 0 5 1
A getCypherLength() 0 3 1
A getContext() 0 5 1
A getEncodedPublicKey() 0 3 1
A getEncodedSalt() 0 3 1
A getContentEncryptionKey() 0 5 1
A __construct() 0 12 1
A getNonce() 0 5 1
A getPublicKey() 0 3 1
A getCypher() 0 18 2
1
<?php
2
3
namespace AlexLisenkov\LaravelWebPush;
4
5
use AlexLisenkov\LaravelWebPush\Contracts\P256EncryptedMessageContract;
6
use Base64Url\Base64Url;
7
use Elliptic\EC\KeyPair;
8
9
class P256EncryptedMessage implements P256EncryptedMessageContract
10
{
11
    /**
12
     * @var KeyPair
13
     */
14
    private $private;
15
    /**
16
     * @var KeyPair
17
     */
18
    private $subscriber;
19
    /**
20
     * @var string
21
     */
22
    private $salt;
23
    /**
24
     * @var string
25
     */
26
    private $auth_token;
27
    /**
28
     * @var string
29
     */
30
    private $payload;
31
    /**
32
     * @var string
33
     */
34
    private $cypher;
35
36
    /**
37
     * P256EncryptedMessage constructor.
38
     *
39
     * @param KeyPair $private
40
     * @param KeyPair $subscriber
41
     * @param string $auth_token Decoded version of auth token
42
     * @param string $salt 16 byte salt
43
     * @param string $payload
44
     */
45 13
    public function __construct(
46
        KeyPair $private,
47
        KeyPair $subscriber,
48
        string $auth_token,
49
        string $salt,
50
        string $payload
51
    ) {
52 13
        $this->private = $private;
53 13
        $this->subscriber = $subscriber;
54 13
        $this->salt = $salt;
55 13
        $this->auth_token = $auth_token;
56 13
        $this->payload = $payload;
57 13
    }
58
59
    /**
60
     * @return int
61
     */
62 1
    public function getCypherLength(): int
63
    {
64 1
        return mb_strlen($this->getCypher(), '8bit');
65
    }
66
67
    /**
68
     * @return string
69
     */
70 2
    public function getCypher(): string
71
    {
72 2
        if (!$this->cypher) {
73 2
            $payload = $this->padPayload($this->payload);
74
75 2
            $cypher = openssl_encrypt(
76 2
                $payload,
77 2
                'aes-128-gcm',
78 2
                $this->getContentEncryptionKey(),
79 2
                OPENSSL_RAW_DATA,
80 2
                $this->getNonce(),
81
                $tag
82
            );
83
84 2
            $this->cypher = $cypher . $tag;
85
        }
86
87 2
        return $this->cypher;
88
    }
89
90
    /**
91
     * @param $payload
92
     *
93
     * @return string
94
     */
95 2
    private function padPayload($payload): string
96
    {
97 2
        $payload_length = mb_strlen($payload, '8bit');
98 2
        $pad_length = Constants::PADDED_PAYLOAD_LENGTH - $payload_length;
99
100 2
        return pack('n*', $pad_length) . str_pad($payload, Constants::PADDED_PAYLOAD_LENGTH, "\x00", STR_PAD_LEFT);
101
    }
102
103
    /**
104
     * The content encryption key (CEK) is the key that will ultimately be used to encrypt our payload.
105
     *
106
     * @return string
107
     */
108 3
    public function getContentEncryptionKey(): string
109
    {
110 3
        $keyLabelInfo = "Content-Encoding: aesgcm\x00P-256" . $this->getContext();
111
112 3
        return $this->hkdf($this->salt, $this->getPseudoRandomKey(), $keyLabelInfo, 16);
113
    }
114
115
    /**
116
     * The "context" is a set of bytes that is used to calculate two values later on in the encryption browser.
117
     * It's essentially an array of bytes containing the subscription public key and the local public key.
118
     */
119 5
    public function getContext(): string
120
    {
121 5
        $len = "\x00A";
122
123 5
        return "\x00" . $len . $this->getSubscriberPublicKey() . $len . $this->getPublicKey();
124
    }
125
126
    /**
127
     * @return string
128
     */
129 5
    private function getSubscriberPublicKey(): string
130
    {
131 5
        return hex2bin($this->subscriber->getPublic('hex'));
132
    }
133
134
    /**
135
     * @return string
136
     */
137 6
    public function getPublicKey(): string
138
    {
139 6
        return hex2bin($this->private->getPublic('hex'));
140
    }
141
142
    /**
143
     * @param string $salt
144
     * @param string $ikm
145
     * @param string $info
146
     * @param int $length
147
     *
148
     * @return string
149
     */
150 5
    private function hkdf(string $salt, string $ikm, string $info, int $length): string
151
    {
152
        // extract
153 5
        $prk = hash_hmac('sha256', $ikm, $salt, true);
154
155
        // expand
156 5
        return mb_substr(hash_hmac('sha256', $info . chr(1), $prk, true), 0, $length, '8bit');
157
    }
158
159
    /**
160
     * The Pseudo Random Key (PRK) is the combination of the push subscription's auth secret,
161
     * and the shared secret.
162
     *
163
     * @return string
164
     */
165 5
    public function getPseudoRandomKey(): string
166
    {
167 5
        $info = "Content-Encoding: auth\x00";
168
169 5
        return $this->hkdf($this->auth_token, $this->getSharedSecret(), $info, 32);
170
    }
171
172
    /**
173
     * @return bool|string
174
     */
175 6
    public function getSharedSecret(): string
176
    {
177 6
        $shared_secret = $this->private->derive($this->subscriber->getPublic());
178
179 6
        return hex2bin(str_pad(gmp_strval($shared_secret->toString(), 16), 64, '0', STR_PAD_LEFT));
180
    }
181
182
    /**
183
     * A nonce is a value that prevents replay attacks as it should only be used once.
184
     *
185
     * @return string
186
     */
187 3
    public function getNonce(): string
188
    {
189 3
        $nonceEncInfo = "Content-Encoding: nonce\x00P-256" . $this->getContext();
190
191 3
        return $this->hkdf($this->salt, $this->getPseudoRandomKey(), $nonceEncInfo, 12);
192
    }
193
194
    /**
195
     * Get Salt
196
     *
197
     * @return string
198
     */
199 1
    public function getSalt(): string
200
    {
201 1
        return $this->salt;
202
    }
203
204
    /**
205
     * Get Salt
206
     *
207
     * @return string
208
     */
209 1
    public function getEncodedSalt(): string
210
    {
211 1
        return Base64Url::encode($this->salt);
212
    }
213
214
    /**
215
     * @return string
216
     */
217 1
    public function getPrivateKey(): string
218
    {
219 1
        return hex2bin($this->private->getPrivate('hex'));
220
    }
221
222
    /**
223
     * @return string
224
     */
225 1
    public function getEncodedPrivateKey(): string
226
    {
227 1
        return Base64Url::encode(hex2bin($this->private->getPrivate('hex')));
228
    }
229
230
    /**
231
     * @return string
232
     */
233 1
    public function getEncodedPublicKey(): string
234
    {
235 1
        return Base64Url::encode(hex2bin($this->private->getPublic('hex')));
236
    }
237
}
238