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
   * </ol>
234
   *
235
   * @see PublicKeyCredentialCreationOptions#getAttestation()
236
   * @see <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-attestation">§6.4.
237
   *     Attestation</a>
238
   */
239
  @Builder.Default @NonNull
240
  private final List<PublicKeyCredentialParameters> preferredPubkeyParams =
241
      Collections.unmodifiableList(
242
          Arrays.asList(
243
              PublicKeyCredentialParameters.ES256,
244
              PublicKeyCredentialParameters.EdDSA,
245
              PublicKeyCredentialParameters.ES384,
246
              PublicKeyCredentialParameters.ES512,
247
              PublicKeyCredentialParameters.Ed448,
248
              PublicKeyCredentialParameters.RS256,
249
              PublicKeyCredentialParameters.RS384,
250
              PublicKeyCredentialParameters.RS512));
251
252
  /**
253
   * If <code>true</code>, the origin matching rule is relaxed to allow any port number.
254
   *
255
   * <p>The default is <code>false</code>.
256
   *
257
   * <p>Examples with <code>
258
   * origins: ["https://example.org", "https://accounts.example.org", "https://acme.com:8443"]
259
   * </code>
260
   *
261
   * <ul>
262
   *   <li>
263
   *       <p><code>allowOriginPort: false</code>
264
   *       <p>Accepted:
265
   *       <ul>
266
   *         <li><code>https://example.org</code>
267
   *         <li><code>https://accounts.example.org</code>
268
   *         <li><code>https://acme.com:8443</code>
269
   *       </ul>
270
   *       <p>Rejected:
271
   *       <ul>
272
   *         <li><code>https://example.org:8443</code>
273
   *         <li><code>https://shop.example.org</code>
274
   *         <li><code>https://acme.com</code>
275
   *         <li><code>https://acme.com:9000</code>
276
   *       </ul>
277
   *   <li>
278
   *       <p><code>allowOriginPort: true</code>
279
   *       <p>Accepted:
280
   *       <ul>
281
   *         <li><code>https://example.org</code>
282
   *         <li><code>https://example.org:8443</code>
283
   *         <li><code>https://accounts.example.org</code>
284
   *         <li><code>https://acme.com</code>
285
   *         <li><code>https://acme.com:8443</code>
286
   *         <li><code>https://acme.com:9000</code>
287
   *       </ul>
288
   *       <p>Rejected:
289
   *       <ul>
290
   *         <li><code>https://shop.example.org</code>
291
   *       </ul>
292
   * </ul>
293
   */
294
  @Builder.Default private final boolean allowOriginPort = false;
295
296
  /**
297
   * If <code>true</code>, the origin matching rule is relaxed to allow any subdomain, of any depth,
298
   * of the values of {@link RelyingPartyV2Builder#origins(Set) origins}.
299
   *
300
   * <p>The default is <code>false</code>.
301
   *
302
   * <p>Examples with <code>origins: ["https://example.org", "https://acme.com:8443"]</code>
303
   *
304
   * <ul>
305
   *   <li>
306
   *       <p><code>allowOriginSubdomain: false</code>
307
   *       <p>Accepted:
308
   *       <ul>
309
   *         <li><code>https://example.org</code>
310
   *         <li><code>https://acme.com:8443</code>
311
   *       </ul>
312
   *       <p>Rejected:
313
   *       <ul>
314
   *         <li><code>https://example.org:8443</code>
315
   *         <li><code>https://accounts.example.org</code>
316
   *         <li><code>https://acme.com</code>
317
   *         <li><code>https://eu.shop.acme.com:8443</code>
318
   *       </ul>
319
   *   <li>
320
   *       <p><code>allowOriginSubdomain: true</code>
321
   *       <p>Accepted:
322
   *       <ul>
323
   *         <li><code>https://example.org</code>
324
   *         <li><code>https://accounts.example.org</code>
325
   *         <li><code>https://acme.com:8443</code>
326
   *         <li><code>https://eu.shop.acme.com:8443</code>
327
   *       </ul>
328
   *       <p>Rejected:
329
   *       <ul>
330
   *         <li><code>https://example.org:8443</code>
331
   *         <li><code>https://acme.com</code>
332
   *       </ul>
333
   * </ul>
334
   */
335
  @Builder.Default private final boolean allowOriginSubdomain = false;
336
337
  /**
338
   * If <code>false</code>, {@link #finishRegistration(FinishRegistrationOptions)
339
   * finishRegistration} will only allow registrations where the attestation signature can be linked
340
   * to a trusted attestation root. This excludes none attestation, and self attestation unless the
341
   * self attestation key is explicitly trusted.
342
   *
343
   * <p>Regardless of the value of this option, invalid attestation statements of supported formats
344
   * will always be rejected. For example, a "packed" attestation statement with an invalid
345
   * signature will be rejected even if this option is set to <code>true</code>.
346
   *
347
   * <p>The default is <code>true</code>.
348
   */
349
  @Builder.Default private final boolean allowUntrustedAttestation = true;
350
351
  /**
352
   * If <code>true</code>, {@link #finishAssertion(FinishAssertionOptions) finishAssertion} will
353
   * succeed only if the {@link AuthenticatorData#getSignatureCounter() signature counter value} in
354
   * the response is strictly greater than the {@link RegisteredCredential#getSignatureCount()
355
   * stored signature counter value}, or if both counters are exactly zero.
356
   *
357
   * <p>The default is <code>true</code>.
358
   */
359
  @Builder.Default private final boolean validateSignatureCounter = true;
360
361
  /**
362
   * A {@link Clock} which will be used to tell the current time while verifying attestation
363
   * certificate chains.
364
   *
365
   * <p>This is intended primarily for testing, and relevant only if {@link
366
   * RelyingPartyV2Builder#attestationTrustSource(AttestationTrustSource)} is set.
367
   *
368
   * <p>The default is <code>Clock.systemUTC()</code>.
369
   */
370
  @Builder.Default @NonNull private final Clock clock = Clock.systemUTC();
371
372
  @Builder
373
  private RelyingPartyV2(
374 1 1. <init> : negated conditional → KILLED
      @NonNull RelyingPartyIdentity identity,
375
      Set<String> origins,
376 1 1. <init> : negated conditional → KILLED
      @NonNull CredentialRepositoryV2<C> credentialRepository,
377
      UsernameRepository usernameRepository,
378 1 1. <init> : negated conditional → KILLED
      @NonNull Optional<AppId> appId,
379 1 1. <init> : negated conditional → KILLED
      @NonNull Optional<AttestationConveyancePreference> attestationConveyancePreference,
380 1 1. <init> : negated conditional → KILLED
      @NonNull Optional<AttestationTrustSource> attestationTrustSource,
381
      List<PublicKeyCredentialParameters> preferredPubkeyParams,
382
      boolean allowOriginPort,
383
      boolean allowOriginSubdomain,
384
      boolean allowUntrustedAttestation,
385
      boolean validateSignatureCounter,
386
      Clock clock) {
387
    this.identity = identity;
388
    this.origins =
389 1 1. <init> : negated conditional → KILLED
        origins != null
390
            ? CollectionUtil.immutableSet(origins)
391
            : Collections.singleton("https://" + identity.getId());
392
393
    for (String origin : this.origins) {
394
      try {
395
        new URL(origin);
396
      } catch (MalformedURLException e) {
397
        log.warn(
398
            "Allowed origin is not a valid URL, it will match only by exact string equality: {}",
399
            origin);
400
      }
401
    }
402
403
    this.credentialRepository = credentialRepository;
404
    this.usernameRepository = usernameRepository;
405
    this.appId = appId;
406
    this.attestationConveyancePreference = attestationConveyancePreference;
407
    this.attestationTrustSource = attestationTrustSource;
408
    this.preferredPubkeyParams = filterAvailableAlgorithms(preferredPubkeyParams);
409
    this.allowOriginPort = allowOriginPort;
410
    this.allowOriginSubdomain = allowOriginSubdomain;
411
    this.allowUntrustedAttestation = allowUntrustedAttestation;
412
    this.validateSignatureCounter = validateSignatureCounter;
413
    this.clock = clock;
414
  }
415
416
  private static ByteArray generateChallenge() {
417
    byte[] bytes = new byte[32];
418 1 1. generateChallenge : removed call to java/security/SecureRandom::nextBytes → KILLED
    random.nextBytes(bytes);
419 1 1. generateChallenge : replaced return value with null for com/yubico/webauthn/RelyingPartyV2::generateChallenge → KILLED
    return new ByteArray(bytes);
420
  }
421
422
  /**
423
   * Filter <code>pubKeyCredParams</code> to only contain algorithms with a {@link KeyFactory} and a
424
   * {@link Signature} available, and log a warning for every unsupported algorithm.
425
   *
426
   * @return a new {@link List} containing only the algorithms supported in the current JCA context.
427
   */
428
  private static List<PublicKeyCredentialParameters> filterAvailableAlgorithms(
429
      List<PublicKeyCredentialParameters> pubKeyCredParams) {
430 1 1. filterAvailableAlgorithms : replaced return value with Collections.emptyList for com/yubico/webauthn/RelyingPartyV2::filterAvailableAlgorithms → KILLED
    return RelyingParty.filterAvailableAlgorithms(pubKeyCredParams);
431
  }
432
433
  public PublicKeyCredentialCreationOptions startRegistration(
434
      StartRegistrationOptions startRegistrationOptions) {
435
    PublicKeyCredentialCreationOptionsBuilder builder =
436
        PublicKeyCredentialCreationOptions.builder()
437
            .rp(identity)
438
            .user(startRegistrationOptions.getUser())
439
            .challenge(generateChallenge())
440
            .pubKeyCredParams(preferredPubkeyParams)
441
            .excludeCredentials(
442
                credentialRepository
443
                    .getCredentialDescriptorsForUserHandle(
444
                        startRegistrationOptions.getUser().getId())
445
                    .stream()
446
                    .map(ToPublicKeyCredentialDescriptor::toPublicKeyCredentialDescriptor)
447
                    .collect(Collectors.toSet()))
448
            .authenticatorSelection(startRegistrationOptions.getAuthenticatorSelection())
449
            .extensions(
450
                startRegistrationOptions
451
                    .getExtensions()
452
                    .merge(
453
                        RegistrationExtensionInputs.builder()
454
                            .appidExclude(appId)
455
                            .credProps()
456
                            .build()))
457
            .timeout(startRegistrationOptions.getTimeout())
458
            .hints(startRegistrationOptions.getHints());
459 1 1. startRegistration : removed call to java/util/Optional::ifPresent → KILLED
    attestationConveyancePreference.ifPresent(builder::attestation);
460 1 1. startRegistration : replaced return value with null for com/yubico/webauthn/RelyingPartyV2::startRegistration → KILLED
    return builder.build();
461
  }
462
463
  public RegistrationResult finishRegistration(FinishRegistrationOptions finishRegistrationOptions)
464
      throws RegistrationFailedException {
465
    try {
466 1 1. finishRegistration : replaced return value with null for com/yubico/webauthn/RelyingPartyV2::finishRegistration → KILLED
      return _finishRegistration(finishRegistrationOptions).run();
467
    } catch (IllegalArgumentException e) {
468
      throw new RegistrationFailedException(e);
469
    }
470
  }
471
472
  /**
473
   * This method is NOT part of the public API.
474
   *
475
   * <p>This method is called internally by {@link #finishRegistration(FinishRegistrationOptions)}.
476
   * It is a separate method to facilitate testing; users should call {@link
477
   * #finishRegistration(FinishRegistrationOptions)} instead of this method.
478
   */
479
  FinishRegistrationSteps _finishRegistration(FinishRegistrationOptions options) {
480 1 1. _finishRegistration : replaced return value with null for com/yubico/webauthn/RelyingPartyV2::_finishRegistration → KILLED
    return new FinishRegistrationSteps(this, options);
481
  }
482
483
  public AssertionRequest startAssertion(StartAssertionOptions startAssertionOptions) {
484 2 1. startAssertion : negated conditional → KILLED
2. startAssertion : negated conditional → KILLED
    if (startAssertionOptions.getUsername().isPresent() && usernameRepository == null) {
485
      throw new IllegalArgumentException(
486
          "StartAssertionOptions.username must not be set when usernameRepository is not configured.");
487
    }
488
489
    PublicKeyCredentialRequestOptionsBuilder pkcro =
490
        PublicKeyCredentialRequestOptions.builder()
491
            .challenge(generateChallenge())
492
            .rpId(identity.getId())
493
            .allowCredentials(
494
                OptionalUtil.orElseOptional(
495
                        startAssertionOptions.getUserHandle(),
496
                        () ->
497 1 1. lambda$startAssertion$1 : replaced return value with Optional.empty for com/yubico/webauthn/RelyingPartyV2::lambda$startAssertion$1 → KILLED
                            Optional.ofNullable(usernameRepository)
498
                                .flatMap(
499
                                    unr ->
500 1 1. lambda$startAssertion$0 : replaced return value with Optional.empty for com/yubico/webauthn/RelyingPartyV2::lambda$startAssertion$0 → KILLED
                                        startAssertionOptions
501
                                            .getUsername()
502
                                            .flatMap(unr::getUserHandleForUsername)))
503
                    .map(credentialRepository::getCredentialDescriptorsForUserHandle)
504
                    .map(
505
                        descriptors ->
506
                            descriptors.stream()
507
                                .map(
508
                                    ToPublicKeyCredentialDescriptor
509
                                        ::toPublicKeyCredentialDescriptor)
510 1 1. lambda$startAssertion$2 : replaced return value with Collections.emptyList for com/yubico/webauthn/RelyingPartyV2::lambda$startAssertion$2 → KILLED
                                .collect(Collectors.toList())))
511
            .extensions(
512
                startAssertionOptions
513
                    .getExtensions()
514
                    .merge(startAssertionOptions.getExtensions().toBuilder().appid(appId).build()))
515
            .timeout(startAssertionOptions.getTimeout())
516
            .hints(startAssertionOptions.getHints());
517
518 1 1. startAssertion : removed call to java/util/Optional::ifPresent → KILLED
    startAssertionOptions.getUserVerification().ifPresent(pkcro::userVerification);
519
520 1 1. startAssertion : replaced return value with null for com/yubico/webauthn/RelyingPartyV2::startAssertion → KILLED
    return AssertionRequest.builder()
521
        .publicKeyCredentialRequestOptions(pkcro.build())
522
        .username(startAssertionOptions.getUsername())
523
        .userHandle(startAssertionOptions.getUserHandle())
524
        .build();
525
  }
526
527
  /**
528
   * @throws InvalidSignatureCountException if {@link
529
   *     RelyingPartyV2Builder#validateSignatureCounter(boolean) validateSignatureCounter} is <code>
530
   *     true</code>, the {@link AuthenticatorData#getSignatureCounter() signature count} in the
531
   *     response is less than or equal to the {@link RegisteredCredential#getSignatureCount()
532
   *     stored signature count}, and at least one of the signature count values is nonzero.
533
   * @throws AssertionFailedException if validation fails for any other reason.
534
   */
535
  public AssertionResultV2<C> finishAssertion(FinishAssertionOptions finishAssertionOptions)
536
      throws AssertionFailedException {
537
    try {
538 1 1. finishAssertion : replaced return value with null for com/yubico/webauthn/RelyingPartyV2::finishAssertion → KILLED
      return _finishAssertion(finishAssertionOptions).runV2();
539
    } catch (IllegalArgumentException e) {
540
      throw new AssertionFailedException(e);
541
    }
542
  }
543
544
  /**
545
   * This method is NOT part of the public API.
546
   *
547
   * <p>This method is called internally by {@link #finishAssertion(FinishAssertionOptions)}. It is
548
   * a separate method to facilitate testing; users should call {@link
549
   * #finishAssertion(FinishAssertionOptions)} instead of this method.
550
   */
551
  FinishAssertionSteps<C> _finishAssertion(FinishAssertionOptions options) {
552 1 1. _finishAssertion : replaced return value with null for com/yubico/webauthn/RelyingPartyV2::_finishAssertion → KILLED
    return new FinishAssertionSteps<C>(this, options);
553
  }
554
555
  static <C extends CredentialRecord> RelyingPartyV2Builder<C> builder(
556
      RelyingPartyIdentity identity, CredentialRepositoryV2<C> credentialRepository) {
557 1 1. builder : replaced return value with null for com/yubico/webauthn/RelyingPartyV2::builder → KILLED
    return new RelyingPartyV2Builder<C>()
558
        .identity(identity)
559
        .credentialRepository(credentialRepository);
560
  }
561
562
  public static class RelyingPartyV2Builder<C extends CredentialRecord> {
563
    private @NonNull Optional<AppId> appId = Optional.empty();
564
    private @NonNull Optional<AttestationConveyancePreference> attestationConveyancePreference =
565
        Optional.empty();
566
    private @NonNull Optional<AttestationTrustSource> attestationTrustSource = Optional.empty();
567
568
    /**
569
     * The allowed origins that returned authenticator responses will be compared against.
570
     *
571
     * <p>The default is the set containing only the string <code>
572
     * "https://" + {@link #getIdentity()}.getId()</code>.
573
     *
574
     * <p>If {@link RelyingPartyV2Builder#allowOriginPort(boolean) allowOriginPort} and {@link
575
     * RelyingPartyV2Builder#allowOriginSubdomain(boolean) allowOriginSubdomain} are both <code>
576
     * false</code> (the default), then a successful registration or authentication operation
577
     * requires {@link CollectedClientData#getOrigin()} to exactly equal one of these values.
578
     *
579
     * <p>If {@link RelyingPartyV2Builder#allowOriginPort(boolean) allowOriginPort} is <code>true
580
     * </code> , then the above rule is relaxed to allow any port number in {@link
581
     * CollectedClientData#getOrigin()}, regardless of any port specified.
582
     *
583
     * <p>If {@link RelyingPartyV2Builder#allowOriginSubdomain(boolean) allowOriginSubdomain} is
584
     * <code>true</code>, then the above rule is relaxed to allow any subdomain, of any depth, of
585
     * any of these values.
586
     *
587
     * <p>For either of the above relaxations to take effect, both the allowed origin and the client
588
     * data origin must be valid URLs. Origins that are not valid URLs are matched only by exact
589
     * string equality.
590
     *
591
     * @since 2.7.0
592
     * @see #getIdentity()
593
     * @see #origins(Optional)
594
     */
595 1 1. origins : negated conditional → KILLED
    public RelyingPartyV2Builder<C> origins(@NonNull Set<String> origins) {
596
      this.origins = origins;
597 1 1. origins : replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::origins → KILLED
      return this;
598
    }
599
600
    /**
601
     * The allowed origins that returned authenticator responses will be compared against.
602
     *
603
     * <p>If set to empty, this setting reverts to the default value.
604
     *
605
     * <p>The default is the set containing only the string <code>
606
     * "https://" + {@link #getIdentity()}.getId()</code>.
607
     *
608
     * <p>If {@link RelyingPartyV2Builder#allowOriginPort(boolean) allowOriginPort} and {@link
609
     * RelyingPartyV2Builder#allowOriginSubdomain(boolean) allowOriginSubdomain} are both <code>
610
     * false</code> (the default), then a successful registration or authentication operation
611
     * requires {@link CollectedClientData#getOrigin()} to exactly equal one of these values.
612
     *
613
     * <p>If {@link RelyingPartyV2Builder#allowOriginPort(boolean) allowOriginPort} is <code>true
614
     * </code> , then the above rule is relaxed to allow any port number in {@link
615
     * CollectedClientData#getOrigin()}, regardless of any port specified.
616
     *
617
     * <p>If {@link RelyingPartyV2Builder#allowOriginSubdomain(boolean) allowOriginSubdomain} is
618
     * <code>true</code>, then the above rule is relaxed to allow any subdomain, of any depth, of
619
     * any of these values.
620
     *
621
     * <p>For either of the above relaxations to take effect, both the allowed origin and the client
622
     * data origin must be valid URLs. Origins that are not valid URLs are matched only by exact
623
     * string equality.
624
     *
625
     * @since 2.7.0
626
     * @see #getIdentity()
627
     * @see #origins(Set)
628
     */
629 1 1. origins : negated conditional → KILLED
    public RelyingPartyV2Builder<C> origins(@NonNull Optional<Set<String>> origins) {
630
      this.origins = origins.orElse(null);
631 1 1. origins : replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::origins → KILLED
      return this;
632
    }
633
634
    /**
635
     * The extension input to set for the <code>appid</code> and <code>appidExclude</code>
636
     * extensions.
637
     *
638
     * <p>You do not need this extension if you have not previously supported U2F. Its purpose is to
639
     * make already-registered U2F credentials forward-compatible with the WebAuthn API. It is not
640
     * needed for new registrations, even of U2F authenticators.
641
     *
642
     * <p>If this member is set, {@link #startAssertion(StartAssertionOptions) startAssertion} will
643
     * automatically set the <code>appid</code> extension input, and {@link
644
     * #finishAssertion(FinishAssertionOptions) finishAssertion} will adjust its verification logic
645
     * to also accept this AppID as an alternative to the RP ID. Likewise, {@link
646
     * #startRegistration(StartRegistrationOptions)} startRegistration} will automatically set the
647
     * <code>appidExclude</code> extension input.
648
     *
649
     * <p>By default, this is not set.
650
     *
651
     * @see AssertionExtensionInputs#getAppid()
652
     * @see RegistrationExtensionInputs#getAppidExclude()
653
     * @see <a
654
     *     href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-appid-extension">§10.1.
655
     *     FIDO AppID Extension (appid)</a>
656
     * @see <a
657
     *     href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-appid-exclude-extension">§10.2.
658
     *     FIDO AppID Exclusion Extension (appidExclude)</a>
659
     */
660 1 1. appId : negated conditional → KILLED
    public RelyingPartyV2Builder<C> appId(@NonNull Optional<AppId> appId) {
661
      this.appId = appId;
662 1 1. appId : replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::appId → KILLED
      return this;
663
    }
664
665
    /**
666
     * The extension input to set for the <code>appid</code> and <code>appidExclude</code>
667
     * extensions.
668
     *
669
     * <p>You do not need this extension if you have not previously supported U2F. Its purpose is to
670
     * make already-registered U2F credentials forward-compatible with the WebAuthn API. It is not
671
     * needed for new registrations, even of U2F authenticators.
672
     *
673
     * <p>If this member is set, {@link #startAssertion(StartAssertionOptions) startAssertion} will
674
     * automatically set the <code>appid</code> extension input, and {@link
675
     * #finishAssertion(FinishAssertionOptions) finishAssertion} will adjust its verification logic
676
     * to also accept this AppID as an alternative to the RP ID. Likewise, {@link
677
     * #startRegistration(StartRegistrationOptions)} startRegistration} will automatically set the
678
     * <code>appidExclude</code> extension input.
679
     *
680
     * <p>By default, this is not set.
681
     *
682
     * @see AssertionExtensionInputs#getAppid()
683
     * @see RegistrationExtensionInputs#getAppidExclude()
684
     * @see <a
685
     *     href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-appid-extension">§10.1.
686
     *     FIDO AppID Extension (appid)</a>
687
     * @see <a
688
     *     href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-appid-exclude-extension">§10.2.
689
     *     FIDO AppID Exclusion Extension (appidExclude)</a>
690
     */
691 1 1. appId : negated conditional → KILLED
    public RelyingPartyV2Builder<C> appId(@NonNull AppId appId) {
692 1 1. appId : replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::appId → KILLED
      return this.appId(Optional.of(appId));
693
    }
694
695
    /**
696
     * The argument for the {@link PublicKeyCredentialCreationOptions#getAttestation() attestation}
697
     * parameter in registration operations.
698
     *
699
     * <p>Unless your application has a concrete policy for authenticator attestation, it is
700
     * recommended to leave this parameter undefined.
701
     *
702
     * <p>If you set this, you may want to explicitly set {@link
703
     * RelyingPartyV2Builder#allowUntrustedAttestation(boolean) allowUntrustedAttestation} and
704
     * {@link RelyingPartyV2Builder#attestationTrustSource(AttestationTrustSource)
705
     * attestationTrustSource} too.
706
     *
707
     * <p>By default, this is not set.
708
     *
709
     * @see PublicKeyCredentialCreationOptions#getAttestation()
710
     * @see <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-attestation">§6.4.
711
     *     Attestation</a>
712
     */
713
    public RelyingPartyV2Builder<C> attestationConveyancePreference(
714 1 1. attestationConveyancePreference : negated conditional → KILLED
        @NonNull Optional<AttestationConveyancePreference> attestationConveyancePreference) {
715
      this.attestationConveyancePreference = attestationConveyancePreference;
716 1 1. attestationConveyancePreference : replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::attestationConveyancePreference → KILLED
      return this;
717
    }
718
719
    /**
720
     * The argument for the {@link PublicKeyCredentialCreationOptions#getAttestation() attestation}
721
     * parameter in registration operations.
722
     *
723
     * <p>Unless your application has a concrete policy for authenticator attestation, it is
724
     * recommended to leave this parameter undefined.
725
     *
726
     * <p>If you set this, you may want to explicitly set {@link
727
     * RelyingPartyV2Builder#allowUntrustedAttestation(boolean) allowUntrustedAttestation} and
728
     * {@link RelyingPartyV2Builder#attestationTrustSource(AttestationTrustSource)
729
     * attestationTrustSource} too.
730
     *
731
     * <p>By default, this is not set.
732
     *
733
     * @see PublicKeyCredentialCreationOptions#getAttestation()
734
     * @see <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-attestation">§6.4.
735
     *     Attestation</a>
736
     */
737
    public RelyingPartyV2Builder<C> attestationConveyancePreference(
738 1 1. attestationConveyancePreference : negated conditional → KILLED
        @NonNull AttestationConveyancePreference attestationConveyancePreference) {
739 1 1. attestationConveyancePreference : replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::attestationConveyancePreference → KILLED
      return this.attestationConveyancePreference(Optional.of(attestationConveyancePreference));
740
    }
741
742
    /**
743
     * An {@link AttestationTrustSource} instance to use for looking up trust roots for
744
     * authenticator attestation. This matters only if {@link #getAttestationConveyancePreference()}
745
     * is non-empty and not set to {@link AttestationConveyancePreference#NONE}.
746
     *
747
     * <p>By default, this is not set.
748
     *
749
     * @see PublicKeyCredentialCreationOptions#getAttestation()
750
     * @see <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-attestation">§6.4.
751
     *     Attestation</a>
752
     */
753
    public RelyingPartyV2Builder<C> attestationTrustSource(
754 1 1. attestationTrustSource : negated conditional → KILLED
        @NonNull Optional<AttestationTrustSource> attestationTrustSource) {
755
      this.attestationTrustSource = attestationTrustSource;
756 1 1. attestationTrustSource : replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::attestationTrustSource → KILLED
      return this;
757
    }
758
759
    /**
760
     * An {@link AttestationTrustSource} instance to use for looking up trust roots for
761
     * authenticator attestation. This matters only if {@link #getAttestationConveyancePreference()}
762
     * is non-empty and not set to {@link AttestationConveyancePreference#NONE}.
763
     *
764
     * <p>By default, this is not set.
765
     *
766
     * @see PublicKeyCredentialCreationOptions#getAttestation()
767
     * @see <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-attestation">§6.4.
768
     *     Attestation</a>
769
     */
770
    public RelyingPartyV2Builder<C> attestationTrustSource(
771 1 1. attestationTrustSource : negated conditional → KILLED
        @NonNull AttestationTrustSource attestationTrustSource) {
772 1 1. attestationTrustSource : replaced return value with null for com/yubico/webauthn/RelyingPartyV2$RelyingPartyV2Builder::attestationTrustSource → KILLED
      return this.attestationTrustSource(Optional.of(attestationTrustSource));
773
    }
774
  }
775
}

Mutations

374

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

376

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

378

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

379

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

380

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

389

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

418

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

419

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

430

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

459

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

460

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

466

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

480

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

484

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

497

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

500

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

510

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

518

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

520

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

538

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

552

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

557

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

595

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

597

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

629

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

631

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

660

1.1
Location : appId
Killed by : com.yubico.webauthn.RelyingPartyStartOperationSpec
negated conditional → KILLED

662

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

691

1.1
Location : appId
Killed by : com.yubico.webauthn.RelyingPartyStartOperationSpec
negated conditional → KILLED

692

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

714

1.1
Location : attestationConveyancePreference
Killed by : com.yubico.webauthn.RelyingPartyStartOperationSpec
negated conditional → KILLED

716

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

738

1.1
Location : attestationConveyancePreference
Killed by : com.yubico.webauthn.RelyingPartyStartOperationSpec
negated conditional → KILLED

739

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

754

1.1
Location : attestationTrustSource
Killed by : com.yubico.webauthn.RelyingPartyV2RegistrationSpec
negated conditional → KILLED

756

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

771

1.1
Location : attestationTrustSource
Killed by : com.yubico.webauthn.RelyingPartyV2RegistrationSpec
negated conditional → KILLED

772

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.15.0