RelyingPartyV2.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 com.yubico.internal.util.CollectionUtil;
28
import com.yubico.internal.util.OptionalUtil;
29
import com.yubico.webauthn.attestation.AttestationTrustSource;
30
import com.yubico.webauthn.data.AssertionExtensionInputs;
31
import com.yubico.webauthn.data.AttestationConveyancePreference;
32
import com.yubico.webauthn.data.AuthenticatorData;
33
import com.yubico.webauthn.data.ByteArray;
34
import com.yubico.webauthn.data.CollectedClientData;
35
import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
36
import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions.PublicKeyCredentialCreationOptionsBuilder;
37
import com.yubico.webauthn.data.PublicKeyCredentialParameters;
38
import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions;
39
import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions.PublicKeyCredentialRequestOptionsBuilder;
40
import com.yubico.webauthn.data.RegistrationExtensionInputs;
41
import com.yubico.webauthn.data.RelyingPartyIdentity;
42
import com.yubico.webauthn.exception.AssertionFailedException;
43
import com.yubico.webauthn.exception.InvalidSignatureCountException;
44
import com.yubico.webauthn.exception.RegistrationFailedException;
45
import com.yubico.webauthn.extension.appid.AppId;
46
import java.net.MalformedURLException;
47
import java.net.URL;
48
import java.security.KeyFactory;
49
import java.security.SecureRandom;
50
import java.security.Signature;
51
import java.time.Clock;
52
import java.util.Arrays;
53
import java.util.Collections;
54
import java.util.List;
55
import java.util.Optional;
56
import java.util.Set;
57
import java.util.stream.Collectors;
58
import lombok.Builder;
59
import lombok.NonNull;
60
import lombok.Value;
61
import lombok.extern.slf4j.Slf4j;
62
63
/**
64
 * Encapsulates the four basic Web Authentication operations - start/finish registration,
65
 * start/finish authentication - along with overall operational settings for them.
66
 *
67
 * <p>This class has no mutable state. An instance of this class may therefore be thought of as a
68
 * container for specialized versions (function closures) of these four operations rather than a
69
 * stateful object.
70
 */
71
@Slf4j
72
@Builder(toBuilder = true)
73
@Value
74
public class RelyingPartyV2<C extends CredentialRecord> {
75
76
  private static final SecureRandom random = new SecureRandom();
77
78
  /**
79
   * The {@link RelyingPartyIdentity} that will be set as the {@link
80
   * PublicKeyCredentialCreationOptions#getRp() rp} parameter when initiating registration
81
   * operations, and which {@link AuthenticatorData#getRpIdHash()} will be compared against. This is
82
   * a required parameter.
83
   *
84
   * <p>A successful registration or authentication operation requires {@link
85
   * AuthenticatorData#getRpIdHash()} to exactly equal the SHA-256 hash of this member's {@link
86
   * RelyingPartyIdentity#getId() id} member. Alternatively, it may instead equal the SHA-256 hash
87
   * of {@link #getAppId() appId} if the latter is present.
88
   *
89
   * @see #startRegistration(StartRegistrationOptions)
90
   * @see PublicKeyCredentialCreationOptions
91
   */
92
  @NonNull private final RelyingPartyIdentity identity;
93
94
  /**
95
   * The allowed origins that returned authenticator responses will be compared against.
96
   *
97
   * <p>The default is the set containing only the string <code>
98
   * "https://" + {@link #getIdentity()}.getId()</code>.
99
   *
100
   * <p>If {@link RelyingPartyV2Builder#allowOriginPort(boolean) allowOriginPort} and {@link
101
   * RelyingPartyV2Builder#allowOriginSubdomain(boolean) allowOriginSubdomain} are both <code>false
102
   * </code> (the default), then a successful registration or authentication operation requires
103
   * {@link CollectedClientData#getOrigin()} to exactly equal one of these values.
104
   *
105
   * <p>If {@link RelyingPartyV2Builder#allowOriginPort(boolean) allowOriginPort} is <code>true
106
   * </code> , then the above rule is relaxed to allow any port number in {@link
107
   * CollectedClientData#getOrigin()}, regardless of any port specified.
108
   *
109
   * <p>If {@link RelyingPartyV2Builder#allowOriginSubdomain(boolean) allowOriginSubdomain} is
110
   * <code>
111
   * true</code>, then the above rule is relaxed to allow any subdomain, of any depth, of any of
112
   * these values.
113
   *
114
   * <p>For either of the above relaxations to take effect, both the allowed origin and the client
115
   * data origin must be valid URLs. Origins that are not valid URLs are matched only by exact
116
   * string equality.
117
   *
118
   * @see #getIdentity()
119
   */
120
  @NonNull private final Set<String> origins;
121
122
  /**
123
   * An abstract database which can look up credentials, usernames and user handles from usernames,
124
   * user handles and credential IDs. This is a required parameter.
125
   *
126
   * <p>This is used to look up:
127
   *
128
   * <ul>
129
   *   <li>the user handle for a user logging in via user name
130
   *   <li>the user name for a user logging in via user handle
131
   *   <li>the credential IDs to include in {@link
132
   *       PublicKeyCredentialCreationOptions#getExcludeCredentials()}
133
   *   <li>the credential IDs to include in {@link
134
   *       PublicKeyCredentialRequestOptions#getAllowCredentials()}
135
   *   <li>that the correct user owns the credential when verifying an assertion
136
   *   <li>the public key to use to verify an assertion
137
   *   <li>the stored signature counter when verifying an assertion
138
   * </ul>
139
   */
140
  @NonNull private final CredentialRepositoryV2<C> credentialRepository;
141
142
  /**
143
   * Enable support for identifying users by username.
144
   *
145
   * <p>If set, then {@link #startAssertion(StartAssertionOptions)} allows setting the {@link
146
   * StartAssertionOptions.StartAssertionOptionsBuilder#username(String) username} parameter when
147
   * starting an assertion.
148
   *
149
   * <p>By default, this is not set.
150
   *
151
   * @deprecated EXPERIMENTAL: This is an experimental feature. It is likely to change or be deleted
152
   *     before reaching a mature release.
153
   */
154
  @Deprecated private final UsernameRepository usernameRepository;
155
156
  /**
157
   * The extension input to set for the <code>appid</code> and <code>appidExclude</code> extensions.
158
   *
159
   * <p>You do not need this extension if you have not previously supported U2F. Its purpose is to
160
   * make already-registered U2F credentials forward-compatible with the WebAuthn API. It is not
161
   * needed for new registrations, even of U2F authenticators.
162
   *
163
   * <p>If this member is set, {@link #startAssertion(StartAssertionOptions) startAssertion} will
164
   * automatically set the <code>appid</code> extension input, and {@link
165
   * #finishAssertion(FinishAssertionOptions) finishAssertion} will adjust its verification logic to
166
   * also accept this AppID as an alternative to the RP ID. Likewise, {@link
167
   * #startRegistration(StartRegistrationOptions)} startRegistration} will automatically set the
168
   * <code>appidExclude</code> extension input.
169
   *
170
   * <p>By default, this is not set.
171
   *
172
   * @see AssertionExtensionInputs#getAppid()
173
   * @see RegistrationExtensionInputs#getAppidExclude()
174
   * @see <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-appid-extension">§10.1.
175
   *     FIDO AppID Extension (appid)</a>
176
   * @see <a
177
   *     href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-appid-exclude-extension">§10.2.
178
   *     FIDO AppID Exclusion Extension (appidExclude)</a>
179
   */
180
  @NonNull private final Optional<AppId> appId;
181
182
  /**
183
   * The argument for the {@link PublicKeyCredentialCreationOptions#getAttestation() attestation}
184
   * parameter in registration operations.
185
   *
186
   * <p>Unless your application has a concrete policy for authenticator attestation, it is
187
   * recommended to leave this parameter undefined.
188
   *
189
   * <p>If you set this, you may want to explicitly set {@link
190
   * RelyingPartyV2Builder#allowUntrustedAttestation(boolean) allowUntrustedAttestation} and {@link
191
   * RelyingPartyV2Builder#attestationTrustSource(AttestationTrustSource) attestationTrustSource}
192
   * too.
193
   *
194
   * <p>By default, this is not set.
195
   *
196
   * @see PublicKeyCredentialCreationOptions#getAttestation()
197
   * @see <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-attestation">§6.4.
198
   *     Attestation</a>
199
   */
200
  @NonNull private final Optional<AttestationConveyancePreference> attestationConveyancePreference;
201
202
  /**
203
   * An {@link AttestationTrustSource} instance to use for looking up trust roots for authenticator
204
   * attestation. This matters only if {@link #getAttestationConveyancePreference()} is non-empty
205
   * and not set to {@link AttestationConveyancePreference#NONE}.
206
   *
207
   * <p>By default, this is not set.
208
   *
209
   * @see PublicKeyCredentialCreationOptions#getAttestation()
210
   * @see <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-attestation">§6.4.
211
   *     Attestation</a>
212
   */
213
  @NonNull private final Optional<AttestationTrustSource> attestationTrustSource;
214
215
  /**
216
   * The argument for the {@link PublicKeyCredentialCreationOptions#getPubKeyCredParams()
217
   * pubKeyCredParams} parameter in registration operations.
218
   *
219
   * <p>This is a list of acceptable public key algorithms and their parameters, ordered from most
220
   * to least preferred.
221
   *
222
   * <p>The default is the following list, in order:
223
   *
224
   * <ol>
225
   *   <li>{@link PublicKeyCredentialParameters#ES256 ES256}
226
   *   <li>{@link PublicKeyCredentialParameters#EdDSA EdDSA}
227
   *   <li>{@link PublicKeyCredentialParameters#ES384 ES384}
228
   *   <li>{@link PublicKeyCredentialParameters#ES512 ES512}
229
   *   <li>{@link PublicKeyCredentialParameters#Ed448 Ed448}
230
   *   <li>{@link PublicKeyCredentialParameters#RS256 RS256}
231
   *   <li>{@link PublicKeyCredentialParameters#RS384 RS384}
232
   *   <li>{@link PublicKeyCredentialParameters#RS512 RS512}
233
   *   <li>{@link PublicKeyCredentialParameters#ML_DSA_44 ML_DSA_44}
234
   *   <li>{@link PublicKeyCredentialParameters#ML_DSA_65 ML_DSA_65}
235
   *   <li>{@link PublicKeyCredentialParameters#ML_DSA_87 ML_DSA_87}
236
   * </ol>
237
   *
238
   * @see PublicKeyCredentialCreationOptions#getAttestation()
239
   * @see <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-attestation">§6.4.
240
   *     Attestation</a>
241
   */
242
  @Builder.Default @NonNull
243
  private final List<PublicKeyCredentialParameters> preferredPubkeyParams =
244
      Collections.unmodifiableList(
245
          Arrays.asList(
246
              PublicKeyCredentialParameters.ES256,
247
              PublicKeyCredentialParameters.EdDSA,
248
              PublicKeyCredentialParameters.ES384,
249
              PublicKeyCredentialParameters.ES512,
250
              PublicKeyCredentialParameters.Ed448,
251
              PublicKeyCredentialParameters.RS256,
252
              PublicKeyCredentialParameters.RS384,
253
              PublicKeyCredentialParameters.RS512,
254
              PublicKeyCredentialParameters.ML_DSA_44,
255
              PublicKeyCredentialParameters.ML_DSA_65,
256
              PublicKeyCredentialParameters.ML_DSA_87));
257
258
  /**
259
   * If <code>true</code>, the origin matching rule is relaxed to allow any port number.
260
   *
261
   * <p>The default is <code>false</code>.
262
   *
263
   * <p>Examples with <code>
264
   * origins: ["https://example.org", "https://accounts.example.org", "https://acme.com:8443"]
265
   * </code>
266
   *
267
   * <ul>
268
   *   <li>
269
   *       <p><code>allowOriginPort: false</code>
270
   *       <p>Accepted:
271
   *       <ul>
272
   *         <li><code>https://example.org</code>
273
   *         <li><code>https://accounts.example.org</code>
274
   *         <li><code>https://acme.com:8443</code>
275
   *       </ul>
276
   *       <p>Rejected:
277
   *       <ul>
278
   *         <li><code>https://example.org:8443</code>
279
   *         <li><code>https://shop.example.org</code>
280
   *         <li><code>https://acme.com</code>
281
   *         <li><code>https://acme.com:9000</code>
282
   *       </ul>
283
   *   <li>
284
   *       <p><code>allowOriginPort: true</code>
285
   *       <p>Accepted:
286
   *       <ul>
287
   *         <li><code>https://example.org</code>
288
   *         <li><code>https://example.org:8443</code>
289
   *         <li><code>https://accounts.example.org</code>
290
   *         <li><code>https://acme.com</code>
291
   *         <li><code>https://acme.com:8443</code>
292
   *         <li><code>https://acme.com:9000</code>
293
   *       </ul>
294
   *       <p>Rejected:
295
   *       <ul>
296
   *         <li><code>https://shop.example.org</code>
297
   *       </ul>
298
   * </ul>
299
   */
300
  @Builder.Default private final boolean allowOriginPort = false;
301
302
  /**
303
   * If <code>true</code>, the origin matching rule is relaxed to allow any subdomain, of any depth,
304
   * of the values of {@link RelyingPartyV2Builder#origins(Set) origins}.
305
   *
306
   * <p>The default is <code>false</code>.
307
   *
308
   * <p>Examples with <code>origins: ["https://example.org", "https://acme.com:8443"]</code>
309
   *
310
   * <ul>
311
   *   <li>
312
   *       <p><code>allowOriginSubdomain: false</code>
313
   *       <p>Accepted:
314
   *       <ul>
315
   *         <li><code>https://example.org</code>
316
   *         <li><code>https://acme.com:8443</code>
317
   *       </ul>
318
   *       <p>Rejected:
319
   *       <ul>
320
   *         <li><code>https://example.org:8443</code>
321
   *         <li><code>https://accounts.example.org</code>
322
   *         <li><code>https://acme.com</code>
323
   *         <li><code>https://eu.shop.acme.com:8443</code>
324
   *       </ul>
325
   *   <li>
326
   *       <p><code>allowOriginSubdomain: true</code>
327
   *       <p>Accepted:
328
   *       <ul>
329
   *         <li><code>https://example.org</code>
330
   *         <li><code>https://accounts.example.org</code>
331
   *         <li><code>https://acme.com:8443</code>
332
   *         <li><code>https://eu.shop.acme.com:8443</code>
333
   *       </ul>
334
   *       <p>Rejected:
335
   *       <ul>
336
   *         <li><code>https://example.org:8443</code>
337
   *         <li><code>https://acme.com</code>
338
   *       </ul>
339
   * </ul>
340
   */
341
  @Builder.Default private final boolean allowOriginSubdomain = false;
342
343
  /**
344
   * If <code>false</code>, {@link #finishRegistration(FinishRegistrationOptions)
345
   * finishRegistration} will only allow registrations where the attestation signature can be linked
346
   * to a trusted attestation root. This excludes none attestation, and self attestation unless the
347
   * self attestation key is explicitly trusted.
348
   *
349
   * <p>Regardless of the value of this option, invalid attestation statements of supported formats
350
   * will always be rejected. For example, a "packed" attestation statement with an invalid
351
   * signature will be rejected even if this option is set to <code>true</code>.
352
   *
353
   * <p>The default is <code>true</code>.
354
   */
355
  @Builder.Default private final boolean allowUntrustedAttestation = true;
356
357
  /**
358
   * If <code>true</code>, {@link #finishAssertion(FinishAssertionOptions) finishAssertion} will
359
   * succeed only if the {@link AuthenticatorData#getSignatureCounter() signature counter value} in
360
   * the response is strictly greater than the {@link RegisteredCredential#getSignatureCount()
361
   * stored signature counter value}, or if both counters are exactly zero.
362
   *
363
   * <p>The default is <code>true</code>.
364
   */
365
  @Builder.Default private final boolean validateSignatureCounter = true;
366
367
  /**
368
   * A {@link Clock} which will be used to tell the current time while verifying attestation
369
   * certificate chains.
370
   *
371
   * <p>This is intended primarily for testing, and relevant only if {@link
372
   * RelyingPartyV2Builder#attestationTrustSource(AttestationTrustSource)} is set.
373
   *
374
   * <p>The default is <code>Clock.systemUTC()</code>.
375
   */
376
  @Builder.Default @NonNull private final Clock clock = Clock.systemUTC();
377
378
  @Builder
379
  private RelyingPartyV2(
380
      @NonNull RelyingPartyIdentity identity,
381
      Set<String> origins,
382
      @NonNull CredentialRepositoryV2<C> credentialRepository,
383
      UsernameRepository usernameRepository,
384
      @NonNull Optional<AppId> appId,
385
      @NonNull Optional<AttestationConveyancePreference> attestationConveyancePreference,
386
      @NonNull Optional<AttestationTrustSource> attestationTrustSource,
387
      List<PublicKeyCredentialParameters> preferredPubkeyParams,
388
      boolean allowOriginPort,
389
      boolean allowOriginSubdomain,
390
      boolean allowUntrustedAttestation,
391
      boolean validateSignatureCounter,
392
      Clock clock) {
393
    this.identity = identity;
394
    this.origins =
395 1 1. <init> : negated conditional → KILLED
        origins != null
396
            ? CollectionUtil.immutableSet(origins)
397
            : Collections.singleton("https://" + identity.getId());
398
399
    for (String origin : this.origins) {
400
      try {
401
        new URL(origin);
402
      } catch (MalformedURLException e) {
403
        log.warn(
404
            "Allowed origin is not a valid URL, it will match only by exact string equality: {}",
405
            origin);
406
      }
407
    }
408
409
    this.credentialRepository = credentialRepository;
410
    this.usernameRepository = usernameRepository;
411
    this.appId = appId;
412
    this.attestationConveyancePreference = attestationConveyancePreference;
413
    this.attestationTrustSource = attestationTrustSource;
414
    this.preferredPubkeyParams = filterAvailableAlgorithms(preferredPubkeyParams);
415
    this.allowOriginPort = allowOriginPort;
416
    this.allowOriginSubdomain = allowOriginSubdomain;
417
    this.allowUntrustedAttestation = allowUntrustedAttestation;
418
    this.validateSignatureCounter = validateSignatureCounter;
419
    this.clock = clock;
420
  }
421
422
  private static ByteArray generateChallenge() {
423
    byte[] bytes = new byte[32];
424 1 1. generateChallenge : removed call to java/security/SecureRandom::nextBytes → KILLED
    random.nextBytes(bytes);
425 1 1. generateChallenge : replaced return value with null for com/yubico/webauthn/RelyingPartyV2::generateChallenge → KILLED
    return new ByteArray(bytes);
426
  }
427
428
  /**
429
   * Filter <code>pubKeyCredParams</code> to only contain algorithms with a {@link KeyFactory} and a
430
   * {@link Signature} available, and log a warning for every unsupported algorithm.
431
   *
432
   * @return a new {@link List} containing only the algorithms supported in the current JCA context.
433
   */
434
  private static List<PublicKeyCredentialParameters> filterAvailableAlgorithms(
435
      List<PublicKeyCredentialParameters> pubKeyCredParams) {
436 1 1. filterAvailableAlgorithms : replaced return value with Collections.emptyList for com/yubico/webauthn/RelyingPartyV2::filterAvailableAlgorithms → KILLED
    return RelyingParty.filterAvailableAlgorithms(pubKeyCredParams);
437
  }
438
439
  public PublicKeyCredentialCreationOptions startRegistration(
440
      StartRegistrationOptions startRegistrationOptions) {
441
    PublicKeyCredentialCreationOptionsBuilder builder =
442
        PublicKeyCredentialCreationOptions.builder()
443
            .rp(identity)
444
            .user(startRegistrationOptions.getUser())
445
            .challenge(generateChallenge())
446
            .pubKeyCredParams(preferredPubkeyParams)
447
            .excludeCredentials(
448
                credentialRepository
449
                    .getCredentialDescriptorsForUserHandle(
450
                        startRegistrationOptions.getUser().getId())
451
                    .stream()
452
                    .map(ToPublicKeyCredentialDescriptor::toPublicKeyCredentialDescriptor)
453
                    .collect(Collectors.toSet()))
454
            .authenticatorSelection(startRegistrationOptions.getAuthenticatorSelection())
455
            .extensions(
456
                startRegistrationOptions
457
                    .getExtensions()
458
                    .merge(
459
                        RegistrationExtensionInputs.builder()
460
                            .appidExclude(appId)
461
                            .credProps()
462
                            .build()))
463
            .timeout(startRegistrationOptions.getTimeout())
464
            .hints(startRegistrationOptions.getHints());
465 1 1. startRegistration : removed call to java/util/Optional::ifPresent → KILLED
    attestationConveyancePreference.ifPresent(builder::attestation);
466 1 1. startRegistration : replaced return value with null for com/yubico/webauthn/RelyingPartyV2::startRegistration → KILLED
    return builder.build();
467
  }
468
469
  public RegistrationResult finishRegistration(FinishRegistrationOptions finishRegistrationOptions)
470
      throws RegistrationFailedException {
471
    try {
472 1 1. finishRegistration : replaced return value with null for com/yubico/webauthn/RelyingPartyV2::finishRegistration → KILLED
      return _finishRegistration(finishRegistrationOptions).run();
473
    } catch (IllegalArgumentException e) {
474
      throw new RegistrationFailedException(e);
475
    }
476
  }
477
478
  /**
479
   * This method is NOT part of the public API.
480
   *
481
   * <p>This method is called internally by {@link #finishRegistration(FinishRegistrationOptions)}.
482
   * It is a separate method to facilitate testing; users should call {@link
483
   * #finishRegistration(FinishRegistrationOptions)} instead of this method.
484
   */
485
  FinishRegistrationSteps _finishRegistration(FinishRegistrationOptions options) {
486 1 1. _finishRegistration : replaced return value with null for com/yubico/webauthn/RelyingPartyV2::_finishRegistration → KILLED
    return new FinishRegistrationSteps(this, options);
487
  }
488
489
  public AssertionRequest startAssertion(StartAssertionOptions startAssertionOptions) {
490 2 1. startAssertion : negated conditional → KILLED
2. startAssertion : negated conditional → KILLED
    if (startAssertionOptions.getUsername().isPresent() && usernameRepository == null) {
491
      throw new IllegalArgumentException(
492
          "StartAssertionOptions.username must not be set when usernameRepository is not configured.");
493
    }
494
495
    PublicKeyCredentialRequestOptionsBuilder pkcro =
496
        PublicKeyCredentialRequestOptions.builder()
497
            .challenge(generateChallenge())
498
            .rpId(identity.getId())
499
            .allowCredentials(
500
                OptionalUtil.orElseOptional(
501
                        startAssertionOptions.getUserHandle(),
502
                        () ->
503 1 1. lambda$startAssertion$1 : replaced return value with Optional.empty for com/yubico/webauthn/RelyingPartyV2::lambda$startAssertion$1 → KILLED
                            Optional.ofNullable(usernameRepository)
504
                                .flatMap(
505
                                    unr ->
506 1 1. lambda$startAssertion$0 : replaced return value with Optional.empty for com/yubico/webauthn/RelyingPartyV2::lambda$startAssertion$0 → KILLED
                                        startAssertionOptions
507
                                            .getUsername()
508
                                            .flatMap(unr::getUserHandleForUsername)))
509
                    .map(credentialRepository::getCredentialDescriptorsForUserHandle)
510
                    .map(
511
                        descriptors ->
512
                            descriptors.stream()
513
                                .map(
514
                                    ToPublicKeyCredentialDescriptor
515
                                        ::toPublicKeyCredentialDescriptor)
516 1 1. lambda$startAssertion$2 : replaced return value with Collections.emptyList for com/yubico/webauthn/RelyingPartyV2::lambda$startAssertion$2 → KILLED
                                .collect(Collectors.toList())))
517
            .extensions(
518
                startAssertionOptions
519
                    .getExtensions()
520
                    .merge(startAssertionOptions.getExtensions().toBuilder().appid(appId).build()))
521
            .timeout(startAssertionOptions.getTimeout())
522
            .hints(startAssertionOptions.getHints());
523
524 1 1. startAssertion : removed call to java/util/Optional::ifPresent → KILLED
    startAssertionOptions.getUserVerification().ifPresent(pkcro::userVerification);
525
526 1 1. startAssertion : replaced return value with null for com/yubico/webauthn/RelyingPartyV2::startAssertion → KILLED
    return AssertionRequest.builder()
527
        .publicKeyCredentialRequestOptions(pkcro.build())
528
        .username(startAssertionOptions.getUsername())
529
        .userHandle(startAssertionOptions.getUserHandle())
530
        .build();
531
  }
532
533
  /**
534
   * @throws InvalidSignatureCountException if {@link
535
   *     RelyingPartyV2Builder#validateSignatureCounter(boolean) validateSignatureCounter} is <code>
536
   *     true</code>, the {@link AuthenticatorData#getSignatureCounter() signature count} in the
537
   *     response is less than or equal to the {@link RegisteredCredential#getSignatureCount()
538
   *     stored signature count}, and at least one of the signature count values is nonzero.
539
   * @throws AssertionFailedException if validation fails for any other reason.
540
   */
541
  public AssertionResultV2<C> finishAssertion(FinishAssertionOptions finishAssertionOptions)
542
      throws AssertionFailedException {
543
    try {
544 1 1. finishAssertion : replaced return value with null for com/yubico/webauthn/RelyingPartyV2::finishAssertion → KILLED
      return _finishAssertion(finishAssertionOptions).runV2();
545
    } catch (IllegalArgumentException e) {
546
      throw new AssertionFailedException(e);
547
    }
548
  }
549
550
  /**
551
   * This method is NOT part of the public API.
552
   *
553
   * <p>This method is called internally by {@link #finishAssertion(FinishAssertionOptions)}. It is
554
   * a separate method to facilitate testing; users should call {@link
555
   * #finishAssertion(FinishAssertionOptions)} instead of this method.
556
   */
557
  FinishAssertionSteps<C> _finishAssertion(FinishAssertionOptions options) {
558 1 1. _finishAssertion : replaced return value with null for com/yubico/webauthn/RelyingPartyV2::_finishAssertion → KILLED
    return new FinishAssertionSteps<C>(this, options);
559
  }
560
561
  static <C extends CredentialRecord> RelyingPartyV2Builder<C> builder(
562
      RelyingPartyIdentity identity, CredentialRepositoryV2<C> credentialRepository) {
563 1 1. builder : replaced return value with null for com/yubico/webauthn/RelyingPartyV2::builder → KILLED
    return new RelyingPartyV2Builder<C>()
564
        .identity(identity)
565
        .credentialRepository(credentialRepository);
566
  }
567
568
  public static class RelyingPartyV2Builder<C extends CredentialRecord> {
569
    private @NonNull Optional<AppId> appId = Optional.empty();
570
    private @NonNull Optional<AttestationConveyancePreference> attestationConveyancePreference =
571
        Optional.empty();
572
    private @NonNull Optional<AttestationTrustSource> attestationTrustSource = Optional.empty();
573
574
    /**
575
     * The allowed origins that returned authenticator responses will be compared against.
576
     *
577
     * <p>The default is the set containing only the string <code>
578
     * "https://" + {@link #getIdentity()}.getId()</code>.
579
     *
580
     * <p>If {@link RelyingPartyV2Builder#allowOriginPort(boolean) allowOriginPort} and {@link
581
     * RelyingPartyV2Builder#allowOriginSubdomain(boolean) allowOriginSubdomain} are both <code>
582
     * false</code> (the default), then a successful registration or authentication operation
583
     * requires {@link CollectedClientData#getOrigin()} to exactly equal one of these values.
584
     *
585
     * <p>If {@link RelyingPartyV2Builder#allowOriginPort(boolean) allowOriginPort} is <code>true
586
     * </code> , then the above rule is relaxed to allow any port number in {@link
587
     * CollectedClientData#getOrigin()}, regardless of any port specified.
588
     *
589
     * <p>If {@link RelyingPartyV2Builder#allowOriginSubdomain(boolean) allowOriginSubdomain} is
590
     * <code>true</code>, then the above rule is relaxed to allow any subdomain, of any depth, of
591
     * any of these values.
592
     *
593
     * <p>For either of the above relaxations to take effect, both the allowed origin and the client
594
     * data origin must be valid URLs. Origins that are not valid URLs are matched only by exact
595
     * string equality.
596
     *
597
     * @since 2.7.0
598
     * @see #getIdentity()
599
     * @see #origins(Optional)
600
     */
601
    public RelyingPartyV2Builder<C> origins(@NonNull Set<String> origins) {
602
      this.origins = origins;
603 1 1. origins : replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::origins → KILLED
      return this;
604
    }
605
606
    /**
607
     * The allowed origins that returned authenticator responses will be compared against.
608
     *
609
     * <p>If set to empty, this setting reverts to the default value.
610
     *
611
     * <p>The default is the set containing only the string <code>
612
     * "https://" + {@link #getIdentity()}.getId()</code>.
613
     *
614
     * <p>If {@link RelyingPartyV2Builder#allowOriginPort(boolean) allowOriginPort} and {@link
615
     * RelyingPartyV2Builder#allowOriginSubdomain(boolean) allowOriginSubdomain} are both <code>
616
     * false</code> (the default), then a successful registration or authentication operation
617
     * requires {@link CollectedClientData#getOrigin()} to exactly equal one of these values.
618
     *
619
     * <p>If {@link RelyingPartyV2Builder#allowOriginPort(boolean) allowOriginPort} is <code>true
620
     * </code> , then the above rule is relaxed to allow any port number in {@link
621
     * CollectedClientData#getOrigin()}, regardless of any port specified.
622
     *
623
     * <p>If {@link RelyingPartyV2Builder#allowOriginSubdomain(boolean) allowOriginSubdomain} is
624
     * <code>true</code>, then the above rule is relaxed to allow any subdomain, of any depth, of
625
     * any of these values.
626
     *
627
     * <p>For either of the above relaxations to take effect, both the allowed origin and the client
628
     * data origin must be valid URLs. Origins that are not valid URLs are matched only by exact
629
     * string equality.
630
     *
631
     * @since 2.7.0
632
     * @see #getIdentity()
633
     * @see #origins(Set)
634
     */
635
    public RelyingPartyV2Builder<C> origins(@NonNull Optional<Set<String>> origins) {
636
      this.origins = origins.orElse(null);
637 1 1. origins : replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::origins → KILLED
      return this;
638
    }
639
640
    /**
641
     * The extension input to set for the <code>appid</code> and <code>appidExclude</code>
642
     * extensions.
643
     *
644
     * <p>You do not need this extension if you have not previously supported U2F. Its purpose is to
645
     * make already-registered U2F credentials forward-compatible with the WebAuthn API. It is not
646
     * needed for new registrations, even of U2F authenticators.
647
     *
648
     * <p>If this member is set, {@link #startAssertion(StartAssertionOptions) startAssertion} will
649
     * automatically set the <code>appid</code> extension input, and {@link
650
     * #finishAssertion(FinishAssertionOptions) finishAssertion} will adjust its verification logic
651
     * to also accept this AppID as an alternative to the RP ID. Likewise, {@link
652
     * #startRegistration(StartRegistrationOptions)} startRegistration} will automatically set the
653
     * <code>appidExclude</code> extension input.
654
     *
655
     * <p>By default, this is not set.
656
     *
657
     * @see AssertionExtensionInputs#getAppid()
658
     * @see RegistrationExtensionInputs#getAppidExclude()
659
     * @see <a
660
     *     href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-appid-extension">§10.1.
661
     *     FIDO AppID Extension (appid)</a>
662
     * @see <a
663
     *     href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-appid-exclude-extension">§10.2.
664
     *     FIDO AppID Exclusion Extension (appidExclude)</a>
665
     */
666
    public RelyingPartyV2Builder<C> appId(@NonNull Optional<AppId> appId) {
667
      this.appId = appId;
668 1 1. appId : replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::appId → KILLED
      return this;
669
    }
670
671
    /**
672
     * The extension input to set for the <code>appid</code> and <code>appidExclude</code>
673
     * extensions.
674
     *
675
     * <p>You do not need this extension if you have not previously supported U2F. Its purpose is to
676
     * make already-registered U2F credentials forward-compatible with the WebAuthn API. It is not
677
     * needed for new registrations, even of U2F authenticators.
678
     *
679
     * <p>If this member is set, {@link #startAssertion(StartAssertionOptions) startAssertion} will
680
     * automatically set the <code>appid</code> extension input, and {@link
681
     * #finishAssertion(FinishAssertionOptions) finishAssertion} will adjust its verification logic
682
     * to also accept this AppID as an alternative to the RP ID. Likewise, {@link
683
     * #startRegistration(StartRegistrationOptions)} startRegistration} will automatically set the
684
     * <code>appidExclude</code> extension input.
685
     *
686
     * <p>By default, this is not set.
687
     *
688
     * @see AssertionExtensionInputs#getAppid()
689
     * @see RegistrationExtensionInputs#getAppidExclude()
690
     * @see <a
691
     *     href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-appid-extension">§10.1.
692
     *     FIDO AppID Extension (appid)</a>
693
     * @see <a
694
     *     href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-appid-exclude-extension">§10.2.
695
     *     FIDO AppID Exclusion Extension (appidExclude)</a>
696
     */
697
    public RelyingPartyV2Builder<C> appId(@NonNull AppId appId) {
698 1 1. appId : replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::appId → KILLED
      return this.appId(Optional.of(appId));
699
    }
700
701
    /**
702
     * The argument for the {@link PublicKeyCredentialCreationOptions#getAttestation() attestation}
703
     * parameter in registration operations.
704
     *
705
     * <p>Unless your application has a concrete policy for authenticator attestation, it is
706
     * recommended to leave this parameter undefined.
707
     *
708
     * <p>If you set this, you may want to explicitly set {@link
709
     * RelyingPartyV2Builder#allowUntrustedAttestation(boolean) allowUntrustedAttestation} and
710
     * {@link RelyingPartyV2Builder#attestationTrustSource(AttestationTrustSource)
711
     * attestationTrustSource} too.
712
     *
713
     * <p>By default, this is not set.
714
     *
715
     * @see PublicKeyCredentialCreationOptions#getAttestation()
716
     * @see <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-attestation">§6.4.
717
     *     Attestation</a>
718
     */
719
    public RelyingPartyV2Builder<C> attestationConveyancePreference(
720
        @NonNull Optional<AttestationConveyancePreference> attestationConveyancePreference) {
721
      this.attestationConveyancePreference = attestationConveyancePreference;
722 1 1. attestationConveyancePreference : replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::attestationConveyancePreference → KILLED
      return this;
723
    }
724
725
    /**
726
     * The argument for the {@link PublicKeyCredentialCreationOptions#getAttestation() attestation}
727
     * parameter in registration operations.
728
     *
729
     * <p>Unless your application has a concrete policy for authenticator attestation, it is
730
     * recommended to leave this parameter undefined.
731
     *
732
     * <p>If you set this, you may want to explicitly set {@link
733
     * RelyingPartyV2Builder#allowUntrustedAttestation(boolean) allowUntrustedAttestation} and
734
     * {@link RelyingPartyV2Builder#attestationTrustSource(AttestationTrustSource)
735
     * attestationTrustSource} too.
736
     *
737
     * <p>By default, this is not set.
738
     *
739
     * @see PublicKeyCredentialCreationOptions#getAttestation()
740
     * @see <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-attestation">§6.4.
741
     *     Attestation</a>
742
     */
743
    public RelyingPartyV2Builder<C> attestationConveyancePreference(
744
        @NonNull AttestationConveyancePreference attestationConveyancePreference) {
745 1 1. attestationConveyancePreference : replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::attestationConveyancePreference → KILLED
      return this.attestationConveyancePreference(Optional.of(attestationConveyancePreference));
746
    }
747
748
    /**
749
     * An {@link AttestationTrustSource} instance to use for looking up trust roots for
750
     * authenticator attestation. This matters only if {@link #getAttestationConveyancePreference()}
751
     * is non-empty and not set to {@link AttestationConveyancePreference#NONE}.
752
     *
753
     * <p>By default, this is not set.
754
     *
755
     * @see PublicKeyCredentialCreationOptions#getAttestation()
756
     * @see <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-attestation">§6.4.
757
     *     Attestation</a>
758
     */
759
    public RelyingPartyV2Builder<C> attestationTrustSource(
760
        @NonNull Optional<AttestationTrustSource> attestationTrustSource) {
761
      this.attestationTrustSource = attestationTrustSource;
762 1 1. attestationTrustSource : replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::attestationTrustSource → KILLED
      return this;
763
    }
764
765
    /**
766
     * An {@link AttestationTrustSource} instance to use for looking up trust roots for
767
     * authenticator attestation. This matters only if {@link #getAttestationConveyancePreference()}
768
     * is non-empty and not set to {@link AttestationConveyancePreference#NONE}.
769
     *
770
     * <p>By default, this is not set.
771
     *
772
     * @see PublicKeyCredentialCreationOptions#getAttestation()
773
     * @see <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-attestation">§6.4.
774
     *     Attestation</a>
775
     */
776
    public RelyingPartyV2Builder<C> attestationTrustSource(
777
        @NonNull AttestationTrustSource attestationTrustSource) {
778 1 1. attestationTrustSource : replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::attestationTrustSource → KILLED
      return this.attestationTrustSource(Optional.of(attestationTrustSource));
779
    }
780
  }
781
}

Mutations

395

1.1
Location : <init>
Killed by : com.yubico.webauthn.RelyingPartyTest.testOriginsWithOptionalSet(com.yubico.webauthn.RelyingPartyTest)
negated conditional → KILLED

424

1.1
Location : generateChallenge
Killed by : com.yubico.webauthn.RelyingPartyStartOperationSpec
removed call to java/security/SecureRandom::nextBytes → KILLED

425

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

436

1.1
Location : filterAvailableAlgorithms
Killed by : com.yubico.webauthn.RelyingPartyV2RegistrationSpec
replaced return value with Collections.emptyList for com/yubico/webauthn/RelyingPartyV2::filterAvailableAlgorithms → KILLED

465

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

466

1.1
Location : startRegistration
Killed by : com.yubico.webauthn.RelyingPartyStartOperationSpec
replaced return value with null for com/yubico/webauthn/RelyingPartyV2::startRegistration → KILLED

472

1.1
Location : finishRegistration
Killed by : com.yubico.webauthn.RelyingPartyV2RegistrationSpec
replaced return value with null for com/yubico/webauthn/RelyingPartyV2::finishRegistration → KILLED

486

1.1
Location : _finishRegistration
Killed by : com.yubico.webauthn.RelyingPartyV2RegistrationSpec
replaced return value with null for com/yubico/webauthn/RelyingPartyV2::_finishRegistration → KILLED

490

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

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

503

1.1
Location : lambda$startAssertion$1
Killed by : com.yubico.webauthn.RelyingPartyStartOperationSpec
replaced return value with Optional.empty for com/yubico/webauthn/RelyingPartyV2::lambda$startAssertion$1 → KILLED

506

1.1
Location : lambda$startAssertion$0
Killed by : com.yubico.webauthn.RelyingPartyStartOperationSpec
replaced return value with Optional.empty for com/yubico/webauthn/RelyingPartyV2::lambda$startAssertion$0 → KILLED

516

1.1
Location : lambda$startAssertion$2
Killed by : com.yubico.webauthn.RelyingPartyStartOperationSpec
replaced return value with Collections.emptyList for com/yubico/webauthn/RelyingPartyV2::lambda$startAssertion$2 → KILLED

524

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

526

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

544

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

558

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

563

1.1
Location : builder
Killed by : com.yubico.webauthn.RelyingPartyTest.testOriginsWithOptionalSet(com.yubico.webauthn.RelyingPartyTest)
replaced return value with null for com/yubico/webauthn/RelyingPartyV2::builder → KILLED

603

1.1
Location : origins
Killed by : com.yubico.webauthn.RelyingPartyUserIdentificationSpec
replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::origins → KILLED

637

1.1
Location : origins
Killed by : com.yubico.webauthn.RelyingPartyTest.testOriginsWithOptionalSet(com.yubico.webauthn.RelyingPartyTest)
replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::origins → KILLED

668

1.1
Location : appId
Killed by : com.yubico.webauthn.RelyingPartyStartOperationSpec
replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::appId → KILLED

698

1.1
Location : appId
Killed by : com.yubico.webauthn.RelyingPartyStartOperationSpec
replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::appId → KILLED

722

1.1
Location : attestationConveyancePreference
Killed by : com.yubico.webauthn.RelyingPartyStartOperationSpec
replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::attestationConveyancePreference → KILLED

745

1.1
Location : attestationConveyancePreference
Killed by : com.yubico.webauthn.RelyingPartyStartOperationSpec
replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::attestationConveyancePreference → KILLED

762

1.1
Location : attestationTrustSource
Killed by : com.yubico.webauthn.RelyingPartyV2RegistrationSpec
replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::attestationTrustSource → KILLED

778

1.1
Location : attestationTrustSource
Killed by : com.yubico.webauthn.RelyingPartyV2RegistrationSpec
replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::attestationTrustSource → KILLED

Active mutators

Tests examined


Report generated by PIT 1.20.3