Skip to content

Commit

Permalink
Merge branch 'experimental/credential-repository-v2'
Browse files Browse the repository at this point in the history
  • Loading branch information
emlun committed Nov 9, 2023
2 parents d386583 + 85de45b commit ec528be
Show file tree
Hide file tree
Showing 31 changed files with 11,691 additions and 928 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release-verify-signatures.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:

strategy:
matrix:
java: ["17.0.7"]
java: ["17.0.9"]
distribution: [temurin, zulu, microsoft]

steps:
Expand Down
35 changes: 35 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,45 @@ New features:
** `ResidentKeyRequirement.fromValue(String): Optional<ResidentKeyRequirement>`
** `TokenBindingStatus.fromValue(String): Optional<TokenBindingStatus>`
** `UserVerificationRequirement.fromValue(String): Optional<UserVerificationRequirement>`
* Added public builder to `CredentialPropertiesOutput`.
* Added public factory function
`LargeBlobRegistrationOutput.supported(boolean)`.
* Added public factory functions to `LargeBlobAuthenticationOutput`.
* (Experimental) Added option `isSecurePaymentConfirmation(boolean)` to
`FinishAssertionOptions`. When set, `RelyingParty.finishAssertion()` will
adapt the validation logic for a Secure Payment Confirmation (SPC) response
instead of an ordinary WebAuthn response. See the JavaDoc for details.
** NOTE: Experimental features may receive breaking changes without a major
version increase.
* (Experimental) Added a new suite of interfaces, starting with
`CredentialRepositoryV2`. `RelyingParty` can now be configured with a
`CredentialRepositoryV2` instance instead of a `CredentialRepository`
instance. This changes the result of the `RelyingParty` builder to
`RelyingPartyV2`. `CredentialRepositoryV2` and `RelyingPartyV2` enable a suite
of new features:
** `CredentialRepositoryV2` does not assume that the application has usernames,
instead username support is modular. In addition to the
`CredentialRepositoryV2`, `RelyingPartyV2` can be optionally configured with
a `UsernameRepository` as well. If a `UsernameRepository` is not set, then
`RelyingPartyV2.startAssertion(StartAssertionOptions)` will fail at runtime
if `StartAssertionOptions.username` is set.
** `CredentialRepositoryV2` uses a new interface `CredentialRecord` to
represent registered credentials, instead of the concrete
`RegisteredCredential` class (although `RegisteredCredential` also
implements `CredentialRecord`). This provides implementations greater
flexibility while also automating the type conversion to
`PublicKeyCredentialDescriptor` needed in `startRegistration()` and
`startAssertion()`.
** `RelyingPartyV2.finishAssertion()` returns a new type `AssertionResultV2`
with a new method `getCredential()`, which returns the `CredentialRecord`
that was verified. The return type of `getCredential()` is generic and
preserves the concrete type of `CredentialRecord` returned by the
`CredentialRepositoryV2` implementation.
** NOTE: Experimental features may receive breaking changes without a major
version increase.
* (Experimental) Added property `RegisteredCredential.transports`.
** NOTE: Experimental features may receive breaking changes without a major
version increase.


== Version 2.5.0 ==
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
// Copyright (c) 2018, Yubico AB
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package com.yubico.webauthn;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.yubico.webauthn.data.AuthenticatorAssertionExtensionOutputs;
import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
import com.yubico.webauthn.data.AuthenticatorAttachment;
import com.yubico.webauthn.data.AuthenticatorData;
import com.yubico.webauthn.data.AuthenticatorDataFlags;
import com.yubico.webauthn.data.AuthenticatorResponse;
import com.yubico.webauthn.data.ByteArray;
import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
import com.yubico.webauthn.data.PublicKeyCredential;
import java.util.Optional;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.Value;

/**
* The result of a call to {@link RelyingPartyV2#finishAssertion(FinishAssertionOptions)}.
*
* @deprecated EXPERIMENTAL: This is an experimental feature. It is likely to change or be deleted
* before reaching a mature release.
*/
@Deprecated
@Value
public class AssertionResultV2<C extends CredentialRecord> {

/** <code>true</code> if the assertion was verified successfully. */
private final boolean success;

@JsonProperty
@Getter(AccessLevel.NONE)
private final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
credentialResponse;

/**
* The {@link CredentialRecord} that was returned by {@link
* CredentialRepositoryV2#lookup(ByteArray, ByteArray)} and whose public key was used to
* successfully verify the assertion signature.
*
* <p>NOTE: The {@link CredentialRecord#getSignatureCount() signature count}, {@link
* CredentialRecord#isBackupEligible() backup eligibility} and {@link
* CredentialRecord#isBackedUp() backup state} properties in this object will reflect the state
* <i>before</i> the assertion operation, not the new state. When updating your database state,
* use the signature counter and backup state from {@link #getSignatureCount()}, {@link
* #isBackupEligible()} and {@link #isBackedUp()} instead.
*
* @deprecated EXPERIMENTAL: This is an experimental feature. It is likely to change or be deleted
* before reaching a mature release.
*/
@Deprecated private final C credential;

/**
* <code>true</code> if and only if at least one of the following is true:
*
* <ul>
* <li>The {@link AuthenticatorData#getSignatureCounter() signature counter value} in the
* assertion was strictly greater than {@link CredentialRecord#getSignatureCount() the
* stored one}.
* <li>The {@link AuthenticatorData#getSignatureCounter() signature counter value} in the
* assertion and {@link CredentialRecord#getSignatureCount() the stored one} were both zero.
* </ul>
*
* @see <a
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-authenticator-data">§6.1.
* Authenticator Data</a>
* @see AuthenticatorData#getSignatureCounter()
* @see CredentialRecord#getSignatureCount()
* @see RelyingParty.RelyingPartyBuilder#validateSignatureCounter(boolean)
*/
private final boolean signatureCounterValid;

@JsonCreator
AssertionResultV2(
@JsonProperty("success") boolean success,
@NonNull @JsonProperty("credentialResponse")
PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
credentialResponse,
@NonNull @JsonProperty("credential") C credential,
@JsonProperty("signatureCounterValid") boolean signatureCounterValid) {
this.success = success;
this.credentialResponse = credentialResponse;
this.credential = credential;
this.signatureCounterValid = signatureCounterValid;
}

/**
* Check whether the <a href="https://www.w3.org/TR/webauthn/#user-verification">user
* verification</a> as performed during the authentication ceremony.
*
* <p>This flag is also available via <code>
* {@link PublicKeyCredential}.{@link PublicKeyCredential#getResponse() getResponse()}.{@link AuthenticatorResponse#getParsedAuthenticatorData() getParsedAuthenticatorData()}.{@link AuthenticatorData#getFlags() getFlags()}.{@link AuthenticatorDataFlags#UV UV}
* </code>.
*
* @return <code>true</code> if and only if the authenticator claims to have performed user
* verification during the authentication ceremony.
* @see <a href="https://www.w3.org/TR/webauthn/#user-verification">User Verification</a>
* @see <a href="https://w3c.github.io/webauthn/#authdata-flags-uv">UV flag in §6.1. Authenticator
* Data</a>
*/
@JsonIgnore
public boolean isUserVerified() {
return credentialResponse.getResponse().getParsedAuthenticatorData().getFlags().UV;
}

/**
* Check whether the asserted credential is <a
* href="https://w3c.github.io/webauthn/#backup-eligible">backup eligible</a>, using the <a
* href="https://w3c.github.io/webauthn/#authdata-flags-be">BE flag</a> in the authenticator data.
*
* <p>You SHOULD store this value in your representation of the corresponding {@link
* CredentialRecord} if no value is stored yet. {@link CredentialRepository} implementations
* SHOULD set this value when reconstructing that {@link CredentialRecord}.
*
* @return <code>true</code> if and only if the created credential is backup eligible. NOTE that
* this is only a hint and not a guarantee, unless backed by a trusted authenticator
* attestation.
* @see <a href="https://w3c.github.io/webauthn/#backup-eligible">Backup Eligible in §4.
* Terminology</a>
* @see <a href="https://w3c.github.io/webauthn/#authdata-flags-be">BE flag in §6.1. Authenticator
* Data</a>
* @deprecated EXPERIMENTAL: This feature is from a not yet mature standard; it could change as
* the standard matures.
*/
@Deprecated
@JsonIgnore
public boolean isBackupEligible() {
return credentialResponse.getResponse().getParsedAuthenticatorData().getFlags().BE;
}

/**
* Get the current <a href="https://w3c.github.io/webauthn/#backup-state">backup state</a> of the
* asserted credential, using the <a href="https://w3c.github.io/webauthn/#authdata-flags-bs">BS
* flag</a> in the authenticator data.
*
* <p>You SHOULD update this value in your representation of a {@link CredentialRecord}. {@link
* CredentialRepository} implementations SHOULD set this value when reconstructing that {@link
* CredentialRecord}.
*
* @return <code>true</code> if and only if the created credential is believed to currently be
* backed up. NOTE that this is only a hint and not a guarantee, unless backed by a trusted
* authenticator attestation.
* @see <a href="https://w3c.github.io/webauthn/#backup-state">Backup State in §4. Terminology</a>
* @see <a href="https://w3c.github.io/webauthn/#authdata-flags-bs">BS flag in §6.1. Authenticator
* Data</a>
* @deprecated EXPERIMENTAL: This feature is from a not yet mature standard; it could change as
* the standard matures.
*/
@Deprecated
@JsonIgnore
public boolean isBackedUp() {
return credentialResponse.getResponse().getParsedAuthenticatorData().getFlags().BS;
}

/**
* The <a href="https://w3c.github.io/webauthn/#authenticator-attachment-modality">authenticator
* attachment modality</a> in effect at the time the asserted credential was used.
*
* @see PublicKeyCredential#getAuthenticatorAttachment()
* @deprecated EXPERIMENTAL: This feature is from a not yet mature standard; it could change as
* the standard matures.
*/
@Deprecated
@JsonIgnore
public Optional<AuthenticatorAttachment> getAuthenticatorAttachment() {
return credentialResponse.getAuthenticatorAttachment();
}

/**
* The new <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#signcount">signature
* count</a> of the credential used for the assertion.
*
* <p>You should update this value in your database.
*
* @see AuthenticatorData#getSignatureCounter()
*/
@JsonIgnore
public long getSignatureCount() {
return credentialResponse.getResponse().getParsedAuthenticatorData().getSignatureCounter();
}

/**
* The <a
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-extension-output">client
* extension outputs</a>, if any.
*
* <p>This is present if and only if at least one extension output is present in the return value.
*
* @see <a
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-client-extension-processing">§9.4.
* Client Extension Processing</a>
* @see ClientAssertionExtensionOutputs
* @see #getAuthenticatorExtensionOutputs() ()
*/
@JsonIgnore
public Optional<ClientAssertionExtensionOutputs> getClientExtensionOutputs() {
return Optional.of(credentialResponse.getClientExtensionResults())
.filter(ceo -> !ceo.getExtensionIds().isEmpty());
}

/**
* The <a
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#authenticator-extension-output">authenticator
* extension outputs</a>, if any.
*
* <p>This is present if and only if at least one extension output is present in the return value.
*
* @see <a
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-authenticator-extension-processing">§9.5.
* Authenticator Extension Processing</a>
* @see AuthenticatorAssertionExtensionOutputs
* @see #getClientExtensionOutputs()
*/
@JsonIgnore
public Optional<AuthenticatorAssertionExtensionOutputs> getAuthenticatorExtensionOutputs() {
return AuthenticatorAssertionExtensionOutputs.fromAuthenticatorData(
credentialResponse.getResponse().getParsedAuthenticatorData());
}
}
Loading

1 comment on commit ec528be

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mutation test results

Package Coverage Stats Prev Prev
Overall 82 % 🔹 1369 🔺 / 1656 🔹 82 % 1362 / 1656
com.yubico.fido.metadata 70 % 🟢 229 🔺 / 323 🔹 68 % 222 / 323
com.yubico.internal.util 46 % 🔹 57 🔹 / 123 🔹 46 % 57 / 123
com.yubico.webauthn 89 % 🔹 642 🔹 / 720 🔹 89 % 642 / 720
com.yubico.webauthn.attestation 92 % 🔹 13 🔹 / 14 🔹 92 % 13 / 14
com.yubico.webauthn.data 93 % 🔹 403 🔹 / 429 🔹 93 % 403 / 429
com.yubico.webauthn.extension.appid 100 % 🏆 13 🔹 / 13 🔹 100 % 13 / 13
com.yubico.webauthn.extension.uvm 50 % 🔹 12 🔹 / 24 🔹 50 % 12 / 24
com.yubico.webauthn.meta 0 % 🔹 0 🔹 / 10 🔹 0 % 0 / 10

Previous run: 3c74bb5 - Diff

Detailed reports: workflow run #248

Please sign in to comment.