AttestationObject.php 5.6 KB

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