File manager - Edit - /home/opticamezl/www/newok/web-auth.tar
Back
metadata-service/src/VerificationMethodANDCombinations.php 0000644 00000001712 15174023160 0017736 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; use Assert\Assertion; class VerificationMethodANDCombinations { /** * @var VerificationMethodDescriptor[] */ private $verificationMethods = []; /** * @return VerificationMethodDescriptor[] */ public function getVerificationMethods(): array { return $this->verificationMethods; } public static function createFromArray(array $data): self { $object = new self(); foreach ($data as $datum) { Assertion::isArray($datum, 'Invalid verificationMethod and combinations'); $object->verificationMethods[] = VerificationMethodDescriptor::createFromArray($datum); } return $object; } } metadata-service/src/AuthenticatorStatus.php 0000644 00000002360 15174023160 0015300 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; abstract class AuthenticatorStatus { public const NOT_FIDO_CERTIFIED = 'NOT_FIDO_CERTIFIED'; public const FIDO_CERTIFIED = 'FIDO_CERTIFIED'; public const USER_VERIFICATION_BYPASS = 'USER_VERIFICATION_BYPASS'; public const ATTESTATION_KEY_COMPROMISE = 'ATTESTATION_KEY_COMPROMISE'; public const USER_KEY_REMOTE_COMPROMISE = 'USER_KEY_REMOTE_COMPROMISE'; public const USER_KEY_PHYSICAL_COMPROMISE = 'USER_KEY_PHYSICAL_COMPROMISE'; public const UPDATE_AVAILABLE = 'UPDATE_AVAILABLE'; public const REVOKED = 'REVOKED'; public const SELF_ASSERTION_SUBMITTED = 'SELF_ASSERTION_SUBMITTED'; public const FIDO_CERTIFIED_L1 = 'FIDO_CERTIFIED_L1'; public const FIDO_CERTIFIED_L1plus = 'FIDO_CERTIFIED_L1plus'; public const FIDO_CERTIFIED_L2 = 'FIDO_CERTIFIED_L2'; public const FIDO_CERTIFIED_L2plus = 'FIDO_CERTIFIED_L2plus'; public const FIDO_CERTIFIED_L3 = 'FIDO_CERTIFIED_L3'; public const FIDO_CERTIFIED_L3plus = 'FIDO_CERTIFIED_L3plus'; } metadata-service/src/SingleMetadata.php 0000644 00000002364 15174023160 0014150 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; use Assert\Assertion; class SingleMetadata { /** * @var MetadataStatement */ private $statement; /** * @var string */ private $data; /** * @var bool */ private $isBare64Encoded; public function __construct(string $data, bool $isBare64Encoded) { $this->data = $data; $this->isBare64Encoded = $isBare64Encoded; } public function getMetadataStatement(): MetadataStatement { if (null === $this->statement) { $json = $this->data; if ($this->isBare64Encoded) { $json = base64_decode($this->data, true); Assertion::string($json, 'Unable to decode the data'); } $statement = json_decode($json, true); Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Unable to decode the data'); $this->statement = MetadataStatement::createFromArray($statement); } return $this->statement; } } metadata-service/src/BiometricStatusReport.php 0000644 00000004251 15174023160 0015600 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; class BiometricStatusReport { /** * @var int */ private $certLevel; /** * @var int */ private $modality; /** * @var string|null */ private $effectiveDate; /** * @var string|null */ private $certificationDescriptor; /** * @var string|null */ private $certificateNumber; /** * @var string|null */ private $certificationPolicyVersion; /** * @var string|null */ private $certificationRequirementsVersion; public function getCertLevel(): int { return $this->certLevel; } public function getModality(): int { return $this->modality; } public function getEffectiveDate(): ?string { return $this->effectiveDate; } public function getCertificationDescriptor(): ?string { return $this->certificationDescriptor; } public function getCertificateNumber(): ?string { return $this->certificateNumber; } public function getCertificationPolicyVersion(): ?string { return $this->certificationPolicyVersion; } public function getCertificationRequirementsVersion(): ?string { return $this->certificationRequirementsVersion; } public static function createFromArray(array $data): self { $object = new self(); $object->certLevel = $data['certLevel'] ?? null; $object->modality = $data['modality'] ?? null; $object->effectiveDate = $data['effectiveDate'] ?? null; $object->certificationDescriptor = $data['certificationDescriptor'] ?? null; $object->certificateNumber = $data['certificateNumber'] ?? null; $object->certificationPolicyVersion = $data['certificationPolicyVersion'] ?? null; $object->certificationRequirementsVersion = $data['certificationRequirementsVersion'] ?? null; return $object; } } metadata-service/src/MetadataService.php 0000644 00000004365 15174023160 0014332 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; use function League\Uri\build; use function League\Uri\build_query; use function League\Uri\parse; use function League\Uri\parse_query; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; class MetadataService { /** * @var ClientInterface */ private $httpClient; /** * @var RequestFactoryInterface */ private $requestFactory; /** * @var array */ private $additionalQueryStringValues; /** * @var array */ private $additionalHeaders; /** * @var string */ private $serviceUri; public function __construct(string $serviceUri, ClientInterface $httpClient, RequestFactoryInterface $requestFactory, array $additionalQueryStringValues = [], array $additionalHeaders = []) { $this->serviceUri = $serviceUri; $this->httpClient = $httpClient; $this->requestFactory = $requestFactory; $this->additionalQueryStringValues = $additionalQueryStringValues; $this->additionalHeaders = $additionalHeaders; } public function getMetadataStatementFor(MetadataTOCPayloadEntry $entry): MetadataStatement { $uri = $this->buildUri($entry->getUrl()); return MetadataStatementFetcher::fetchMetadataStatement($uri, true, $this->httpClient, $this->requestFactory, $this->additionalHeaders); } public function getMetadataTOCPayload(): MetadataTOCPayload { $uri = $this->buildUri($this->serviceUri); return MetadataStatementFetcher::fetchTableOfContent($uri, $this->httpClient, $this->requestFactory, $this->additionalHeaders); } private function buildUri(string $uri): string { $parsedUri = parse($uri); $queryString = $parsedUri['query']; $query = parse_query($queryString ?? ''); foreach ($this->additionalQueryStringValues as $k => $v) { $query[$k] = $v; } $parsedUri['query'] = build_query($query); return build($parsedUri); } } metadata-service/src/VerificationMethodDescriptor.php 0000644 00000004146 15174023160 0017110 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; class VerificationMethodDescriptor { public const USER_VERIFY_PRESENCE = 0x00000001; public const USER_VERIFY_FINGERPRINT = 0x00000002; public const USER_VERIFY_PASSCODE = 0x00000004; public const USER_VERIFY_VOICEPRINT = 0x00000008; public const USER_VERIFY_FACEPRINT = 0x00000010; public const USER_VERIFY_LOCATION = 0x00000020; public const USER_VERIFY_EYEPRINT = 0x00000040; public const USER_VERIFY_PATTERN = 0x00000080; public const USER_VERIFY_HANDPRINT = 0x00000100; public const USER_VERIFY_NONE = 0x00000200; public const USER_VERIFY_ALL = 0x00000400; /** * @var int */ private $userVerification; /** * @var CodeAccuracyDescriptor|null */ private $caDesc; /** * @var BiometricAccuracyDescriptor|null */ private $baDesc; /** * @var PatternAccuracyDescriptor|null */ private $paDesc; public function getUserVerification(): int { return $this->userVerification; } public function getCaDesc(): ?CodeAccuracyDescriptor { return $this->caDesc; } public function getBaDesc(): ?BiometricAccuracyDescriptor { return $this->baDesc; } public function getPaDesc(): ?PatternAccuracyDescriptor { return $this->paDesc; } public static function createFromArray(array $data): self { $object = new self(); $object->userVerification = $data['userVerification'] ?? null; $object->caDesc = isset($data['caDesc']) ? CodeAccuracyDescriptor::createFromArray($data['caDesc']) : null; $object->baDesc = isset($data['baDesc']) ? BiometricAccuracyDescriptor::createFromArray($data['baDesc']) : null; $object->paDesc = isset($data['paDesc']) ? PatternAccuracyDescriptor::createFromArray($data['paDesc']) : null; return $object; } } metadata-service/src/BiometricAccuracyDescriptor.php 0000644 00000003336 15174023160 0016715 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; class BiometricAccuracyDescriptor { /** * @var int|null */ private $selfAttestedFRR; /** * @var int|null */ private $selfAttestedFAR; /** * @var int|null */ private $maxTemplates; /** * @var int|null */ private $maxRetries; /** * @var int|null */ private $blockSlowdown; /** * @return int */ public function getSelfAttestedFRR(): ?int { return $this->selfAttestedFRR; } /** * @return int */ public function getSelfAttestedFAR(): ?int { return $this->selfAttestedFAR; } /** * @return int|null */ public function getMaxTemplates(): ?int { return $this->maxTemplates; } /** * @return int|null */ public function getMaxRetries(): ?int { return $this->maxRetries; } /** * @return int|null */ public function getBlockSlowdown(): ?int { return $this->blockSlowdown; } public static function createFromArray(array $data): self { $object = new self(); $object->selfAttestedFRR = $data['selfAttestedFRR'] ?? null; $object->selfAttestedFAR = $data['selfAttestedFAR'] ?? null; $object->maxTemplates = $data['maxTemplates'] ?? null; $object->maxRetries = $data['maxRetries'] ?? null; $object->blockSlowdown = $data['blockSlowdown'] ?? null; return $object; } } metadata-service/src/DisplayPNGCharacteristicsDescriptor.php 0000644 00000004467 15174023160 0020341 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; use Assert\Assertion; class DisplayPNGCharacteristicsDescriptor { /** * @var int */ private $width; /** * @var int */ private $height; /** * @var int */ private $bitDepth; /** * @var int */ private $colorType; /** * @var int */ private $compression; /** * @var int */ private $filter; /** * @var int */ private $interlace; /** * @var RgbPaletteEntry[] */ private $plte = []; public function getWidth(): int { return $this->width; } public function getHeight(): int { return $this->height; } public function getBitDepth(): int { return $this->bitDepth; } public function getColorType(): int { return $this->colorType; } public function getCompression(): int { return $this->compression; } public function getFilter(): int { return $this->filter; } public function getInterlace(): int { return $this->interlace; } /** * @return RgbPaletteEntry[] */ public function getPlte(): array { return $this->plte; } public static function createFromArray(array $data): self { $object = new self(); $object->width = $data['width'] ?? null; $object->compression = $data['compression'] ?? null; $object->height = $data['height'] ?? null; $object->bitDepth = $data['bitDepth'] ?? null; $object->colorType = $data['colorType'] ?? null; $object->compression = $data['compression'] ?? null; $object->filter = $data['filter'] ?? null; $object->interlace = $data['interlace'] ?? null; if (isset($data['plte'])) { $plte = $data['plte']; Assertion::isArray($plte, 'Invalid "plte" parameter'); foreach ($plte as $item) { $object->plte[] = RgbPaletteEntry::createFromArray($item); } } return $object; } } metadata-service/src/CodeAccuracyDescriptor.php 0000644 00000002335 15174023160 0015650 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; class CodeAccuracyDescriptor { /** * @var int */ private $base; /** * @var int */ private $minLength; /** * @var int|null */ private $maxRetries; /** * @var int|null */ private $blockSlowdown; public function getBase(): int { return $this->base; } public function getMinLength(): int { return $this->minLength; } public function getMaxRetries(): ?int { return $this->maxRetries; } public function getBlockSlowdown(): ?int { return $this->blockSlowdown; } public static function createFromArray(array $data): self { $object = new self(); $object->base = $data['base'] ?? null; $object->minLength = $data['minLength'] ?? null; $object->maxRetries = $data['maxRetries'] ?? null; $object->blockSlowdown = $data['blockSlowdown'] ?? null; return $object; } } metadata-service/src/MetadataStatement.php 0000644 00000032025 15174023160 0014670 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; use Assert\Assertion; use InvalidArgumentException; class MetadataStatement { public const KEY_PROTECTION_SOFTWARE = 0x0001; public const KEY_PROTECTION_HARDWARE = 0x0002; public const KEY_PROTECTION_TEE = 0x0004; public const KEY_PROTECTION_SECURE_ELEMENT = 0x0008; public const KEY_PROTECTION_REMOTE_HANDLE = 0x0010; public const MATCHER_PROTECTION_SOFTWARE = 0x0001; public const MATCHER_PROTECTION_TEE = 0x0002; public const MATCHER_PROTECTION_ON_CHIP = 0x0004; public const ATTACHMENT_HINT_INTERNAL = 0x0001; public const ATTACHMENT_HINT_EXTERNAL = 0x0002; public const ATTACHMENT_HINT_WIRED = 0x0004; public const ATTACHMENT_HINT_WIRELESS = 0x0008; public const ATTACHMENT_HINT_NFC = 0x0010; public const ATTACHMENT_HINT_BLUETOOTH = 0x0020; public const ATTACHMENT_HINT_NETWORK = 0x0040; public const ATTACHMENT_HINT_READY = 0x0080; public const ATTACHMENT_HINT_WIFI_DIRECT = 0x0100; public const TRANSACTION_CONFIRMATION_DISPLAY_ANY = 0x0001; public const TRANSACTION_CONFIRMATION_DISPLAY_PRIVILEGED_SOFTWARE = 0x0002; public const TRANSACTION_CONFIRMATION_DISPLAY_TEE = 0x0004; public const TRANSACTION_CONFIRMATION_DISPLAY_HARDWARE = 0x0008; public const TRANSACTION_CONFIRMATION_DISPLAY_REMOTE = 0x0010; public const ALG_SIGN_SECP256R1_ECDSA_SHA256_RAW = 0x0001; public const ALG_SIGN_SECP256R1_ECDSA_SHA256_DER = 0x0002; public const ALG_SIGN_RSASSA_PSS_SHA256_RAW = 0x0003; public const ALG_SIGN_RSASSA_PSS_SHA256_DER = 0x0004; public const ALG_SIGN_SECP256K1_ECDSA_SHA256_RAW = 0x0005; public const ALG_SIGN_SECP256K1_ECDSA_SHA256_DER = 0x0006; public const ALG_SIGN_SM2_SM3_RAW = 0x0007; public const ALG_SIGN_RSA_EMSA_PKCS1_SHA256_RAW = 0x0008; public const ALG_SIGN_RSA_EMSA_PKCS1_SHA256_DER = 0x0009; public const ALG_SIGN_RSASSA_PSS_SHA384_RAW = 0x000A; public const ALG_SIGN_RSASSA_PSS_SHA512_RAW = 0x000B; public const ALG_SIGN_RSASSA_PKCSV15_SHA256_RAW = 0x000C; public const ALG_SIGN_RSASSA_PKCSV15_SHA384_RAW = 0x000D; public const ALG_SIGN_RSASSA_PKCSV15_SHA512_RAW = 0x000E; public const ALG_SIGN_RSASSA_PKCSV15_SHA1_RAW = 0x000F; public const ALG_SIGN_SECP384R1_ECDSA_SHA384_RAW = 0x0010; public const ALG_SIGN_SECP521R1_ECDSA_SHA512_RAW = 0x0011; public const ALG_SIGN_ED25519_EDDSA_SHA256_RAW = 0x0012; public const ALG_KEY_ECC_X962_RAW = 0x0100; public const ALG_KEY_ECC_X962_DER = 0x0101; public const ALG_KEY_RSA_2048_RAW = 0x0102; public const ALG_KEY_RSA_2048_DER = 0x0103; public const ALG_KEY_COSE = 0x0104; public const ATTESTATION_BASIC_FULL = 0x3E07; public const ATTESTATION_BASIC_SURROGATE = 0x3E08; public const ATTESTATION_ECDAA = 0x3E09; public const ATTESTATION_ATTCA = 0x3E0A; /** * @var string|null */ private $legalHeader; /** * @var string|null */ private $aaid; /** * @var string|null */ private $aaguid; /** * @var string[] */ private $attestationCertificateKeyIdentifiers = []; /** * @var string */ private $description; /** * @var string[] */ private $alternativeDescriptions = []; /** * @var int */ private $authenticatorVersion; /** * @var string */ private $protocolFamily; /** * @var Version[] */ private $upv = []; /** * @var string|null */ private $assertionScheme; /** * @var int|null */ private $authenticationAlgorithm; /** * @var int[] */ private $authenticationAlgorithms = []; /** * @var int|null */ private $publicKeyAlgAndEncoding; /** * @var int[] */ private $publicKeyAlgAndEncodings = []; /** * @var int[] */ private $attestationTypes = []; /** * @var VerificationMethodANDCombinations[] */ private $userVerificationDetails = []; /** * @var int */ private $keyProtection; /** * @var bool|null */ private $isKeyRestricted; /** * @var bool|null */ private $isFreshUserVerificationRequired; /** * @var int */ private $matcherProtection; /** * @var int|null */ private $cryptoStrength; /** * @var string|null */ private $operatingEnv; /** * @var int */ private $attachmentHint = 0; /** * @var bool|null */ private $isSecondFactorOnly; /** * @var int */ private $tcDisplay; /** * @var string|null */ private $tcDisplayContentType; /** * @var DisplayPNGCharacteristicsDescriptor[] */ private $tcDisplayPNGCharacteristics = []; /** * @var string[] */ private $attestationRootCertificates = []; /** * @var EcdaaTrustAnchor[] */ private $ecdaaTrustAnchors = []; /** * @var string|null */ private $icon; /** * @var ExtensionDescriptor[] */ private $supportedExtensions = []; public function getLegalHeader(): ?string { return $this->legalHeader; } public function getAaid(): ?string { return $this->aaid; } public function getAaguid(): ?string { return $this->aaguid; } /** * @return string[] */ public function getAttestationCertificateKeyIdentifiers(): array { return $this->attestationCertificateKeyIdentifiers; } public function getDescription(): string { return $this->description; } /** * @return string[] */ public function getAlternativeDescriptions(): array { return $this->alternativeDescriptions; } public function getAuthenticatorVersion(): int { return $this->authenticatorVersion; } public function getProtocolFamily(): string { return $this->protocolFamily; } /** * @return Version[] */ public function getUpv(): array { return $this->upv; } public function getAssertionScheme(): ?string { return $this->assertionScheme; } public function getAuthenticationAlgorithm(): ?int { return $this->authenticationAlgorithm; } /** * @return int[] */ public function getAuthenticationAlgorithms(): array { return $this->authenticationAlgorithms; } public function getPublicKeyAlgAndEncoding(): ?int { return $this->publicKeyAlgAndEncoding; } /** * @return int[] */ public function getPublicKeyAlgAndEncodings(): array { return $this->publicKeyAlgAndEncodings; } /** * @return int[] */ public function getAttestationTypes(): array { return $this->attestationTypes; } /** * @return VerificationMethodANDCombinations[] */ public function getUserVerificationDetails(): array { return $this->userVerificationDetails; } public function getKeyProtection(): int { return $this->keyProtection; } public function isKeyRestricted(): ?bool { return (bool) $this->isKeyRestricted; } public function isFreshUserVerificationRequired(): ?bool { return (bool) $this->isFreshUserVerificationRequired; } public function getMatcherProtection(): int { return $this->matcherProtection; } public function getCryptoStrength(): ?int { return $this->cryptoStrength; } public function getOperatingEnv(): ?string { return $this->operatingEnv; } public function getAttachmentHint(): int { return $this->attachmentHint; } public function isSecondFactorOnly(): ?bool { return (bool) $this->isSecondFactorOnly; } public function getTcDisplay(): int { return $this->tcDisplay; } public function getTcDisplayContentType(): ?string { return $this->tcDisplayContentType; } /** * @return DisplayPNGCharacteristicsDescriptor[] */ public function getTcDisplayPNGCharacteristics(): array { return $this->tcDisplayPNGCharacteristics; } /** * @return string[] */ public function getAttestationRootCertificates(): array { return $this->attestationRootCertificates; } /** * @return EcdaaTrustAnchor[] */ public function getEcdaaTrustAnchors(): array { return $this->ecdaaTrustAnchors; } public function getIcon(): ?string { return $this->icon; } /** * @return ExtensionDescriptor[] */ public function getSupportedExtensions(): array { return $this->supportedExtensions; } public static function createFromArray(array $data): self { $object = new self(); foreach (['description', 'protocolFamily'] as $key) { if (!isset($data[$key])) { throw new InvalidArgumentException(sprintf('The parameter "%s" is missing', $key)); } } $object->legalHeader = $data['legalHeader'] ?? null; $object->aaid = $data['aaid'] ?? null; $object->aaguid = $data['aaguid'] ?? null; $object->attestationCertificateKeyIdentifiers = $data['attestationCertificateKeyIdentifiers'] ?? []; $object->description = $data['description']; $object->alternativeDescriptions = $data['alternativeDescriptions'] ?? []; $object->authenticatorVersion = $data['authenticatorVersion'] ?? 0; $object->protocolFamily = $data['protocolFamily']; if (isset($data['upv'])) { $upv = $data['upv']; Assertion::isArray($upv, 'Invalid Metadata Statement'); foreach ($upv as $value) { Assertion::isArray($value, 'Invalid Metadata Statement'); $object->upv[] = Version::createFromArray($value); } } $object->assertionScheme = $data['assertionScheme'] ?? null; $object->authenticationAlgorithm = $data['authenticationAlgorithm'] ?? null; $object->authenticationAlgorithms = $data['authenticationAlgorithms'] ?? []; $object->publicKeyAlgAndEncoding = $data['publicKeyAlgAndEncoding'] ?? null; $object->publicKeyAlgAndEncodings = $data['publicKeyAlgAndEncodings'] ?? []; $object->attestationTypes = $data['attestationTypes'] ?? []; if (isset($data['userVerificationDetails'])) { $userVerificationDetails = $data['userVerificationDetails']; Assertion::isArray($userVerificationDetails, 'Invalid Metadata Statement'); foreach ($userVerificationDetails as $value) { Assertion::isArray($value, 'Invalid Metadata Statement'); $object->userVerificationDetails[] = VerificationMethodANDCombinations::createFromArray($value); } } $object->keyProtection = $data['keyProtection'] ?? 0; $object->isKeyRestricted = $data['isKeyRestricted'] ?? null; $object->isFreshUserVerificationRequired = $data['isFreshUserVerificationRequired'] ?? null; $object->matcherProtection = $data['matcherProtection'] ?? 0; $object->cryptoStrength = $data['cryptoStrength'] ?? null; $object->operatingEnv = $data['operatingEnv'] ?? null; $object->attachmentHint = $data['attachmentHint'] ?? 0; $object->isSecondFactorOnly = $data['isSecondFactorOnly'] ?? null; $object->tcDisplay = $data['tcDisplay'] ?? 0; $object->tcDisplayContentType = $data['tcDisplayContentType'] ?? null; if (isset($data['tcDisplayPNGCharacteristics'])) { $tcDisplayPNGCharacteristics = $data['tcDisplayPNGCharacteristics']; Assertion::isArray($tcDisplayPNGCharacteristics, 'Invalid Metadata Statement'); foreach ($tcDisplayPNGCharacteristics as $tcDisplayPNGCharacteristic) { Assertion::isArray($tcDisplayPNGCharacteristic, 'Invalid Metadata Statement'); $object->tcDisplayPNGCharacteristics[] = DisplayPNGCharacteristicsDescriptor::createFromArray($tcDisplayPNGCharacteristic); } } $object->attestationRootCertificates = $data['attestationRootCertificates'] ?? []; $object->ecdaaTrustAnchors = $data['ecdaaTrustAnchors'] ?? []; $object->icon = $data['icon'] ?? null; if (isset($data['supportedExtensions'])) { $supportedExtensions = $data['supportedExtensions']; Assertion::isArray($supportedExtensions, 'Invalid Metadata Statement'); foreach ($supportedExtensions as $supportedExtension) { Assertion::isArray($supportedExtension, 'Invalid Metadata Statement'); $object->supportedExtensions[] = ExtensionDescriptor::createFromArray($supportedExtension); } } return $object; } } metadata-service/src/EcdaaTrustAnchor.php 0000644 00000002667 15174023160 0014466 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; class EcdaaTrustAnchor { /** * @var string */ private $X; /** * @var string */ private $Y; /** * @var string */ private $c; /** * @var string */ private $sx; /** * @var string */ private $sy; /** * @var string */ private $G1Curve; public function getX(): string { return $this->X; } public function getY(): string { return $this->Y; } public function getC(): string { return $this->c; } public function getSx(): string { return $this->sx; } public function getSy(): string { return $this->sy; } public function getG1Curve(): string { return $this->G1Curve; } public static function createFromArray(array $data): self { $object = new self(); $object->X = $data['X'] ?? null; $object->Y = $data['Y'] ?? null; $object->c = $c['data'] ?? null; $object->sx = $data['sx'] ?? null; $object->sy = $data['sy'] ?? null; $object->G1Curve = $data['G1Curve'] ?? null; return $object; } } metadata-service/src/MetadataTOCPayloadEntry.php 0000644 00000006216 15174023160 0015710 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; use Assert\Assertion; class MetadataTOCPayloadEntry { /** * @var string|null */ private $aaid; /** * @var string|null */ private $aaguid; /** * @var string[] */ private $attestationCertificateKeyIdentifiers = []; /** * @var string|null */ private $hash; /** * @var string|null */ private $url; /** * @var BiometricStatusReport[] */ private $biometricStatusReports = []; /** * @var StatusReport[] */ private $statusReports = []; /** * @var string */ private $timeOfLastStatusChange; /** * @var string */ private $rogueListURL; /** * @var string */ private $rogueListHash; public function getAaid(): ?string { return $this->aaid; } public function getAaguid(): ?string { return $this->aaguid; } public function getAttestationCertificateKeyIdentifiers(): array { return $this->attestationCertificateKeyIdentifiers; } public function getHash(): ?string { return $this->hash; } public function getUrl(): ?string { return $this->url; } public function getBiometricStatusReports(): array { return $this->biometricStatusReports; } /** * @return StatusReport[] */ public function getStatusReports(): array { return $this->statusReports; } public function getTimeOfLastStatusChange(): string { return $this->timeOfLastStatusChange; } public function getRogueListURL(): string { return $this->rogueListURL; } public function getRogueListHash(): string { return $this->rogueListHash; } public static function createFromArray(array $data): self { $object = new self(); $object->aaid = $data['aaid'] ?? null; $object->aaguid = $data['aaguid'] ?? null; $object->attestationCertificateKeyIdentifiers = $data['attestationCertificateKeyIdentifiers'] ?? null; $object->hash = $data['hash'] ?? null; $object->url = $data['url'] ?? null; $object->biometricStatusReports = isset($data['biometricStatusReports']) ? BiometricStatusReport::createFromArray($data['biometricStatusReports']) : null; $object->statusReports = []; if (isset($data['statusReports'])) { Assertion::isArray($data['statusReports'], 'Invalid status report'); foreach ($data['statusReports'] as $k => $statusReport) { $object->statusReports[$k] = StatusReport::createFromArray($statusReport); } } $object->timeOfLastStatusChange = $data['timeOfLastStatusChange'] ?? null; $object->rogueListURL = $data['rogueListURL'] ?? null; $object->rogueListHash = $data['rogueListHash'] ?? null; return $object; } } metadata-service/src/MetadataTOCPayload.php 0000644 00000002722 15174023160 0014664 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; class MetadataTOCPayload { /** * @var string|null */ private $legalHeader; /** * @var int */ private $no; /** * @var string */ private $nextUpdate; /** * @var MetadataTOCPayloadEntry[] */ private $entries = []; public function getLegalHeader(): ?string { return $this->legalHeader; } public function getNo(): int { return $this->no; } public function getNextUpdate(): string { return $this->nextUpdate; } /** * @return MetadataTOCPayloadEntry[] */ public function getEntries(): array { return $this->entries; } public static function createFromArray(array $data): self { $object = new self(); $object->legalHeader = $data['legalHeader'] ?? null; $object->nextUpdate = $data['nextUpdate'] ?? null; $object->no = $data['no'] ?? null; $object->entries = []; if (isset($data['entries'])) { foreach ($data['entries'] as $k => $entry) { $object->entries[$k] = MetadataTOCPayloadEntry::createFromArray($entry); } } return $object; } } metadata-service/src/MetadataStatementRepository.php 0000644 00000000602 15174023160 0016764 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; interface MetadataStatementRepository { public function findOneByAAGUID(string $aaguid): ?MetadataStatement; } metadata-service/src/DistantSingleMetadataFactory.php 0000644 00000002070 15174023160 0017021 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; class DistantSingleMetadataFactory { /** * @var ClientInterface */ private $httpClient; /** * @var RequestFactoryInterface */ private $requestFactory; public function __construct(ClientInterface $httpClient, RequestFactoryInterface $requestFactory) { $this->httpClient = $httpClient; $this->requestFactory = $requestFactory; } public function create(string $uri, bool $isBare64Encoded, array $additionalHeaders = [], ?ClientInterface $client = null): DistantSingleMetadata { $client = $client ?? $this->httpClient; return new DistantSingleMetadata($uri, $isBare64Encoded, $client, $this->requestFactory, $additionalHeaders); } } metadata-service/src/Version.php 0000644 00000001412 15174023160 0012704 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; class Version { /** * @var int */ private $major; /** * @var int */ private $minor; public function getMajor(): int { return $this->major; } public function getMinor(): int { return $this->minor; } public static function createFromArray(array $data): self { $object = new self(); $object->major = $data['major'] ?? null; $object->minor = $data['minor'] ?? null; return $object; } } metadata-service/src/ExtensionDescriptor.php 0000644 00000002251 15174023160 0015274 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; class ExtensionDescriptor { /** * @var string */ private $id; /** * @var int|null */ private $tag; /** * @var string|null */ private $data; /** * @var bool */ private $fail_if_unknown; public function getId(): string { return $this->id; } public function getTag(): ?int { return $this->tag; } public function getData(): ?string { return $this->data; } public function isFailIfUnknown(): bool { return $this->fail_if_unknown; } public static function createFromArray(array $data): self { $object = new self(); $object->id = $data['id'] ?? null; $object->tag = $data['tag'] ?? null; $object->data = $data['data'] ?? null; $object->fail_if_unknown = $data['fail_if_unknown'] ?? null; return $object; } } metadata-service/src/MetadataServiceFactory.php 0000644 00000002122 15174023160 0015647 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; class MetadataServiceFactory { /** * @var ClientInterface */ private $httpClient; /** * @var RequestFactoryInterface */ private $requestFactory; public function __construct(ClientInterface $httpClient, RequestFactoryInterface $requestFactory) { $this->httpClient = $httpClient; $this->requestFactory = $requestFactory; } public function create(string $serviceUri, array $additionalQueryStringValues = [], array $additionalHeaders = [], ?ClientInterface $client = null): MetadataService { $client = $client ?? $this->httpClient; return new MetadataService($serviceUri, $client, $this->requestFactory, $additionalQueryStringValues, $additionalHeaders); } } metadata-service/src/DistantSingleMetadata.php 0000644 00000002662 15174023160 0015500 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; class DistantSingleMetadata extends SingleMetadata { /** * @var ClientInterface */ private $httpClient; /** * @var RequestFactoryInterface */ private $requestFactory; /** * @var array */ private $additionalHeaders; /** * @var string */ private $uri; /** * @var bool */ private $isBare64Encoded; public function __construct(string $uri, bool $isBare64Encoded, ClientInterface $httpClient, RequestFactoryInterface $requestFactory, array $additionalHeaders = []) { parent::__construct($uri, $isBare64Encoded); //Useless $this->uri = $uri; $this->isBare64Encoded = $isBare64Encoded; $this->httpClient = $httpClient; $this->requestFactory = $requestFactory; $this->additionalHeaders = $additionalHeaders; } public function getMetadataStatement(): MetadataStatement { return MetadataStatementFetcher::fetchMetadataStatement($this->uri, $this->isBare64Encoded, $this->httpClient, $this->requestFactory, $this->additionalHeaders); } } metadata-service/src/RgbPaletteEntry.php 0000644 00000001612 15174023160 0014334 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; class RgbPaletteEntry { /** * @var int */ private $r; /** * @var int */ private $g; /** * @var int */ private $b; public function getR(): int { return $this->r; } public function getG(): int { return $this->g; } public function getB(): int { return $this->b; } public static function createFromArray(array $data): self { $object = new self(); $object->r = $data['r'] ?? null; $object->g = $data['g'] ?? null; $object->b = $data['b'] ?? null; return $object; } } metadata-service/src/MetadataStatementFetcher.php 0000644 00000007114 15174023160 0016172 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; use Assert\Assertion; use Base64Url\Base64Url; use Jose\Component\KeyManagement\JWKFactory; use Jose\Component\Signature\Algorithm\ES256; use Jose\Component\Signature\Serializer\CompactSerializer; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; class MetadataStatementFetcher { public static function fetchTableOfContent(string $uri, ClientInterface $client, RequestFactoryInterface $requestFactory, array $additionalHeaders = []): MetadataTOCPayload { $content = self::fetch($uri, $client, $requestFactory, $additionalHeaders); $payload = self::getJwsPayload($content); $data = json_decode($payload, true); Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Unable to decode the data'); return MetadataTOCPayload::createFromArray($data); } public static function fetchMetadataStatement(string $uri, bool $isBase64UrlEncoded, ClientInterface $client, RequestFactoryInterface $requestFactory, array $additionalHeaders = []): MetadataStatement { $payload = self::fetch($uri, $client, $requestFactory, $additionalHeaders); $json = $isBase64UrlEncoded ? Base64Url::decode($payload) : $payload; $data = json_decode($json, true); Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Unable to decode the data'); return MetadataStatement::createFromArray($data); } private static function fetch(string $uri, ClientInterface $client, RequestFactoryInterface $requestFactory, array $additionalHeaders = []): string { $request = $requestFactory->createRequest('GET', $uri); foreach ($additionalHeaders as $k => $v) { $request = $request->withHeader($k, $v); } $response = $client->sendRequest($request); Assertion::eq(200, $response->getStatusCode(), sprintf('Unable to contact the server. Response code is %d', $response->getStatusCode())); $content = $response->getBody()->getContents(); Assertion::notEmpty($content, 'Unable to contact the server. The response has no content'); return $content; } private static function getJwsPayload(string $token): string { $jws = (new CompactSerializer())->unserialize($token); Assertion::eq(1, $jws->countSignatures(), 'Invalid response from the metadata service. Only one signature shall be present.'); $signature = $jws->getSignature(0); $payload = $jws->getPayload(); Assertion::notEmpty($payload, 'Invalid response from the metadata service. The token payload is empty.'); $header = $signature->getProtectedHeader(); Assertion::keyExists($header, 'alg', 'The "alg" parameter is missing.'); Assertion::eq($header['alg'], 'ES256', 'The expected "alg" parameter value should be "ES256".'); Assertion::keyExists($header, 'x5c', 'The "x5c" parameter is missing.'); Assertion::isArray($header['x5c'], 'The "x5c" parameter should be an array.'); $key = JWKFactory::createFromX5C($header['x5c']); $algorithm = new ES256(); $isValid = $algorithm->verify($key, $signature->getEncodedProtectedHeader().'.'.$jws->getEncodedPayload(), $signature->getSignature()); Assertion::true($isValid, 'Invalid response from the metadata service. The token signature is invalid.'); return $jws->getPayload(); } } metadata-service/src/StatusReport.php 0000644 00000004617 15174023160 0013750 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; class StatusReport { /** * @var string * * @see AuthenticatorStatus */ private $status; /** * @var string|null */ private $effectiveDate; /** * @var string|null */ private $certificate; /** * @var string|null */ private $url; /** * @var string|null */ private $certificationDescriptor; /** * @var string|null */ private $certificateNumber; /** * @var string|null */ private $certificationPolicyVersion; /** * @var string|null */ private $certificationRequirementsVersion; public function getStatus(): string { return $this->status; } public function getEffectiveDate(): ?string { return $this->effectiveDate; } public function getCertificate(): ?string { return $this->certificate; } public function getUrl(): ?string { return $this->url; } public function getCertificationDescriptor(): ?string { return $this->certificationDescriptor; } public function getCertificateNumber(): ?string { return $this->certificateNumber; } public function getCertificationPolicyVersion(): ?string { return $this->certificationPolicyVersion; } public function getCertificationRequirementsVersion(): ?string { return $this->certificationRequirementsVersion; } public static function createFromArray(array $data): self { $object = new self(); $object->status = $data['status'] ?? null; $object->effectiveDate = $data['effectiveDate'] ?? null; $object->certificate = $data['certificate'] ?? null; $object->url = $data['url'] ?? null; $object->certificationDescriptor = $data['certificationDescriptor'] ?? null; $object->certificateNumber = $data['certificateNumber'] ?? null; $object->certificationPolicyVersion = $data['certificationPolicyVersion'] ?? null; $object->certificationRequirementsVersion = $data['certificationRequirementsVersion'] ?? null; return $object; } } metadata-service/src/PatternAccuracyDescriptor.php 0000644 00000002105 15174023160 0016406 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; class PatternAccuracyDescriptor { /** * @var int */ private $minComplexity; /** * @var int|null */ private $maxRetries; /** * @var int|null */ private $blockSlowdown; public function getMinComplexity(): int { return $this->minComplexity; } public function getMaxRetries(): ?int { return $this->maxRetries; } public function getBlockSlowdown(): ?int { return $this->blockSlowdown; } public static function createFromArray(array $data): self { $object = new self(); $object->minComplexity = $data['minComplexity'] ?? null; $object->maxRetries = $data['maxRetries'] ?? null; $object->blockSlowdown = $data['blockSlowdown'] ?? null; return $object; } } metadata-service/src/SimpleMetadataStatementRepository.php 0000644 00000011143 15174023160 0020140 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; use DateTimeImmutable; use Psr\Cache\CacheItemPoolInterface; use Throwable; class SimpleMetadataStatementRepository implements MetadataStatementRepository { /** * @var CacheItemPoolInterface */ private $cacheItemPool; /** * @var MetadataService[] */ private $services = []; /** * @var SingleMetadata[] */ private $singleStatements = []; public function __construct(CacheItemPoolInterface $cacheItemPool) { $this->cacheItemPool = $cacheItemPool; } public function addService(string $name, MetadataService $service): void { $this->services[$name] = $service; } public function addSingleStatement(string $name, SingleMetadata $singleStatements): void { $this->singleStatements[$name] = $singleStatements; } public function findOneByAAGUID(string $aaguid): ?MetadataStatement { $metadataStatement = $this->findOneByAAGUIDFromServices($aaguid); if (null !== $metadataStatement) { return $metadataStatement; } return $this->findOneByAAGUIDFromSingleStatements($aaguid); } private function findOneByAAGUIDFromSingleStatements(string $aaguid): ?MetadataStatement { foreach ($this->singleStatements as $name => $singleStatement) { try { $singleCacheItem = $this->cacheItemPool->getItem(sprintf('MDS-%s', $name)); if (!$singleCacheItem->isHit()) { $metadataStatement = $singleStatement->getMetadataStatement(); $singleCacheItem->set($metadataStatement); $this->cacheItemPool->save($singleCacheItem); } else { $metadataStatement = $singleCacheItem->get(); } if ($metadataStatement->getAaguid() === $aaguid) { return $metadataStatement; } } catch (Throwable $throwable) { continue; } } return null; } private function findOneByAAGUIDFromServices(string $aaguid): ?MetadataStatement { foreach ($this->services as $name => $service) { try { $tocCacheItem = $this->cacheItemPool->getItem(sprintf('TOC-%s', $name)); if (!$tocCacheItem->isHit()) { $tableOfContent = $service->getMetadataTOCPayload(); $tocCacheItem->set($tableOfContent); $this->cacheItemPool->save($tocCacheItem); $needCacheUpdate = true; } else { $tableOfContent = $tocCacheItem->get(); $nextUpdate = DateTimeImmutable::createFromFormat('Y-m-d', $tableOfContent->getNextUpdate()); if (false === $nextUpdate) { $needCacheUpdate = true; } else { $needCacheUpdate = $nextUpdate->getTimestamp() < time(); if ($needCacheUpdate) { $tableOfContent = $service->getMetadataTOCPayload(); $tocCacheItem->set($tableOfContent); $this->cacheItemPool->save($tocCacheItem); } } } } catch (Throwable $throwable) { continue; } foreach ($tableOfContent->getEntries() as $entry) { $url = $entry->getUrl(); if (null === $url) { continue; } try { $mdsCacheItem = $this->cacheItemPool->getItem(sprintf('MDS-%s', urlencode($url))); if ($mdsCacheItem->isHit() && !$needCacheUpdate) { $metadataStatement = $mdsCacheItem->get(); } else { $metadataStatement = $service->getMetadataStatementFor($entry); $mdsCacheItem->set($metadataStatement); $this->cacheItemPool->save($mdsCacheItem); } if ($metadataStatement->getAaguid() === $aaguid) { return $metadataStatement; } } catch (Throwable $throwable) { continue; } } } return null; } } metadata-service/src/RogueListEntry.php 0000644 00000001410 15174023160 0014214 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\MetadataService; class RogueListEntry { /** * @var string */ private $sk; /** * @var string */ private $date; public function getSk(): string { return $this->sk; } public function getDate(): string { return $this->date; } public static function createFromArray(array $data): self { $object = new self(); $object->sk = $data['sk'] ?? null; $object->date = $data['date'] ?? null; return $object; } } metadata-service/LICENSE 0000644 00000002054 15174023160 0010767 0 ustar 00 MIT License Copyright (c) 2018 Spomky-Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. webauthn-lib/LICENSE 0000644 00000002054 15174023160 0010132 0 ustar 00 MIT License Copyright (c) 2018 Spomky-Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. webauthn-lib/src/AuthenticatorAttestationResponse.php 0000644 00000001556 15174023160 0017204 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Webauthn\AttestationStatement\AttestationObject; /** * @see https://www.w3.org/TR/webauthn/#authenticatorattestationresponse */ class AuthenticatorAttestationResponse extends AuthenticatorResponse { /** * @var AttestationObject */ private $attestationObject; public function __construct(CollectedClientData $clientDataJSON, AttestationObject $attestationObject) { parent::__construct($clientDataJSON); $this->attestationObject = $attestationObject; } public function getAttestationObject(): AttestationObject { return $this->attestationObject; } } webauthn-lib/src/AuthenticatorData.php 0000644 00000005543 15174023160 0014037 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputs; /** * @see https://www.w3.org/TR/webauthn/#sec-authenticator-data */ class AuthenticatorData { /** * @var string */ protected $authData; /** * @var string */ protected $rpIdHash; /** * @var string */ protected $flags; /** * @var int */ protected $signCount; /** * @var AttestedCredentialData|null */ protected $attestedCredentialData; /** * @var AuthenticationExtensionsClientOutputs|null */ protected $extensions; private const FLAG_UP = 0b00000001; private const FLAG_RFU1 = 0b00000010; private const FLAG_UV = 0b00000100; private const FLAG_RFU2 = 0b00111000; private const FLAG_AT = 0b01000000; private const FLAG_ED = 0b10000000; public function __construct(string $authData, string $rpIdHash, string $flags, int $signCount, ?AttestedCredentialData $attestedCredentialData, ?AuthenticationExtensionsClientOutputs $extensions) { $this->rpIdHash = $rpIdHash; $this->flags = $flags; $this->signCount = $signCount; $this->attestedCredentialData = $attestedCredentialData; $this->extensions = $extensions; $this->authData = $authData; } public function getAuthData(): string { return $this->authData; } public function getRpIdHash(): string { return $this->rpIdHash; } public function isUserPresent(): bool { return 0 !== (\ord($this->flags) & self::FLAG_UP) ? true : false; } public function isUserVerified(): bool { return 0 !== (\ord($this->flags) & self::FLAG_UV) ? true : false; } public function hasAttestedCredentialData(): bool { return 0 !== (\ord($this->flags) & self::FLAG_AT) ? true : false; } public function hasExtensions(): bool { return 0 !== (\ord($this->flags) & self::FLAG_ED) ? true : false; } public function getReservedForFutureUse1(): int { return \ord($this->flags) & self::FLAG_RFU1; } public function getReservedForFutureUse2(): int { return \ord($this->flags) & self::FLAG_RFU2; } public function getSignCount(): int { return $this->signCount; } public function getAttestedCredentialData(): ?AttestedCredentialData { return $this->attestedCredentialData; } public function getExtensions(): ?AuthenticationExtensionsClientOutputs { return null !== $this->extensions && $this->hasExtensions() ? $this->extensions : null; } } webauthn-lib/src/Server.php 0000644 00000026306 15174023160 0011701 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Assert\Assertion; use Cose\Algorithm\Algorithm; use Cose\Algorithm\ManagerFactory; use Cose\Algorithm\Signature\ECDSA; use Cose\Algorithm\Signature\EdDSA; use Cose\Algorithm\Signature\RSA; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\ServerRequestInterface; use Webauthn\AttestationStatement\AndroidKeyAttestationStatementSupport; use Webauthn\AttestationStatement\AndroidSafetyNetAttestationStatementSupport; use Webauthn\AttestationStatement\AttestationObjectLoader; use Webauthn\AttestationStatement\AttestationStatementSupportManager; use Webauthn\AttestationStatement\FidoU2FAttestationStatementSupport; use Webauthn\AttestationStatement\NoneAttestationStatementSupport; use Webauthn\AttestationStatement\PackedAttestationStatementSupport; use Webauthn\AttestationStatement\TPMAttestationStatementSupport; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler; use Webauthn\MetadataService\MetadataStatementRepository; use Webauthn\TokenBinding\TokenBindingNotSupportedHandler; class Server { /** * @var int */ public $timeout = 60000; /** * @var int */ public $challengeSize = 32; /** * @var PublicKeyCredentialRpEntity */ private $rpEntity; /** * @var ManagerFactory */ private $coseAlgorithmManagerFactory; /** * @var PublicKeyCredentialSourceRepository */ private $publicKeyCredentialSourceRepository; /** * @var TokenBindingNotSupportedHandler */ private $tokenBindingHandler; /** * @var ExtensionOutputCheckerHandler */ private $extensionOutputCheckerHandler; /** * @var string[] */ private $selectedAlgorithms; /** * @var MetadataStatementRepository|null */ private $metadataStatementRepository; /** * @var ClientInterface */ private $httpClient; /** * @var string */ private $googleApiKey; /** * @var RequestFactoryInterface */ private $requestFactory; public function __construct(PublicKeyCredentialRpEntity $relayingParty, PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository, ?MetadataStatementRepository $metadataStatementRepository) { $this->rpEntity = $relayingParty; $this->coseAlgorithmManagerFactory = new ManagerFactory(); $this->coseAlgorithmManagerFactory->add('RS1', new RSA\RS1()); $this->coseAlgorithmManagerFactory->add('RS256', new RSA\RS256()); $this->coseAlgorithmManagerFactory->add('RS384', new RSA\RS384()); $this->coseAlgorithmManagerFactory->add('RS512', new RSA\RS512()); $this->coseAlgorithmManagerFactory->add('PS256', new RSA\PS256()); $this->coseAlgorithmManagerFactory->add('PS384', new RSA\PS384()); $this->coseAlgorithmManagerFactory->add('PS512', new RSA\PS512()); $this->coseAlgorithmManagerFactory->add('ES256', new ECDSA\ES256()); $this->coseAlgorithmManagerFactory->add('ES256K', new ECDSA\ES256K()); $this->coseAlgorithmManagerFactory->add('ES384', new ECDSA\ES384()); $this->coseAlgorithmManagerFactory->add('ES512', new ECDSA\ES512()); $this->coseAlgorithmManagerFactory->add('Ed25519', new EdDSA\Ed25519()); $this->selectedAlgorithms = ['RS256', 'RS512', 'PS256', 'PS512', 'ES256', 'ES512', 'Ed25519']; $this->publicKeyCredentialSourceRepository = $publicKeyCredentialSourceRepository; $this->tokenBindingHandler = new TokenBindingNotSupportedHandler(); $this->extensionOutputCheckerHandler = new ExtensionOutputCheckerHandler(); $this->metadataStatementRepository = $metadataStatementRepository; } /** * @param string[] $selectedAlgorithms */ public function setSelectedAlgorithms(array $selectedAlgorithms): void { $this->selectedAlgorithms = $selectedAlgorithms; } public function setTokenBindingHandler(TokenBindingNotSupportedHandler $tokenBindingHandler): void { $this->tokenBindingHandler = $tokenBindingHandler; } public function addAlgorithm(string $alias, Algorithm $algorithm): void { $this->coseAlgorithmManagerFactory->add($alias, $algorithm); $this->selectedAlgorithms[] = $alias; $this->selectedAlgorithms = array_unique($this->selectedAlgorithms); } public function setExtensionOutputCheckerHandler(ExtensionOutputCheckerHandler $extensionOutputCheckerHandler): void { $this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler; } /** * @param PublicKeyCredentialDescriptor[] $excludedPublicKeyDescriptors */ public function generatePublicKeyCredentialCreationOptions(PublicKeyCredentialUserEntity $userEntity, ?string $attestationMode = PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE, array $excludedPublicKeyDescriptors = [], ?AuthenticatorSelectionCriteria $criteria = null, ?AuthenticationExtensionsClientInputs $extensions = null): PublicKeyCredentialCreationOptions { $coseAlgorithmManager = $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms); $publicKeyCredentialParametersList = []; foreach ($coseAlgorithmManager->all() as $algorithm) { $publicKeyCredentialParametersList[] = new PublicKeyCredentialParameters( PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, $algorithm::identifier() ); } $criteria = $criteria ?? new AuthenticatorSelectionCriteria(); $extensions = $extensions ?? new AuthenticationExtensionsClientInputs(); $challenge = random_bytes($this->challengeSize); return new PublicKeyCredentialCreationOptions( $this->rpEntity, $userEntity, $challenge, $publicKeyCredentialParametersList, $this->timeout, $excludedPublicKeyDescriptors, $criteria, $attestationMode, $extensions ); } /** * @param PublicKeyCredentialDescriptor[] $allowedPublicKeyDescriptors */ public function generatePublicKeyCredentialRequestOptions(?string $userVerification = PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED, array $allowedPublicKeyDescriptors = [], ?AuthenticationExtensionsClientInputs $extensions = null): PublicKeyCredentialRequestOptions { return new PublicKeyCredentialRequestOptions( random_bytes($this->challengeSize), $this->timeout, $this->rpEntity->getId(), $allowedPublicKeyDescriptors, $userVerification, $extensions ?? new AuthenticationExtensionsClientInputs() ); } public function loadAndCheckAttestationResponse(string $data, PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, ServerRequestInterface $serverRequest): PublicKeyCredentialSource { $attestationStatementSupportManager = $this->getAttestationStatementSupportManager(); $attestationObjectLoader = new AttestationObjectLoader($attestationStatementSupportManager); $publicKeyCredentialLoader = new PublicKeyCredentialLoader($attestationObjectLoader); $publicKeyCredential = $publicKeyCredentialLoader->load($data); $authenticatorResponse = $publicKeyCredential->getResponse(); Assertion::isInstanceOf($authenticatorResponse, AuthenticatorAttestationResponse::class, 'Not an authenticator attestation response'); $authenticatorAttestationResponseValidator = new AuthenticatorAttestationResponseValidator( $attestationStatementSupportManager, $this->publicKeyCredentialSourceRepository, $this->tokenBindingHandler, $this->extensionOutputCheckerHandler ); return $authenticatorAttestationResponseValidator->check($authenticatorResponse, $publicKeyCredentialCreationOptions, $serverRequest); } public function loadAndCheckAssertionResponse(string $data, PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, ?PublicKeyCredentialUserEntity $userEntity, ServerRequestInterface $serverRequest): PublicKeyCredentialSource { $attestationStatementSupportManager = $this->getAttestationStatementSupportManager(); $attestationObjectLoader = new AttestationObjectLoader($attestationStatementSupportManager); $publicKeyCredentialLoader = new PublicKeyCredentialLoader($attestationObjectLoader); $publicKeyCredential = $publicKeyCredentialLoader->load($data); $authenticatorResponse = $publicKeyCredential->getResponse(); Assertion::isInstanceOf($authenticatorResponse, AuthenticatorAssertionResponse::class, 'Not an authenticator assertion response'); $authenticatorAssertionResponseValidator = new AuthenticatorAssertionResponseValidator( $this->publicKeyCredentialSourceRepository, null, $this->tokenBindingHandler, $this->extensionOutputCheckerHandler, $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms) ); return $authenticatorAssertionResponseValidator->check( $publicKeyCredential->getRawId(), $authenticatorResponse, $publicKeyCredentialRequestOptions, $serverRequest, null !== $userEntity ? $userEntity->getId() : null ); } public function enforceAndroidSafetyNetVerification(ClientInterface $client, string $apiKey, RequestFactoryInterface $requestFactory): void { $this->httpClient = $client; $this->googleApiKey = $apiKey; $this->requestFactory = $requestFactory; } private function getAttestationStatementSupportManager(): AttestationStatementSupportManager { $attestationStatementSupportManager = new AttestationStatementSupportManager(); $attestationStatementSupportManager->add(new NoneAttestationStatementSupport()); if (null !== $this->metadataStatementRepository) { $coseAlgorithmManager = $this->coseAlgorithmManagerFactory->create($this->selectedAlgorithms); $attestationStatementSupportManager->add(new FidoU2FAttestationStatementSupport(null, $this->metadataStatementRepository)); $attestationStatementSupportManager->add(new AndroidSafetyNetAttestationStatementSupport($this->httpClient, $this->googleApiKey, $this->requestFactory, 2000, 60000, $this->metadataStatementRepository)); $attestationStatementSupportManager->add(new AndroidKeyAttestationStatementSupport(null, $this->metadataStatementRepository)); $attestationStatementSupportManager->add(new TPMAttestationStatementSupport($this->metadataStatementRepository)); $attestationStatementSupportManager->add(new PackedAttestationStatementSupport(null, $coseAlgorithmManager, $this->metadataStatementRepository)); } return $attestationStatementSupportManager; } } webauthn-lib/src/Credential.php 0000644 00000001365 15174023160 0012503 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; /** * @see https://w3c.github.io/webappsec-credential-management/#credential */ abstract class Credential { /** * @var string */ protected $id; /** * @var string */ protected $type; public function __construct(string $id, string $type) { $this->id = $id; $this->type = $type; } public function getId(): string { return $this->id; } public function getType(): string { return $this->type; } } webauthn-lib/src/StringStream.php 0000644 00000003226 15174023160 0013051 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Assert\Assertion; use CBOR\Stream; final class StringStream implements Stream { /** * @var resource */ private $data; /** * @var int */ private $length; /** * @var int */ private $totalRead = 0; public function __construct(string $data) { $this->length = mb_strlen($data, '8bit'); $resource = fopen('php://memory', 'rb+'); Assertion::isResource($resource, 'Unable to open memory'); $result = fwrite($resource, $data); Assertion::integer($result, 'Unable to write memory'); $result = rewind($resource); Assertion::true($result, 'Unable to read memory'); $this->data = $resource; } public function read(int $length): string { if (0 === $length) { return ''; } $read = fread($this->data, $length); Assertion::string($read, 'Unable to read memory'); $bytesRead = mb_strlen($read, '8bit'); Assertion::length($read, $length, sprintf('Out of range. Expected: %d, read: %d.', $length, $bytesRead), null, '8bit'); $this->totalRead += $bytesRead; return $read; } public function close(): void { $result = fclose($this->data); Assertion::true($result, 'Unable to close the memory'); } public function isEOF(): bool { return $this->totalRead === $this->length; } } webauthn-lib/src/PublicKeyCredentialRpEntity.php 0000644 00000002167 15174023160 0016013 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Assert\Assertion; class PublicKeyCredentialRpEntity extends PublicKeyCredentialEntity { /** * @var string|null */ protected $id; public function __construct(string $name, ?string $id = null, ?string $icon = null) { parent::__construct($name, $icon); $this->id = $id; } public function getId(): ?string { return $this->id; } public static function createFromArray(array $json): self { Assertion::keyExists($json, 'name', 'Invalid input. "name" is missing.'); return new self( $json['name'], $json['id'] ?? null, $json['icon'] ?? null ); } public function jsonSerialize(): array { $json = parent::jsonSerialize(); if (null !== $this->id) { $json['id'] = $this->id; } return $json; } } webauthn-lib/src/PublicKeyCredentialOptions.php 0000644 00000002556 15174023160 0015672 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use JsonSerializable; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; abstract class PublicKeyCredentialOptions implements JsonSerializable { /** * @var string */ protected $challenge; /** * @var int|null */ protected $timeout; /** * @var AuthenticationExtensionsClientInputs */ protected $extensions; public function __construct(string $challenge, ?int $timeout = null, ?AuthenticationExtensionsClientInputs $extensions = null) { $this->challenge = $challenge; $this->timeout = $timeout; $this->extensions = $extensions ?? new AuthenticationExtensionsClientInputs(); } public function getChallenge(): string { return $this->challenge; } public function getTimeout(): ?int { return $this->timeout; } public function getExtensions(): AuthenticationExtensionsClientInputs { return $this->extensions; } abstract public static function createFromString(string $data): self; abstract public static function createFromArray(array $json): self; } webauthn-lib/src/PublicKeyCredentialCreationOptions.php 0000644 00000013257 15174023160 0017357 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Assert\Assertion; use Base64Url\Base64Url; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; class PublicKeyCredentialCreationOptions extends PublicKeyCredentialOptions { public const ATTESTATION_CONVEYANCE_PREFERENCE_NONE = 'none'; public const ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT = 'indirect'; public const ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT = 'direct'; /** * @var PublicKeyCredentialRpEntity */ private $rp; /** * @var PublicKeyCredentialUserEntity */ private $user; /** * @var PublicKeyCredentialParameters[] */ private $pubKeyCredParams; /** * @var PublicKeyCredentialDescriptor[] */ private $excludeCredentials; /** * @var AuthenticatorSelectionCriteria */ private $authenticatorSelection; /** * @var string */ private $attestation; /** * PublicKeyCredentialCreationOptions constructor. * * @param PublicKeyCredentialParameters[] $pubKeyCredParams * @param PublicKeyCredentialDescriptor[] $excludeCredentials */ public function __construct(PublicKeyCredentialRpEntity $rp, PublicKeyCredentialUserEntity $user, string $challenge, array $pubKeyCredParams, ?int $timeout, array $excludeCredentials, AuthenticatorSelectionCriteria $authenticatorSelection, string $attestation, ?AuthenticationExtensionsClientInputs $extensions) { parent::__construct($challenge, $timeout, $extensions); $this->rp = $rp; $this->user = $user; $this->pubKeyCredParams = array_values($pubKeyCredParams); $this->excludeCredentials = array_values($excludeCredentials); $this->authenticatorSelection = $authenticatorSelection; $this->attestation = $attestation; } public function getRp(): PublicKeyCredentialRpEntity { return $this->rp; } public function getUser(): PublicKeyCredentialUserEntity { return $this->user; } /** * @return PublicKeyCredentialParameters[] */ public function getPubKeyCredParams(): array { return $this->pubKeyCredParams; } /** * @return PublicKeyCredentialDescriptor[] */ public function getExcludeCredentials(): array { return $this->excludeCredentials; } public function getAuthenticatorSelection(): AuthenticatorSelectionCriteria { return $this->authenticatorSelection; } public function getAttestation(): string { return $this->attestation; } public static function createFromString(string $data): PublicKeyCredentialOptions { $data = json_decode($data, true); Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid data'); Assertion::isArray($data, 'Invalid data'); return self::createFromArray($data); } public static function createFromArray(array $json): PublicKeyCredentialOptions { Assertion::keyExists($json, 'rp', 'Invalid input. "rp" is missing.'); Assertion::keyExists($json, 'pubKeyCredParams', 'Invalid input. "pubKeyCredParams" is missing.'); Assertion::isArray($json['pubKeyCredParams'], 'Invalid input. "pubKeyCredParams" is not an array.'); Assertion::keyExists($json, 'challenge', 'Invalid input. "challenge" is missing.'); Assertion::keyExists($json, 'attestation', 'Invalid input. "attestation" is missing.'); Assertion::keyExists($json, 'user', 'Invalid input. "user" is missing.'); Assertion::keyExists($json, 'authenticatorSelection', 'Invalid input. "authenticatorSelection" is missing.'); $pubKeyCredParams = []; foreach ($json['pubKeyCredParams'] as $pubKeyCredParam) { $pubKeyCredParams[] = PublicKeyCredentialParameters::createFromArray($pubKeyCredParam); } $excludeCredentials = []; if (isset($json['excludeCredentials'])) { foreach ($json['excludeCredentials'] as $excludeCredential) { $excludeCredentials[] = PublicKeyCredentialDescriptor::createFromArray($excludeCredential); } } return new self( PublicKeyCredentialRpEntity::createFromArray($json['rp']), PublicKeyCredentialUserEntity::createFromArray($json['user']), Base64Url::decode($json['challenge']), $pubKeyCredParams, $json['timeout'] ?? null, $excludeCredentials, AuthenticatorSelectionCriteria::createFromArray($json['authenticatorSelection']), $json['attestation'], isset($json['extensions']) ? AuthenticationExtensionsClientInputs::createFromArray($json['extensions']) : new AuthenticationExtensionsClientInputs() ); } public function jsonSerialize(): array { $json = [ 'rp' => $this->rp, 'pubKeyCredParams' => $this->pubKeyCredParams, 'challenge' => Base64Url::encode($this->challenge), 'attestation' => $this->attestation, 'user' => $this->user, 'authenticatorSelection' => $this->authenticatorSelection, ]; if (0 !== \count($this->excludeCredentials)) { $json['excludeCredentials'] = $this->excludeCredentials; } if (0 !== $this->extensions->count()) { $json['extensions'] = $this->extensions; } if (null !== $this->timeout) { $json['timeout'] = $this->timeout; } return $json; } } webauthn-lib/src/PublicKeyCredentialEntity.php 0000644 00000001736 15174023160 0015512 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use JsonSerializable; abstract class PublicKeyCredentialEntity implements JsonSerializable { /** * @var string */ protected $name; /** * @var string|null */ protected $icon; public function __construct(string $name, ?string $icon) { $this->name = $name; $this->icon = $icon; } public function getName(): string { return $this->name; } public function getIcon(): ?string { return $this->icon; } public function jsonSerialize(): array { $json = [ 'name' => $this->name, ]; if (null !== $this->icon) { $json['icon'] = $this->icon; } return $json; } } webauthn-lib/src/AuthenticatorAssertionResponse.php 0000644 00000002742 15174023160 0016652 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Assert\Assertion; /** * @see https://www.w3.org/TR/webauthn/#authenticatorassertionresponse */ class AuthenticatorAssertionResponse extends AuthenticatorResponse { /** * @var AuthenticatorData */ private $authenticatorData; /** * @var string */ private $signature; /** * @var string|null */ private $userHandle; public function __construct(CollectedClientData $clientDataJSON, AuthenticatorData $authenticatorData, string $signature, ?string $userHandle) { parent::__construct($clientDataJSON); $this->authenticatorData = $authenticatorData; $this->signature = $signature; $this->userHandle = $userHandle; } public function getAuthenticatorData(): AuthenticatorData { return $this->authenticatorData; } public function getSignature(): string { return $this->signature; } public function getUserHandle(): ?string { if (null === $this->userHandle || '' === $this->userHandle) { return $this->userHandle; } $decoded = base64_decode($this->userHandle, true); Assertion::string($decoded, 'Unable to decode the data'); return $decoded; } } webauthn-lib/src/PublicKeyCredentialParameters.php 0000644 00000003231 15174023160 0016331 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Assert\Assertion; use JsonSerializable; class PublicKeyCredentialParameters implements JsonSerializable { /** * @var string */ private $type; /** * @var int */ private $alg; public function __construct(string $type, int $alg) { $this->type = $type; $this->alg = $alg; } public function getType(): string { return $this->type; } public function getAlg(): int { return $this->alg; } public static function createFromString(string $data): self { $data = json_decode($data, true); Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid data'); Assertion::isArray($data, 'Invalid data'); return self::createFromArray($data); } public static function createFromArray(array $json): self { Assertion::keyExists($json, 'type', 'Invalid input. "type" is missing.'); Assertion::string($json['type'], 'Invalid input. "type" is not a string.'); Assertion::keyExists($json, 'alg', 'Invalid input. "alg" is missing.'); Assertion::integer($json['alg'], 'Invalid input. "alg" is not an integer.'); return new self( $json['type'], $json['alg'] ); } public function jsonSerialize(): array { return [ 'type' => $this->type, 'alg' => $this->alg, ]; } } webauthn-lib/src/AuthenticatorResponse.php 0000644 00000001256 15174023160 0014761 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; /** * @see https://www.w3.org/TR/webauthn/#authenticatorresponse */ abstract class AuthenticatorResponse { /** * @var CollectedClientData */ private $clientDataJSON; public function __construct(CollectedClientData $clientDataJSON) { $this->clientDataJSON = $clientDataJSON; } public function getClientDataJSON(): CollectedClientData { return $this->clientDataJSON; } } webauthn-lib/src/PublicKeyCredentialSource.php 0000644 00000015205 15174023160 0015472 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Assert\Assertion; use Base64Url\Base64Url; use InvalidArgumentException; use JsonSerializable; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; use Throwable; use Webauthn\TrustPath\TrustPath; use Webauthn\TrustPath\TrustPathLoader; /** * @see https://www.w3.org/TR/webauthn/#iface-pkcredential */ class PublicKeyCredentialSource implements JsonSerializable { /** * @var string */ protected $publicKeyCredentialId; /** * @var string */ protected $type; /** * @var string[] */ protected $transports; /** * @var string */ protected $attestationType; /** * @var TrustPath */ protected $trustPath; /** * @var UuidInterface */ protected $aaguid; /** * @var string */ protected $credentialPublicKey; /** * @var string */ protected $userHandle; /** * @var int */ protected $counter; public function __construct(string $publicKeyCredentialId, string $type, array $transports, string $attestationType, TrustPath $trustPath, UuidInterface $aaguid, string $credentialPublicKey, string $userHandle, int $counter) { $this->publicKeyCredentialId = $publicKeyCredentialId; $this->type = $type; $this->transports = $transports; $this->aaguid = $aaguid; $this->credentialPublicKey = $credentialPublicKey; $this->userHandle = $userHandle; $this->counter = $counter; $this->attestationType = $attestationType; $this->trustPath = $trustPath; } /** * @deprecated Deprecated since v2.1. Will be removed in v3.0. Please use response from the credential source returned by the AuthenticatorAttestationResponseValidator after "check" method */ public static function createFromPublicKeyCredential(PublicKeyCredential $publicKeyCredential, string $userHandle): self { $response = $publicKeyCredential->getResponse(); Assertion::isInstanceOf($response, AuthenticatorAttestationResponse::class, 'This method is only available with public key credential containing an authenticator attestation response.'); $publicKeyCredentialDescriptor = $publicKeyCredential->getPublicKeyCredentialDescriptor(); $attestationStatement = $response->getAttestationObject()->getAttStmt(); $authenticatorData = $response->getAttestationObject()->getAuthData(); $attestedCredentialData = $authenticatorData->getAttestedCredentialData(); Assertion::notNull($attestedCredentialData, 'No attested credential data available'); return new self( $publicKeyCredentialDescriptor->getId(), $publicKeyCredentialDescriptor->getType(), $publicKeyCredentialDescriptor->getTransports(), $attestationStatement->getType(), $attestationStatement->getTrustPath(), $attestedCredentialData->getAaguid(), $attestedCredentialData->getCredentialPublicKey(), $userHandle, $authenticatorData->getSignCount() ); } public function getPublicKeyCredentialId(): string { return $this->publicKeyCredentialId; } public function getPublicKeyCredentialDescriptor(): PublicKeyCredentialDescriptor { return new PublicKeyCredentialDescriptor( $this->type, $this->publicKeyCredentialId, $this->transports ); } public function getAttestationType(): string { return $this->attestationType; } public function getTrustPath(): TrustPath { return $this->trustPath; } public function getAttestedCredentialData(): AttestedCredentialData { return new AttestedCredentialData( $this->aaguid, $this->publicKeyCredentialId, $this->credentialPublicKey ); } public function getType(): string { return $this->type; } /** * @return string[] */ public function getTransports(): array { return $this->transports; } public function getAaguid(): UuidInterface { return $this->aaguid; } public function getCredentialPublicKey(): string { return $this->credentialPublicKey; } public function getUserHandle(): string { return $this->userHandle; } public function getCounter(): int { return $this->counter; } public function setCounter(int $counter): void { $this->counter = $counter; } public static function createFromArray(array $data): self { $keys = array_keys(get_class_vars(self::class)); foreach ($keys as $key) { Assertion::keyExists($data, $key, sprintf('The parameter "%s" is missing', $key)); } switch (true) { case 36 === mb_strlen($data['aaguid'], '8bit'): $uuid = Uuid::fromString($data['aaguid']); break; default: // Kept for compatibility with old format $decoded = base64_decode($data['aaguid'], true); Assertion::string($decoded, 'Invalid AAGUID'); $uuid = Uuid::fromBytes($decoded); } try { return new self( Base64Url::decode($data['publicKeyCredentialId']), $data['type'], $data['transports'], $data['attestationType'], TrustPathLoader::loadTrustPath($data['trustPath']), $uuid, Base64Url::decode($data['credentialPublicKey']), Base64Url::decode($data['userHandle']), $data['counter'] ); } catch (Throwable $throwable) { throw new InvalidArgumentException('Unable to load the data', $throwable->getCode(), $throwable); } } public function jsonSerialize(): array { return [ 'publicKeyCredentialId' => Base64Url::encode($this->publicKeyCredentialId), 'type' => $this->type, 'transports' => $this->transports, 'attestationType' => $this->attestationType, 'trustPath' => $this->trustPath, 'aaguid' => $this->aaguid->toString(), 'credentialPublicKey' => Base64Url::encode($this->credentialPublicKey), 'userHandle' => Base64Url::encode($this->userHandle), 'counter' => $this->counter, ]; } } webauthn-lib/src/CertificateToolbox.php 0000644 00000020321 15174023160 0014213 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Assert\Assertion; use InvalidArgumentException; use Symfony\Component\Process\Process; use Webauthn\AttestationStatement\AttestationStatement; use Webauthn\MetadataService\MetadataStatement; use Webauthn\MetadataService\MetadataStatementRepository; class CertificateToolbox { public static function checkChain(array $certificates, array $trustedCertificates = []): void { $certificates = array_unique(array_merge($certificates, $trustedCertificates)); if (0 === \count($certificates)) { return; } self::checkCertificatesValidity($certificates); $filenames = []; $leafFilename = tempnam(sys_get_temp_dir(), 'webauthn-leaf-'); Assertion::string($leafFilename, 'Unable to get a temporary filename'); $leafCertificate = array_shift($certificates); $result = file_put_contents($leafFilename, $leafCertificate); Assertion::integer($result, 'Unable to write temporary data'); $filenames[] = $leafFilename; $processArguments = []; if (0 !== \count($certificates)) { $caFilename = tempnam(sys_get_temp_dir(), 'webauthn-ca-'); Assertion::string($caFilename, 'Unable to get a temporary filename'); $caCertificate = array_pop($certificates); $result = file_put_contents($caFilename, $caCertificate); Assertion::integer($result, 'Unable to write temporary data'); $processArguments[] = '-CAfile'; $processArguments[] = $caFilename; $filenames[] = $caFilename; } if (0 !== \count($certificates)) { $untrustedFilename = tempnam(sys_get_temp_dir(), 'webauthn-untrusted-'); Assertion::string($untrustedFilename, 'Unable to get a temporary filename'); foreach ($certificates as $certificate) { $result = file_put_contents($untrustedFilename, $certificate, FILE_APPEND); Assertion::integer($result, 'Unable to write temporary data'); $result = file_put_contents($untrustedFilename, PHP_EOL, FILE_APPEND); Assertion::integer($result, 'Unable to write temporary data'); } $processArguments[] = '-untrusted'; $processArguments[] = $untrustedFilename; $filenames[] = $untrustedFilename; } $processArguments[] = $leafFilename; array_unshift($processArguments, 'openssl', 'verify'); $process = new Process($processArguments); $process->start(); while ($process->isRunning()) { } foreach ($filenames as $filename) { $result = unlink($filename); Assertion::true($result, 'Unable to delete temporary file'); } if (!$process->isSuccessful()) { throw new InvalidArgumentException('Invalid certificate or certificate chain. Error is: '.$process->getErrorOutput()); } } public static function checkAttestationMedata(AttestationStatement $attestationStatement, string $aaguid, array $certificates, MetadataStatementRepository $metadataStatementRepository): array { $metadataStatement = $metadataStatementRepository->findOneByAAGUID($aaguid); if (null === $metadataStatement) { //Check certificate CA chain self::checkChain($certificates); return $certificates; } //FIXME: to decide later if relevant /*Assertion::eq('fido2', $metadataStatement->getProtocolFamily(), sprintf('The protocol family of the authenticator "%s" should be "fido2". Got "%s".', $aaguid, $metadataStatement->getProtocolFamily())); if (null !== $metadataStatement->getAssertionScheme()) { Assertion::eq('FIDOV2', $metadataStatement->getAssertionScheme(), sprintf('The assertion scheme of the authenticator "%s" should be "FIDOV2". Got "%s".', $aaguid, $metadataStatement->getAssertionScheme())); }*/ // Check Attestation Type is allowed if (0 !== \count($metadataStatement->getAttestationTypes())) { $type = self::getAttestationType($attestationStatement); Assertion::inArray($type, $metadataStatement->getAttestationTypes(), 'Invalid attestation statement. The attestation type is not allowed for this authenticator'); } $attestationRootCertificates = $metadataStatement->getAttestationRootCertificates(); if (0 === \count($attestationRootCertificates)) { self::checkChain($certificates); return $certificates; } foreach ($attestationRootCertificates as $key => $attestationRootCertificate) { $attestationRootCertificates[$key] = self::fixPEMStructure($attestationRootCertificate); } //Check certificate CA chain self::checkChain($certificates, $attestationRootCertificates); return $certificates; } private static function getAttestationType(AttestationStatement $attestationStatement): int { switch ($attestationStatement->getType()) { case AttestationStatement::TYPE_BASIC: return MetadataStatement::ATTESTATION_BASIC_FULL; case AttestationStatement::TYPE_SELF: return MetadataStatement::ATTESTATION_BASIC_SURROGATE; case AttestationStatement::TYPE_ATTCA: return MetadataStatement::ATTESTATION_ATTCA; case AttestationStatement::TYPE_ECDAA: return MetadataStatement::ATTESTATION_ECDAA; default: throw new InvalidArgumentException('Invalid attestation type'); } } public static function fixPEMStructure(string $certificate): string { $pemCert = '-----BEGIN CERTIFICATE-----'.PHP_EOL; $pemCert .= chunk_split($certificate, 64, PHP_EOL); $pemCert .= '-----END CERTIFICATE-----'.PHP_EOL; return $pemCert; } public static function convertDERToPEM(string $certificate): string { $derCertificate = self::unusedBytesFix($certificate); return self::fixPEMStructure(base64_encode($derCertificate)); } public static function convertAllDERToPEM(array $certificates): array { $certs = []; foreach ($certificates as $publicKey) { $certs[] = self::convertDERToPEM($publicKey); } return $certs; } private static function unusedBytesFix(string $certificate): string { $certificateHash = hash('sha256', $certificate); if (\in_array($certificateHash, self::getCertificateHashes(), true)) { $certificate[mb_strlen($certificate, '8bit') - 257] = "\0"; } return $certificate; } /** * @param string[] $certificates */ private static function checkCertificatesValidity(array $certificates): void { foreach ($certificates as $certificate) { $parsed = openssl_x509_parse($certificate); Assertion::isArray($parsed, 'Unable to read the certificate'); Assertion::keyExists($parsed, 'validTo_time_t', 'The certificate has no validity period'); Assertion::keyExists($parsed, 'validFrom_time_t', 'The certificate has no validity period'); Assertion::lessOrEqualThan(time(), $parsed['validTo_time_t'], 'The certificate expired'); Assertion::greaterOrEqualThan(time(), $parsed['validFrom_time_t'], 'The certificate is not usable yet'); } } /** * @return string[] */ private static function getCertificateHashes(): array { return [ '349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8', 'dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f', '1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae', 'd0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb', '6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897', 'ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511', ]; } } webauthn-lib/src/PublicKeyCredentialDescriptorCollection.php 0000644 00000004040 15174023160 0020357 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use ArrayIterator; use Assert\Assertion; use Countable; use Iterator; use IteratorAggregate; use JsonSerializable; class PublicKeyCredentialDescriptorCollection implements JsonSerializable, Countable, IteratorAggregate { /** * @var PublicKeyCredentialDescriptor[] */ private $publicKeyCredentialDescriptors = []; public function add(PublicKeyCredentialDescriptor $publicKeyCredentialDescriptor): void { $this->publicKeyCredentialDescriptors[$publicKeyCredentialDescriptor->getId()] = $publicKeyCredentialDescriptor; } public function has(string $id): bool { return \array_key_exists($id, $this->publicKeyCredentialDescriptors); } public function remove(string $id): void { if (!$this->has($id)) { return; } unset($this->publicKeyCredentialDescriptors[$id]); } public function getIterator(): Iterator { return new ArrayIterator($this->publicKeyCredentialDescriptors); } public function count(int $mode = COUNT_NORMAL): int { return \count($this->publicKeyCredentialDescriptors, $mode); } public function jsonSerialize(): array { return array_values($this->publicKeyCredentialDescriptors); } public static function createFromString(string $data): self { $data = json_decode($data, true); Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid data'); Assertion::isArray($data, 'Invalid data'); return self::createFromArray($data); } public static function createFromArray(array $json): self { $collection = new self(); foreach ($json as $item) { $collection->add(PublicKeyCredentialDescriptor::createFromArray($item)); } return $collection; } } webauthn-lib/src/AuthenticatorAssertionResponseValidator.php 0000644 00000022034 15174023160 0020514 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Assert\Assertion; use CBOR\Decoder; use CBOR\OtherObject\OtherObjectManager; use CBOR\Tag\TagObjectManager; use Cose\Algorithm\Manager; use Cose\Algorithm\Signature\Signature; use Cose\Key\Key; use Psr\Http\Message\ServerRequestInterface; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputs; use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler; use Webauthn\TokenBinding\TokenBindingHandler; use Webauthn\Util\CoseSignatureFixer; class AuthenticatorAssertionResponseValidator { /** * @var PublicKeyCredentialSourceRepository */ private $publicKeyCredentialSourceRepository; /** * @var Decoder */ private $decoder; /** * @var TokenBindingHandler */ private $tokenBindingHandler; /** * @var ExtensionOutputCheckerHandler */ private $extensionOutputCheckerHandler; /** * @var Manager|null */ private $algorithmManager; public function __construct(PublicKeyCredentialSourceRepository $publicKeyCredentialSourceRepository, ?Decoder $decoder, TokenBindingHandler $tokenBindingHandler, ExtensionOutputCheckerHandler $extensionOutputCheckerHandler, Manager $algorithmManager) { if (null !== $decoder) { @trigger_error('The argument "$decoder" is deprecated since 2.1 and will be removed in v3.0. Set null instead', E_USER_DEPRECATED); } $this->publicKeyCredentialSourceRepository = $publicKeyCredentialSourceRepository; $this->decoder = $decoder ?? new Decoder(new TagObjectManager(), new OtherObjectManager()); $this->tokenBindingHandler = $tokenBindingHandler; $this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler; $this->algorithmManager = $algorithmManager; } /** * @see https://www.w3.org/TR/webauthn/#verifying-assertion */ public function check(string $credentialId, AuthenticatorAssertionResponse $authenticatorAssertionResponse, PublicKeyCredentialRequestOptions $publicKeyCredentialRequestOptions, ServerRequestInterface $request, ?string $userHandle): PublicKeyCredentialSource { /* @see 7.2.1 */ if (0 !== \count($publicKeyCredentialRequestOptions->getAllowCredentials())) { Assertion::true($this->isCredentialIdAllowed($credentialId, $publicKeyCredentialRequestOptions->getAllowCredentials()), 'The credential ID is not allowed.'); } /* @see 7.2.2 */ $publicKeyCredentialSource = $this->publicKeyCredentialSourceRepository->findOneByCredentialId($credentialId); Assertion::notNull($publicKeyCredentialSource, 'The credential ID is invalid.'); /* @see 7.2.3 */ $attestedCredentialData = $publicKeyCredentialSource->getAttestedCredentialData(); $credentialUserHandle = $publicKeyCredentialSource->getUserHandle(); $responseUserHandle = $authenticatorAssertionResponse->getUserHandle(); /* @see 7.2.2 User Handle*/ if (null !== $userHandle) { //If the user was identified before the authentication ceremony was initiated, Assertion::eq($credentialUserHandle, $userHandle, 'Invalid user handle'); if (null !== $responseUserHandle && '' !== $responseUserHandle) { Assertion::eq($credentialUserHandle, $responseUserHandle, 'Invalid user handle'); } } else { Assertion::notEmpty($responseUserHandle, 'User handle is mandatory'); Assertion::eq($credentialUserHandle, $responseUserHandle, 'Invalid user handle'); } $credentialPublicKey = $attestedCredentialData->getCredentialPublicKey(); Assertion::notNull($credentialPublicKey, 'No public key available.'); $stream = new StringStream($credentialPublicKey); $credentialPublicKeyStream = $this->decoder->decode($stream); Assertion::true($stream->isEOF(), 'Invalid key. Presence of extra bytes.'); $stream->close(); /** @see 7.2.4 */ /** @see 7.2.5 */ //Nothing to do. Use of objects directly /** @see 7.2.6 */ $C = $authenticatorAssertionResponse->getClientDataJSON(); /* @see 7.2.7 */ Assertion::eq('webauthn.get', $C->getType(), 'The client data type is not "webauthn.get".'); /* @see 7.2.8 */ Assertion::true(hash_equals($publicKeyCredentialRequestOptions->getChallenge(), $C->getChallenge()), 'Invalid challenge.'); /** @see 7.2.9 */ $rpId = $publicKeyCredentialRequestOptions->getRpId() ?? $request->getUri()->getHost(); $rpIdLength = mb_strlen($rpId); $parsedRelyingPartyId = parse_url($C->getOrigin()); Assertion::isArray($parsedRelyingPartyId, 'Invalid origin'); $scheme = $parsedRelyingPartyId['scheme'] ?? ''; Assertion::eq('https', $scheme, 'Invalid scheme. HTTPS required.'); $clientDataRpId = $parsedRelyingPartyId['host'] ?? ''; Assertion::notEmpty($clientDataRpId, 'Invalid origin rpId.'); Assertion::eq(mb_substr($clientDataRpId, -$rpIdLength), $rpId, 'rpId mismatch.'); /* @see 7.2.10 */ if (null !== $C->getTokenBinding()) { $this->tokenBindingHandler->check($C->getTokenBinding(), $request); } /** @see 7.2.11 */ $facetId = $this->getFacetId($rpId, $publicKeyCredentialRequestOptions->getExtensions(), $authenticatorAssertionResponse->getAuthenticatorData()->getExtensions()); $rpIdHash = hash('sha256', $rpId, true); Assertion::true(hash_equals($rpIdHash, $authenticatorAssertionResponse->getAuthenticatorData()->getRpIdHash()), 'rpId hash mismatch.'); /* @see 7.2.12 */ /* @see 7.2.13 */ if (AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED === $publicKeyCredentialRequestOptions->getUserVerification()) { Assertion::true($authenticatorAssertionResponse->getAuthenticatorData()->isUserPresent(), 'User was not present'); Assertion::true($authenticatorAssertionResponse->getAuthenticatorData()->isUserVerified(), 'User authentication required.'); } /* @see 7.2.14 */ $extensions = $authenticatorAssertionResponse->getAuthenticatorData()->getExtensions(); if (null !== $extensions) { $this->extensionOutputCheckerHandler->check($extensions); } /** @see 7.2.15 */ $getClientDataJSONHash = hash('sha256', $authenticatorAssertionResponse->getClientDataJSON()->getRawData(), true); /* @see 7.2.16 */ $dataToVerify = $authenticatorAssertionResponse->getAuthenticatorData()->getAuthData().$getClientDataJSONHash; $signature = $authenticatorAssertionResponse->getSignature(); $coseKey = new Key($credentialPublicKeyStream->getNormalizedData()); $algorithm = $this->algorithmManager->get($coseKey->alg()); Assertion::isInstanceOf($algorithm, Signature::class, 'Invalid algorithm identifier. Should refer to a signature algorithm'); $signature = CoseSignatureFixer::fix($signature, $algorithm); Assertion::true($algorithm->verify($dataToVerify, $coseKey, $signature), 'Invalid signature.'); /* @see 7.2.17 */ $storedCounter = $publicKeyCredentialSource->getCounter(); $currentCounter = $authenticatorAssertionResponse->getAuthenticatorData()->getSignCount(); if (0 !== $currentCounter || 0 !== $storedCounter) { Assertion::greaterThan($currentCounter, $storedCounter, 'Invalid counter.'); } $publicKeyCredentialSource->setCounter($currentCounter); $this->publicKeyCredentialSourceRepository->saveCredentialSource($publicKeyCredentialSource); /* @see 7.2.18 */ //All good. We can continue. return $publicKeyCredentialSource; } private function isCredentialIdAllowed(string $credentialId, array $allowedCredentials): bool { foreach ($allowedCredentials as $allowedCredential) { if (hash_equals($allowedCredential->getId(), $credentialId)) { return true; } } return false; } private function getFacetId(string $rpId, AuthenticationExtensionsClientInputs $authenticationExtensionsClientInputs, ?AuthenticationExtensionsClientOutputs $authenticationExtensionsClientOutputs): string { switch (true) { case !$authenticationExtensionsClientInputs->has('appid'): return $rpId; case null === $authenticationExtensionsClientOutputs: return $rpId; case !$authenticationExtensionsClientOutputs->has('appid'): return $rpId; case true !== $authenticationExtensionsClientOutputs->get('appid'): return $rpId; default: return $authenticationExtensionsClientInputs->get('appid'); } } } webauthn-lib/src/AttestationStatement/AndroidKeyAttestationStatementSupport.php 0000644 00000017435 15174023160 0024355 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\AttestationStatement; use Assert\Assertion; use CBOR\Decoder; use CBOR\OtherObject\OtherObjectManager; use CBOR\Tag\TagObjectManager; use Cose\Algorithms; use Cose\Key\Ec2Key; use Cose\Key\Key; use Cose\Key\RsaKey; use FG\ASN1\ASNObject; use FG\ASN1\ExplicitlyTaggedObject; use FG\ASN1\Universal\OctetString; use FG\ASN1\Universal\Sequence; use Webauthn\AuthenticatorData; use Webauthn\CertificateToolbox; use Webauthn\MetadataService\MetadataStatementRepository; use Webauthn\StringStream; use Webauthn\TrustPath\CertificateTrustPath; final class AndroidKeyAttestationStatementSupport implements AttestationStatementSupport { /** * @var Decoder */ private $decoder; /** * @var MetadataStatementRepository|null */ private $metadataStatementRepository; public function __construct(?Decoder $decoder = null, ?MetadataStatementRepository $metadataStatementRepository = null) { if (null !== $decoder) { @trigger_error('The argument "$decoder" is deprecated since 2.1 and will be removed in v3.0. Set null instead', E_USER_DEPRECATED); } if (null === $metadataStatementRepository) { @trigger_error('Setting "null" for argument "$metadataStatementRepository" is deprecated since 2.1 and will be mandatory in v3.0.', E_USER_DEPRECATED); } $this->decoder = $decoder ?? new Decoder(new TagObjectManager(), new OtherObjectManager()); $this->metadataStatementRepository = $metadataStatementRepository; } public function name(): string { return 'android-key'; } public function load(array $attestation): AttestationStatement { Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object'); foreach (['sig', 'x5c', 'alg'] as $key) { Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key)); } $certificates = $attestation['attStmt']['x5c']; Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.'); Assertion::greaterThan(\count($certificates), 0, 'The attestation statement value "x5c" must be a list with at least one certificate.'); Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.'); $certificates = CertificateToolbox::convertAllDERToPEM($certificates); return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates)); } public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool { $trustPath = $attestationStatement->getTrustPath(); Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path'); $certificates = $trustPath->getCertificates(); if (null !== $this->metadataStatementRepository) { $certificates = CertificateToolbox::checkAttestationMedata( $attestationStatement, $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(), $certificates, $this->metadataStatementRepository ); } //Decode leaf attestation certificate $leaf = $certificates[0]; $this->checkCertificateAndGetPublicKey($leaf, $clientDataJSONHash, $authenticatorData); $signedData = $authenticatorData->getAuthData().$clientDataJSONHash; $alg = $attestationStatement->get('alg'); return 1 === openssl_verify($signedData, $attestationStatement->get('sig'), $leaf, Algorithms::getOpensslAlgorithmFor((int) $alg)); } private function checkCertificateAndGetPublicKey(string $certificate, string $clientDataHash, AuthenticatorData $authenticatorData): void { $resource = openssl_pkey_get_public($certificate); Assertion::isResource($resource, 'Unable to read the certificate'); $details = openssl_pkey_get_details($resource); Assertion::isArray($details, 'Unable to read the certificate'); //Check that authData publicKey matches the public key in the attestation certificate $attestedCredentialData = $authenticatorData->getAttestedCredentialData(); Assertion::notNull($attestedCredentialData, 'No attested credential data found'); $publicKeyData = $attestedCredentialData->getCredentialPublicKey(); Assertion::notNull($publicKeyData, 'No attested public key found'); $publicDataStream = new StringStream($publicKeyData); $coseKey = $this->decoder->decode($publicDataStream)->getNormalizedData(false); Assertion::true($publicDataStream->isEOF(), 'Invalid public key data. Presence of extra bytes.'); $publicDataStream->close(); $publicKey = Key::createFromData($coseKey); Assertion::true(($publicKey instanceof Ec2Key) || ($publicKey instanceof RsaKey), 'Unsupported key type'); Assertion::eq($publicKey->asPEM(), $details['key'], 'Invalid key'); /*---------------------------*/ $certDetails = openssl_x509_parse($certificate); //Find Android KeyStore Extension with OID “1.3.6.1.4.1.11129.2.1.17” in certificate extensions Assertion::keyExists($certDetails, 'extensions', 'The certificate has no extension'); Assertion::isArray($certDetails['extensions'], 'The certificate has no extension'); Assertion::keyExists($certDetails['extensions'], '1.3.6.1.4.1.11129.2.1.17', 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is missing'); $extension = $certDetails['extensions']['1.3.6.1.4.1.11129.2.1.17']; $extensionAsAsn1 = ASNObject::fromBinary($extension); Assertion::isInstanceOf($extensionAsAsn1, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); $objects = $extensionAsAsn1->getChildren(); //Check that attestationChallenge is set to the clientDataHash. Assertion::keyExists($objects, 4, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); Assertion::isInstanceOf($objects[4], OctetString::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); Assertion::eq($clientDataHash, hex2bin(($objects[4])->getContent()), 'The client data hash is not valid'); //Check that both teeEnforced and softwareEnforced structures don’t contain allApplications(600) tag. Assertion::keyExists($objects, 6, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); $softwareEnforcedFlags = $objects[6]; Assertion::isInstanceOf($softwareEnforcedFlags, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); $this->checkAbsenceOfAllApplicationsTag($softwareEnforcedFlags); Assertion::keyExists($objects, 7, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); $teeEnforcedFlags = $objects[6]; Assertion::isInstanceOf($teeEnforcedFlags, Sequence::class, 'The certificate extension "1.3.6.1.4.1.11129.2.1.17" is invalid'); $this->checkAbsenceOfAllApplicationsTag($teeEnforcedFlags); } private function checkAbsenceOfAllApplicationsTag(Sequence $sequence): void { foreach ($sequence->getChildren() as $tag) { Assertion::isInstanceOf($tag, ExplicitlyTaggedObject::class, 'Invalid tag'); /* @var ExplicitlyTaggedObject $tag */ Assertion::notEq(600, (int) $tag->getTag(), 'Forbidden tag 600 found'); } } } webauthn-lib/src/AttestationStatement/AttestationObjectLoader.php 0000644 00000007637 15174023160 0021402 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\AttestationStatement; use Assert\Assertion; use Base64Url\Base64Url; use CBOR\Decoder; use CBOR\MapObject; use CBOR\OtherObject\OtherObjectManager; use CBOR\Tag\TagObjectManager; use Ramsey\Uuid\Uuid; use Webauthn\AttestedCredentialData; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputsLoader; use Webauthn\AuthenticatorData; use Webauthn\StringStream; class AttestationObjectLoader { private const FLAG_AT = 0b01000000; private const FLAG_ED = 0b10000000; /** * @var Decoder */ private $decoder; /** * @var AttestationStatementSupportManager */ private $attestationStatementSupportManager; public function __construct(AttestationStatementSupportManager $attestationStatementSupportManager, ?Decoder $decoder = null) { if (null !== $decoder) { @trigger_error('The argument "$decoder" is deprecated since 2.1 and will be removed in v3.0. Set null instead', E_USER_DEPRECATED); } $this->decoder = $decoder ?? new Decoder(new TagObjectManager(), new OtherObjectManager()); $this->attestationStatementSupportManager = $attestationStatementSupportManager; } public function load(string $data): AttestationObject { $decodedData = Base64Url::decode($data); $stream = new StringStream($decodedData); $parsed = $this->decoder->decode($stream); $attestationObject = $parsed->getNormalizedData(); Assertion::true($stream->isEOF(), 'Invalid attestation object. Presence of extra bytes.'); $stream->close(); Assertion::isArray($attestationObject, 'Invalid attestation object'); Assertion::keyExists($attestationObject, 'authData', 'Invalid attestation object'); Assertion::keyExists($attestationObject, 'fmt', 'Invalid attestation object'); Assertion::keyExists($attestationObject, 'attStmt', 'Invalid attestation object'); $authData = $attestationObject['authData']; $attestationStatementSupport = $this->attestationStatementSupportManager->get($attestationObject['fmt']); $attestationStatement = $attestationStatementSupport->load($attestationObject); $authDataStream = new StringStream($authData); $rp_id_hash = $authDataStream->read(32); $flags = $authDataStream->read(1); $signCount = $authDataStream->read(4); $signCount = unpack('N', $signCount)[1]; $attestedCredentialData = null; if (0 !== (\ord($flags) & self::FLAG_AT)) { $aaguid = Uuid::fromBytes($authDataStream->read(16)); $credentialLength = $authDataStream->read(2); $credentialLength = unpack('n', $credentialLength)[1]; $credentialId = $authDataStream->read($credentialLength); $credentialPublicKey = $this->decoder->decode($authDataStream); Assertion::isInstanceOf($credentialPublicKey, MapObject::class, 'The data does not contain a valid credential public key.'); $attestedCredentialData = new AttestedCredentialData($aaguid, $credentialId, (string) $credentialPublicKey); } $extension = null; if (0 !== (\ord($flags) & self::FLAG_ED)) { $extension = $this->decoder->decode($authDataStream); $extension = AuthenticationExtensionsClientOutputsLoader::load($extension); } Assertion::true($authDataStream->isEOF(), 'Invalid authentication data. Presence of extra bytes.'); $authDataStream->close(); $authenticatorData = new AuthenticatorData($authData, $rp_id_hash, $flags, $signCount, $attestedCredentialData, $extension); return new AttestationObject($data, $attestationStatement, $authenticatorData); } } webauthn-lib/src/AttestationStatement/AttestationStatementSupport.php 0000644 00000001132 15174023160 0022366 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\AttestationStatement; use Webauthn\AuthenticatorData; interface AttestationStatementSupport { public function name(): string; public function load(array $attestation): AttestationStatement; public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool; } webauthn-lib/src/AttestationStatement/AttestationStatement.php 0000644 00000006422 15174023160 0021000 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\AttestationStatement; use Assert\Assertion; use JsonSerializable; use Webauthn\TrustPath\TrustPath; use Webauthn\TrustPath\TrustPathLoader; class AttestationStatement implements JsonSerializable { public const TYPE_NONE = 'none'; public const TYPE_BASIC = 'basic'; public const TYPE_SELF = 'self'; public const TYPE_ATTCA = 'attca'; public const TYPE_ECDAA = 'ecdaa'; /** * @var string */ private $fmt; /** * @var array */ private $attStmt; /** * @var TrustPath */ private $trustPath; /** * @var string */ private $type; public function __construct(string $fmt, array $attStmt, string $type, TrustPath $trustPath) { $this->fmt = $fmt; $this->attStmt = $attStmt; $this->type = $type; $this->trustPath = $trustPath; } public static function createNone(string $fmt, array $attStmt, TrustPath $trustPath): self { return new self($fmt, $attStmt, self::TYPE_NONE, $trustPath); } public static function createBasic(string $fmt, array $attStmt, TrustPath $trustPath): self { return new self($fmt, $attStmt, self::TYPE_BASIC, $trustPath); } public static function createSelf(string $fmt, array $attStmt, TrustPath $trustPath): self { return new self($fmt, $attStmt, self::TYPE_SELF, $trustPath); } public static function createAttCA(string $fmt, array $attStmt, TrustPath $trustPath): self { return new self($fmt, $attStmt, self::TYPE_ATTCA, $trustPath); } public static function createEcdaa(string $fmt, array $attStmt, TrustPath $trustPath): self { return new self($fmt, $attStmt, self::TYPE_ECDAA, $trustPath); } public function getFmt(): string { return $this->fmt; } public function getAttStmt(): array { return $this->attStmt; } public function has(string $key): bool { return \array_key_exists($key, $this->attStmt); } /** * @return mixed */ public function get(string $key) { Assertion::true($this->has($key), sprintf('The attestation statement has no key "%s".', $key)); return $this->attStmt[$key]; } public function getTrustPath(): TrustPath { return $this->trustPath; } public function getType(): string { return $this->type; } public static function createFromArray(array $data): self { foreach (['fmt', 'attStmt', 'trustPath', 'type'] as $key) { Assertion::keyExists($data, $key, sprintf('The key "%s" is missing', $key)); } return new self( $data['fmt'], $data['attStmt'], $data['type'], TrustPathLoader::loadTrustPath($data['trustPath']) ); } public function jsonSerialize(): array { return [ 'fmt' => $this->fmt, 'attStmt' => $this->attStmt, 'trustPath' => $this->trustPath, 'type' => $this->type, ]; } } webauthn-lib/src/AttestationStatement/NoneAttestationStatementSupport.php 0000644 00000002011 15174023160 0023203 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\AttestationStatement; use Assert\Assertion; use Webauthn\AuthenticatorData; use Webauthn\TrustPath\EmptyTrustPath; final class NoneAttestationStatementSupport implements AttestationStatementSupport { public function name(): string { return 'none'; } public function load(array $attestation): AttestationStatement { Assertion::noContent($attestation['attStmt'], 'Invalid attestation object'); return AttestationStatement::createNone($attestation['fmt'], $attestation['attStmt'], new EmptyTrustPath()); } public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool { return 0 === \count($attestationStatement->getAttStmt()); } } webauthn-lib/src/AttestationStatement/FidoU2FAttestationStatementSupport.php 0000644 00000013531 15174023160 0023513 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\AttestationStatement; use Assert\Assertion; use CBOR\Decoder; use CBOR\MapObject; use CBOR\OtherObject\OtherObjectManager; use CBOR\Tag\TagObjectManager; use Cose\Key\Ec2Key; use InvalidArgumentException; use Throwable; use Webauthn\AuthenticatorData; use Webauthn\CertificateToolbox; use Webauthn\MetadataService\MetadataStatementRepository; use Webauthn\StringStream; use Webauthn\TrustPath\CertificateTrustPath; final class FidoU2FAttestationStatementSupport implements AttestationStatementSupport { /** * @var Decoder */ private $decoder; /** * @var MetadataStatementRepository|null */ private $metadataStatementRepository; public function __construct(?Decoder $decoder = null, ?MetadataStatementRepository $metadataStatementRepository = null) { if (null !== $decoder) { @trigger_error('The argument "$decoder" is deprecated since 2.1 and will be removed in v3.0. Set null instead', E_USER_DEPRECATED); } if (null === $metadataStatementRepository) { @trigger_error('Setting "null" for argument "$metadataStatementRepository" is deprecated since 2.1 and will be mandatory in v3.0.', E_USER_DEPRECATED); } $this->decoder = $decoder ?? new Decoder(new TagObjectManager(), new OtherObjectManager()); $this->metadataStatementRepository = $metadataStatementRepository; } public function name(): string { return 'fido-u2f'; } public function load(array $attestation): AttestationStatement { Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object'); foreach (['sig', 'x5c'] as $key) { Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key)); } $certificates = $attestation['attStmt']['x5c']; Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with one certificate.'); Assertion::count($certificates, 1, 'The attestation statement value "x5c" must be a list with one certificate.'); Assertion::allString($certificates, 'The attestation statement value "x5c" must be a list with one certificate.'); reset($certificates); $certificates = CertificateToolbox::convertAllDERToPEM($certificates); $this->checkCertificate($certificates[0]); return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates)); } public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool { Assertion::eq( $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(), '00000000-0000-0000-0000-000000000000', 'Invalid AAGUID for fido-u2f attestation statement. Shall be "00000000-0000-0000-0000-000000000000"' ); if (null !== $this->metadataStatementRepository) { CertificateToolbox::checkAttestationMedata( $attestationStatement, $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(), [], $this->metadataStatementRepository ); } $trustPath = $attestationStatement->getTrustPath(); Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path'); $dataToVerify = "\0"; $dataToVerify .= $authenticatorData->getRpIdHash(); $dataToVerify .= $clientDataJSONHash; $dataToVerify .= $authenticatorData->getAttestedCredentialData()->getCredentialId(); $dataToVerify .= $this->extractPublicKey($authenticatorData->getAttestedCredentialData()->getCredentialPublicKey()); return 1 === openssl_verify($dataToVerify, $attestationStatement->get('sig'), $trustPath->getCertificates()[0], OPENSSL_ALGO_SHA256); } private function extractPublicKey(?string $publicKey): string { Assertion::notNull($publicKey, 'The attested credential data does not contain a valid public key.'); $publicKeyStream = new StringStream($publicKey); $coseKey = $this->decoder->decode($publicKeyStream); Assertion::true($publicKeyStream->isEOF(), 'Invalid public key. Presence of extra bytes.'); $publicKeyStream->close(); Assertion::isInstanceOf($coseKey, MapObject::class, 'The attested credential data does not contain a valid public key.'); $coseKey = $coseKey->getNormalizedData(); $ec2Key = new Ec2Key($coseKey + [Ec2Key::TYPE => 2, Ec2Key::DATA_CURVE => Ec2Key::CURVE_P256]); return "\x04".$ec2Key->x().$ec2Key->y(); } private function checkCertificate(string $publicKey): void { try { $resource = openssl_pkey_get_public($publicKey); Assertion::isResource($resource, 'Unable to load the public key'); } catch (Throwable $throwable) { throw new InvalidArgumentException('Invalid certificate or certificate chain', 0, $throwable); } $details = openssl_pkey_get_details($resource); Assertion::keyExists($details, 'ec', 'Invalid certificate or certificate chain'); Assertion::keyExists($details['ec'], 'curve_name', 'Invalid certificate or certificate chain'); Assertion::eq($details['ec']['curve_name'], 'prime256v1', 'Invalid certificate or certificate chain'); Assertion::keyExists($details['ec'], 'curve_oid', 'Invalid certificate or certificate chain'); Assertion::eq($details['ec']['curve_oid'], '1.2.840.10045.3.1.7', 'Invalid certificate or certificate chain'); } } webauthn-lib/src/AttestationStatement/AndroidSafetyNetAttestationStatementSupport.php 0000644 00000024171 15174023160 0025522 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\AttestationStatement; use Assert\Assertion; use InvalidArgumentException; use Jose\Component\Core\AlgorithmManager; use Jose\Component\Core\Util\JsonConverter; use Jose\Component\KeyManagement\JWKFactory; use Jose\Component\Signature\Algorithm; use Jose\Component\Signature\JWS; use Jose\Component\Signature\JWSVerifier; use Jose\Component\Signature\Serializer\CompactSerializer; use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; use Psr\Http\Message\ResponseInterface; use RuntimeException; use Webauthn\AuthenticatorData; use Webauthn\CertificateToolbox; use Webauthn\MetadataService\MetadataStatementRepository; use Webauthn\TrustPath\CertificateTrustPath; final class AndroidSafetyNetAttestationStatementSupport implements AttestationStatementSupport { /** * @var string|null */ private $apiKey; /** * @var ClientInterface|null */ private $client; /** * @var CompactSerializer */ private $jwsSerializer; /** * @var JWSVerifier|null */ private $jwsVerifier; /** * @var RequestFactoryInterface|null */ private $requestFactory; /** * @var int */ private $leeway; /** * @var int */ private $maxAge; /** * @var MetadataStatementRepository|null */ private $metadataStatementRepository; public function __construct(?ClientInterface $client = null, ?string $apiKey = null, ?RequestFactoryInterface $requestFactory = null, int $leeway = 0, int $maxAge = 60000, ?MetadataStatementRepository $metadataStatementRepository = null) { foreach ([Algorithm\RS256::class] as $algorithm) { if (!class_exists($algorithm)) { throw new RuntimeException('The algorithms RS256 is missing. Did you forget to install the package web-token/jwt-signature-algorithm-rsa?'); } } $this->jwsSerializer = new CompactSerializer(); $this->apiKey = $apiKey; $this->client = $client; $this->requestFactory = $requestFactory; $this->initJwsVerifier(); $this->leeway = $leeway; $this->maxAge = $maxAge; $this->metadataStatementRepository = $metadataStatementRepository; } public function name(): string { return 'android-safetynet'; } public function load(array $attestation): AttestationStatement { Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object'); foreach (['ver', 'response'] as $key) { Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key)); Assertion::notEmpty($attestation['attStmt'][$key], sprintf('The attestation statement value "%s" is empty.', $key)); } $jws = $this->jwsSerializer->unserialize($attestation['attStmt']['response']); $jwsHeader = $jws->getSignature(0)->getProtectedHeader(); Assertion::keyExists($jwsHeader, 'x5c', 'The response in the attestation statement must contain a "x5c" header.'); Assertion::notEmpty($jwsHeader['x5c'], 'The "x5c" parameter in the attestation statement response must contain at least one certificate.'); $certificates = $this->convertCertificatesToPem($jwsHeader['x5c']); $attestation['attStmt']['jws'] = $jws; return AttestationStatement::createBasic( $this->name(), $attestation['attStmt'], new CertificateTrustPath($certificates) ); } public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool { $trustPath = $attestationStatement->getTrustPath(); Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path'); $certificates = $trustPath->getCertificates(); if (null !== $this->metadataStatementRepository) { $certificates = CertificateToolbox::checkAttestationMedata( $attestationStatement, $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(), $certificates, $this->metadataStatementRepository ); } $parsedCertificate = openssl_x509_parse(current($certificates)); Assertion::isArray($parsedCertificate, 'Invalid attestation object'); Assertion::keyExists($parsedCertificate, 'subject', 'Invalid attestation object'); Assertion::keyExists($parsedCertificate['subject'], 'CN', 'Invalid attestation object'); Assertion::eq($parsedCertificate['subject']['CN'], 'attest.android.com', 'Invalid attestation object'); /** @var JWS $jws */ $jws = $attestationStatement->get('jws'); $payload = $jws->getPayload(); $this->validatePayload($payload, $clientDataJSONHash, $authenticatorData); //Check the signature $this->validateSignature($jws, $trustPath); //Check against Google service $this->validateUsingGoogleApi($attestationStatement); return true; } private function validatePayload(?string $payload, string $clientDataJSONHash, AuthenticatorData $authenticatorData): void { Assertion::notNull($payload, 'Invalid attestation object'); $payload = JsonConverter::decode($payload); Assertion::isArray($payload, 'Invalid attestation object'); Assertion::keyExists($payload, 'nonce', 'Invalid attestation object. "nonce" is missing.'); Assertion::eq($payload['nonce'], base64_encode(hash('sha256', $authenticatorData->getAuthData().$clientDataJSONHash, true)), 'Invalid attestation object. Invalid nonce'); Assertion::keyExists($payload, 'ctsProfileMatch', 'Invalid attestation object. "ctsProfileMatch" is missing.'); Assertion::true($payload['ctsProfileMatch'], 'Invalid attestation object. "ctsProfileMatch" value is false.'); Assertion::keyExists($payload, 'timestampMs', 'Invalid attestation object. Timestamp is missing.'); Assertion::integer($payload['timestampMs'], 'Invalid attestation object. Timestamp shall be an integer.'); $currentTime = time() * 1000; Assertion::lessOrEqualThan($payload['timestampMs'], $currentTime + $this->leeway, sprintf('Invalid attestation object. Issued in the future. Current time: %d. Response time: %d', $currentTime, $payload['timestampMs'])); Assertion::lessOrEqualThan($currentTime - $payload['timestampMs'], $this->maxAge, sprintf('Invalid attestation object. Too old. Current time: %d. Response time: %d', $currentTime, $payload['timestampMs'])); } private function validateSignature(JWS $jws, CertificateTrustPath $trustPath): void { $jwk = JWKFactory::createFromCertificate($trustPath->getCertificates()[0]); $isValid = $this->jwsVerifier->verifyWithKey($jws, $jwk, 0); Assertion::true($isValid, 'Invalid response signature'); } private function validateUsingGoogleApi(AttestationStatement $attestationStatement): void { if (null === $this->client || null === $this->apiKey || null === $this->requestFactory) { return; } $uri = sprintf('https://www.googleapis.com/androidcheck/v1/attestations/verify?key=%s', urlencode($this->apiKey)); $requestBody = sprintf('{"signedAttestation":"%s"}', $attestationStatement->get('response')); $request = $this->requestFactory->createRequest('POST', $uri); $request = $request->withHeader('content-type', 'application/json'); $request->getBody()->write($requestBody); $response = $this->client->sendRequest($request); $this->checkGoogleApiResponse($response); $responseBody = $this->getResponseBody($response); $responseBodyJson = json_decode($responseBody, true); Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid response.'); Assertion::keyExists($responseBodyJson, 'isValidSignature', 'Invalid response.'); Assertion::boolean($responseBodyJson['isValidSignature'], 'Invalid response.'); Assertion::true($responseBodyJson['isValidSignature'], 'Invalid response.'); } private function getResponseBody(ResponseInterface $response): string { $responseBody = ''; $response->getBody()->rewind(); do { $tmp = $response->getBody()->read(1024); if ('' === $tmp) { break; } $responseBody .= $tmp; } while (true); return $responseBody; } private function checkGoogleApiResponse(ResponseInterface $response): void { Assertion::eq(200, $response->getStatusCode(), 'Request did not succeeded'); Assertion::true($response->hasHeader('content-type'), 'Unrecognized response'); foreach ($response->getHeader('content-type') as $header) { if (0 === mb_strpos($header, 'application/json')) { return; } } throw new InvalidArgumentException('Unrecognized response'); } private function convertCertificatesToPem(array $certificates): array { foreach ($certificates as $k => $v) { $certificates[$k] = CertificateToolbox::fixPEMStructure($v); } return $certificates; } private function initJwsVerifier(): void { $algorithmClasses = [ Algorithm\RS256::class, Algorithm\RS384::class, Algorithm\RS512::class, Algorithm\PS256::class, Algorithm\PS384::class, Algorithm\PS512::class, Algorithm\ES256::class, Algorithm\ES384::class, Algorithm\ES512::class, Algorithm\EdDSA::class, ]; $algorithms = []; foreach ($algorithmClasses as $key => $algorithm) { if (class_exists($algorithm)) { $algorithms[] = new $algorithm(); } } $algorithmManager = new AlgorithmManager($algorithms); $this->jwsVerifier = new JWSVerifier($algorithmManager); } } webauthn-lib/src/AttestationStatement/TPMAttestationStatementSupport.php 0000644 00000032412 15174023160 0022754 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\AttestationStatement; use Assert\Assertion; use Base64Url\Base64Url; use CBOR\Decoder; use CBOR\MapObject; use CBOR\OtherObject\OtherObjectManager; use CBOR\Tag\TagObjectManager; use Cose\Algorithms; use Cose\Key\Ec2Key; use Cose\Key\Key; use Cose\Key\OkpKey; use Cose\Key\RsaKey; use DateTimeImmutable; use InvalidArgumentException; use RuntimeException; use Webauthn\AuthenticatorData; use Webauthn\CertificateToolbox; use Webauthn\MetadataService\MetadataStatementRepository; use Webauthn\StringStream; use Webauthn\TrustPath\CertificateTrustPath; use Webauthn\TrustPath\EcdaaKeyIdTrustPath; final class TPMAttestationStatementSupport implements AttestationStatementSupport { /** * @var MetadataStatementRepository|null */ private $metadataStatementRepository; public function name(): string { return 'tpm'; } public function __construct(?MetadataStatementRepository $metadataStatementRepository = null) { $this->metadataStatementRepository = $metadataStatementRepository; } public function load(array $attestation): AttestationStatement { Assertion::keyExists($attestation, 'attStmt', 'Invalid attestation object'); Assertion::keyNotExists($attestation['attStmt'], 'ecdaaKeyId', 'ECDAA not supported'); foreach (['ver', 'ver', 'sig', 'alg', 'certInfo', 'pubArea'] as $key) { Assertion::keyExists($attestation['attStmt'], $key, sprintf('The attestation statement value "%s" is missing.', $key)); } Assertion::eq('2.0', $attestation['attStmt']['ver'], 'Invalid attestation object'); $certInfo = $this->checkCertInfo($attestation['attStmt']['certInfo']); Assertion::eq('8017', bin2hex($certInfo['type']), 'Invalid attestation object'); $pubArea = $this->checkPubArea($attestation['attStmt']['pubArea']); $pubAreaAttestedNameAlg = mb_substr($certInfo['attestedName'], 0, 2, '8bit'); $pubAreaHash = hash($this->getTPMHash($pubAreaAttestedNameAlg), $attestation['attStmt']['pubArea'], true); $attestedName = $pubAreaAttestedNameAlg.$pubAreaHash; Assertion::eq($attestedName, $certInfo['attestedName'], 'Invalid attested name'); $attestation['attStmt']['parsedCertInfo'] = $certInfo; $attestation['attStmt']['parsedPubArea'] = $pubArea; $certificates = CertificateToolbox::convertAllDERToPEM($attestation['attStmt']['x5c']); Assertion::minCount($certificates, 1, 'The attestation statement value "x5c" must be a list with at least one certificate.'); return AttestationStatement::createAttCA( $this->name(), $attestation['attStmt'], new CertificateTrustPath($certificates) ); } public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool { $attToBeSigned = $authenticatorData->getAuthData().$clientDataJSONHash; $attToBeSignedHash = hash(Algorithms::getHashAlgorithmFor((int) $attestationStatement->get('alg')), $attToBeSigned, true); Assertion::eq($attestationStatement->get('parsedCertInfo')['extraData'], $attToBeSignedHash, 'Invalid attestation hash'); $this->checkUniquePublicKey( $attestationStatement->get('parsedPubArea')['unique'], $authenticatorData->getAttestedCredentialData()->getCredentialPublicKey() ); switch (true) { case $attestationStatement->getTrustPath() instanceof CertificateTrustPath: return $this->processWithCertificate($clientDataJSONHash, $attestationStatement, $authenticatorData); case $attestationStatement->getTrustPath() instanceof EcdaaKeyIdTrustPath: return $this->processWithECDAA(); default: throw new InvalidArgumentException('Unsupported attestation statement'); } } private function checkUniquePublicKey(string $unique, string $cborPublicKey): void { $cborDecoder = new Decoder(new TagObjectManager(), new OtherObjectManager()); $publicKey = $cborDecoder->decode(new StringStream($cborPublicKey)); Assertion::isInstanceOf($publicKey, MapObject::class, 'Invalid public key'); $key = new Key($publicKey->getNormalizedData(false)); switch ($key->type()) { case Key::TYPE_OKP: $uniqueFromKey = (new OkpKey($key->getData()))->x(); break; case Key::TYPE_EC2: $ec2Key = new Ec2Key($key->getData()); $uniqueFromKey = "\x04".$ec2Key->x().$ec2Key->y(); break; case Key::TYPE_RSA: $uniqueFromKey = (new RsaKey($key->getData()))->n(); break; default: throw new InvalidArgumentException('Invalid or unsupported key type.'); } Assertion::eq($unique, $uniqueFromKey, 'Invalid pubArea.unique value'); } private function checkCertInfo(string $data): array { $certInfo = new StringStream($data); $magic = $certInfo->read(4); Assertion::eq('ff544347', bin2hex($magic), 'Invalid attestation object'); $type = $certInfo->read(2); $qualifiedSignerLength = unpack('n', $certInfo->read(2))[1]; $qualifiedSigner = $certInfo->read($qualifiedSignerLength); //Ignored $extraDataLength = unpack('n', $certInfo->read(2))[1]; $extraData = $certInfo->read($extraDataLength); $clockInfo = $certInfo->read(17); //Ignore $firmwareVersion = $certInfo->read(8); $attestedNameLength = unpack('n', $certInfo->read(2))[1]; $attestedName = $certInfo->read($attestedNameLength); $attestedQualifiedNameLength = unpack('n', $certInfo->read(2))[1]; $attestedQualifiedName = $certInfo->read($attestedQualifiedNameLength); //Ignore Assertion::true($certInfo->isEOF(), 'Invalid certificate information. Presence of extra bytes.'); $certInfo->close(); return [ 'magic' => $magic, 'type' => $type, 'qualifiedSigner' => $qualifiedSigner, 'extraData' => $extraData, 'clockInfo' => $clockInfo, 'firmwareVersion' => $firmwareVersion, 'attestedName' => $attestedName, 'attestedQualifiedName' => $attestedQualifiedName, ]; } private function checkPubArea(string $data): array { $pubArea = new StringStream($data); $type = $pubArea->read(2); $nameAlg = $pubArea->read(2); $objectAttributes = $pubArea->read(4); $authPolicyLength = unpack('n', $pubArea->read(2))[1]; $authPolicy = $pubArea->read($authPolicyLength); $parameters = $this->getParameters($type, $pubArea); $uniqueLength = unpack('n', $pubArea->read(2))[1]; $unique = $pubArea->read($uniqueLength); Assertion::true($pubArea->isEOF(), 'Invalid public area. Presence of extra bytes.'); $pubArea->close(); return [ 'type' => $type, 'nameAlg' => $nameAlg, 'objectAttributes' => $objectAttributes, 'authPolicy' => $authPolicy, 'parameters' => $parameters, 'unique' => $unique, ]; } private function getParameters(string $type, StringStream $stream): array { switch (bin2hex($type)) { case '0001': case '0014': case '0016': return [ 'symmetric' => $stream->read(2), 'scheme' => $stream->read(2), 'keyBits' => unpack('n', $stream->read(2))[1], 'exponent' => $this->getExponent($stream->read(4)), ]; case '0018': return [ 'symmetric' => $stream->read(2), 'scheme' => $stream->read(2), 'curveId' => $stream->read(2), 'kdf' => $stream->read(2), ]; default: throw new InvalidArgumentException('Unsupported type'); } } private function getExponent(string $exponent): string { return '00000000' === bin2hex($exponent) ? Base64Url::decode('AQAB') : $exponent; } private function convertCertificatesToPem(array $certificates): array { foreach ($certificates as $k => $v) { $tmp = '-----BEGIN CERTIFICATE-----'.PHP_EOL; $tmp .= chunk_split(base64_encode($v), 64, PHP_EOL); $tmp .= '-----END CERTIFICATE-----'.PHP_EOL; $certificates[$k] = $tmp; } return $certificates; } private function getTPMHash(string $nameAlg): string { switch (bin2hex($nameAlg)) { case '0004': return 'sha1'; //: "TPM_ALG_SHA1", case '000b': return 'sha256'; //: "TPM_ALG_SHA256", case '000c': return 'sha384'; //: "TPM_ALG_SHA384", case '000d': return 'sha512'; //: "TPM_ALG_SHA512", default: throw new InvalidArgumentException('Unsupported hash algorithm'); } } private function processWithCertificate(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool { $trustPath = $attestationStatement->getTrustPath(); Assertion::isInstanceOf($trustPath, CertificateTrustPath::class, 'Invalid trust path'); $certificates = $trustPath->getCertificates(); if (null !== $this->metadataStatementRepository) { $certificates = CertificateToolbox::checkAttestationMedata( $attestationStatement, $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(), $certificates, $this->metadataStatementRepository ); } // Check certificate CA chain and returns the Attestation Certificate $this->checkCertificate($certificates[0], $authenticatorData); // Get the COSE algorithm identifier and the corresponding OpenSSL one $coseAlgorithmIdentifier = (int) $attestationStatement->get('alg'); $opensslAlgorithmIdentifier = Algorithms::getOpensslAlgorithmFor($coseAlgorithmIdentifier); $result = openssl_verify($attestationStatement->get('certInfo'), $attestationStatement->get('sig'), $certificates[0], $opensslAlgorithmIdentifier); return 1 === $result; } private function checkCertificate(string $attestnCert, AuthenticatorData $authenticatorData): void { $parsed = openssl_x509_parse($attestnCert); Assertion::isArray($parsed, 'Invalid certificate'); //Check version Assertion::false(!isset($parsed['version']) || 2 !== $parsed['version'], 'Invalid certificate version'); //Check subject field is empty Assertion::false(!isset($parsed['subject']) || !\is_array($parsed['subject']) || 0 !== \count($parsed['subject']), 'Invalid certificate name. The Subject should be empty'); // Check period of validity Assertion::keyExists($parsed, 'validFrom_time_t', 'Invalid certificate start date.'); Assertion::integer($parsed['validFrom_time_t'], 'Invalid certificate start date.'); $startDate = (new DateTimeImmutable())->setTimestamp($parsed['validFrom_time_t']); Assertion::true($startDate < new DateTimeImmutable(), 'Invalid certificate start date.'); Assertion::keyExists($parsed, 'validTo_time_t', 'Invalid certificate end date.'); Assertion::integer($parsed['validTo_time_t'], 'Invalid certificate end date.'); $endDate = (new DateTimeImmutable())->setTimestamp($parsed['validTo_time_t']); Assertion::true($endDate > new DateTimeImmutable(), 'Invalid certificate end date.'); //Check extensions Assertion::false(!isset($parsed['extensions']) || !\is_array($parsed['extensions']), 'Certificate extensions are missing'); //Check subjectAltName Assertion::false(!isset($parsed['extensions']['subjectAltName']), 'The "subjectAltName" is missing'); //Check extendedKeyUsage Assertion::false(!isset($parsed['extensions']['extendedKeyUsage']), 'The "subjectAltName" is missing'); Assertion::eq($parsed['extensions']['extendedKeyUsage'], '2.23.133.8.3', 'The "extendedKeyUsage" is invalid'); // id-fido-gen-ce-aaguid OID check Assertion::false(\in_array('1.3.6.1.4.1.45724.1.1.4', $parsed['extensions'], true) && !hash_equals($authenticatorData->getAttestedCredentialData()->getAaguid()->getBytes(), $parsed['extensions']['1.3.6.1.4.1.45724.1.1.4']), 'The value of the "aaguid" does not match with the certificate'); // TODO: For attestationRoot in metadata.attestationRootCertificates, generate verification chain verifX5C by appending attestationRoot to the x5c. Try verifying verifX5C. If successful go to next step. If fail try next attestationRoot. If no attestationRoots left to try, fail. } private function processWithECDAA(): bool { throw new RuntimeException('ECDAA not supported'); } } webauthn-lib/src/AttestationStatement/PackedAttestationStatementSupport.php 0000644 00000022442 15174023160 0023505 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\AttestationStatement; use Assert\Assertion; use CBOR\Decoder; use CBOR\MapObject; use CBOR\OtherObject\OtherObjectManager; use CBOR\Tag\TagObjectManager; use Cose\Algorithm\Manager; use Cose\Algorithm\Signature\Signature; use Cose\Algorithms; use Cose\Key\Key; use InvalidArgumentException; use RuntimeException; use Webauthn\AuthenticatorData; use Webauthn\CertificateToolbox; use Webauthn\MetadataService\MetadataStatementRepository; use Webauthn\StringStream; use Webauthn\TrustPath\CertificateTrustPath; use Webauthn\TrustPath\EcdaaKeyIdTrustPath; use Webauthn\TrustPath\EmptyTrustPath; use Webauthn\Util\CoseSignatureFixer; final class PackedAttestationStatementSupport implements AttestationStatementSupport { /** * @var Decoder */ private $decoder; /** * @var Manager */ private $algorithmManager; /** * @var MetadataStatementRepository|null */ private $metadataStatementRepository; public function __construct(?Decoder $decoder, Manager $algorithmManager, ?MetadataStatementRepository $metadataStatementRepository = null) { if (null !== $decoder) { @trigger_error('The argument "$decoder" is deprecated since 2.1 and will be removed in v3.0. Set null instead', E_USER_DEPRECATED); } if (null === $metadataStatementRepository) { @trigger_error('Setting "null" for argument "$metadataStatementRepository" is deprecated since 2.1 and will be mandatory in v3.0.', E_USER_DEPRECATED); } $this->decoder = $decoder ?? new Decoder(new TagObjectManager(), new OtherObjectManager()); $this->algorithmManager = $algorithmManager; $this->metadataStatementRepository = $metadataStatementRepository; } public function name(): string { return 'packed'; } public function load(array $attestation): AttestationStatement { Assertion::keyExists($attestation['attStmt'], 'sig', 'The attestation statement value "sig" is missing.'); Assertion::keyExists($attestation['attStmt'], 'alg', 'The attestation statement value "alg" is missing.'); Assertion::string($attestation['attStmt']['sig'], 'The attestation statement value "sig" is missing.'); switch (true) { case \array_key_exists('x5c', $attestation['attStmt']): return $this->loadBasicType($attestation); case \array_key_exists('ecdaaKeyId', $attestation['attStmt']): return $this->loadEcdaaType($attestation['attStmt']); default: return $this->loadEmptyType($attestation); } } public function isValid(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool { $trustPath = $attestationStatement->getTrustPath(); switch (true) { case $trustPath instanceof CertificateTrustPath: return $this->processWithCertificate($clientDataJSONHash, $attestationStatement, $authenticatorData, $trustPath); case $trustPath instanceof EcdaaKeyIdTrustPath: return $this->processWithECDAA(); case $trustPath instanceof EmptyTrustPath: return $this->processWithSelfAttestation($clientDataJSONHash, $attestationStatement, $authenticatorData); default: throw new InvalidArgumentException('Unsupported attestation statement'); } } private function loadBasicType(array $attestation): AttestationStatement { $certificates = $attestation['attStmt']['x5c']; Assertion::isArray($certificates, 'The attestation statement value "x5c" must be a list with at least one certificate.'); Assertion::minCount($certificates, 1, 'The attestation statement value "x5c" must be a list with at least one certificate.'); $certificates = CertificateToolbox::convertAllDERToPEM($certificates); return AttestationStatement::createBasic($attestation['fmt'], $attestation['attStmt'], new CertificateTrustPath($certificates)); } private function loadEcdaaType(array $attestation): AttestationStatement { $ecdaaKeyId = $attestation['attStmt']['ecdaaKeyId']; Assertion::string($ecdaaKeyId, 'The attestation statement value "ecdaaKeyId" is invalid.'); return AttestationStatement::createEcdaa($attestation['fmt'], $attestation['attStmt'], new EcdaaKeyIdTrustPath($attestation['ecdaaKeyId'])); } private function loadEmptyType(array $attestation): AttestationStatement { return AttestationStatement::createSelf($attestation['fmt'], $attestation['attStmt'], new EmptyTrustPath()); } private function checkCertificate(string $attestnCert, AuthenticatorData $authenticatorData): void { $parsed = openssl_x509_parse($attestnCert); Assertion::isArray($parsed, 'Invalid certificate'); //Check version Assertion::false(!isset($parsed['version']) || 2 !== $parsed['version'], 'Invalid certificate version'); //Check subject field Assertion::false(!isset($parsed['name']) || false === mb_strpos($parsed['name'], '/OU=Authenticator Attestation'), 'Invalid certificate name. The Subject Organization Unit must be "Authenticator Attestation"'); //Check extensions Assertion::false(!isset($parsed['extensions']) || !\is_array($parsed['extensions']), 'Certificate extensions are missing'); //Check certificate is not a CA cert Assertion::false(!isset($parsed['extensions']['basicConstraints']) || 'CA:FALSE' !== $parsed['extensions']['basicConstraints'], 'The Basic Constraints extension must have the CA component set to false'); $attestedCredentialData = $authenticatorData->getAttestedCredentialData(); Assertion::notNull($attestedCredentialData, 'No attested credential available'); // id-fido-gen-ce-aaguid OID check Assertion::false(\in_array('1.3.6.1.4.1.45724.1.1.4', $parsed['extensions'], true) && !hash_equals($attestedCredentialData->getAaguid()->getBytes(), $parsed['extensions']['1.3.6.1.4.1.45724.1.1.4']), 'The value of the "aaguid" does not match with the certificate'); } private function processWithCertificate(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData, CertificateTrustPath $trustPath): bool { $certificates = $trustPath->getCertificates(); if (null !== $this->metadataStatementRepository) { $certificates = CertificateToolbox::checkAttestationMedata( $attestationStatement, $authenticatorData->getAttestedCredentialData()->getAaguid()->toString(), $certificates, $this->metadataStatementRepository ); } // Check leaf certificate $this->checkCertificate($certificates[0], $authenticatorData); // Get the COSE algorithm identifier and the corresponding OpenSSL one $coseAlgorithmIdentifier = (int) $attestationStatement->get('alg'); $opensslAlgorithmIdentifier = Algorithms::getOpensslAlgorithmFor($coseAlgorithmIdentifier); // Verification of the signature $signedData = $authenticatorData->getAuthData().$clientDataJSONHash; $result = openssl_verify($signedData, $attestationStatement->get('sig'), $certificates[0], $opensslAlgorithmIdentifier); return 1 === $result; } private function processWithECDAA(): bool { throw new RuntimeException('ECDAA not supported'); } private function processWithSelfAttestation(string $clientDataJSONHash, AttestationStatement $attestationStatement, AuthenticatorData $authenticatorData): bool { $attestedCredentialData = $authenticatorData->getAttestedCredentialData(); Assertion::notNull($attestedCredentialData, 'No attested credential available'); $credentialPublicKey = $attestedCredentialData->getCredentialPublicKey(); Assertion::notNull($credentialPublicKey, 'No credential public key available'); $publicKeyStream = new StringStream($credentialPublicKey); $publicKey = $this->decoder->decode($publicKeyStream); Assertion::true($publicKeyStream->isEOF(), 'Invalid public key. Presence of extra bytes.'); $publicKeyStream->close(); Assertion::isInstanceOf($publicKey, MapObject::class, 'The attested credential data does not contain a valid public key.'); $publicKey = $publicKey->getNormalizedData(false); $publicKey = new Key($publicKey); Assertion::eq($publicKey->alg(), (int) $attestationStatement->get('alg'), 'The algorithm of the attestation statement and the key are not identical.'); $dataToVerify = $authenticatorData->getAuthData().$clientDataJSONHash; $algorithm = $this->algorithmManager->get((int) $attestationStatement->get('alg')); if (!$algorithm instanceof Signature) { throw new RuntimeException('Invalid algorithm'); } $signature = CoseSignatureFixer::fix($attestationStatement->get('sig'), $algorithm); return $algorithm->verify($dataToVerify, $publicKey, $signature); } } webauthn-lib/src/AttestationStatement/AttestationObject.php 0000644 00000002143 15174023160 0020236 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\AttestationStatement; use Webauthn\AuthenticatorData; class AttestationObject { /** * @var string */ private $rawAttestationObject; /** * @var AttestationStatement */ private $attStmt; /** * @var AuthenticatorData */ private $authData; public function __construct(string $rawAttestationObject, AttestationStatement $attStmt, AuthenticatorData $authData) { $this->rawAttestationObject = $rawAttestationObject; $this->attStmt = $attStmt; $this->authData = $authData; } public function getRawAttestationObject(): string { return $this->rawAttestationObject; } public function getAttStmt(): AttestationStatement { return $this->attStmt; } public function getAuthData(): AuthenticatorData { return $this->authData; } } webauthn-lib/src/AttestationStatement/AttestationStatementSupportManager.php 0000644 00000002043 15174023160 0023663 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\AttestationStatement; use Assert\Assertion; class AttestationStatementSupportManager { /** * @var AttestationStatementSupport[] */ private $attestationStatementSupports = []; public function add(AttestationStatementSupport $attestationStatementSupport): void { $this->attestationStatementSupports[$attestationStatementSupport->name()] = $attestationStatementSupport; } public function has(string $name): bool { return \array_key_exists($name, $this->attestationStatementSupports); } public function get(string $name): AttestationStatementSupport { Assertion::true($this->has($name), sprintf('The attestation statement format "%s" is not supported.', $name)); return $this->attestationStatementSupports[$name]; } } webauthn-lib/src/AttestedCredentialData.php 0000644 00000005117 15174023161 0014773 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Assert\Assertion; use JsonSerializable; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; /** * @see https://www.w3.org/TR/webauthn/#sec-attested-credential-data */ class AttestedCredentialData implements JsonSerializable { /** * @var UuidInterface */ private $aaguid; /** * @var string */ private $credentialId; /** * @var string|null */ private $credentialPublicKey; public function __construct(UuidInterface $aaguid, string $credentialId, ?string $credentialPublicKey) { $this->aaguid = $aaguid; $this->credentialId = $credentialId; $this->credentialPublicKey = $credentialPublicKey; } public function getAaguid(): UuidInterface { return $this->aaguid; } public function getCredentialId(): string { return $this->credentialId; } public function getCredentialPublicKey(): ?string { return $this->credentialPublicKey; } public static function createFromArray(array $json): self { Assertion::keyExists($json, 'aaguid', 'Invalid input. "aaguid" is missing.'); Assertion::keyExists($json, 'credentialId', 'Invalid input. "credentialId" is missing.'); switch (true) { case 36 === mb_strlen($json['aaguid'], '8bit'): $uuid = Uuid::fromString($json['aaguid']); break; default: // Kept for compatibility with old format $decoded = base64_decode($json['aaguid'], true); Assertion::string($decoded, 'Unable to decode the data'); $uuid = Uuid::fromBytes($decoded); } $credentialId = base64_decode($json['credentialId'], true); Assertion::string($credentialId, 'Unable to decode the data'); return new self( $uuid, $credentialId, isset($json['credentialPublicKey']) ? base64_decode($json['credentialPublicKey'], true) : null ); } public function jsonSerialize(): array { $result = [ 'aaguid' => $this->aaguid->toString(), 'credentialId' => base64_encode($this->credentialId), ]; if (null !== $this->credentialPublicKey) { $result['credentialPublicKey'] = base64_encode($this->credentialPublicKey); } return $result; } } webauthn-lib/src/TokenBinding/IgnoreTokenBindingHandler.php 0000644 00000001003 15174023161 0020007 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\TokenBinding; use Psr\Http\Message\ServerRequestInterface; final class IgnoreTokenBindingHandler implements TokenBindingHandler { public function check(TokenBinding $tokenBinding, ServerRequestInterface $request): void { //Does nothing } } webauthn-lib/src/TokenBinding/TokenBinding.php 0000644 00000003572 15174023161 0015362 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\TokenBinding; use Assert\Assertion; use Base64Url\Base64Url; class TokenBinding { public const TOKEN_BINDING_STATUS_PRESENT = 'present'; public const TOKEN_BINDING_STATUS_SUPPORTED = 'supported'; public const TOKEN_BINDING_STATUS_NOT_SUPPORTED = 'not-supported'; /** * @var string */ private $status; /** * @var string|null */ private $id; public function __construct(string $status, ?string $id) { Assertion::false(self::TOKEN_BINDING_STATUS_PRESENT === $status && null === $id, 'The member "id" is required when status is "present"'); $this->status = $status; $this->id = $id; } public static function createFormArray(array $json): self { Assertion::keyExists($json, 'status', 'The member "status" is required'); $status = $json['status']; Assertion::inArray( $status, self::getSupportedStatus(), sprintf('The member "status" is invalid. Supported values are: %s', implode(', ', self::getSupportedStatus())) ); $id = \array_key_exists('id', $json) ? Base64Url::decode($json['id']) : null; return new self($status, $id); } public function getStatus(): string { return $this->status; } public function getId(): ?string { return $this->id; } /** * @return string[] */ private static function getSupportedStatus(): array { return [ self::TOKEN_BINDING_STATUS_PRESENT, self::TOKEN_BINDING_STATUS_SUPPORTED, self::TOKEN_BINDING_STATUS_NOT_SUPPORTED, ]; } } webauthn-lib/src/TokenBinding/TokenBindingNotSupportedHandler.php 0000644 00000001214 15174023161 0021236 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\TokenBinding; use Assert\Assertion; use Psr\Http\Message\ServerRequestInterface; final class TokenBindingNotSupportedHandler implements TokenBindingHandler { public function check(TokenBinding $tokenBinding, ServerRequestInterface $request): void { Assertion::true(TokenBinding::TOKEN_BINDING_STATUS_PRESENT !== $tokenBinding->getStatus(), 'Token binding not supported.'); } } webauthn-lib/src/TokenBinding/TokenBindingHandler.php 0000644 00000000672 15174023161 0016656 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\TokenBinding; use Psr\Http\Message\ServerRequestInterface; interface TokenBindingHandler { public function check(TokenBinding $tokenBinding, ServerRequestInterface $request): void; } webauthn-lib/src/PublicKeyCredentialSourceRepository.php 0000644 00000001251 15174023161 0017567 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; interface PublicKeyCredentialSourceRepository { public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource; /** * @return PublicKeyCredentialSource[] */ public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array; public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource): void; } webauthn-lib/src/Util/CoseSignatureFixer.php 0000644 00000002717 15174023161 0015122 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\Util; use Cose\Algorithm\Signature\ECDSA; use Cose\Algorithm\Signature\Signature; /** * This class fixes the signature of the ECDSA based algorithms. * * @internal * * @see https://www.w3.org/TR/webauthn/#signature-attestation-types */ abstract class CoseSignatureFixer { public static function fix(string $signature, Signature $algorithm): string { switch ($algorithm::identifier()) { case ECDSA\ES256K::ID: case ECDSA\ES256::ID: if (64 === mb_strlen($signature, '8bit')) { return $signature; } return ECDSA\ECSignature::fromAsn1($signature, 64); //TODO: fix this hardcoded value by adding a dedicated method for the algorithms case ECDSA\ES384::ID: if (96 === mb_strlen($signature, '8bit')) { return $signature; } return ECDSA\ECSignature::fromAsn1($signature, 96); case ECDSA\ES512::ID: if (132 === mb_strlen($signature, '8bit')) { return $signature; } return ECDSA\ECSignature::fromAsn1($signature, 132); } return $signature; } } webauthn-lib/src/PublicKeyCredentialRequestOptions.php 0000644 00000007120 15174023161 0017234 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Assert\Assertion; use Base64Url\Base64Url; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs; class PublicKeyCredentialRequestOptions extends PublicKeyCredentialOptions { public const USER_VERIFICATION_REQUIREMENT_REQUIRED = 'required'; public const USER_VERIFICATION_REQUIREMENT_PREFERRED = 'preferred'; public const USER_VERIFICATION_REQUIREMENT_DISCOURAGED = 'discouraged'; /** * @var string|null */ private $rpId; /** * @var PublicKeyCredentialDescriptor[] */ private $allowCredentials; /** * @var string|null */ private $userVerification; /** * @param PublicKeyCredentialDescriptor[] $allowCredentials */ public function __construct(string $challenge, ?int $timeout = null, ?string $rpId = null, array $allowCredentials = [], ?string $userVerification = null, ?AuthenticationExtensionsClientInputs $extensions = null) { parent::__construct($challenge, $timeout, $extensions); $this->rpId = $rpId; $this->allowCredentials = array_values($allowCredentials); $this->userVerification = $userVerification; } public function getRpId(): ?string { return $this->rpId; } /** * @return PublicKeyCredentialDescriptor[] */ public function getAllowCredentials(): array { return $this->allowCredentials; } public function getUserVerification(): ?string { return $this->userVerification; } public static function createFromString(string $data): PublicKeyCredentialOptions { $data = json_decode($data, true); Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid data'); Assertion::isArray($data, 'Invalid data'); return self::createFromArray($data); } public static function createFromArray(array $json): PublicKeyCredentialOptions { Assertion::keyExists($json, 'challenge', 'Invalid input. "challenge" is missing.'); $allowCredentials = []; $allowCredentialList = $json['allowCredentials'] ?? []; foreach ($allowCredentialList as $allowCredential) { $allowCredentials[] = PublicKeyCredentialDescriptor::createFromArray($allowCredential); } return new self( Base64Url::decode($json['challenge']), $json['timeout'] ?? null, $json['rpId'] ?? null, $allowCredentials, $json['userVerification'] ?? null, isset($json['extensions']) ? AuthenticationExtensionsClientInputs::createFromArray($json['extensions']) : new AuthenticationExtensionsClientInputs() ); } public function jsonSerialize(): array { $json = [ 'challenge' => Base64Url::encode($this->challenge), ]; if (null !== $this->rpId) { $json['rpId'] = $this->rpId; } if (null !== $this->userVerification) { $json['userVerification'] = $this->userVerification; } if (0 !== \count($this->allowCredentials)) { $json['allowCredentials'] = $this->allowCredentials; } if (0 !== $this->extensions->count()) { $json['extensions'] = $this->extensions; } if (null !== $this->timeout) { $json['timeout'] = $this->timeout; } return $json; } } webauthn-lib/src/AuthenticatorAttestationResponseValidator.php 0000644 00000014655 15174023161 0021057 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Assert\Assertion; use Psr\Http\Message\ServerRequestInterface; use Webauthn\AttestationStatement\AttestationObject; use Webauthn\AttestationStatement\AttestationStatementSupportManager; use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler; use Webauthn\TokenBinding\TokenBindingHandler; class AuthenticatorAttestationResponseValidator { /** * @var AttestationStatementSupportManager */ private $attestationStatementSupportManager; /** * @var PublicKeyCredentialSourceRepository */ private $publicKeyCredentialSource; /** * @var TokenBindingHandler */ private $tokenBindingHandler; /** * @var ExtensionOutputCheckerHandler */ private $extensionOutputCheckerHandler; public function __construct(AttestationStatementSupportManager $attestationStatementSupportManager, PublicKeyCredentialSourceRepository $publicKeyCredentialSource, TokenBindingHandler $tokenBindingHandler, ExtensionOutputCheckerHandler $extensionOutputCheckerHandler) { $this->attestationStatementSupportManager = $attestationStatementSupportManager; $this->publicKeyCredentialSource = $publicKeyCredentialSource; $this->tokenBindingHandler = $tokenBindingHandler; $this->extensionOutputCheckerHandler = $extensionOutputCheckerHandler; } /** * @see https://www.w3.org/TR/webauthn/#registering-a-new-credential */ public function check(AuthenticatorAttestationResponse $authenticatorAttestationResponse, PublicKeyCredentialCreationOptions $publicKeyCredentialCreationOptions, ServerRequestInterface $request): PublicKeyCredentialSource { /** @see 7.1.1 */ //Nothing to do /** @see 7.1.2 */ $C = $authenticatorAttestationResponse->getClientDataJSON(); /* @see 7.1.3 */ Assertion::eq('webauthn.create', $C->getType(), 'The client data type is not "webauthn.create".'); /* @see 7.1.4 */ Assertion::true(hash_equals($publicKeyCredentialCreationOptions->getChallenge(), $C->getChallenge()), 'Invalid challenge.'); /** @see 7.1.5 */ $rpId = $publicKeyCredentialCreationOptions->getRp()->getId() ?? $request->getUri()->getHost(); $parsedRelyingPartyId = parse_url($C->getOrigin()); Assertion::isArray($parsedRelyingPartyId, sprintf('The origin URI "%s" is not valid', $C->getOrigin())); Assertion::keyExists($parsedRelyingPartyId, 'scheme', 'Invalid origin rpId.'); $scheme = $parsedRelyingPartyId['scheme'] ?? ''; Assertion::eq('https', $scheme, 'Invalid scheme. HTTPS required.'); $clientDataRpId = $parsedRelyingPartyId['host'] ?? ''; Assertion::notEmpty($clientDataRpId, 'Invalid origin rpId.'); $rpIdLength = mb_strlen($rpId); Assertion::eq(mb_substr($clientDataRpId, -$rpIdLength), $rpId, 'rpId mismatch.'); /* @see 7.1.6 */ if (null !== $C->getTokenBinding()) { $this->tokenBindingHandler->check($C->getTokenBinding(), $request); } /** @see 7.1.7 */ $clientDataJSONHash = hash('sha256', $authenticatorAttestationResponse->getClientDataJSON()->getRawData(), true); /** @see 7.1.8 */ $attestationObject = $authenticatorAttestationResponse->getAttestationObject(); /** @see 7.1.9 */ $rpIdHash = hash('sha256', $rpId, true); Assertion::true(hash_equals($rpIdHash, $attestationObject->getAuthData()->getRpIdHash()), 'rpId hash mismatch.'); /* @see 7.1.10 */ /* @see 7.1.11 */ if (AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED === $publicKeyCredentialCreationOptions->getAuthenticatorSelection()->getUserVerification()) { Assertion::true($attestationObject->getAuthData()->isUserPresent(), 'User was not present'); Assertion::true($attestationObject->getAuthData()->isUserVerified(), 'User authentication required.'); } /* @see 7.1.12 */ $extensions = $attestationObject->getAuthData()->getExtensions(); if (null !== $extensions) { $this->extensionOutputCheckerHandler->check($extensions); } /** @see 7.1.13 */ $fmt = $attestationObject->getAttStmt()->getFmt(); Assertion::true($this->attestationStatementSupportManager->has($fmt), 'Unsupported attestation statement format.'); /** @see 7.1.14 */ $attestationStatementSupport = $this->attestationStatementSupportManager->get($fmt); Assertion::true($attestationStatementSupport->isValid($clientDataJSONHash, $attestationObject->getAttStmt(), $attestationObject->getAuthData()), 'Invalid attestation statement.'); /* @see 7.1.15 */ /* @see 7.1.16 */ /* @see 7.1.17 */ Assertion::true($attestationObject->getAuthData()->hasAttestedCredentialData(), 'There is no attested credential data.'); $attestedCredentialData = $attestationObject->getAuthData()->getAttestedCredentialData(); Assertion::notNull($attestedCredentialData, 'There is no attested credential data.'); $credentialId = $attestedCredentialData->getCredentialId(); Assertion::null($this->publicKeyCredentialSource->findOneByCredentialId($credentialId), 'The credential ID already exists.'); /* @see 7.1.18 */ /* @see 7.1.19 */ return $this->createPublicKeyCredentialSource( $credentialId, $attestedCredentialData, $attestationObject, $publicKeyCredentialCreationOptions->getUser()->getId() ); } private function createPublicKeyCredentialSource(string $credentialId, AttestedCredentialData $attestedCredentialData, AttestationObject $attestationObject, string $userHandle): PublicKeyCredentialSource { return new PublicKeyCredentialSource( $credentialId, PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, [], $attestationObject->getAttStmt()->getType(), $attestationObject->getAttStmt()->getTrustPath(), $attestedCredentialData->getAaguid(), $attestedCredentialData->getCredentialPublicKey(), $userHandle, $attestationObject->getAuthData()->getSignCount() ); } } webauthn-lib/src/PublicKeyCredentialUserEntity.php 0000644 00000003650 15174023161 0016347 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Assert\Assertion; class PublicKeyCredentialUserEntity extends PublicKeyCredentialEntity { /** * @var string */ protected $id; /** * @var string */ protected $displayName; public function __construct(string $name, string $id, string $displayName, ?string $icon = null) { parent::__construct($name, $icon); $this->id = $id; $this->displayName = $displayName; } public function getId(): string { return $this->id; } public function getDisplayName(): string { return $this->displayName; } public static function createFromString(string $data): self { $data = json_decode($data, true); Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid data'); Assertion::isArray($data, 'Invalid data'); return self::createFromArray($data); } public static function createFromArray(array $json): self { Assertion::keyExists($json, 'name', 'Invalid input. "name" is missing.'); Assertion::keyExists($json, 'id', 'Invalid input. "id" is missing.'); Assertion::keyExists($json, 'displayName', 'Invalid input. "displayName" is missing.'); $id = base64_decode($json['id'], true); Assertion::string($id, 'Invalid parameter "id".'); return new self( $json['name'], $id, $json['displayName'], $json['icon'] ?? null ); } public function jsonSerialize(): array { $json = parent::jsonSerialize(); $json['id'] = base64_encode($this->id); $json['displayName'] = $this->displayName; return $json; } } webauthn-lib/src/PublicKeyCredentialLoader.php 0000644 00000012346 15174023161 0015444 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Assert\Assertion; use Base64Url\Base64Url; use CBOR\Decoder; use CBOR\MapObject; use CBOR\OtherObject\OtherObjectManager; use CBOR\Tag\TagObjectManager; use InvalidArgumentException; use Ramsey\Uuid\Uuid; use Webauthn\AttestationStatement\AttestationObjectLoader; use Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientOutputsLoader; class PublicKeyCredentialLoader { private const FLAG_AT = 0b01000000; private const FLAG_ED = 0b10000000; /** * @var AttestationObjectLoader */ private $attestationObjectLoader; /** * @var Decoder */ private $decoder; public function __construct(AttestationObjectLoader $attestationObjectLoader, ?Decoder $decoder = null) { if (null !== $decoder) { @trigger_error('The argument "$decoder" is deprecated since 2.1 and will be removed in v3.0. Set null instead', E_USER_DEPRECATED); } $this->decoder = $decoder ?? new Decoder(new TagObjectManager(), new OtherObjectManager()); $this->attestationObjectLoader = $attestationObjectLoader; } public function loadArray(array $json): PublicKeyCredential { foreach (['id', 'rawId', 'type'] as $key) { Assertion::keyExists($json, $key, sprintf('The parameter "%s" is missing', $key)); Assertion::string($json[$key], sprintf('The parameter "%s" shall be a string', $key)); } Assertion::keyExists($json, 'response', 'The parameter "response" is missing'); Assertion::isArray($json['response'], 'The parameter "response" shall be an array'); Assertion::eq($json['type'], 'public-key', sprintf('Unsupported type "%s"', $json['type'])); $id = Base64Url::decode($json['id']); $rawId = Base64Url::decode($json['rawId']); Assertion::true(hash_equals($id, $rawId)); $publicKeyCredential = new PublicKeyCredential( $json['id'], $json['type'], $rawId, $this->createResponse($json['response']) ); return $publicKeyCredential; } public function load(string $data): PublicKeyCredential { $json = json_decode($data, true); Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid data'); return $this->loadArray($json); } private function createResponse(array $response): AuthenticatorResponse { Assertion::keyExists($response, 'clientDataJSON'); switch (true) { case \array_key_exists('attestationObject', $response): $attestationObject = $this->attestationObjectLoader->load($response['attestationObject']); return new AuthenticatorAttestationResponse(CollectedClientData::createFormJson($response['clientDataJSON']), $attestationObject); case \array_key_exists('authenticatorData', $response) && \array_key_exists('signature', $response): $authData = Base64Url::decode($response['authenticatorData']); $authDataStream = new StringStream($authData); $rp_id_hash = $authDataStream->read(32); $flags = $authDataStream->read(1); $signCount = $authDataStream->read(4); $signCount = unpack('N', $signCount)[1]; $attestedCredentialData = null; if (0 !== (\ord($flags) & self::FLAG_AT)) { $aaguid = Uuid::fromBytes($authDataStream->read(16)); $credentialLength = $authDataStream->read(2); $credentialLength = unpack('n', $credentialLength)[1]; $credentialId = $authDataStream->read($credentialLength); $credentialPublicKey = $this->decoder->decode($authDataStream); Assertion::isInstanceOf($credentialPublicKey, MapObject::class, 'The data does not contain a valid credential public key.'); $attestedCredentialData = new AttestedCredentialData($aaguid, $credentialId, (string) $credentialPublicKey); } $extension = null; if (0 !== (\ord($flags) & self::FLAG_ED)) { $extension = $this->decoder->decode($authDataStream); $extension = AuthenticationExtensionsClientOutputsLoader::load($extension); } Assertion::true($authDataStream->isEOF(), 'Invalid authentication data. Presence of extra bytes.'); $authDataStream->close(); $authenticatorData = new AuthenticatorData($authData, $rp_id_hash, $flags, $signCount, $attestedCredentialData, $extension); return new AuthenticatorAssertionResponse( CollectedClientData::createFormJson($response['clientDataJSON']), $authenticatorData, Base64Url::decode($response['signature']), $response['userHandle'] ?? null ); default: throw new InvalidArgumentException('Unable to create the response object'); } } } webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientInputs.php 0000644 00000003165 15174023161 0025072 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\AuthenticationExtensions; use ArrayIterator; use Assert\Assertion; use function count; use Countable; use Iterator; use IteratorAggregate; use JsonSerializable; class AuthenticationExtensionsClientInputs implements JsonSerializable, Countable, IteratorAggregate { /** * @var AuthenticationExtension[] */ private $extensions = []; public function add(AuthenticationExtension $extension): void { $this->extensions[$extension->name()] = $extension; } public static function createFromArray(array $json): self { $object = new self(); foreach ($json as $k => $v) { $object->add(new AuthenticationExtension($k, $v)); } return $object; } public function has(string $key): bool { return \array_key_exists($key, $this->extensions); } /** * @return mixed */ public function get(string $key) { Assertion::true($this->has($key), sprintf('The extension with key "%s" is not available', $key)); return $this->extensions[$key]; } public function jsonSerialize(): array { return $this->extensions; } public function getIterator(): Iterator { return new ArrayIterator($this->extensions); } public function count(int $mode = COUNT_NORMAL): int { return \count($this->extensions, $mode); } } webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputsLoader.php 0000644 00000001630 15174023161 0026415 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\AuthenticationExtensions; use Assert\Assertion; use CBOR\CBORObject; use CBOR\MapObject; class AuthenticationExtensionsClientOutputsLoader { public static function load(CBORObject $object): AuthenticationExtensionsClientOutputs { Assertion::isInstanceOf($object, MapObject::class, 'Invalid extension object'); $data = $object->getNormalizedData(); $extensions = new AuthenticationExtensionsClientOutputs(); foreach ($data as $key => $value) { Assertion::string($key, 'Invalid extension key'); $extensions->add(new AuthenticationExtension($key, $value)); } return $extensions; } } webauthn-lib/src/AuthenticationExtensions/ExtensionOutputCheckerHandler.php 0000644 00000001373 15174023161 0023450 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\AuthenticationExtensions; class ExtensionOutputCheckerHandler { /** * @var ExtensionOutputChecker[] */ private $checkers = []; public function add(ExtensionOutputChecker $checker): void { $this->checkers[] = $checker; } /** * @throws ExtensionOutputError */ public function check(AuthenticationExtensionsClientOutputs $extensions): void { foreach ($this->checkers as $checker) { $checker->check($extensions); } } } webauthn-lib/src/AuthenticationExtensions/AuthenticationExtensionsClientOutputs.php 0000644 00000003605 15174023161 0025272 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\AuthenticationExtensions; use ArrayIterator; use Assert\Assertion; use Countable; use Iterator; use IteratorAggregate; use JsonSerializable; class AuthenticationExtensionsClientOutputs implements JsonSerializable, Countable, IteratorAggregate { /** * @var AuthenticationExtension[] */ private $extensions = []; public function add(AuthenticationExtension $extension): void { $this->extensions[$extension->name()] = $extension; } public static function createFromString(string $data): self { $data = json_decode($data, true); Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid data'); Assertion::isArray($data, 'Invalid data'); return self::createFromArray($data); } public static function createFromArray(array $json): self { $object = new self(); foreach ($json as $k => $v) { $object->add(new AuthenticationExtension($k, $v)); } return $object; } public function has(string $key): bool { return \array_key_exists($key, $this->extensions); } /** * @return mixed */ public function get(string $key) { Assertion::true($this->has($key), sprintf('The extension with key "%s" is not available', $key)); return $this->extensions[$key]; } public function jsonSerialize(): array { return $this->extensions; } public function getIterator(): Iterator { return new ArrayIterator($this->extensions); } public function count(int $mode = COUNT_NORMAL): int { return \count($this->extensions, $mode); } } webauthn-lib/src/AuthenticationExtensions/ExtensionOutputError.php 0000644 00000001544 15174023161 0021677 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\AuthenticationExtensions; use Exception; use Throwable; class ExtensionOutputError extends Exception { /** * @var AuthenticationExtension */ private $authenticationExtension; public function __construct(AuthenticationExtension $authenticationExtension, string $message = '', int $code = 0, Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->authenticationExtension = $authenticationExtension; } public function getAuthenticationExtension(): AuthenticationExtension { return $this->authenticationExtension; } } webauthn-lib/src/AuthenticationExtensions/AuthenticationExtension.php 0000644 00000001656 15174023161 0022350 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\AuthenticationExtensions; use JsonSerializable; class AuthenticationExtension implements JsonSerializable { /** * @var string */ private $name; /** * @var mixed */ private $value; /** * @param mixed $value */ public function __construct(string $name, $value) { $this->name = $name; $this->value = $value; } public function name(): string { return $this->name; } /** * @return mixed */ public function value() { return $this->value; } /** * @return mixed */ public function jsonSerialize() { return $this->value; } } webauthn-lib/src/AuthenticationExtensions/ExtensionOutputChecker.php 0000644 00000000705 15174023161 0022150 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\AuthenticationExtensions; interface ExtensionOutputChecker { /** * @throws ExtensionOutputError */ public function check(AuthenticationExtensionsClientOutputs $extensions): void; } webauthn-lib/src/TrustPath/CertificateTrustPath.php 0000644 00000002073 15174023161 0016466 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\TrustPath; use Assert\Assertion; final class CertificateTrustPath implements TrustPath { /** * @var string[] */ private $certificates; /** * @param string[] $certificates */ public function __construct(array $certificates) { $this->certificates = $certificates; } /** * @return string[] */ public function getCertificates(): array { return $this->certificates; } public static function createFromArray(array $data): TrustPath { Assertion::keyExists($data, 'x5c', 'The trust path type is invalid'); return new CertificateTrustPath($data['x5c']); } public function jsonSerialize(): array { return [ 'type' => self::class, 'x5c' => $this->certificates, ]; } } webauthn-lib/src/TrustPath/TrustPathLoader.php 0000644 00000002621 15174023161 0015451 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\TrustPath; use Assert\Assertion; use InvalidArgumentException; abstract class TrustPathLoader { public static function loadTrustPath(array $data): TrustPath { Assertion::keyExists($data, 'type', 'The trust path type is missing'); $type = $data['type']; $oldTypes = self::oldTrustPathTypes(); switch (true) { case \array_key_exists($type, $oldTypes): return $oldTypes[$type]::createFromArray($data); case class_exists($type): $implements = class_implements($type); if (\is_array($implements) && \in_array(TrustPath::class, $implements, true)) { return $type::createFromArray($data); } // no break default: throw new InvalidArgumentException(sprintf('The trust path type "%s" is not supported', $data['type'])); } } private static function oldTrustPathTypes(): array { return [ 'empty' => EmptyTrustPath::class, 'ecdaa_key_id' => EcdaaKeyIdTrustPath::class, 'x5c' => CertificateTrustPath::class, ]; } } webauthn-lib/src/TrustPath/TrustPath.php 0000644 00000000620 15174023161 0014317 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\TrustPath; use JsonSerializable; interface TrustPath extends JsonSerializable { public static function createFromArray(array $data): self; } webauthn-lib/src/TrustPath/EcdaaKeyIdTrustPath.php 0000644 00000001743 15174023161 0016172 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\TrustPath; use Assert\Assertion; final class EcdaaKeyIdTrustPath implements TrustPath { /** * @var string */ private $ecdaaKeyId; public function __construct(string $ecdaaKeyId) { $this->ecdaaKeyId = $ecdaaKeyId; } public function getEcdaaKeyId(): string { return $this->ecdaaKeyId; } public function jsonSerialize(): array { return [ 'type' => self::class, 'ecdaaKeyId' => $this->ecdaaKeyId, ]; } public static function createFromArray(array $data): TrustPath { Assertion::keyExists($data, 'ecdaaKeyId', 'The trust path type is invalid'); return new EcdaaKeyIdTrustPath($data['ecdaaKeyId']); } } webauthn-lib/src/TrustPath/EmptyTrustPath.php 0000644 00000001050 15174023161 0015334 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn\TrustPath; final class EmptyTrustPath implements TrustPath { public function jsonSerialize(): array { return [ 'type' => self::class, ]; } public static function createFromArray(array $data): TrustPath { return new EmptyTrustPath(); } } webauthn-lib/src/PublicKeyCredential.php 0000644 00000002550 15174023161 0014311 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Assert\Assertion; /** * @see https://www.w3.org/TR/webauthn/#iface-pkcredential */ class PublicKeyCredential extends Credential { /** * @var string */ protected $rawId; /** * @var AuthenticatorResponse */ protected $response; public function __construct(string $id, string $type, string $rawId, AuthenticatorResponse $response) { parent::__construct($id, $type); $this->rawId = $rawId; $this->response = $response; } public function getRawId(): string { return $this->rawId; } public function getResponse(): AuthenticatorResponse { return $this->response; } /** * @param string[] $transport */ public function getPublicKeyCredentialDescriptor(array $transport = []): PublicKeyCredentialDescriptor { return new PublicKeyCredentialDescriptor($this->getType(), $this->getRawId(), $transport); } public function __toString() { $encoded = json_encode($this); Assertion::string($encoded, 'Unable to encode the data'); return $encoded; } } webauthn-lib/src/AuthenticatorSelectionCriteria.php 0000644 00000005156 15174023161 0016577 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Assert\Assertion; use JsonSerializable; class AuthenticatorSelectionCriteria implements JsonSerializable { public const AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE = null; public const AUTHENTICATOR_ATTACHMENT_PLATFORM = 'platform'; public const AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM = 'cross-platform'; public const USER_VERIFICATION_REQUIREMENT_REQUIRED = 'required'; public const USER_VERIFICATION_REQUIREMENT_PREFERRED = 'preferred'; public const USER_VERIFICATION_REQUIREMENT_DISCOURAGED = 'discouraged'; /** * @var string|null */ private $authenticatorAttachment; /** * @var bool */ private $requireResidentKey; /** * @var string */ private $userVerification; public function __construct(?string $authenticatorAttachment = null, bool $requireResidentKey = false, string $userVerification = self::USER_VERIFICATION_REQUIREMENT_PREFERRED) { $this->authenticatorAttachment = $authenticatorAttachment; $this->requireResidentKey = $requireResidentKey; $this->userVerification = $userVerification; } public function getAuthenticatorAttachment(): ?string { return $this->authenticatorAttachment; } public function isRequireResidentKey(): bool { return $this->requireResidentKey; } public function getUserVerification(): string { return $this->userVerification; } public static function createFromString(string $data): self { $data = json_decode($data, true); Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid data'); Assertion::isArray($data, 'Invalid data'); return self::createFromArray($data); } public static function createFromArray(array $json): self { return new self( $json['authenticatorAttachment'] ?? null, $json['requireResidentKey'] ?? false, $json['userVerification'] ?? self::USER_VERIFICATION_REQUIREMENT_PREFERRED ); } public function jsonSerialize(): array { $json = [ 'requireResidentKey' => $this->requireResidentKey, 'userVerification' => $this->userVerification, ]; if (null !== $this->authenticatorAttachment) { $json['authenticatorAttachment'] = $this->authenticatorAttachment; } return $json; } } webauthn-lib/src/CollectedClientData.php 0000644 00000005627 15174023161 0014266 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Assert\Assertion; use Base64Url\Base64Url; use InvalidArgumentException; use Webauthn\TokenBinding\TokenBinding; class CollectedClientData { /** * @var string */ private $rawData; /** * @var array */ private $data; /** * @var string */ private $type; /** * @var string */ private $challenge; /** * @var string */ private $origin; /** * @var array|null */ private $tokenBinding; public function __construct(string $rawData, array $data) { $this->type = $this->findData($data, 'type'); $this->challenge = $this->findData($data, 'challenge', true, true); $this->origin = $this->findData($data, 'origin'); $this->tokenBinding = $this->findData($data, 'tokenBinding', false); $this->rawData = $rawData; $this->data = $data; } public static function createFormJson(string $data): self { $rawData = Base64Url::decode($data); $json = json_decode($rawData, true); Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid collected client data'); Assertion::isArray($json, 'Invalid collected client data'); return new self($rawData, $json); } public function getType(): string { return $this->type; } public function getChallenge(): string { return $this->challenge; } public function getOrigin(): string { return $this->origin; } public function getTokenBinding(): ?TokenBinding { return null === $this->tokenBinding ? null : TokenBinding::createFormArray($this->tokenBinding); } public function getRawData(): string { return $this->rawData; } /** * @return string[] */ public function all(): array { return array_keys($this->data); } public function has(string $key): bool { return \array_key_exists($key, $this->data); } /** * @return mixed */ public function get(string $key) { if (!$this->has($key)) { throw new InvalidArgumentException(sprintf('The key "%s" is missing', $key)); } return $this->data[$key]; } /** * @return mixed|null */ private function findData(array $json, string $key, bool $isRequired = true, bool $isB64 = false) { if (!\array_key_exists($key, $json)) { if ($isRequired) { throw new InvalidArgumentException(sprintf('The key "%s" is missing', $key)); } return; } return $isB64 ? Base64Url::decode($json[$key]) : $json[$key]; } } webauthn-lib/src/PublicKeyCredentialDescriptor.php 0000644 00000004465 15174023161 0016357 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Webauthn; use Assert\Assertion; use Base64Url\Base64Url; use JsonSerializable; class PublicKeyCredentialDescriptor implements JsonSerializable { public const CREDENTIAL_TYPE_PUBLIC_KEY = 'public-key'; public const AUTHENTICATOR_TRANSPORT_USB = 'usb'; public const AUTHENTICATOR_TRANSPORT_NFC = 'nfc'; public const AUTHENTICATOR_TRANSPORT_BLE = 'ble'; public const AUTHENTICATOR_TRANSPORT_INTERNAL = 'internal'; /** * @var string */ protected $type; /** * @var string */ protected $id; /** * @var string[] */ protected $transports; /** * @param string[] $transports */ public function __construct(string $type, string $id, array $transports = []) { $this->type = $type; $this->id = $id; $this->transports = $transports; } public function getType(): string { return $this->type; } public function getId(): string { return $this->id; } /** * @return string[] */ public function getTransports(): array { return $this->transports; } public static function createFromString(string $data): self { $data = json_decode($data, true); Assertion::eq(JSON_ERROR_NONE, json_last_error(), 'Invalid data'); Assertion::isArray($data, 'Invalid data'); return self::createFromArray($data); } public static function createFromArray(array $json): self { Assertion::keyExists($json, 'type', 'Invalid input. "type" is missing.'); Assertion::keyExists($json, 'id', 'Invalid input. "id" is missing.'); return new self( $json['type'], Base64Url::decode($json['id']), $json['transports'] ?? [] ); } public function jsonSerialize(): array { $json = [ 'type' => $this->type, 'id' => Base64Url::encode($this->id), ]; if (0 !== \count($this->transports)) { $json['transports'] = $this->transports; } return $json; } } cose-lib/src/Verifier.php 0000644 00000000416 15174023161 0011315 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose; class Verifier { } cose-lib/src/Key/OkpKey.php 0000644 00000003222 15174023161 0011472 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Key; use Assert\Assertion; class OkpKey extends Key { public const CURVE_X25519 = 4; public const CURVE_X448 = 5; public const CURVE_ED25519 = 6; public const CURVE_ED448 = 7; private const SUPPORTED_CURVES = [ self::CURVE_X25519, self::CURVE_X448, self::CURVE_ED25519, self::CURVE_ED448, ]; public const DATA_CURVE = -1; public const DATA_X = -2; public const DATA_D = -4; public function __construct(array $data) { parent::__construct($data); Assertion::eq($data[self::TYPE], self::TYPE_OKP, 'Invalid OKP key. The key type does not correspond to an OKP key'); Assertion::keyExists($data, self::DATA_CURVE, 'Invalid EC2 key. The curve is missing'); Assertion::keyExists($data, self::DATA_X, 'Invalid OKP key. The x coordinate is missing'); Assertion::inArray((int) $data[self::DATA_CURVE], self::SUPPORTED_CURVES, 'The curve is not supported'); } public function x(): string { return $this->get(self::DATA_X); } public function isPrivate(): bool { return \array_key_exists(self::DATA_D, $this->getData()); } public function d(): string { Assertion::true($this->isPrivate(), 'The key is not private'); return $this->get(self::DATA_D); } public function curve(): int { return (int) $this->get(self::DATA_CURVE); } } cose-lib/src/Key/Key.php 0000644 00000003602 15174023161 0011022 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Key; use Assert\Assertion; class Key { public const TYPE = 1; public const TYPE_OKP = 1; public const TYPE_EC2 = 2; public const TYPE_RSA = 3; public const TYPE_OCT = 4; public const KID = 2; public const ALG = 3; public const KEY_OPS = 4; public const BASE_IV = 5; /** * @var array */ private $data; public function __construct(array $data) { Assertion::keyExists($data, self::TYPE, 'Invalid key: the type is not defined'); $this->data = $data; } public static function createFromData(array $data): self { Assertion::keyExists($data, self::TYPE, 'Invalid key: the type is not defined'); switch ($data[self::TYPE]) { case 1: return new OkpKey($data); case 2: return new Ec2Key($data); case 3: return new RsaKey($data); case 4: return new SymmetricKey($data); default: return new self($data); } } /** * @return int|string */ public function type() { return $this->data[self::TYPE]; } public function alg(): int { return (int) $this->get(self::ALG); } public function getData(): array { return $this->data; } public function has(int $key): bool { return \array_key_exists($key, $this->data); } /** * @return mixed */ public function get(int $key) { Assertion::keyExists($this->data, $key, sprintf('The key has no data at index %d', $key)); return $this->data[$key]; } } cose-lib/src/Key/SymmetricKey.php 0000644 00000001375 15174023161 0012724 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Key; use Assert\Assertion; class SymmetricKey extends Key { public const DATA_K = -1; public function __construct(array $data) { parent::__construct($data); Assertion::eq($data[self::TYPE], self::TYPE_OCT, 'Invalid symmetric key. The key type does not correspond to a symmetric key'); Assertion::keyExists($data, self::DATA_K, 'Invalid symmetric key. The parameter "k" is missing'); } public function k(): string { return $this->get(self::DATA_K); } } cose-lib/src/Key/RsaKey.php 0000644 00000011372 15174023161 0011473 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Key; use Assert\Assertion; use FG\ASN1\Universal\BitString; use FG\ASN1\Universal\Integer; use FG\ASN1\Universal\NullObject; use FG\ASN1\Universal\ObjectIdentifier; use FG\ASN1\Universal\Sequence; class RsaKey extends Key { public const DATA_N = -1; public const DATA_E = -2; public const DATA_D = -3; public const DATA_P = -4; public const DATA_Q = -5; public const DATA_DP = -6; public const DATA_DQ = -7; public const DATA_QI = -8; public const DATA_OTHER = -9; public const DATA_RI = -10; public const DATA_DI = -11; public const DATA_TI = -12; public function __construct(array $data) { parent::__construct($data); Assertion::eq($data[self::TYPE], self::TYPE_RSA, 'Invalid RSA key. The key type does not correspond to a RSA key'); Assertion::keyExists($data, self::DATA_N, 'Invalid RSA key. The modulus is missing'); Assertion::keyExists($data, self::DATA_E, 'Invalid RSA key. The exponent is missing'); } public function n(): string { return $this->get(self::DATA_N); } public function e(): string { return $this->get(self::DATA_E); } public function d(): string { Assertion::true($this->isPrivate(), 'The key is not private.'); return $this->get(self::DATA_D); } public function p(): string { Assertion::true($this->isPrivate(), 'The key is not private.'); return $this->get(self::DATA_P); } public function q(): string { Assertion::true($this->isPrivate(), 'The key is not private.'); return $this->get(self::DATA_Q); } public function dP(): string { Assertion::true($this->isPrivate(), 'The key is not private.'); return $this->get(self::DATA_DP); } public function dQ(): string { Assertion::true($this->isPrivate(), 'The key is not private.'); return $this->get(self::DATA_DQ); } public function QInv(): string { Assertion::true($this->isPrivate(), 'The key is not private.'); return $this->get(self::DATA_QI); } public function other(): array { Assertion::true($this->isPrivate(), 'The key is not private.'); return $this->get(self::DATA_OTHER); } public function rI(): string { Assertion::true($this->isPrivate(), 'The key is not private.'); return $this->get(self::DATA_RI); } public function dI(): string { Assertion::true($this->isPrivate(), 'The key is not private.'); return $this->get(self::DATA_DI); } public function tI(): string { Assertion::true($this->isPrivate(), 'The key is not private.'); return $this->get(self::DATA_TI); } public function hasPrimes(): bool { return $this->has(self::DATA_P) && $this->has(self::DATA_Q); } public function primes(): array { return [ $this->p(), $this->q(), ]; } public function hasExponents(): bool { return $this->has(self::DATA_DP) && $this->has(self::DATA_DQ); } public function exponents(): array { return [ $this->dP(), $this->dQ(), ]; } public function hasCoefficient(): bool { return $this->has(self::DATA_QI); } public function isPublic(): bool { return !$this->isPrivate(); } public function isPrivate(): bool { return \array_key_exists(self::DATA_D, $this->getData()); } public function asPem(): string { Assertion::false($this->isPrivate(), 'Unsupported for private keys.'); $bitSring = new Sequence( new Integer($this->fromBase64ToInteger($this->n())), new Integer($this->fromBase64ToInteger($this->e())) ); $der = new Sequence( new Sequence( new ObjectIdentifier('1.2.840.113549.1.1.1'), new NullObject() ), new BitString(\bin2hex($bitSring->getBinary())) ); return $this->pem('PUBLIC KEY', $der->getBinary()); } private function fromBase64ToInteger(string $value): string { return gmp_strval(gmp_init(current(unpack('H*', $value)), 16), 10); } private function pem(string $type, string $der): string { return sprintf("-----BEGIN %s-----\n", mb_strtoupper($type)). chunk_split(base64_encode($der), 64, "\n"). sprintf("-----END %s-----\n", mb_strtoupper($type)); } } cose-lib/src/Key/Ec2Key.php 0000644 00000010420 15174023161 0011350 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Key; use Assert\Assertion; use FG\ASN1\ExplicitlyTaggedObject; use FG\ASN1\Universal\BitString; use FG\ASN1\Universal\Integer; use FG\ASN1\Universal\ObjectIdentifier; use FG\ASN1\Universal\OctetString; use FG\ASN1\Universal\Sequence; class Ec2Key extends Key { public const CURVE_P256 = 1; public const CURVE_P256K = 8; public const CURVE_P384 = 2; public const CURVE_P521 = 3; private const SUPPORTED_CURVES = [ self::CURVE_P256, self::CURVE_P256K, self::CURVE_P384, self::CURVE_P521, ]; public const DATA_CURVE = -1; public const DATA_X = -2; public const DATA_Y = -3; public const DATA_D = -4; private const NAMED_CURVE_OID = [ self::CURVE_P256 => '1.2.840.10045.3.1.7', // NIST P-256 / secp256r1 self::CURVE_P256K => '1.3.132.0.10', // NIST P-256K / secp256k1 self::CURVE_P384 => '1.3.132.0.34', // NIST P-384 / secp384r1 self::CURVE_P521 => '1.3.132.0.35', // NIST P-521 / secp521r1 ]; private const CURVE_KEY_LENGTH = [ self::CURVE_P256 => 32, self::CURVE_P256K => 32, self::CURVE_P384 => 48, self::CURVE_P521 => 66, ]; public function __construct(array $data) { parent::__construct($data); Assertion::eq($data[self::TYPE], self::TYPE_EC2, 'Invalid EC2 key. The key type does not correspond to an EC2 key'); Assertion::keyExists($data, self::DATA_CURVE, 'Invalid EC2 key. The curve is missing'); Assertion::keyExists($data, self::DATA_X, 'Invalid EC2 key. The x coordinate is missing'); Assertion::keyExists($data, self::DATA_Y, 'Invalid EC2 key. The y coordinate is missing'); Assertion::length($data[self::DATA_X], self::CURVE_KEY_LENGTH[$data[self::DATA_CURVE]], 'Invalid length for x coordinate', null, '8bit'); Assertion::length($data[self::DATA_Y], self::CURVE_KEY_LENGTH[$data[self::DATA_CURVE]], 'Invalid length for y coordinate', null, '8bit'); Assertion::inArray((int) $data[self::DATA_CURVE], self::SUPPORTED_CURVES, 'The curve is not supported'); } public function toPublic(): self { $data = $this->getData(); unset($data[self::DATA_D]); return new self($data); } public function x(): string { return $this->get(self::DATA_X); } public function y(): string { return $this->get(self::DATA_Y); } public function isPrivate(): bool { return \array_key_exists(self::DATA_D, $this->getData()); } public function d(): string { Assertion::true($this->isPrivate(), 'The key is not private'); return $this->get(self::DATA_D); } public function curve(): int { return (int) $this->get(self::DATA_CURVE); } public function asPEM(): string { if ($this->isPrivate()) { $der = new Sequence( new Integer(1), new OctetString(bin2hex($this->d())), new ExplicitlyTaggedObject(0, new ObjectIdentifier($this->getCurveOid())), new ExplicitlyTaggedObject(1, new BitString(\bin2hex($this->getUncompressedCoordinates()))) ); return $this->pem('EC PRIVATE KEY', $der->getBinary()); } $der = new Sequence( new Sequence( new ObjectIdentifier('1.2.840.10045.2.1'), new ObjectIdentifier($this->getCurveOid()) ), new BitString(\bin2hex($this->getUncompressedCoordinates())) ); return $this->pem('PUBLIC KEY', $der->getBinary()); } private function getCurveOid(): string { return self::NAMED_CURVE_OID[$this->curve()]; } public function getUncompressedCoordinates(): string { return "\x04".$this->x().$this->y(); } private function pem(string $type, string $der): string { return sprintf("-----BEGIN %s-----\n", mb_strtoupper($type)). chunk_split(base64_encode($der), 64, "\n"). sprintf("-----END %s-----\n", mb_strtoupper($type)); } } cose-lib/src/Algorithms.php 0000644 00000014272 15174023161 0011660 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose; use Assert\Assertion; use Cose\Algorithm\Algorithm; use Cose\Algorithm\Mac; use Cose\Algorithm\Signature\ECDSA; use Cose\Algorithm\Signature\EdDSA; use Cose\Algorithm\Signature\RSA; /** * @see https://www.iana.org/assignments/cose/cose.xhtml#algorithms */ abstract class Algorithms { public const COSE_ALGORITHM_AES_CCM_64_128_256 = 33; public const COSE_ALGORITHM_AES_CCM_64_128_128 = 32; public const COSE_ALGORITHM_AES_CCM_16_128_256 = 31; public const COSE_ALGORITHM_AES_CCM_16_128_128 = 30; public const COSE_ALGORITHM_AES_MAC_256_128 = 26; public const COSE_ALGORITHM_AES_MAC_128_128 = 25; public const COSE_ALGORITHM_CHACHA20_POLY1305 = 24; public const COSE_ALGORITHM_AES_MAC_256_64 = 15; public const COSE_ALGORITHM_AES_MAC_128_64 = 14; public const COSE_ALGORITHM_AES_CCM_64_64_256 = 13; public const COSE_ALGORITHM_AES_CCM_64_64_128 = 12; public const COSE_ALGORITHM_AES_CCM_16_64_256 = 11; public const COSE_ALGORITHM_AES_CCM_16_64_128 = 10; public const COSE_ALGORITHM_HS512 = 7; public const COSE_ALGORITHM_HS384 = 6; public const COSE_ALGORITHM_HS256 = 5; public const COSE_ALGORITHM_HS256_64 = 4; public const COSE_ALGORITHM_A256GCM = 3; public const COSE_ALGORITHM_A192GCM = 2; public const COSE_ALGORITHM_A128GCM = 1; public const COSE_ALGORITHM_A128KW = -3; public const COSE_ALGORITHM_A192KW = -4; public const COSE_ALGORITHM_A256KW = -5; public const COSE_ALGORITHM_DIRECT = -6; public const COSE_ALGORITHM_ES256 = -7; public const COSE_ALGORITHM_EdDSA = -8; public const COSE_ALGORITHM_ED256 = -260; public const COSE_ALGORITHM_ED512 = -261; public const COSE_ALGORITHM_DIRECT_HKDF_SHA_256 = -10; public const COSE_ALGORITHM_DIRECT_HKDF_SHA_512 = -11; public const COSE_ALGORITHM_DIRECT_HKDF_AES_128 = -12; public const COSE_ALGORITHM_DIRECT_HKDF_AES_256 = -13; public const COSE_ALGORITHM_ECDH_ES_HKDF_256 = -25; public const COSE_ALGORITHM_ECDH_ES_HKDF_512 = -26; public const COSE_ALGORITHM_ECDH_SS_HKDF_256 = -27; public const COSE_ALGORITHM_ECDH_SS_HKDF_512 = -28; public const COSE_ALGORITHM_ECDH_ES_A128KW = -29; public const COSE_ALGORITHM_ECDH_ES_A192KW = -30; public const COSE_ALGORITHM_ECDH_ES_A256KW = -31; public const COSE_ALGORITHM_ECDH_SS_A128KW = -32; public const COSE_ALGORITHM_ECDH_SS_A192KW = -33; public const COSE_ALGORITHM_ECDH_SS_A256KW = -34; public const COSE_ALGORITHM_ES384 = -35; public const COSE_ALGORITHM_ES512 = -36; public const COSE_ALGORITHM_PS256 = -37; public const COSE_ALGORITHM_PS384 = -38; public const COSE_ALGORITHM_PS512 = -39; public const COSE_ALGORITHM_RSAES_OAEP = -40; public const COSE_ALGORITHM_RSAES_OAEP_256 = -41; public const COSE_ALGORITHM_RSAES_OAEP_512 = -42; public const COSE_ALGORITHM_ES256K = -43; public const COSE_ALGORITHM_RS256 = -257; public const COSE_ALGORITHM_RS384 = -258; public const COSE_ALGORITHM_RS512 = -259; public const COSE_ALGORITHM_RS1 = -65535; public const COSE_ALGORITHM_MAP = [ self::COSE_ALGORITHM_ES256 => OPENSSL_ALGO_SHA256, self::COSE_ALGORITHM_ES384 => OPENSSL_ALGO_SHA384, self::COSE_ALGORITHM_ES512 => OPENSSL_ALGO_SHA512, self::COSE_ALGORITHM_RS256 => OPENSSL_ALGO_SHA256, self::COSE_ALGORITHM_RS384 => OPENSSL_ALGO_SHA384, self::COSE_ALGORITHM_RS512 => OPENSSL_ALGO_SHA512, self::COSE_ALGORITHM_RS1 => OPENSSL_ALGO_SHA1, ]; public const COSE_HASH_MAP = [ self::COSE_ALGORITHM_ES256K => 'sha256', self::COSE_ALGORITHM_ES256 => 'sha256', self::COSE_ALGORITHM_ES384 => 'sha384', self::COSE_ALGORITHM_ES512 => 'sha512', self::COSE_ALGORITHM_RS256 => 'sha256', self::COSE_ALGORITHM_RS384 => 'sha384', self::COSE_ALGORITHM_RS512 => 'sha512', self::COSE_ALGORITHM_PS256 => 'sha256', self::COSE_ALGORITHM_PS384 => 'sha384', self::COSE_ALGORITHM_PS512 => 'sha512', self::COSE_ALGORITHM_RS1 => 'sha1', ]; public static function getOpensslAlgorithmFor(int $algorithmIdentifier): int { Assertion::keyExists(self::COSE_ALGORITHM_MAP, $algorithmIdentifier, 'The specified algorithm identifier is not supported'); return self::COSE_ALGORITHM_MAP[$algorithmIdentifier]; } public static function getHashAlgorithmFor(int $algorithmIdentifier): string { Assertion::keyExists(self::COSE_HASH_MAP, $algorithmIdentifier, 'The specified algorithm identifier is not supported'); return self::COSE_HASH_MAP[$algorithmIdentifier]; } /** * @deprecated Will be removed in v3.0. Please use the Manager or the ManagerFactory */ public static function getAlgorithm(int $identifier): Algorithm { $algs = static::getAlgorithms(); Assertion::keyExists($algs, $identifier, 'The specified algorithm identifier is not supported'); return $algs[$identifier]; } /** * @deprecated Will be removed in v3.0. Please use the Manager or the ManagerFactory * * @return Algorithm[] */ public static function getAlgorithms(): array { return [ Mac\HS256::identifier() => new Mac\HS256(), Mac\HS384::identifier() => new Mac\HS384(), Mac\HS512::identifier() => new Mac\HS512(), RSA\RS256::identifier() => new RSA\RS256(), RSA\RS384::identifier() => new RSA\RS384(), RSA\RS512::identifier() => new RSA\RS512(), RSA\PS256::identifier() => new RSA\PS256(), RSA\PS384::identifier() => new RSA\PS384(), RSA\PS512::identifier() => new RSA\PS512(), ECDSA\ES256K::identifier() => new ECDSA\ES256K(), ECDSA\ES256::identifier() => new ECDSA\ES256(), ECDSA\ES384::identifier() => new ECDSA\ES384(), ECDSA\ES512::identifier() => new ECDSA\ES512(), EdDSA\ED512::identifier() => new EdDSA\ED512(), ]; } } cose-lib/src/Algorithm/ManagerFactory.php 0000644 00000002046 15174023161 0014373 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm; use Assert\Assertion; class ManagerFactory { /** * @var Algorithm[] */ private $algorithms = []; public function add(string $alias, Algorithm $algorithm): void { $this->algorithms[$alias] = $algorithm; } public function list(): iterable { yield from array_keys($this->algorithms); } public function all(): iterable { yield from array_keys($this->algorithms); } public function create(array $aliases): Manager { $manager = new Manager(); foreach ($aliases as $alias) { Assertion::keyExists($this->algorithms, $alias, sprintf('The algorithm with alias "%s" is not supported', $alias)); $manager->add($this->algorithms[$alias]); } return $manager; } } cose-lib/src/Algorithm/Signature/ECDSA/ECSignature.php 0000644 00000010552 15174023161 0016423 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature\ECDSA; use InvalidArgumentException; use const STR_PAD_LEFT; /** * @internal */ final class ECSignature { private const ASN1_SEQUENCE = '30'; private const ASN1_INTEGER = '02'; private const ASN1_MAX_SINGLE_BYTE = 128; private const ASN1_LENGTH_2BYTES = '81'; private const ASN1_BIG_INTEGER_LIMIT = '7f'; private const ASN1_NEGATIVE_INTEGER = '00'; private const BYTE_SIZE = 2; public static function toAsn1(string $signature, int $length): string { $signature = bin2hex($signature); if (self::octetLength($signature) !== $length) { throw new InvalidArgumentException('Invalid signature length.'); } $pointR = self::preparePositiveInteger(mb_substr($signature, 0, $length, '8bit')); $pointS = self::preparePositiveInteger(mb_substr($signature, $length, null, '8bit')); $lengthR = self::octetLength($pointR); $lengthS = self::octetLength($pointS); $totalLength = $lengthR + $lengthS + self::BYTE_SIZE + self::BYTE_SIZE; $lengthPrefix = $totalLength > self::ASN1_MAX_SINGLE_BYTE ? self::ASN1_LENGTH_2BYTES : ''; return self::hex2bin( self::ASN1_SEQUENCE .$lengthPrefix.dechex($totalLength) .self::ASN1_INTEGER.dechex($lengthR).$pointR .self::ASN1_INTEGER.dechex($lengthS).$pointS ); } public static function fromAsn1(string $signature, int $length): string { $message = bin2hex($signature); $position = 0; if (self::ASN1_SEQUENCE !== self::readAsn1Content($message, $position, self::BYTE_SIZE)) { throw new InvalidArgumentException('Invalid data. Should start with a sequence.'); } if (self::ASN1_LENGTH_2BYTES === self::readAsn1Content($message, $position, self::BYTE_SIZE)) { $position += self::BYTE_SIZE; } $pointR = self::retrievePositiveInteger(self::readAsn1Integer($message, $position)); $pointS = self::retrievePositiveInteger(self::readAsn1Integer($message, $position)); return self::hex2bin(str_pad($pointR, $length, '0', STR_PAD_LEFT).str_pad($pointS, $length, '0', STR_PAD_LEFT)); } private static function octetLength(string $data): int { return (int) (mb_strlen($data, '8bit') / self::BYTE_SIZE); } private static function preparePositiveInteger(string $data): string { if (mb_substr($data, 0, self::BYTE_SIZE, '8bit') > self::ASN1_BIG_INTEGER_LIMIT) { return self::ASN1_NEGATIVE_INTEGER.$data; } while (self::ASN1_NEGATIVE_INTEGER === mb_substr($data, 0, self::BYTE_SIZE, '8bit') && mb_substr($data, 2, self::BYTE_SIZE, '8bit') <= self::ASN1_BIG_INTEGER_LIMIT) { $data = mb_substr($data, 2, null, '8bit'); } return $data; } private static function readAsn1Content(string $message, int &$position, int $length): string { $content = mb_substr($message, $position, $length, '8bit'); $position += $length; return $content; } private static function readAsn1Integer(string $message, int &$position): string { if (self::ASN1_INTEGER !== self::readAsn1Content($message, $position, self::BYTE_SIZE)) { throw new InvalidArgumentException('Invalid data. Should contain an integer.'); } $length = (int) hexdec(self::readAsn1Content($message, $position, self::BYTE_SIZE)); return self::readAsn1Content($message, $position, $length * self::BYTE_SIZE); } private static function retrievePositiveInteger(string $data): string { while (self::ASN1_NEGATIVE_INTEGER === mb_substr($data, 0, self::BYTE_SIZE, '8bit') && mb_substr($data, 2, self::BYTE_SIZE, '8bit') > self::ASN1_BIG_INTEGER_LIMIT) { $data = mb_substr($data, 2, null, '8bit'); } return $data; } private static function hex2bin(string $data): string { $result = \hex2bin($data); if (false === $result) { throw new InvalidArgumentException('Unable to convert the data'); } return $result; } } cose-lib/src/Algorithm/Signature/ECDSA/ECDSA.php 0000644 00000003057 15174023161 0015073 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature\ECDSA; use Assert\Assertion; use Cose\Algorithm\Signature\Signature; use Cose\Key\Ec2Key; use Cose\Key\Key; abstract class ECDSA implements Signature { public function __construct() { if (!method_exists($this, 'getSignaturePartLength')) { @trigger_error('The method "getSignaturePartLength" is needed since 2.1 and will be mandatory in v3.0', E_USER_DEPRECATED); } } public function sign(string $data, Key $key): string { $key = $this->handleKey($key); $result = openssl_sign($data, $signature, $key->asPEM(), $this->getHashAlgorithm()); Assertion::true($result, 'Unable to sign the data'); return $signature; } public function verify(string $data, Key $key, string $signature): bool { $key = $this->handleKey($key); $publicKey = $key->toPublic(); return 1 === openssl_verify($data, $signature, $publicKey->asPEM(), $this->getHashAlgorithm()); } private function handleKey(Key $key): Ec2Key { $key = new Ec2Key($key->getData()); Assertion::eq($key->curve(), $this->getCurve(), 'This key cannot be used with this algorithm'); return $key; } abstract protected function getCurve(): int; abstract protected function getHashAlgorithm(): int; } cose-lib/src/Algorithm/Signature/ECDSA/ES512.php 0000644 00000002661 15174023161 0015013 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature\ECDSA; use Cose\Key\Ec2Key; use Cose\Key\Key; final class ES512 extends ECDSA { public const ID = -36; public static function identifier(): int { return self::ID; } public function sign(string $data, Key $key): string { $signature = parent::sign($data, $key); return ECSignature::fromAsn1($signature, $this->getSignaturePartLength()); } public function verify(string $data, Key $key, string $signature): bool { if (mb_strlen($signature, '8bit') !== $this->getSignaturePartLength()) { @trigger_error('Since v2.1, the method "verify" accepts ASN.1 structures and raw ECDSA signature. In v3.0 and ASN.1 structures will be rejected', E_USER_DEPRECATED); } else { $signature = ECSignature::toAsn1($signature, $this->getSignaturePartLength()); } return parent::verify($data, $key, $signature); } protected function getHashAlgorithm(): int { return OPENSSL_ALGO_SHA512; } protected function getCurve(): int { return Ec2Key::CURVE_P521; } protected function getSignaturePartLength(): int { return 132; } } cose-lib/src/Algorithm/Signature/ECDSA/ES256.php 0000644 00000002642 15174023161 0015017 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature\ECDSA; use Cose\Key\Ec2Key; use Cose\Key\Key; final class ES256 extends ECDSA { public const ID = -7; public static function identifier(): int { return self::ID; } public function sign(string $data, Key $key): string { $signature = parent::sign($data, $key); return ECSignature::fromAsn1($signature, $this->getSignaturePartLength()); } public function verify(string $data, Key $key, string $signature): bool { if (mb_strlen($signature, '8bit') !== $this->getSignaturePartLength()) { @trigger_error('Since v2.1, the method "verify" will only accept raw ECDSA signature in v3.0 and ASN.1 structures will be rejected', E_USER_DEPRECATED); } else { $signature = ECSignature::toAsn1($signature, $this->getSignaturePartLength()); } return parent::verify($data, $key, $signature); } protected function getHashAlgorithm(): int { return OPENSSL_ALGO_SHA256; } protected function getCurve(): int { return Ec2Key::CURVE_P256; } protected function getSignaturePartLength(): int { return 64; } } cose-lib/src/Algorithm/Signature/ECDSA/ES384.php 0000644 00000002643 15174023161 0015022 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature\ECDSA; use Cose\Key\Ec2Key; use Cose\Key\Key; final class ES384 extends ECDSA { public const ID = -35; public static function identifier(): int { return self::ID; } public function sign(string $data, Key $key): string { $signature = parent::sign($data, $key); return ECSignature::fromAsn1($signature, $this->getSignaturePartLength()); } public function verify(string $data, Key $key, string $signature): bool { if (mb_strlen($signature, '8bit') !== $this->getSignaturePartLength()) { @trigger_error('Since v2.1, the method "verify" will only accept raw ECDSA signature in v3.0 and ASN.1 structures will be rejected', E_USER_DEPRECATED); } else { $signature = ECSignature::toAsn1($signature, $this->getSignaturePartLength()); } return parent::verify($data, $key, $signature); } protected function getHashAlgorithm(): int { return OPENSSL_ALGO_SHA384; } protected function getCurve(): int { return Ec2Key::CURVE_P384; } protected function getSignaturePartLength(): int { return 96; } } cose-lib/src/Algorithm/Signature/ECDSA/ES256K.php 0000644 00000002645 15174023161 0015135 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature\ECDSA; use Cose\Key\Ec2Key; use Cose\Key\Key; final class ES256K extends ECDSA { public const ID = -43; public static function identifier(): int { return self::ID; } public function sign(string $data, Key $key): string { $signature = parent::sign($data, $key); return ECSignature::fromAsn1($signature, $this->getSignaturePartLength()); } public function verify(string $data, Key $key, string $signature): bool { if (mb_strlen($signature, '8bit') !== $this->getSignaturePartLength()) { @trigger_error('Since v2.1, the method "verify" will only accept raw ECDSA signature in v3.0 and ASN.1 structures will be rejected', E_USER_DEPRECATED); } else { $signature = ECSignature::toAsn1($signature, $this->getSignaturePartLength()); } return parent::verify($data, $key, $signature); } protected function getHashAlgorithm(): int { return OPENSSL_ALGO_SHA256; } protected function getCurve(): int { return Ec2Key::CURVE_P256K; } protected function getSignaturePartLength(): int { return 64; } } cose-lib/src/Algorithm/Signature/RSA/PSSRSA.php 0000644 00000013601 15174023161 0015071 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature\RSA; use function ceil; use Cose\Algorithm\Signature\Signature; use Cose\Key\Key; use Cose\Key\RsaKey; use function hash_equals; use InvalidArgumentException; use Jose\Component\Core\Util\BigInteger; use Jose\Component\Core\Util\Hash; use function mb_strlen; use function mb_substr; use function pack; use function random_bytes; use RuntimeException; use function str_pad; use function str_repeat; /** * @internal */ abstract class PSSRSA implements Signature { public function sign(string $data, Key $key): string { $key = $this->handleKey($key); $modulusLength = mb_strlen($key->n(), '8bit'); $em = $this->encodeEMSAPSS($data, 8 * $modulusLength - 1, $this->getHashAlgorithm()); $message = BigInteger::createFromBinaryString($em); $signature = $this->exponentiate($key, $message); return $this->convertIntegerToOctetString($signature, $modulusLength); } public function verify(string $data, Key $key, string $signature): bool { $key = $this->handleKey($key); $modulusLength = mb_strlen($key->n(), '8bit'); if (mb_strlen($signature, '8bit') !== $modulusLength) { throw new InvalidArgumentException('Invalid modulus length'); } $s2 = BigInteger::createFromBinaryString($signature); $m2 = $this->exponentiate($key, $s2); $em = $this->convertIntegerToOctetString($m2, $modulusLength); $modBits = 8 * $modulusLength; return $this->verifyEMSAPSS($data, $em, $modBits - 1, $this->getHashAlgorithm()); } private function handleKey(Key $key): RsaKey { return new RsaKey($key->getData()); } abstract protected function getHashAlgorithm(): Hash; private function convertIntegerToOctetString(BigInteger $x, int $xLen): string { $x = $x->toBytes(); if (mb_strlen($x, '8bit') > $xLen) { throw new RuntimeException('Unable to convert the integer'); } return str_pad($x, $xLen, \chr(0), STR_PAD_LEFT); } /** * MGF1. */ private function getMGF1(string $mgfSeed, int $maskLen, Hash $mgfHash): string { $t = ''; $count = ceil($maskLen / $mgfHash->getLength()); for ($i = 0; $i < $count; ++$i) { $c = pack('N', $i); $t .= $mgfHash->hash($mgfSeed.$c); } return mb_substr($t, 0, $maskLen, '8bit'); } /** * EMSA-PSS-ENCODE. */ private function encodeEMSAPSS(string $message, int $modulusLength, Hash $hash): string { $emLen = ($modulusLength + 1) >> 3; $sLen = $hash->getLength(); $mHash = $hash->hash($message); if ($emLen <= $hash->getLength() + $sLen + 2) { throw new RuntimeException(); } $salt = random_bytes($sLen); $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt; $h = $hash->hash($m2); $ps = str_repeat(\chr(0), $emLen - $sLen - $hash->getLength() - 2); $db = $ps.\chr(1).$salt; $dbMask = $this->getMGF1($h, $emLen - $hash->getLength() - 1, $hash); $maskedDB = $db ^ $dbMask; $maskedDB[0] = ~\chr(0xFF << ($modulusLength & 7)) & $maskedDB[0]; return $maskedDB.$h.\chr(0xBC); } /** * EMSA-PSS-VERIFY. */ private function verifyEMSAPSS(string $m, string $em, int $emBits, Hash $hash): bool { $emLen = ($emBits + 1) >> 3; $sLen = $hash->getLength(); $mHash = $hash->hash($m); if ($emLen < $hash->getLength() + $sLen + 2) { throw new InvalidArgumentException(); } if ($em[mb_strlen($em, '8bit') - 1] !== \chr(0xBC)) { throw new InvalidArgumentException(); } $maskedDB = mb_substr($em, 0, -$hash->getLength() - 1, '8bit'); $h = mb_substr($em, -$hash->getLength() - 1, $hash->getLength(), '8bit'); $temp = \chr(0xFF << ($emBits & 7)); if ((~$maskedDB[0] & $temp) !== $temp) { throw new InvalidArgumentException(); } $dbMask = $this->getMGF1($h, $emLen - $hash->getLength() - 1, $hash/*MGF*/); $db = $maskedDB ^ $dbMask; $db[0] = ~\chr(0xFF << ($emBits & 7)) & $db[0]; $temp = $emLen - $hash->getLength() - $sLen - 2; if (mb_substr($db, 0, $temp, '8bit') !== str_repeat(\chr(0), $temp)) { throw new InvalidArgumentException(); } if (1 !== \ord($db[$temp])) { throw new InvalidArgumentException(); } $salt = mb_substr($db, $temp + 1, null, '8bit'); // should be $sLen long $m2 = "\0\0\0\0\0\0\0\0".$mHash.$salt; $h2 = $hash->hash($m2); return hash_equals($h, $h2); } /** * Exponentiate with or without Chinese Remainder Theorem. * Operation with primes 'p' and 'q' is appox. 2x faster. */ public function exponentiate(RsaKey $key, BigInteger $c): BigInteger { if ($c->compare(BigInteger::createFromDecimal(0)) < 0 || $c->compare(BigInteger::createFromBinaryString($key->n())) > 0) { throw new RuntimeException(); } if ($key->isPublic() || !$key->hasPrimes() || !$key->hasExponents() || !$key->hasCoefficient()) { return $c->modPow(BigInteger::createFromBinaryString($key->e()), BigInteger::createFromBinaryString($key->n())); } $p = $key->primes()[0]; $q = $key->primes()[1]; $dP = $key->exponents()[0]; $dQ = $key->exponents()[1]; $qInv = BigInteger::createFromBinaryString($key->QInv()); $m1 = $c->modPow($dP, $p); $m2 = $c->modPow($dQ, $q); $h = $qInv->multiply($m1->subtract($m2)->add($p))->mod($p); return $m2->add($h->multiply($q)); } } cose-lib/src/Algorithm/Signature/RSA/PS512.php 0000644 00000001046 15174023161 0014630 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature\RSA; use Jose\Component\Core\Util\Hash; final class PS512 extends PSSRSA { public const ID = -39; public static function identifier(): int { return self::ID; } protected function getHashAlgorithm(): Hash { return Hash::sha512(); } } cose-lib/src/Algorithm/Signature/RSA/RS384.php 0000644 00000001004 15174023161 0014633 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature\RSA; final class RS384 extends RSA { public const ID = -258; public static function identifier(): int { return self::ID; } protected function getHashAlgorithm(): int { return OPENSSL_ALGO_SHA384; } } cose-lib/src/Algorithm/Signature/RSA/PS256.php 0000644 00000001046 15174023161 0014635 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature\RSA; use Jose\Component\Core\Util\Hash; final class PS256 extends PSSRSA { public const ID = -37; public static function identifier(): int { return self::ID; } protected function getHashAlgorithm(): Hash { return Hash::sha256(); } } cose-lib/src/Algorithm/Signature/RSA/RS1.php 0000644 00000001002 15174023161 0014453 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature\RSA; final class RS1 extends RSA { public const ID = -65535; public static function identifier(): int { return self::ID; } protected function getHashAlgorithm(): int { return OPENSSL_ALGO_SHA1; } } cose-lib/src/Algorithm/Signature/RSA/RS512.php 0000644 00000001004 15174023161 0014624 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature\RSA; final class RS512 extends RSA { public const ID = -259; public static function identifier(): int { return self::ID; } protected function getHashAlgorithm(): int { return OPENSSL_ALGO_SHA512; } } cose-lib/src/Algorithm/Signature/RSA/RS256.php 0000644 00000001004 15174023161 0014631 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature\RSA; final class RS256 extends RSA { public const ID = -257; public static function identifier(): int { return self::ID; } protected function getHashAlgorithm(): int { return OPENSSL_ALGO_SHA256; } } cose-lib/src/Algorithm/Signature/RSA/PS384.php 0000644 00000001046 15174023161 0014637 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature\RSA; use Jose\Component\Core\Util\Hash; final class PS384 extends PSSRSA { public const ID = -38; public static function identifier(): int { return self::ID; } protected function getHashAlgorithm(): Hash { return Hash::sha384(); } } cose-lib/src/Algorithm/Signature/RSA/RSA.php 0000644 00000002225 15174023161 0014503 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature\RSA; use Assert\Assertion; use Cose\Algorithm\Signature\Signature; use Cose\Key\Key; use Cose\Key\RsaKey; abstract class RSA implements Signature { public function sign(string $data, Key $key): string { $key = $this->handleKey($key); Assertion::true($key->isPrivate(), 'The key is not private'); $result = openssl_sign($data, $signature, $key->asPem(), $this->getHashAlgorithm()); Assertion::true($result, 'Unable to sign the data'); return $signature; } public function verify(string $data, Key $key, string $signature): bool { $key = $this->handleKey($key); return 1 === openssl_verify($data, $signature, $key->asPem(), $this->getHashAlgorithm()); } private function handleKey(Key $key): RsaKey { return new RsaKey($key->getData()); } abstract protected function getHashAlgorithm(): int; } cose-lib/src/Algorithm/Signature/Signature.php 0000644 00000000762 15174023161 0015376 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature; use Cose\Algorithm\Algorithm; use Cose\Key\Key; interface Signature extends Algorithm { public function sign(string $data, Key $key): string; public function verify(string $data, Key $key, string $signature): bool; } cose-lib/src/Algorithm/Signature/EdDSA/Ed25519.php 0000644 00000000650 15174023161 0015247 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature\EdDSA; final class Ed25519 extends EdDSA { public const ID = -8; public static function identifier(): int { return self::ID; } } cose-lib/src/Algorithm/Signature/EdDSA/ED256.php 0000644 00000001460 15174023161 0015036 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature\EdDSA; use Cose\Key\Key; final class ED256 extends EdDSA { public const ID = -260; public static function identifier(): int { return self::ID; } public function sign(string $data, Key $key): string { $hashedData = hash('sha256', $data, true); return parent::sign($hashedData, $key); } public function verify(string $data, Key $key, string $signature): bool { $hashedData = hash('sha256', $data, true); return parent::verify($hashedData, $key, $signature); } } cose-lib/src/Algorithm/Signature/EdDSA/ED512.php 0000644 00000001460 15174023161 0015031 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature\EdDSA; use Cose\Key\Key; final class ED512 extends EdDSA { public const ID = -261; public static function identifier(): int { return self::ID; } public function sign(string $data, Key $key): string { $hashedData = hash('sha512', $data, true); return parent::sign($hashedData, $key); } public function verify(string $data, Key $key, string $signature): bool { $hashedData = hash('sha512', $data, true); return parent::verify($hashedData, $key, $signature); } } cose-lib/src/Algorithm/Signature/EdDSA/EdDSA.php 0000644 00000003200 15174023161 0015163 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Signature\EdDSA; use Assert\Assertion; use Cose\Algorithm\Signature\Signature; use Cose\Algorithms; use Cose\Key\Key; use Cose\Key\OkpKey; use InvalidArgumentException; use function sodium_crypto_sign_detached; use function sodium_crypto_sign_verify_detached; class EdDSA implements Signature { public function sign(string $data, Key $key): string { $key = $this->handleKey($key); Assertion::true($key->isPrivate(), 'The key is not private'); $x = $key->x(); $d = $key->d(); $secret = $d.$x; switch ($key->curve()) { case OkpKey::CURVE_ED25519: return sodium_crypto_sign_detached($data, $secret); default: throw new InvalidArgumentException('Unsupported curve'); } } public function verify(string $data, Key $key, string $signature): bool { $key = $this->handleKey($key); switch ($key->curve()) { case OkpKey::CURVE_ED25519: return sodium_crypto_sign_verify_detached($signature, $data, $key->x()); default: throw new InvalidArgumentException('Unsupported curve'); } } public static function identifier(): int { return Algorithms::COSE_ALGORITHM_EdDSA; } private function handleKey(Key $key): OkpKey { return new OkpKey($key->getData()); } } cose-lib/src/Algorithm/Manager.php 0000644 00000002360 15174023161 0013042 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm; use Assert\Assertion; class Manager { /** * @var Algorithm[] */ private $algorithms = []; public function add(Algorithm $algorithm): void { $identifier = $algorithm::identifier(); $this->algorithms[$identifier] = $algorithm; } /** * @deprecated Will be removed in v3.0. Please use all() instead */ public function getAlgorithms(): iterable { yield from $this->algorithms; } public function list(): iterable { yield from array_keys($this->algorithms); } /** * @return Algorithm[] */ public function all(): iterable { yield from $this->algorithms; } public function has(int $identifier): bool { return \array_key_exists($identifier, $this->algorithms); } public function get(int $identifier): Algorithm { Assertion::true($this->has($identifier), 'Unsupported algorithm'); return $this->algorithms[$identifier]; } } cose-lib/src/Algorithm/Algorithm.php 0000644 00000000513 15174023161 0013414 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm; interface Algorithm { public static function identifier(): int; } cose-lib/src/Algorithm/Mac/HS384.php 0000644 00000001102 15174023161 0012732 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Mac; final class HS384 extends Hmac { public const ID = 6; public static function identifier(): int { return self::ID; } protected function getHashAlgorithm(): string { return 'sha384'; } protected function getSignatureLength(): int { return 384; } } cose-lib/src/Algorithm/Mac/Hmac.php 0000644 00000002136 15174023161 0013041 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Mac; use Assert\Assertion; use Cose\Key\Key; abstract class Hmac implements Mac { public function hash(string $data, Key $key): string { $this->checKey($key); $signature = hash_hmac($this->getHashAlgorithm(), $data, $key->get(-1), true); return mb_substr($signature, 0, $this->getSignatureLength() / 8, '8bit'); } public function verify(string $data, Key $key, string $signature): bool { return hash_equals($this->hash($data, $key), $signature); } private function checKey(Key $key): void { Assertion::eq($key->type(), 4, 'Invalid key. Must be of type symmetric'); Assertion::true($key->has(-1), 'Invalid key. The value of the key is missing'); } abstract protected function getHashAlgorithm(): string; abstract protected function getSignatureLength(): int; } cose-lib/src/Algorithm/Mac/HS512.php 0000644 00000001102 15174023161 0012723 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Mac; final class HS512 extends Hmac { public const ID = 7; public static function identifier(): int { return self::ID; } protected function getHashAlgorithm(): string { return 'sha512'; } protected function getSignatureLength(): int { return 512; } } cose-lib/src/Algorithm/Mac/Mac.php 0000644 00000000746 15174023161 0012676 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Mac; use Cose\Algorithm\Algorithm; use Cose\Key\Key; interface Mac extends Algorithm { public function hash(string $data, Key $key): string; public function verify(string $data, Key $key, string $signature): bool; } cose-lib/src/Algorithm/Mac/HS256.php 0000644 00000001102 15174023161 0012730 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Mac; final class HS256 extends Hmac { public const ID = 5; public static function identifier(): int { return self::ID; } protected function getHashAlgorithm(): string { return 'sha256'; } protected function getSignatureLength(): int { return 256; } } cose-lib/src/Algorithm/Mac/HS256Truncated64.php 0000644 00000001114 15174023161 0014757 0 ustar 00 <?php declare(strict_types=1); /* * The MIT License (MIT) * * Copyright (c) 2014-2019 Spomky-Labs * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ namespace Cose\Algorithm\Mac; final class HS256Truncated64 extends Hmac { public const ID = 4; public static function identifier(): int { return self::ID; } protected function getHashAlgorithm(): string { return 'sha256'; } protected function getSignatureLength(): int { return 64; } } cose-lib/LICENSE 0000644 00000002054 15174023161 0007247 0 ustar 00 MIT License Copyright (c) 2018 Spomky-Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
| ver. 1.4 |
Github
|
.
| PHP 8.3.23 | Generation time: 0 |
proxy
|
phpinfo
|
Settings