Passed
Pull Request — master (#21)
by
unknown
02:31
created

AuthenticatorData::__construct()   B

Complexity

Conditions 7
Paths 48

Size

Total Lines 40
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 8.323

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 30
c 1
b 0
f 0
nc 48
nop 1
dl 0
loc 40
ccs 21
cts 30
cp 0.7
crap 8.323
rs 8.5066
1
<?php
2
3
namespace MadWizard\WebAuthn\Attestation;
4
5
use MadWizard\WebAuthn\Attestation\Identifier\Aaguid;
6
use MadWizard\WebAuthn\Crypto\CoseKey;
7
use MadWizard\WebAuthn\Crypto\CoseKeyInterface;
8
use MadWizard\WebAuthn\Exception\ByteBufferException;
9
use MadWizard\WebAuthn\Exception\CborException;
10
use MadWizard\WebAuthn\Exception\DataValidationException;
11
use MadWizard\WebAuthn\Exception\NotAvailableException;
12
use MadWizard\WebAuthn\Exception\ParseException;
13
use MadWizard\WebAuthn\Format\ByteBuffer;
14
use MadWizard\WebAuthn\Format\CborDecoder;
15
use MadWizard\WebAuthn\Format\CborMap;
16
17
final class AuthenticatorData
18
{
19
    /**
20
     * User present (UP).
21
     */
22
    private const FLAG_UP = 1 << 0;
23
24
    /**
25
     * User verified (UV).
26
     */
27
    private const FLAG_UV = 1 << 2;
28
29
    /**
30
     * Attested credential data included (AT).
31
     */
32
    private const FLAG_AT = 1 << 6;
33
34
    /**
35
     * Extension data included (ED).
36
     */
37
    private const FLAG_ED = 1 << 7;
38
    
39
     /**
40
     * Backup Eligibility (BE).
41
     */
42
    private const FLAG_BE = 1 << 3;
43
    
44
     /**
45
     * Backup State (BS).
46
     */
47
    private const FLAG_BS = 1 << 4;
48
49
    /**
50
     * SHA-256 hash of the RP ID associated with the credential.
51
     *
52
     * @var ByteBuffer
53
     */
54
    private $rpIdHash;
55
56
    /**
57
     * @var int FLAG_* flags
58
     */
59
    private $flags;
60
61
    /**
62
     * @var int
63
     */
64
    private $signCount;
65
66
    /**
67
     * @var CoseKeyInterface|null
68
     */
69
    private $key;
70
71
    /**
72
     * @var ByteBuffer|null
73
     */
74
    private $credentialId;
75
76
    /**
77
     * @var ByteBuffer|null
78
     */
79
    private $aaguid;
80
81
    /**
82
     * @var ByteBuffer
83
     */
84
    private $raw;
85
86
    /**
87
     * @var CborMap|null Authenticator extension output
88
     */
89
    private $extensionData;
90
91
    private const LENGTH_RP_ID_HASH = 32;
92
93
    private const LENGTH_AAGUID = 16;
94
95
    /**
96
     * AuthenticatorData constructor.
97
     *
98
     * @throws ParseException
99
     * @throws DataValidationException
100
     */
101 27
    public function __construct(ByteBuffer $data)
102
    {
103 27
        $this->raw = $data;
104 27
        $offset = 0;
105
106
        try {
107 27
            $this->rpIdHash = new ByteBuffer($data->getBytes(0, self::LENGTH_RP_ID_HASH));
108 27
            $offset += self::LENGTH_RP_ID_HASH;
109
110 27
            $this->flags = $data->getByteVal($offset);
111 27
            $offset++;
112 27
            $this->signCount = $data->getUint32Val($offset);
113 27
            $offset += 4;
114
115 27
            if ($this->hasAttestedCredentialData()) {
116 12
                $this->aaguid = new ByteBuffer($data->getBytes($offset, self::LENGTH_AAGUID));
117 12
                $offset += self::LENGTH_AAGUID;
118 12
                $credentialIdLength = $data->getUint16Val($offset);
119 12
                $offset += 2;
120 12
                $this->credentialId = new ByteBuffer($data->getBytes($offset, $credentialIdLength));
121 12
                $offset += $credentialIdLength;
122 12
                $this->key = CoseKey::parseCbor($data, $offset, $endOffset);
123 12
                $offset = $endOffset;
124
            }
125
126 27
            if ($this->hasExtensionData()) {
127
                $extensionData = CborDecoder::decodeInPlace($data, $offset, $endOffset);
128
                $offset = $endOffset;
129
                if (!$extensionData instanceof CborMap) {
130
                    throw new ParseException('Expected CBOR map for extension data in authenticator data.');
131
                }
132
                $this->extensionData = $extensionData;
133
            }
134 27
            if ($offset !== $data->getLength()) {
135 27
                throw new ParseException('Unexpected bytes at end of AuthenticatorData.');
136
            }
137
        } catch (ByteBufferException $e) {
138
            throw new ParseException('Failed to parse authenticator data buffer.', 0, $e);
139
        } catch (CborException $e) {
140
            throw new ParseException('Failed to parse CBOR authenticator data.', 0, $e);
141
        }
142 27
    }
143
144 10
    public function getRpIdHash(): ByteBuffer
145
    {
146 10
        return $this->rpIdHash;
147
    }
148
149 5
    public function getSignCount(): int
150
    {
151 5
        return $this->signCount;
152
    }
153
154 6
    public function isUserPresent(): bool
155
    {
156 6
        return ($this->flags & self::FLAG_UP) !== 0;
157
    }
158
159 1
    public function isUserVerified(): bool
160
    {
161 1
        return ($this->flags & self::FLAG_UV) !== 0;
162
    }
163
    
164
    public function isBackupEligable(): bool
165
    {
166
        return ($this->flags & self::FLAG_BE) !== 0;
167
    }
168
169
    public function isBackedUp(): bool
170
    {
171
        return ($this->flags & self::FLAG_BS) !== 0;
172
    }
173
174 27
    public function hasAttestedCredentialData(): bool
175
    {
176 27
        return ($this->flags & self::FLAG_AT) !== 0;
177
    }
178
179 27
    public function hasExtensionData(): bool
180
    {
181 27
        return ($this->flags & self::FLAG_ED) !== 0;
182
    }
183
184 5
    public function getCredentialId(): ?ByteBuffer
185
    {
186 5
        return $this->credentialId;
187
    }
188
189
    /**
190
     * @throws NotAvailableException when authenticator data does not contain a key.
191
     *
192
     * @see hasKey
193
     */
194 9
    public function getKey(): CoseKeyInterface
195
    {
196 9
        if ($this->key === null) {
197
            throw new NotAvailableException('AuthenticatorData does not contain a key.');
198
        }
199 9
        return $this->key;
200
    }
201
202 2
    public function hasKey(): bool
203
    {
204 2
        return $this->key !== null;
205
    }
206
207
    public function hasAaguid(): bool
208
    {
209
        return $this->aaguid !== null;
210
    }
211
212 6
    public function getAaguid(): Aaguid
213
    {
214 6
        if ($this->aaguid === null) {
215
            throw new NotAvailableException('AuthenticatorData does not contain an AAGUID.');
216
        }
217 6
        return new Aaguid($this->aaguid);
218
    }
219
220
    /**
221
     * Returns the authenticator extension data output.
222
     */
223
    public function getExtensionData(): CborMap
224
    {
225
        if ($this->extensionData === null) {
226
            throw new NotAvailableException('AuthenticatorData does not contain extension data.');
227
        }
228
229
        return $this->extensionData;
230
    }
231
232 7
    public function getRaw(): ByteBuffer
233
    {
234 7
        return $this->raw;
235
    }
236
}
237