1<?php
2
3declare(strict_types=1);
4
5/*
6 * The MIT License (MIT)
7 *
8 * Copyright (c) 2014-2021 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
14namespace Webauthn\MetadataService;
15
16use Assert\Assertion;
17use InvalidArgumentException;
18use JsonSerializable;
19use function Safe\json_decode;
20use function Safe\sprintf;
21
22class MetadataStatement implements JsonSerializable
23{
24    public const KEY_PROTECTION_SOFTWARE = 0x0001;
25    public const KEY_PROTECTION_HARDWARE = 0x0002;
26    public const KEY_PROTECTION_TEE = 0x0004;
27    public const KEY_PROTECTION_SECURE_ELEMENT = 0x0008;
28    public const KEY_PROTECTION_REMOTE_HANDLE = 0x0010;
29
30    public const MATCHER_PROTECTION_SOFTWARE = 0x0001;
31    public const MATCHER_PROTECTION_TEE = 0x0002;
32    public const MATCHER_PROTECTION_ON_CHIP = 0x0004;
33
34    public const ATTACHMENT_HINT_INTERNAL = 0x0001;
35    public const ATTACHMENT_HINT_EXTERNAL = 0x0002;
36    public const ATTACHMENT_HINT_WIRED = 0x0004;
37    public const ATTACHMENT_HINT_WIRELESS = 0x0008;
38    public const ATTACHMENT_HINT_NFC = 0x0010;
39    public const ATTACHMENT_HINT_BLUETOOTH = 0x0020;
40    public const ATTACHMENT_HINT_NETWORK = 0x0040;
41    public const ATTACHMENT_HINT_READY = 0x0080;
42    public const ATTACHMENT_HINT_WIFI_DIRECT = 0x0100;
43
44    public const TRANSACTION_CONFIRMATION_DISPLAY_ANY = 0x0001;
45    public const TRANSACTION_CONFIRMATION_DISPLAY_PRIVILEGED_SOFTWARE = 0x0002;
46    public const TRANSACTION_CONFIRMATION_DISPLAY_TEE = 0x0004;
47    public const TRANSACTION_CONFIRMATION_DISPLAY_HARDWARE = 0x0008;
48    public const TRANSACTION_CONFIRMATION_DISPLAY_REMOTE = 0x0010;
49
50    public const ALG_SIGN_SECP256R1_ECDSA_SHA256_RAW = 0x0001;
51    public const ALG_SIGN_SECP256R1_ECDSA_SHA256_DER = 0x0002;
52    public const ALG_SIGN_RSASSA_PSS_SHA256_RAW = 0x0003;
53    public const ALG_SIGN_RSASSA_PSS_SHA256_DER = 0x0004;
54    public const ALG_SIGN_SECP256K1_ECDSA_SHA256_RAW = 0x0005;
55    public const ALG_SIGN_SECP256K1_ECDSA_SHA256_DER = 0x0006;
56    public const ALG_SIGN_SM2_SM3_RAW = 0x0007;
57    public const ALG_SIGN_RSA_EMSA_PKCS1_SHA256_RAW = 0x0008;
58    public const ALG_SIGN_RSA_EMSA_PKCS1_SHA256_DER = 0x0009;
59    public const ALG_SIGN_RSASSA_PSS_SHA384_RAW = 0x000A;
60    public const ALG_SIGN_RSASSA_PSS_SHA512_RAW = 0x000B;
61    public const ALG_SIGN_RSASSA_PKCSV15_SHA256_RAW = 0x000C;
62    public const ALG_SIGN_RSASSA_PKCSV15_SHA384_RAW = 0x000D;
63    public const ALG_SIGN_RSASSA_PKCSV15_SHA512_RAW = 0x000E;
64    public const ALG_SIGN_RSASSA_PKCSV15_SHA1_RAW = 0x000F;
65    public const ALG_SIGN_SECP384R1_ECDSA_SHA384_RAW = 0x0010;
66    public const ALG_SIGN_SECP521R1_ECDSA_SHA512_RAW = 0x0011;
67    public const ALG_SIGN_ED25519_EDDSA_SHA256_RAW = 0x0012;
68
69    public const ALG_KEY_ECC_X962_RAW = 0x0100;
70    public const ALG_KEY_ECC_X962_DER = 0x0101;
71    public const ALG_KEY_RSA_2048_RAW = 0x0102;
72    public const ALG_KEY_RSA_2048_DER = 0x0103;
73    public const ALG_KEY_COSE = 0x0104;
74
75    public const ATTESTATION_BASIC_FULL = 0x3E07;
76    public const ATTESTATION_BASIC_SURROGATE = 0x3E08;
77    public const ATTESTATION_ECDAA = 0x3E09;
78    public const ATTESTATION_ATTCA = 0x3E0A;
79
80    /**
81     * @var string|null
82     */
83    private $legalHeader;
84
85    /**
86     * @var string|null
87     */
88    private $aaid;
89
90    /**
91     * @var string|null
92     */
93    private $aaguid;
94    /**
95     * @var string[]
96     */
97    private $attestationCertificateKeyIdentifiers = [];
98
99    /**
100     * @var string
101     */
102    private $description;
103
104    /**
105     * @var string[]
106     */
107    private $alternativeDescriptions = [];
108
109    /**
110     * @var int
111     */
112    private $authenticatorVersion;
113
114    /**
115     * @var string
116     */
117    private $protocolFamily;
118
119    /**
120     * @var Version[]
121     */
122    private $upv = [];
123
124    /**
125     * @var string|null
126     */
127    private $assertionScheme;
128
129    /**
130     * @var int|null
131     */
132    private $authenticationAlgorithm;
133
134    /**
135     * @var int[]
136     */
137    private $authenticationAlgorithms = [];
138
139    /**
140     * @var int|null
141     */
142    private $publicKeyAlgAndEncoding;
143
144    /**
145     * @var int[]
146     */
147    private $publicKeyAlgAndEncodings = [];
148
149    /**
150     * @var int[]
151     */
152    private $attestationTypes = [];
153
154    /**
155     * @var VerificationMethodANDCombinations[]
156     */
157    private $userVerificationDetails = [];
158
159    /**
160     * @var int
161     */
162    private $keyProtection;
163
164    /**
165     * @var bool|null
166     */
167    private $isKeyRestricted;
168
169    /**
170     * @var bool|null
171     */
172    private $isFreshUserVerificationRequired;
173
174    /**
175     * @var int
176     */
177    private $matcherProtection;
178
179    /**
180     * @var int|null
181     */
182    private $cryptoStrength;
183
184    /**
185     * @var string|null
186     */
187    private $operatingEnv;
188
189    /**
190     * @var int
191     */
192    private $attachmentHint = 0;
193
194    /**
195     * @var bool|null
196     */
197    private $isSecondFactorOnly;
198
199    /**
200     * @var int
201     */
202    private $tcDisplay;
203
204    /**
205     * @var string|null
206     */
207    private $tcDisplayContentType;
208
209    /**
210     * @var DisplayPNGCharacteristicsDescriptor[]
211     */
212    private $tcDisplayPNGCharacteristics = [];
213
214    /**
215     * @var string[]
216     */
217    private $attestationRootCertificates = [];
218
219    /**
220     * @var EcdaaTrustAnchor[]
221     */
222    private $ecdaaTrustAnchors = [];
223
224    /**
225     * @var string|null
226     */
227    private $icon;
228
229    /**
230     * @var ExtensionDescriptor[]
231     */
232    private $supportedExtensions = [];
233
234    /**
235     * @var array<int, StatusReport>
236     */
237    private $statusReports = [];
238
239    /**
240     * @var string[]
241     */
242    private $rootCertificates = [];
243
244    public static function createFromString(string $statement): self
245    {
246        $data = json_decode($statement, true);
247        Assertion::isArray($data, 'Invalid Metadata Statement');
248
249        return self::createFromArray($data);
250    }
251
252    public function getLegalHeader(): ?string
253    {
254        return $this->legalHeader;
255    }
256
257    public function getAaid(): ?string
258    {
259        return $this->aaid;
260    }
261
262    public function getAaguid(): ?string
263    {
264        return $this->aaguid;
265    }
266
267    /**
268     * @return string[]
269     */
270    public function getAttestationCertificateKeyIdentifiers(): array
271    {
272        return $this->attestationCertificateKeyIdentifiers;
273    }
274
275    public function getDescription(): string
276    {
277        return $this->description;
278    }
279
280    /**
281     * @return string[]
282     */
283    public function getAlternativeDescriptions(): array
284    {
285        return $this->alternativeDescriptions;
286    }
287
288    public function getAuthenticatorVersion(): int
289    {
290        return $this->authenticatorVersion;
291    }
292
293    public function getProtocolFamily(): string
294    {
295        return $this->protocolFamily;
296    }
297
298    /**
299     * @return Version[]
300     */
301    public function getUpv(): array
302    {
303        return $this->upv;
304    }
305
306    public function getAssertionScheme(): ?string
307    {
308        return $this->assertionScheme;
309    }
310
311    public function getAuthenticationAlgorithm(): ?int
312    {
313        return $this->authenticationAlgorithm;
314    }
315
316    /**
317     * @return int[]
318     */
319    public function getAuthenticationAlgorithms(): array
320    {
321        return $this->authenticationAlgorithms;
322    }
323
324    public function getPublicKeyAlgAndEncoding(): ?int
325    {
326        return $this->publicKeyAlgAndEncoding;
327    }
328
329    /**
330     * @return int[]
331     */
332    public function getPublicKeyAlgAndEncodings(): array
333    {
334        return $this->publicKeyAlgAndEncodings;
335    }
336
337    /**
338     * @return int[]
339     */
340    public function getAttestationTypes(): array
341    {
342        return $this->attestationTypes;
343    }
344
345    /**
346     * @return VerificationMethodANDCombinations[]
347     */
348    public function getUserVerificationDetails(): array
349    {
350        return $this->userVerificationDetails;
351    }
352
353    public function getKeyProtection(): int
354    {
355        return $this->keyProtection;
356    }
357
358    public function isKeyRestricted(): ?bool
359    {
360        return (bool) $this->isKeyRestricted;
361    }
362
363    public function isFreshUserVerificationRequired(): ?bool
364    {
365        return (bool) $this->isFreshUserVerificationRequired;
366    }
367
368    public function getMatcherProtection(): int
369    {
370        return $this->matcherProtection;
371    }
372
373    public function getCryptoStrength(): ?int
374    {
375        return $this->cryptoStrength;
376    }
377
378    public function getOperatingEnv(): ?string
379    {
380        return $this->operatingEnv;
381    }
382
383    public function getAttachmentHint(): int
384    {
385        return $this->attachmentHint;
386    }
387
388    public function isSecondFactorOnly(): ?bool
389    {
390        return (bool) $this->isSecondFactorOnly;
391    }
392
393    public function getTcDisplay(): int
394    {
395        return $this->tcDisplay;
396    }
397
398    public function getTcDisplayContentType(): ?string
399    {
400        return $this->tcDisplayContentType;
401    }
402
403    /**
404     * @return DisplayPNGCharacteristicsDescriptor[]
405     */
406    public function getTcDisplayPNGCharacteristics(): array
407    {
408        return $this->tcDisplayPNGCharacteristics;
409    }
410
411    /**
412     * @return string[]
413     */
414    public function getAttestationRootCertificates(): array
415    {
416        return $this->attestationRootCertificates;
417    }
418
419    /**
420     * @return EcdaaTrustAnchor[]
421     */
422    public function getEcdaaTrustAnchors(): array
423    {
424        return $this->ecdaaTrustAnchors;
425    }
426
427    public function getIcon(): ?string
428    {
429        return $this->icon;
430    }
431
432    /**
433     * @return ExtensionDescriptor[]
434     */
435    public function getSupportedExtensions(): array
436    {
437        return $this->supportedExtensions;
438    }
439
440    public static function createFromArray(array $data): self
441    {
442        $object = new self();
443        foreach (['description', 'protocolFamily'] as $key) {
444            if (!isset($data[$key])) {
445                throw new InvalidArgumentException(sprintf('The parameter "%s" is missing', $key));
446            }
447        }
448        $object->legalHeader = $data['legalHeader'] ?? null;
449        $object->aaid = $data['aaid'] ?? null;
450        $object->aaguid = $data['aaguid'] ?? null;
451        $object->attestationCertificateKeyIdentifiers = $data['attestationCertificateKeyIdentifiers'] ?? [];
452        $object->description = $data['description'];
453        $object->alternativeDescriptions = $data['alternativeDescriptions'] ?? [];
454        $object->authenticatorVersion = $data['authenticatorVersion'] ?? 0;
455        $object->protocolFamily = $data['protocolFamily'];
456        if (isset($data['upv'])) {
457            $upv = $data['upv'];
458            Assertion::isArray($upv, 'Invalid Metadata Statement');
459            foreach ($upv as $value) {
460                Assertion::isArray($value, 'Invalid Metadata Statement');
461                $object->upv[] = Version::createFromArray($value);
462            }
463        }
464        $object->assertionScheme = $data['assertionScheme'] ?? null;
465        $object->authenticationAlgorithm = $data['authenticationAlgorithm'] ?? null;
466        $object->authenticationAlgorithms = $data['authenticationAlgorithms'] ?? [];
467        $object->publicKeyAlgAndEncoding = $data['publicKeyAlgAndEncoding'] ?? null;
468        $object->publicKeyAlgAndEncodings = $data['publicKeyAlgAndEncodings'] ?? [];
469        $object->attestationTypes = $data['attestationTypes'] ?? [];
470        if (isset($data['userVerificationDetails'])) {
471            $userVerificationDetails = $data['userVerificationDetails'];
472            Assertion::isArray($userVerificationDetails, 'Invalid Metadata Statement');
473            foreach ($userVerificationDetails as $value) {
474                Assertion::isArray($value, 'Invalid Metadata Statement');
475                $object->userVerificationDetails[] = VerificationMethodANDCombinations::createFromArray($value);
476            }
477        }
478        $object->keyProtection = $data['keyProtection'] ?? 0;
479        $object->isKeyRestricted = $data['isKeyRestricted'] ?? null;
480        $object->isFreshUserVerificationRequired = $data['isFreshUserVerificationRequired'] ?? null;
481        $object->matcherProtection = $data['matcherProtection'] ?? 0;
482        $object->cryptoStrength = $data['cryptoStrength'] ?? null;
483        $object->operatingEnv = $data['operatingEnv'] ?? null;
484        $object->attachmentHint = $data['attachmentHint'] ?? 0;
485        $object->isSecondFactorOnly = $data['isSecondFactorOnly'] ?? null;
486        $object->tcDisplay = $data['tcDisplay'] ?? 0;
487        $object->tcDisplayContentType = $data['tcDisplayContentType'] ?? null;
488        if (isset($data['tcDisplayPNGCharacteristics'])) {
489            $tcDisplayPNGCharacteristics = $data['tcDisplayPNGCharacteristics'];
490            Assertion::isArray($tcDisplayPNGCharacteristics, 'Invalid Metadata Statement');
491            foreach ($tcDisplayPNGCharacteristics as $tcDisplayPNGCharacteristic) {
492                Assertion::isArray($tcDisplayPNGCharacteristic, 'Invalid Metadata Statement');
493                $object->tcDisplayPNGCharacteristics[] = DisplayPNGCharacteristicsDescriptor::createFromArray($tcDisplayPNGCharacteristic);
494            }
495        }
496        $object->attestationRootCertificates = $data['attestationRootCertificates'] ?? [];
497        $object->ecdaaTrustAnchors = $data['ecdaaTrustAnchors'] ?? [];
498        $object->icon = $data['icon'] ?? null;
499        if (isset($data['supportedExtensions'])) {
500            $supportedExtensions = $data['supportedExtensions'];
501            Assertion::isArray($supportedExtensions, 'Invalid Metadata Statement');
502            foreach ($supportedExtensions as $supportedExtension) {
503                Assertion::isArray($supportedExtension, 'Invalid Metadata Statement');
504                $object->supportedExtensions[] = ExtensionDescriptor::createFromArray($supportedExtension);
505            }
506        }
507        $object->rootCertificates = $data['rootCertificates'] ?? [];
508        if (isset($data['statusReports'])) {
509            $reports = $data['statusReports'];
510            Assertion::isArray($reports, 'Invalid Metadata Statement');
511            foreach ($reports as $report) {
512                Assertion::isArray($report, 'Invalid Metadata Statement');
513                $object->statusReports[] = StatusReport::createFromArray($report);
514            }
515        }
516
517        return $object;
518    }
519
520    public function jsonSerialize(): array
521    {
522        $data = [
523            'legalHeader' => $this->legalHeader,
524            'aaid' => $this->aaid,
525            'aaguid' => $this->aaguid,
526            'attestationCertificateKeyIdentifiers' => $this->attestationCertificateKeyIdentifiers,
527            'description' => $this->description,
528            'alternativeDescriptions' => $this->alternativeDescriptions,
529            'authenticatorVersion' => $this->authenticatorVersion,
530            'protocolFamily' => $this->protocolFamily,
531            'upv' => $this->upv,
532            'assertionScheme' => $this->assertionScheme,
533            'authenticationAlgorithm' => $this->authenticationAlgorithm,
534            'authenticationAlgorithms' => $this->authenticationAlgorithms,
535            'publicKeyAlgAndEncoding' => $this->publicKeyAlgAndEncoding,
536            'publicKeyAlgAndEncodings' => $this->publicKeyAlgAndEncodings,
537            'attestationTypes' => $this->attestationTypes,
538            'userVerificationDetails' => $this->userVerificationDetails,
539            'keyProtection' => $this->keyProtection,
540            'isKeyRestricted' => $this->isKeyRestricted,
541            'isFreshUserVerificationRequired' => $this->isFreshUserVerificationRequired,
542            'matcherProtection' => $this->matcherProtection,
543            'cryptoStrength' => $this->cryptoStrength,
544            'operatingEnv' => $this->operatingEnv,
545            'attachmentHint' => $this->attachmentHint,
546            'isSecondFactorOnly' => $this->isSecondFactorOnly,
547            'tcDisplay' => $this->tcDisplay,
548            'tcDisplayContentType' => $this->tcDisplayContentType,
549            'tcDisplayPNGCharacteristics' => array_map(static function (DisplayPNGCharacteristicsDescriptor $object): array {
550                return $object->jsonSerialize();
551            }, $this->tcDisplayPNGCharacteristics),
552            'attestationRootCertificates' => $this->attestationRootCertificates,
553            'ecdaaTrustAnchors' => array_map(static function (EcdaaTrustAnchor $object): array {
554                return $object->jsonSerialize();
555            }, $this->ecdaaTrustAnchors),
556            'icon' => $this->icon,
557            'supportedExtensions' => array_map(static function (ExtensionDescriptor $object): array {
558                return $object->jsonSerialize();
559            }, $this->supportedExtensions),
560            'rootCertificates' => $this->rootCertificates,
561            'statusReports' => $this->statusReports,
562        ];
563
564        return Utils::filterNullValues($data);
565    }
566
567    /**
568     * @return StatusReport[]
569     */
570    public function getStatusReports(): array
571    {
572        return $this->statusReports;
573    }
574
575    /**
576     * @param StatusReport[] $statusReports
577     */
578    public function setStatusReports(array $statusReports): self
579    {
580        $this->statusReports = $statusReports;
581
582        return $this;
583    }
584
585    /**
586     * @return string[]
587     */
588    public function getRootCertificates(): array
589    {
590        return $this->rootCertificates;
591    }
592
593    /**
594     * @param string[] $rootCertificates
595     */
596    public function setRootCertificates(array $rootCertificates): self
597    {
598        $this->rootCertificates = $rootCertificates;
599
600        return $this;
601    }
602}
603