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