PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 4.0.0
WP 2FA – Two-factor authentication for WordPress v4.0.0
4.0.0 1.7.1 2.0.0 2.0.1 2.1.0 2.2.0 2.2.1 2.3.0 2.4.0 2.4.1 2.4.2 2.5.0 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.8.0 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0 3.0.1 3.1.0 3.1.1 3.1.1.2 trunk 1.2.0 1.3.0 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.6.0 1.6.1 1.6.2 1.7.0
wp-2fa / includes / classes / Admin / Methods / passkeys / format / tpm.php
wp-2fa / includes / classes / Admin / Methods / passkeys / format Last commit date
android-key.php 1 day ago android-safety-net.php 1 day ago apple.php 1 day ago format-base.php 1 day ago index.php 1 day ago none.php 1 day ago packed.php 1 day ago tpm.php 1 day ago u2f.php 1 day ago
tpm.php
257 lines
1 <?php
2 /**
3 * Passkeys formatters
4 *
5 * @package wp-2fa
6 * @since 3.0.0
7 */
8
9 namespace WP2FA\Passkeys\Format;
10
11 use WP2FA\Methods\Passkeys\Byte_Buffer;
12 use WP2FA\Methods\Passkeys\Web_Authn_Exception;
13 use WP2FA\Admin\Methods\passkeys\Authenticator_Data;
14
15 /**
16 * Responsible for tpm format
17 *
18 * @since 3.0.0
19 */
20 class Tpm extends Format_Base {
21
22 private const TPM_GENERATED_VALUE = "\xFF\x54\x43\x47";
23 private const TPM_ST_ATTEST_CERTIFY = "\x80\x17";
24
25 /**
26 * Algorithm to be used
27 *
28 * @var string
29 *
30 * @since 3.0.0
31 */
32 private $alg;
33
34 /**
35 * Signature
36 *
37 * @var string
38 *
39 * @since 3.0.0
40 */
41 private $signature;
42
43 /**
44 * Public area
45 *
46 * @var Byte_Buffer
47 *
48 * @since 3.0.0
49 */
50 private $pub_area;
51
52 /**
53 * X5c certificate
54 *
55 * @var string
56 *
57 * @since 3.0.0
58 */
59 private $x5c;
60
61 /**
62 * Cert info
63 *
64 * @var Byte_Buffer
65 *
66 * @since 3.0.0
67 */
68 private $cert_info;
69
70 /**
71 * Default constructor
72 *
73 * @param \StdClass $attestion_object - attestation object.
74 * @param \WP2FA\Admin\Methods\passkeys\Authenticator_Data $authenticator_data - Authentication data.
75 *
76 * @throws Web_Authn_Exception - if can not be created.
77 *
78 * @since 3.0.0
79 */
80 public function __construct( $attestion_object, Authenticator_Data $authenticator_data ) {
81 parent::__construct( $attestion_object, $authenticator_data );
82
83 // check packed data.
84 $att_stmt = $this->attestation_object['attStmt'];
85
86 if ( '2.0' !== ! \array_key_exists( 'ver', $att_stmt ) || $att_stmt['ver'] ) {
87 throw new Web_Authn_Exception( 'invalid tpm version: ' . $att_stmt['ver'], Web_Authn_Exception::INVALID_DATA );
88 }
89
90 if ( ! \array_key_exists( 'alg', $att_stmt ) || $this->_get_cose_algorithm( $att_stmt['alg'] ) === null ) {
91 throw new Web_Authn_Exception( 'unsupported alg: ' . $att_stmt['alg'], Web_Authn_Exception::INVALID_DATA );
92 }
93
94 if ( ! \array_key_exists( 'sig', $att_stmt ) || ! \is_object( $att_stmt['sig'] ) || ! ( $att_stmt['sig'] instanceof Byte_Buffer ) ) {
95 throw new Web_Authn_Exception( 'signature not found', Web_Authn_Exception::INVALID_DATA );
96 }
97
98 if ( ! \array_key_exists( 'certInfo', $att_stmt ) || ! \is_object( $att_stmt['certInfo'] ) || ! ( $att_stmt['certInfo'] instanceof Byte_Buffer ) ) {
99 throw new Web_Authn_Exception( 'certInfo not found', Web_Authn_Exception::INVALID_DATA );
100 }
101
102 if ( ! \array_key_exists( 'pubArea', $att_stmt ) || ! \is_object( $att_stmt['pubArea'] ) || ! ( $att_stmt['pubArea'] instanceof Byte_Buffer ) ) {
103 throw new Web_Authn_Exception( 'pubArea not found', Web_Authn_Exception::INVALID_DATA );
104 }
105
106 $this->alg = $att_stmt['alg'];
107 $this->signature = $att_stmt['sig']->getBinaryString();
108 $this->cert_info = $att_stmt['certInfo'];
109 $this->pub_area = $att_stmt['pubArea'];
110
111 // certificate for validation.
112 if ( \array_key_exists( 'x5c', $att_stmt ) && \is_array( $att_stmt['x5c'] ) && \count( $att_stmt['x5c'] ) > 0 ) {
113
114 // The attestation certificate attestn_cert MUST be the first element in the array.
115 $attestn_cert = array_shift( $att_stmt['x5c'] );
116
117 if ( ! ( $attestn_cert instanceof Byte_Buffer ) ) {
118 throw new Web_Authn_Exception( 'invalid x5c certificate', Web_Authn_Exception::INVALID_DATA );
119 }
120
121 $this->x5c = $attestn_cert->getBinaryString();
122
123 // certificate chain.
124 foreach ( $att_stmt['x5c'] as $chain ) {
125 if ( $chain instanceof Byte_Buffer ) {
126 $this->x5c_chain[] = $chain->getBinaryString();
127 }
128 }
129 } else {
130 throw new Web_Authn_Exception( 'no x5c certificate found', Web_Authn_Exception::INVALID_DATA );
131 }
132 }
133
134 /**
135 * Returns the key certificate in PEM format
136 *
137 * @return string|null
138 *
139 * @since 3.0.0
140 */
141 public function get_certificate_pem() {
142 if ( ! $this->x5c ) {
143 return null;
144 }
145 return $this->_create_certificate_pem( $this->x5c );
146 }
147
148 /**
149 * Validator
150 *
151 * @param string $client_data_hash - Hash collected.
152 *
153 * @throws Web_Authn_Exception - Throws exception if validation fails.
154 *
155 * @since 3.0.0
156 */
157 public function validate_attestation( $client_data_hash ) {
158 return $this->_validate_over_x5c( $client_data_hash );
159 }
160
161 /**
162 * Validates the certificate against root certificates
163 *
164 * @param array $root_cas - Array with values.
165 *
166 * @return boolean
167 *
168 * @throws Web_Authn_Exception - Throws exception.
169 *
170 * @since 3.0.0
171 */
172 public function validate_root_certificate( $root_cas ) {
173 if ( ! $this->x5c ) {
174 return false;
175 }
176
177 $chain_c = $this->_create_x5c_chain_file();
178 if ( $chain_c ) {
179 $root_cas[] = $chain_c;
180 }
181
182 $v = \openssl_x509_checkpurpose( $this->get_certificate_pem(), -1, $root_cas );
183 if ( -1 === $v ) {
184 throw new Web_Authn_Exception( 'error on validating root certificate: ' . \openssl_error_string(), Web_Authn_Exception::CERTIFICATE_NOT_TRUSTED );
185 }
186 return $v;
187 }
188
189 /**
190 * Validate if x5c is present
191 *
192 * @param string $client_data_hash - Hash collected.
193 *
194 * @return bool
195 *
196 * @throws Web_Authn_Exception - Throws exception.
197 *
198 * @since 3.0.0
199 */
200 protected function _validate_over_x5c( $client_data_hash ) {
201 $public_key = \openssl_pkey_get_public( $this->get_certificate_pem() );
202
203 if ( false === $public_key ) {
204 throw new Web_Authn_Exception( 'invalid public key: ' . \openssl_error_string(), Web_Authn_Exception::INVALID_PUBLIC_KEY );
205 }
206
207 // Concatenate Authenticator_Data and client_data_hash to form att_to_be_signed.
208 $att_to_be_signed = $this->authenticator_data->get_binary();
209 $att_to_be_signed .= $client_data_hash;
210
211 // Validate that certInfo is valid:.
212
213 // Verify that magic is set to TPM_GENERATED_VALUE.
214 if ( $this->cert_info->getBytes( 0, 4 ) !== self::TPM_GENERATED_VALUE ) {
215 throw new Web_Authn_Exception( 'tpm magic not TPM_GENERATED_VALUE', Web_Authn_Exception::INVALID_DATA );
216 }
217
218 // Verify that type is set to TPM_ST_ATTEST_CERTIFY.
219 if ( $this->cert_info->getBytes( 4, 2 ) !== self::TPM_ST_ATTEST_CERTIFY ) {
220 throw new Web_Authn_Exception( 'tpm type not TPM_ST_ATTEST_CERTIFY', Web_Authn_Exception::INVALID_DATA );
221 }
222
223 $offset = 6;
224 $qualified_signer = $this->_tpmReadLengthPrefixed( $this->cert_info, $offset );
225 $extra_data = $this->_tpmReadLengthPrefixed( $this->cert_info, $offset );
226 $cose_alg = $this->_get_cose_algorithm( $this->alg );
227
228 // Verify that extra_data is set to the hash of att_to_be_signed using the hash algorithm employed in "alg".
229 if ( $extra_data->getBinaryString() !== \hash( $cose_alg->hash, $att_to_be_signed, true ) ) {
230 throw new Web_Authn_Exception( 'certInfo:extraData not hash of attToBeSigned', Web_Authn_Exception::INVALID_DATA );
231 }
232
233 // Verify the sig is a valid signature over certInfo using the attestation
234 // public key in aikCert with the algorithm specified in alg.
235 return \openssl_verify( $this->cert_info->getBinaryString(), $this->signature, $public_key, $cose_alg->openssl ) === 1;
236 }
237
238
239 /**
240 * Returns next part of Byte_Buffer
241 *
242 * @param Byte_Buffer $buffer - Buffer to read from.
243 * @param int $offset - Offset to read.
244 *
245 * @return Byte_Buffer
246 *
247 * @since 3.0.0
248 */
249 protected function _tpmReadLengthPrefixed( Byte_Buffer $buffer, &$offset ) {
250 $len = $buffer->getUint16Val( $offset );
251 $data = $buffer->getBytes( $offset + 2, $len );
252 $offset += ( 2 + $len );
253
254 return new Byte_Buffer( $data );
255 }
256 }
257