1 | // Copyright (c) 2018, Yubico AB | |
2 | // All rights reserved. | |
3 | // | |
4 | // Redistribution and use in source and binary forms, with or without | |
5 | // modification, are permitted provided that the following conditions are met: | |
6 | // | |
7 | // 1. Redistributions of source code must retain the above copyright notice, this | |
8 | // list of conditions and the following disclaimer. | |
9 | // | |
10 | // 2. Redistributions in binary form must reproduce the above copyright notice, | |
11 | // this list of conditions and the following disclaimer in the documentation | |
12 | // and/or other materials provided with the distribution. | |
13 | // | |
14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
17 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
18 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
19 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
20 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
21 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
22 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
23 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
24 | ||
25 | package com.yubico.webauthn; | |
26 | ||
27 | import static com.yubico.internal.util.ExceptionUtil.assertTrue; | |
28 | ||
29 | import com.yubico.internal.util.OptionalUtil; | |
30 | import com.yubico.webauthn.data.AuthenticatorAssertionResponse; | |
31 | import com.yubico.webauthn.data.ByteArray; | |
32 | import com.yubico.webauthn.data.COSEAlgorithmIdentifier; | |
33 | import com.yubico.webauthn.data.ClientAssertionExtensionOutputs; | |
34 | import com.yubico.webauthn.data.CollectedClientData; | |
35 | import com.yubico.webauthn.data.PublicKeyCredential; | |
36 | import com.yubico.webauthn.data.UserVerificationRequirement; | |
37 | import com.yubico.webauthn.exception.InvalidSignatureCountException; | |
38 | import com.yubico.webauthn.extension.appid.AppId; | |
39 | import java.io.IOException; | |
40 | import java.security.NoSuchAlgorithmException; | |
41 | import java.security.PublicKey; | |
42 | import java.security.spec.InvalidKeySpecException; | |
43 | import java.util.Optional; | |
44 | import java.util.Set; | |
45 | import lombok.AllArgsConstructor; | |
46 | import lombok.Value; | |
47 | import lombok.extern.slf4j.Slf4j; | |
48 | ||
49 | @Slf4j | |
50 | @AllArgsConstructor | |
51 | final class FinishAssertionSteps { | |
52 | ||
53 | private static final String CLIENT_DATA_TYPE = "webauthn.get"; | |
54 | private static final String SPC_CLIENT_DATA_TYPE = "payment.get"; | |
55 | ||
56 | private final AssertionRequest request; | |
57 | private final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> | |
58 | response; | |
59 | private final Optional<ByteArray> callerTokenBindingId; | |
60 | private final Set<String> origins; | |
61 | private final String rpId; | |
62 | private final CredentialRepository credentialRepository; | |
63 | private final boolean allowOriginPort; | |
64 | private final boolean allowOriginSubdomain; | |
65 | private final boolean validateSignatureCounter; | |
66 | private final boolean isSecurePaymentConfirmation; | |
67 | ||
68 | FinishAssertionSteps(RelyingParty rp, FinishAssertionOptions options) { | |
69 | this( | |
70 | options.getRequest(), | |
71 | options.getResponse(), | |
72 | options.getCallerTokenBindingId(), | |
73 | rp.getOrigins(), | |
74 | rp.getIdentity().getId(), | |
75 | rp.getCredentialRepository(), | |
76 | rp.isAllowOriginPort(), | |
77 | rp.isAllowOriginSubdomain(), | |
78 | rp.isValidateSignatureCounter(), | |
79 | options.isSecurePaymentConfirmation()); | |
80 | } | |
81 | ||
82 | private Optional<String> getUsernameForUserHandle(final ByteArray userHandle) { | |
83 |
1
1. getUsernameForUserHandle : replaced return value with Optional.empty for com/yubico/webauthn/FinishAssertionSteps::getUsernameForUserHandle → NO_COVERAGE |
return credentialRepository.getUsernameForUserHandle(userHandle); |
84 | } | |
85 | ||
86 | public Step5 begin() { | |
87 |
1
1. begin : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps::begin → KILLED |
return new Step5(); |
88 | } | |
89 | ||
90 | public AssertionResult run() throws InvalidSignatureCountException { | |
91 |
1
1. run : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps::run → KILLED |
return begin().run(); |
92 | } | |
93 | ||
94 | interface Step<Next extends Step<?>> { | |
95 | Next nextStep(); | |
96 | ||
97 | void validate() throws InvalidSignatureCountException; | |
98 | ||
99 | default Optional<AssertionResult> result() { | |
100 | return Optional.empty(); | |
101 | } | |
102 | ||
103 | default Next next() throws InvalidSignatureCountException { | |
104 |
1
1. next : removed call to com/yubico/webauthn/FinishAssertionSteps$Step::validate → KILLED |
validate(); |
105 |
1
1. next : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step::next → KILLED |
return nextStep(); |
106 | } | |
107 | ||
108 | default AssertionResult run() throws InvalidSignatureCountException { | |
109 |
1
1. run : negated conditional → KILLED |
if (result().isPresent()) { |
110 |
1
1. run : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step::run → KILLED |
return result().get(); |
111 | } else { | |
112 |
1
1. run : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step::run → KILLED |
return next().run(); |
113 | } | |
114 | } | |
115 | } | |
116 | ||
117 | // Steps 1 through 4 are to create the request and run the client-side part | |
118 | ||
119 | @Value | |
120 | class Step5 implements Step<Step6> { | |
121 | @Override | |
122 | public Step6 nextStep() { | |
123 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step5::nextStep → KILLED |
return new Step6(); |
124 | } | |
125 | ||
126 | @Override | |
127 | public void validate() { | |
128 | request | |
129 | .getPublicKeyCredentialRequestOptions() | |
130 | .getAllowCredentials() | |
131 |
2
1. lambda$validate$0 : replaced boolean return with true for com/yubico/webauthn/FinishAssertionSteps$Step5::lambda$validate$0 → KILLED 2. lambda$validate$0 : negated conditional → KILLED |
.filter(allowCredentials -> !allowCredentials.isEmpty()) |
132 |
1
1. validate : removed call to java/util/Optional::ifPresent → KILLED |
.ifPresent( |
133 | allowed -> { | |
134 |
1
1. lambda$validate$2 : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
135 |
2
1. lambda$validate$1 : replaced boolean return with false for com/yubico/webauthn/FinishAssertionSteps$Step5::lambda$validate$1 → KILLED 2. lambda$validate$1 : replaced boolean return with true for com/yubico/webauthn/FinishAssertionSteps$Step5::lambda$validate$1 → KILLED |
allowed.stream().anyMatch(allow -> allow.getId().equals(response.getId())), |
136 | "Unrequested credential ID: %s", | |
137 | response.getId()); | |
138 | }); | |
139 | } | |
140 | } | |
141 | ||
142 | @Value | |
143 | class Step6 implements Step<Step7> { | |
144 | ||
145 | private final Optional<ByteArray> userHandle = | |
146 | OptionalUtil.orElseOptional( | |
147 | request.getUserHandle(), | |
148 | () -> | |
149 |
1
1. lambda$new$1 : replaced return value with Optional.empty for com/yubico/webauthn/FinishAssertionSteps$Step6::lambda$new$1 → KILLED |
OptionalUtil.orElseOptional( |
150 | response.getResponse().getUserHandle(), | |
151 | () -> | |
152 |
1
1. lambda$new$0 : replaced return value with Optional.empty for com/yubico/webauthn/FinishAssertionSteps$Step6::lambda$new$0 → KILLED |
request |
153 | .getUsername() | |
154 | .flatMap(credentialRepository::getUserHandleForUsername))); | |
155 | ||
156 | private final Optional<String> username = | |
157 | OptionalUtil.orElseOptional( | |
158 | request.getUsername(), | |
159 |
1
1. lambda$new$2 : replaced return value with Optional.empty for com/yubico/webauthn/FinishAssertionSteps$Step6::lambda$new$2 → KILLED |
() -> userHandle.flatMap(credentialRepository::getUsernameForUserHandle)); |
160 | ||
161 | private final Optional<RegisteredCredential> registration = | |
162 |
1
1. lambda$new$3 : replaced return value with Optional.empty for com/yubico/webauthn/FinishAssertionSteps$Step6::lambda$new$3 → KILLED |
userHandle.flatMap(uh -> credentialRepository.lookup(response.getId(), uh)); |
163 | ||
164 | @Override | |
165 | public Step7 nextStep() { | |
166 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step6::nextStep → KILLED |
return new Step7(username.get(), userHandle.get(), registration); |
167 | } | |
168 | ||
169 | @Override | |
170 | public void validate() { | |
171 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED |
assertTrue( |
172 |
1
1. validate : negated conditional → KILLED |
request.getUsername().isPresent() |
173 |
1
1. validate : negated conditional → KILLED |
|| request.getUserHandle().isPresent() |
174 |
1
1. validate : negated conditional → KILLED |
|| response.getResponse().getUserHandle().isPresent(), |
175 | "At least one of username and user handle must be given; none was."); | |
176 |
1
1. validate : negated conditional → KILLED |
if (request.getUserHandle().isPresent() |
177 |
1
1. validate : negated conditional → KILLED |
&& response.getResponse().getUserHandle().isPresent()) { |
178 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
179 | request.getUserHandle().get().equals(response.getResponse().getUserHandle().get()), | |
180 | "User handle set in request (%s) does not match user handle in response (%s).", | |
181 | request.getUserHandle().get(), | |
182 | response.getResponse().getUserHandle().get()); | |
183 | } | |
184 | ||
185 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED |
assertTrue( |
186 | userHandle.isPresent(), | |
187 | "User handle not found for username: %s", | |
188 | request.getUsername(), | |
189 | response.getResponse().getUserHandle()); | |
190 | ||
191 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED |
assertTrue( |
192 | username.isPresent(), | |
193 | "Username not found for userHandle: %s", | |
194 | request.getUsername(), | |
195 | response.getResponse().getUserHandle()); | |
196 | ||
197 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED |
assertTrue(registration.isPresent(), "Unknown credential: %s", response.getId()); |
198 | ||
199 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
200 | userHandle.get().equals(registration.get().getUserHandle()), | |
201 | "User handle %s does not own credential %s", | |
202 | userHandle.get(), | |
203 | response.getId()); | |
204 | ||
205 | final Optional<String> usernameFromRequest = request.getUsername(); | |
206 | final Optional<ByteArray> userHandleFromResponse = response.getResponse().getUserHandle(); | |
207 |
2
1. validate : negated conditional → KILLED 2. validate : negated conditional → KILLED |
if (usernameFromRequest.isPresent() && userHandleFromResponse.isPresent()) { |
208 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
209 | userHandleFromResponse.equals( | |
210 | credentialRepository.getUserHandleForUsername(usernameFromRequest.get())), | |
211 | "User handle %s in response does not match username %s in request", | |
212 | userHandleFromResponse, | |
213 | usernameFromRequest); | |
214 | } | |
215 | } | |
216 | } | |
217 | ||
218 | @Value | |
219 | class Step7 implements Step<Step8> { | |
220 | private final String username; | |
221 | private final ByteArray userHandle; | |
222 | private final Optional<RegisteredCredential> credential; | |
223 | ||
224 | @Override | |
225 | public Step8 nextStep() { | |
226 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step7::nextStep → KILLED |
return new Step8(username, credential.get()); |
227 | } | |
228 | ||
229 | @Override | |
230 | public void validate() { | |
231 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
232 | credential.isPresent(), | |
233 | "Unknown credential. Credential ID: %s, user handle: %s", | |
234 | response.getId(), | |
235 | userHandle); | |
236 | } | |
237 | } | |
238 | ||
239 | @Value | |
240 | class Step8 implements Step<Step10> { | |
241 | ||
242 | private final String username; | |
243 | private final RegisteredCredential credential; | |
244 | ||
245 | @Override | |
246 | public void validate() { | |
247 |
2
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED 2. validate : negated conditional → KILLED |
assertTrue(clientData() != null, "Missing client data."); |
248 |
2
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED 2. validate : negated conditional → KILLED |
assertTrue(authenticatorData() != null, "Missing authenticator data."); |
249 |
2
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED 2. validate : negated conditional → KILLED |
assertTrue(signature() != null, "Missing signature."); |
250 | } | |
251 | ||
252 | @Override | |
253 | public Step10 nextStep() { | |
254 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step8::nextStep → KILLED |
return new Step10(username, credential); |
255 | } | |
256 | ||
257 | public ByteArray authenticatorData() { | |
258 |
1
1. authenticatorData : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step8::authenticatorData → KILLED |
return response.getResponse().getAuthenticatorData(); |
259 | } | |
260 | ||
261 | public ByteArray clientData() { | |
262 |
1
1. clientData : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step8::clientData → KILLED |
return response.getResponse().getClientDataJSON(); |
263 | } | |
264 | ||
265 | public ByteArray signature() { | |
266 |
1
1. signature : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step8::signature → KILLED |
return response.getResponse().getSignature(); |
267 | } | |
268 | } | |
269 | ||
270 | // Nothing to do for step 9 | |
271 | ||
272 | @Value | |
273 | class Step10 implements Step<Step11> { | |
274 | private final String username; | |
275 | private final RegisteredCredential credential; | |
276 | ||
277 | @Override | |
278 | public void validate() { | |
279 |
2
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED 2. validate : negated conditional → KILLED |
assertTrue(clientData() != null, "Missing client data."); |
280 | } | |
281 | ||
282 | @Override | |
283 | public Step11 nextStep() { | |
284 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step10::nextStep → KILLED |
return new Step11(username, credential, clientData()); |
285 | } | |
286 | ||
287 | public CollectedClientData clientData() { | |
288 |
1
1. clientData : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step10::clientData → KILLED |
return response.getResponse().getClientData(); |
289 | } | |
290 | } | |
291 | ||
292 | @Value | |
293 | class Step11 implements Step<Step12> { | |
294 | private final String username; | |
295 | private final RegisteredCredential credential; | |
296 | private final CollectedClientData clientData; | |
297 | ||
298 | @Override | |
299 | public void validate() { | |
300 | final String expectedType = | |
301 |
1
1. validate : negated conditional → KILLED |
isSecurePaymentConfirmation ? SPC_CLIENT_DATA_TYPE : CLIENT_DATA_TYPE; |
302 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
303 | expectedType.equals(clientData.getType()), | |
304 | "The \"type\" in the client data must be exactly \"%s\", was: %s", | |
305 | expectedType, | |
306 | clientData.getType()); | |
307 | } | |
308 | ||
309 | @Override | |
310 | public Step12 nextStep() { | |
311 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step11::nextStep → KILLED |
return new Step12(username, credential); |
312 | } | |
313 | } | |
314 | ||
315 | @Value | |
316 | class Step12 implements Step<Step13> { | |
317 | private final String username; | |
318 | private final RegisteredCredential credential; | |
319 | ||
320 | @Override | |
321 | public void validate() { | |
322 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
323 | request | |
324 | .getPublicKeyCredentialRequestOptions() | |
325 | .getChallenge() | |
326 | .equals(response.getResponse().getClientData().getChallenge()), | |
327 | "Incorrect challenge."); | |
328 | } | |
329 | ||
330 | @Override | |
331 | public Step13 nextStep() { | |
332 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step12::nextStep → KILLED |
return new Step13(username, credential); |
333 | } | |
334 | } | |
335 | ||
336 | @Value | |
337 | class Step13 implements Step<Step14> { | |
338 | private final String username; | |
339 | private final RegisteredCredential credential; | |
340 | ||
341 | @Override | |
342 | public void validate() { | |
343 | final String responseOrigin = response.getResponse().getClientData().getOrigin(); | |
344 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
345 | OriginMatcher.isAllowed(responseOrigin, origins, allowOriginPort, allowOriginSubdomain), | |
346 | "Incorrect origin, please see the RelyingParty.origins setting: %s", | |
347 | responseOrigin); | |
348 | } | |
349 | ||
350 | @Override | |
351 | public Step14 nextStep() { | |
352 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step13::nextStep → KILLED |
return new Step14(username, credential); |
353 | } | |
354 | } | |
355 | ||
356 | @Value | |
357 | class Step14 implements Step<Step15> { | |
358 | private final String username; | |
359 | private final RegisteredCredential credential; | |
360 | ||
361 | @Override | |
362 | public void validate() { | |
363 | TokenBindingValidator.validate( | |
364 | response.getResponse().getClientData().getTokenBinding(), callerTokenBindingId); | |
365 | } | |
366 | ||
367 | @Override | |
368 | public Step15 nextStep() { | |
369 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step14::nextStep → KILLED |
return new Step15(username, credential); |
370 | } | |
371 | } | |
372 | ||
373 | @Value | |
374 | class Step15 implements Step<Step16> { | |
375 | private final String username; | |
376 | private final RegisteredCredential credential; | |
377 | ||
378 | @Override | |
379 | public void validate() { | |
380 | try { | |
381 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
382 | Crypto.sha256(rpId) | |
383 | .equals(response.getResponse().getParsedAuthenticatorData().getRpIdHash()), | |
384 | "Wrong RP ID hash."); | |
385 | } catch (IllegalArgumentException e) { | |
386 | Optional<AppId> appid = | |
387 | request.getPublicKeyCredentialRequestOptions().getExtensions().getAppid(); | |
388 |
1
1. validate : negated conditional → KILLED |
if (appid.isPresent()) { |
389 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
390 | Crypto.sha256(appid.get().getId()) | |
391 | .equals(response.getResponse().getParsedAuthenticatorData().getRpIdHash()), | |
392 | "Wrong RP ID hash."); | |
393 | } else { | |
394 | throw e; | |
395 | } | |
396 | } | |
397 | } | |
398 | ||
399 | @Override | |
400 | public Step16 nextStep() { | |
401 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step15::nextStep → KILLED |
return new Step16(username, credential); |
402 | } | |
403 | } | |
404 | ||
405 | @Value | |
406 | class Step16 implements Step<Step17> { | |
407 | private final String username; | |
408 | private final RegisteredCredential credential; | |
409 | ||
410 | @Override | |
411 | public void validate() { | |
412 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
413 | response.getResponse().getParsedAuthenticatorData().getFlags().UP, | |
414 | "User Presence is required."); | |
415 | } | |
416 | ||
417 | @Override | |
418 | public Step17 nextStep() { | |
419 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step16::nextStep → KILLED |
return new Step17(username, credential); |
420 | } | |
421 | } | |
422 | ||
423 | @Value | |
424 | class Step17 implements Step<PendingStep16> { | |
425 | private final String username; | |
426 | private final RegisteredCredential credential; | |
427 | ||
428 | @Override | |
429 | public void validate() { | |
430 | if (request | |
431 | .getPublicKeyCredentialRequestOptions() | |
432 | .getUserVerification() | |
433 |
1
1. validate : negated conditional → KILLED |
.equals(Optional.of(UserVerificationRequirement.REQUIRED))) { |
434 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
435 | response.getResponse().getParsedAuthenticatorData().getFlags().UV, | |
436 | "User Verification is required."); | |
437 | } | |
438 | } | |
439 | ||
440 | @Override | |
441 | public PendingStep16 nextStep() { | |
442 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step17::nextStep → KILLED |
return new PendingStep16(username, credential); |
443 | } | |
444 | } | |
445 | ||
446 | @Value | |
447 | // Step 16 in editor's draft as of 2022-11-09 https://w3c.github.io/webauthn/ | |
448 | // TODO: Finalize this when spec matures | |
449 | class PendingStep16 implements Step<Step18> { | |
450 | private final String username; | |
451 | private final RegisteredCredential credential; | |
452 | ||
453 | @Override | |
454 | public void validate() { | |
455 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
456 |
1
1. validate : negated conditional → KILLED |
!credential.isBackupEligible().isPresent() |
457 | || response.getResponse().getParsedAuthenticatorData().getFlags().BE | |
458 |
1
1. validate : negated conditional → KILLED |
== credential.isBackupEligible().get(), |
459 | "Backup eligibility must not change; Stored: BE=%s, received: BE=%s for credential: %s", | |
460 | credential.isBackupEligible(), | |
461 | response.getResponse().getParsedAuthenticatorData().getFlags().BE, | |
462 | credential.getCredentialId()); | |
463 | } | |
464 | ||
465 | @Override | |
466 | public Step18 nextStep() { | |
467 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$PendingStep16::nextStep → KILLED |
return new Step18(username, credential); |
468 | } | |
469 | } | |
470 | ||
471 | @Value | |
472 | class Step18 implements Step<Step19> { | |
473 | private final String username; | |
474 | private final RegisteredCredential credential; | |
475 | ||
476 | @Override | |
477 | public void validate() {} | |
478 | ||
479 | @Override | |
480 | public Step19 nextStep() { | |
481 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step18::nextStep → KILLED |
return new Step19(username, credential); |
482 | } | |
483 | } | |
484 | ||
485 | @Value | |
486 | class Step19 implements Step<Step20> { | |
487 | private final String username; | |
488 | private final RegisteredCredential credential; | |
489 | ||
490 | @Override | |
491 | public void validate() { | |
492 |
2
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED 2. validate : negated conditional → KILLED |
assertTrue(clientDataJsonHash().size() == 32, "Failed to compute hash of client data"); |
493 | } | |
494 | ||
495 | @Override | |
496 | public Step20 nextStep() { | |
497 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step19::nextStep → KILLED |
return new Step20(username, credential, clientDataJsonHash()); |
498 | } | |
499 | ||
500 | public ByteArray clientDataJsonHash() { | |
501 |
1
1. clientDataJsonHash : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step19::clientDataJsonHash → KILLED |
return Crypto.sha256(response.getResponse().getClientDataJSON()); |
502 | } | |
503 | } | |
504 | ||
505 | @Value | |
506 | class Step20 implements Step<Step21> { | |
507 | private final String username; | |
508 | private final RegisteredCredential credential; | |
509 | private final ByteArray clientDataJsonHash; | |
510 | ||
511 | @Override | |
512 | public void validate() { | |
513 | final ByteArray cose = credential.getPublicKeyCose(); | |
514 | final PublicKey key; | |
515 | ||
516 | try { | |
517 | key = WebAuthnCodecs.importCosePublicKey(cose); | |
518 | } catch (IOException | InvalidKeySpecException e) { | |
519 | throw new IllegalArgumentException( | |
520 | String.format( | |
521 | "Failed to decode public key: Credential ID: %s COSE: %s", | |
522 | credential.getCredentialId().getBase64Url(), cose.getBase64Url()), | |
523 | e); | |
524 | } catch (NoSuchAlgorithmException e) { | |
525 | throw new RuntimeException(e); | |
526 | } | |
527 | ||
528 | final COSEAlgorithmIdentifier alg = | |
529 | COSEAlgorithmIdentifier.fromPublicKey(cose) | |
530 | .orElseThrow( | |
531 | () -> | |
532 |
1
1. lambda$validate$0 : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step20::lambda$validate$0 → NO_COVERAGE |
new IllegalArgumentException( |
533 | String.format("Failed to decode \"alg\" from COSE key: %s", cose))); | |
534 | ||
535 |
1
1. validate : negated conditional → KILLED |
if (!Crypto.verifySignature(key, signedBytes(), response.getResponse().getSignature(), alg)) { |
536 | throw new IllegalArgumentException("Invalid assertion signature."); | |
537 | } | |
538 | } | |
539 | ||
540 | @Override | |
541 | public Step21 nextStep() { | |
542 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step20::nextStep → KILLED |
return new Step21(username, credential); |
543 | } | |
544 | ||
545 | public ByteArray signedBytes() { | |
546 |
1
1. signedBytes : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step20::signedBytes → KILLED |
return response.getResponse().getAuthenticatorData().concat(clientDataJsonHash); |
547 | } | |
548 | } | |
549 | ||
550 | @Value | |
551 | class Step21 implements Step<Finished> { | |
552 | private final String username; | |
553 | private final RegisteredCredential credential; | |
554 | private final long assertionSignatureCount; | |
555 | private final long storedSignatureCountBefore; | |
556 | ||
557 | public Step21(String username, RegisteredCredential credential) { | |
558 | this.username = username; | |
559 | this.credential = credential; | |
560 | this.assertionSignatureCount = | |
561 | response.getResponse().getParsedAuthenticatorData().getSignatureCounter(); | |
562 | this.storedSignatureCountBefore = credential.getSignatureCount(); | |
563 | } | |
564 | ||
565 | @Override | |
566 | public void validate() throws InvalidSignatureCountException { | |
567 |
2
1. validate : negated conditional → KILLED 2. validate : negated conditional → KILLED |
if (validateSignatureCounter && !signatureCounterValid()) { |
568 | throw new InvalidSignatureCountException( | |
569 |
1
1. validate : Replaced long addition with subtraction → KILLED |
response.getId(), storedSignatureCountBefore + 1, assertionSignatureCount); |
570 | } | |
571 | } | |
572 | ||
573 | private boolean signatureCounterValid() { | |
574 |
5
1. signatureCounterValid : negated conditional → KILLED 2. signatureCounterValid : negated conditional → KILLED 3. signatureCounterValid : replaced boolean return with true for com/yubico/webauthn/FinishAssertionSteps$Step21::signatureCounterValid → KILLED 4. signatureCounterValid : negated conditional → KILLED 5. signatureCounterValid : changed conditional boundary → KILLED |
return (assertionSignatureCount == 0 && storedSignatureCountBefore == 0) |
575 | || assertionSignatureCount > storedSignatureCountBefore; | |
576 | } | |
577 | ||
578 | @Override | |
579 | public Finished nextStep() { | |
580 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step21::nextStep → KILLED |
return new Finished(credential, username, assertionSignatureCount, signatureCounterValid()); |
581 | } | |
582 | } | |
583 | ||
584 | @Value | |
585 | class Finished implements Step<Finished> { | |
586 | private final RegisteredCredential credential; | |
587 | private final String username; | |
588 | private final long assertionSignatureCount; | |
589 | private final boolean signatureCounterValid; | |
590 | ||
591 | @Override | |
592 | public void validate() { | |
593 | /* No-op */ | |
594 | } | |
595 | ||
596 | @Override | |
597 | public Finished nextStep() { | |
598 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Finished::nextStep → NO_COVERAGE |
return this; |
599 | } | |
600 | ||
601 | @Override | |
602 | public Optional<AssertionResult> result() { | |
603 |
1
1. result : replaced return value with Optional.empty for com/yubico/webauthn/FinishAssertionSteps$Finished::result → KILLED |
return Optional.of( |
604 | new AssertionResult(true, response, credential, username, signatureCounterValid)); | |
605 | } | |
606 | } | |
607 | } | |
Mutations | ||
83 |
1.1 |
|
87 |
1.1 |
|
91 |
1.1 |
|
104 |
1.1 |
|
105 |
1.1 |
|
109 |
1.1 |
|
110 |
1.1 |
|
112 |
1.1 |
|
123 |
1.1 |
|
131 |
1.1 2.2 |
|
132 |
1.1 |
|
134 |
1.1 |
|
135 |
1.1 2.2 |
|
149 |
1.1 |
|
152 |
1.1 |
|
159 |
1.1 |
|
162 |
1.1 |
|
166 |
1.1 |
|
171 |
1.1 |
|
172 |
1.1 |
|
173 |
1.1 |
|
174 |
1.1 |
|
176 |
1.1 |
|
177 |
1.1 |
|
178 |
1.1 |
|
185 |
1.1 |
|
191 |
1.1 |
|
197 |
1.1 |
|
199 |
1.1 |
|
207 |
1.1 2.2 |
|
208 |
1.1 |
|
226 |
1.1 |
|
231 |
1.1 |
|
247 |
1.1 2.2 |
|
248 |
1.1 2.2 |
|
249 |
1.1 2.2 |
|
254 |
1.1 |
|
258 |
1.1 |
|
262 |
1.1 |
|
266 |
1.1 |
|
279 |
1.1 2.2 |
|
284 |
1.1 |
|
288 |
1.1 |
|
301 |
1.1 |
|
302 |
1.1 |
|
311 |
1.1 |
|
322 |
1.1 |
|
332 |
1.1 |
|
344 |
1.1 |
|
352 |
1.1 |
|
369 |
1.1 |
|
381 |
1.1 |
|
388 |
1.1 |
|
389 |
1.1 |
|
401 |
1.1 |
|
412 |
1.1 |
|
419 |
1.1 |
|
433 |
1.1 |
|
434 |
1.1 |
|
442 |
1.1 |
|
455 |
1.1 |
|
456 |
1.1 |
|
458 |
1.1 |
|
467 |
1.1 |
|
481 |
1.1 |
|
492 |
1.1 2.2 |
|
497 |
1.1 |
|
501 |
1.1 |
|
532 |
1.1 |
|
535 |
1.1 |
|
542 |
1.1 |
|
546 |
1.1 |
|
567 |
1.1 2.2 |
|
569 |
1.1 |
|
574 |
1.1 2.2 3.3 4.4 5.5 |
|
580 |
1.1 |
|
598 |
1.1 |
|
603 |
1.1 |