AttestationObject.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. <?php
  2. namespace lbuchs\WebAuthn\Attestation;
  3. use lbuchs\WebAuthn\WebAuthnException;
  4. use lbuchs\WebAuthn\CBOR\CborDecoder;
  5. use lbuchs\WebAuthn\Binary\ByteBuffer;
  6. /**
  7. * @author Lukas Buchs
  8. * @license https://github.com/lbuchs/WebAuthn/blob/master/LICENSE MIT
  9. */
  10. class AttestationObject {
  11. private $_authenticatorData;
  12. private $_attestationFormat;
  13. private $_attestationFormatName;
  14. public function __construct($binary , $allowedFormats) {
  15. $enc = CborDecoder::decode($binary);
  16. // validation
  17. if (!\is_array($enc) || !\array_key_exists('fmt', $enc) || !is_string($enc['fmt'])) {
  18. throw new WebAuthnException('invalid attestation format', WebAuthnException::INVALID_DATA);
  19. }
  20. if (!\array_key_exists('attStmt', $enc) || !\is_array($enc['attStmt'])) {
  21. throw new WebAuthnException('invalid attestation format (attStmt not available)', WebAuthnException::INVALID_DATA);
  22. }
  23. if (!\array_key_exists('authData', $enc) || !\is_object($enc['authData']) || !($enc['authData'] instanceof ByteBuffer)) {
  24. throw new WebAuthnException('invalid attestation format (authData not available)', WebAuthnException::INVALID_DATA);
  25. }
  26. $this->_authenticatorData = new AuthenticatorData($enc['authData']->getBinaryString());
  27. $this->_attestationFormatName = $enc['fmt'];
  28. // Format ok?
  29. if (!in_array($this->_attestationFormatName, $allowedFormats)) {
  30. throw new WebAuthnException('invalid atttestation format: ' . $this->_attestationFormatName, WebAuthnException::INVALID_DATA);
  31. }
  32. switch ($this->_attestationFormatName) {
  33. case 'android-key': $this->_attestationFormat = new Format\AndroidKey($enc, $this->_authenticatorData); break;
  34. case 'android-safetynet': $this->_attestationFormat = new Format\AndroidSafetyNet($enc, $this->_authenticatorData); break;
  35. case 'apple': $this->_attestationFormat = new Format\Apple($enc, $this->_authenticatorData); break;
  36. case 'fido-u2f': $this->_attestationFormat = new Format\U2f($enc, $this->_authenticatorData); break;
  37. case 'none': $this->_attestationFormat = new Format\None($enc, $this->_authenticatorData); break;
  38. case 'packed': $this->_attestationFormat = new Format\Packed($enc, $this->_authenticatorData); break;
  39. case 'tpm': $this->_attestationFormat = new Format\Tpm($enc, $this->_authenticatorData); break;
  40. default: throw new WebAuthnException('invalid attestation format: ' . $enc['fmt'], WebAuthnException::INVALID_DATA);
  41. }
  42. }
  43. /**
  44. * returns the attestation format name
  45. * @return string
  46. */
  47. public function getAttestationFormatName() {
  48. return $this->_attestationFormatName;
  49. }
  50. /**
  51. * returns the attestation public key in PEM format
  52. * @return AuthenticatorData
  53. */
  54. public function getAuthenticatorData() {
  55. return $this->_authenticatorData;
  56. }
  57. /**
  58. * returns the certificate chain as PEM
  59. * @return string|null
  60. */
  61. public function getCertificateChain() {
  62. return $this->_attestationFormat->getCertificateChain();
  63. }
  64. /**
  65. * return the certificate issuer as string
  66. * @return string
  67. */
  68. public function getCertificateIssuer() {
  69. $pem = $this->getCertificatePem();
  70. $issuer = '';
  71. if ($pem) {
  72. $certInfo = \openssl_x509_parse($pem);
  73. if (\is_array($certInfo) && \array_key_exists('issuer', $certInfo) && \is_array($certInfo['issuer'])) {
  74. $cn = $certInfo['issuer']['CN'] ?? '';
  75. $o = $certInfo['issuer']['O'] ?? '';
  76. $ou = $certInfo['issuer']['OU'] ?? '';
  77. if ($cn) {
  78. $issuer .= $cn;
  79. }
  80. if ($issuer && ($o || $ou)) {
  81. $issuer .= ' (' . trim($o . ' ' . $ou) . ')';
  82. } else {
  83. $issuer .= trim($o . ' ' . $ou);
  84. }
  85. }
  86. }
  87. return $issuer;
  88. }
  89. /**
  90. * return the certificate subject as string
  91. * @return string
  92. */
  93. public function getCertificateSubject() {
  94. $pem = $this->getCertificatePem();
  95. $subject = '';
  96. if ($pem) {
  97. $certInfo = \openssl_x509_parse($pem);
  98. if (\is_array($certInfo) && \array_key_exists('subject', $certInfo) && \is_array($certInfo['subject'])) {
  99. $cn = $certInfo['subject']['CN'] ?? '';
  100. $o = $certInfo['subject']['O'] ?? '';
  101. $ou = $certInfo['subject']['OU'] ?? '';
  102. if ($cn) {
  103. $subject .= $cn;
  104. }
  105. if ($subject && ($o || $ou)) {
  106. $subject .= ' (' . trim($o . ' ' . $ou) . ')';
  107. } else {
  108. $subject .= trim($o . ' ' . $ou);
  109. }
  110. }
  111. }
  112. return $subject;
  113. }
  114. /**
  115. * returns the key certificate in PEM format
  116. * @return string
  117. */
  118. public function getCertificatePem() {
  119. return $this->_attestationFormat->getCertificatePem();
  120. }
  121. /**
  122. * checks validity of the signature
  123. * @param string $clientDataHash
  124. * @return bool
  125. * @throws WebAuthnException
  126. */
  127. public function validateAttestation($clientDataHash) {
  128. return $this->_attestationFormat->validateAttestation($clientDataHash);
  129. }
  130. /**
  131. * validates the certificate against root certificates
  132. * @param array $rootCas
  133. * @return boolean
  134. * @throws WebAuthnException
  135. */
  136. public function validateRootCertificate($rootCas) {
  137. return $this->_attestationFormat->validateRootCertificate($rootCas);
  138. }
  139. /**
  140. * checks if the RpId-Hash is valid
  141. * @param string$rpIdHash
  142. * @return bool
  143. */
  144. public function validateRpIdHash($rpIdHash) {
  145. return $rpIdHash === $this->_authenticatorData->getRpIdHash();
  146. }
  147. }