| 1 | // Copyright (c) 2018, Yubico AB | |
| 2 | // All rights reserved. | |
| 3 | // | |
| 4 | // Redistribution and use in source and binary forms, with or without | |
| 5 | // modification, are permitted provided that the following conditions are met: | |
| 6 | // | |
| 7 | // 1. Redistributions of source code must retain the above copyright notice, this | |
| 8 | // list of conditions and the following disclaimer. | |
| 9 | // | |
| 10 | // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| 11 | // this list of conditions and the following disclaimer in the documentation | |
| 12 | // and/or other materials provided with the distribution. | |
| 13 | // | |
| 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| 17 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| 18 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| 19 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| 20 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| 21 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| 22 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 23 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 24 | ||
| 25 | package com.yubico.webauthn; | |
| 26 | ||
| 27 | import static com.yubico.internal.util.ExceptionUtil.assertTrue; | |
| 28 | import static com.yubico.internal.util.ExceptionUtil.wrapAndLog; | |
| 29 | ||
| 30 | import com.upokecenter.cbor.CBORObject; | |
| 31 | import com.yubico.internal.util.CertificateParser; | |
| 32 | import com.yubico.internal.util.OptionalUtil; | |
| 33 | import com.yubico.webauthn.attestation.AttestationTrustSource; | |
| 34 | import com.yubico.webauthn.attestation.AttestationTrustSource.TrustRootsResult; | |
| 35 | import com.yubico.webauthn.data.AttestationObject; | |
| 36 | import com.yubico.webauthn.data.AttestationType; | |
| 37 | import com.yubico.webauthn.data.AuthenticatorAttestationResponse; | |
| 38 | import com.yubico.webauthn.data.AuthenticatorSelectionCriteria; | |
| 39 | import com.yubico.webauthn.data.ByteArray; | |
| 40 | import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs; | |
| 41 | import com.yubico.webauthn.data.CollectedClientData; | |
| 42 | import com.yubico.webauthn.data.Extensions; | |
| 43 | import com.yubico.webauthn.data.PublicKeyCredential; | |
| 44 | import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions; | |
| 45 | import com.yubico.webauthn.data.PublicKeyCredentialParameters; | |
| 46 | import com.yubico.webauthn.data.UserVerificationRequirement; | |
| 47 | import java.io.IOException; | |
| 48 | import java.security.InvalidAlgorithmParameterException; | |
| 49 | import java.security.NoSuchAlgorithmException; | |
| 50 | import java.security.cert.CertPath; | |
| 51 | import java.security.cert.CertPathValidator; | |
| 52 | import java.security.cert.CertPathValidatorException; | |
| 53 | import java.security.cert.CertificateException; | |
| 54 | import java.security.cert.CertificateFactory; | |
| 55 | import java.security.cert.PKIXCertPathValidatorResult; | |
| 56 | import java.security.cert.PKIXParameters; | |
| 57 | import java.security.cert.PKIXReason; | |
| 58 | import java.security.cert.TrustAnchor; | |
| 59 | import java.security.cert.X509Certificate; | |
| 60 | import java.security.spec.InvalidKeySpecException; | |
| 61 | import java.sql.Date; | |
| 62 | import java.time.Clock; | |
| 63 | import java.util.List; | |
| 64 | import java.util.Optional; | |
| 65 | import java.util.Set; | |
| 66 | import java.util.stream.Collectors; | |
| 67 | import lombok.AllArgsConstructor; | |
| 68 | import lombok.Value; | |
| 69 | import lombok.extern.slf4j.Slf4j; | |
| 70 | ||
| 71 | @Slf4j | |
| 72 | @AllArgsConstructor | |
| 73 | final class FinishRegistrationSteps { | |
| 74 | ||
| 75 | private static final String CLIENT_DATA_TYPE = "webauthn.create"; | |
| 76 | private static final ByteArray ZERO_AAGUID = | |
| 77 | new ByteArray(new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); | |
| 78 | ||
| 79 | private final PublicKeyCredentialCreationOptions request; | |
| 80 | private final PublicKeyCredential< | |
| 81 | AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> | |
| 82 | response; | |
| 83 | private final Optional<ByteArray> callerTokenBindingId; | |
| 84 | private final Set<String> origins; | |
| 85 | private final String rpId; | |
| 86 | private final boolean allowUntrustedAttestation; | |
| 87 | private final Optional<AttestationTrustSource> attestationTrustSource; | |
| 88 | private final CredentialRepositoryV2<?> credentialRepositoryV2; | |
| 89 | private final Clock clock; | |
| 90 | private final boolean allowOriginPort; | |
| 91 | private final boolean allowOriginSubdomain; | |
| 92 | private final boolean isConditionalCreate; | |
| 93 | ||
| 94 | static FinishRegistrationSteps fromV1(RelyingParty rp, FinishRegistrationOptions options) { | |
| 95 |
1
1. fromV1 : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps::fromV1 → KILLED |
return new FinishRegistrationSteps( |
| 96 | options.getRequest(), | |
| 97 | options.getResponse(), | |
| 98 | options.getCallerTokenBindingId(), | |
| 99 | rp.getOrigins(), | |
| 100 | rp.getIdentity().getId(), | |
| 101 | rp.isAllowUntrustedAttestation(), | |
| 102 | rp.getAttestationTrustSource(), | |
| 103 | new CredentialRepositoryV1ToV2Adapter(rp.getCredentialRepository()), | |
| 104 | rp.getClock(), | |
| 105 | rp.isAllowOriginPort(), | |
| 106 | rp.isAllowOriginSubdomain(), | |
| 107 | options.isConditionalCreate()); | |
| 108 | } | |
| 109 | ||
| 110 | FinishRegistrationSteps(RelyingPartyV2<?> rp, FinishRegistrationOptions options) { | |
| 111 | this( | |
| 112 | options.getRequest(), | |
| 113 | options.getResponse(), | |
| 114 | options.getCallerTokenBindingId(), | |
| 115 | rp.getOrigins(), | |
| 116 | rp.getIdentity().getId(), | |
| 117 | rp.isAllowUntrustedAttestation(), | |
| 118 | rp.getAttestationTrustSource(), | |
| 119 | rp.getCredentialRepository(), | |
| 120 | rp.getClock(), | |
| 121 | rp.isAllowOriginPort(), | |
| 122 | rp.isAllowOriginSubdomain(), | |
| 123 | options.isConditionalCreate()); | |
| 124 | } | |
| 125 | ||
| 126 | public Step6 begin() { | |
| 127 |
1
1. begin : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps::begin → KILLED |
return new Step6(); |
| 128 | } | |
| 129 | ||
| 130 | public RegistrationResult run() { | |
| 131 |
1
1. run : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps::run → KILLED |
return begin().run(); |
| 132 | } | |
| 133 | ||
| 134 | interface Step<Next extends Step<?>> { | |
| 135 | Next nextStep(); | |
| 136 | ||
| 137 | void validate(); | |
| 138 | ||
| 139 | default Optional<RegistrationResult> result() { | |
| 140 | return Optional.empty(); | |
| 141 | } | |
| 142 | ||
| 143 | default Next next() { | |
| 144 |
1
1. next : removed call to com/yubico/webauthn/FinishRegistrationSteps$Step::validate → TIMED_OUT |
validate(); |
| 145 |
1
1. next : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step::next → KILLED |
return nextStep(); |
| 146 | } | |
| 147 | ||
| 148 | default RegistrationResult run() { | |
| 149 |
1
1. run : negated conditional → KILLED |
if (result().isPresent()) { |
| 150 |
1
1. run : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step::run → KILLED |
return result().get(); |
| 151 | } else { | |
| 152 |
1
1. run : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step::run → KILLED |
return next().run(); |
| 153 | } | |
| 154 | } | |
| 155 | } | |
| 156 | ||
| 157 | // Steps 1 through 4 are to create the request and run the client-side part | |
| 158 | ||
| 159 | // Step 5 is integrated into step 6 here | |
| 160 | ||
| 161 | @Value | |
| 162 | class Step6 implements Step<Step7> { | |
| 163 | @Override | |
| 164 | public void validate() { | |
| 165 |
2
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED 2. validate : negated conditional → KILLED |
assertTrue(clientData() != null, "Client data must not be null."); |
| 166 | } | |
| 167 | ||
| 168 | @Override | |
| 169 | public Step7 nextStep() { | |
| 170 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step6::nextStep → KILLED |
return new Step7(clientData()); |
| 171 | } | |
| 172 | ||
| 173 | public CollectedClientData clientData() { | |
| 174 |
1
1. clientData : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step6::clientData → KILLED |
return response.getResponse().getClientData(); |
| 175 | } | |
| 176 | } | |
| 177 | ||
| 178 | @Value | |
| 179 | class Step7 implements Step<Step8> { | |
| 180 | private final CollectedClientData clientData; | |
| 181 | ||
| 182 | @Override | |
| 183 | public void validate() { | |
| 184 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
| 185 | CLIENT_DATA_TYPE.equals(clientData.getType()), | |
| 186 | "The \"type\" in the client data must be exactly \"%s\", was: %s", | |
| 187 | CLIENT_DATA_TYPE, | |
| 188 | clientData.getType()); | |
| 189 | } | |
| 190 | ||
| 191 | @Override | |
| 192 | public Step8 nextStep() { | |
| 193 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step7::nextStep → KILLED |
return new Step8(clientData); |
| 194 | } | |
| 195 | } | |
| 196 | ||
| 197 | @Value | |
| 198 | class Step8 implements Step<Step9> { | |
| 199 | private final CollectedClientData clientData; | |
| 200 | ||
| 201 | @Override | |
| 202 | public void validate() { | |
| 203 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue(request.getChallenge().equals(clientData.getChallenge()), "Incorrect challenge."); |
| 204 | } | |
| 205 | ||
| 206 | @Override | |
| 207 | public Step9 nextStep() { | |
| 208 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step8::nextStep → KILLED |
return new Step9(clientData); |
| 209 | } | |
| 210 | } | |
| 211 | ||
| 212 | @Value | |
| 213 | class Step9 implements Step<Step10> { | |
| 214 | private final CollectedClientData clientData; | |
| 215 | ||
| 216 | @Override | |
| 217 | public void validate() { | |
| 218 | final String responseOrigin = clientData.getOrigin(); | |
| 219 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
| 220 | OriginMatcher.isAllowed(responseOrigin, origins, allowOriginPort, allowOriginSubdomain), | |
| 221 | "Incorrect origin, please see the RelyingParty.origins setting: %s", | |
| 222 | responseOrigin); | |
| 223 | } | |
| 224 | ||
| 225 | @Override | |
| 226 | public Step10 nextStep() { | |
| 227 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step9::nextStep → KILLED |
return new Step10(clientData); |
| 228 | } | |
| 229 | } | |
| 230 | ||
| 231 | @Value | |
| 232 | class Step10 implements Step<Step11> { | |
| 233 | private final CollectedClientData clientData; | |
| 234 | ||
| 235 | @Override | |
| 236 | public void validate() { | |
| 237 | TokenBindingValidator.validate(clientData.getTokenBinding(), callerTokenBindingId); | |
| 238 | } | |
| 239 | ||
| 240 | @Override | |
| 241 | public Step11 nextStep() { | |
| 242 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step10::nextStep → KILLED |
return new Step11(); |
| 243 | } | |
| 244 | } | |
| 245 | ||
| 246 | @Value | |
| 247 | class Step11 implements Step<Step12> { | |
| 248 | @Override | |
| 249 | public void validate() { | |
| 250 |
2
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED 2. validate : negated conditional → KILLED |
assertTrue(clientDataJsonHash().size() == 32, "Failed to compute hash of client data"); |
| 251 | } | |
| 252 | ||
| 253 | @Override | |
| 254 | public Step12 nextStep() { | |
| 255 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step11::nextStep → KILLED |
return new Step12(clientDataJsonHash()); |
| 256 | } | |
| 257 | ||
| 258 | public ByteArray clientDataJsonHash() { | |
| 259 |
1
1. clientDataJsonHash : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step11::clientDataJsonHash → KILLED |
return Crypto.sha256(response.getResponse().getClientDataJSON()); |
| 260 | } | |
| 261 | } | |
| 262 | ||
| 263 | @Value | |
| 264 | class Step12 implements Step<Step13> { | |
| 265 | private final ByteArray clientDataJsonHash; | |
| 266 | ||
| 267 | @Override | |
| 268 | public void validate() { | |
| 269 |
2
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED 2. validate : negated conditional → KILLED |
assertTrue(attestation() != null, "Malformed attestation object."); |
| 270 | } | |
| 271 | ||
| 272 | @Override | |
| 273 | public Step13 nextStep() { | |
| 274 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step12::nextStep → KILLED |
return new Step13(clientDataJsonHash, attestation()); |
| 275 | } | |
| 276 | ||
| 277 | public AttestationObject attestation() { | |
| 278 |
1
1. attestation : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step12::attestation → KILLED |
return response.getResponse().getAttestation(); |
| 279 | } | |
| 280 | } | |
| 281 | ||
| 282 | @Value | |
| 283 | class Step13 implements Step<Step14> { | |
| 284 | private final ByteArray clientDataJsonHash; | |
| 285 | private final AttestationObject attestation; | |
| 286 | ||
| 287 | @Override | |
| 288 | public void validate() { | |
| 289 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
| 290 | Crypto.sha256(rpId) | |
| 291 | .equals(response.getResponse().getAttestation().getAuthenticatorData().getRpIdHash()), | |
| 292 | "Wrong RP ID hash."); | |
| 293 | } | |
| 294 | ||
| 295 | @Override | |
| 296 | public Step14 nextStep() { | |
| 297 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step13::nextStep → KILLED |
return new Step14(clientDataJsonHash, attestation); |
| 298 | } | |
| 299 | } | |
| 300 | ||
| 301 | @Value | |
| 302 | class Step14 implements Step<Step15> { | |
| 303 | private final ByteArray clientDataJsonHash; | |
| 304 | private final AttestationObject attestation; | |
| 305 | ||
| 306 | @Override | |
| 307 | public void validate() { | |
| 308 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
| 309 |
2
1. validate : negated conditional → KILLED 2. validate : negated conditional → KILLED |
isConditionalCreate || response.getResponse().getParsedAuthenticatorData().getFlags().UP, |
| 310 | "User Presence is required unless isConditionalCreate is true."); | |
| 311 | } | |
| 312 | ||
| 313 | @Override | |
| 314 | public Step15 nextStep() { | |
| 315 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step14::nextStep → KILLED |
return new Step15(clientDataJsonHash, attestation); |
| 316 | } | |
| 317 | } | |
| 318 | ||
| 319 | @Value | |
| 320 | class Step15 implements Step<Step16> { | |
| 321 | private final ByteArray clientDataJsonHash; | |
| 322 | private final AttestationObject attestation; | |
| 323 | ||
| 324 | @Override | |
| 325 | public void validate() { | |
| 326 | if (request | |
| 327 | .getAuthenticatorSelection() | |
| 328 | .flatMap(AuthenticatorSelectionCriteria::getUserVerification) | |
| 329 |
1
1. validate : negated conditional → KILLED |
.orElse(UserVerificationRequirement.PREFERRED) |
| 330 | == UserVerificationRequirement.REQUIRED) { | |
| 331 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
| 332 | response.getResponse().getParsedAuthenticatorData().getFlags().UV, | |
| 333 | "User Verification is required."); | |
| 334 | } | |
| 335 | } | |
| 336 | ||
| 337 | @Override | |
| 338 | public Step16 nextStep() { | |
| 339 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step15::nextStep → KILLED |
return new Step16(clientDataJsonHash, attestation); |
| 340 | } | |
| 341 | } | |
| 342 | ||
| 343 | @Value | |
| 344 | class Step16 implements Step<Step17> { | |
| 345 | private final ByteArray clientDataJsonHash; | |
| 346 | private final AttestationObject attestation; | |
| 347 | ||
| 348 | @Override | |
| 349 | public void validate() { | |
| 350 | final ByteArray publicKeyCose = | |
| 351 | response | |
| 352 | .getResponse() | |
| 353 | .getAttestation() | |
| 354 | .getAuthenticatorData() | |
| 355 | .getAttestedCredentialData() | |
| 356 | .get() | |
| 357 | .getCredentialPublicKey(); | |
| 358 | CBORObject publicKeyCbor = CBORObject.DecodeFromBytes(publicKeyCose.getBytes()); | |
| 359 | final int alg = publicKeyCbor.get(CBORObject.FromObject(3)).AsInt32(); | |
| 360 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
| 361 | request.getPubKeyCredParams().stream() | |
| 362 |
2
1. lambda$validate$0 : replaced boolean return with true for com/yubico/webauthn/FinishRegistrationSteps$Step16::lambda$validate$0 → KILLED 2. lambda$validate$0 : negated conditional → KILLED |
.anyMatch(pkcparam -> pkcparam.getAlg().getId() == alg), |
| 363 | "Unrequested credential key algorithm: got %d, expected one of: %s", | |
| 364 | alg, | |
| 365 | request.getPubKeyCredParams().stream() | |
| 366 | .map(PublicKeyCredentialParameters::getAlg) | |
| 367 | .collect(Collectors.toList())); | |
| 368 | try { | |
| 369 | WebAuthnCodecs.importCosePublicKey(publicKeyCose); | |
| 370 | } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) { | |
| 371 | throw wrapAndLog(log, "Failed to parse credential public key", e); | |
| 372 | } | |
| 373 | } | |
| 374 | ||
| 375 | @Override | |
| 376 | public Step17 nextStep() { | |
| 377 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step16::nextStep → KILLED |
return new Step17(clientDataJsonHash, attestation); |
| 378 | } | |
| 379 | } | |
| 380 | ||
| 381 | @Value | |
| 382 | class Step17 implements Step<Step18> { | |
| 383 | private final ByteArray clientDataJsonHash; | |
| 384 | private final AttestationObject attestation; | |
| 385 | ||
| 386 | @Override | |
| 387 | public void validate() { | |
| 388 |
1
1. validate : removed call to com/yubico/webauthn/data/Extensions$CredentialProtection::validateExtensionOutput → KILLED |
Extensions.CredentialProtection.validateExtensionOutput(request, response); |
| 389 | } | |
| 390 | ||
| 391 | @Override | |
| 392 | public Step18 nextStep() { | |
| 393 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step17::nextStep → KILLED |
return new Step18(clientDataJsonHash, attestation); |
| 394 | } | |
| 395 | } | |
| 396 | ||
| 397 | @Value | |
| 398 | class Step18 implements Step<Step19> { | |
| 399 | private final ByteArray clientDataJsonHash; | |
| 400 | private final AttestationObject attestation; | |
| 401 | ||
| 402 | @Override | |
| 403 | public void validate() {} | |
| 404 | ||
| 405 | @Override | |
| 406 | public Step19 nextStep() { | |
| 407 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step18::nextStep → KILLED |
return new Step19(clientDataJsonHash, attestation, attestationStatementVerifier()); |
| 408 | } | |
| 409 | ||
| 410 | public String format() { | |
| 411 |
1
1. format : replaced return value with "" for com/yubico/webauthn/FinishRegistrationSteps$Step18::format → KILLED |
return attestation.getFormat(); |
| 412 | } | |
| 413 | ||
| 414 | public Optional<AttestationStatementVerifier> attestationStatementVerifier() { | |
| 415 | switch (format()) { | |
| 416 | case "fido-u2f": | |
| 417 |
1
1. attestationStatementVerifier : replaced return value with Optional.empty for com/yubico/webauthn/FinishRegistrationSteps$Step18::attestationStatementVerifier → KILLED |
return Optional.of(new FidoU2fAttestationStatementVerifier()); |
| 418 | case "none": | |
| 419 |
1
1. attestationStatementVerifier : replaced return value with Optional.empty for com/yubico/webauthn/FinishRegistrationSteps$Step18::attestationStatementVerifier → KILLED |
return Optional.of(new NoneAttestationStatementVerifier()); |
| 420 | case "packed": | |
| 421 |
1
1. attestationStatementVerifier : replaced return value with Optional.empty for com/yubico/webauthn/FinishRegistrationSteps$Step18::attestationStatementVerifier → KILLED |
return Optional.of(new PackedAttestationStatementVerifier()); |
| 422 | case "android-safetynet": | |
| 423 |
1
1. attestationStatementVerifier : replaced return value with Optional.empty for com/yubico/webauthn/FinishRegistrationSteps$Step18::attestationStatementVerifier → KILLED |
return Optional.of(new AndroidSafetynetAttestationStatementVerifier()); |
| 424 | case "apple": | |
| 425 |
1
1. attestationStatementVerifier : replaced return value with Optional.empty for com/yubico/webauthn/FinishRegistrationSteps$Step18::attestationStatementVerifier → KILLED |
return Optional.of(new AppleAttestationStatementVerifier()); |
| 426 | case "tpm": | |
| 427 |
1
1. attestationStatementVerifier : replaced return value with Optional.empty for com/yubico/webauthn/FinishRegistrationSteps$Step18::attestationStatementVerifier → KILLED |
return Optional.of(new TpmAttestationStatementVerifier()); |
| 428 | default: | |
| 429 | return Optional.empty(); | |
| 430 | } | |
| 431 | } | |
| 432 | } | |
| 433 | ||
| 434 | @Value | |
| 435 | class Step19 implements Step<Step20> { | |
| 436 | private final ByteArray clientDataJsonHash; | |
| 437 | private final AttestationObject attestation; | |
| 438 | private final Optional<AttestationStatementVerifier> attestationStatementVerifier; | |
| 439 | ||
| 440 | @Override | |
| 441 | public void validate() { | |
| 442 |
1
1. validate : removed call to java/util/Optional::ifPresent → KILLED |
attestationStatementVerifier.ifPresent( |
| 443 | verifier -> { | |
| 444 |
1
1. lambda$validate$0 : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
| 445 | verifier.verifyAttestationSignature(attestation, clientDataJsonHash), | |
| 446 | "Invalid attestation signature."); | |
| 447 | }); | |
| 448 | ||
| 449 |
2
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → SURVIVED 2. validate : negated conditional → KILLED |
assertTrue(attestationType() != null, "Failed to determine attestation type"); |
| 450 | } | |
| 451 | ||
| 452 | @Override | |
| 453 | public Step20 nextStep() { | |
| 454 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step19::nextStep → KILLED |
return new Step20(attestation, attestationType(), attestationTrustPath()); |
| 455 | } | |
| 456 | ||
| 457 | public AttestationType attestationType() { | |
| 458 | try { | |
| 459 |
1
1. attestationType : negated conditional → KILLED |
if (attestationStatementVerifier.isPresent()) { |
| 460 |
1
1. attestationType : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step19::attestationType → KILLED |
return attestationStatementVerifier.get().getAttestationType(attestation); |
| 461 | } else { | |
| 462 |
1
1. attestationType : negated conditional → SURVIVED |
switch (attestation.getFormat()) { |
| 463 | case "android-key": | |
| 464 | // TODO delete this once android-key attestation verification is implemented | |
| 465 |
1
1. attestationType : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step19::attestationType → KILLED |
return AttestationType.BASIC; |
| 466 | default: | |
| 467 |
1
1. attestationType : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step19::attestationType → KILLED |
return AttestationType.UNKNOWN; |
| 468 | } | |
| 469 | } | |
| 470 | } catch (IOException | CertificateException e) { | |
| 471 | throw new IllegalArgumentException("Failed to resolve attestation type.", e); | |
| 472 | } | |
| 473 | } | |
| 474 | ||
| 475 | public Optional<List<X509Certificate>> attestationTrustPath() { | |
| 476 |
1
1. attestationTrustPath : negated conditional → KILLED |
if (attestationStatementVerifier.isPresent()) { |
| 477 | AttestationStatementVerifier verifier = attestationStatementVerifier.get(); | |
| 478 |
1
1. attestationTrustPath : negated conditional → KILLED |
if (verifier instanceof X5cAttestationStatementVerifier) { |
| 479 | try { | |
| 480 |
1
1. attestationTrustPath : replaced return value with Optional.empty for com/yubico/webauthn/FinishRegistrationSteps$Step19::attestationTrustPath → KILLED |
return ((X5cAttestationStatementVerifier) verifier) |
| 481 | .getAttestationTrustPath(attestation); | |
| 482 | } catch (CertificateException e) { | |
| 483 | throw new IllegalArgumentException("Failed to resolve attestation trust path.", e); | |
| 484 | } | |
| 485 | } else { | |
| 486 | return Optional.empty(); | |
| 487 | } | |
| 488 | } else { | |
| 489 | return Optional.empty(); | |
| 490 | } | |
| 491 | } | |
| 492 | } | |
| 493 | ||
| 494 | @Value | |
| 495 | class Step20 implements Step<Step21> { | |
| 496 | private final AttestationObject attestation; | |
| 497 | private final AttestationType attestationType; | |
| 498 | private final Optional<List<X509Certificate>> attestationTrustPath; | |
| 499 | ||
| 500 | private final Optional<AttestationTrustSource.TrustRootsResult> trustRoots; | |
| 501 | ||
| 502 | public Step20( | |
| 503 | AttestationObject attestation, | |
| 504 | AttestationType attestationType, | |
| 505 | Optional<List<X509Certificate>> attestationTrustPath) { | |
| 506 | this.attestation = attestation; | |
| 507 | this.attestationType = attestationType; | |
| 508 | this.attestationTrustPath = attestationTrustPath; | |
| 509 | this.trustRoots = findTrustRoots(); | |
| 510 | } | |
| 511 | ||
| 512 | @Override | |
| 513 | public void validate() {} | |
| 514 | ||
| 515 | @Override | |
| 516 | public Step21 nextStep() { | |
| 517 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step20::nextStep → KILLED |
return new Step21(attestation, attestationType, attestationTrustPath, trustRoots); |
| 518 | } | |
| 519 | ||
| 520 | private Optional<AttestationTrustSource.TrustRootsResult> findTrustRoots() { | |
| 521 |
1
1. findTrustRoots : replaced return value with Optional.empty for com/yubico/webauthn/FinishRegistrationSteps$Step20::findTrustRoots → KILLED |
return attestationTrustSource.flatMap( |
| 522 | attestationTrustSource -> | |
| 523 |
1
1. lambda$findTrustRoots$3 : replaced return value with Optional.empty for com/yubico/webauthn/FinishRegistrationSteps$Step20::lambda$findTrustRoots$3 → KILLED |
attestationTrustPath.map( |
| 524 | atp -> | |
| 525 |
1
1. lambda$findTrustRoots$2 : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step20::lambda$findTrustRoots$2 → KILLED |
attestationTrustSource.findTrustRoots( |
| 526 | atp, | |
| 527 | OptionalUtil.orElseOptional( | |
| 528 | Optional.of( | |
| 529 | attestation | |
| 530 | .getAuthenticatorData() | |
| 531 | .getAttestedCredentialData() | |
| 532 | .get() | |
| 533 | .getAaguid()) | |
| 534 |
2
1. lambda$findTrustRoots$0 : negated conditional → KILLED 2. lambda$findTrustRoots$0 : replaced boolean return with true for com/yubico/webauthn/FinishRegistrationSteps$Step20::lambda$findTrustRoots$0 → KILLED |
.filter(aaguid -> !aaguid.equals(ZERO_AAGUID)), |
| 535 | () -> { | |
| 536 |
1
1. lambda$findTrustRoots$1 : negated conditional → KILLED |
if (!atp.isEmpty()) { |
| 537 |
1
1. lambda$findTrustRoots$1 : replaced return value with Optional.empty for com/yubico/webauthn/FinishRegistrationSteps$Step20::lambda$findTrustRoots$1 → KILLED |
return CertificateParser.parseFidoAaguidExtension(atp.get(0)) |
| 538 | .map(ByteArray::new); | |
| 539 | } else { | |
| 540 | return Optional.empty(); | |
| 541 | } | |
| 542 | })))); | |
| 543 | } | |
| 544 | } | |
| 545 | ||
| 546 | @Value | |
| 547 | class Step21 implements Step<Step22> { | |
| 548 | private final AttestationObject attestation; | |
| 549 | private final AttestationType attestationType; | |
| 550 | private final Optional<List<X509Certificate>> attestationTrustPath; | |
| 551 | private final Optional<AttestationTrustSource.TrustRootsResult> trustRoots; | |
| 552 | ||
| 553 | private final boolean attestationTrusted; | |
| 554 | ||
| 555 | public Step21( | |
| 556 | AttestationObject attestation, | |
| 557 | AttestationType attestationType, | |
| 558 | Optional<List<X509Certificate>> attestationTrustPath, | |
| 559 | Optional<AttestationTrustSource.TrustRootsResult> trustRoots) { | |
| 560 | this.attestation = attestation; | |
| 561 | this.attestationType = attestationType; | |
| 562 | this.attestationTrustPath = attestationTrustPath; | |
| 563 | this.trustRoots = trustRoots; | |
| 564 | ||
| 565 | this.attestationTrusted = attestationTrusted(); | |
| 566 | } | |
| 567 | ||
| 568 | @Override | |
| 569 | public void validate() { | |
| 570 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
| 571 |
2
1. validate : negated conditional → KILLED 2. validate : negated conditional → KILLED |
allowUntrustedAttestation || attestationTrusted, |
| 572 | "Failed to derive trust for attestation key."); | |
| 573 | } | |
| 574 | ||
| 575 | @Override | |
| 576 | public Step22 nextStep() { | |
| 577 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step21::nextStep → KILLED |
return new Step22(attestationType, attestationTrusted, attestationTrustPath); |
| 578 | } | |
| 579 | ||
| 580 | public boolean attestationTrusted() { | |
| 581 |
2
1. attestationTrusted : negated conditional → KILLED 2. attestationTrusted : negated conditional → KILLED |
if (attestationTrustPath.isPresent() && attestationTrustSource.isPresent()) { |
| 582 | try { | |
| 583 |
2
1. attestationTrusted : negated conditional → KILLED 2. attestationTrusted : negated conditional → KILLED |
if (!trustRoots.isPresent() || trustRoots.get().getTrustRoots().isEmpty()) { |
| 584 |
1
1. attestationTrusted : replaced boolean return with true for com/yubico/webauthn/FinishRegistrationSteps$Step21::attestationTrusted → KILLED |
return false; |
| 585 | ||
| 586 | } else { | |
| 587 | final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); | |
| 588 | final CertPathValidator cpv = CertPathValidator.getInstance("PKIX"); | |
| 589 | final CertPath certPath = certFactory.generateCertPath(attestationTrustPath.get()); | |
| 590 | final PKIXParameters pathParams = | |
| 591 | new PKIXParameters( | |
| 592 | trustRoots.get().getTrustRoots().stream() | |
| 593 |
1
1. lambda$attestationTrusted$0 : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step21::lambda$attestationTrusted$0 → KILLED |
.map(rootCert -> new TrustAnchor(rootCert, null)) |
| 594 | .collect(Collectors.toSet())); | |
| 595 |
1
1. attestationTrusted : removed call to java/security/cert/PKIXParameters::setDate → KILLED |
pathParams.setDate(Date.from(clock.instant())); |
| 596 |
1
1. attestationTrusted : removed call to java/security/cert/PKIXParameters::setRevocationEnabled → KILLED |
pathParams.setRevocationEnabled(trustRoots.get().isEnableRevocationChecking()); |
| 597 |
1
1. attestationTrusted : removed call to java/security/cert/PKIXParameters::setPolicyQualifiersRejected → SURVIVED |
pathParams.setPolicyQualifiersRejected( |
| 598 |
1
1. attestationTrusted : negated conditional → SURVIVED |
!trustRoots.get().getPolicyTreeValidator().isPresent()); |
| 599 |
1
1. attestationTrusted : removed call to java/util/Optional::ifPresent → KILLED |
trustRoots.get().getCertStore().ifPresent(pathParams::addCertStore); |
| 600 | final PKIXCertPathValidatorResult result = | |
| 601 | (PKIXCertPathValidatorResult) cpv.validate(certPath, pathParams); | |
| 602 |
2
1. attestationTrusted : replaced boolean return with true for com/yubico/webauthn/FinishRegistrationSteps$Step21::attestationTrusted → KILLED 2. attestationTrusted : replaced boolean return with false for com/yubico/webauthn/FinishRegistrationSteps$Step21::attestationTrusted → KILLED |
return trustRoots |
| 603 | .get() | |
| 604 | .getPolicyTreeValidator() | |
| 605 | .map( | |
| 606 | policyNodePredicate -> { | |
| 607 |
1
1. lambda$attestationTrusted$1 : negated conditional → KILLED |
if (policyNodePredicate.test(result.getPolicyTree())) { |
| 608 |
1
1. lambda$attestationTrusted$1 : replaced Boolean return with False for com/yubico/webauthn/FinishRegistrationSteps$Step21::lambda$attestationTrusted$1 → KILLED |
return true; |
| 609 | } else { | |
| 610 | log.info( | |
| 611 | "Failed to derive trust in attestation statement: Certificate path policy tree does not satisfy policy tree validator. Attestation object: {}", | |
| 612 | response.getResponse().getAttestationObject()); | |
| 613 |
1
1. lambda$attestationTrusted$1 : replaced Boolean return with True for com/yubico/webauthn/FinishRegistrationSteps$Step21::lambda$attestationTrusted$1 → KILLED |
return false; |
| 614 | } | |
| 615 | }) | |
| 616 | .orElse(true); | |
| 617 | } | |
| 618 | ||
| 619 | } catch (CertPathValidatorException e) { | |
| 620 | log.info( | |
| 621 | "Failed to derive trust in attestation statement: {} at cert index {}: {}. Attestation object: {}", | |
| 622 | e.getReason(), | |
| 623 | e.getIndex(), | |
| 624 | e.getMessage(), | |
| 625 | response.getResponse().getAttestationObject()); | |
| 626 |
1
1. attestationTrusted : negated conditional → SURVIVED |
if (PKIXReason.INVALID_POLICY.equals(e.getReason())) { |
| 627 | log.info( | |
| 628 | "You may need to set the policyTreeValidator property on the {} returned by your {}.", | |
| 629 | TrustRootsResult.class.getSimpleName(), | |
| 630 | AttestationTrustSource.class.getSimpleName()); | |
| 631 | } | |
| 632 |
1
1. attestationTrusted : replaced boolean return with true for com/yubico/webauthn/FinishRegistrationSteps$Step21::attestationTrusted → SURVIVED |
return false; |
| 633 | ||
| 634 | } catch (CertificateException e) { | |
| 635 | log.warn( | |
| 636 | "Failed to build attestation certificate path. Attestation object: {}", | |
| 637 | response.getResponse().getAttestationObject(), | |
| 638 | e); | |
| 639 |
1
1. attestationTrusted : replaced boolean return with true for com/yubico/webauthn/FinishRegistrationSteps$Step21::attestationTrusted → NO_COVERAGE |
return false; |
| 640 | ||
| 641 | } catch (NoSuchAlgorithmException e) { | |
| 642 | throw new RuntimeException( | |
| 643 | "Failed to check attestation trust path. A JCA provider is likely missing in the runtime environment.", | |
| 644 | e); | |
| 645 | ||
| 646 | } catch (InvalidAlgorithmParameterException e) { | |
| 647 | throw new RuntimeException( | |
| 648 | "Failed to initialize attestation trust path validator. This is likely a bug, please file a bug report.", | |
| 649 | e); | |
| 650 | } | |
| 651 | } else { | |
| 652 |
1
1. attestationTrusted : replaced boolean return with true for com/yubico/webauthn/FinishRegistrationSteps$Step21::attestationTrusted → KILLED |
return false; |
| 653 | } | |
| 654 | } | |
| 655 | } | |
| 656 | ||
| 657 | @Value | |
| 658 | class Step22 implements Step<Finished> { | |
| 659 | private final AttestationType attestationType; | |
| 660 | private final boolean attestationTrusted; | |
| 661 | private final Optional<List<X509Certificate>> attestationTrustPath; | |
| 662 | ||
| 663 | @Override | |
| 664 | public void validate() { | |
| 665 |
1
1. validate : removed call to com/yubico/internal/util/ExceptionUtil::assertTrue → KILLED |
assertTrue( |
| 666 |
1
1. validate : negated conditional → KILLED |
!credentialRepositoryV2.credentialIdExists(response.getId()), |
| 667 | "Credential ID is already registered: %s", | |
| 668 | response.getId()); | |
| 669 | } | |
| 670 | ||
| 671 | @Override | |
| 672 | public Finished nextStep() { | |
| 673 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Step22::nextStep → KILLED |
return new Finished(attestationType, attestationTrusted, attestationTrustPath); |
| 674 | } | |
| 675 | } | |
| 676 | ||
| 677 | // Step 23 will be performed externally by library user | |
| 678 | // Nothing to do for step 24 | |
| 679 | ||
| 680 | @Value | |
| 681 | class Finished implements Step<Finished> { | |
| 682 | private final AttestationType attestationType; | |
| 683 | private final boolean attestationTrusted; | |
| 684 | private final Optional<List<X509Certificate>> attestationTrustPath; | |
| 685 | ||
| 686 | @Override | |
| 687 | public void validate() { | |
| 688 | /* No-op */ | |
| 689 | } | |
| 690 | ||
| 691 | @Override | |
| 692 | public Finished nextStep() { | |
| 693 |
1
1. nextStep : replaced return value with null for com/yubico/webauthn/FinishRegistrationSteps$Finished::nextStep → NO_COVERAGE |
return this; |
| 694 | } | |
| 695 | ||
| 696 | @Override | |
| 697 | public Optional<RegistrationResult> result() { | |
| 698 |
1
1. result : replaced return value with Optional.empty for com/yubico/webauthn/FinishRegistrationSteps$Finished::result → KILLED |
return Optional.of( |
| 699 | new RegistrationResult( | |
| 700 | response, attestationTrusted, attestationType, attestationTrustPath)); | |
| 701 | } | |
| 702 | } | |
| 703 | } | |
Mutations | ||
| 95 |
1.1 |
|
| 127 |
1.1 |
|
| 131 |
1.1 |
|
| 144 |
1.1 |
|
| 145 |
1.1 |
|
| 149 |
1.1 |
|
| 150 |
1.1 |
|
| 152 |
1.1 |
|
| 165 |
1.1 2.2 |
|
| 170 |
1.1 |
|
| 174 |
1.1 |
|
| 184 |
1.1 |
|
| 193 |
1.1 |
|
| 203 |
1.1 |
|
| 208 |
1.1 |
|
| 219 |
1.1 |
|
| 227 |
1.1 |
|
| 242 |
1.1 |
|
| 250 |
1.1 2.2 |
|
| 255 |
1.1 |
|
| 259 |
1.1 |
|
| 269 |
1.1 2.2 |
|
| 274 |
1.1 |
|
| 278 |
1.1 |
|
| 289 |
1.1 |
|
| 297 |
1.1 |
|
| 308 |
1.1 |
|
| 309 |
1.1 2.2 |
|
| 315 |
1.1 |
|
| 329 |
1.1 |
|
| 331 |
1.1 |
|
| 339 |
1.1 |
|
| 360 |
1.1 |
|
| 362 |
1.1 2.2 |
|
| 377 |
1.1 |
|
| 388 |
1.1 |
|
| 393 |
1.1 |
|
| 407 |
1.1 |
|
| 411 |
1.1 |
|
| 417 |
1.1 |
|
| 419 |
1.1 |
|
| 421 |
1.1 |
|
| 423 |
1.1 |
|
| 425 |
1.1 |
|
| 427 |
1.1 |
|
| 442 |
1.1 |
|
| 444 |
1.1 |
|
| 449 |
1.1 2.2 |
|
| 454 |
1.1 |
|
| 459 |
1.1 |
|
| 460 |
1.1 |
|
| 462 |
1.1 |
|
| 465 |
1.1 |
|
| 467 |
1.1 |
|
| 476 |
1.1 |
|
| 478 |
1.1 |
|
| 480 |
1.1 |
|
| 517 |
1.1 |
|
| 521 |
1.1 |
|
| 523 |
1.1 |
|
| 525 |
1.1 |
|
| 534 |
1.1 2.2 |
|
| 536 |
1.1 |
|
| 537 |
1.1 |
|
| 570 |
1.1 |
|
| 571 |
1.1 2.2 |
|
| 577 |
1.1 |
|
| 581 |
1.1 2.2 |
|
| 583 |
1.1 2.2 |
|
| 584 |
1.1 |
|
| 593 |
1.1 |
|
| 595 |
1.1 |
|
| 596 |
1.1 |
|
| 597 |
1.1 |
|
| 598 |
1.1 |
|
| 599 |
1.1 |
|
| 602 |
1.1 2.2 |
|
| 607 |
1.1 |
|
| 608 |
1.1 |
|
| 613 |
1.1 |
|
| 626 |
1.1 |
|
| 632 |
1.1 |
|
| 639 |
1.1 |
|
| 652 |
1.1 |
|
| 665 |
1.1 |
|
| 666 |
1.1 |
|
| 673 |
1.1 |
|
| 693 |
1.1 |
|
| 698 |
1.1 |