Completed
Push — master ( 234d27...d96c64 )
by Florent
02:47
created

createAuthenticatorResponse()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 1
dl 0
loc 13
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2018 Spomky-Labs
9
 *
10
 * This software may be modified and distributed under the terms
11
 * of the MIT license.  See the LICENSE file for details.
12
 */
13
14
namespace U2FAuthentication\Fido2;
15
16
use Base64Url\Base64Url;
17
use CBOR\Decoder;
18
use U2FAuthentication\Fido2\AttestationStatement\AttestationObjectLoader;
19
20
class PublicKeyCredentialLoader
21
{
22
    private const FLAG_AT = 0b01000000;
23
    private const FLAG_ED = 0b10000000;
24
25
    private $decoder;
26
27
    private $attestationObjectLoader;
28
29
    public function __construct(Decoder $decoder, AttestationObjectLoader $attestationObjectLoader)
30
    {
31
        $this->decoder = $decoder;
32
        $this->attestationObjectLoader = $attestationObjectLoader;
33
    }
34
35
    public function load(string $data): PublicKeyCredential
36
    {
37
        $json = json_decode($data, true);
38
        if (!array_key_exists('id', $json)) {
39
            throw new \InvalidArgumentException();
40
        }
41
        $id = Base64Url::decode($json['id']);
42
        if (!array_key_exists('rawId', $json)) {
43
            throw new \InvalidArgumentException();
44
        }
45
        $rawId = Base64Url::decode($json['rawId']);
46
        if (!array_key_exists('type', $json)) {
47
            throw new \InvalidArgumentException();
48
        }
49
        if (!hash_equals($id, $rawId)) {
50
            throw new \InvalidArgumentException();
51
        }
52
        if (!array_key_exists('response', $json)) {
53
            throw new \InvalidArgumentException();
54
        }
55
56
        $publicKeyCredential = new PublicKeyCredential(
57
            $json['id'],
58
            $json['type'],
59
            $rawId,
60
            $this->createResponse($json['response'])
61
        );
62
63
        return $publicKeyCredential;
64
    }
65
66
    private function createResponse(array $response): AuthenticatorResponse
67
    {
68
        if (!array_key_exists('clientDataJSON', $response)) {
69
            throw new \InvalidArgumentException();
70
        }
71
        if (array_key_exists('attestationObject', $response)) {
72
            $attestationObject = $this->attestationObjectLoader->load($response['attestationObject']);
73
74
            return new AuthenticatorAttestationResponse(
75
                CollectedClientData::createFormJson($response['clientDataJSON']),
76
                $attestationObject
77
            );
78
        }
79
        if (array_key_exists('authenticatorData', $response) && array_key_exists('signature', $response)) {
80
            $authData = Base64Url::decode($response['authenticatorData']);
81
82
            $authDataStream = new StringStream($authData);
83
            $rp_id_hash = $authDataStream->read(32);
84
            $flags = $authDataStream->read(1);
85
            $signCount = $authDataStream->read(4);
86
            $signCount = unpack('N', $signCount)[1];
87
88
            if (\ord($flags) & self::FLAG_AT) {
89
                $aaguid = $authDataStream->read(16);
90
                $credentialLength = $authDataStream->read(2);
91
                $credentialLength = unpack('n', $credentialLength)[1];
92
                $credentialId = $authDataStream->read($credentialLength);
93
                $credentialPublicKey = $this->decoder->decode($authDataStream);
94
                $attestedCredentialData = new AttestedCredentialData($aaguid, $credentialId, $credentialPublicKey);
0 ignored issues
show
Bug introduced by
It seems like $credentialPublicKey can also be of type CBOR\InfiniteListObject and CBOR\TagObject and CBOR\OtherObject and CBOR\InfiniteMapObject and CBOR\UnsignedIntegerObject and CBOR\ByteStringObject and CBOR\TextStringWithChunkObject and CBOR\TextStringObject and CBOR\SignedIntegerObject and CBOR\ByteStringWithChunkObject and CBOR\ListObject; however, parameter $credentialPublicKey of U2FAuthentication\Fido2\...tialData::__construct() does only seem to accept null|CBOR\MapObject, 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

94
                $attestedCredentialData = new AttestedCredentialData($aaguid, $credentialId, /** @scrutinizer ignore-type */ $credentialPublicKey);
Loading history...
95
            } else {
96
                $attestedCredentialData = null;
97
            }
98
99
            if (\ord($flags) & self::FLAG_ED) {
100
                $extension = $this->decoder->decode($authDataStream);
101
            } else {
102
                $extension = null;
103
            }
104
            $authenticatorData = new AuthenticatorData(
105
                $rp_id_hash,
106
                $flags,
107
                $signCount,
108
                $attestedCredentialData,
109
                $extension
110
            );
111
112
            return new AuthenticatorAssertionResponse(
113
                CollectedClientData::createFormJson($response['clientDataJSON']),
114
                $authenticatorData,
115
                Base64Url::decode($response['signature']),
116
                $response['userHandle'] ?? null
117
            );
118
        }
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 79 is false. This is incompatible with the type-hinted return U2FAuthentication\Fido2\AuthenticatorResponse. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
119
    }
120
}
121