@@ -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 | } |
@@ -149,10 +149,10 @@ discard block |
||
149 | 149 | /** |
150 | 150 | * §7.2 STEP 18 : detect physical object cloning on the token |
151 | 151 | */ |
152 | - $counter = $authObject->getCounter(); |
|
153 | - if ($previousCounter == 0 && $counter == 0) { |
|
152 | + $counter = $authObject->getCounter(); |
|
153 | + if ($previousCounter == 0 && $counter == 0) { |
|
154 | 154 | // no cloning check, it is a brand new token |
155 | - } elseif ($counter > $previousCounter) { |
|
155 | + } elseif ($counter > $previousCounter) { |
|
156 | 156 | // Signature counter was incremented compared to last time, good |
157 | 157 | $store = $state['webauthn:store']; |
158 | 158 | $store->updateSignCount($oneToken[0], $counter); |
@@ -208,7 +208,7 @@ discard block |
||
208 | 208 | 'private' => false, |
209 | 209 | ]); |
210 | 210 | $response->setExpires(new DateTime('Thu, 19 Nov 1981 08:52:00 GMT')); |
211 | - */ |
|
211 | + */ |
|
212 | 212 | |
213 | 213 | return $response; |
214 | 214 | } |
@@ -122,9 +122,9 @@ discard block |
||
122 | 122 | case "android-safetynet": |
123 | 123 | $this->validateAttestationFormatAndroidSafetyNet($attestationArray); |
124 | 124 | break; |
125 | - case "apple": |
|
126 | - $this->validateAttestationFormatApple($attestationArray); |
|
127 | - break; |
|
125 | + case "apple": |
|
126 | + $this->validateAttestationFormatApple($attestationArray); |
|
127 | + break; |
|
128 | 128 | case "tpm": |
129 | 129 | case "android-key": |
130 | 130 | $this->fail("Attestation format " . $attestationArray['fmt'] . " validation not supported right now."); |
@@ -161,9 +161,9 @@ discard block |
||
161 | 161 | private function validateAttestationFormatApple(array $attestationArray): void |
162 | 162 | { |
163 | 163 | |
164 | - // found at: https://www.apple.com/certificateauthority/private/ |
|
164 | + // found at: https://www.apple.com/certificateauthority/private/ |
|
165 | 165 | |
166 | - $APPLE_WEBAUTHN_ROOT_CA = "-----BEGIN CERTIFICATE----- |
|
166 | + $APPLE_WEBAUTHN_ROOT_CA = "-----BEGIN CERTIFICATE----- |
|
167 | 167 | MIICEjCCAZmgAwIBAgIQaB0BbHo84wIlpQGUKEdXcTAKBggqhkjOPQQDAzBLMR8w |
168 | 168 | HQYDVQQDDBZBcHBsZSBXZWJBdXRobiBSb290IENBMRMwEQYDVQQKDApBcHBsZSBJ |
169 | 169 | bmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMB4XDTIwMDMxODE4MjEzMloXDTQ1MDMx |
@@ -178,66 +178,66 @@ discard block |
||
178 | 178 | 1bWeT0vT |
179 | 179 | -----END CERTIFICATE-----"; |
180 | 180 | // § 8.8 Bullet 1 of the draft spec at https://pr-preview.s3.amazonaws.com/alanwaketan/webauthn/pull/1491.html#sctn-apple-anonymous-attestation |
181 | - // draft implemented in state of 11 Feb 2021 |
|
181 | + // draft implemented in state of 11 Feb 2021 |
|
182 | 182 | |
183 | - // 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! |
|
184 | - // Found the root CA with Google, see above, and will perform chain validation even if the spec doesn't say so. |
|
183 | + // 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! |
|
184 | + // Found the root CA with Google, see above, and will perform chain validation even if the spec doesn't say so. |
|
185 | 185 | |
186 | - // first, clear the openssl error backlog. We might need error data in case things go sideways. |
|
187 | - while(openssl_error_string() !== false); |
|
186 | + // first, clear the openssl error backlog. We might need error data in case things go sideways. |
|
187 | + while(openssl_error_string() !== false); |
|
188 | 188 | |
189 | 189 | $stmtDecoded = $attestationArray['attStmt']; |
190 | - if (!isset($stmtDecoded['x5c'])) { |
|
191 | - $this->fail("Apple attestation statement does not contain an x5c attestation statement!"); |
|
192 | - } |
|
193 | - // § 8.8 Bullet 2 |
|
190 | + if (!isset($stmtDecoded['x5c'])) { |
|
191 | + $this->fail("Apple attestation statement does not contain an x5c attestation statement!"); |
|
192 | + } |
|
193 | + // § 8.8 Bullet 2 |
|
194 | 194 | $nonceToHash = $attestationArray['authData'] . $this->clientDataHash; |
195 | - // § 8.8 Bullet 3 |
|
196 | - $nonce = hash("sha256", $nonceToHash, TRUE); // does raw_output have to be FALSE or TRUE? |
|
195 | + // § 8.8 Bullet 3 |
|
196 | + $nonce = hash("sha256", $nonceToHash, TRUE); // does raw_output have to be FALSE or TRUE? |
|
197 | 197 | $certProps = openssl_x509_parse(Utils\Crypto::der2pem($stmtDecoded['x5c'][0])); |
198 | - // § 8.8 Bullet 4 |
|
198 | + // § 8.8 Bullet 4 |
|
199 | 199 | if ( |
200 | - !isset($certProps['extensions']['1.2.840.113635.100.8.2']) |
|
200 | + !isset($certProps['extensions']['1.2.840.113635.100.8.2']) |
|
201 | 201 | || empty($certProps['extensions']['1.2.840.113635.100.8.2']) |
202 | 202 | ) { |
203 | 203 | $this->fail( "The required nonce value is not present in the OID." ); |
204 | 204 | } |
205 | - $toCompare = substr($certProps['extensions']['1.2.840.113635.100.8.2'], 6); |
|
206 | - if ($nonce != $toCompare) { |
|
207 | - $this->fail("There is a mismatch between the nonce and the OID (XXX $nonce XXX , XXX $toCompare XXX )."); |
|
208 | - } |
|
205 | + $toCompare = substr($certProps['extensions']['1.2.840.113635.100.8.2'], 6); |
|
206 | + if ($nonce != $toCompare) { |
|
207 | + $this->fail("There is a mismatch between the nonce and the OID (XXX $nonce XXX , XXX $toCompare XXX )."); |
|
208 | + } |
|
209 | 209 | |
210 | - // chain validation first |
|
211 | - foreach ( $stmtDecoded['x5c'] as $runIndex => $runCert ) { |
|
212 | - if (isset($stmtDecoded['x5c'][$runIndex + 1])) { // there is a next cert, so follow the chain |
|
213 | - $certResource = openssl_x509_read(Utils\Crypto::der2pem($runCert)); |
|
214 | - $signerPubKey = openssl_pkey_get_public(Utils\Crypto::der2pem($stmtDecoded['x5c'][$runIndex + 1])); |
|
215 | - if (openssl_x509_verify($certResource, $signerPubKey) != 1) { |
|
216 | - $this->fail("Error during chain validation of the attestation certificate (while validating cert #$runIndex, which is " |
|
210 | + // chain validation first |
|
211 | + foreach ( $stmtDecoded['x5c'] as $runIndex => $runCert ) { |
|
212 | + if (isset($stmtDecoded['x5c'][$runIndex + 1])) { // there is a next cert, so follow the chain |
|
213 | + $certResource = openssl_x509_read(Utils\Crypto::der2pem($runCert)); |
|
214 | + $signerPubKey = openssl_pkey_get_public(Utils\Crypto::der2pem($stmtDecoded['x5c'][$runIndex + 1])); |
|
215 | + if (openssl_x509_verify($certResource, $signerPubKey) != 1) { |
|
216 | + $this->fail("Error during chain validation of the attestation certificate (while validating cert #$runIndex, which is " |
|
217 | 217 | . Utils\Crypto::der2pem($runCert) |
218 | 218 | . "; next cert was " |
219 | 219 | . Utils\Crypto::der2pem($stmtDecoded['x5c'][$runIndex + 1])); |
220 | - } |
|
221 | - } else { // last cert, compare to the root |
|
222 | - $certResource = openssl_x509_read(Utils\Crypto::der2pem($runCert)); |
|
223 | - $signerPubKey = openssl_pkey_get_public($APPLE_WEBAUTHN_ROOT_CA); |
|
224 | - if (openssl_x509_verify($certResource, $signerPubKey) != 1) { |
|
220 | + } |
|
221 | + } else { // last cert, compare to the root |
|
222 | + $certResource = openssl_x509_read(Utils\Crypto::der2pem($runCert)); |
|
223 | + $signerPubKey = openssl_pkey_get_public($APPLE_WEBAUTHN_ROOT_CA); |
|
224 | + if (openssl_x509_verify($certResource, $signerPubKey) != 1) { |
|
225 | 225 | $this->fail("Error during root CA validation of the attestation chain certificate, which is ".Utils\Crypto::der2pem($runCert)); |
226 | 226 | } |
227 | - } |
|
228 | - } |
|
227 | + } |
|
228 | + } |
|
229 | 229 | |
230 | 230 | $keyResource = openssl_pkey_get_public(Utils\Crypto::der2pem($stmtDecoded['x5c'][0])); |
231 | 231 | if ($keyResource === FALSE) { |
232 | - $this->fail("Did not get a parseable X.509 structure out of the Apple attestation statement - x5c nr. 0 statement was: XXX " |
|
232 | + $this->fail("Did not get a parseable X.509 structure out of the Apple attestation statement - x5c nr. 0 statement was: XXX " |
|
233 | 233 | . $stmtDecoded['x5c'][0] |
234 | 234 | . " XXX; PEM equivalent is " |
235 | 235 | . Utils\Crypto::der2pem($stmtDecoded['x5c'][0]) |
236 | 236 | . ". OpenSSL error: " |
237 | 237 | . openssl_error_string() |
238 | 238 | ); |
239 | - } |
|
240 | - // $this->credential is a public key in CBOR, not "PEM". We need to convert it first. |
|
239 | + } |
|
240 | + // $this->credential is a public key in CBOR, not "PEM". We need to convert it first. |
|
241 | 241 | $keyArray = $this->cborDecode(hex2bin($this->credential)); |
242 | 242 | $keyObject = new Ec2Key($keyArray); |
243 | 243 | $credentialResource = openssl_pkey_get_public($keyObject->asPEM()); |
@@ -251,20 +251,20 @@ discard block |
||
251 | 251 | . openssl_error_string() |
252 | 252 | ); |
253 | 253 | } |
254 | - // § 8.8 Bullet 5 |
|
255 | - $credentialDetails = openssl_pkey_get_details($credentialResource); |
|
256 | - $keyDetails = openssl_pkey_get_details($keyResource); |
|
257 | - if ( $credentialDetails['bits'] != $keyDetails['bits'] || |
|
254 | + // § 8.8 Bullet 5 |
|
255 | + $credentialDetails = openssl_pkey_get_details($credentialResource); |
|
256 | + $keyDetails = openssl_pkey_get_details($keyResource); |
|
257 | + if ( $credentialDetails['bits'] != $keyDetails['bits'] || |
|
258 | 258 | $credentialDetails['key'] != $keyDetails['key'] || |
259 | 259 | $credentialDetails['type'] != $keyDetails['type'] ) { |
260 | - $this->fail("The credential public key does not match the certificate public key in attestationData. (" |
|
261 | - . $credentialDetails['key'] |
|
262 | - . " - " |
|
263 | - . $keyDetails['key'] |
|
264 | - . ")"); |
|
265 | - } |
|
266 | - $this->pass("Apple attestation format verification passed."); |
|
267 | - return; |
|
260 | + $this->fail("The credential public key does not match the certificate public key in attestationData. (" |
|
261 | + . $credentialDetails['key'] |
|
262 | + . " - " |
|
263 | + . $keyDetails['key'] |
|
264 | + . ")"); |
|
265 | + } |
|
266 | + $this->pass("Apple attestation format verification passed."); |
|
267 | + return; |
|
268 | 268 | } |
269 | 269 | |
270 | 270 | /** |
@@ -391,7 +391,7 @@ discard block |
||
391 | 391 | $keyObject = new Ec2Key($this->cborDecode(hex2bin($this->credential))); |
392 | 392 | $keyResource = openssl_pkey_get_public($keyObject->asPEM()); |
393 | 393 | if ($keyResource === false) { |
394 | - $this->fail("Unable to construct ECDSA public key resource from PEM."); |
|
394 | + $this->fail("Unable to construct ECDSA public key resource from PEM."); |
|
395 | 395 | }; |
396 | 396 | break; |
397 | 397 | case self::PK_ALGORITHM_RSA: |