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 Tpm extends FormatBase { 10 private $_TPM_GENERATED_VALUE = "\xFF\x54\x43\x47"; 11 private $_TPM_ST_ATTEST_CERTIFY = "\x80\x17"; 12 private $_alg; 13 private $_signature; 14 private $_pubArea; 15 private $_x5c; 16 17 /** 18 * @var ByteBuffer 19 */ 20 private $_certInfo; 21 22 23 public function __construct($AttestionObject, AuthenticatorData $authenticatorData) { 24 parent::__construct($AttestionObject, $authenticatorData); 25 26 // check packed data 27 $attStmt = $this->_attestationObject['attStmt']; 28 29 if (!\array_key_exists('ver', $attStmt) || $attStmt['ver'] !== '2.0') { 30 throw new WebAuthnException('invalid tpm version: ' . $attStmt['ver'], WebAuthnException::INVALID_DATA); 31 } 32 33 if (!\array_key_exists('alg', $attStmt) || $this->_getCoseAlgorithm($attStmt['alg']) === null) { 34 throw new WebAuthnException('unsupported alg: ' . $attStmt['alg'], WebAuthnException::INVALID_DATA); 35 } 36 37 if (!\array_key_exists('sig', $attStmt) || !\is_object($attStmt['sig']) || !($attStmt['sig'] instanceof ByteBuffer)) { 38 throw new WebAuthnException('signature not found', WebAuthnException::INVALID_DATA); 39 } 40 41 if (!\array_key_exists('certInfo', $attStmt) || !\is_object($attStmt['certInfo']) || !($attStmt['certInfo'] instanceof ByteBuffer)) { 42 throw new WebAuthnException('certInfo not found', WebAuthnException::INVALID_DATA); 43 } 44 45 if (!\array_key_exists('pubArea', $attStmt) || !\is_object($attStmt['pubArea']) || !($attStmt['pubArea'] instanceof ByteBuffer)) { 46 throw new WebAuthnException('pubArea not found', WebAuthnException::INVALID_DATA); 47 } 48 49 $this->_alg = $attStmt['alg']; 50 $this->_signature = $attStmt['sig']->getBinaryString(); 51 $this->_certInfo = $attStmt['certInfo']; 52 $this->_pubArea = $attStmt['pubArea']; 53 54 // certificate for validation 55 if (\array_key_exists('x5c', $attStmt) && \is_array($attStmt['x5c']) && \count($attStmt['x5c']) > 0) { 56 57 // The attestation certificate attestnCert MUST be the first element in the array 58 $attestnCert = array_shift($attStmt['x5c']); 59 60 if (!($attestnCert instanceof ByteBuffer)) { 61 throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); 62 } 63 64 $this->_x5c = $attestnCert->getBinaryString(); 65 66 // certificate chain 67 foreach ($attStmt['x5c'] as $chain) { 68 if ($chain instanceof ByteBuffer) { 69 $this->_x5c_chain[] = $chain->getBinaryString(); 70 } 71 } 72 73 } else { 74 throw new WebAuthnException('no x5c certificate found', WebAuthnException::INVALID_DATA); 75 } 76 } 77 78 79 /* 80 * returns the key certificate in PEM format 81 * @return string|null 82 */ 83 public function getCertificatePem() { 84 if (!$this->_x5c) { 85 return null; 86 } 87 return $this->_createCertificatePem($this->_x5c); 88 } 89 90 /** 91 * @param string $clientDataHash 92 */ 93 public function validateAttestation($clientDataHash) { 94 return $this->_validateOverX5c($clientDataHash); 95 } 96 97 /** 98 * validates the certificate against root certificates 99 * @param array $rootCas 100 * @return boolean 101 * @throws WebAuthnException 102 */ 103 public function validateRootCertificate($rootCas) { 104 if (!$this->_x5c) { 105 return false; 106 } 107 108 $chainC = $this->_createX5cChainFile(); 109 if ($chainC) { 110 $rootCas[] = $chainC; 111 } 112 113 $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas); 114 if ($v === -1) { 115 throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED); 116 } 117 return $v; 118 } 119 120 /** 121 * validate if x5c is present 122 * @param string $clientDataHash 123 * @return bool 124 * @throws WebAuthnException 125 */ 126 protected function _validateOverX5c($clientDataHash) { 127 $publicKey = \openssl_pkey_get_public($this->getCertificatePem()); 128 129 if ($publicKey === false) { 130 throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY); 131 } 132 133 // Concatenate authenticatorData and clientDataHash to form attToBeSigned. 134 $attToBeSigned = $this->_authenticatorData->getBinary(); 135 $attToBeSigned .= $clientDataHash; 136 137 // Validate that certInfo is valid: 138 139 // Verify that magic is set to TPM_GENERATED_VALUE. 140 if ($this->_certInfo->getBytes(0, 4) !== $this->_TPM_GENERATED_VALUE) { 141 throw new WebAuthnException('tpm magic not TPM_GENERATED_VALUE', WebAuthnException::INVALID_DATA); 142 } 143 144 // Verify that type is set to TPM_ST_ATTEST_CERTIFY. 145 if ($this->_certInfo->getBytes(4, 2) !== $this->_TPM_ST_ATTEST_CERTIFY) { 146 throw new WebAuthnException('tpm type not TPM_ST_ATTEST_CERTIFY', WebAuthnException::INVALID_DATA); 147 } 148 149 $offset = 6; 150 $qualifiedSigner = $this->_tpmReadLengthPrefixed($this->_certInfo, $offset); 151 $extraData = $this->_tpmReadLengthPrefixed($this->_certInfo, $offset); 152 $coseAlg = $this->_getCoseAlgorithm($this->_alg); 153 154 // Verify that extraData is set to the hash of attToBeSigned using the hash algorithm employed in "alg". 155 if ($extraData->getBinaryString() !== \hash($coseAlg->hash, $attToBeSigned, true)) { 156 throw new WebAuthnException('certInfo:extraData not hash of attToBeSigned', WebAuthnException::INVALID_DATA); 157 } 158 159 // Verify the sig is a valid signature over certInfo using the attestation 160 // public key in aikCert with the algorithm specified in alg. 161 return \openssl_verify($this->_certInfo->getBinaryString(), $this->_signature, $publicKey, $coseAlg->openssl) === 1; 162 } 163 164 165 /** 166 * returns next part of ByteBuffer 167 * @param ByteBuffer $buffer 168 * @param int $offset 169 * @return ByteBuffer 170 */ 171 protected function _tpmReadLengthPrefixed(ByteBuffer $buffer, &$offset) { 172 $len = $buffer->getUint16Val($offset); 173 $data = $buffer->getBytes($offset + 2, $len); 174 $offset += (2 + $len); 175 176 return new ByteBuffer($data); 177 } 178 179 } 180
title
Description
Body
title
Description
Body
title
Description
Body
title
Body