[Web] FIDO2: Add Face ID via Apple
This commit is contained in:
		| @@ -1,153 +0,0 @@ | ||||
| <?php | ||||
|  | ||||
| namespace WebAuthn\Attestation; | ||||
| use WebAuthn\WebAuthnException; | ||||
| use WebAuthn\CBOR\CborDecoder; | ||||
| use WebAuthn\Binary\ByteBuffer; | ||||
|  | ||||
| /** | ||||
|  * @author Lukas Buchs | ||||
|  * @license https://github.com/lbuchs/WebAuthn/blob/master/LICENSE MIT | ||||
|  */ | ||||
| class AttestationObject { | ||||
|     private $_authenticatorData; | ||||
|     private $_attestationFormat; | ||||
|  | ||||
|     public function __construct($binary , $allowedFormats) { | ||||
|         $enc = CborDecoder::decode($binary); | ||||
|         // validation | ||||
|         if (!\is_array($enc) || !\array_key_exists('fmt', $enc) || !is_string($enc['fmt'])) { | ||||
|             throw new WebAuthnException('invalid attestation format', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         if (!\array_key_exists('attStmt', $enc) || !\is_array($enc['attStmt'])) { | ||||
|             throw new WebAuthnException('invalid attestation format (attStmt not available)', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         if (!\array_key_exists('authData', $enc) || !\is_object($enc['authData']) || !($enc['authData'] instanceof ByteBuffer)) { | ||||
|             throw new WebAuthnException('invalid attestation format (authData not available)', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         $this->_authenticatorData = new AuthenticatorData($enc['authData']->getBinaryString()); | ||||
|  | ||||
|         // Format ok? | ||||
|         if (!in_array($enc['fmt'], $allowedFormats)) { | ||||
|             throw new WebAuthnException('invalid atttestation format: ' . $enc['fmt'], WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         switch ($enc['fmt']) { | ||||
|             case 'android-key': $this->_attestationFormat = new Format\AndroidKey($enc, $this->_authenticatorData); break; | ||||
|             case 'android-safetynet': $this->_attestationFormat = new Format\AndroidSafetyNet($enc, $this->_authenticatorData); break; | ||||
|             case 'fido-u2f': $this->_attestationFormat = new Format\U2f($enc, $this->_authenticatorData); break; | ||||
|             case 'none': $this->_attestationFormat = new Format\None($enc, $this->_authenticatorData); break; | ||||
|             case 'packed': $this->_attestationFormat = new Format\Packed($enc, $this->_authenticatorData); break; | ||||
|             case 'tpm': $this->_attestationFormat = new Format\Tpm($enc, $this->_authenticatorData); break; | ||||
|             default: throw new WebAuthnException('invalid attestation format: ' . $enc['fmt'], WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * returns the attestation public key in PEM format | ||||
|      * @return AuthenticatorData | ||||
|      */ | ||||
|     public function getAuthenticatorData() { | ||||
|         return $this->_authenticatorData; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * returns the certificate chain as PEM | ||||
|      * @return string|null | ||||
|      */ | ||||
|     public function getCertificateChain() { | ||||
|         return $this->_attestationFormat->getCertificateChain(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * return the certificate issuer as string | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getCertificateIssuer() { | ||||
|         $pem = $this->getCertificatePem(); | ||||
|         $issuer = ''; | ||||
|         if ($pem) { | ||||
|             $certInfo = \openssl_x509_parse($pem); | ||||
|             if (\is_array($certInfo) && \is_array($certInfo['issuer'])) { | ||||
|                 if ($certInfo['issuer']['CN']) { | ||||
|                     $issuer .= \trim($certInfo['issuer']['CN']); | ||||
|                 } | ||||
|                 if ($certInfo['issuer']['O'] || $certInfo['issuer']['OU']) { | ||||
|                     if ($issuer) { | ||||
|                         $issuer .= ' (' . \trim($certInfo['issuer']['O'] . ' ' . $certInfo['issuer']['OU']) . ')'; | ||||
|                     } else { | ||||
|                         $issuer .= \trim($certInfo['issuer']['O'] . ' ' . $certInfo['issuer']['OU']); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return $issuer; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * return the certificate subject as string | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getCertificateSubject() { | ||||
|         $pem = $this->getCertificatePem(); | ||||
|         $subject = ''; | ||||
|         if ($pem) { | ||||
|             $certInfo = \openssl_x509_parse($pem); | ||||
|             if (\is_array($certInfo) && \is_array($certInfo['subject'])) { | ||||
|                 if ($certInfo['subject']['CN']) { | ||||
|                     $subject .= \trim($certInfo['subject']['CN']); | ||||
|                 } | ||||
|                 if ($certInfo['subject']['O'] || $certInfo['subject']['OU']) { | ||||
|                     if ($subject) { | ||||
|                         $subject .= ' (' . \trim($certInfo['subject']['O'] . ' ' . $certInfo['subject']['OU']) . ')'; | ||||
|                     } else { | ||||
|                         $subject .= \trim($certInfo['subject']['O'] . ' ' . $certInfo['subject']['OU']); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return $subject; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * returns the key certificate in PEM format | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getCertificatePem() { | ||||
|         return $this->_attestationFormat->getCertificatePem(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * checks validity of the signature | ||||
|      * @param string $clientDataHash | ||||
|      * @return bool | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     public function validateAttestation($clientDataHash) { | ||||
|         return $this->_attestationFormat->validateAttestation($clientDataHash); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * validates the certificate against root certificates | ||||
|      * @param array $rootCas | ||||
|      * @return boolean | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     public function validateRootCertificate($rootCas) { | ||||
|         return $this->_attestationFormat->validateRootCertificate($rootCas); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * checks if the RpId-Hash is valid | ||||
|      * @param string$rpIdHash | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function validateRpIdHash($rpIdHash) { | ||||
|         return $rpIdHash === $this->_authenticatorData->getRpIdHash(); | ||||
|     } | ||||
| } | ||||
| @@ -1,423 +0,0 @@ | ||||
| <?php | ||||
|  | ||||
| namespace WebAuthn\Attestation; | ||||
| use WebAuthn\WebAuthnException; | ||||
| use WebAuthn\CBOR\CborDecoder; | ||||
| use WebAuthn\Binary\ByteBuffer; | ||||
|  | ||||
| /** | ||||
|  * @author Lukas Buchs | ||||
|  * @license https://github.com/lbuchs/WebAuthn/blob/master/LICENSE MIT | ||||
|  */ | ||||
| class AuthenticatorData { | ||||
|     protected $_binary; | ||||
|     protected $_rpIdHash; | ||||
|     protected $_flags; | ||||
|     protected $_signCount; | ||||
|     protected $_attestedCredentialData; | ||||
|     protected $_extensionData; | ||||
|  | ||||
|  | ||||
|  | ||||
|     // Cose encoded keys | ||||
|     private static $_COSE_KTY = 1; | ||||
|     private static $_COSE_ALG = 3; | ||||
|  | ||||
|     // Cose EC2 ES256 P-256 curve | ||||
|     private static $_COSE_CRV = -1; | ||||
|     private static $_COSE_X = -2; | ||||
|     private static $_COSE_Y = -3; | ||||
|  | ||||
|     // Cose RSA PS256 | ||||
|     private static $_COSE_N = -1; | ||||
|     private static $_COSE_E = -2; | ||||
|  | ||||
|     private static $_EC2_TYPE = 2; | ||||
|     private static $_EC2_ES256 = -7; | ||||
|     private static $_EC2_P256 = 1; | ||||
|  | ||||
|     private static $_RSA_TYPE = 3; | ||||
|     private static $_RSA_RS256 = -257; | ||||
|  | ||||
|     /** | ||||
|      * Parsing the authenticatorData binary. | ||||
|      * @param string $binary | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     public function __construct($binary) { | ||||
|         if (!\is_string($binary) || \strlen($binary) < 37) { | ||||
|             throw new WebAuthnException('Invalid authenticatorData input', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|         $this->_binary = $binary; | ||||
|  | ||||
|         // Read infos from binary | ||||
|         // https://www.w3.org/TR/webauthn/#sec-authenticator-data | ||||
|  | ||||
|         // RP ID | ||||
|         $this->_rpIdHash = \substr($binary, 0, 32); | ||||
|  | ||||
|         // flags (1 byte) | ||||
|         $flags = \unpack('Cflags', \substr($binary, 32, 1))['flags']; | ||||
|         $this->_flags = $this->_readFlags($flags); | ||||
|  | ||||
|         // signature counter: 32-bit unsigned big-endian integer. | ||||
|         $this->_signCount = \unpack('Nsigncount', \substr($binary, 33, 4))['signcount']; | ||||
|  | ||||
|         $offset = 37; | ||||
|         // https://www.w3.org/TR/webauthn/#sec-attested-credential-data | ||||
|         if ($this->_flags->attestedDataIncluded) { | ||||
|             $this->_attestedCredentialData = $this->_readAttestData($binary, $offset); | ||||
|         } | ||||
|  | ||||
|         if ($this->_flags->extensionDataIncluded) { | ||||
|             $this->_readExtensionData(\substr($binary, $offset)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Authenticator Attestation Globally Unique Identifier, a unique number | ||||
|      * that identifies the model of the authenticator (not the specific instance | ||||
|      * of the authenticator) | ||||
|      * The aaguid may be 0 if the user is using a old u2f device and/or if | ||||
|      * the browser is using the fido-u2f format. | ||||
|      * @return string | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     public function getAAGUID() { | ||||
|         if (!($this->_attestedCredentialData instanceof \stdClass)) { | ||||
|             throw  new WebAuthnException('credential data not included in authenticator data', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|         return $this->_attestedCredentialData->aaguid; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * returns the authenticatorData as binary | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getBinary() { | ||||
|         return $this->_binary; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * returns the credentialId | ||||
|      * @return string | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     public function getCredentialId() { | ||||
|         if (!($this->_attestedCredentialData instanceof \stdClass)) { | ||||
|             throw  new WebAuthnException('credential id not included in authenticator data', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|         return $this->_attestedCredentialData->credentialId; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * returns the public key in PEM format | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getPublicKeyPem() { | ||||
|         $der = null; | ||||
|         switch ($this->_attestedCredentialData->credentialPublicKey->kty) { | ||||
|             case self::$_EC2_TYPE: $der = $this->_getEc2Der(); break; | ||||
|             case self::$_RSA_TYPE: $der = $this->_getRsaDer(); break; | ||||
|             default: throw new WebAuthnException('invalid key type', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         $pem = '-----BEGIN PUBLIC KEY-----' . "\n"; | ||||
|         $pem .= \chunk_split(\base64_encode($der), 64, "\n"); | ||||
|         $pem .= '-----END PUBLIC KEY-----' . "\n"; | ||||
|         return $pem; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * returns the public key in U2F format | ||||
|      * @return string | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     public function getPublicKeyU2F() { | ||||
|         if (!($this->_attestedCredentialData instanceof \stdClass)) { | ||||
|             throw  new WebAuthnException('credential data not included in authenticator data', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|         return "\x04" . // ECC uncompressed | ||||
|                 $this->_attestedCredentialData->credentialPublicKey->x . | ||||
|                 $this->_attestedCredentialData->credentialPublicKey->y; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * returns the SHA256 hash of the relying party id (=hostname) | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getRpIdHash() { | ||||
|         return $this->_rpIdHash; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * returns the sign counter | ||||
|      * @return int | ||||
|      */ | ||||
|     public function getSignCount() { | ||||
|         return $this->_signCount; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * returns true if the user is present | ||||
|      * @return boolean | ||||
|      */ | ||||
|     public function getUserPresent() { | ||||
|         return $this->_flags->userPresent; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * returns true if the user is verified | ||||
|      * @return boolean | ||||
|      */ | ||||
|     public function getUserVerified() { | ||||
|         return $this->_flags->userVerified; | ||||
|     } | ||||
|  | ||||
|     // ----------------------------------------------- | ||||
|     // PRIVATE | ||||
|     // ----------------------------------------------- | ||||
|  | ||||
|     /** | ||||
|      * Returns DER encoded EC2 key | ||||
|      * @return string | ||||
|      */ | ||||
|     private function _getEc2Der() { | ||||
|         return $this->_der_sequence( | ||||
|             $this->_der_sequence( | ||||
|                 $this->_der_oid("\x2A\x86\x48\xCE\x3D\x02\x01") . // OID 1.2.840.10045.2.1 ecPublicKey | ||||
|                 $this->_der_oid("\x2A\x86\x48\xCE\x3D\x03\x01\x07")  // 1.2.840.10045.3.1.7 prime256v1 | ||||
|             ) . | ||||
|             $this->_der_bitString($this->getPublicKeyU2F()) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns DER encoded RSA key | ||||
|      * @return string | ||||
|      */ | ||||
|     private function _getRsaDer() { | ||||
|         return $this->_der_sequence( | ||||
|             $this->_der_sequence( | ||||
|                 $this->_der_oid("\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01") . // OID 1.2.840.113549.1.1.1 rsaEncryption | ||||
|                 $this->_der_nullValue() | ||||
|             ) . | ||||
|             $this->_der_bitString( | ||||
|                 $this->_der_sequence( | ||||
|                     $this->_der_unsignedInteger($this->_attestedCredentialData->credentialPublicKey->n) . | ||||
|                     $this->_der_unsignedInteger($this->_attestedCredentialData->credentialPublicKey->e) | ||||
|                 ) | ||||
|             ) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * reads the flags from flag byte | ||||
|      * @param string $binFlag | ||||
|      * @return \stdClass | ||||
|      */ | ||||
|     private function _readFlags($binFlag) { | ||||
|         $flags = new \stdClass(); | ||||
|  | ||||
|         $flags->bit_0 = !!($binFlag & 1); | ||||
|         $flags->bit_1 = !!($binFlag & 2); | ||||
|         $flags->bit_2 = !!($binFlag & 4); | ||||
|         $flags->bit_3 = !!($binFlag & 8); | ||||
|         $flags->bit_4 = !!($binFlag & 16); | ||||
|         $flags->bit_5 = !!($binFlag & 32); | ||||
|         $flags->bit_6 = !!($binFlag & 64); | ||||
|         $flags->bit_7 = !!($binFlag & 128); | ||||
|  | ||||
|         // named flags | ||||
|         $flags->userPresent = $flags->bit_0; | ||||
|         $flags->userVerified = $flags->bit_2; | ||||
|         $flags->attestedDataIncluded = $flags->bit_6; | ||||
|         $flags->extensionDataIncluded = $flags->bit_7; | ||||
|         return $flags; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * read attested data | ||||
|      * @param string $binary | ||||
|      * @param int $endOffset | ||||
|      * @return \stdClass | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     private function _readAttestData($binary, &$endOffset) { | ||||
|         $attestedCData = new \stdClass(); | ||||
|         if (\strlen($binary) <= 55) { | ||||
|             throw new WebAuthnException('Attested data should be present but is missing', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         // The AAGUID of the authenticator | ||||
|         $attestedCData->aaguid = \substr($binary, 37, 16); | ||||
|  | ||||
|         //Byte length L of Credential ID, 16-bit unsigned big-endian integer. | ||||
|         $length = \unpack('nlength', \substr($binary, 53, 2))['length']; | ||||
|         $attestedCData->credentialId = \substr($binary, 55, $length); | ||||
|  | ||||
|         // set end offset | ||||
|         $endOffset = 55 + $length; | ||||
|  | ||||
|         // extract public key | ||||
|         $attestedCData->credentialPublicKey = $this->_readCredentialPublicKey($binary, 55 + $length, $endOffset); | ||||
|  | ||||
|         return $attestedCData; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * reads COSE key-encoded elliptic curve public key in EC2 format | ||||
|      * @param string $binary | ||||
|      * @param int $endOffset | ||||
|      * @return \stdClass | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     private function _readCredentialPublicKey($binary, $offset, &$endOffset) { | ||||
|         $enc = CborDecoder::decodeInPlace($binary, $offset, $endOffset); | ||||
|  | ||||
|         // COSE key-encoded elliptic curve public key in EC2 format | ||||
|         $credPKey = new \stdClass(); | ||||
|         $credPKey->kty = $enc[self::$_COSE_KTY]; | ||||
|         $credPKey->alg = $enc[self::$_COSE_ALG]; | ||||
|  | ||||
|         switch ($credPKey->alg) { | ||||
|             case self::$_EC2_ES256: $this->_readCredentialPublicKeyES256($credPKey, $enc); break; | ||||
|             case self::$_RSA_RS256: $this->_readCredentialPublicKeyRS256($credPKey, $enc); break; | ||||
|         } | ||||
|  | ||||
|         return $credPKey; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * extract ES256 informations from cose | ||||
|      * @param \stdClass $credPKey | ||||
|      * @param \stdClass $enc | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     private function _readCredentialPublicKeyES256(&$credPKey, $enc) { | ||||
|         $credPKey->crv = $enc[self::$_COSE_CRV]; | ||||
|         $credPKey->x   = $enc[self::$_COSE_X] instanceof ByteBuffer ? $enc[self::$_COSE_X]->getBinaryString() : null; | ||||
|         $credPKey->y   = $enc[self::$_COSE_Y] instanceof ByteBuffer ? $enc[self::$_COSE_Y]->getBinaryString() : null; | ||||
|         unset ($enc); | ||||
|  | ||||
|         // Validation | ||||
|         if ($credPKey->kty !== self::$_EC2_TYPE) { | ||||
|             throw new WebAuthnException('public key not in EC2 format', WebAuthnException::INVALID_PUBLIC_KEY); | ||||
|         } | ||||
|  | ||||
|         if ($credPKey->alg !== self::$_EC2_ES256) { | ||||
|             throw new WebAuthnException('signature algorithm not ES256', WebAuthnException::INVALID_PUBLIC_KEY); | ||||
|         } | ||||
|  | ||||
|         if ($credPKey->crv !== self::$_EC2_P256) { | ||||
|             throw new WebAuthnException('curve not P-256', WebAuthnException::INVALID_PUBLIC_KEY); | ||||
|         } | ||||
|  | ||||
|         if (\strlen($credPKey->x) !== 32) { | ||||
|             throw new WebAuthnException('Invalid X-coordinate', WebAuthnException::INVALID_PUBLIC_KEY); | ||||
|         } | ||||
|  | ||||
|         if (\strlen($credPKey->y) !== 32) { | ||||
|             throw new WebAuthnException('Invalid Y-coordinate', WebAuthnException::INVALID_PUBLIC_KEY); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * extract RS256 informations from COSE | ||||
|      * @param \stdClass $credPKey | ||||
|      * @param \stdClass $enc | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     private function _readCredentialPublicKeyRS256(&$credPKey, $enc) { | ||||
|         $credPKey->n = $enc[self::$_COSE_N] instanceof ByteBuffer ? $enc[self::$_COSE_N]->getBinaryString() : null; | ||||
|         $credPKey->e = $enc[self::$_COSE_E] instanceof ByteBuffer ? $enc[self::$_COSE_E]->getBinaryString() : null; | ||||
|         unset ($enc); | ||||
|  | ||||
|         // Validation | ||||
|         if ($credPKey->kty !== self::$_RSA_TYPE) { | ||||
|             throw new WebAuthnException('public key not in RSA format', WebAuthnException::INVALID_PUBLIC_KEY); | ||||
|         } | ||||
|  | ||||
|         if ($credPKey->alg !== self::$_RSA_RS256) { | ||||
|             throw new WebAuthnException('signature algorithm not ES256', WebAuthnException::INVALID_PUBLIC_KEY); | ||||
|         } | ||||
|  | ||||
|         if (\strlen($credPKey->n) !== 256) { | ||||
|             throw new WebAuthnException('Invalid RSA modulus', WebAuthnException::INVALID_PUBLIC_KEY); | ||||
|         } | ||||
|  | ||||
|         if (\strlen($credPKey->e) !== 3) { | ||||
|             throw new WebAuthnException('Invalid RSA public exponent', WebAuthnException::INVALID_PUBLIC_KEY); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * reads cbor encoded extension data. | ||||
|      * @param string $binary | ||||
|      * @return array | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     private function _readExtensionData($binary) { | ||||
|         $ext = CborDecoder::decode($binary); | ||||
|         if (!\is_array($ext)) { | ||||
|             throw new WebAuthnException('invalid extension data', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         return $ext; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     // --------------- | ||||
|     // DER functions | ||||
|     // --------------- | ||||
|  | ||||
|     private function _der_length($len) { | ||||
|         if ($len < 128) { | ||||
|             return \chr($len); | ||||
|         } | ||||
|         $lenBytes = ''; | ||||
|         while ($len > 0) { | ||||
|             $lenBytes = \chr($len % 256) . $lenBytes; | ||||
|             $len = \intdiv($len, 256); | ||||
|         } | ||||
|         return \chr(0x80 | \strlen($lenBytes)) . $lenBytes; | ||||
|     } | ||||
|  | ||||
|     private function _der_sequence($contents) { | ||||
|         return "\x30" . $this->_der_length(\strlen($contents)) . $contents; | ||||
|     } | ||||
|  | ||||
|     private function _der_oid($encoded) { | ||||
|         return "\x06" . $this->_der_length(\strlen($encoded)) . $encoded; | ||||
|     } | ||||
|  | ||||
|     private function _der_bitString($bytes) { | ||||
|         return "\x03" . $this->_der_length(\strlen($bytes) + 1) . "\x00" . $bytes; | ||||
|     } | ||||
|  | ||||
|     private function _der_nullValue() { | ||||
|         return "\x05\x00"; | ||||
|     } | ||||
|  | ||||
|     private function _der_unsignedInteger($bytes) { | ||||
|         $len = \strlen($bytes); | ||||
|  | ||||
|         // Remove leading zero bytes | ||||
|         for ($i = 0; $i < ($len - 1); $i++) { | ||||
|             if (\ord($bytes[$i]) !== 0) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         if ($i !== 0) { | ||||
|             $bytes = \substr($bytes, $i); | ||||
|         } | ||||
|  | ||||
|         // If most significant bit is set, prefix with another zero to prevent it being seen as negative number | ||||
|         if ((\ord($bytes[0]) & 0x80) !== 0) { | ||||
|             $bytes = "\x00" . $bytes; | ||||
|         } | ||||
|  | ||||
|         return "\x02" . $this->_der_length(\strlen($bytes)) . $bytes; | ||||
|     } | ||||
| } | ||||
| @@ -1,95 +0,0 @@ | ||||
| <?php | ||||
|  | ||||
| namespace WebAuthn\Attestation\Format; | ||||
| use WebAuthn\WebAuthnException; | ||||
| use WebAuthn\Binary\ByteBuffer; | ||||
|  | ||||
| class AndroidKey extends FormatBase { | ||||
|     private $_alg; | ||||
|     private $_signature; | ||||
|     private $_x5c; | ||||
|  | ||||
|     public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) { | ||||
|         parent::__construct($AttestionObject, $authenticatorData); | ||||
|  | ||||
|         // check u2f data | ||||
|         $attStmt = $this->_attestationObject['attStmt']; | ||||
|  | ||||
|         if (!\array_key_exists('alg', $attStmt) || $this->_getCoseAlgorithm($attStmt['alg']) === null) { | ||||
|             throw new WebAuthnException('unsupported alg: ' . $attStmt['alg'], WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         if (!\array_key_exists('sig', $attStmt) || !\is_object($attStmt['sig']) || !($attStmt['sig'] instanceof ByteBuffer)) { | ||||
|             throw new WebAuthnException('no signature found', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         if (!\array_key_exists('x5c', $attStmt) || !\is_array($attStmt['x5c']) || \count($attStmt['x5c']) < 1) { | ||||
|             throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         if (!\is_object($attStmt['x5c'][0]) || !($attStmt['x5c'][0] instanceof ByteBuffer)) { | ||||
|             throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         $this->_alg = $attStmt['alg']; | ||||
|         $this->_signature = $attStmt['sig']->getBinaryString(); | ||||
|         $this->_x5c = $attStmt['x5c'][0]->getBinaryString(); | ||||
|  | ||||
|         if (count($attStmt['x5c']) > 1) { | ||||
|             for ($i=1; $i<count($attStmt['x5c']); $i++) { | ||||
|                 $this->_x5c_chain[] = $attStmt['x5c'][$i]->getBinaryString(); | ||||
|             } | ||||
|             unset ($i); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /* | ||||
|      * returns the key certificate in PEM format | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getCertificatePem() { | ||||
|         return $this->_createCertificatePem($this->_x5c); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param string $clientDataHash | ||||
|      */ | ||||
|     public function validateAttestation($clientDataHash) { | ||||
|         $publicKey = \openssl_pkey_get_public($this->getCertificatePem()); | ||||
|  | ||||
|         if ($publicKey === false) { | ||||
|             throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY); | ||||
|         } | ||||
|  | ||||
|         // Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash | ||||
|         // using the attestation public key in attestnCert with the algorithm specified in alg. | ||||
|         $dataToVerify = $this->_authenticatorData->getBinary(); | ||||
|         $dataToVerify .= $clientDataHash; | ||||
|  | ||||
|         $coseAlgorithm = $this->_getCoseAlgorithm($this->_alg); | ||||
|  | ||||
|         // check certificate | ||||
|         return \openssl_verify($dataToVerify, $this->_signature, $publicKey, $coseAlgorithm->openssl) === 1; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * validates the certificate against root certificates | ||||
|      * @param array $rootCas | ||||
|      * @return boolean | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     public function validateRootCertificate($rootCas) { | ||||
|         $chainC = $this->_createX5cChainFile(); | ||||
|         if ($chainC) { | ||||
|             $rootCas[] = $chainC; | ||||
|         } | ||||
|  | ||||
|         $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas); | ||||
|         if ($v === -1) { | ||||
|             throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED); | ||||
|         } | ||||
|         return $v; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -1,140 +0,0 @@ | ||||
| <?php | ||||
|  | ||||
|  | ||||
| namespace WebAuthn\Attestation\Format; | ||||
| use WebAuthn\WebAuthnException; | ||||
| use WebAuthn\Binary\ByteBuffer; | ||||
|  | ||||
| class AndroidSafetyNet extends FormatBase { | ||||
|     private $_signature; | ||||
|     private $_signedValue; | ||||
|     private $_x5c; | ||||
|     private $_payload; | ||||
|  | ||||
|     public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) { | ||||
|         parent::__construct($AttestionObject, $authenticatorData); | ||||
|  | ||||
|         // check data | ||||
|         $attStmt = $this->_attestationObject['attStmt']; | ||||
|  | ||||
|         if (!\array_key_exists('ver', $attStmt) || !$attStmt['ver']) { | ||||
|             throw new WebAuthnException('invalid Android Safety Net Format', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         if (!\array_key_exists('response', $attStmt) || !($attStmt['response'] instanceof ByteBuffer)) { | ||||
|             throw new WebAuthnException('invalid Android Safety Net Format', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         $response = $attStmt['response']->getBinaryString(); | ||||
|  | ||||
|         // Response is a JWS [RFC7515] object in Compact Serialization. | ||||
|         // JWSs have three segments separated by two period ('.') characters | ||||
|         $parts = \explode('.', $response); | ||||
|         unset ($response); | ||||
|         if (\count($parts) !== 3) { | ||||
|             throw new WebAuthnException('invalid JWS data', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         $header = $this->_base64url_decode($parts[0]); | ||||
|         $payload = $this->_base64url_decode($parts[1]); | ||||
|         $this->_signature = $this->_base64url_decode($parts[2]); | ||||
|         $this->_signedValue = $parts[0] . '.' . $parts[1]; | ||||
|         unset ($parts); | ||||
|  | ||||
|         $header = \json_decode($header); | ||||
|         $payload = \json_decode($payload); | ||||
|  | ||||
|         if (!($header instanceof \stdClass)) { | ||||
|             throw new WebAuthnException('invalid JWS header', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|         if (!($payload instanceof \stdClass)) { | ||||
|             throw new WebAuthnException('invalid JWS payload', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         if (!$header->x5c || !is_array($header->x5c) || count($header->x5c) === 0) { | ||||
|             throw new WebAuthnException('No X.509 signature in JWS Header', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         // algorithm | ||||
|         if (!\in_array($header->alg, array('RS256', 'ES256'))) { | ||||
|             throw new WebAuthnException('invalid JWS algorithm ' . $header->alg, WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         $this->_x5c = \base64_decode($header->x5c[0]); | ||||
|         $this->_payload = $payload; | ||||
|  | ||||
|         if (count($header->x5c) > 1) { | ||||
|             for ($i=1; $i<count($header->x5c); $i++) { | ||||
|                 $this->_x5c_chain[] = \base64_decode($header->x5c[$i]); | ||||
|             } | ||||
|             unset ($i); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /* | ||||
|      * returns the key certificate in PEM format | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getCertificatePem() { | ||||
|         return $this->_createCertificatePem($this->_x5c); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param string $clientDataHash | ||||
|      */ | ||||
|     public function validateAttestation($clientDataHash) { | ||||
|         $publicKey = \openssl_pkey_get_public($this->getCertificatePem()); | ||||
|  | ||||
|         // Verify that the nonce in the response is identical to the Base64 encoding | ||||
|         // of the SHA-256 hash of the concatenation of authenticatorData and clientDataHash. | ||||
|         if (!$this->_payload->nonce || $this->_payload->nonce !== \base64_encode(\hash('SHA256', $this->_authenticatorData->getBinary() . $clientDataHash, true))) { | ||||
|             throw new WebAuthnException('invalid nonce in JWS payload', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         // Verify that attestationCert is issued to the hostname "attest.android.com" | ||||
|         $certInfo = \openssl_x509_parse($this->getCertificatePem()); | ||||
|         if (!\is_array($certInfo) || !$certInfo['subject'] || $certInfo['subject']['CN'] !== 'attest.android.com') { | ||||
|             throw new WebAuthnException('invalid certificate CN in JWS (' . $certInfo['subject']['CN']. ')', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         // Verify that the ctsProfileMatch attribute in the payload of response is true. | ||||
|         if (!$this->_payload->ctsProfileMatch) { | ||||
|             throw new WebAuthnException('invalid ctsProfileMatch in payload', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         // check certificate | ||||
|         return \openssl_verify($this->_signedValue, $this->_signature, $publicKey, OPENSSL_ALGO_SHA256) === 1; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * validates the certificate against root certificates | ||||
|      * @param array $rootCas | ||||
|      * @return boolean | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     public function validateRootCertificate($rootCas) { | ||||
|         $chainC = $this->_createX5cChainFile(); | ||||
|         if ($chainC) { | ||||
|             $rootCas[] = $chainC; | ||||
|         } | ||||
|  | ||||
|         $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas); | ||||
|         if ($v === -1) { | ||||
|             throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED); | ||||
|         } | ||||
|         return $v; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * decode base64 url | ||||
|      * @param string $data | ||||
|      * @return string | ||||
|      */ | ||||
|     private function _base64url_decode($data) { | ||||
|         return \base64_decode(\strtr($data, '-_', '+/') . \str_repeat('=', 3 - (3 + \strlen($data)) % 4)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -1,183 +0,0 @@ | ||||
| <?php | ||||
|  | ||||
|  | ||||
| namespace WebAuthn\Attestation\Format; | ||||
| use WebAuthn\WebAuthnException; | ||||
|  | ||||
|  | ||||
| abstract class FormatBase { | ||||
|     protected $_attestationObject = null; | ||||
|     protected $_authenticatorData = null; | ||||
|     protected $_x5c_chain = array(); | ||||
|     protected $_x5c_tempFile = null; | ||||
|  | ||||
|     /** | ||||
|      * | ||||
|      * @param Array $AttestionObject | ||||
|      * @param \WebAuthn\Attestation\AuthenticatorData $authenticatorData | ||||
|      */ | ||||
|     public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) { | ||||
|         $this->_attestationObject = $AttestionObject; | ||||
|         $this->_authenticatorData = $authenticatorData; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * | ||||
|      */ | ||||
|     public function __destruct() { | ||||
|         // delete X.509 chain certificate file after use | ||||
|         if (\is_file($this->_x5c_tempFile)) { | ||||
|             \unlink($this->_x5c_tempFile); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * returns the certificate chain in PEM format | ||||
|      * @return string|null | ||||
|      */ | ||||
|     public function getCertificateChain() { | ||||
|         if (\is_file($this->_x5c_tempFile)) { | ||||
|             return \file_get_contents($this->_x5c_tempFile); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * returns the key X.509 certificate in PEM format | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getCertificatePem() { | ||||
|         // need to be overwritten | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * checks validity of the signature | ||||
|      * @param string $clientDataHash | ||||
|      * @return bool | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     public function validateAttestation($clientDataHash) { | ||||
|         // need to be overwritten | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * validates the certificate against root certificates | ||||
|      * @param array $rootCas | ||||
|      * @return boolean | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     public function validateRootCertificate($rootCas) { | ||||
|         // need to be overwritten | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * create a PEM encoded certificate with X.509 binary data | ||||
|      * @param string $x5c | ||||
|      * @return string | ||||
|      */ | ||||
|     protected function _createCertificatePem($x5c) { | ||||
|         $pem = '-----BEGIN CERTIFICATE-----' . "\n"; | ||||
|         $pem .= \chunk_split(\base64_encode($x5c), 64, "\n"); | ||||
|         $pem .= '-----END CERTIFICATE-----' . "\n"; | ||||
|         return $pem; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * creates a PEM encoded chain file | ||||
|      * @return type | ||||
|      */ | ||||
|     protected function _createX5cChainFile() { | ||||
|         $content = ''; | ||||
|         if (\is_array($this->_x5c_chain) && \count($this->_x5c_chain) > 0) { | ||||
|             foreach ($this->_x5c_chain as $x5c) { | ||||
|                 $certInfo = \openssl_x509_parse($this->_createCertificatePem($x5c)); | ||||
|                 // check if issuer = subject (self signed) | ||||
|                 if (\is_array($certInfo) && \is_array($certInfo['issuer']) && \is_array($certInfo['subject'])) { | ||||
|                     $selfSigned = true; | ||||
|                     foreach ($certInfo['issuer'] as $k => $v) { | ||||
|                         if ($certInfo['subject'][$k] !== $v) { | ||||
|                             $selfSigned = false; | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     if (!$selfSigned) { | ||||
|                         $content .= "\n" . $this->_createCertificatePem($x5c) . "\n"; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if ($content) { | ||||
|             $this->_x5c_tempFile = \sys_get_temp_dir() . '/x5c_chain_' . \base_convert(\rand(), 10, 36) . '.pem'; | ||||
|             if (\file_put_contents($this->_x5c_tempFile, $content) !== false) { | ||||
|                 return $this->_x5c_tempFile; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * returns the name and openssl key for provided cose number. | ||||
|      * @param int $coseNumber | ||||
|      * @return \stdClass|null | ||||
|      */ | ||||
|     protected function _getCoseAlgorithm($coseNumber) { | ||||
|         // https://www.iana.org/assignments/cose/cose.xhtml#algorithms | ||||
|         $coseAlgorithms = array( | ||||
|             array( | ||||
|                 'hash' => 'SHA1', | ||||
|                 'openssl' => OPENSSL_ALGO_SHA1, | ||||
|                 'cose' => array( | ||||
|                     -65535  // RS1 | ||||
|                 )), | ||||
|  | ||||
|             array( | ||||
|                 'hash' => 'SHA256', | ||||
|                 'openssl' => OPENSSL_ALGO_SHA256, | ||||
|                 'cose' => array( | ||||
|                     -257, // RS256 | ||||
|                     -37,  // PS256 | ||||
|                     -7,   // ES256 | ||||
|                     5     // HMAC256 | ||||
|                 )), | ||||
|  | ||||
|             array( | ||||
|                 'hash' => 'SHA384', | ||||
|                 'openssl' => OPENSSL_ALGO_SHA384, | ||||
|                 'cose' => array( | ||||
|                     -258, // RS384 | ||||
|                     -38,  // PS384 | ||||
|                     -35,  // ES384 | ||||
|                     6     // HMAC384 | ||||
|                 )), | ||||
|  | ||||
|             array( | ||||
|                 'hash' => 'SHA512', | ||||
|                 'openssl' => OPENSSL_ALGO_SHA512, | ||||
|                 'cose' => array( | ||||
|                     -259, // RS512 | ||||
|                     -39,  // PS512 | ||||
|                     -36,  // ES512 | ||||
|                     7     // HMAC512 | ||||
|                 )) | ||||
|         ); | ||||
|  | ||||
|         foreach ($coseAlgorithms as $coseAlgorithm) { | ||||
|             if (\in_array($coseNumber, $coseAlgorithm['cose'], true)) { | ||||
|                 $return = new \stdClass(); | ||||
|                 $return->hash = $coseAlgorithm['hash']; | ||||
|                 $return->openssl = $coseAlgorithm['openssl']; | ||||
|                 return $return; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
| @@ -1,39 +0,0 @@ | ||||
| <?php | ||||
|  | ||||
|  | ||||
| namespace WebAuthn\Attestation\Format; | ||||
| use WebAuthn\WebAuthnException; | ||||
|  | ||||
| class None extends FormatBase { | ||||
|  | ||||
|  | ||||
|     public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) { | ||||
|         parent::__construct($AttestionObject, $authenticatorData); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /* | ||||
|      * returns the key certificate in PEM format | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getCertificatePem() { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param string $clientDataHash | ||||
|      */ | ||||
|     public function validateAttestation($clientDataHash) { | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * validates the certificate against root certificates | ||||
|      * @param array $rootCas | ||||
|      * @return boolean | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     public function validateRootCertificate($rootCas) { | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
| @@ -1,138 +0,0 @@ | ||||
| <?php | ||||
|  | ||||
|  | ||||
| namespace WebAuthn\Attestation\Format; | ||||
| use WebAuthn\WebAuthnException; | ||||
| use WebAuthn\Binary\ByteBuffer; | ||||
|  | ||||
| class Packed extends FormatBase { | ||||
|     private $_alg; | ||||
|     private $_signature; | ||||
|     private $_x5c; | ||||
|  | ||||
|     public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) { | ||||
|         parent::__construct($AttestionObject, $authenticatorData); | ||||
|  | ||||
|         // check packed data | ||||
|         $attStmt = $this->_attestationObject['attStmt']; | ||||
|  | ||||
|         if (!\array_key_exists('alg', $attStmt) || $this->_getCoseAlgorithm($attStmt['alg']) === null) { | ||||
|             throw new WebAuthnException('unsupported alg: ' . $attStmt['alg'], WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         if (!\array_key_exists('sig', $attStmt) || !\is_object($attStmt['sig']) || !($attStmt['sig'] instanceof ByteBuffer)) { | ||||
|             throw new WebAuthnException('no signature found', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         $this->_alg = $attStmt['alg']; | ||||
|         $this->_signature = $attStmt['sig']->getBinaryString(); | ||||
|  | ||||
|         // certificate for validation | ||||
|         if (\array_key_exists('x5c', $attStmt) && \is_array($attStmt['x5c']) && \count($attStmt['x5c']) > 0) { | ||||
|  | ||||
|             // The attestation certificate attestnCert MUST be the first element in the array | ||||
|             $attestnCert = array_shift($attStmt['x5c']); | ||||
|  | ||||
|             if (!($attestnCert instanceof ByteBuffer)) { | ||||
|                 throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); | ||||
|             } | ||||
|  | ||||
|             $this->_x5c = $attestnCert->getBinaryString(); | ||||
|  | ||||
|             // certificate chain | ||||
|             foreach ($attStmt['x5c'] as $chain) { | ||||
|                 if ($chain instanceof ByteBuffer) { | ||||
|                     $this->_x5c_chain[] = $chain->getBinaryString(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /* | ||||
|      * returns the key certificate in PEM format | ||||
|      * @return string|null | ||||
|      */ | ||||
|     public function getCertificatePem() { | ||||
|         if (!$this->_x5c) { | ||||
|             return null; | ||||
|         } | ||||
|         return $this->_createCertificatePem($this->_x5c); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param string $clientDataHash | ||||
|      */ | ||||
|     public function validateAttestation($clientDataHash) { | ||||
|         if ($this->_x5c) { | ||||
|             return $this->_validateOverX5c($clientDataHash); | ||||
|         } else { | ||||
|             return $this->_validateSelfAttestation($clientDataHash); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * validates the certificate against root certificates | ||||
|      * @param array $rootCas | ||||
|      * @return boolean | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     public function validateRootCertificate($rootCas) { | ||||
|         if (!$this->_x5c) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         $chainC = $this->_createX5cChainFile(); | ||||
|         if ($chainC) { | ||||
|             $rootCas[] = $chainC; | ||||
|         } | ||||
|  | ||||
|         $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas); | ||||
|         if ($v === -1) { | ||||
|             throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED); | ||||
|         } | ||||
|         return $v; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * validate if x5c is present | ||||
|      * @param string $clientDataHash | ||||
|      * @return bool | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     protected function _validateOverX5c($clientDataHash) { | ||||
|         $publicKey = \openssl_pkey_get_public($this->getCertificatePem()); | ||||
|  | ||||
|         if ($publicKey === false) { | ||||
|             throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY); | ||||
|         } | ||||
|  | ||||
|         // Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash | ||||
|         // using the attestation public key in attestnCert with the algorithm specified in alg. | ||||
|         $dataToVerify = $this->_authenticatorData->getBinary(); | ||||
|         $dataToVerify .= $clientDataHash; | ||||
|  | ||||
|         $coseAlgorithm = $this->_getCoseAlgorithm($this->_alg); | ||||
|  | ||||
|         // check certificate | ||||
|         return \openssl_verify($dataToVerify, $this->_signature, $publicKey, $coseAlgorithm->openssl) === 1; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * validate if self attestation is in use | ||||
|      * @param string $clientDataHash | ||||
|      * @return bool | ||||
|      */ | ||||
|     protected function _validateSelfAttestation($clientDataHash) { | ||||
|         // Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash | ||||
|         // using the credential public key with alg. | ||||
|         $dataToVerify = $this->_authenticatorData->getBinary(); | ||||
|         $dataToVerify .= $clientDataHash; | ||||
|  | ||||
|         $publicKey = $this->_authenticatorData->getPublicKeyPem(); | ||||
|  | ||||
|         // check certificate | ||||
|         return \openssl_verify($dataToVerify, $this->_signature, $publicKey, OPENSSL_ALGO_SHA256) === 1; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -1,179 +0,0 @@ | ||||
| <?php | ||||
|  | ||||
|  | ||||
| namespace WebAuthn\Attestation\Format; | ||||
| use WebAuthn\WebAuthnException; | ||||
| use WebAuthn\Binary\ByteBuffer; | ||||
|  | ||||
| class Tpm extends FormatBase { | ||||
|     private $_TPM_GENERATED_VALUE = "\xFF\x54\x43\x47"; | ||||
|     private $_TPM_ST_ATTEST_CERTIFY = "\x80\x17"; | ||||
|     private $_alg; | ||||
|     private $_signature; | ||||
|     private $_pubArea; | ||||
|     private $_x5c; | ||||
|  | ||||
|     /** | ||||
|      * @var ByteBuffer | ||||
|      */ | ||||
|     private $_certInfo; | ||||
|  | ||||
|  | ||||
|     public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) { | ||||
|         parent::__construct($AttestionObject, $authenticatorData); | ||||
|  | ||||
|         // check packed data | ||||
|         $attStmt = $this->_attestationObject['attStmt']; | ||||
|  | ||||
|         if (!\array_key_exists('ver', $attStmt) || $attStmt['ver'] !== '2.0') { | ||||
|             throw new WebAuthnException('invalid tpm version: ' . $attStmt['ver'], WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         if (!\array_key_exists('alg', $attStmt) || $this->_getCoseAlgorithm($attStmt['alg']) === null) { | ||||
|             throw new WebAuthnException('unsupported alg: ' . $attStmt['alg'], WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         if (!\array_key_exists('sig', $attStmt) || !\is_object($attStmt['sig']) || !($attStmt['sig'] instanceof ByteBuffer)) { | ||||
|             throw new WebAuthnException('signature not found', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         if (!\array_key_exists('certInfo', $attStmt) || !\is_object($attStmt['certInfo']) || !($attStmt['certInfo'] instanceof ByteBuffer)) { | ||||
|             throw new WebAuthnException('certInfo not found', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         if (!\array_key_exists('pubArea', $attStmt) || !\is_object($attStmt['pubArea']) || !($attStmt['pubArea'] instanceof ByteBuffer)) { | ||||
|             throw new WebAuthnException('pubArea not found', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         $this->_alg = $attStmt['alg']; | ||||
|         $this->_signature = $attStmt['sig']->getBinaryString(); | ||||
|         $this->_certInfo = $attStmt['certInfo']; | ||||
|         $this->_pubArea = $attStmt['pubArea']; | ||||
|  | ||||
|         // certificate for validation | ||||
|         if (\array_key_exists('x5c', $attStmt) && \is_array($attStmt['x5c']) && \count($attStmt['x5c']) > 0) { | ||||
|  | ||||
|             // The attestation certificate attestnCert MUST be the first element in the array | ||||
|             $attestnCert = array_shift($attStmt['x5c']); | ||||
|  | ||||
|             if (!($attestnCert instanceof ByteBuffer)) { | ||||
|                 throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); | ||||
|             } | ||||
|  | ||||
|             $this->_x5c = $attestnCert->getBinaryString(); | ||||
|  | ||||
|             // certificate chain | ||||
|             foreach ($attStmt['x5c'] as $chain) { | ||||
|                 if ($chain instanceof ByteBuffer) { | ||||
|                     $this->_x5c_chain[] = $chain->getBinaryString(); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         } else { | ||||
|             throw new WebAuthnException('no x5c certificate found', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /* | ||||
|      * returns the key certificate in PEM format | ||||
|      * @return string|null | ||||
|      */ | ||||
|     public function getCertificatePem() { | ||||
|         if (!$this->_x5c) { | ||||
|             return null; | ||||
|         } | ||||
|         return $this->_createCertificatePem($this->_x5c); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param string $clientDataHash | ||||
|      */ | ||||
|     public function validateAttestation($clientDataHash) { | ||||
|         return $this->_validateOverX5c($clientDataHash); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * validates the certificate against root certificates | ||||
|      * @param array $rootCas | ||||
|      * @return boolean | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     public function validateRootCertificate($rootCas) { | ||||
|         if (!$this->_x5c) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         $chainC = $this->_createX5cChainFile(); | ||||
|         if ($chainC) { | ||||
|             $rootCas[] = $chainC; | ||||
|         } | ||||
|  | ||||
|         $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas); | ||||
|         if ($v === -1) { | ||||
|             throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED); | ||||
|         } | ||||
|         return $v; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * validate if x5c is present | ||||
|      * @param string $clientDataHash | ||||
|      * @return bool | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     protected function _validateOverX5c($clientDataHash) { | ||||
|         $publicKey = \openssl_pkey_get_public($this->getCertificatePem()); | ||||
|  | ||||
|         if ($publicKey === false) { | ||||
|             throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY); | ||||
|         } | ||||
|  | ||||
|         // Concatenate authenticatorData and clientDataHash to form attToBeSigned. | ||||
|         $attToBeSigned = $this->_authenticatorData->getBinary(); | ||||
|         $attToBeSigned .= $clientDataHash; | ||||
|  | ||||
|         // Validate that certInfo is valid: | ||||
|  | ||||
|         // Verify that magic is set to TPM_GENERATED_VALUE. | ||||
|         if ($this->_certInfo->getBytes(0, 4) !== $this->_TPM_GENERATED_VALUE) { | ||||
|             throw new WebAuthnException('tpm magic not TPM_GENERATED_VALUE', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         // Verify that type is set to TPM_ST_ATTEST_CERTIFY. | ||||
|         if ($this->_certInfo->getBytes(4, 2) !== $this->_TPM_ST_ATTEST_CERTIFY) { | ||||
|             throw new WebAuthnException('tpm type not TPM_ST_ATTEST_CERTIFY', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         $offset = 6; | ||||
|         $qualifiedSigner = $this->_tpmReadLengthPrefixed($this->_certInfo, $offset); | ||||
|         $extraData = $this->_tpmReadLengthPrefixed($this->_certInfo, $offset); | ||||
|         $coseAlg = $this->_getCoseAlgorithm($this->_alg); | ||||
|  | ||||
|         // Verify that extraData is set to the hash of attToBeSigned using the hash algorithm employed in "alg". | ||||
|         if ($extraData->getBinaryString() !== \hash($coseAlg->hash, $attToBeSigned, true)) { | ||||
|             throw new WebAuthnException('certInfo:extraData not hash of attToBeSigned', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         // Verify the sig is a valid signature over certInfo using the attestation | ||||
|         // public key in aikCert with the algorithm specified in alg. | ||||
|         return \openssl_verify($this->_certInfo->getBinaryString(), $this->_signature, $publicKey, $coseAlg->openssl) === 1; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * returns next part of ByteBuffer | ||||
|      * @param ByteBuffer $buffer | ||||
|      * @param int $offset | ||||
|      * @return ByteBuffer | ||||
|      */ | ||||
|     protected function _tpmReadLengthPrefixed(ByteBuffer $buffer, &$offset) { | ||||
|         $len = $buffer->getUint16Val($offset); | ||||
|         $data = $buffer->getBytes($offset + 2, $len); | ||||
|         $offset += (2 + $len); | ||||
|  | ||||
|         return new ByteBuffer($data); | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| @@ -1,93 +0,0 @@ | ||||
| <?php | ||||
|  | ||||
|  | ||||
| namespace WebAuthn\Attestation\Format; | ||||
| use WebAuthn\WebAuthnException; | ||||
| use WebAuthn\Binary\ByteBuffer; | ||||
|  | ||||
| class U2f extends FormatBase { | ||||
|     private $_alg; | ||||
|     private $_signature; | ||||
|     private $_x5c; | ||||
|  | ||||
|     public function __construct($AttestionObject, \WebAuthn\Attestation\AuthenticatorData $authenticatorData) { | ||||
|         parent::__construct($AttestionObject, $authenticatorData); | ||||
|  | ||||
|         // check u2f data | ||||
|         $attStmt = $this->_attestationObject['attStmt']; | ||||
|  | ||||
|         if (!\array_key_exists('alg', $attStmt) || $this->_getCoseAlgorithm($attStmt['alg']) === null) { | ||||
|             throw new WebAuthnException('unsupported alg: ' . $attStmt['alg'], WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         if (!\array_key_exists('sig', $attStmt) || !\is_object($attStmt['sig']) || !($attStmt['sig'] instanceof ByteBuffer)) { | ||||
|             throw new WebAuthnException('no signature found', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         if (!\array_key_exists('x5c', $attStmt) || !\is_array($attStmt['x5c']) || \count($attStmt['x5c']) !== 1) { | ||||
|             throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         if (!\is_object($attStmt['x5c'][0]) || !($attStmt['x5c'][0] instanceof ByteBuffer)) { | ||||
|             throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         $this->_alg = $attStmt['alg']; | ||||
|         $this->_signature = $attStmt['sig']->getBinaryString(); | ||||
|         $this->_x5c = $attStmt['x5c'][0]->getBinaryString(); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /* | ||||
|      * returns the key certificate in PEM format | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getCertificatePem() { | ||||
|         $pem = '-----BEGIN CERTIFICATE-----' . "\n"; | ||||
|         $pem .= \chunk_split(\base64_encode($this->_x5c), 64, "\n"); | ||||
|         $pem .= '-----END CERTIFICATE-----' . "\n"; | ||||
|         return $pem; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param string $clientDataHash | ||||
|      */ | ||||
|     public function validateAttestation($clientDataHash) { | ||||
|         $publicKey = \openssl_pkey_get_public($this->getCertificatePem()); | ||||
|  | ||||
|         if ($publicKey === false) { | ||||
|             throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY); | ||||
|         } | ||||
|  | ||||
|         // Let verificationData be the concatenation of (0x00 || rpIdHash || clientDataHash || credentialId || publicKeyU2F) | ||||
|         $dataToVerify = "\x00"; | ||||
|         $dataToVerify .= $this->_authenticatorData->getRpIdHash(); | ||||
|         $dataToVerify .= $clientDataHash; | ||||
|         $dataToVerify .= $this->_authenticatorData->getCredentialId(); | ||||
|         $dataToVerify .= $this->_authenticatorData->getPublicKeyU2F(); | ||||
|  | ||||
|         $coseAlgorithm = $this->_getCoseAlgorithm($this->_alg); | ||||
|  | ||||
|         // check certificate | ||||
|         return \openssl_verify($dataToVerify, $this->_signature, $publicKey, $coseAlgorithm->openssl) === 1; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * validates the certificate against root certificates | ||||
|      * @param array $rootCas | ||||
|      * @return boolean | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     public function validateRootCertificate($rootCas) { | ||||
|         $chainC = $this->_createX5cChainFile(); | ||||
|         if ($chainC) { | ||||
|             $rootCas[] = $chainC; | ||||
|         } | ||||
|  | ||||
|         $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas); | ||||
|         if ($v === -1) { | ||||
|             throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED); | ||||
|         } | ||||
|         return $v; | ||||
|     } | ||||
| } | ||||
| @@ -1,255 +0,0 @@ | ||||
| <?php | ||||
|  | ||||
|  | ||||
| namespace WebAuthn\Binary; | ||||
| use WebAuthn\WebAuthnException; | ||||
|  | ||||
| /** | ||||
|  * Modified version of https://github.com/madwizard-thomas/webauthn-server/blob/master/src/Format/ByteBuffer.php | ||||
|  * Copyright © 2018 Thomas Bleeker - MIT licensed | ||||
|  * Modified by Lukas Buchs | ||||
|  * Thanks Thomas for your work! | ||||
|  */ | ||||
| class ByteBuffer implements \JsonSerializable, \Serializable { | ||||
|     /** | ||||
|      * @var bool | ||||
|      */ | ||||
|     public static $useBase64UrlEncoding = false; | ||||
|  | ||||
|     /** | ||||
|      * @var string | ||||
|      */ | ||||
|     private $_data; | ||||
|  | ||||
|     /** | ||||
|      * @var int | ||||
|      */ | ||||
|     private $_length; | ||||
|  | ||||
|     public function __construct($binaryData) { | ||||
|         $this->_data = $binaryData; | ||||
|         $this->_length = \strlen($binaryData); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     // ----------------------- | ||||
|     // PUBLIC STATIC | ||||
|     // ----------------------- | ||||
|  | ||||
|     /** | ||||
|      * create a ByteBuffer from a base64 url encoded string | ||||
|      * @param string $base64url | ||||
|      * @return \WebAuthn\Binary\ByteBuffer | ||||
|      */ | ||||
|     public static function fromBase64Url($base64url) { | ||||
|         $bin = self::_base64url_decode($base64url); | ||||
|         if ($bin === false) { | ||||
|             throw new WebAuthnException('ByteBuffer: Invalid base64 url string', WebAuthnException::BYTEBUFFER); | ||||
|         } | ||||
|         return new ByteBuffer($bin); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * create a ByteBuffer from a base64 url encoded string | ||||
|      * @param string $hex | ||||
|      * @return \WebAuthn\Binary\ByteBuffer | ||||
|      */ | ||||
|     public static function fromHex($hex) { | ||||
|         $bin = \hex2bin($hex); | ||||
|         if ($bin === false) { | ||||
|             throw new WebAuthnException('ByteBuffer: Invalid hex string', WebAuthnException::BYTEBUFFER); | ||||
|         } | ||||
|         return new ByteBuffer($bin); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * create a random ByteBuffer | ||||
|      * @param string $length | ||||
|      * @return \WebAuthn\Binary\ByteBuffer | ||||
|      */ | ||||
|     public static function randomBuffer($length) { | ||||
|         if (\function_exists('random_bytes')) { // >PHP 7.0 | ||||
|             return new ByteBuffer(\random_bytes($length)); | ||||
|  | ||||
|         } else if (\function_exists('openssl_random_pseudo_bytes')) { | ||||
|             return new ByteBuffer(\openssl_random_pseudo_bytes($length)); | ||||
|  | ||||
|         } else { | ||||
|             throw new WebAuthnException('ByteBuffer: cannot generate random bytes', WebAuthnException::BYTEBUFFER); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // ----------------------- | ||||
|     // PUBLIC | ||||
|     // ----------------------- | ||||
|  | ||||
|     public function getBytes($offset, $length) { | ||||
|         if ($offset < 0 || $length < 0 || ($offset + $length > $this->_length)) { | ||||
|             throw new WebAuthnException('ByteBuffer: Invalid offset or length', WebAuthnException::BYTEBUFFER); | ||||
|         } | ||||
|         return \substr($this->_data, $offset, $length); | ||||
|     } | ||||
|  | ||||
|     public function getByteVal($offset) { | ||||
|         if ($offset < 0 || $offset >= $this->_length) { | ||||
|             throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER); | ||||
|         } | ||||
|         return \ord(\substr($this->_data, $offset, 1)); | ||||
|     } | ||||
|  | ||||
|     public function getLength() { | ||||
|         return $this->_length; | ||||
|     } | ||||
|  | ||||
|     public function getUint16Val($offset) { | ||||
|         if ($offset < 0 || ($offset + 2) > $this->_length) { | ||||
|             throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER); | ||||
|         } | ||||
|         return unpack('n', $this->_data, $offset)[1]; | ||||
|     } | ||||
|  | ||||
|     public function getUint32Val($offset) { | ||||
|         if ($offset < 0 || ($offset + 4) > $this->_length) { | ||||
|             throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER); | ||||
|         } | ||||
|         $val = unpack('N', $this->_data, $offset)[1]; | ||||
|  | ||||
|         // Signed integer overflow causes signed negative numbers | ||||
|         if ($val < 0) { | ||||
|             throw new WebAuthnException('ByteBuffer: Value out of integer range.', WebAuthnException::BYTEBUFFER); | ||||
|         } | ||||
|         return $val; | ||||
|     } | ||||
|  | ||||
|     public function getUint64Val($offset) { | ||||
|         if (PHP_INT_SIZE < 8) { | ||||
|             throw new WebAuthnException('ByteBuffer: 64-bit values not supported by this system', WebAuthnException::BYTEBUFFER); | ||||
|         } | ||||
|         if ($offset < 0 || ($offset + 8) > $this->_length) { | ||||
|             throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER); | ||||
|         } | ||||
|         $val = unpack('J', $this->_data, $offset)[1]; | ||||
|  | ||||
|         // Signed integer overflow causes signed negative numbers | ||||
|         if ($val < 0) { | ||||
|             throw new WebAuthnException('ByteBuffer: Value out of integer range.', WebAuthnException::BYTEBUFFER); | ||||
|         } | ||||
|  | ||||
|         return $val; | ||||
|     } | ||||
|  | ||||
|     public function getHalfFloatVal($offset) { | ||||
|         //FROM spec pseudo decode_half(unsigned char *halfp) | ||||
|         $half = $this->getUint16Val($offset); | ||||
|  | ||||
|         $exp = ($half >> 10) & 0x1f; | ||||
|         $mant = $half & 0x3ff; | ||||
|  | ||||
|         if ($exp === 0) { | ||||
|             $val = $mant * (2 ** -24); | ||||
|         } elseif ($exp !== 31) { | ||||
|             $val = ($mant + 1024) * (2 ** ($exp - 25)); | ||||
|         } else { | ||||
|             $val = ($mant === 0) ? INF : NAN; | ||||
|         } | ||||
|  | ||||
|         return ($half & 0x8000) ? -$val : $val; | ||||
|     } | ||||
|  | ||||
|     public function getFloatVal($offset) { | ||||
|         if ($offset < 0 || ($offset + 4) > $this->_length) { | ||||
|             throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER); | ||||
|         } | ||||
|         return unpack('G', $this->_data, $offset)[1]; | ||||
|     } | ||||
|  | ||||
|     public function getDoubleVal($offset) { | ||||
|         if ($offset < 0 || ($offset + 8) > $this->_length) { | ||||
|             throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER); | ||||
|         } | ||||
|         return unpack('E', $this->_data, $offset)[1]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getBinaryString() { | ||||
|         return $this->_data; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param string $buffer | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function equals($buffer) { | ||||
|         return is_string($this->_data) && $this->_data === $buffer->data; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getHex() { | ||||
|         return \bin2hex($this->_data); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function isEmpty() { | ||||
|         return $this->_length === 0; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * jsonSerialize interface | ||||
|      * return binary data in RFC 1342-Like serialized string | ||||
|      * @return \stdClass | ||||
|      */ | ||||
|     public function jsonSerialize() { | ||||
|         if (ByteBuffer::$useBase64UrlEncoding) { | ||||
|             return self::_base64url_encode($this->_data); | ||||
|  | ||||
|         } else { | ||||
|             return '=?BINARY?B?' . \base64_encode($this->_data) . '?='; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Serializable-Interface | ||||
|      * @return string | ||||
|      */ | ||||
|     public function serialize() { | ||||
|         return \serialize($this->_data); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Serializable-Interface | ||||
|      * @param string $serialized | ||||
|      */ | ||||
|     public function unserialize($serialized) { | ||||
|         $this->_data = \unserialize($serialized); | ||||
|         $this->_length = \strlen($this->_data); | ||||
|     } | ||||
|  | ||||
|     // ----------------------- | ||||
|     // PROTECTED STATIC | ||||
|     // ----------------------- | ||||
|  | ||||
|     /** | ||||
|      * base64 url decoding | ||||
|      * @param string $data | ||||
|      * @return string | ||||
|      */ | ||||
|     protected static function _base64url_decode($data) { | ||||
|         return \base64_decode(\strtr($data, '-_', '+/') . \str_repeat('=', 3 - (3 + \strlen($data)) % 4)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * base64 url encoding | ||||
|      * @param string $data | ||||
|      * @return string | ||||
|      */ | ||||
|     protected static function _base64url_encode($data) { | ||||
|         return \rtrim(\strtr(\base64_encode($data), '+/', '-_'), '='); | ||||
|     } | ||||
| } | ||||
| @@ -1,220 +0,0 @@ | ||||
| <?php | ||||
|  | ||||
|  | ||||
| namespace WebAuthn\CBOR; | ||||
| use WebAuthn\WebAuthnException; | ||||
| use WebAuthn\Binary\ByteBuffer; | ||||
|  | ||||
| /** | ||||
|  * Modified version of https://github.com/madwizard-thomas/webauthn-server/blob/master/src/Format/CborDecoder.php | ||||
|  * Copyright © 2018 Thomas Bleeker - MIT licensed | ||||
|  * Modified by Lukas Buchs | ||||
|  * Thanks Thomas for your work! | ||||
|  */ | ||||
| class CborDecoder { | ||||
|     const CBOR_MAJOR_UNSIGNED_INT = 0; | ||||
|     const CBOR_MAJOR_TEXT_STRING = 3; | ||||
|     const CBOR_MAJOR_FLOAT_SIMPLE = 7; | ||||
|     const CBOR_MAJOR_NEGATIVE_INT = 1; | ||||
|     const CBOR_MAJOR_ARRAY = 4; | ||||
|     const CBOR_MAJOR_TAG = 6; | ||||
|     const CBOR_MAJOR_MAP = 5; | ||||
|     const CBOR_MAJOR_BYTE_STRING = 2; | ||||
|  | ||||
|     /** | ||||
|      * @param ByteBuffer|string $bufOrBin | ||||
|      * @return mixed | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     public static function decode($bufOrBin) { | ||||
|         $buf = $bufOrBin instanceof ByteBuffer ? $bufOrBin : new ByteBuffer($bufOrBin); | ||||
|  | ||||
|         $offset = 0; | ||||
|         $result = self::_parseItem($buf, $offset); | ||||
|         if ($offset !== $buf->getLength()) { | ||||
|             throw new WebAuthnException('Unused bytes after data item.', WebAuthnException::CBOR); | ||||
|         } | ||||
|         return $result; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param ByteBuffer|string $bufOrBin | ||||
|      * @param int $startOffset | ||||
|      * @param int|null $endOffset | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public static function decodeInPlace($bufOrBin, $startOffset, &$endOffset = null) { | ||||
|         $buf = $bufOrBin instanceof ByteBuffer ? $bufOrBin : new ByteBuffer($bufOrBin); | ||||
|  | ||||
|         $offset = $startOffset; | ||||
|         $data = self::_parseItem($buf, $offset); | ||||
|         $endOffset = $offset; | ||||
|         return $data; | ||||
|     } | ||||
|  | ||||
|     // --------------------- | ||||
|     // protected | ||||
|     // --------------------- | ||||
|  | ||||
|     /** | ||||
|      * @param ByteBuffer $buf | ||||
|      * @param int $offset | ||||
|      * @return mixed | ||||
|      */ | ||||
|     protected static function _parseItem(ByteBuffer $buf, &$offset) { | ||||
|         $first = $buf->getByteVal($offset++); | ||||
|         $type = $first >> 5; | ||||
|         $val = $first & 0b11111; | ||||
|  | ||||
|         if ($type === self::CBOR_MAJOR_FLOAT_SIMPLE) { | ||||
|             return self::_parseFloatSimple($val, $buf, $offset); | ||||
|         } | ||||
|  | ||||
|         $val = self::_parseExtraLength($val, $buf, $offset); | ||||
|  | ||||
|         return self::_parseItemData($type, $val, $buf, $offset); | ||||
|     } | ||||
|  | ||||
|     protected static function _parseFloatSimple($val, ByteBuffer $buf, &$offset) { | ||||
|         switch ($val) { | ||||
|             case 24: | ||||
|                 $val = $buf->getByteVal($offset); | ||||
|                 $offset++; | ||||
|                 return self::_parseSimple($val); | ||||
|  | ||||
|             case 25: | ||||
|                 $floatValue = $buf->getHalfFloatVal($offset); | ||||
|                 $offset += 2; | ||||
|                 return $floatValue; | ||||
|  | ||||
|             case 26: | ||||
|                 $floatValue = $buf->getFloatVal($offset); | ||||
|                 $offset += 4; | ||||
|                 return $floatValue; | ||||
|  | ||||
|             case 27: | ||||
|                 $floatValue = $buf->getDoubleVal($offset); | ||||
|                 $offset += 8; | ||||
|                 return $floatValue; | ||||
|  | ||||
|             case 28: | ||||
|             case 29: | ||||
|             case 30: | ||||
|                 throw new WebAuthnException('Reserved value used.', WebAuthnException::CBOR); | ||||
|  | ||||
|             case 31: | ||||
|                 throw new WebAuthnException('Indefinite length is not supported.', WebAuthnException::CBOR); | ||||
|         } | ||||
|  | ||||
|         return self::_parseSimple($val); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param int $val | ||||
|      * @return mixed | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     protected static function _parseSimple($val) { | ||||
|         if ($val === 20) { | ||||
|             return false; | ||||
|         } | ||||
|         if ($val === 21) { | ||||
|             return true; | ||||
|         } | ||||
|         if ($val === 22) { | ||||
|             return null; | ||||
|         } | ||||
|         throw new WebAuthnException(sprintf('Unsupported simple value %d.', $val), WebAuthnException::CBOR); | ||||
|     } | ||||
|  | ||||
|     protected static function _parseExtraLength($val, ByteBuffer $buf, &$offset) { | ||||
|         switch ($val) { | ||||
|             case 24: | ||||
|                 $val = $buf->getByteVal($offset); | ||||
|                 $offset++; | ||||
|                 break; | ||||
|  | ||||
|             case 25: | ||||
|                 $val = $buf->getUint16Val($offset); | ||||
|                 $offset += 2; | ||||
|                 break; | ||||
|  | ||||
|             case 26: | ||||
|                 $val = $buf->getUint32Val($offset); | ||||
|                 $offset += 4; | ||||
|                 break; | ||||
|  | ||||
|             case 27: | ||||
|                 $val = $buf->getUint64Val($offset); | ||||
|                 $offset += 8; | ||||
|                 break; | ||||
|  | ||||
|             case 28: | ||||
|             case 29: | ||||
|             case 30: | ||||
|                 throw new WebAuthnException('Reserved value used.', WebAuthnException::CBOR); | ||||
|  | ||||
|             case 31: | ||||
|                 throw new WebAuthnException('Indefinite length is not supported.', WebAuthnException::CBOR); | ||||
|         } | ||||
|  | ||||
|         return $val; | ||||
|     } | ||||
|  | ||||
|     protected static function _parseItemData($type, $val, ByteBuffer $buf, &$offset) { | ||||
|         switch ($type) { | ||||
|             case self::CBOR_MAJOR_UNSIGNED_INT: // uint | ||||
|                 return $val; | ||||
|  | ||||
|             case self::CBOR_MAJOR_NEGATIVE_INT: | ||||
|                 return -1 - $val; | ||||
|  | ||||
|             case self::CBOR_MAJOR_BYTE_STRING: | ||||
|                 $data = $buf->getBytes($offset, $val); | ||||
|                 $offset += $val; | ||||
|                 return new ByteBuffer($data); // bytes | ||||
|  | ||||
|             case self::CBOR_MAJOR_TEXT_STRING: | ||||
|                 $data = $buf->getBytes($offset, $val); | ||||
|                 $offset += $val; | ||||
|                 return $data; // UTF-8 | ||||
|  | ||||
|             case self::CBOR_MAJOR_ARRAY: | ||||
|                 return self::_parseArray($buf, $offset, $val); | ||||
|  | ||||
|             case self::CBOR_MAJOR_MAP: | ||||
|                 return self::_parseMap($buf, $offset, $val); | ||||
|  | ||||
|             case self::CBOR_MAJOR_TAG: | ||||
|                 return self::_parseItem($buf, $offset); // 1 embedded data item | ||||
|         } | ||||
|  | ||||
|         // This should never be reached | ||||
|         throw new WebAuthnException(sprintf('Unknown major type %d.', $type), WebAuthnException::CBOR); | ||||
|     } | ||||
|  | ||||
|     protected static function _parseMap(ByteBuffer $buf, &$offset, $count) { | ||||
|         $map = array(); | ||||
|  | ||||
|         for ($i = 0; $i < $count; $i++) { | ||||
|             $mapKey = self::_parseItem($buf, $offset); | ||||
|             $mapVal = self::_parseItem($buf, $offset); | ||||
|  | ||||
|             if (!\is_int($mapKey) && !\is_string($mapKey)) { | ||||
|                 throw new WebAuthnException('Can only use strings or integers as map keys', WebAuthnException::CBOR); | ||||
|             } | ||||
|  | ||||
|             $map[$mapKey] = $mapVal; // todo dup | ||||
|         } | ||||
|         return $map; | ||||
|     } | ||||
|  | ||||
|     protected static function _parseArray(ByteBuffer $buf, &$offset, $count) { | ||||
|         $arr = array(); | ||||
|         for ($i = 0; $i < $count; $i++) { | ||||
|             $arr[] = self::_parseItem($buf, $offset); | ||||
|         } | ||||
|  | ||||
|         return $arr; | ||||
|     } | ||||
| } | ||||
| @@ -1,22 +0,0 @@ | ||||
| MIT License | ||||
|  | ||||
| Copyright © 2019 Lukas Buchs | ||||
| Copyright © 2018 Thomas Bleeker (CBOR & ByteBuffer part) | ||||
|  | ||||
| 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. | ||||
| @@ -1,487 +0,0 @@ | ||||
| <?php | ||||
|  | ||||
| namespace WebAuthn; | ||||
| use WebAuthn\Binary\ByteBuffer; | ||||
| require_once 'WebAuthnException.php'; | ||||
| require_once 'Binary/ByteBuffer.php'; | ||||
| require_once 'Attestation/AttestationObject.php'; | ||||
| require_once 'Attestation/AuthenticatorData.php'; | ||||
| require_once 'Attestation/Format/FormatBase.php'; | ||||
| require_once 'Attestation/Format/None.php'; | ||||
| require_once 'Attestation/Format/AndroidKey.php'; | ||||
| require_once 'Attestation/Format/AndroidSafetyNet.php'; | ||||
| require_once 'Attestation/Format/Packed.php'; | ||||
| require_once 'Attestation/Format/Tpm.php'; | ||||
| require_once 'Attestation/Format/U2f.php'; | ||||
| require_once 'CBOR/CborDecoder.php'; | ||||
|  | ||||
| /** | ||||
|  * WebAuthn | ||||
|  * @author Lukas Buchs | ||||
|  * @license https://github.com/lbuchs/WebAuthn/blob/master/LICENSE MIT | ||||
|  */ | ||||
| class WebAuthn { | ||||
|     // relying party | ||||
|     private $_rpName; | ||||
|     private $_rpId; | ||||
|     private $_rpIdHash; | ||||
|     private $_challenge; | ||||
|     private $_signatureCounter; | ||||
|     private $_caFiles; | ||||
|     private $_formats; | ||||
|  | ||||
|     /** | ||||
|      * Initialize a new WebAuthn server | ||||
|      * @param string $rpName the relying party name | ||||
|      * @param string $rpId the relying party ID = the domain name | ||||
|      * @param bool $useBase64UrlEncoding true to use base64 url encoding for binary data in json objects. Default is a RFC 1342-Like serialized string. | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     public function __construct($rpName, $rpId, $allowedFormats=null, $useBase64UrlEncoding=false) { | ||||
|         $this->_rpName = $rpName; | ||||
|         $this->_rpId = $rpId; | ||||
|         $this->_rpIdHash = \hash('sha256', $rpId, true); | ||||
|         ByteBuffer::$useBase64UrlEncoding = !!$useBase64UrlEncoding; | ||||
|  | ||||
|         if (!\function_exists('\openssl_open')) { | ||||
|             throw new WebAuthnException('OpenSSL-Module not installed');; | ||||
|         } | ||||
|  | ||||
|         if (!\in_array('SHA256', \array_map('\strtoupper', \openssl_get_md_methods()))) { | ||||
|             throw new WebAuthnException('SHA256 not supported by this openssl installation.'); | ||||
|         } | ||||
|  | ||||
|         // default value | ||||
|         if (!is_array($allowedFormats)) { | ||||
|             $allowedFormats = array('android-key', 'fido-u2f', 'packed', 'tpm'); | ||||
|         } | ||||
|         $this->_formats = $allowedFormats; | ||||
|  | ||||
|         // validate formats | ||||
|         $invalidFormats = \array_diff($this->_formats, array('android-key', 'android-safetynet', 'fido-u2f', 'none', 'packed', 'tpm')); | ||||
|         if (!$this->_formats || $invalidFormats) { | ||||
|             throw new WebAuthnException('Invalid formats on construct: ' . implode(', ', $invalidFormats)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * add a root certificate to verify new registrations | ||||
|      * @param string $path file path of / directory with root certificates | ||||
|      */ | ||||
|     public function addRootCertificates($path) { | ||||
|         if (!\is_array($this->_caFiles)) { | ||||
|             $this->_caFiles = array(); | ||||
|         } | ||||
|         $path = \rtrim(\trim($path), '\\/'); | ||||
|         if (\is_dir($path)) { | ||||
|             foreach (\scandir($path) as $ca) { | ||||
|                 if (\is_file($path . '/' . $ca)) { | ||||
|                     $this->addRootCertificates($path . '/' . $ca); | ||||
|                 } | ||||
|             } | ||||
|         } else if (\is_file($path) && !\in_array(\realpath($path), $this->_caFiles)) { | ||||
|             $this->_caFiles[] = \realpath($path); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the generated challenge to save for later validation | ||||
|      * @return ByteBuffer | ||||
|      */ | ||||
|     public function getChallenge() { | ||||
|         return $this->_challenge; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * generates the object for a key registration | ||||
|      * provide this data to navigator.credentials.create | ||||
|      * @param string $userId | ||||
|      * @param string $userName | ||||
|      * @param string $userDisplayName | ||||
|      * @param int $timeout timeout in seconds | ||||
|      * @param bool $requireResidentKey true, if the key should be stored by the authentication device | ||||
|      * @param bool|string $requireUserVerification indicates that you require user verification and will fail the operation | ||||
|      *                                             if the response does not have the UV flag set. | ||||
|      *                                             Valid values: | ||||
|      *                                             true = required | ||||
|      *                                             false = preferred | ||||
|      *                                             string 'required' 'preferred' 'discouraged' | ||||
|      * @param array $excludeCredentialIds a array of ids, which are already registered, to prevent re-registration | ||||
|      * @return \stdClass | ||||
|      */ | ||||
|     public function getCreateArgs($userId, $userName, $userDisplayName, $timeout=20, $requireResidentKey=false, $requireUserVerification=false, $excludeCredentialIds=array()) { | ||||
|  | ||||
|         // validate User Verification Requirement | ||||
|         if (\is_bool($requireUserVerification)) { | ||||
|             $requireUserVerification = $requireUserVerification ? 'required' : 'preferred'; | ||||
|         } else if (\is_string($requireUserVerification) && \in_array(\strtolower($requireUserVerification), ['required', 'preferred', 'discouraged'])) { | ||||
|             $requireUserVerification = \strtolower($requireUserVerification); | ||||
|         } else { | ||||
|             $requireUserVerification = 'preferred'; | ||||
|         } | ||||
|  | ||||
|         $args = new \stdClass(); | ||||
|         $args->publicKey = new \stdClass(); | ||||
|  | ||||
|         // relying party | ||||
|         $args->publicKey->rp = new \stdClass(); | ||||
|         $args->publicKey->rp->name = $this->_rpName; | ||||
|         $args->publicKey->rp->id = $this->_rpId; | ||||
|  | ||||
|         $args->publicKey->authenticatorSelection = new \stdClass(); | ||||
|         $args->publicKey->authenticatorSelection->userVerification = $requireUserVerification; | ||||
|         if ($requireResidentKey) { | ||||
|             $args->publicKey->authenticatorSelection->requireResidentKey = true; | ||||
|         } | ||||
|  | ||||
|         // user | ||||
|         $args->publicKey->user = new \stdClass(); | ||||
|         $args->publicKey->user->id = new ByteBuffer($userId); // binary | ||||
|         $args->publicKey->user->name = $userName; | ||||
|         $args->publicKey->user->displayName = $userDisplayName; | ||||
|  | ||||
|         $args->publicKey->pubKeyCredParams = array(); | ||||
|         $tmp = new \stdClass(); | ||||
|         $tmp->type = 'public-key'; | ||||
|         $tmp->alg = -7; // ES256 | ||||
|         $args->publicKey->pubKeyCredParams[] = $tmp; | ||||
|         unset ($tmp); | ||||
|  | ||||
|         $tmp = new \stdClass(); | ||||
|         $tmp->type = 'public-key'; | ||||
|         $tmp->alg = -257; // RS256 | ||||
|         $args->publicKey->pubKeyCredParams[] = $tmp; | ||||
|         unset ($tmp); | ||||
|  | ||||
|         // if there are root certificates added, we need direct attestation to validate | ||||
|         // against the root certificate. If there are no root-certificates added, | ||||
|         // anonymization ca are also accepted, because we can't validate the root anyway. | ||||
|         $attestation = 'indirect'; | ||||
|         if (\is_array($this->_caFiles)) { | ||||
|             $attestation = 'direct'; | ||||
|         } | ||||
|  | ||||
|         $args->publicKey->attestation = \count($this->_formats) === 1 && \in_array('none', $this->_formats) ? 'none' : $attestation; | ||||
|         $args->publicKey->extensions = new \stdClass(); | ||||
|         $args->publicKey->extensions->exts = true; | ||||
|         $args->publicKey->timeout = $timeout * 1000; // microseconds | ||||
|         $args->publicKey->challenge = $this->_createChallenge(); // binary | ||||
|  | ||||
|         //prevent re-registration by specifying existing credentials | ||||
|         $args->publicKey->excludeCredentials = array(); | ||||
|  | ||||
|         if (is_array($excludeCredentialIds)) { | ||||
|             foreach ($excludeCredentialIds as $id) { | ||||
|                 $tmp = new \stdClass(); | ||||
|                 $tmp->id = $id instanceof ByteBuffer ? $id : new ByteBuffer($id);  // binary | ||||
|                 $tmp->type = 'public-key'; | ||||
|                 $tmp->transports = array('usb', 'ble', 'nfc', 'internal'); | ||||
|                 $args->publicKey->excludeCredentials[] = $tmp; | ||||
|                 unset ($tmp); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return $args; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * generates the object for key validation | ||||
|      * Provide this data to navigator.credentials.get | ||||
|      * @param array $credentialIds binary | ||||
|      * @param int $timeout timeout in seconds | ||||
|      * @param bool $allowUsb allow removable USB | ||||
|      * @param bool $allowNfc allow Near Field Communication (NFC) | ||||
|      * @param bool $allowBle allow Bluetooth | ||||
|      * @param bool $allowInternal allow client device-specific transport. These authenticators are not removable from the client device. | ||||
|      * @param bool|string $requireUserVerification indicates that you require user verification and will fail the operation | ||||
|      *                                             if the response does not have the UV flag set. | ||||
|      *                                             Valid values: | ||||
|      *                                             true = required | ||||
|      *                                             false = preferred | ||||
|      *                                             string 'required' 'preferred' 'discouraged' | ||||
|      * @return \stdClass | ||||
|      */ | ||||
|     public function getGetArgs($credentialIds=array(), $timeout=20, $allowUsb=true, $allowNfc=true, $allowBle=true, $allowInternal=true, $requireUserVerification=false) { | ||||
|  | ||||
|         // validate User Verification Requirement | ||||
|         if (\is_bool($requireUserVerification)) { | ||||
|             $requireUserVerification = $requireUserVerification ? 'required' : 'preferred'; | ||||
|         } else if (\is_string($requireUserVerification) && \in_array(\strtolower($requireUserVerification), ['required', 'preferred', 'discouraged'])) { | ||||
|             $requireUserVerification = \strtolower($requireUserVerification); | ||||
|         } else { | ||||
|             $requireUserVerification = 'preferred'; | ||||
|         } | ||||
|  | ||||
|         $args = new \stdClass(); | ||||
|         $args->publicKey = new \stdClass(); | ||||
|         $args->publicKey->timeout = $timeout * 1000; // microseconds | ||||
|         $args->publicKey->challenge = $this->_createChallenge();  // binary | ||||
|         $args->publicKey->userVerification = $requireUserVerification; | ||||
|         $args->publicKey->rpId = $this->_rpId; | ||||
|  | ||||
|         if (\is_array($credentialIds) && \count($credentialIds) > 0) { | ||||
|             $args->publicKey->allowCredentials = array(); | ||||
|  | ||||
|             foreach ($credentialIds as $id) { | ||||
|                 $tmp = new \stdClass(); | ||||
|                 $tmp->id = $id instanceof ByteBuffer ? $id : new ByteBuffer($id);  // binary | ||||
|                 $tmp->transports = array(); | ||||
|  | ||||
|                 if ($allowUsb) { | ||||
|                     $tmp->transports[] = 'usb'; | ||||
|                 } | ||||
|                 if ($allowNfc) { | ||||
|                     $tmp->transports[] = 'nfc'; | ||||
|                 } | ||||
|                 if ($allowBle) { | ||||
|                     $tmp->transports[] = 'ble'; | ||||
|                 } | ||||
|                 if ($allowInternal) { | ||||
|                     $tmp->transports[] = 'internal'; | ||||
|                 } | ||||
|  | ||||
|                 $tmp->type = 'public-key'; | ||||
|                 $args->publicKey->allowCredentials[] = $tmp; | ||||
|                 unset ($tmp); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return $args; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * returns the new signature counter value. | ||||
|      * returns null if there is no counter | ||||
|      * @return ?int | ||||
|      */ | ||||
|     public function getSignatureCounter() { | ||||
|         return \is_int($this->_signatureCounter) ? $this->_signatureCounter : null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * process a create request and returns data to save for future logins | ||||
|      * @param string $clientDataJSON binary from browser | ||||
|      * @param string $attestationObject binary from browser | ||||
|      * @param string|ByteBuffer $challenge binary used challange | ||||
|      * @param bool $requireUserVerification true, if the device must verify user (e.g. by biometric data or pin) | ||||
|      * @param bool $requireUserPresent false, if the device must NOT check user presence (e.g. by pressing a button) | ||||
|      * @return \stdClass | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     public function processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true) { | ||||
|         $clientDataHash = \hash('sha256', $clientDataJSON, true); | ||||
|         $clientData = \json_decode($clientDataJSON); | ||||
|         $challenge = $challenge instanceof ByteBuffer ? $challenge : new ByteBuffer($challenge); | ||||
|  | ||||
|         // security: https://www.w3.org/TR/webauthn/#registering-a-new-credential | ||||
|  | ||||
|         // 2. Let C, the client data claimed as collected during the credential creation, | ||||
|         //    be the result of running an implementation-specific JSON parser on JSONtext. | ||||
|         if (!\is_object($clientData)) { | ||||
|             throw new WebAuthnException('Invalid client data', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         // 3. Verify that the value of C.type is webauthn.create. | ||||
|         if (!\property_exists($clientData, 'type') || $clientData->type !== 'webauthn.create') { | ||||
|             throw new WebAuthnException('Invalid type', WebAuthnException::INVALID_TYPE); | ||||
|         } | ||||
|  | ||||
|         // 4. Verify that the value of C.challenge matches the challenge that was sent to the authenticator in the create() call. | ||||
|         if (!\property_exists($clientData, 'challenge') || ByteBuffer::fromBase64Url($clientData->challenge)->getBinaryString() !== $challenge->getBinaryString()) { | ||||
|             throw new WebAuthnException('Invalid challenge', WebAuthnException::INVALID_CHALLENGE); | ||||
|         } | ||||
|  | ||||
|         // 5. Verify that the value of C.origin matches the Relying Party's origin. | ||||
|         if (!\property_exists($clientData, 'origin') || !$this->_checkOrigin($clientData->origin)) { | ||||
|             throw new WebAuthnException('Invalid origin', WebAuthnException::INVALID_ORIGIN); | ||||
|         } | ||||
|  | ||||
|         // Attestation | ||||
|         $attestationObject = new Attestation\AttestationObject($attestationObject, $this->_formats); | ||||
|  | ||||
|         // 9. Verify that the RP ID hash in authData is indeed the SHA-256 hash of the RP ID expected by the RP. | ||||
|         if (!$attestationObject->validateRpIdHash($this->_rpIdHash)) { | ||||
|             throw new WebAuthnException('Invalid rpId hash', WebAuthnException::INVALID_RELYING_PARTY); | ||||
|         } | ||||
|  | ||||
|         // 14. Verify that attStmt is a correct attestation statement, conveying a valid attestation signature | ||||
|         if (!$attestationObject->validateAttestation($clientDataHash)) { | ||||
|             throw new WebAuthnException('Invalid certificate signature', WebAuthnException::INVALID_SIGNATURE); | ||||
|         } | ||||
|  | ||||
|         // 15. If validation is successful, obtain a list of acceptable trust anchors | ||||
|         if (is_array($this->_caFiles) && !$attestationObject->validateRootCertificate($this->_caFiles)) { | ||||
|             throw new WebAuthnException('Invalid root certificate', WebAuthnException::CERTIFICATE_NOT_TRUSTED); | ||||
|         } | ||||
|  | ||||
|         // 10. Verify that the User Present bit of the flags in authData is set. | ||||
|         if ($requireUserPresent && !$attestationObject->getAuthenticatorData()->getUserPresent()) { | ||||
|             throw new WebAuthnException('User not present during authentication', WebAuthnException::USER_PRESENT); | ||||
|         } | ||||
|  | ||||
|         // 11. If user verification is required for this registration, verify that the User Verified bit of the flags in authData is set. | ||||
|         if ($requireUserVerification && !$attestationObject->getAuthenticatorData()->getUserVerified()) { | ||||
|             throw new WebAuthnException('User not verificated during authentication', WebAuthnException::USER_VERIFICATED); | ||||
|         } | ||||
|  | ||||
|         $signCount = $attestationObject->getAuthenticatorData()->getSignCount(); | ||||
|         if ($signCount > 0) { | ||||
|             $this->_signatureCounter = $signCount; | ||||
|         } | ||||
|  | ||||
|         // prepare data to store for future logins | ||||
|         $data = new \stdClass(); | ||||
|         $data->rpId = $this->_rpId; | ||||
|         $data->credentialId = $attestationObject->getAuthenticatorData()->getCredentialId(); | ||||
|         $data->credentialPublicKey = $attestationObject->getAuthenticatorData()->getPublicKeyPem(); | ||||
|         $data->certificateChain = $attestationObject->getCertificateChain(); | ||||
|         $data->certificate = $attestationObject->getCertificatePem(); | ||||
|         $data->certificateIssuer = $attestationObject->getCertificateIssuer(); | ||||
|         $data->certificateSubject = $attestationObject->getCertificateSubject(); | ||||
|         $data->signatureCounter = $this->_signatureCounter; | ||||
|         $data->AAGUID = $attestationObject->getAuthenticatorData()->getAAGUID(); | ||||
|         return $data; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * process a get request | ||||
|      * @param string $clientDataJSON binary from browser | ||||
|      * @param string $authenticatorData binary from browser | ||||
|      * @param string $signature binary from browser | ||||
|      * @param string $credentialPublicKey string PEM-formated public key from used credentialId | ||||
|      * @param string|ByteBuffer $challenge  binary from used challange | ||||
|      * @param int $prevSignatureCnt signature count value of the last login | ||||
|      * @param bool $requireUserVerification true, if the device must verify user (e.g. by biometric data or pin) | ||||
|      * @param bool $requireUserPresent true, if the device must check user presence (e.g. by pressing a button) | ||||
|      * @return boolean true if get is successful | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     public function processGet($clientDataJSON, $authenticatorData, $signature, $credentialPublicKey, $challenge, $prevSignatureCnt=null, $requireUserVerification=false, $requireUserPresent=true) { | ||||
|         $authenticatorObj = new Attestation\AuthenticatorData($authenticatorData); | ||||
|         $clientDataHash = \hash('sha256', $clientDataJSON, true); | ||||
|         $clientData = \json_decode($clientDataJSON); | ||||
|         $challenge = $challenge instanceof ByteBuffer ? $challenge : new ByteBuffer($challenge); | ||||
|  | ||||
|         // https://www.w3.org/TR/webauthn/#verifying-assertion | ||||
|  | ||||
|         // 1. If the allowCredentials option was given when this authentication ceremony was initiated, | ||||
|         //    verify that credential.id identifies one of the public key credentials that were listed in allowCredentials. | ||||
|         //    -> TO BE VERIFIED BY IMPLEMENTATION | ||||
|  | ||||
|         // 2. If credential.response.userHandle is present, verify that the user identified | ||||
|         //    by this value is the owner of the public key credential identified by credential.id. | ||||
|         //    -> TO BE VERIFIED BY IMPLEMENTATION | ||||
|  | ||||
|         // 3. Using credential’s id attribute (or the corresponding rawId, if base64url encoding is | ||||
|         //    inappropriate for your use case), look up the corresponding credential public key. | ||||
|         //    -> TO BE LOOKED UP BY IMPLEMENTATION | ||||
|  | ||||
|         // 5. Let JSONtext be the result of running UTF-8 decode on the value of cData. | ||||
|         if (!\is_object($clientData)) { | ||||
|             throw new WebAuthnException('Invalid client data', WebAuthnException::INVALID_DATA); | ||||
|         } | ||||
|  | ||||
|         // 7. Verify that the value of C.type is the string webauthn.get. | ||||
|         if (!\property_exists($clientData, 'type') || $clientData->type !== 'webauthn.get') { | ||||
|             throw new WebAuthnException('Invalid type', WebAuthnException::INVALID_TYPE); | ||||
|         } | ||||
|  | ||||
|         // 8. Verify that the value of C.challenge matches the challenge that was sent to the | ||||
|         //    authenticator in the PublicKeyCredentialRequestOptions passed to the get() call. | ||||
|         if (!\property_exists($clientData, 'challenge') || ByteBuffer::fromBase64Url($clientData->challenge)->getBinaryString() !== $challenge->getBinaryString()) { | ||||
|             throw new WebAuthnException('Invalid challenge', WebAuthnException::INVALID_CHALLENGE); | ||||
|         } | ||||
|  | ||||
|         // 9. Verify that the value of C.origin matches the Relying Party's origin. | ||||
|         if (!\property_exists($clientData, 'origin') || !$this->_checkOrigin($clientData->origin)) { | ||||
|             throw new WebAuthnException('Invalid origin', WebAuthnException::INVALID_ORIGIN); | ||||
|         } | ||||
|  | ||||
|         // 11. Verify that the rpIdHash in authData is the SHA-256 hash of the RP ID expected by the Relying Party. | ||||
|         if ($authenticatorObj->getRpIdHash() !== $this->_rpIdHash) { | ||||
|             throw new WebAuthnException('Invalid rpId hash', WebAuthnException::INVALID_RELYING_PARTY); | ||||
|         } | ||||
|  | ||||
|         // 12. Verify that the User Present bit of the flags in authData is set | ||||
|         if ($requireUserPresent && !$authenticatorObj->getUserPresent()) { | ||||
|             throw new WebAuthnException('User not present during authentication', WebAuthnException::USER_PRESENT); | ||||
|         } | ||||
|  | ||||
|         // 13. If user verification is required for this assertion, verify that the User Verified bit of the flags in authData is set. | ||||
|         if ($requireUserVerification && !$authenticatorObj->getUserVerified()) { | ||||
|             throw new WebAuthnException('User not verificated during authentication', WebAuthnException::USER_VERIFICATED); | ||||
|         } | ||||
|  | ||||
|         // 14. Verify the values of the client extension outputs | ||||
|         //     (extensions not implemented) | ||||
|  | ||||
|         // 16. Using the credential public key looked up in step 3, verify that sig is a valid signature | ||||
|         //     over the binary concatenation of authData and hash. | ||||
|         $dataToVerify = ''; | ||||
|         $dataToVerify .= $authenticatorData; | ||||
|         $dataToVerify .= $clientDataHash; | ||||
|  | ||||
|         $publicKey = \openssl_pkey_get_public($credentialPublicKey); | ||||
|         if ($publicKey === false) { | ||||
|             throw new WebAuthnException('public key invalid', WebAuthnException::INVALID_PUBLIC_KEY); | ||||
|         } | ||||
|  | ||||
|         if (\openssl_verify($dataToVerify, $signature, $publicKey, OPENSSL_ALGO_SHA256) !== 1) { | ||||
|             throw new WebAuthnException('Invalid signature', WebAuthnException::INVALID_SIGNATURE); | ||||
|         } | ||||
|  | ||||
|         // 17. If the signature counter value authData.signCount is nonzero, | ||||
|         //     if less than or equal to the signature counter value stored, | ||||
|         //     is a signal that the authenticator may be cloned | ||||
|         $signatureCounter = $authenticatorObj->getSignCount(); | ||||
|         if ($signatureCounter > 0) { | ||||
|             $this->_signatureCounter = $signatureCounter; | ||||
|             if ($prevSignatureCnt !== null && $prevSignatureCnt >= $signatureCounter) { | ||||
|                 throw new WebAuthnException('signature counter not valid', WebAuthnException::SIGNATURE_COUNTER); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     // ----------------------------------------------- | ||||
|     // PRIVATE | ||||
|     // ----------------------------------------------- | ||||
|  | ||||
|     /** | ||||
|      * checks if the origin matchs the RP ID | ||||
|      * @param string $origin | ||||
|      * @return boolean | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     private function _checkOrigin($origin) { | ||||
|         // https://www.w3.org/TR/webauthn/#rp-id | ||||
|  | ||||
|         // The origin's scheme must be https | ||||
|         if ($this->_rpId !== 'localhost' && \parse_url($origin, PHP_URL_SCHEME) !== 'https') { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         // extract host from origin | ||||
|         $host = \parse_url($origin, PHP_URL_HOST); | ||||
|         $host = \trim($host, '.'); | ||||
|  | ||||
|         // The RP ID must be equal to the origin's effective domain, or a registrable | ||||
|         // domain suffix of the origin's effective domain. | ||||
|         return \preg_match('/' . \preg_quote($this->_rpId) . '$/i', $host) === 1; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * generates a new challange | ||||
|      * @param int $length | ||||
|      * @return string | ||||
|      * @throws WebAuthnException | ||||
|      */ | ||||
|     private function _createChallenge($length = 32) { | ||||
|         if (!$this->_challenge) { | ||||
|             $this->_challenge = ByteBuffer::randomBuffer($length); | ||||
|         } | ||||
|         return $this->_challenge; | ||||
|     } | ||||
| } | ||||
| @@ -1,27 +0,0 @@ | ||||
| <?php | ||||
| namespace WebAuthn; | ||||
|  | ||||
| /** | ||||
|  * @author Lukas Buchs | ||||
|  * @license https://github.com/lbuchs/WebAuthn/blob/master/LICENSE MIT | ||||
|  */ | ||||
| class WebAuthnException extends \Exception { | ||||
|     const INVALID_DATA = 1; | ||||
|     const INVALID_TYPE = 2; | ||||
|     const INVALID_CHALLENGE = 3; | ||||
|     const INVALID_ORIGIN = 4; | ||||
|     const INVALID_RELYING_PARTY = 5; | ||||
|     const INVALID_SIGNATURE = 6; | ||||
|     const INVALID_PUBLIC_KEY = 7; | ||||
|     const CERTIFICATE_NOT_TRUSTED = 8; | ||||
|     const USER_PRESENT = 9; | ||||
|     const USER_VERIFICATED = 10; | ||||
|     const SIGNATURE_COUNTER = 11; | ||||
|     const CRYPTO_STRONG = 13; | ||||
|     const BYTEBUFFER = 14; | ||||
|     const CBOR = 15; | ||||
|  | ||||
|     public function __construct($message = "", $code = 0, $previous = null) { | ||||
|         parent::__construct($message, $code, $previous); | ||||
|     } | ||||
| } | ||||
| @@ -1,48 +0,0 @@ | ||||
|     Certificate: | ||||
|     Data: | ||||
|         Version: 3 (0x2) | ||||
|         Serial Number: | ||||
|             68:1d:01:6c:7a:3c:e3:02:25:a5:01:94:28:47:57:71 | ||||
|  | ||||
|     Signature Algorithm: ecdsa-with-SHA384 | ||||
|  | ||||
|         Issuer: | ||||
|             stateOrProvinceName       = California | ||||
|             organizationName          = Apple Inc. | ||||
|             commonName                = Apple WebAuthn Root CA | ||||
|  | ||||
|         Validity | ||||
|             Not Before: Mar 18 18:21:32 2020 GMT | ||||
|             Not After : Mar 15 00:00:00 2045 GMT | ||||
|  | ||||
|         Subject: | ||||
|             stateOrProvinceName       = California | ||||
|             organizationName          = Apple Inc. | ||||
|             commonName                = Apple WebAuthn Root CA | ||||
|  | ||||
|         Subject Public Key Info: | ||||
|             Public Key Algorithm: id-ecPublicKey | ||||
|                 ASN1 OID: secp384r1 | ||||
|  | ||||
|         X509v3 extensions: | ||||
|             X509v3 Basic Constraints: critical | ||||
|                 CA:TRUE | ||||
|             X509v3 Subject Key Identifier: | ||||
|                 26:D7:64:D9:C5:78:C2:5A:67:D1:A7:DE:6B:12:D0:1B:63:F1:C6:D7 | ||||
|             X509v3 Key Usage: critical | ||||
|                 Certificate Sign, CRL Sign | ||||
|  | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIICEjCCAZmgAwIBAgIQaB0BbHo84wIlpQGUKEdXcTAKBggqhkjOPQQDAzBLMR8w | ||||
| HQYDVQQDDBZBcHBsZSBXZWJBdXRobiBSb290IENBMRMwEQYDVQQKDApBcHBsZSBJ | ||||
| bmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMB4XDTIwMDMxODE4MjEzMloXDTQ1MDMx | ||||
| NTAwMDAwMFowSzEfMB0GA1UEAwwWQXBwbGUgV2ViQXV0aG4gUm9vdCBDQTETMBEG | ||||
| A1UECgwKQXBwbGUgSW5jLjETMBEGA1UECAwKQ2FsaWZvcm5pYTB2MBAGByqGSM49 | ||||
| AgEGBSuBBAAiA2IABCJCQ2pTVhzjl4Wo6IhHtMSAzO2cv+H9DQKev3//fG59G11k | ||||
| xu9eI0/7o6V5uShBpe1u6l6mS19S1FEh6yGljnZAJ+2GNP1mi/YK2kSXIuTHjxA/ | ||||
| pcoRf7XkOtO4o1qlcaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUJtdk | ||||
| 2cV4wlpn0afeaxLQG2PxxtcwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA | ||||
| MGQCMFrZ+9DsJ1PW9hfNdBywZDsWDbWFp28it1d/5w2RPkRX3Bbn/UbDTNLx7Jr3 | ||||
| jAGGiQIwHFj+dJZYUJR786osByBelJYsVZd2GbHQu209b5RCmGQ21gpSAk9QZW4B | ||||
| 1bWeT0vT | ||||
| -----END CERTIFICATE----- | ||||
| @@ -1,37 +0,0 @@ | ||||
| Certificate: | ||||
|     Data: | ||||
|         Version: 3 (0x2) | ||||
|         Serial Number: | ||||
|             04:00:00:00:00:01:0f:86:26:e6:0d | ||||
|     Signature Algorithm: sha1WithRSAEncryption | ||||
|         Issuer: OU=GlobalSign Root CA - R2, O=GlobalSign, CN=GlobalSign | ||||
|         Validity | ||||
|             Not Before: Dec 15 08:00:00 2006 GMT | ||||
|             Not After : Dec 15 08:00:00 2021 GMT | ||||
|         Subject: OU=GlobalSign Root CA - R2, O=GlobalSign, CN=GlobalSign | ||||
|         Subject Public Key Info: | ||||
|             Public Key Algorithm: rsaEncryption | ||||
|                 Public-Key: (2048 bit) | ||||
|  | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G | ||||
| A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp | ||||
| Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 | ||||
| MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG | ||||
| A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI | ||||
| hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL | ||||
| v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 | ||||
| eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq | ||||
| tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd | ||||
| C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa | ||||
| zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB | ||||
| mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH | ||||
| V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n | ||||
| bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG | ||||
| 3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs | ||||
| J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO | ||||
| 291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS | ||||
| ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd | ||||
| AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 | ||||
| TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== | ||||
| -----END CERTIFICATE----- | ||||
| @@ -1,130 +0,0 @@ | ||||
| Google Hardware Attestation Root certificate | ||||
| ---------------------------------------------- | ||||
|  | ||||
| https://developer.android.com/training/articles/security-key-attestation.html | ||||
|  | ||||
| Certificate: | ||||
|     Data: | ||||
|         Version: 3 (0x2) | ||||
|         Serial Number: | ||||
|             e8:fa:19:63:14:d2:fa:18 | ||||
|     Signature Algorithm: sha256WithRSAEncryption | ||||
|         Issuer: serialNumber = f92009e853b6b045 | ||||
|         Validity | ||||
|             Not Before: May 26 16:28:52 2016 GMT | ||||
|             Not After : May 24 16:28:52 2026 GMT | ||||
|         Subject: serialNumber = f92009e853b6b045 | ||||
|         Subject Public Key Info: | ||||
|             Public Key Algorithm: rsaEncryption | ||||
|                 Public-Key: (4096 bit) | ||||
|                 Exponent: 65537 (0x10001) | ||||
|         X509v3 extensions: | ||||
|             X509v3 Subject Key Identifier: | ||||
|                 36:61:E1:00:7C:88:05:09:51:8B:44:6C:47:FF:1A:4C:C9:EA:4F:12 | ||||
|             X509v3 Authority Key Identifier: | ||||
|                 keyid:36:61:E1:00:7C:88:05:09:51:8B:44:6C:47:FF:1A:4C:C9:EA:4F:12 | ||||
|  | ||||
|             X509v3 Basic Constraints: critical | ||||
|                 CA:TRUE | ||||
|             X509v3 Key Usage: critical | ||||
|                 Digital Signature, Certificate Sign, CRL Sign | ||||
|             X509v3 CRL Distribution Points: | ||||
|  | ||||
|                 Full Name: | ||||
|                   URI:https://android.googleapis.com/attestation/crl/ | ||||
|  | ||||
|     Signature Algorithm: sha256WithRSAEncryption | ||||
|  | ||||
|  | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIFYDCCA0igAwIBAgIJAOj6GWMU0voYMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV | ||||
| BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTYwNTI2MTYyODUyWhcNMjYwNTI0MTYy | ||||
| ODUyWjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B | ||||
| AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS | ||||
| Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7 | ||||
| tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj | ||||
| nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq | ||||
| C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ | ||||
| oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O | ||||
| JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg | ||||
| sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi | ||||
| igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M | ||||
| RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E | ||||
| aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um | ||||
| AGMCAwEAAaOBpjCBozAdBgNVHQ4EFgQUNmHhAHyIBQlRi0RsR/8aTMnqTxIwHwYD | ||||
| VR0jBBgwFoAUNmHhAHyIBQlRi0RsR/8aTMnqTxIwDwYDVR0TAQH/BAUwAwEB/zAO | ||||
| BgNVHQ8BAf8EBAMCAYYwQAYDVR0fBDkwNzA1oDOgMYYvaHR0cHM6Ly9hbmRyb2lk | ||||
| Lmdvb2dsZWFwaXMuY29tL2F0dGVzdGF0aW9uL2NybC8wDQYJKoZIhvcNAQELBQAD | ||||
| ggIBACDIw41L3KlXG0aMiS//cqrG+EShHUGo8HNsw30W1kJtjn6UBwRM6jnmiwfB | ||||
| Pb8VA91chb2vssAtX2zbTvqBJ9+LBPGCdw/E53Rbf86qhxKaiAHOjpvAy5Y3m00m | ||||
| qC0w/Zwvju1twb4vhLaJ5NkUJYsUS7rmJKHHBnETLi8GFqiEsqTWpG/6ibYCv7rY | ||||
| DBJDcR9W62BW9jfIoBQcxUCUJouMPH25lLNcDc1ssqvC2v7iUgI9LeoM1sNovqPm | ||||
| QUiG9rHli1vXxzCyaMTjwftkJLkf6724DFhuKug2jITV0QkXvaJWF4nUaHOTNA4u | ||||
| JU9WDvZLI1j83A+/xnAJUucIv/zGJ1AMH2boHqF8CY16LpsYgBt6tKxxWH00XcyD | ||||
| CdW2KlBCeqbQPcsFmWyWugxdcekhYsAWyoSf818NUsZdBWBaR/OukXrNLfkQ79Iy | ||||
| ZohZbvabO/X+MVT3rriAoKc8oE2Uws6DF+60PV7/WIPjNvXySdqspImSN78mflxD | ||||
| qwLqRBYkA3I75qppLGG9rp7UCdRjxMl8ZDBld+7yvHVgt1cVzJx9xnyGCC23Uaic | ||||
| MDSXYrB4I4WHXPGjxhZuCuPBLTdOLU8YRvMYdEvYebWHMpvwGCF6bAx3JBpIeOQ1 | ||||
| wDB5y0USicV3YgYGmi+NZfhA4URSh77Yd6uuJOJENRaNVTzk | ||||
| -----END CERTIFICATE----- | ||||
|  | ||||
|  | ||||
|     Certificate: | ||||
|     Data: | ||||
|         Version: 3 (0x2) | ||||
|         Serial Number: 15352756130135856819 (0xd50ff25ba3f2d6b3) | ||||
|     Signature Algorithm: sha256WithRSAEncryption | ||||
|         Issuer: | ||||
|             serialNumber              = f92009e853b6b045 | ||||
|         Validity | ||||
|             Not Before: Nov 22 20:37:58 2019 GMT | ||||
|             Not After : Nov 18 20:37:58 2034 GMT | ||||
|         Subject: | ||||
|             serialNumber              = f92009e853b6b045 | ||||
|         Subject Public Key Info: | ||||
|             Public Key Algorithm: rsaEncryption | ||||
|                 Public-Key: (4096 bit) | ||||
|                 Exponent: 65537 (0x10001) | ||||
|         X509v3 extensions: | ||||
|             X509v3 Subject Key Identifier: | ||||
|                 36:61:E1:00:7C:88:05:09:51:8B:44:6C:47:FF:1A:4C:C9:EA:4F:12 | ||||
|             X509v3 Authority Key Identifier: | ||||
|                 keyid:36:61:E1:00:7C:88:05:09:51:8B:44:6C:47:FF:1A:4C:C9:EA:4F:12 | ||||
|  | ||||
|             X509v3 Basic Constraints: critical | ||||
|                 CA:TRUE | ||||
|             X509v3 Key Usage: critical | ||||
|                 Certificate Sign | ||||
|     Signature Algorithm: sha256WithRSAEncryption | ||||
|  | ||||
|  | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIFHDCCAwSgAwIBAgIJANUP8luj8tazMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV | ||||
| BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTkxMTIyMjAzNzU4WhcNMzQxMTE4MjAz | ||||
| NzU4WjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B | ||||
| AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS | ||||
| Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7 | ||||
| tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj | ||||
| nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq | ||||
| C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ | ||||
| oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O | ||||
| JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg | ||||
| sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi | ||||
| igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M | ||||
| RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E | ||||
| aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um | ||||
| AGMCAwEAAaNjMGEwHQYDVR0OBBYEFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMB8GA1Ud | ||||
| IwQYMBaAFDZh4QB8iAUJUYtEbEf/GkzJ6k8SMA8GA1UdEwEB/wQFMAMBAf8wDgYD | ||||
| VR0PAQH/BAQDAgIEMA0GCSqGSIb3DQEBCwUAA4ICAQBOMaBc8oumXb2voc7XCWnu | ||||
| XKhBBK3e2KMGz39t7lA3XXRe2ZLLAkLM5y3J7tURkf5a1SutfdOyXAmeE6SRo83U | ||||
| h6WszodmMkxK5GM4JGrnt4pBisu5igXEydaW7qq2CdC6DOGjG+mEkN8/TA6p3cno | ||||
| L/sPyz6evdjLlSeJ8rFBH6xWyIZCbrcpYEJzXaUOEaxxXxgYz5/cTiVKN2M1G2ok | ||||
| QBUIYSY6bjEL4aUN5cfo7ogP3UvliEo3Eo0YgwuzR2v0KR6C1cZqZJSTnghIC/vA | ||||
| D32KdNQ+c3N+vl2OTsUVMC1GiWkngNx1OO1+kXW+YTnnTUOtOIswUP/Vqd5SYgAI | ||||
| mMAfY8U9/iIgkQj6T2W6FsScy94IN9fFhE1UtzmLoBIuUFsVXJMTz+Jucth+IqoW | ||||
| Fua9v1R93/k98p41pjtFX+H8DslVgfP097vju4KDlqN64xV1grw3ZLl4CiOe/A91 | ||||
| oeLm2UHOq6wn3esB4r2EIQKb6jTVGu5sYCcdWpXr0AUVqcABPdgL+H7qJguBw09o | ||||
| jm6xNIrw2OocrDKsudk/okr/AwqEyPKw9WnMlQgLIKw1rODG2NvU9oR3GVGdMkUB | ||||
| ZutL8VuFkERQGt6vQ2OCw0sV47VMkuYbacK/xyZFiRcrPJPb41zgbQj9XAEyLKCH | ||||
| ex0SdDrx+tWUDqG8At2JHA== | ||||
| -----END CERTIFICATE----- | ||||
| @@ -1,31 +0,0 @@ | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIFZDCCA0ygAwIBAgIIYsLLTehAXpYwDQYJKoZIhvcNAQELBQAwUDELMAkGA1UE | ||||
| BhMCQ04xDzANBgNVBAoMBkh1YXdlaTETMBEGA1UECwwKSHVhd2VpIENCRzEbMBkG | ||||
| A1UEAwwSSHVhd2VpIENCRyBSb290IENBMB4XDTE3MDgyMTEwNTYyN1oXDTQyMDgx | ||||
| NTEwNTYyN1owUDELMAkGA1UEBhMCQ04xDzANBgNVBAoMBkh1YXdlaTETMBEGA1UE | ||||
| CwwKSHVhd2VpIENCRzEbMBkGA1UEAwwSSHVhd2VpIENCRyBSb290IENBMIICIjAN | ||||
| BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1OyKm3Ig/6eibB7Uz2o93UqGk2M7 | ||||
| 84WdfF8mvffvu218d61G5M3Px54E3kefUTk5Ky1ywHvw7Rp9KDuYv7ktaHkk+yr5 | ||||
| 9Ihseu3a7iM/C6SnMSGt+LfB/Bcob9Abw95EigXQ4yQddX9hbNrin3AwZw8wMjEI | ||||
| SYYDo5GuYDL0NbAiYg2Y5GpfYIqRzoi6GqDz+evLrsl20kJeCEPgJZN4Jg00Iq9k | ||||
| ++EKOZ5Jc/Zx22ZUgKpdwKABkvzshEgG6WWUPB+gosOiLv++inu/9blDpEzQZhjZ | ||||
| 9WVHpURHDK1YlCvubVAMhDpnbqNHZ0AxlPletdoyugrH/OLKl5inhMXNj3Re7Hl8 | ||||
| WsBWLUKp6sXFf0dvSFzqnr2jkhicS+K2IYZnjghC9cOBRO8fnkonh0EBt0evjUIK | ||||
| r5ClbCKioBX8JU+d4ldtWOpp2FlxeFTLreDJ5ZBU4//bQpTwYMt7gwMK+MO5Wtok | ||||
| Ux3UF98Z6GdUgbl6nBjBe82c7oIQXhHGHPnURQO7DDPgyVnNOnTPIkmiHJh/e3vk | ||||
| VhiZNHFCCLTip6GoJVrLxwb9i4q+d0thw4doxVJ5NB9OfDMV64/ybJgpf7m3Ld2y | ||||
| E0gsf1prrRlDFDXjlYyqqpf1l9Y0u3ctXo7UpXMgbyDEpUQhq3a7txZQO/17luTD | ||||
| oA6Tz1ADavvBwHkCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF | ||||
| MAMBAf8wHQYDVR0OBBYEFKrE03lH6G4ja+/wqWwicz16GWmhMA0GCSqGSIb3DQEB | ||||
| CwUAA4ICAQC1d3TMB+VHZdGrWJbfaBShFNiCTN/MceSHOpzBn6JumQP4N7mxCOwd | ||||
| RSsGKQxV2NPH7LTXWNhUvUw5Sek96FWx/+Oa7jsj3WNAVtmS3zKpCQ5iGb08WIRO | ||||
| cFnx3oUQ5rcO8r/lUk7Q2cN0E+rF4xsdQrH9k2cd3kAXZXBjfxfKPJTdPy1XnZR/ | ||||
| h8H5EwEK5DWjSzK1wKd3G/Fxdm3E23pcr4FZgdYdOlFSiqW2TJ3Qe6lF4GOKOOyd | ||||
| WHkpu54ieTsqoYcuMKnKMjT2SLNNgv9Gu5ipaG8Olz6g9C7Htp943lmK/1Vtnhgg | ||||
| pL3rDTsFX/+ehk7OtxuNzRMD9lXUtEfok7f8XB0dcL4ZjnEhDmp5QZqC1kMubHQt | ||||
| QnTauEiv0YkSGOwJAUZpK1PIff5GgxXYfaHfBC6Op4q02ppl5Q3URl7XIjYLjvs9 | ||||
| t4S9xPe8tb6416V2fe1dZ62vOXMMKHkZjVihh+IceYpJYHuyfKoYJyahLOQXZykG | ||||
| K5iPAEEtq3HPfMVF43RKHOwfhrAH5KwelUA/0EkcR4Gzth1MKEqojdnYNemkkSy7 | ||||
| aNPPT4LEm5R7sV6vG1CjwbgvQrWCgc4nMb8ngdfnVF7Ydqjqi9SAqUzIk4+Uf0ZY | ||||
| +6RY5IcHdCaiPaWIE1xURQ8B0DRUURsQwXdjZhgLN/DKJpCl5aCCxg== | ||||
| -----END CERTIFICATE----- | ||||
| @@ -1,56 +0,0 @@ | ||||
| HyperFIDO U2F Security Key Attestation CA | ||||
| https://hypersecu.com/support/downloads/attestation | ||||
|  | ||||
| Last Update: 2017-01-01 | ||||
|  | ||||
| HyperFIDO U2F Security Key devices which contain attestation certificates signed by a set of CAs. | ||||
| This file contains the CA certificates that Relying Parties (RP) need to configure their software | ||||
| with to be able to verify U2F device certificates. | ||||
|  | ||||
| The file will be updated as needed when we publish more CA certificates. | ||||
|  | ||||
| Issuer: CN=FT FIDO 0100 | ||||
|  | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIBjTCCATOgAwIBAgIBATAKBggqhkjOPQQDAjAXMRUwEwYDVQQDEwxGVCBGSURP | ||||
| IDAxMDAwHhcNMTQwNzAxMTUzNjI2WhcNNDQwNzAzMTUzNjI2WjAXMRUwEwYDVQQD | ||||
| EwxGVCBGSURPIDAxMDAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASxdLxJx8ol | ||||
| S3DS5cIHzunPF0gg69d+o8ZVCMJtpRtlfBzGuVL4YhaXk2SC2gptPTgmpZCV2vbN | ||||
| fAPi5gOF0vbZo3AwbjAdBgNVHQ4EFgQUXt4jWlYDgwhaPU+EqLmeM9LoPRMwPwYD | ||||
| VR0jBDgwNoAUXt4jWlYDgwhaPU+EqLmeM9LoPROhG6QZMBcxFTATBgNVBAMTDEZU | ||||
| IEZJRE8gMDEwMIIBATAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMCA0gAMEUCIQC2 | ||||
| D9o9cconKTo8+4GZPyZBJ3amc8F0/kzyidX9dhrAIAIgM9ocs5BW/JfmshVP9Mb+ | ||||
| Joa/kgX4dWbZxrk0ioTfJZg= | ||||
| -----END CERTIFICATE----- | ||||
|  | ||||
|  | ||||
|     Certificate: | ||||
|     Data: | ||||
|         Version: 3 (0x2) | ||||
|         Serial Number: 4107 (0x100b) | ||||
|     Signature Algorithm: ecdsa-with-SHA256 | ||||
|         Issuer: | ||||
|             commonName                = HYPERFIDO 0200 | ||||
|             organizationName          = HYPERSECU | ||||
|             countryName               = CA | ||||
|         Validity | ||||
|             Not Before: Jan  1 00:00:00 2018 GMT | ||||
|             Not After : Dec 31 23:59:59 2047 GMT | ||||
|         Subject: | ||||
|             commonName                = HYPERFIDO 0200 | ||||
|             organizationName          = HYPERSECU | ||||
|             countryName               = CA | ||||
|  | ||||
|  | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIBxzCCAWygAwIBAgICEAswCgYIKoZIzj0EAwIwOjELMAkGA1UEBhMCQ0ExEjAQ | ||||
| BgNVBAoMCUhZUEVSU0VDVTEXMBUGA1UEAwwOSFlQRVJGSURPIDAyMDAwIBcNMTgw | ||||
| MTAxMDAwMDAwWhgPMjA0NzEyMzEyMzU5NTlaMDoxCzAJBgNVBAYTAkNBMRIwEAYD | ||||
| VQQKDAlIWVBFUlNFQ1UxFzAVBgNVBAMMDkhZUEVSRklETyAwMjAwMFkwEwYHKoZI | ||||
| zj0CAQYIKoZIzj0DAQcDQgAErKUI1G0S7a6IOLlmHipLlBuxTYjsEESQvzQh3dB7 | ||||
| dvxxWWm7kWL91rq6S7ayZG0gZPR+zYqdFzwAYDcG4+aX66NgMF4wHQYDVR0OBBYE | ||||
| FLZYcfMMwkQAGbt3ryzZFPFypmsIMB8GA1UdIwQYMBaAFLZYcfMMwkQAGbt3ryzZ | ||||
| FPFypmsIMAwGA1UdEwQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMC | ||||
| A0kAMEYCIQCG2/ppMGt7pkcRie5YIohS3uDPIrmiRcTjqDclKVWg0gIhANcPNDZH | ||||
| E2/zZ+uB5ThG9OZus+xSb4knkrbAyXKX2zm/ | ||||
| -----END CERTIFICATE----- | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,41 +0,0 @@ | ||||
| Solokeys FIDO2/U2F Device Attestation CA | ||||
| ======================================== | ||||
|     Data: | ||||
|         Version: 1 (0x0) | ||||
|         Serial Number: 14143382635911888524 (0xc44763928ff4be8c) | ||||
|     Signature Algorithm: ecdsa-with-SHA256 | ||||
|  | ||||
|         Issuer: | ||||
|             emailAddress              = hello@solokeys.com | ||||
|             commonName                = solokeys.com | ||||
|             organizationalUnitName    = Root CA | ||||
|             organizationName          = Solo Keys | ||||
|             stateOrProvinceName       = Maryland | ||||
|             countryName               = US | ||||
|  | ||||
|         Validity | ||||
|             Not Before: Nov 11 12:51:42 2018 GMT | ||||
|             Not After : Oct 29 12:51:42 2068 GMT | ||||
|  | ||||
|         Subject: | ||||
|             emailAddress              = hello@solokeys.com | ||||
|             commonName                = solokeys.com | ||||
|             organizationalUnitName    = Root CA | ||||
|             organizationName          = Solo Keys | ||||
|             stateOrProvinceName       = Maryland | ||||
|             countryName               = US | ||||
|  | ||||
|  | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIB9DCCAZoCCQDER2OSj/S+jDAKBggqhkjOPQQDAjCBgDELMAkGA1UEBhMCVVMx | ||||
| ETAPBgNVBAgMCE1hcnlsYW5kMRIwEAYDVQQKDAlTb2xvIEtleXMxEDAOBgNVBAsM | ||||
| B1Jvb3QgQ0ExFTATBgNVBAMMDHNvbG9rZXlzLmNvbTEhMB8GCSqGSIb3DQEJARYS | ||||
| aGVsbG9Ac29sb2tleXMuY29tMCAXDTE4MTExMTEyNTE0MloYDzIwNjgxMDI5MTI1 | ||||
| MTQyWjCBgDELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1hcnlsYW5kMRIwEAYDVQQK | ||||
| DAlTb2xvIEtleXMxEDAOBgNVBAsMB1Jvb3QgQ0ExFTATBgNVBAMMDHNvbG9rZXlz | ||||
| LmNvbTEhMB8GCSqGSIb3DQEJARYSaGVsbG9Ac29sb2tleXMuY29tMFkwEwYHKoZI | ||||
| zj0CAQYIKoZIzj0DAQcDQgAEWHAN0CCJVZdMs0oktZ5m93uxmB1iyq8ELRLtqVFL | ||||
| SOiHQEab56qRTB/QzrpGAY++Y2mw+vRuQMNhBiU0KzwjBjAKBggqhkjOPQQDAgNI | ||||
| ADBFAiEAz9SlrAXIlEu87vra54rICPs+4b0qhp3PdzcTg7rvnP0CIGjxzlteQQx+ | ||||
| jQGd7rwSZuE5RWUPVygYhUstQO9zNUOs | ||||
| -----END CERTIFICATE----- | ||||
| @@ -1,42 +0,0 @@ | ||||
| Yubico U2F Device Attestation CA | ||||
| ================================ | ||||
|  | ||||
| Last Update: 2014-09-01 | ||||
|  | ||||
| Yubico manufacturer U2F devices that contains device attestation | ||||
| certificates signed by a set of Yubico CAs.  This file contains the CA | ||||
| certificates that Relying Parties (RP) need to configure their | ||||
| software with to be able to verify U2F device certificates. | ||||
|  | ||||
| This file has been signed with OpenPGP and you should verify the | ||||
| signature and the authenticity of the public key before trusting the | ||||
| content.  The signature is located next to the file: | ||||
|  | ||||
|   https://developers.yubico.com/u2f/yubico-u2f-ca-certs.txt | ||||
|   https://developers.yubico.com/u2f/yubico-u2f-ca-certs.txt.sig | ||||
|  | ||||
| We will update this file from time to time when we publish more CA | ||||
| certificates. | ||||
|  | ||||
| Name: Yubico U2F Root CA Serial 457200631 | ||||
| Issued: 2014-08-01 | ||||
|  | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIDHjCCAgagAwIBAgIEG0BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ | ||||
| dWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw | ||||
| MDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290 | ||||
| IENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK | ||||
| AoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk | ||||
| 5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep | ||||
| 8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbw | ||||
| nebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT | ||||
| 9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXw | ||||
| LvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJ | ||||
| hjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAN | ||||
| BgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4 | ||||
| MYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kt | ||||
| hX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2k | ||||
| LVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1U | ||||
| sG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc | ||||
| U9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw== | ||||
| -----END CERTIFICATE----- | ||||
		Reference in New Issue
	
	Block a user