FinishAssertionSteps.java

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
Location : getUsernameForUserHandle
Killed by : none
replaced return value with Optional.empty for com/yubico/webauthn/FinishAssertionSteps::getUsernameForUserHandle → NO_COVERAGE

87

1.1
Location : begin
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps::begin → KILLED

91

1.1
Location : run
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps::run → KILLED

104

1.1
Location : next
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
removed call to com/yubico/webauthn/FinishAssertionSteps$Step::validate → KILLED

105

1.1
Location : next
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step::next → KILLED

109

1.1
Location : run
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

110

1.1
Location : run
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step::run → KILLED

112

1.1
Location : run
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step::run → KILLED

123

1.1
Location : nextStep
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step5::nextStep → KILLED

131

1.1
Location : lambda$validate$0
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
replaced boolean return with true for com/yubico/webauthn/FinishAssertionSteps$Step5::lambda$validate$0 → KILLED

2.2
Location : lambda$validate$0
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
negated conditional → KILLED

132

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
removed call to java/util/Optional::ifPresent → KILLED

134

1.1
Location : lambda$validate$2
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED

135

1.1
Location : lambda$validate$1
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced boolean return with false for com/yubico/webauthn/FinishAssertionSteps$Step5::lambda$validate$1 → KILLED

2.2
Location : lambda$validate$1
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
replaced boolean return with true for com/yubico/webauthn/FinishAssertionSteps$Step5::lambda$validate$1 → KILLED

149

1.1
Location : lambda$new$1
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with Optional.empty for com/yubico/webauthn/FinishAssertionSteps$Step6::lambda$new$1 → KILLED

152

1.1
Location : lambda$new$0
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with Optional.empty for com/yubico/webauthn/FinishAssertionSteps$Step6::lambda$new$0 → KILLED

159

1.1
Location : lambda$new$2
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with Optional.empty for com/yubico/webauthn/FinishAssertionSteps$Step6::lambda$new$2 → KILLED

162

1.1
Location : lambda$new$3
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with Optional.empty for com/yubico/webauthn/FinishAssertionSteps$Step6::lambda$new$3 → KILLED

166

1.1
Location : nextStep
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step6::nextStep → KILLED

171

1.1
Location : validate
Killed by : none
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED

172

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

173

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
negated conditional → KILLED

174

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

176

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

177

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
negated conditional → KILLED

178

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED

185

1.1
Location : validate
Killed by : none
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED

191

1.1
Location : validate
Killed by : none
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED

197

1.1
Location : validate
Killed by : none
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED

199

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED

207

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

2.2
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

208

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED

226

1.1
Location : nextStep
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step7::nextStep → KILLED

231

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED

247

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

2.2
Location : validate
Killed by : none
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED

248

1.1
Location : validate
Killed by : none
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED

2.2
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

249

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

2.2
Location : validate
Killed by : none
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED

254

1.1
Location : nextStep
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step8::nextStep → KILLED

258

1.1
Location : authenticatorData
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step8::authenticatorData → KILLED

262

1.1
Location : clientData
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step8::clientData → KILLED

266

1.1
Location : signature
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step8::signature → KILLED

279

1.1
Location : validate
Killed by : none
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED

2.2
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

284

1.1
Location : nextStep
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step10::nextStep → KILLED

288

1.1
Location : clientData
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step10::clientData → KILLED

301

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

302

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED

311

1.1
Location : nextStep
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step11::nextStep → KILLED

322

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED

332

1.1
Location : nextStep
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step12::nextStep → KILLED

344

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED

352

1.1
Location : nextStep
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step13::nextStep → KILLED

369

1.1
Location : nextStep
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step14::nextStep → KILLED

381

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED

388

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
negated conditional → KILLED

389

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED

401

1.1
Location : nextStep
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step15::nextStep → KILLED

412

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED

419

1.1
Location : nextStep
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step16::nextStep → KILLED

433

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

434

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED

442

1.1
Location : nextStep
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step17::nextStep → KILLED

455

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED

456

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

458

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
negated conditional → KILLED

467

1.1
Location : nextStep
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$PendingStep16::nextStep → KILLED

481

1.1
Location : nextStep
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step18::nextStep → KILLED

492

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

2.2
Location : validate
Killed by : none
removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED

497

1.1
Location : nextStep
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step19::nextStep → KILLED

501

1.1
Location : clientDataJsonHash
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step19::clientDataJsonHash → KILLED

532

1.1
Location : lambda$validate$0
Killed by : none
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step20::lambda$validate$0 → NO_COVERAGE

535

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

542

1.1
Location : nextStep
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step20::nextStep → KILLED

546

1.1
Location : signedBytes
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step20::signedBytes → KILLED

567

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

2.2
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
negated conditional → KILLED

569

1.1
Location : validate
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
Replaced long addition with subtraction → KILLED

574

1.1
Location : signatureCounterValid
Killed by : com.yubico.webauthn.RelyingPartyCeremoniesSpec
negated conditional → KILLED

2.2
Location : signatureCounterValid
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
negated conditional → KILLED

3.3
Location : signatureCounterValid
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
replaced boolean return with true for com/yubico/webauthn/FinishAssertionSteps$Step21::signatureCounterValid → KILLED

4.4
Location : signatureCounterValid
Killed by : com.yubico.webauthn.RelyingPartyCeremoniesSpec
negated conditional → KILLED

5.5
Location : signatureCounterValid
Killed by : com.yubico.webauthn.RelyingPartyAssertionSpec
changed conditional boundary → KILLED

580

1.1
Location : nextStep
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Step21::nextStep → KILLED

598

1.1
Location : nextStep
Killed by : none
replaced return value with null for com/yubico/webauthn/FinishAssertionSteps$Finished::nextStep → NO_COVERAGE

603

1.1
Location : result
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with Optional.empty for com/yubico/webauthn/FinishAssertionSteps$Finished::result → KILLED

Active mutators

Tests examined


Report generated by PIT 1.15.0