Skip to content

Commit

Permalink
Release 2.3.0
Browse files Browse the repository at this point in the history
New features:

- (Experimental) Added `authenticatorAttachment` property to response
  objects:
  - NOTE: Experimental features may receive breaking changes without a
    major version increase.
  - Added method `getAuthenticatorAttachment()` to
    `PublicKeyCredential` and corresponding builder method
    `authenticatorAttachment(AuthenticatorAttachment)`.
  - Added method `getAuthenticatorAttachment()` to
    `RegistrationResult` and `AssertionResult`, which echo
    `getAuthenticatorAttachment()` from the corresponding
    `PublicKeyCredential`.
  - Thanks to GitHub user luisgoncalves for the contribution, see
    #250

Other:

- Fixed the README description of SemVer exceptions: `@Deprecated`
  features are still part of the public API unless they also have an
  `EXPERIMENTAL:` tag in JavaDoc.
- Brought `com.yubico.webauthn` package JavaDoc up to date with new
  library features.
  • Loading branch information
emlun committed Jan 11, 2023
2 parents ab1fac4 + 640c5a4 commit 424520f
Show file tree
Hide file tree
Showing 15 changed files with 382 additions and 184 deletions.
27 changes: 26 additions & 1 deletion NEWS
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
== Version 2.3.0 ==

New features:

* (Experimental) Added `authenticatorAttachment` property to response objects:
** NOTE: Experimental features may receive breaking changes without a major
version increase.
** Added method `getAuthenticatorAttachment()` to `PublicKeyCredential` and
corresponding builder method
`authenticatorAttachment(AuthenticatorAttachment)`.
** Added method `getAuthenticatorAttachment()` to `RegistrationResult` and
`AssertionResult`, which echo `getAuthenticatorAttachment()` from the
corresponding `PublicKeyCredential`.
** Thanks to GitHub user luisgoncalves for the contribution, see
https://github.com/Yubico/java-webauthn-server/pull/250

Other:

* Fixed the README description of SemVer exceptions: `@Deprecated` features are
still part of the public API unless they also have an `EXPERIMENTAL:` tag in
JavaDoc.
* Brought `com.yubico.webauthn` package JavaDoc up to date with new library
features.


== Version 2.2.0 ==

`webauthn-server-core`:
Expand Down Expand Up @@ -44,7 +69,7 @@ Fixes:

Fixes:

* Improved documentation of guaranteed provided by `FidoMetadataDownloader` and
* Improved documentation of guarantees provided by `FidoMetadataDownloader` and
required of its parameters.


Expand Down
150 changes: 76 additions & 74 deletions README

Large diffs are not rendered by default.

15 changes: 14 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ buildscript {
}
dependencies {
classpath 'com.cinnober.gradle:semver-git:2.5.0'
classpath 'com.diffplug.spotless:spotless-plugin-gradle:6.11.0'
classpath 'com.diffplug.spotless:spotless-plugin-gradle:6.12.1'
classpath 'io.github.cosmicsilence:gradle-scalafix:0.1.13'
}
}
Expand Down Expand Up @@ -257,6 +257,19 @@ subprojects { project ->

}

// Configure cross-links from webauthn-server-attestation JavaDoc to core JavaDoc
project(':webauthn-server-attestation').tasks.javadoc {
var coreProj = project(':webauthn-server-core')
var coreJavadoc = coreProj.tasks.javadoc
inputs.files coreJavadoc.outputs.files

// These links won't work locally, but they will work on developers.yubico.com
options.linksOffline("../../webauthn-server-core/${coreProj.version}", "${coreJavadoc.destinationDir}")

// Use this instead for local testing
//options.linksOffline("file://${coreJavadoc.destinationDir}", "${coreJavadoc.destinationDir}")
}

// The root project has no sources, but the dependency platform also needs to be published as an artifact
// See https://docs.gradle.org/current/userguide/java_platform_plugin.html
// See https://github.com/Yubico/java-webauthn-server/issues/93#issuecomment-822806951
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ repositories {
}

dependencies {
implementation("info.solidsoft.gradle.pitest:gradle-pitest-plugin:1.9.0")
implementation("info.solidsoft.gradle.pitest:gradle-pitest-plugin:1.9.11")
}
101 changes: 51 additions & 50 deletions webauthn-server-attestation/README.adoc

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
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.ByteArray;
import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
Expand Down Expand Up @@ -195,6 +196,20 @@ 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.yubico.webauthn.RelyingParty.RelyingPartyBuilder;
import com.yubico.webauthn.attestation.AttestationTrustSource;
import com.yubico.webauthn.data.AttestationType;
import com.yubico.webauthn.data.AuthenticatorAttachment;
import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
import com.yubico.webauthn.data.AuthenticatorRegistrationExtensionOutputs;
import com.yubico.webauthn.data.ByteArray;
Expand Down Expand Up @@ -175,6 +176,20 @@ public boolean isBackedUp() {
return credential.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 credential was created.
*
* @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 credential.getAuthenticatorAttachment();
}

/**
* The signature count returned with the created credential.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.Optional;
import java.util.stream.Stream;
import lombok.AllArgsConstructor;
import lombok.Getter;
Expand Down Expand Up @@ -73,18 +72,8 @@ public enum AuthenticatorAttachment {

@JsonValue @Getter @NonNull private final String value;

private static Optional<AuthenticatorAttachment> fromString(@NonNull String value) {
return Stream.of(values()).filter(v -> v.value.equals(value)).findAny();
}

@JsonCreator
private static AuthenticatorAttachment fromJsonString(@NonNull String value) {
return fromString(value)
.orElseThrow(
() ->
new IllegalArgumentException(
String.format(
"Unknown %s value: %s",
AuthenticatorAttachment.class.getSimpleName(), value)));
return Stream.of(values()).filter(v -> v.value.equals(value)).findAny().orElse(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,16 @@
package com.yubico.webauthn.data;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import com.yubico.internal.util.JacksonCodecs;
import com.yubico.webauthn.AssertionResult;
import com.yubico.webauthn.FinishAssertionOptions;
import com.yubico.webauthn.FinishRegistrationOptions;
import com.yubico.webauthn.RegistrationResult;
import com.yubico.webauthn.RelyingParty;
import java.io.IOException;
import java.util.Optional;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NonNull;
Expand All @@ -46,7 +51,6 @@
*/
@Value
@Builder(toBuilder = true)
@JsonIgnoreProperties({"authenticatorAttachment"})
public class PublicKeyCredential<
A extends AuthenticatorResponse, B extends ClientExtensionOutputs> {

Expand All @@ -68,6 +72,8 @@ public class PublicKeyCredential<
*/
@NonNull private final A response;

private final AuthenticatorAttachment authenticatorAttachment;

/**
* A map containing extension identifier → client extension output entries produced by the
* extension’s client extension processing.
Expand All @@ -83,6 +89,7 @@ private PublicKeyCredential(
@JsonProperty("id") ByteArray id,
@JsonProperty("rawId") ByteArray rawId,
@NonNull @JsonProperty("response") A response,
@JsonProperty("authenticatorAttachment") AuthenticatorAttachment authenticatorAttachment,
@NonNull @JsonProperty("clientExtensionResults") B clientExtensionResults,
@NonNull @JsonProperty("type") PublicKeyCredentialType type) {
if (id == null && rawId == null) {
Expand All @@ -95,16 +102,41 @@ private PublicKeyCredential(

this.id = id == null ? rawId : id;
this.response = response;
this.authenticatorAttachment = authenticatorAttachment;
this.clientExtensionResults = clientExtensionResults;
this.type = type;
}

private PublicKeyCredential(
ByteArray id,
@NonNull A response,
AuthenticatorAttachment authenticatorAttachment,
@NonNull B clientExtensionResults,
@NonNull PublicKeyCredentialType type) {
this(id, null, response, clientExtensionResults, type);
this(id, null, response, authenticatorAttachment, clientExtensionResults, type);
}

/**
* The <a href="https://w3c.github.io/webauthn/#authenticator-attachment-modality">authenticator
* attachment modality</a> in effect at the time the credential was created or used.
*
* <p>If parsed from JSON, this will be present if and only if the input was a valid value of
* {@link AuthenticatorAttachment}.
*
* <p>The same value will also be available via {@link
* RegistrationResult#getAuthenticatorAttachment()} or {@link
* AssertionResult#getAuthenticatorAttachment()} on the result from {@link
* RelyingParty#finishRegistration(FinishRegistrationOptions)} or {@link
* RelyingParty#finishAssertion(FinishAssertionOptions)}.
*
* @see RegistrationResult#getAuthenticatorAttachment()
* @see AssertionResult#getAuthenticatorAttachment()
* @deprecated EXPERIMENTAL: This feature is from a not yet mature standard; it could change as
* the standard matures.
*/
@Deprecated
public Optional<AuthenticatorAttachment> getAuthenticatorAttachment() {
return Optional.ofNullable(authenticatorAttachment);
}

public static <A extends AuthenticatorResponse, B extends ClientExtensionOutputs>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* This package makes up the public API of the webauthn-server-core library.
* This package and its subpackages make up the public API of the webauthn-server-core library.
*
* <p>The main entry point is the {@link com.yubico.webauthn.RelyingParty} class. It provides
* methods for generating inputs to the <code>navigator.credentials.create()</code> and <code>
Expand Down Expand Up @@ -82,15 +82,22 @@
* com.yubico.webauthn.data.PublicKeyCredentialCreationOptions} which can be serialized to JSON and
* passed as the <code>publicKey</code> argument to <code>navigator.credentials.create()</code>. You
* can use the {@link com.yubico.webauthn.data.PublicKeyCredentialCreationOptions#toBuilder()
* toBuilder()} method to make any modifications you need. You should store this in temporary
* storage so that it can later be passed as an argument to {@link
* com.yubico.webauthn.RelyingParty#finishRegistration(FinishRegistrationOptions)}.
*
* <p>After receiving the response from the client, construct a {@link
* com.yubico.webauthn.data.PublicKeyCredential}&lt;{@link
* com.yubico.webauthn.data.AuthenticatorAttestationResponse}, {@link
* com.yubico.webauthn.data.ClientRegistrationExtensionOutputs}&gt; from the response and wrap that
* in a {@link com.yubico.webauthn.FinishRegistrationOptions} along with the {@link
* toBuilder()} method to make any modifications you need, then the {@link
* com.yubico.webauthn.data.PublicKeyCredentialCreationOptions#toCredentialsCreateJson()} method is
* suitable for converting the value to JSON to send to the client.
*
* <p>You should also store the {@link com.yubico.webauthn.data.PublicKeyCredentialCreationOptions}
* object in temporary storage so that it can later be passed as an argument to {@link
* com.yubico.webauthn.RelyingParty#finishRegistration(FinishRegistrationOptions)}. If you need to
* serialize the object for storage, the {@link
* com.yubico.webauthn.data.PublicKeyCredentialCreationOptions#toJson()} and {@link
* com.yubico.webauthn.data.PublicKeyCredentialCreationOptions#fromJson(java.lang.String)} methods
* are suitable for serializing to and from a string value.
*
* <p>After receiving the response from the client, use the {@link
* com.yubico.webauthn.data.PublicKeyCredential#parseRegistrationResponseJson(java.lang.String)}
* function to parse the response and wrap it in a {@link
* com.yubico.webauthn.FinishRegistrationOptions} along with the {@link
* com.yubico.webauthn.data.PublicKeyCredentialCreationOptions} used to initiate the request. Pass
* that as the argument to {@link
* com.yubico.webauthn.RelyingParty#finishRegistration(FinishRegistrationOptions)}, which will
Expand All @@ -107,14 +114,19 @@
* com.yubico.webauthn.RegistrationResult#getPublicKeyCose() publicKeyCose} as a new
* credential for the user. The {@link com.yubico.webauthn.CredentialRepository} will need to
* look these up for authentication.
* <li>Store the {@link com.yubico.webauthn.RegistrationResult#getSignatureCount() signature
* counter} value in the new credential. If available, this will be used in future
* authentication ceremonies do detect authenticator cloning.
* <li>Optionally, store the {@link com.yubico.webauthn.RegistrationResult#isDiscoverable()
* isDiscoverable} flag, if present, in the new credential. This may help you determine which
* user interaction flows are possible with which credential.
* <li>If you care about authenticator attestation, check that the {@link
* com.yubico.webauthn.RegistrationResult#isAttestationTrusted() attestationTrusted} field
* satisfies your attestation policy. For this you will likely need to configure the {@link
* com.yubico.webauthn.RelyingParty.RelyingPartyBuilder#attestationTrustSource(com.yubico.webauthn.attestation.AttestationTrustSource)
* attestationTrustSource} setting on your {@link com.yubico.webauthn.RelyingParty} instance.
* You may also want to consult some external data source to verify the authenticity of the
* {@link com.yubico.webauthn.data.AuthenticatorAttestationResponse#getAttestationObject()
* attestation object}.
* See also the <code>webauthn-server-attestation</code> for an implementation of such an
* attestation trust and metadata source.
* <li>If you care about authenticator attestation, it is recommended to also store the raw {@link
* com.yubico.webauthn.data.AuthenticatorAttestationResponse#getAttestationObject()
* attestation object} as part of the credential. This enables you to retroactively inspect
Expand All @@ -130,11 +142,13 @@
* com.yubico.webauthn.RelyingParty#startAssertion(StartAssertionOptions)}. The main parameter you
* need to set here is the {@link
* com.yubico.webauthn.StartAssertionOptions.StartAssertionOptionsBuilder#username(java.util.Optional)
* username} of the user to authenticate, but even this parameter is optional. If the username is
* not set, then the {@link
* username} or {@link
* com.yubico.webauthn.StartAssertionOptions.StartAssertionOptionsBuilder#userHandle(java.util.Optional)
* user handle} of the user to authenticate, but even these parameters are optional. If neither is
* set, then the {@link
* com.yubico.webauthn.data.PublicKeyCredentialRequestOptions#getAllowCredentials()
* allowCredentials} parameter will not be set. This which means the user must use a <a
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">client-side-resident
* allowCredentials} parameter will not be set. This means the user must use a <a
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">client-side-discoverable
* credential</a> to authenticate; also known as "first-factor authentication". This use case has
* both advantages and disadvantages; see the Web Authentication specification for an extended
* discussion of this.
Expand All @@ -143,18 +157,21 @@
* startAssertion} method returns an {@link com.yubico.webauthn.AssertionRequest} containing the
* username, if any, and a {@link com.yubico.webauthn.data.PublicKeyCredentialRequestOptions}
* instance which can be serialized to JSON and passed as the <code>publicKey</code> argument to
* <code>navigator.credentials.get()</code>. Again, store the {@link
* com.yubico.webauthn.AssertionRequest} in temporary storage so it can be passed as an argument to
* {@link
* <code>navigator.credentials.get()</code>. Again, use {@link
* com.yubico.webauthn.AssertionRequest#toBuilder()} to make any necessary changes, {@link
* com.yubico.webauthn.AssertionRequest#toCredentialsGetJson()} to convert it to JSON for sending to
* the client, and store the {@link com.yubico.webauthn.AssertionRequest} in temporary storage so it
* can be passed as an argument to {@link
* com.yubico.webauthn.RelyingParty#finishAssertion(com.yubico.webauthn.FinishAssertionOptions)}.
*
* <p>After receiving the response from the client, construct a {@link
* com.yubico.webauthn.data.PublicKeyCredential}&lt;{@link
* com.yubico.webauthn.data.AuthenticatorAssertionResponse}, {@link
* com.yubico.webauthn.data.ClientAssertionExtensionOutputs}&gt; from the response and wrap that in
* a {@link com.yubico.webauthn.FinishAssertionOptions} along with the {@link
* com.yubico.webauthn.AssertionRequest} used to initiate the request. Pass that as the argument to
* {@link
* Again, {@link com.yubico.webauthn.AssertionRequest#toJson()} and {@link
* com.yubico.webauthn.AssertionRequest#fromJson(java.lang.String)} can be used to convert to and
* from JSON for storage.
*
* <p>After receiving the response from the client, use {@link
* com.yubico.webauthn.data.PublicKeyCredential#parseAssertionResponseJson(java.lang.String)} to
* parse the response, then wrap that in a {@link com.yubico.webauthn.FinishAssertionOptions} along
* with the {@link com.yubico.webauthn.AssertionRequest} used to initiate the request. Pass that as
* the argument to {@link
* com.yubico.webauthn.RelyingParty#finishAssertion(com.yubico.webauthn.FinishAssertionOptions)},
* which will return an {@link com.yubico.webauthn.AssertionResult} if successful and throw an
* exception if not. Regardless of whether it succeeds, you should remove the {@link
Expand Down
Loading

1 comment on commit 424520f

@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 80 % 🔹 1218 🔺 / 1514 🔹 80 % 1216 / 1514
com.yubico.fido.metadata 67 % 🟢 214 🔺 / 316 🔹 67 % 212 / 316
com.yubico.internal.util 37 % 🔹 36 🔹 / 97 🔹 37 % 36 / 97
com.yubico.webauthn 87 % 🔹 542 🔹 / 622 🔹 87 % 542 / 622
com.yubico.webauthn.attestation 92 % 🔹 13 🔹 / 14 🔹 92 % 13 / 14
com.yubico.webauthn.data 92 % 🔹 388 🔹 / 418 🔹 92 % 388 / 418
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: 9e47c43

Detailed reports: workflow run #193

Please sign in to comment.