@@ -130,7 +130,7 @@ |
||
130 | 130 | $frontendData = []; |
131 | 131 | $frontendData['challengeEncoded'] = $challengeEncoded; |
132 | 132 | $frontendData['state'] = []; |
133 | - foreach (['Source', 'FIDO2Scope','FIDO2Username','FIDO2Displayname','requestTokenModel'] as $stateItem) { |
|
133 | + foreach (['Source', 'FIDO2Scope', 'FIDO2Username', 'FIDO2Displayname', 'requestTokenModel'] as $stateItem) { |
|
134 | 134 | $frontendData['state'][$stateItem] = $state[$stateItem]; |
135 | 135 | } |
136 | 136 |
@@ -176,7 +176,7 @@ |
||
176 | 176 | |
177 | 177 | if ($debugEnabled) { |
178 | 178 | $response = new StreamedResponse(); |
179 | - $response->setCallback(function ($authObject, $state) { |
|
179 | + $response->setCallback(function($authObject, $state) { |
|
180 | 180 | echo $authObject->getDebugBuffer(); |
181 | 181 | echo $authObject->getValidateBuffer(); |
182 | 182 | echo "Debug mode, not continuing to " . ($state['FIDO2WantsRegister'] ? "credential registration page." : "destination."); |
@@ -202,7 +202,7 @@ |
||
202 | 202 | 'private' => false, |
203 | 203 | ]); |
204 | 204 | $response->setExpires(new DateTime('Thu, 19 Nov 1981 08:52:00 GMT')); |
205 | - */ |
|
205 | + */ |
|
206 | 206 | |
207 | 207 | return $response; |
208 | 208 | } |
@@ -183,7 +183,7 @@ |
||
183 | 183 | $id = $this->authState::saveState($state, 'webauthn:request'); |
184 | 184 | if ($debugEnabled === true) { |
185 | 185 | $response = new StreamedResponse(); |
186 | - $response->setCallback(function ($regObject, $id) { |
|
186 | + $response->setCallback(function($regObject, $id) { |
|
187 | 187 | echo $regObject->getDebugBuffer(); |
188 | 188 | echo $regObject->getValidateBuffer(); |
189 | 189 | echo "<form id='regform' method='POST' action='" . |
@@ -202,7 +202,7 @@ |
||
202 | 202 | 'private' => false, |
203 | 203 | ]); |
204 | 204 | $response->setExpires(new DateTime('Thu, 19 Nov 1981 08:52:00 GMT')); |
205 | - */ |
|
205 | + */ |
|
206 | 206 | |
207 | 207 | return $response; |
208 | 208 | } |
@@ -202,7 +202,7 @@ |
||
202 | 202 | 'private' => false, |
203 | 203 | ]); |
204 | 204 | $response->setExpires(new DateTime('Thu, 19 Nov 1981 08:52:00 GMT')); |
205 | - */ |
|
205 | + */ |
|
206 | 206 | |
207 | 207 | return $response; |
208 | 208 | } |
@@ -118,9 +118,9 @@ discard block |
||
118 | 118 | case "android-safetynet": |
119 | 119 | $this->validateAttestationFormatAndroidSafetyNet($attestationArray); |
120 | 120 | break; |
121 | - case "apple": |
|
122 | - $this->validateAttestationFormatApple($attestationArray); |
|
123 | - break; |
|
121 | + case "apple": |
|
122 | + $this->validateAttestationFormatApple($attestationArray); |
|
123 | + break; |
|
124 | 124 | case "tpm": |
125 | 125 | case "android-key": |
126 | 126 | $this->fail("Attestation format " . $attestationArray['fmt'] . " validation not supported right now."); |
@@ -158,9 +158,9 @@ discard block |
||
158 | 158 | private function validateAttestationFormatApple(array $attestationArray): void |
159 | 159 | { |
160 | 160 | |
161 | - // found at: https://www.apple.com/certificateauthority/private/ |
|
161 | + // found at: https://www.apple.com/certificateauthority/private/ |
|
162 | 162 | |
163 | - $APPLE_WEBAUTHN_ROOT_CA = "-----BEGIN CERTIFICATE----- |
|
163 | + $APPLE_WEBAUTHN_ROOT_CA = "-----BEGIN CERTIFICATE----- |
|
164 | 164 | MIICEjCCAZmgAwIBAgIQaB0BbHo84wIlpQGUKEdXcTAKBggqhkjOPQQDAzBLMR8w |
165 | 165 | HQYDVQQDDBZBcHBsZSBXZWJBdXRobiBSb290IENBMRMwEQYDVQQKDApBcHBsZSBJ |
166 | 166 | bmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMB4XDTIwMDMxODE4MjEzMloXDTQ1MDMx |
@@ -175,66 +175,66 @@ discard block |
||
175 | 175 | 1bWeT0vT |
176 | 176 | -----END CERTIFICATE-----"; |
177 | 177 | // § 8.8 Bullet 1 of the draft spec at https://pr-preview.s3.amazonaws.com/alanwaketan/webauthn/pull/1491.html#sctn-apple-anonymous-attestation |
178 | - // draft implemented in state of 11 Feb 2021 |
|
178 | + // draft implemented in state of 11 Feb 2021 |
|
179 | 179 | |
180 | - // I can't help but notice that the verification procedure does NOTHING with CA certs from the chain, nor is there a root to validate to! |
|
181 | - // Found the root CA with Google, see above, and will perform chain validation even if the spec doesn't say so. |
|
180 | + // I can't help but notice that the verification procedure does NOTHING with CA certs from the chain, nor is there a root to validate to! |
|
181 | + // Found the root CA with Google, see above, and will perform chain validation even if the spec doesn't say so. |
|
182 | 182 | |
183 | - // first, clear the openssl error backlog. We might need error data in case things go sideways. |
|
184 | - while(openssl_error_string() !== false); |
|
183 | + // first, clear the openssl error backlog. We might need error data in case things go sideways. |
|
184 | + while(openssl_error_string() !== false); |
|
185 | 185 | |
186 | 186 | $stmtDecoded = $attestationArray['attStmt']; |
187 | - if (!isset($stmtDecoded['x5c'])) { |
|
188 | - $this->fail("Apple attestation statement does not contain an x5c attestation statement!"); |
|
189 | - } |
|
190 | - // § 8.8 Bullet 2 |
|
187 | + if (!isset($stmtDecoded['x5c'])) { |
|
188 | + $this->fail("Apple attestation statement does not contain an x5c attestation statement!"); |
|
189 | + } |
|
190 | + // § 8.8 Bullet 2 |
|
191 | 191 | $nonceToHash = $attestationArray['authData'] . $this->clientDataHash; |
192 | - // § 8.8 Bullet 3 |
|
193 | - $nonce = hash("sha256", $nonceToHash, TRUE); // does raw_output have to be FALSE or TRUE? |
|
192 | + // § 8.8 Bullet 3 |
|
193 | + $nonce = hash("sha256", $nonceToHash, TRUE); // does raw_output have to be FALSE or TRUE? |
|
194 | 194 | $certProps = openssl_x509_parse(Utils\Crypto::der2pem($stmtDecoded['x5c'][0])); |
195 | - // § 8.8 Bullet 4 |
|
195 | + // § 8.8 Bullet 4 |
|
196 | 196 | if ( |
197 | - !isset($certProps['extensions']['1.2.840.113635.100.8.2']) |
|
197 | + !isset($certProps['extensions']['1.2.840.113635.100.8.2']) |
|
198 | 198 | || empty($certProps['extensions']['1.2.840.113635.100.8.2']) |
199 | 199 | ) { |
200 | 200 | $this->fail( "The required nonce value is not present in the OID." ); |
201 | 201 | } |
202 | - $toCompare = substr($certProps['extensions']['1.2.840.113635.100.8.2'], 6); |
|
203 | - if ($nonce != $toCompare) { |
|
204 | - $this->fail("There is a mismatch between the nonce and the OID (XXX $nonce XXX , XXX $toCompare XXX )."); |
|
205 | - } |
|
206 | - |
|
207 | - // chain validation first |
|
208 | - foreach ( $stmtDecoded['x5c'] as $runIndex => $runCert ) { |
|
209 | - if (isset($stmtDecoded['x5c'][$runIndex + 1])) { // there is a next cert, so follow the chain |
|
210 | - $certResource = openssl_x509_read(Utils\Crypto::der2pem($runCert)); |
|
211 | - $signerPubKey = openssl_pkey_get_public(Utils\Crypto::der2pem($stmtDecoded['x5c'][$runIndex + 1])); |
|
212 | - if (openssl_x509_verify($certResource, $signerPubKey) != 1) { |
|
213 | - $this->fail("Error during chain validation of the attestation certificate (while validating cert #$runIndex, which is " |
|
202 | + $toCompare = substr($certProps['extensions']['1.2.840.113635.100.8.2'], 6); |
|
203 | + if ($nonce != $toCompare) { |
|
204 | + $this->fail("There is a mismatch between the nonce and the OID (XXX $nonce XXX , XXX $toCompare XXX )."); |
|
205 | + } |
|
206 | + |
|
207 | + // chain validation first |
|
208 | + foreach ( $stmtDecoded['x5c'] as $runIndex => $runCert ) { |
|
209 | + if (isset($stmtDecoded['x5c'][$runIndex + 1])) { // there is a next cert, so follow the chain |
|
210 | + $certResource = openssl_x509_read(Utils\Crypto::der2pem($runCert)); |
|
211 | + $signerPubKey = openssl_pkey_get_public(Utils\Crypto::der2pem($stmtDecoded['x5c'][$runIndex + 1])); |
|
212 | + if (openssl_x509_verify($certResource, $signerPubKey) != 1) { |
|
213 | + $this->fail("Error during chain validation of the attestation certificate (while validating cert #$runIndex, which is " |
|
214 | 214 | . Utils\Crypto::der2pem($runCert) |
215 | 215 | . "; next cert was " |
216 | 216 | . Utils\Crypto::der2pem($stmtDecoded['x5c'][$runIndex + 1])); |
217 | - } |
|
218 | - } else { // last cert, compare to the root |
|
219 | - $certResource = openssl_x509_read(Utils\Crypto::der2pem($runCert)); |
|
220 | - $signerPubKey = openssl_pkey_get_public($APPLE_WEBAUTHN_ROOT_CA); |
|
221 | - if (openssl_x509_verify($certResource, $signerPubKey) != 1) { |
|
217 | + } |
|
218 | + } else { // last cert, compare to the root |
|
219 | + $certResource = openssl_x509_read(Utils\Crypto::der2pem($runCert)); |
|
220 | + $signerPubKey = openssl_pkey_get_public($APPLE_WEBAUTHN_ROOT_CA); |
|
221 | + if (openssl_x509_verify($certResource, $signerPubKey) != 1) { |
|
222 | 222 | $this->fail("Error during root CA validation of the attestation chain certificate, which is ".Utils\Crypto::der2pem($runCert)); |
223 | 223 | } |
224 | - } |
|
225 | - } |
|
224 | + } |
|
225 | + } |
|
226 | 226 | |
227 | 227 | $keyResource = openssl_pkey_get_public(Utils\Crypto::der2pem($stmtDecoded['x5c'][0])); |
228 | 228 | if ($keyResource === FALSE) { |
229 | - $this->fail("Did not get a parseable X.509 structure out of the Apple attestation statement - x5c nr. 0 statement was: XXX " |
|
229 | + $this->fail("Did not get a parseable X.509 structure out of the Apple attestation statement - x5c nr. 0 statement was: XXX " |
|
230 | 230 | . $stmtDecoded['x5c'][0] |
231 | 231 | . " XXX; PEM equivalent is " |
232 | 232 | . Utils\Crypto::der2pem($stmtDecoded['x5c'][0]) |
233 | 233 | . ". OpenSSL error: " |
234 | 234 | . openssl_error_string() |
235 | 235 | ); |
236 | - } |
|
237 | - // $this->credential is a public key in CBOR, not "PEM". We need to convert it first. |
|
236 | + } |
|
237 | + // $this->credential is a public key in CBOR, not "PEM". We need to convert it first. |
|
238 | 238 | $keyArray = $this->cborDecode(hex2bin($this->credential)); |
239 | 239 | $keyObject = new Ec2Key($keyArray); |
240 | 240 | $credentialResource = openssl_pkey_get_public($keyObject->asPEM()); |
@@ -248,20 +248,20 @@ discard block |
||
248 | 248 | . openssl_error_string() |
249 | 249 | ); |
250 | 250 | } |
251 | - // § 8.8 Bullet 5 |
|
252 | - $credentialDetails = openssl_pkey_get_details($credentialResource); |
|
253 | - $keyDetails = openssl_pkey_get_details($keyResource); |
|
254 | - if ( $credentialDetails['bits'] != $keyDetails['bits'] || |
|
251 | + // § 8.8 Bullet 5 |
|
252 | + $credentialDetails = openssl_pkey_get_details($credentialResource); |
|
253 | + $keyDetails = openssl_pkey_get_details($keyResource); |
|
254 | + if ( $credentialDetails['bits'] != $keyDetails['bits'] || |
|
255 | 255 | $credentialDetails['key'] != $keyDetails['key'] || |
256 | 256 | $credentialDetails['type'] != $keyDetails['type'] ) { |
257 | - $this->fail("The credential public key does not match the certificate public key in attestationData. (" |
|
258 | - . $credentialDetails['key'] |
|
259 | - . " - " |
|
260 | - . $keyDetails['key'] |
|
261 | - . ")"); |
|
262 | - } |
|
263 | - $this->pass("Apple attestation format verification passed."); |
|
264 | - return; |
|
257 | + $this->fail("The credential public key does not match the certificate public key in attestationData. (" |
|
258 | + . $credentialDetails['key'] |
|
259 | + . " - " |
|
260 | + . $keyDetails['key'] |
|
261 | + . ")"); |
|
262 | + } |
|
263 | + $this->pass("Apple attestation format verification passed."); |
|
264 | + return; |
|
265 | 265 | } |
266 | 266 | |
267 | 267 | /** |
@@ -181,7 +181,7 @@ discard block |
||
181 | 181 | // Found the root CA with Google, see above, and will perform chain validation even if the spec doesn't say so. |
182 | 182 | |
183 | 183 | // first, clear the openssl error backlog. We might need error data in case things go sideways. |
184 | - while(openssl_error_string() !== false); |
|
184 | + while (openssl_error_string() !== false); |
|
185 | 185 | |
186 | 186 | $stmtDecoded = $attestationArray['attStmt']; |
187 | 187 | if (!isset($stmtDecoded['x5c'])) { |
@@ -197,7 +197,7 @@ discard block |
||
197 | 197 | !isset($certProps['extensions']['1.2.840.113635.100.8.2']) |
198 | 198 | || empty($certProps['extensions']['1.2.840.113635.100.8.2']) |
199 | 199 | ) { |
200 | - $this->fail( "The required nonce value is not present in the OID." ); |
|
200 | + $this->fail("The required nonce value is not present in the OID."); |
|
201 | 201 | } |
202 | 202 | $toCompare = substr($certProps['extensions']['1.2.840.113635.100.8.2'], 6); |
203 | 203 | if ($nonce != $toCompare) { |
@@ -205,7 +205,7 @@ discard block |
||
205 | 205 | } |
206 | 206 | |
207 | 207 | // chain validation first |
208 | - foreach ( $stmtDecoded['x5c'] as $runIndex => $runCert ) { |
|
208 | + foreach ($stmtDecoded['x5c'] as $runIndex => $runCert) { |
|
209 | 209 | if (isset($stmtDecoded['x5c'][$runIndex + 1])) { // there is a next cert, so follow the chain |
210 | 210 | $certResource = openssl_x509_read(Utils\Crypto::der2pem($runCert)); |
211 | 211 | $signerPubKey = openssl_pkey_get_public(Utils\Crypto::der2pem($stmtDecoded['x5c'][$runIndex + 1])); |
@@ -219,7 +219,7 @@ discard block |
||
219 | 219 | $certResource = openssl_x509_read(Utils\Crypto::der2pem($runCert)); |
220 | 220 | $signerPubKey = openssl_pkey_get_public($APPLE_WEBAUTHN_ROOT_CA); |
221 | 221 | if (openssl_x509_verify($certResource, $signerPubKey) != 1) { |
222 | - $this->fail("Error during root CA validation of the attestation chain certificate, which is ".Utils\Crypto::der2pem($runCert)); |
|
222 | + $this->fail("Error during root CA validation of the attestation chain certificate, which is " . Utils\Crypto::der2pem($runCert)); |
|
223 | 223 | } |
224 | 224 | } |
225 | 225 | } |
@@ -251,9 +251,9 @@ discard block |
||
251 | 251 | // § 8.8 Bullet 5 |
252 | 252 | $credentialDetails = openssl_pkey_get_details($credentialResource); |
253 | 253 | $keyDetails = openssl_pkey_get_details($keyResource); |
254 | - if ( $credentialDetails['bits'] != $keyDetails['bits'] || |
|
255 | - $credentialDetails['key'] != $keyDetails['key'] || |
|
256 | - $credentialDetails['type'] != $keyDetails['type'] ) { |
|
254 | + if ($credentialDetails['bits'] != $keyDetails['bits'] || |
|
255 | + $credentialDetails['key'] != $keyDetails['key'] || |
|
256 | + $credentialDetails['type'] != $keyDetails['type']) { |
|
257 | 257 | $this->fail("The credential public key does not match the certificate public key in attestationData. (" |
258 | 258 | . $credentialDetails['key'] |
259 | 259 | . " - " |
@@ -90,8 +90,8 @@ |
||
90 | 90 | $this->stateData->scope = $moduleConfig['scope']; |
91 | 91 | } |
92 | 92 | |
93 | - // Set the derived scope so we can compare it to the sent host at a later point |
|
94 | - $httpUtils = new Utils\HTTP(); |
|
93 | + // Set the derived scope so we can compare it to the sent host at a later point |
|
94 | + $httpUtils = new Utils\HTTP(); |
|
95 | 95 | $baseurl = $httpUtils->getSelfHost(); |
96 | 96 | $hostname = parse_url($baseurl, PHP_URL_HOST); |
97 | 97 | if ($hostname !== null) { |
@@ -14,7 +14,7 @@ |
||
14 | 14 | public static function saveStateAndRedirect(array &$state): void |
15 | 15 | { |
16 | 16 | $id = Auth\State::saveState($state, 'webauthn:request'); |
17 | - $url = Module::getModuleURL('webauthn/webauthn'); |
|
17 | + $url = Module::getModuleURL('webauthn/webauthn'); |
|
18 | 18 | $httpUtils = new Utils\HTTP(); |
19 | 19 | $httpUtils->redirectTrustedURL($url, ['StateId' => $id]); |
20 | 20 | } |