1 <?php 2 3 4 namespace lbuchs\WebAuthn\Attestation\Format; 5 use lbuchs\WebAuthn\Attestation\AuthenticatorData; 6 use lbuchs\WebAuthn\WebAuthnException; 7 use lbuchs\WebAuthn\Binary\ByteBuffer; 8 9 class Apple extends FormatBase { 10 private $_x5c; 11 12 public function __construct($AttestionObject, AuthenticatorData $authenticatorData) { 13 parent::__construct($AttestionObject, $authenticatorData); 14 15 // check packed data 16 $attStmt = $this->_attestationObject['attStmt']; 17 18 19 // certificate for validation 20 if (\array_key_exists('x5c', $attStmt) && \is_array($attStmt['x5c']) && \count($attStmt['x5c']) > 0) { 21 22 // The attestation certificate attestnCert MUST be the first element in the array 23 $attestnCert = array_shift($attStmt['x5c']); 24 25 if (!($attestnCert instanceof ByteBuffer)) { 26 throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); 27 } 28 29 $this->_x5c = $attestnCert->getBinaryString(); 30 31 // certificate chain 32 foreach ($attStmt['x5c'] as $chain) { 33 if ($chain instanceof ByteBuffer) { 34 $this->_x5c_chain[] = $chain->getBinaryString(); 35 } 36 } 37 } else { 38 throw new WebAuthnException('invalid Apple attestation statement: missing x5c', WebAuthnException::INVALID_DATA); 39 } 40 } 41 42 43 /* 44 * returns the key certificate in PEM format 45 * @return string|null 46 */ 47 public function getCertificatePem() { 48 return $this->_createCertificatePem($this->_x5c); 49 } 50 51 /** 52 * @param string $clientDataHash 53 */ 54 public function validateAttestation($clientDataHash) { 55 return $this->_validateOverX5c($clientDataHash); 56 } 57 58 /** 59 * validates the certificate against root certificates 60 * @param array $rootCas 61 * @return boolean 62 * @throws WebAuthnException 63 */ 64 public function validateRootCertificate($rootCas) { 65 $chainC = $this->_createX5cChainFile(); 66 if ($chainC) { 67 $rootCas[] = $chainC; 68 } 69 70 $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas); 71 if ($v === -1) { 72 throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED); 73 } 74 return $v; 75 } 76 77 /** 78 * validate if x5c is present 79 * @param string $clientDataHash 80 * @return bool 81 * @throws WebAuthnException 82 */ 83 protected function _validateOverX5c($clientDataHash) { 84 $publicKey = \openssl_pkey_get_public($this->getCertificatePem()); 85 86 if ($publicKey === false) { 87 throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY); 88 } 89 90 // Concatenate authenticatorData and clientDataHash to form nonceToHash. 91 $nonceToHash = $this->_authenticatorData->getBinary(); 92 $nonceToHash .= $clientDataHash; 93 94 // Perform SHA-256 hash of nonceToHash to produce nonce 95 $nonce = hash('SHA256', $nonceToHash, true); 96 97 $credCert = openssl_x509_read($this->getCertificatePem()); 98 if ($credCert === false) { 99 throw new WebAuthnException('invalid x5c certificate: ' . \openssl_error_string(), WebAuthnException::INVALID_DATA); 100 } 101 102 $keyData = openssl_pkey_get_details(openssl_pkey_get_public($credCert)); 103 $key = is_array($keyData) && array_key_exists('key', $keyData) ? $keyData['key'] : null; 104 105 106 // Verify that nonce equals the value of the extension with OID ( 1.2.840.113635.100.8.2 ) in credCert. 107 $parsedCredCert = openssl_x509_parse($credCert); 108 $nonceExtension = $parsedCredCert['extensions']['1.2.840.113635.100.8.2'] ?? ''; 109 110 // nonce padded by ASN.1 string: 30 24 A1 22 04 20 111 // 30 — type tag indicating sequence 112 // 24 — 36 byte following 113 // A1 — Enumerated [1] 114 // 22 — 34 byte following 115 // 04 — type tag indicating octet string 116 // 20 — 32 byte following 117 118 $asn1Padding = "\x30\x24\xA1\x22\x04\x20"; 119 if (substr($nonceExtension, 0, strlen($asn1Padding)) === $asn1Padding) { 120 $nonceExtension = substr($nonceExtension, strlen($asn1Padding)); 121 } 122 123 if ($nonceExtension !== $nonce) { 124 throw new WebAuthnException('nonce doesn\'t equal the value of the extension with OID 1.2.840.113635.100.8.2', WebAuthnException::INVALID_DATA); 125 } 126 127 // Verify that the credential public key equals the Subject Public Key of credCert. 128 $authKeyData = openssl_pkey_get_details(openssl_pkey_get_public($this->_authenticatorData->getPublicKeyPem())); 129 $authKey = is_array($authKeyData) && array_key_exists('key', $authKeyData) ? $authKeyData['key'] : null; 130 131 if ($key === null || $key !== $authKey) { 132 throw new WebAuthnException('credential public key doesn\'t equal the Subject Public Key of credCert', WebAuthnException::INVALID_DATA); 133 } 134 135 return true; 136 } 137 138 } 139
title
Description
Body
title
Description
Body
title
Description
Body
title
Body