Test Failed
Push — master ( 9eb68a...5271f3 )
by Thomas
02:48
created

AuthenticatorData::getExtensionData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 3
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 7
ccs 0
cts 0
cp 0
crap 6
rs 10
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
     * SHA-256 hash of the RP ID associated with the credential.
41
     *
42
     * @var ByteBuffer
43
     */
44
    private $rpIdHash;
45
46
    /**
47
     * @var int FLAG_* flags
48
     */
49
    private $flags;
50
51
    /**
52
     * @var int
53
     */
54
    private $signCount;
55
56
    /**
57
     * @var CoseKeyInterface|null
58
     */
59
    private $key;
60
61
    /**
62
     * @var ByteBuffer|null
63
     */
64
    private $credentialId;
65
66
    /**
67
     * @var ByteBuffer|null
68
     */
69
    private $aaguid;
70
71
    /**
72
     * @var ByteBuffer
73
     */
74
    private $raw;
75
76
    /**
77
     * @var CborMap|null Authenticator extension output
78
     */
79
    private $extensionData;
80
81
    private const LENGTH_RP_ID_HASH = 32;
82
83
    private const LENGTH_AAGUID = 16;
84
85 29
    /**
86
     * AuthenticatorData constructor.
87 29
     *
88 29
     * @throws ParseException
89
     * @throws DataValidationException
90
     */
91 29
    public function __construct(ByteBuffer $data)
92 29
    {
93
        $this->raw = $data;
94 29
        $offset = 0;
95 29
96 29
        try {
97 29
            $this->rpIdHash = new ByteBuffer($data->getBytes(0, self::LENGTH_RP_ID_HASH));
98
            $offset += self::LENGTH_RP_ID_HASH;
99 29
100 11
            $this->flags = $data->getByteVal($offset);
101 11
            $offset++;
102 11
            $this->signCount = $data->getUint32Val($offset);
103 11
            $offset += 4;
104 11
105 11
            if ($this->hasAttestedCredentialData()) {
106 11
                $this->aaguid = new ByteBuffer($data->getBytes($offset, self::LENGTH_AAGUID));
107 11
                $offset += self::LENGTH_AAGUID;
108
                $credentialIdLength = $data->getUint16Val($offset);
109
                $offset += 2;
110 29
                $this->credentialId = new ByteBuffer($data->getBytes($offset, $credentialIdLength));
111
                $offset += $credentialIdLength;
112
                $this->key = CoseKey::parseCbor($data, $offset, $endOffset);
113
                $offset = $endOffset;
114
            }
115
116
            if ($this->hasExtensionData()) {
117 29
                $extensionData = CborDecoder::decodeInPlace($data, $offset, $endOffset);
118 29
                $offset = $endOffset;
119
                if (!$extensionData instanceof CborMap) {
120
                    throw new ParseException('Expected CBOR map for extension data in authenticator data.');
121
                }
122
                $this->extensionData = $extensionData;
123
            }
124
            if ($offset !== $data->getLength()) {
125 29
                throw new ParseException('Unexpected bytes at end of AuthenticatorData.');
126
            }
127 10
        } catch (ByteBufferException $e) {
128
            throw new ParseException('Failed to parse authenticator data buffer.', 0, $e);
129 10
        } catch (CborException $e) {
130
            throw new ParseException('Failed to parse CBOR authenticator data.', 0, $e);
131
        }
132 6
    }
133
134 6
    public function getRpIdHash(): ByteBuffer
135
    {
136
        return $this->rpIdHash;
137 6
    }
138
139 6
    public function getSignCount(): int
140
    {
141
        return $this->signCount;
142 1
    }
143
144 1
    public function isUserPresent(): bool
145
    {
146
        return ($this->flags & self::FLAG_UP) !== 0;
147 29
    }
148
149 29
    public function isUserVerified(): bool
150
    {
151
        return ($this->flags & self::FLAG_UV) !== 0;
152 29
    }
153
154 29
    public function hasAttestedCredentialData(): bool
155
    {
156
        return ($this->flags & self::FLAG_AT) !== 0;
157 5
    }
158
159 5
    public function hasExtensionData(): bool
160
    {
161
        return ($this->flags & self::FLAG_ED) !== 0;
162
    }
163
164
    public function getCredentialId(): ?ByteBuffer
165
    {
166
        return $this->credentialId;
167 8
    }
168
169 8
    /**
170
     * @throws NotAvailableException when authenticator data does not contain a key.
171
     *
172 8
     * @see hasKey
173
     */
174
    public function getKey(): CoseKeyInterface
175 2
    {
176
        if ($this->key === null) {
177 2
            throw new NotAvailableException('AuthenticatorData does not contain a key.');
178
        }
179
        return $this->key;
180
    }
181
182
    public function hasKey(): bool
183
    {
184
        return $this->key !== null;
185 6
    }
186
187 6
    public function hasAaguid(): bool
188
    {
189
        return $this->aaguid !== null;
190 6
    }
191
192
    public function getAaguid(): Aaguid
193 6
    {
194
        if ($this->aaguid === null) {
195 6
            throw new NotAvailableException('AuthenticatorData does not contain an AAGUID.');
196
        }
197
        return new Aaguid($this->aaguid);
198
    }
199
200
    /**
201
     * Returns the authenticator extension data output.
202
     */
203
    public function getExtensionData(): CborMap
204
    {
205
        if ($this->extensionData === null) {
206
            throw new NotAvailableException('AuthenticatorData does not contain extension data.');
207
        }
208
209
        return $this->extensionData;
210
    }
211
212
    public function getRaw(): ByteBuffer
213
    {
214
        return $this->raw;
215
    }
216
}
217