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 / packed.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
packed.php
209 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 /**
19 * Responsible for packed format
20 *
21 * @since 3.0.0
22 */
23 class Packed extends Format_Base {
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 * X5c certificate
45 *
46 * @var string
47 *
48 * @since 3.0.0
49 */
50 private $x5c;
51
52 /**
53 * Default constructor
54 *
55 * @param \StdClass $attestion_object - attestation object.
56 * @param \WP2FA\Admin\Methods\passkeys\Authenticator_Data $authenticator_data - Authentication data.
57 *
58 * @throws Web_Authn_Exception - if can not be created.
59 *
60 * @since 3.0.0
61 */
62 public function __construct( $attestion_object, Authenticator_Data $authenticator_data ) {
63 parent::__construct( $attestion_object, $authenticator_data );
64
65 // check packed data.
66 $att_stmt = $this->attestation_object['attStmt'];
67
68 if ( ! \array_key_exists( 'alg', $att_stmt ) || $this->_get_cose_algorithm( $att_stmt['alg'] ) === null ) {
69 throw new Web_Authn_Exception( 'unsupported alg: ' . $att_stmt['alg'], Web_Authn_Exception::INVALID_DATA );
70 }
71
72 if ( ! \array_key_exists( 'sig', $att_stmt ) || ! \is_object( $att_stmt['sig'] ) || ! ( $att_stmt['sig'] instanceof Byte_Buffer ) ) {
73 throw new Web_Authn_Exception( 'no signature found', Web_Authn_Exception::INVALID_DATA );
74 }
75
76 $this->_alg = $att_stmt['alg'];
77 $this->_signature = $att_stmt['sig']->getBinaryString();
78
79 // certificate for validation.
80 if ( \array_key_exists( 'x5c', $att_stmt ) && \is_array( $att_stmt['x5c'] ) && \count( $att_stmt['x5c'] ) > 0 ) {
81
82 // The attestation certificate attestn_cert MUST be the first element in the array.
83 $attestn_cert = array_shift( $att_stmt['x5c'] );
84
85 if ( ! ( $attestn_cert instanceof Byte_Buffer ) ) {
86 throw new Web_Authn_Exception( 'invalid x5c certificate', Web_Authn_Exception::INVALID_DATA );
87 }
88
89 $this->x5c = $attestn_cert->getBinaryString();
90
91 // certificate chain.
92 foreach ( $att_stmt['x5c'] as $chain ) {
93 if ( $chain instanceof Byte_Buffer ) {
94 $this->x5c_chain[] = $chain->getBinaryString();
95 }
96 }
97 }
98 }
99
100 /**
101 * Returns the key certificate in PEM format
102 *
103 * @return string|null
104 *
105 * @since 3.0.0
106 */
107 public function get_certificate_pem() {
108 if ( ! $this->x5c ) {
109 return null;
110 }
111 return $this->_create_certificate_pem( $this->x5c );
112 }
113
114 /**
115 * Validates the attestation
116 *
117 * @param string $client_data_hash - Hash collected.
118 *
119 * @return bool
120 *
121 * @since 3.0.0
122 */
123 public function validate_attestation( $client_data_hash ) {
124 if ( $this->x5c ) {
125 return $this->_validate_over_x5c( $client_data_hash );
126 } else {
127 return $this->_validate_self_attestation( $client_data_hash );
128 }
129 }
130
131 /**
132 * Validates the certificate against root certificates
133 *
134 * @param array $root_cas - Array with values.
135 *
136 * @return boolean
137 *
138 * @throws Web_Authn_Exception - Throws exception.
139 *
140 * @since 3.0.0
141 */
142 public function validate_root_certificate( $root_cas ) {
143 if ( ! $this->x5c ) {
144 return false;
145 }
146
147 $chain_c = $this->_create_x5c_chain_file();
148 if ( $chain_c ) {
149 $root_cas[] = $chain_c;
150 }
151
152 $v = \openssl_x509_checkpurpose( $this->get_certificate_pem(), -1, $root_cas );
153 if ( -1 === $v ) {
154 throw new Web_Authn_Exception( 'error on validating root certificate: ' . \openssl_error_string(), Web_Authn_Exception::CERTIFICATE_NOT_TRUSTED );
155 }
156 return $v;
157 }
158
159 /**
160 * Validate if x5c is present
161 *
162 * @param string $client_data_hash - Hash collected.
163 *
164 * @return bool
165 *
166 * @throws Web_Authn_Exception - Throws exception.
167 *
168 * @since 3.0.0
169 */
170 protected function _validate_over_x5c( $client_data_hash ) {
171 $public_key = \openssl_pkey_get_public( $this->get_certificate_pem() );
172
173 if ( false === $public_key ) {
174 throw new Web_Authn_Exception( 'invalid public key: ' . \openssl_error_string(), Web_Authn_Exception::INVALID_PUBLIC_KEY );
175 }
176
177 // Verify that sig is a valid signature over the concatenation of Authenticator_Data and client_data_hash
178 // using the attestation public key in attestn_cert with the algorithm specified in alg.
179 $data_to_verify = $this->authenticator_data->get_binary();
180 $data_to_verify .= $client_data_hash;
181
182 $cose_algorithm = $this->_get_cose_algorithm( $this->_alg );
183
184 // check certificate.
185 return \openssl_verify( $data_to_verify, $this->_signature, $public_key, $cose_algorithm->openssl ) === 1;
186 }
187
188 /**
189 * Validate if self attestation is in use
190 *
191 * @param string $client_data_hash - Hash collected.
192 *
193 * @return bool
194 *
195 * @since 3.0.0
196 */
197 protected function _validate_self_attestation( $client_data_hash ) {
198 // Verify that sig is a valid signature over the concatenation of Authenticator_Data and client_data_hash
199 // using the credential public key with alg.
200 $data_to_verify = $this->authenticator_data->get_binary();
201 $data_to_verify .= $client_data_hash;
202
203 $public_key = $this->authenticator_data->get_public_key_pem();
204
205 // check certificate.
206 return \openssl_verify( $data_to_verify, $this->_signature, $public_key, OPENSSL_ALGO_SHA256 ) === 1;
207 }
208 }
209