Passed
Push — develop ( 754fa3...6370fb )
by nguereza
02:20
created

Webauthn::addRootCertificate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
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;
35
36
use Exception;
37
use Platine\Http\Uri;
38
use Platine\Stdlib\Helper\Json;
39
use Platine\Stdlib\Helper\Path;
40
use Platine\Webauthn\Attestation\AttestationData;
41
use Platine\Webauthn\Attestation\AuthenticatorData;
42
use Platine\Webauthn\Entity\AuthenticatorSelection;
43
use Platine\Webauthn\Entity\PublicKey;
44
use Platine\Webauthn\Entity\PublicKeyAuthParam;
45
use Platine\Webauthn\Entity\RelyingParty;
46
use Platine\Webauthn\Entity\UserCredential;
47
use Platine\Webauthn\Entity\UserInfo;
48
use Platine\Webauthn\Enum\AttestationType;
49
use Platine\Webauthn\Enum\KeyFormat;
50
use Platine\Webauthn\Enum\TransportType;
51
use Platine\Webauthn\Exception\WebauthnException;
52
use Platine\Webauthn\Helper\ByteBuffer;
53
54
/**
55
 * @class Webauthn
56
 * @package Platine\Webauthn
57
 */
58
class Webauthn
59
{
60
    /**
61
     * The attestation data formats
62
     * @var array<string>
63
     */
64
    protected array $formats = [];
65
66
    /**
67
     * The challenge to use
68
     * @var ByteBuffer|null
69
     */
70
    protected ?ByteBuffer $challenge = null;
71
72
    /**
73
     * The signature counter
74
     * @var int
75
     */
76
    protected int $signatureCounter = 0;
77
78
    /**
79
     * The relying party entity
80
     * @var RelyingParty
81
     */
82
    protected RelyingParty $relyingParty;
83
84
    /**
85
     * The certificates files path
86
     * @var array<string>
87
     */
88
    protected array $certificates = [];
89
90
    /**
91
     * The configuration instance
92
     * @var WebauthnConfiguration
93
     */
94
    protected WebauthnConfiguration $config;
95
96
    /**
97
     * Create new instance
98
     * @param WebauthnConfiguration $config
99
     * @param array<string> $allowedFormats
100
     */
101
    public function __construct(WebauthnConfiguration $config, array $allowedFormats = [])
102
    {
103
        if (! function_exists('openssl_open')) {
104
            throw new WebauthnException('OpenSSL module not installed in this platform');
105
        }
106
107
        if (! in_array('SHA256', array_map('strtoupper', openssl_get_md_methods()))) {
108
            throw new WebauthnException('SHA256 is not supported by this OpenSSL installation');
109
        }
110
111
        $this->config = $config;
112
        $this->formats = $this->normalizeFormats($allowedFormats);
113
114
        $this->relyingParty = new RelyingParty(
115
            $config->get('relying_party_id'),
0 ignored issues
show
Bug introduced by
It seems like $config->get('relying_party_id') can also be of type null; however, parameter $id of Platine\Webauthn\Entity\...ingParty::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

115
            /** @scrutinizer ignore-type */ $config->get('relying_party_id'),
Loading history...
116
            $config->get('relying_party_name'),
0 ignored issues
show
Bug introduced by
It seems like $config->get('relying_party_name') can also be of type null; however, parameter $name of Platine\Webauthn\Entity\...ingParty::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

116
            /** @scrutinizer ignore-type */ $config->get('relying_party_name'),
Loading history...
117
            $config->get('relying_party_logo')
0 ignored issues
show
Bug introduced by
It seems like $config->get('relying_party_logo') can also be of type null; however, parameter $logo of Platine\Webauthn\Entity\...ingParty::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

117
            /** @scrutinizer ignore-type */ $config->get('relying_party_logo')
Loading history...
118
        );
119
    }
120
121
    /**
122
     * Add a root certificate to verify new registrations
123
     * @param string $path
124
     * @return $this
125
     */
126
    public function addRootCertificate(string $path): self
127
    {
128
        $this->certificates[] = Path::realPath($path);
129
130
        return $this;
131
    }
132
133
    /**
134
     * Return the parameters to be used for the registration
135
     * @param string $userId
136
     * @param string $userName
137
     * @param string $userDisplayName
138
     * @param string $userVerificationType
139
     * @param bool $crossPlatformAttachment
140
     * @param array<string> $excludeCredentialIds
141
     * @param bool $withoutAttestation
142
     * @return PublicKey
143
     */
144
    public function getRegistrationParams(
145
        string $userId,
146
        string $userName,
147
        string $userDisplayName,
148
        string $userVerificationType,
149
        bool $crossPlatformAttachment = false,
150
        array $excludeCredentialIds = [],
151
        bool $withoutAttestation = false
152
    ): PublicKey {
153
        $excludeCredentials = [];
154
        foreach ($excludeCredentialIds as $id) {
155
            $hex = hex2bin($id);
156
            if ($hex === false) {
157
                throw new WebauthnException(sprintf('Can not convert credential id [%s] to binary', $id));
158
            }
159
160
            $excludeCredentials[] = new UserCredential(
161
                new ByteBuffer($hex),
162
                array_values(TransportType::all())
163
            );
164
        }
165
166
        $attestation = AttestationType::INDIRECT;
167
        if (count($this->certificates) > 0) {
168
            $attestation = AttestationType::DIRECT;
169
        }
170
171
        if ($withoutAttestation) {
172
            $attestation = AttestationType::NONE;
173
        }
174
175
        $relyingParty = new RelyingParty(
176
            $this->config->get('relying_party_id'),
0 ignored issues
show
Bug introduced by
It seems like $this->config->get('relying_party_id') can also be of type null; however, parameter $id of Platine\Webauthn\Entity\...ingParty::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

176
            /** @scrutinizer ignore-type */ $this->config->get('relying_party_id'),
Loading history...
177
            $this->config->get('relying_party_name'),
0 ignored issues
show
Bug introduced by
It seems like $this->config->get('relying_party_name') can also be of type null; however, parameter $name of Platine\Webauthn\Entity\...ingParty::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

177
            /** @scrutinizer ignore-type */ $this->config->get('relying_party_name'),
Loading history...
178
            $this->config->get('relying_party_logo')
0 ignored issues
show
Bug introduced by
It seems like $this->config->get('relying_party_logo') can also be of type null; however, parameter $logo of Platine\Webauthn\Entity\...ingParty::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

178
            /** @scrutinizer ignore-type */ $this->config->get('relying_party_logo')
Loading history...
179
        );
180
181
        $userInfo = new UserInfo(
182
            new ByteBuffer($userId),
183
            $userName,
184
            $userDisplayName
185
        );
186
187
        $authenticatorSelection = new AuthenticatorSelection(
188
            $userVerificationType,
189
            false,
190
            $crossPlatformAttachment
191
        );
192
193
        $publicKey = (new PublicKey())
194
                      ->setUserInfo($userInfo)
195
                      ->setRelyingParty($relyingParty)
196
                      ->setAuthenticatorSelection($authenticatorSelection)
197
                      ->setExcludeCredentials($excludeCredentials)
198
                      ->setChallenge($this->createChallenge())
199
                      ->setTimeout($this->config->get('timeout'))
0 ignored issues
show
Bug introduced by
It seems like $this->config->get('timeout') can also be of type null; however, parameter $timeout of Platine\Webauthn\Entity\PublicKey::setTimeout() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

199
                      ->setTimeout(/** @scrutinizer ignore-type */ $this->config->get('timeout'))
Loading history...
200
                      ->setExtensions()
201
                      ->addPublicKeys()
202
                      ->setAttestation($attestation);
203
204
        return $publicKey;
205
    }
206
207
    /**
208
     * Return the authentication parameters
209
     * @param string $userVerificationType
210
     * @param array<string> $credentialIds
211
     * @return PublicKey
212
     */
213
    public function getAuthenticationParams(
214
        string $userVerificationType,
215
        array $credentialIds = []
216
    ): PublicKey {
217
        $allowedCredentials = [];
218
        foreach ($credentialIds as $id) {
219
            $hex = hex2bin($id);
220
            if ($hex === false) {
221
                throw new WebauthnException(sprintf('Can not convert credential id [%s] to binary', $id));
222
            }
223
224
            $allowedCredentials[] = new PublicKeyAuthParam(
225
                new ByteBuffer($hex),
226
                $this->config->get('transport_types')
0 ignored issues
show
Bug introduced by
It seems like $this->config->get('transport_types') can also be of type null; however, parameter $transports of Platine\Webauthn\Entity\...uthParam::__construct() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

226
                /** @scrutinizer ignore-type */ $this->config->get('transport_types')
Loading history...
227
            );
228
        }
229
230
        $publicKey = (new PublicKey())
231
                      ->setRelyingPartyId($this->relyingParty->getId())
232
                      ->setAllowCredentials($allowedCredentials)
233
                      ->setChallenge($this->createChallenge())
234
                      ->setTimeout($this->config->get('timeout'))
0 ignored issues
show
Bug introduced by
It seems like $this->config->get('timeout') can also be of type null; however, parameter $timeout of Platine\Webauthn\Entity\PublicKey::setTimeout() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

234
                      ->setTimeout(/** @scrutinizer ignore-type */ $this->config->get('timeout'))
Loading history...
235
                      ->setUserVerificationType($userVerificationType);
236
237
        return $publicKey;
238
    }
239
240
    /**
241
     * Process the user registration
242
     * @param string $clientDataJson
243
     * @param string $attestationObject
244
     * @param string|ByteBuffer $challenge
245
     * @param bool $requireUserVerification
246
     * @param bool $requireUserPresent
247
     * @param bool $failIfRootCertificateMismatch
248
     * @return array<string, mixed>
249
     */
250
    public function processRegistration(
251
        string $clientDataJson,
252
        string $attestationObject,
253
        $challenge,
254
        bool $requireUserVerification = false,
255
        bool $requireUserPresent = true,
256
        bool $failIfRootCertificateMismatch = true
257
    ): array {
258
        $clientDataHash = hash('sha256', $clientDataJson, true);
259
        if (is_string($challenge)) {
260
            $challenge =  new ByteBuffer($challenge);
261
        }
262
263
        // security: https://www.w3.org/TR/webauthn/#registering-a-new-credential
264
        try {
265
            // 2. Let C, the client data claimed as collected during the credential creation,
266
            // be the result of running an implementation-specific JSON parser on JSONtext.
267
            $clientData = Json::decode($clientDataJson);
268
        } catch (Exception $ex) {
269
            throw new WebauthnException(sprintf('Invalid client data provided, [%s]', $ex->getMessage()));
270
        }
271
272
        // 3. Verify that the value of C.type is webauthn.create.
273
        if (! isset($clientData->type) || $clientData->type !== 'webauthn.create') {
274
            throw new WebauthnException('Invalid client type provided');
275
        }
276
277
        // 4. Verify that the value of C.challenge matches the challenge that was
278
        // sent to the authenticator in the create() call.
279
        if (
280
            ! isset($clientData->challenge) ||
281
            ByteBuffer::fromBase64Url($clientData->challenge)->getBinaryString() !== $challenge->getBinaryString()
282
        ) {
283
            throw new WebauthnException('Invalid challenge provided');
284
        }
285
286
        // 5. Verify that the value of C.origin matches the Relying Party's origin.
287
        if (! isset($clientData->origin) || $this->checkOrigin($clientData->origin) === false) {
288
            throw new WebauthnException('Invalid origin provided');
289
        }
290
291
        $attestation = new AttestationData($attestationObject, $this->formats);
292
293
        // 9. Verify that the RP ID hash in authData is indeed the SHA-256
294
        // hash of the RP ID expected by the RP.
295
        if ($attestation->validateRelyingPartyIdHash($this->relyingParty->getHashId()) === false) {
296
            throw new WebauthnException('Invalid relying party id hash provided');
297
        }
298
299
        // 14. Verify that attStmt is a correct attestation statement, conveying
300
        // a valid attestation signature
301
        if ($attestation->validateAttestation($clientDataHash) === false) {
302
            throw new WebauthnException('Invalid certificate signature');
303
        }
304
305
        // 15. If validation is successful, obtain a list of acceptable trust anchors
306
        $isRootValid = count($this->certificates) > 0
307
                ? $attestation->validateRootCertificate($this->certificates)
308
                : false;
309
310
        if ($failIfRootCertificateMismatch && count($this->certificates) > 0 && $isRootValid === false) {
311
            throw new WebauthnException('Invalid root certificate');
312
        }
313
314
        // 10. Verify that the User Present bit of the flags in authData is set.
315
        $userPresent = $attestation->getAuthenticatorData()->isUserPresent();
316
        if ($requireUserPresent && $userPresent === false) {
317
            throw new WebauthnException('User is not present during authentication');
318
        }
319
320
        // 11. If user verification is required for this registration, verify
321
        // that the User Verified bit of the flags in authData is set.
322
        $userVerified = $attestation->getAuthenticatorData()->isUserVerified();
323
        if ($requireUserVerification && $userVerified === false) {
324
            throw new WebauthnException('User is not verified during authentication');
325
        }
326
327
        $signCount = $attestation->getAuthenticatorData()->getSignatureCount();
328
        if ($signCount > 0) {
329
            $this->signatureCounter = $signCount;
330
        }
331
332
        // prepare data to store for future logins
333
        $data = [
334
            'rp_id' => $this->relyingParty->getId(),
335
            'attestation_format' => $attestation->getFormatName(),
336
            'credential_id' => bin2hex($attestation->getAuthenticatorData()->getCredentialId()),
337
            'credential_public_key' => $attestation->getAuthenticatorData()->getPublicKeyPEM(),
338
            'certificate_chain' => $attestation->getCertificateChain(),
339
            'certificate' => $attestation->getCertificatePem(),
340
            'certificate_issuer' => $attestation->getCertificateIssuer(),
341
            'certificate_subject' => $attestation->getCertificateSubject(),
342
            'root_certificate_valid' => $isRootValid,
343
            'signature_counter' => $this->signatureCounter,
344
            'aaguid' => bin2hex($attestation->getAuthenticatorData()->getAaguid()),
345
            'user_present' => $userPresent,
346
            'user_verified' => $userVerified,
347
        ];
348
349
350
        return $data;
351
    }
352
353
    /**
354
     * Process the user authentication
355
     * @param string $clientDataJson
356
     * @param string $authenticatorData
357
     * @param string $signature
358
     * @param string $credentialPublicKey
359
     * @param ByteBuffer|string $challenge
360
     * @param int|null $previousSignatureCount
361
     * @param bool $requireUserVerification
362
     * @param bool $requireUserPresent
363
     * @return bool
364
     */
365
    public function processAuthentication(
366
        string $clientDataJson,
367
        string $authenticatorData,
368
        string $signature,
369
        string $credentialPublicKey,
370
        $challenge,
371
        ?int $previousSignatureCount = null,
372
        bool $requireUserVerification = false,
373
        bool $requireUserPresent = true
374
    ): bool {
375
        if (is_string($challenge)) {
376
            $challenge =  new ByteBuffer($challenge);
377
        }
378
        $clientDataHash = hash('sha256', $clientDataJson, true);
379
        $authenticator = new AuthenticatorData($authenticatorData);
380
        try {
381
            // 5. Let JSON text be the result of running UTF-8 decode on the value of cData.
382
            $clientData = Json::decode($clientDataJson);
383
        } catch (Exception $ex) {
384
            throw new WebauthnException(sprintf('Invalid client data provided, [%s]', $ex->getMessage()));
385
        }
386
387
        // https://www.w3.org/TR/webauthn/#verifying-assertion
388
389
        // 1. If the allowCredentials option was given when this authentication ceremony was initiated,
390
        //    verify that credential.id identifies one of the public key credentials
391
        //    that were listed in allowCredentials.
392
        //    -> TO BE VERIFIED BY IMPLEMENTATION
393
394
        // 2. If credential.response.userHandle is present, verify that the user identified
395
        //    by this value is the owner of the public key credential identified by credential.id.
396
        //    -> TO BE VERIFIED BY IMPLEMENTATION
397
398
        // 3. Using credential’s id attribute (or the corresponding rawId, if base64url encoding is
399
        //    inappropriate for your use case),
400
        //    look up the corresponding credential public key.
401
        //    -> TO BE LOOKED UP BY IMPLEMENTATION
402
403
        // 7. Verify that the value of C.type is the string webauthn.get.
404
        if (! isset($clientData->type) || $clientData->type !== 'webauthn.get') {
405
            throw new WebauthnException('Invalid client type provided');
406
        }
407
408
        // 8. Verify that the value of C.challenge matches the challenge that was sent to the
409
        //    authenticator in the PublicKeyCredentialRequestOptions passed to the get() call.
410
        if (
411
            ! isset($clientData->challenge) ||
412
            ByteBuffer::fromBase64Url($clientData->challenge)->getBinaryString() !== $challenge->getBinaryString()
413
        ) {
414
            throw new WebauthnException('Invalid challenge provided');
415
        }
416
417
        // 9. Verify that the value of C.origin matches the Relying Party's origin.
418
        if (! isset($clientData->origin) || $this->checkOrigin($clientData->origin) === false) {
419
            throw new WebauthnException('Invalid origin provided');
420
        }
421
422
        // 11. Verify that the rpIdHash in authData is the SHA-256 hash
423
        // of the RP ID expected by the Relying Party.
424
        if ($authenticator->getRelyingPartyIdHash() !== $this->relyingParty->getHashId()) {
425
            throw new WebauthnException('Invalid relying party id hash provided');
426
        }
427
428
        // 12. Verify that the User Present bit of the flags in authData is set
429
        if ($requireUserPresent && $authenticator->isUserPresent() === false) {
430
            throw new WebauthnException('User is not present during authentication');
431
        }
432
433
        // 13. If user verification is required for this assertion, verify that
434
        // the User Verified bit of the flags in authData is set.
435
        if ($requireUserVerification && $authenticator->isUserVerified() === false) {
436
            throw new WebauthnException('User is not verified during authentication');
437
        }
438
439
        // 14. Verify the values of the client extension outputs
440
        // TODO    (extensions not implemented)
441
442
        // 16. Using the credential public key looked up in step 3, verify
443
        // that sig is a valid signature over the binary
444
        //  concatenation of authData and hash.
445
        $dataToVerify = '';
446
        $dataToVerify .= $authenticatorData;
447
        $dataToVerify .= $clientDataHash;
448
449
        $publicKey = openssl_pkey_get_public($credentialPublicKey);
450
        if ($publicKey === false) {
451
            throw new WebauthnException('Invalid public key provided');
452
        }
453
454
        if (
455
            openssl_verify(
456
                $dataToVerify,
457
                $signature,
458
                $publicKey,
459
                OPENSSL_ALGO_SHA256
460
            ) !== 1
461
        ) {
462
            throw new WebauthnException('Invalid signature provided');
463
        }
464
465
        $signatureCount = $authenticator->getSignatureCount();
466
        if ($signatureCount !== 0) {
467
            $this->signatureCounter = $signatureCount;
468
        }
469
470
        // 17. If either of the signature counter value authData.signCount or
471
        //     previous signature count is non-zero, and if authData.signCount
472
        //     less than or equal to previous signature count, it's a signal
473
        //     that the authenticator may be cloned
474
        if ($previousSignatureCount !== null) {
475
            if ($signatureCount !== 0 || $previousSignatureCount !== 0) {
476
                if ($previousSignatureCount >= $signatureCount) {
477
                    throw new WebauthnException('Invalid signature counter provided');
478
                }
479
            }
480
        }
481
482
        return true;
483
    }
484
485
    /**
486
     * Return the challenge
487
     * @return ByteBuffer|null
488
     */
489
    public function getChallenge(): ?ByteBuffer
490
    {
491
        return $this->challenge;
492
    }
493
494
495
    /**
496
     * Check the given origin
497
     * @param string $origin
498
     * @return bool
499
     */
500
    protected function checkOrigin(string $origin): bool
501
    {
502
        // https://www.w3.org/TR/webauthn/#rp-id
503
504
        // The origin's scheme must be https and not be ignored/whitelisted
505
        $url = new Uri($origin);
506
        if (
507
            ! in_array($this->relyingParty->getId(), $this->config->get('ignore_origins')) &&
0 ignored issues
show
Bug introduced by
It seems like $this->config->get('ignore_origins') can also be of type null; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

507
            ! in_array($this->relyingParty->getId(), /** @scrutinizer ignore-type */ $this->config->get('ignore_origins')) &&
Loading history...
508
            $url->getScheme() !== 'https'
509
        ) {
510
            return false;
511
        }
512
513
        // The RP ID must be equal to the origin's effective domain, or a registrable
514
        // domain suffix of the origin's effective domain.
515
        return preg_match('/' . preg_quote($this->relyingParty->getId()) . '$/i', $url->getHost()) === 1;
516
    }
517
518
    /**
519
     * Create the challenge if not yet created
520
     * @return ByteBuffer
521
     */
522
    protected function createChallenge(): ByteBuffer
523
    {
524
        if ($this->challenge === null) {
525
            $length = $this->config->get('challenge_length');
526
            $this->challenge = ByteBuffer::randomBuffer($length);
0 ignored issues
show
Bug introduced by
It seems like $length can also be of type null; however, parameter $length of Platine\Webauthn\Helper\ByteBuffer::randomBuffer() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

526
            $this->challenge = ByteBuffer::randomBuffer(/** @scrutinizer ignore-type */ $length);
Loading history...
527
        }
528
529
        return $this->challenge;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->challenge could return the type null which is incompatible with the type-hinted return Platine\Webauthn\Helper\ByteBuffer. Consider adding an additional type-check to rule them out.
Loading history...
530
    }
531
532
    /**
533
     * Normalize the formats
534
     * @param array<string> $formats
535
     * @return array<string>
536
     */
537
    protected function normalizeFormats(array $formats): array
538
    {
539
        $supportedFormats = KeyFormat::all();
540
        if (count($formats) === 0) {
541
            return $supportedFormats;
542
        }
543
544
        $desiredFormats = array_filter($formats, function ($entry) use ($supportedFormats) {
545
            return in_array($entry, $supportedFormats);
546
        });
547
548
        if (count($desiredFormats) > 0) {
549
            return $desiredFormats;
550
        }
551
552
        return $supportedFormats;
553
    }
554
}
555