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 / u2f.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
u2f.php
157 lines
1 <?php
2 /**
3 * Passkeys formatters
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 namespace WP2FA\Passkeys\Format;
13
14 use WP2FA\Methods\Passkeys\Byte_Buffer;
15 use WP2FA\Methods\Passkeys\Web_Authn_Exception;
16 use WP2FA\Admin\Methods\passkeys\Authenticator_Data;
17 /**
18 * Responsible for u2f format
19 *
20 * @since 3.0.0
21 */
22 class U2f extends Format_Base {
23
24 /**
25 * Algorithm to be used
26 *
27 * @var string
28 *
29 * @since 3.0.0
30 */
31 private $_alg = -7;
32
33 /**
34 * Signature
35 *
36 * @var string
37 *
38 * @since 3.0.0
39 */
40 private $_signature;
41
42 /**
43 * X5c certificate
44 *
45 * @var string
46 *
47 * @since 3.0.0
48 */
49 private $x5c;
50
51 /**
52 * Default constructor
53 *
54 * @param \StdClass $attestion_object - attestation object.
55 * @param \WP2FA\Admin\Methods\passkeys\Authenticator_Data $authenticator_data - Authentication data.
56 *
57 * @throws Web_Authn_Exception - if can not be created.
58 *
59 * @since 3.0.0
60 */
61 public function __construct( $attestion_object, Authenticator_Data $authenticator_data ) {
62 parent::__construct( $attestion_object, $authenticator_data );
63
64 // check u2f data.
65 $att_stmt = $this->attestation_object['attStmt'];
66
67 if ( \array_key_exists( 'alg', $att_stmt ) && $att_stmt['alg'] !== $this->_alg ) {
68 throw new Web_Authn_Exception( 'u2f only accepts algorithm -7 ("ES256"), but got ' . $att_stmt['alg'], Web_Authn_Exception::INVALID_DATA );
69 }
70
71 if ( ! \array_key_exists( 'sig', $att_stmt ) || ! \is_object( $att_stmt['sig'] ) || ! ( $att_stmt['sig'] instanceof Byte_Buffer ) ) {
72 throw new Web_Authn_Exception( 'no signature found', Web_Authn_Exception::INVALID_DATA );
73 }
74
75 if ( ! \array_key_exists( 'x5c', $att_stmt ) || ! \is_array( $att_stmt['x5c'] ) || \count( $att_stmt['x5c'] ) !== 1 ) {
76 throw new Web_Authn_Exception( 'invalid x5c certificate', Web_Authn_Exception::INVALID_DATA );
77 }
78
79 if ( ! \is_object( $att_stmt['x5c'][0] ) || ! ( $att_stmt['x5c'][0] instanceof Byte_Buffer ) ) {
80 throw new Web_Authn_Exception( 'invalid x5c certificate', Web_Authn_Exception::INVALID_DATA );
81 }
82
83 $this->_signature = $att_stmt['sig']->getBinaryString();
84 $this->x5c = $att_stmt['x5c'][0]->getBinaryString();
85 }
86
87
88 /**
89 * Returns the key certificate in PEM format
90 *
91 * @return string
92 *
93 * @since 3.0.0
94 */
95 public function get_certificate_pem() {
96 $pem = '-----BEGIN CERTIFICATE-----' . "\n";
97 $pem .= \chunk_split( \base64_encode( $this->x5c ), 64, "\n" );
98 $pem .= '-----END CERTIFICATE-----' . "\n";
99 return $pem;
100 }
101
102 /**
103 * Validates the attestation
104 *
105 * @param string $client_data_hash - Hash collected.
106 *
107 * @return bool
108 *
109 * @throws Web_Authn_Exception - Throws exception if validation fails.
110 *
111 * @since 3.0.0
112 */
113 public function validate_attestation( $client_data_hash ) {
114 $public_key = \openssl_pkey_get_public( $this->get_certificate_pem() );
115
116 if ( false === $public_key ) {
117 throw new Web_Authn_Exception( 'invalid public key: ' . \openssl_error_string(), Web_Authn_Exception::INVALID_PUBLIC_KEY );
118 }
119
120 // Let verificationData be the concatenation of (0x00 || rpIdHash || client_data_hash || credentialId || publicKeyU2F).
121 $data_to_verify = "\x00";
122 $data_to_verify .= $this->authenticator_data->get_rp_id_hash();
123 $data_to_verify .= $client_data_hash;
124 $data_to_verify .= $this->authenticator_data->get_credential_id();
125 $data_to_verify .= $this->authenticator_data->get_public_key_u2f();
126
127 $cose_algorithm = $this->_get_cose_algorithm( $this->_alg );
128
129 // check certificate.
130 return \openssl_verify( $data_to_verify, $this->_signature, $public_key, $cose_algorithm->openssl ) === 1;
131 }
132
133 /**
134 * Validates the certificate against root certificates
135 *
136 * @param array $root_cas - Array with values.
137 *
138 * @return boolean
139 *
140 * @throws Web_Authn_Exception - Throws exception.
141 *
142 * @since 3.0.0
143 */
144 public function validate_root_certificate( $root_cas ) {
145 $chain_c = $this->_create_x5c_chain_file();
146 if ( $chain_c ) {
147 $root_cas[] = $chain_c;
148 }
149
150 $v = \openssl_x509_checkpurpose( $this->get_certificate_pem(), -1, $root_cas );
151 if ( -1 === $v ) {
152 throw new Web_Authn_Exception( 'error on validating root certificate: ' . \openssl_error_string(), Web_Authn_Exception::CERTIFICATE_NOT_TRUSTED );
153 }
154 return $v;
155 }
156 }
157