MetadataStatement   A
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 307
Duplicated Lines 0 %

Test Coverage

Coverage 77.53%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 102
c 1
b 0
f 0
dl 0
loc 307
ccs 69
cts 89
cp 0.7753
rs 9.44
wmc 37

19 Methods

Rating   Name   Duplication   Size   Complexity  
A getAttestationTypes() 0 3 1
A getDescription() 0 3 1
A getTrustAnchors() 0 7 2
A decodeString() 0 7 2
A validateKeyIdentifiers() 0 11 3
A validateAaid() 0 6 2
A supportsAttestationType() 0 7 2
A getStatusReports() 0 3 1
A decodeJson() 0 46 2
A validateAttestationTypes() 0 8 3
A setStatusReports() 0 3 1
A getAttestationRootCertificates() 0 3 1
A getAaguid() 0 3 1
A getAaid() 0 3 1
A validateAaguid() 0 6 2
A getIdentifiers() 0 15 4
A getAttestationCertificateKeyIdentifiers() 0 3 1
A parseRootCertificates() 0 11 4
A matchesIdentifier() 0 8 3
1
<?php
2
3
namespace MadWizard\WebAuthn\Metadata\Statement;
4
5
use MadWizard\WebAuthn\Attestation\AttestationType;
6
use MadWizard\WebAuthn\Attestation\Identifier\Aaguid;
7
use MadWizard\WebAuthn\Attestation\Identifier\Aaid;
8
use MadWizard\WebAuthn\Attestation\Identifier\AttestationKeyIdentifier;
9
use MadWizard\WebAuthn\Attestation\Identifier\IdentifierInterface;
10
use MadWizard\WebAuthn\Attestation\TrustAnchor\CertificateTrustAnchor;
11
use MadWizard\WebAuthn\Attestation\TrustAnchor\MetadataInterface;
12
use MadWizard\WebAuthn\Exception\ParseException;
13
use MadWizard\WebAuthn\Format\DataValidator;
14
use MadWizard\WebAuthn\Pki\X509Certificate;
15
use function base64_decode;
16
use function in_array;
17
18
final class MetadataStatement implements MetadataInterface
19
{
20
    /** @var Aaid|null */
21
    private $aaid;
22
23
    /** @var Aaguid|null */
24
    private $aaguid;
25
26
    /** @var AttestationKeyIdentifier[]|null */
27
    private $attestationCertificateKeyIdentifiers;
28
29
    /** @var string */
30
    private $description;
31
32
//    /** @var int */
33
//    private $authenticatorVersion;
34
//
35
//    /** @var null|string */
36
//    private $protocolFamily;
37
//
38
//    /** @var Version[] */
39
//    private $upv;
40
//
41
//    /** @var string */
42
//    private $assertionScheme;
43
//
44
45
    /** @var int */
46
    private $authenticationAlgorithm;
47
48
//
49
//    /** @var int */
50
//    private $publicKeyAlgAndEncoding;
51
//
52
53
    /** @var int[] */
54
    private $attestationTypes;
55
56
//
57
//    /** @var VerificationMethodDescriptor[] */
58
//    private $userVerificationDetails;
59
//
60
//    /** @var int */
61
//    private $keyProtection;
62
//
63
//    /** @var null|boolean */
64
//    private $isKeyRestricted;
65
//
66
//    /** @var null|boolean */
67
//    private $isFreshUserVerificationRequired;
68
//
69
//    /** @var int */
70
//    private $matcherProtection;
71
//
72
//    /** @var int */
73
//    private $attachmentHint;         // TODO 32-bit unsigned problem?
74
//
75
//    /** @var boolean */
76
//    private $isSecondFactorOnly;
77
//
78
//    /** @var int */
79
//    private $tcDisplay;
80
//
81
//    /** @var null|string */
82
//    private $tcDisplayContentType;
83
//
84
//    /** @var null|DisplayPNGCharacteristicsDescriptor[] */
85
//    private $tcDisplayPNGCharacteristics;
86
//
87
88
    /** @var string[] */
89
    private $attestationRootCertificates;
90
91
//
92
//    /** @var null|EcdaaTrustAnchor[] */
93
//    private $ecdaaTrustAnchors;
94
//
95
//    /** @var null|string */
96
//    private $icon;
97
//
98
//    /** @var null|ExtensionDescriptor[] */
99
//    private $supportedExtensions;
100
101
    /**
102
     * @var StatusReport[]
103
     */
104
    private $statusReports = [];
105
106 1
    public static function decodeString(string $json): self
107
    {
108 1
        $data = \json_decode($json, true, 20);
109 1
        if (!\is_array($data)) {
110
            throw new ParseException('Invalid JSON metadata statement.');
111
        }
112 1
        return self::decodeJson($data);
113
    }
114
115 1
    public static function decodeJson(array $data): self
116
    {
117 1
        if (is_string($data['isSecondFactorOnly'] ?? null)) {
118
            $data['isSecondFactorOnly'] = (bool) $data['isSecondFactorOnly']; // TODO
119
        }
120 1
        DataValidator::checkArray(
121 1
            $data,
122
            [
123 1
                'aaid' => '?string',   // !!!!
124
                'aaguid' => '?string', // !!!!
125
                'attestationCertificateKeyIdentifiers' => '?array',  // !!!!
126
                'description' => 'string',
127
                'authenticatorVersion' => 'integer',
128
                'protocolFamily' => '?string',
129
                'upv' => 'array', // !!!!
130
                'assertionScheme' => 'string',
131
                'authenticationAlgorithm' => 'integer',
132
                'publicKeyAlgAndEncoding' => 'integer',
133
                'attestationTypes' => 'array',  // !!!!
134
                'userVerificationDetails' => 'array', // !!!!
135
                'keyProtection' => 'integer',
136
                'isKeyRestricted' => '?boolean',
137
                'isFreshUserVerificationRequired' => '?boolean',
138
                'matcherProtection' => 'integer',
139
                'attachmentHint' => 'integer',
140
                'isSecondFactorOnly' => 'boolean',
141
                'tcDisplay' => 'integer',
142
                'tcDisplayContentType' => '?string',
143
                'tcDisplayPNGCharacteristics' => '?array',  // !!!!
144
                'attestationRootCertificates' => 'array', // !!!!
145
                'ecdaaTrustAnchors' => '?array', // !!!!
146
                'icon' => '?string',
147
                'supportedExtensions[]' => '?array', // !!!!
148
            ],
149 1
            false
150
        );
151
152 1
        $statement = new self();
153 1
        $statement->aaid = self::validateAaid($data['aaid'] ?? null);
154 1
        $statement->aaguid = self::validateAaguid($data['aaguid'] ?? null);
155 1
        $statement->attestationCertificateKeyIdentifiers = self::validateKeyIdentifiers($data['attestationCertificateKeyIdentifiers'] ?? null);
156 1
        $statement->attestationTypes = self::validateAttestationTypes($data['attestationTypes']);
157 1
        $statement->description = $data['description'];
158 1
        $statement->authenticationAlgorithm = $data['authenticationAlgorithm'];
159 1
        $statement->attestationRootCertificates = self::parseRootCertificates($data['attestationRootCertificates']);
160 1
        return $statement;
161
    }
162
163 1
    private static function validateAaguid(?string $aaguid): ?Aaguid
164
    {
165 1
        if ($aaguid === null) {
166
            return null;
167
        }
168 1
        return Aaguid::parseString($aaguid);
169
    }
170
171 1
    private static function validateAaid(?string $aaid): ?Aaid
172
    {
173 1
        if ($aaid === null) {
174 1
            return null;
175
        }
176
        return new Aaid($aaid);
177
    }
178
179 1
    private static function validateKeyIdentifiers(?array $list): ?array
180
    {
181 1
        if ($list === null) {
0 ignored issues
show
introduced by
The condition $list === null is always false.
Loading history...
182 1
            return null;
183
        }
184
185
        $result = [];
186
        foreach ($list as $item) {
187
            $result[] = new AttestationKeyIdentifier($item);
188
        }
189
        return $result;
190
    }
191
192 1
    private static function validateAttestationTypes(array $types): array
193
    {
194 1
        foreach ($types as $type) {
195 1
            if (!is_int($type)) {
196
                throw new ParseException('Invalid attestation type in attestationTypes');
197
            }
198
        }
199 1
        return $types;
200
    }
201
202 1
    private static function parseRootCertificates(array $list): array
203
    {
204 1
        foreach ($list as $item) {
205 1
            if (!is_string($item)) {
206
                throw new ParseException('Expecting string in attestationRootCertificates.');
207
            }
208 1
            if (@base64_decode($item, true) === false) {
209
                throw new ParseException('Invalid base64 encoded string in attestationRootCertificates.');
210
            }
211
        }
212 1
        return $list;
213
    }
214
215
    /**
216
     * @return IdentifierInterface[]
217
     */
218 1
    public function getIdentifiers(): array
219
    {
220 1
        $ids = [];
221 1
        if ($this->aaguid !== null) {
222 1
            $ids[] = $this->aaguid;
223
        }
224
225 1
        if ($this->aaid !== null) {
226
            $ids[] = $this->aaid;
227
        }
228 1
        $keyIds = $this->attestationCertificateKeyIdentifiers;
229 1
        if ($keyIds !== null) {
230
            $ids = array_merge($ids, $keyIds);
231
        }
232 1
        return $ids;
233
    }
234
235 1
    public function matchesIdentifier(IdentifierInterface $identifier): bool
236
    {
237 1
        foreach ($this->getIdentifiers() as $candidate) {
238 1
            if ($identifier->equals($candidate)) {
239 1
                return true;
240
            }
241
        }
242
        return false;
243
    }
244
245 1
    public function getAaid(): ?Aaid
246
    {
247 1
        return $this->aaid;
248
    }
249
250 1
    public function getAaguid(): ?Aaguid
251
    {
252 1
        return $this->aaguid;
253
    }
254
255
    /**
256
     * @return AttestationKeyIdentifier[]|null
257
     */
258 1
    public function getAttestationCertificateKeyIdentifiers(): ?array
259
    {
260 1
        return $this->attestationCertificateKeyIdentifiers;
261
    }
262
263
    /**
264
     * @return int[]
265
     *
266
     * @see AttestationConstant
267
     */
268
    public function getAttestationTypes(): array
269
    {
270
        return $this->attestationTypes;
271
    }
272
273
    /**
274
     * @param string $type Attestation type
275
     *
276
     * @return bool True if the authenticator supports the given attestation type.
277
     *
278
     * @see AttestationType
279
     */
280 1
    public function supportsAttestationType(string $type): bool
281
    {
282 1
        $intType = AttestationConstant::convertType($type);
283 1
        if ($intType === null) {
284 1
            return false;
285
        }
286 1
        return in_array($intType, $this->attestationTypes, true);
287
    }
288
289 1
    public function getDescription(): string
290
    {
291 1
        return $this->description;
292
    }
293
294
    /**
295
     * @return string[]
296
     */
297 1
    public function getAttestationRootCertificates(): array
298
    {
299 1
        return $this->attestationRootCertificates;
300
    }
301
302 1
    public function getTrustAnchors(): array
303
    {
304 1
        $trustAnchors = [];
305 1
        foreach ($this->getAttestationRootCertificates() as $pem) {
306 1
            $trustAnchors[] = new CertificateTrustAnchor(X509Certificate::fromBase64($pem));
307
        }
308 1
        return $trustAnchors;
309
    }
310
311
    /**
312
     * @return StatusReport[]
313
     */
314
    public function getStatusReports(): array
315
    {
316
        return $this->statusReports;
317
    }
318
319
    /**
320
     * @param StatusReport[] $statusReports
321
     */
322
    public function setStatusReports(array $statusReports): void
323
    {
324
        $this->statusReports = $statusReports;
325
    }
326
}
327