blob: 115d7878ebd0cdd6c660075f60da6ef64eafe2eb [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001<?php
2
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01003namespace lbuchs\WebAuthn\Attestation;
4use lbuchs\WebAuthn\WebAuthnException;
5use lbuchs\WebAuthn\CBOR\CborDecoder;
6use lbuchs\WebAuthn\Binary\ByteBuffer;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01007
8/**
9 * @author Lukas Buchs
10 * @license https://github.com/lbuchs/WebAuthn/blob/master/LICENSE MIT
11 */
12class AttestationObject {
13 private $_authenticatorData;
14 private $_attestationFormat;
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010015 private $_attestationFormatName;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010016
17 public function __construct($binary , $allowedFormats) {
18 $enc = CborDecoder::decode($binary);
19 // validation
20 if (!\is_array($enc) || !\array_key_exists('fmt', $enc) || !is_string($enc['fmt'])) {
21 throw new WebAuthnException('invalid attestation format', WebAuthnException::INVALID_DATA);
22 }
23
24 if (!\array_key_exists('attStmt', $enc) || !\is_array($enc['attStmt'])) {
25 throw new WebAuthnException('invalid attestation format (attStmt not available)', WebAuthnException::INVALID_DATA);
26 }
27
28 if (!\array_key_exists('authData', $enc) || !\is_object($enc['authData']) || !($enc['authData'] instanceof ByteBuffer)) {
29 throw new WebAuthnException('invalid attestation format (authData not available)', WebAuthnException::INVALID_DATA);
30 }
31
32 $this->_authenticatorData = new AuthenticatorData($enc['authData']->getBinaryString());
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010033 $this->_attestationFormatName = $enc['fmt'];
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010034
35 // Format ok?
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010036 if (!in_array($this->_attestationFormatName, $allowedFormats)) {
37 throw new WebAuthnException('invalid atttestation format: ' . $this->_attestationFormatName, WebAuthnException::INVALID_DATA);
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010038 }
39
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010040
41 switch ($this->_attestationFormatName) {
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010042 case 'android-key': $this->_attestationFormat = new Format\AndroidKey($enc, $this->_authenticatorData); break;
43 case 'android-safetynet': $this->_attestationFormat = new Format\AndroidSafetyNet($enc, $this->_authenticatorData); break;
44 case 'apple': $this->_attestationFormat = new Format\Apple($enc, $this->_authenticatorData); break;
45 case 'fido-u2f': $this->_attestationFormat = new Format\U2f($enc, $this->_authenticatorData); break;
46 case 'none': $this->_attestationFormat = new Format\None($enc, $this->_authenticatorData); break;
47 case 'packed': $this->_attestationFormat = new Format\Packed($enc, $this->_authenticatorData); break;
48 case 'tpm': $this->_attestationFormat = new Format\Tpm($enc, $this->_authenticatorData); break;
49 default: throw new WebAuthnException('invalid attestation format: ' . $enc['fmt'], WebAuthnException::INVALID_DATA);
50 }
51 }
52
53 /**
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010054 * returns the attestation format name
55 * @return string
56 */
57 public function getAttestationFormatName() {
58 return $this->_attestationFormatName;
59 }
60
61 /**
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010062 * returns the attestation public key in PEM format
63 * @return AuthenticatorData
64 */
65 public function getAuthenticatorData() {
66 return $this->_authenticatorData;
67 }
68
69 /**
70 * returns the certificate chain as PEM
71 * @return string|null
72 */
73 public function getCertificateChain() {
74 return $this->_attestationFormat->getCertificateChain();
75 }
76
77 /**
78 * return the certificate issuer as string
79 * @return string
80 */
81 public function getCertificateIssuer() {
82 $pem = $this->getCertificatePem();
83 $issuer = '';
84 if ($pem) {
85 $certInfo = \openssl_x509_parse($pem);
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010086 if (\is_array($certInfo) && \array_key_exists('issuer', $certInfo) && \is_array($certInfo['issuer'])) {
87
88 $cn = $certInfo['issuer']['CN'] ?? '';
89 $o = $certInfo['issuer']['O'] ?? '';
90 $ou = $certInfo['issuer']['OU'] ?? '';
91
92 if ($cn) {
93 $issuer .= $cn;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010094 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010095 if ($issuer && ($o || $ou)) {
96 $issuer .= ' (' . trim($o . ' ' . $ou) . ')';
97 } else {
98 $issuer .= trim($o . ' ' . $ou);
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010099 }
100 }
101 }
102
103 return $issuer;
104 }
105
106 /**
107 * return the certificate subject as string
108 * @return string
109 */
110 public function getCertificateSubject() {
111 $pem = $this->getCertificatePem();
112 $subject = '';
113 if ($pem) {
114 $certInfo = \openssl_x509_parse($pem);
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100115 if (\is_array($certInfo) && \array_key_exists('subject', $certInfo) && \is_array($certInfo['subject'])) {
116
117 $cn = $certInfo['subject']['CN'] ?? '';
118 $o = $certInfo['subject']['O'] ?? '';
119 $ou = $certInfo['subject']['OU'] ?? '';
120
121 if ($cn) {
122 $subject .= $cn;
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100123 }
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100124 if ($subject && ($o || $ou)) {
125 $subject .= ' (' . trim($o . ' ' . $ou) . ')';
126 } else {
127 $subject .= trim($o . ' ' . $ou);
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100128 }
129 }
130 }
131
132 return $subject;
133 }
134
135 /**
136 * returns the key certificate in PEM format
137 * @return string
138 */
139 public function getCertificatePem() {
140 return $this->_attestationFormat->getCertificatePem();
141 }
142
143 /**
144 * checks validity of the signature
145 * @param string $clientDataHash
146 * @return bool
147 * @throws WebAuthnException
148 */
149 public function validateAttestation($clientDataHash) {
150 return $this->_attestationFormat->validateAttestation($clientDataHash);
151 }
152
153 /**
154 * validates the certificate against root certificates
155 * @param array $rootCas
156 * @return boolean
157 * @throws WebAuthnException
158 */
159 public function validateRootCertificate($rootCas) {
160 return $this->_attestationFormat->validateRootCertificate($rootCas);
161 }
162
163 /**
164 * checks if the RpId-Hash is valid
165 * @param string$rpIdHash
166 * @return bool
167 */
168 public function validateRpIdHash($rpIdHash) {
169 return $rpIdHash === $this->_authenticatorData->getRpIdHash();
170 }
171}