Packed::__construct()   B
last analyzed

Complexity

Conditions 11
Paths 7

Size

Total Lines 42
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 23
c 1
b 0
f 0
nc 7
nop 2
dl 0
loc 42
rs 7.3166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Platine Webauth
5
 *
6
 * Platine Webauthn is the implementation of webauthn specifications
7
 *
8
 * This content is released under the MIT License (MIT)
9
 *
10
 * Copyright (c) 2020 Platine Webauth
11
 * Copyright (c) Jakob Bennemann <[email protected]>
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a copy
14
 * of this software and associated documentation files (the "Software"), to deal
15
 * in the Software without restriction, including without limitation the rights
16
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 * copies of the Software, and to permit persons to whom the Software is
18
 * furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all
21
 * copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
 * SOFTWARE.
30
 */
31
32
declare(strict_types=1);
33
34
namespace Platine\Webauthn\Attestation\Format;
35
36
use Platine\Webauthn\Attestation\AuthenticatorData;
37
use Platine\Webauthn\Exception\WebauthnException;
38
use Platine\Webauthn\Helper\ByteBuffer;
39
40
/**
41
 * @class Packed
42
 * @package Platine\Webauthn\Attestation\Format
43
 */
44
class Packed extends BaseFormat
45
{
46
    /**
47
     * The algorithm used
48
     * @var int
49
     */
50
    protected int $algo;
51
52
    /**
53
     * The signature
54
     * @var string
55
     */
56
    protected string $signature;
57
58
    /**
59
     * The X5C information
60
     * @var string
61
     */
62
    protected string $x5c = '';
63
64
    /**
65
     * Create new instance
66
     * @param array<string|int, mixed> $attestationData
67
     * @param AuthenticatorData $authenticatorData
68
     */
69
    public function __construct(
70
        array $attestationData,
71
        AuthenticatorData $authenticatorData
72
    ) {
73
        parent::__construct($attestationData, $authenticatorData);
74
75
        // check u2f data
76
        $attestationStatement = $this->attestationData['attStmt'];
77
        if (
78
            ! array_key_exists('alg', $attestationStatement) ||
79
            $this->getCoseAlgorithm($attestationStatement['alg']) === null
80
        ) {
81
            throw new WebauthnException('Unsupported algorithm or not provided');
82
        }
83
84
        if (
85
            ! array_key_exists('sig', $attestationStatement) ||
86
            ! $attestationStatement['sig'] instanceof ByteBuffer
87
        ) {
88
            throw new WebauthnException('No signature found');
89
        }
90
91
        $this->algo = $attestationStatement['alg'];
92
        $this->signature = $attestationStatement['sig']->getBinaryString();
93
94
        if (
95
            array_key_exists('x5c', $attestationStatement) &&
96
            is_array($attestationStatement['x5c']) &&
97
            count($attestationStatement['x5c']) > 0
98
        ) {
99
            // The attestation certificate attestnCert MUST be the first element in the array
100
            $attestCert = array_shift($attestationStatement['x5c']);
101
            if (! $attestCert instanceof ByteBuffer) {
102
                throw new WebauthnException('Invalid X5C certificate');
103
            }
104
105
            $this->x5c = $attestCert->getBinaryString();
106
107
            // Certificate chains
108
            foreach ($attestationStatement['x5c'] as $chain) {
109
                if ($chain instanceof ByteBuffer) {
110
                    $this->x5cChain[] = $chain->getBinaryString();
111
                }
112
            }
113
        }
114
    }
115
116
    /**
117
    * {@inheritdoc}
118
    */
119
    public function getCertificatePem(): ?string
120
    {
121
        if (empty($this->x5c)) {
122
            return null;
123
        }
124
125
        return $this->createCertificatePem($this->x5c);
126
    }
127
128
    /**
129
    * {@inheritdoc}
130
    */
131
    public function validateAttestation(string $clientData): bool
132
    {
133
        if (! empty($this->x5c)) {
134
            return $this->validateOverX5C($clientData);
135
        }
136
137
        return $this->validateSelfAttestation($clientData);
138
    }
139
140
    /**
141
    * {@inheritdoc}
142
    */
143
    public function validateRootCertificate(array $rootCertificates): bool
144
    {
145
        if (empty($this->x5c)) {
146
            return false;
147
        }
148
149
        $chain = $this->createX5cChainFile();
150
        if ($chain !== null) {
151
            $rootCertificates[] = $chain;
152
        }
153
154
        $value = openssl_x509_checkpurpose(
155
            // TODO phpstan complains so cast to string
156
            (string) $this->getCertificatePem(),
157
            -1,
158
            $rootCertificates
159
        );
160
161
        if ($value === -1) {
162
            throw new WebauthnException(sprintf(
163
                'Error when validate root certificate, error message: [%s]',
164
                openssl_error_string()
165
            ));
166
        }
167
168
        // TODO phpstan complains so cast to bool
169
        return (bool) $value;
170
    }
171
172
    /**
173
     * Validate if x5c is present
174
     * @param string $clientData
175
     * @return bool
176
     */
177
    protected function validateOverX5C(string $clientData): bool
178
    {
179
        // TODO phpstan complains so cast to string
180
        $publicKey = openssl_pkey_get_public((string) $this->getCertificatePem());
181
182
        if ($publicKey === false) {
183
            throw new WebauthnException(sprintf(
184
                'Invalid public key used, error: [%s]',
185
                openssl_error_string()
186
            ));
187
        }
188
189
        // Verify that sig is a valid signature over the concatenation of authenticatorData
190
        // and clientDataHash using the attestation public key in attestnCert
191
        // with the algorithm specified in alg.
192
        $dataToVerify = $this->authenticatorData->getBinary();
193
        $dataToVerify .= $clientData;
194
195
        $coseAlgo = $this->getCoseAlgorithm($this->algo);
196
        if ($coseAlgo === null) {
197
            throw new WebauthnException(sprintf(
198
                'Invalid algorithm [%d]',
199
                $this->algo
200
            ));
201
        }
202
203
        return openssl_verify(
204
            $dataToVerify,
205
            $this->signature,
206
            $publicKey,
207
            $coseAlgo['openssl']
208
        ) === 1;
209
    }
210
211
    /**
212
     * Validate if self attestation is used
213
     * @param string $clientData
214
     * @return bool
215
     */
216
    protected function validateSelfAttestation(string $clientData): bool
217
    {
218
        // Verify that sig is a valid signature over the concatenation of authenticatorData
219
        // and clientDataHash using the credential public key with alg.
220
        $dataToVerify = $this->authenticatorData->getBinary();
221
        $dataToVerify .= $clientData;
222
223
        $publicKey = $this->authenticatorData->getPublicKeyPEM();
224
225
        return openssl_verify(
226
            $dataToVerify,
227
            $this->signature,
228
            $publicKey,
229
            OPENSSL_ALGO_SHA256
230
        ) === 1;
231
    }
232
}
233