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 / class-attestation-object.php
wp-2fa / includes / classes / Admin / Methods / passkeys Last commit date
assets 23 hours ago format 23 hours ago helpers 23 hours ago class-ajax-passkeys.php 23 hours ago class-api-register.php 23 hours ago class-api-signin.php 23 hours ago class-attestation-object.php 23 hours ago class-authenticate-server.php 23 hours ago class-authenticator-data.php 23 hours ago class-byte-buffer.php 23 hours ago class-chor-decoder.php 23 hours ago class-passkeys-endpoints.php 23 hours ago class-passkeys-user-profile.php 23 hours ago class-passkeys-wizard-steps.php 23 hours ago class-passkeys.php 23 hours ago class-pending-2fa-helper.php 23 hours ago class-source-repository.php 23 hours ago class-web-authn-exception.php 23 hours ago class-web-authn.php 23 hours ago index.php 23 hours ago
class-attestation-object.php
283 lines
1 <?php
2 /**
3 * Responsible for the proper attestation object extracting
4 *
5 * @package wp-2fa
6 * @since 3.0.0
7 * @copyright 2026 Melapress
8 * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
9 * @link https://wordpress.org/plugins/wp-2fa/
10 */
11
12 declare(strict_types=1);
13
14 namespace WP2FA\Methods\Passkeys;
15
16 use WP2FA\Passkeys\Format\Tpm;
17 use WP2FA\Passkeys\Format\U2f;
18 use WP2FA\Passkeys\Format\None;
19 use WP2FA\Passkeys\Format\Apple;
20 use WP2FA\Passkeys\Format\Packed;
21 use WP2FA\Passkeys\Format\Android_Key;
22 use WP2FA\Methods\Passkeys\Cbor_Decoder;
23 use WP2FA\Passkeys\Format\Android_Safety_Net;
24 use WP2FA\Admin\Methods\passkeys\Authenticator_Data;
25
26 defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
27
28 if ( ! class_exists( '\WP2FA\Methods\Passkeys\Attestation_Object' ) ) {
29
30 /**
31 * Attestation object class
32 *
33 * @since 3.0.0
34 */
35 class Attestation_Object {
36
37 /**
38 * Data passed from the Authenticator.
39 *
40 * @var Authenticator_Data
41 *
42 * @since 3.0.0
43 */
44 private $authenticator_data;
45
46 /**
47 * Format of the authenticator
48 *
49 * @var Format_Base
50 *
51 * @since 3.0.0
52 */
53 private $attestation_format;
54
55 /**
56 * Format name
57 *
58 * @var string
59 *
60 * @since 3.0.0
61 */
62 private $attestation_format_name;
63
64 /**
65 * Default constructor
66 *
67 * @param string $binary - The binary string.
68 * @param array $allowed_formats - Allowed formats.
69 *
70 * @since 3.0.0
71 *
72 * @throws Web_Authn_Exception - when format is invalid.
73 */
74 public function __construct( $binary, $allowed_formats ) {
75 $enc = Cbor_Decoder::decode( $binary );
76 // validation.
77 if ( ! \is_array( $enc ) || ! \array_key_exists( 'fmt', $enc ) || ! is_string( $enc['fmt'] ) ) {
78 throw new Web_Authn_Exception( 'invalid attestation format', Web_Authn_Exception::INVALID_DATA );
79 }
80
81 if ( ! \array_key_exists( 'attStmt', $enc ) || ! \is_array( $enc['attStmt'] ) ) {
82 throw new Web_Authn_Exception( 'invalid attestation format (attStmt not available)', Web_Authn_Exception::INVALID_DATA );
83 }
84
85 if ( ! \array_key_exists( 'authData', $enc ) || ! \is_object( $enc['authData'] ) || ! ( $enc['authData'] instanceof Byte_Buffer ) ) {
86 throw new Web_Authn_Exception( 'invalid attestation format (authData not available)', Web_Authn_Exception::INVALID_DATA );
87 }
88
89 $this->authenticator_data = new Authenticator_Data( $enc['authData']->getBinaryString() );
90 $this->attestation_format_name = $enc['fmt'];
91
92 // Format ok?
93 if ( ! in_array( $this->attestation_format_name, $allowed_formats ) ) {
94 throw new Web_Authn_Exception( 'invalid atttestation format: ' . $this->attestation_format_name, Web_Authn_Exception::INVALID_DATA );
95 }
96
97 switch ( $this->attestation_format_name ) {
98 case 'android-key':
99 $this->attestation_format = new Android_Key( $enc, $this->authenticator_data );
100 break;
101 case 'android-safetynet':
102 $this->attestation_format = new Android_Safety_Net( $enc, $this->authenticator_data );
103 break;
104 case 'apple':
105 $this->attestation_format = new Apple( $enc, $this->authenticator_data );
106 break;
107 case 'fido-u2f':
108 $this->attestation_format = new U2f( $enc, $this->authenticator_data );
109 break;
110 case 'none':
111 $this->attestation_format = new None( $enc, $this->authenticator_data );
112 break;
113 case 'packed':
114 $this->attestation_format = new Packed( $enc, $this->authenticator_data );
115 break;
116 case 'tpm':
117 $this->attestation_format = new Tpm( $enc, $this->authenticator_data );
118 break;
119 default:
120 throw new Web_Authn_Exception( 'invalid attestation format: ' . $enc['fmt'], Web_Authn_Exception::INVALID_DATA );
121 }
122 }
123
124 /**
125 * Returns the attestation format name
126 *
127 * @return string
128 *
129 * @since 3.0.0
130 */
131 public function get_attestation_format_name() {
132 return $this->attestation_format_name;
133 }
134
135 /**
136 * Returns the attestation format class
137 *
138 * @return Format\Format_Base
139 *
140 * @since 3.0.0
141 */
142 public function get_attestation_format() {
143 return $this->attestation_format;
144 }
145
146 /**
147 * Returns the attestation public key in PEM format
148 *
149 * @return Authenticator_Data
150 *
151 * @since 3.0.0
152 */
153 public function get_authenticator_data() {
154 return $this->authenticator_data;
155 }
156
157 /**
158 * Returns the certificate chain as PEM
159 *
160 * @return string|null
161 *
162 * @since 3.0.0
163 */
164 public function get_certificate_chain() {
165 return $this->attestation_format->get_certificate_chain();
166 }
167
168 /**
169 * Returns the certificate issuer as string
170 *
171 * @return string
172 *
173 * @since 3.0.0
174 */
175 public function get_certificate_issuer() {
176 $pem = $this->get_certificate_pem();
177 $issuer = '';
178 if ( $pem ) {
179 $cert_info = \openssl_x509_parse( $pem );
180 if ( \is_array( $cert_info ) && \array_key_exists( 'issuer', $cert_info ) && \is_array( $cert_info['issuer'] ) ) {
181
182 $cn = $cert_info['issuer']['CN'] ?? '';
183 $o = $cert_info['issuer']['O'] ?? '';
184 $ou = $cert_info['issuer']['OU'] ?? '';
185
186 if ( $cn ) {
187 $issuer .= $cn;
188 }
189 if ( $issuer && ( $o || $ou ) ) {
190 $issuer .= ' (' . trim( $o . ' ' . $ou ) . ')';
191 } else {
192 $issuer .= trim( $o . ' ' . $ou );
193 }
194 }
195 }
196
197 return $issuer;
198 }
199
200 /**
201 * Returns the certificate subject as string
202 *
203 * @return string
204 *
205 * @since 3.0.0
206 */
207 public function get_certificate_subject() {
208 $pem = $this->get_certificate_pem();
209 $subject = '';
210 if ( $pem ) {
211 $cert_info = \openssl_x509_parse( $pem );
212 if ( \is_array( $cert_info ) && \array_key_exists( 'subject', $cert_info ) && \is_array( $cert_info['subject'] ) ) {
213
214 $cn = $cert_info['subject']['CN'] ?? '';
215 $o = $cert_info['subject']['O'] ?? '';
216 $ou = $cert_info['subject']['OU'] ?? '';
217
218 if ( $cn ) {
219 $subject .= $cn;
220 }
221 if ( $subject && ( $o || $ou ) ) {
222 $subject .= ' (' . trim( $o . ' ' . $ou ) . ')';
223 } else {
224 $subject .= trim( $o . ' ' . $ou );
225 }
226 }
227 }
228
229 return $subject;
230 }
231
232 /**
233 * Returns the key certificate in PEM format
234 *
235 * @return string
236 *
237 * @since 3.0.0
238 */
239 public function get_certificate_pem() {
240 return $this->attestation_format->get_certificate_pem();
241 }
242
243 /**
244 * Checks validity of the signature
245 *
246 * @param string $client_data_hash - The data from the client.
247 *
248 * @return bool
249 *
250 * @since 3.0.0
251 */
252 public function validate_attestation( $client_data_hash ) {
253 return $this->attestation_format->validate_attestation( $client_data_hash );
254 }
255
256 /**
257 * Validates the certificate against root certificates
258 *
259 * @param array $root_cas - Root certificate.
260 *
261 * @return boolean
262 *
263 * @since 3.0.0
264 */
265 public function validate_root_certificate( $root_cas ) {
266 return $this->attestation_format->validate_root_certificate( $root_cas );
267 }
268
269 /**
270 * Checks if the RpId-Hash is valid
271 *
272 * @param string $rp_id_hash - Hash string.
273 *
274 * @return bool
275 *
276 * @since 3.0.0
277 */
278 public function validate_rp_id_hash( $rp_id_hash ) {
279 return $rp_id_hash === $this->authenticator_data->get_rp_id_hash();
280 }
281 }
282 }
283