diff --git a/NEWS b/NEWS index 5bc9ea9a3..9c3271281 100644 --- a/NEWS +++ b/NEWS @@ -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`: @@ -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. diff --git a/README b/README index 8b136845d..940d4fc69 100644 --- a/README +++ b/README @@ -28,9 +28,34 @@ If you are, we urge you to upgrade your Java deployment to a version that is saf ========== +*Table of contents* + toc::[] +== Features + +- Generates request objects suitable as parameters to + `navigator.credentials.create()` and `.get()` +- Performs all necessary + https://www.w3.org/TR/webauthn/#sctn-rp-operations[validation logic] on the + response from the client +- No mutable state or side effects - everything (except builders) is thread safe +- Optionally integrates with an "attestation trust source" to verify + https://www.w3.org/TR/webauthn/#sctn-attestation[authenticator attestations] +- Reproducible builds: release signatures match fresh builds from source. See + link:#reproducible-builds[Reproducible builds] below. + + +=== Non-features + +This library has no concept of accounts, sessions, permissions or identity +federation, and it is not an authentication framework; it only deals with +executing the WebAuthn authentication mechanism. Sessions, account management +and other higher level concepts can make use of this authentication mechanism, +but the authentication mechanism alone does not make a security system. + + == Dependency configuration Maven: @@ -39,7 +64,7 @@ Maven: com.yubico webauthn-server-core - 2.2.0 + 2.3.0 compile ---------- @@ -47,7 +72,7 @@ Maven: Gradle: ---------- -compile 'com.yubico:webauthn-server-core:2.2.0' +compile 'com.yubico:webauthn-server-core:2.3.0' ---------- NOTE: You may need additional dependencies with JCA providers to support some signature algorithms. @@ -60,11 +85,11 @@ The library will log warnings if you try to configure it for algorithms with no This library uses link:https://semver.org/[semantic versioning]. The public API consists of all public classes, methods and fields in the `com.yubico.webauthn` package and its subpackages, i.e., everything covered by the -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/package-summary.html[Javadoc], -*with the exception* of things annotated with `@Deprecated`. -In particular, features with a `@deprecated EXPERIMENTAL:` tag in JavaDoc are -considered unstable and may receive breaking changes without a major version -increase. +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/package-summary.html[Javadoc], +*with the exception* of features annotated with a `@Deprecated` annotation and a +`@deprecated EXPERIMENTAL:` tag in JavaDoc. +Such features are considered unstable and may receive breaking changes without a +major version increase. Package-private classes and methods are NOT part of the public API. The `com.yubico:yubico-util` module is NOT part of the public API. @@ -80,61 +105,33 @@ In addition to the main `webauthn-server-core` module, there is also: https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-attestation[attestation statements]. -== Features - -- Generates request objects suitable as parameters to - `navigator.credentials.create()` and `.get()` -- Performs all necessary - https://www.w3.org/TR/webauthn/#sctn-rp-operations[validation logic] on the - response from the client -- No mutable state or side effects - everything (except builders) is thread safe -- Optionally integrates with an "attestation trust source" to verify - https://www.w3.org/TR/webauthn/#sctn-attestation[authenticator attestations] -- Reproducible builds: release signatures match fresh builds from source. See - link:#reproducible-builds[Reproducible builds] below. - - -=== Non-features - -This library has no concept of accounts, sessions, permissions or identity -federation, and it is not an authentication framework; it only deals with -executing the WebAuthn authentication mechanism. Sessions, account management -and other higher level concepts can make use of this authentication mechanism, -but the authentication mechanism alone does not make a security system. - - == Documentation See the -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/package-summary.html[Javadoc] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/package-summary.html[Javadoc] for in-depth API documentation. -== Migrating from version `1.x` - -See link:doc/Migrating_from_v1.adoc[the migration guide]. - - == Getting started Using this library comes in two parts: the server side and the client side. The server side involves: 1. Implement the - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`] interface with your database access logic. 2. Instantiate the - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`] class. 3. Use the - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html#startRegistration(com.yubico.webauthn.StartRegistrationOptions)[`RelyingParty.startRegistration(...)`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html#startRegistration(com.yubico.webauthn.StartRegistrationOptions)[`RelyingParty.startRegistration(...)`] and - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html#finishRegistration(com.yubico.webauthn.FinishRegistrationOptions)[`RelyingParty.fininshRegistration(...)`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html#finishRegistration(com.yubico.webauthn.FinishRegistrationOptions)[`RelyingParty.finishRegistration(...)`] methods to perform registration ceremonies. 4. Use the - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html#startAssertion(com.yubico.webauthn.StartAssertionOptions)[`RelyingParty.startAssertion(...)`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html#startAssertion(com.yubico.webauthn.StartAssertionOptions)[`RelyingParty.startAssertion(...)`] and - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html#finishAssertion(com.yubico.webauthn.FinishAssertionOptions)[`RelyingParty.fininshAssertion(...)`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html#finishAssertion(com.yubico.webauthn.FinishAssertionOptions)[`RelyingParty.finishAssertion(...)`] methods to perform authentication ceremonies. 5. Use the outputs of `finishRegistration` and `finishAssertion` to update your database, initiate sessions, etc. @@ -154,7 +151,7 @@ link:webauthn-server-demo[`webauthn-server-demo`] for a complete demo server. === 1. Implement a `CredentialRepository` The -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`] interface abstracts your database in a database-agnostic way. The concrete implementation will be different for every project, but you can use link:https://github.com/Yubico/java-webauthn-server/blob/main/webauthn-server-demo/src/main/java/demo/webauthn/InMemoryRegistrationStorage.java[`InMemoryRegistrationStorage`] @@ -163,11 +160,11 @@ as a simple example. === 2. Instantiate a `RelyingParty` The -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`] class is the main entry point to the library. You can instantiate it using its builder methods, passing in your -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`] implementation (called `MyCredentialRepository` here) as an argument: [source,java] @@ -189,7 +186,7 @@ RelyingParty rp = RelyingParty.builder() A registration ceremony consists of 5 main steps: 1. Generate registration parameters using - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html#startRegistration(com.yubico.webauthn.StartRegistrationOptions)[`RelyingParty.startRegistration(...)`]. + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html#startRegistration(com.yubico.webauthn.StartRegistrationOptions)[`RelyingParty.startRegistration(...)`]. 2. Send registration parameters to the client and call https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/create[`navigator.credentials.create()`]. 3. With `cred` as the result of the successfully resolved promise, @@ -197,7 +194,7 @@ A registration ceremony consists of 5 main steps: and https://www.w3.org/TR/webauthn-2/#ref-for-dom-authenticatorattestationresponse-gettransports[`cred.response.getTransports()`] and return their results along with `cred` to the server. 4. Validate the response using - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html#finishRegistration(com.yubico.webauthn.FinishRegistrationOptions)[`RelyingParty.fininshRegistration(...)`]. + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html#finishRegistration(com.yubico.webauthn.FinishRegistrationOptions)[`RelyingParty.finishRegistration(...)`]. 5. Update your database using the `finishRegistration` output. This example uses GitHub's link:https://github.com/github/webauthn-json[webauthn-json] library to do both (2) and (3) in one function call. @@ -229,15 +226,15 @@ return credentialCreateJson; // Send to client ---------- You will need to keep this -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/data/PublicKeyCredentialCreationOptions.html[`PublicKeyCredentialCreationOptions`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/data/PublicKeyCredentialCreationOptions.html[`PublicKeyCredentialCreationOptions`] object in temporary storage so you can also pass it into -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html#finishRegistration(com.yubico.webauthn.FinishRegistrationOptions)[`RelyingParty.fininshRegistration(...)`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html#finishRegistration(com.yubico.webauthn.FinishRegistrationOptions)[`RelyingParty.finishRegistration(...)`] later. If needed, you can use the -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/data/PublicKeyCredentialCreationOptions.html#toJson()[toJson()] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/data/PublicKeyCredentialCreationOptions.html#toJson()[`toJson()`] and -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/data/PublicKeyCredentialCreationOptions.html#fromJson(java.lang.String)[fromJson(String)] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/data/PublicKeyCredentialCreationOptions.html#fromJson(java.lang.String)[`fromJson(String)`] methods to serialize and deserialize the value for storage. Now call the WebAuthn API on the client side: @@ -295,7 +292,7 @@ storeCredential( // Some database access method of your own design Like registration ceremonies, an authentication ceremony consists of 5 main steps: 1. Generate authentication parameters using - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html#startAssertion(com.yubico.webauthn.StartAssertionOptions)[`RelyingParty.startAssertion(...)`]. + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html#startAssertion(com.yubico.webauthn.StartAssertionOptions)[`RelyingParty.startAssertion(...)`]. 2. Send authentication parameters to the client, call https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/get[`navigator.credentials.get()`] and return the response. @@ -303,7 +300,7 @@ Like registration ceremonies, an authentication ceremony consists of 5 main step https://www.w3.org/TR/webauthn-2/#ref-for-dom-publickeycredential-getclientextensionresults[`cred.getClientExtensionResults()`] and return the result along with `cred` to the server. 4. Validate the response using - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html#finishAssertion(com.yubico.webauthn.FinishAssertionOptions)[`RelyingParty.fininshAssertion(...)`]. + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html#finishAssertion(com.yubico.webauthn.FinishAssertionOptions)[`RelyingParty.finishAssertion(...)`]. 5. Update your database using the `finishAssertion` output, and act upon the result (for example, grant login access). This example uses GitHub's link:https://github.com/github/webauthn-json[webauthn-json] library to do both (2) and (3) in one function call. @@ -320,15 +317,15 @@ return credentialGetJson; // Send to client ---------- Again, you will need to keep this -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/AssertionRequest.html[`AssertionRequest`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/AssertionRequest.html[`AssertionRequest`] object in temporary storage so you can also pass it into -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html#finishAssertion(com.yubico.webauthn.FinishAssertionOptions)[`RelyingParty.fininshAssertion(...)`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html#finishAssertion(com.yubico.webauthn.FinishAssertionOptions)[`RelyingParty.finishAssertion(...)`] later. If needed, you can use the -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/AssertionRequest.html#toJson()[toJson()] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/AssertionRequest.html#toJson()[`toJson()`] and -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/AssertionRequest.html#fromJson(java.lang.String)[fromJson(String)] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/AssertionRequest.html#fromJson(java.lang.String)[`fromJson(String)`] methods to serialize and deserialize the value for storage. Now call the WebAuthn API on the client side: @@ -369,7 +366,7 @@ throw new RuntimeException("Authentication failed"); ---------- Finally, if the previous step was successful, update your database using the -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/AssertionResult.html[`AssertionResult`]. +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/AssertionResult.html[`AssertionResult`]. Most importantly, you should update the signature counter. That might look something like this: [source,java] @@ -433,7 +430,7 @@ AssertionRequest request = rp.startAssertion(StartAssertionOptions.builder() ---------- Then -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html#finishAssertion(com.yubico.webauthn.FinishAssertionOptions)[`RelyingParty.fininshAssertion(...)`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html#finishAssertion(com.yubico.webauthn.FinishAssertionOptions)[`RelyingParty.finishAssertion(...)`] will enforce that user verification was performed. However, there is no guarantee that the user's authenticator will support this unless the user has some credential created with the @@ -452,6 +449,11 @@ PublicKeyCredentialCreationOptions request = rp.startRegistration( ---------- +== Migrating from version `1.x` + +See link:doc/Migrating_from_v1.adoc[the migration guide]. + + == Migrating from U2F This section is only relevant for applications that have user credentials registered via the @@ -465,14 +467,14 @@ To migrate to using the WebAuthn API, you need to do the following: 1. Follow the link:#getting-started[Getting started] guide above to set up WebAuthn support in general. + -Note that unlike a U2F AppID, the WebAuthn link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/data/RelyingPartyIdentity.RelyingPartyIdentityBuilder.html#id(java.lang.String)[RP ID] +Note that unlike a U2F AppID, the WebAuthn link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/data/RelyingPartyIdentity.RelyingPartyIdentityBuilder.html#id(java.lang.String)[RP ID] consists of only the domain name of the AppID. WebAuthn does not support link:https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-appid-and-facets-v1.2-ps-20170411.html[U2F Trusted Facet Lists]. 2. Set the - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#appId(com.yubico.webauthn.extension.appid.AppId)[`appId()`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#appId(com.yubico.webauthn.extension.appid.AppId)[`appId()`] setting on your - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`] instance. The argument to the `appid()` setting should be the same as you used for the `appId` argument to the link:https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-javascript-api-v1.2-ps-20170411.html#high-level-javascript-api[U2F `register` and `sign` functions]. @@ -490,22 +492,22 @@ extensions and configure the `RelyingParty` to accept the given AppId when verif privacy consideration. 4. When your - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`] creates a - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RegisteredCredential.html[`RegisteredCredential`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RegisteredCredential.html[`RegisteredCredential`] for a U2F credential, use the U2F key handle as the - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RegisteredCredential.RegisteredCredentialBuilder.html#credentialId(com.yubico.webauthn.data.ByteArray)[credential ID]. + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RegisteredCredential.RegisteredCredentialBuilder.html#credentialId(com.yubico.webauthn.data.ByteArray)[credential ID]. If you store key handles base64 encoded, you should decode them using - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/data/ByteArray.html#fromBase64(java.lang.String)[`ByteArray.fromBase64`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/data/ByteArray.html#fromBase64(java.lang.String)[`ByteArray.fromBase64`] or - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/data/ByteArray.html#fromBase64Url(java.lang.String)[`ByteArray.fromBase64Url`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/data/ByteArray.html#fromBase64Url(java.lang.String)[`ByteArray.fromBase64Url`] as appropriate before passing them to the `RegisteredCredential`. 5. When your `CredentialRepository` creates a `RegisteredCredential` for a U2F credential, use the - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RegisteredCredential.RegisteredCredentialBuilder.html#publicKeyEs256Raw(com.yubico.webauthn.data.ByteArray)[`publicKeyEs256Raw()`] - method instead of link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RegisteredCredential.RegisteredCredentialBuilder.html#publicKeyCose(com.yubico.webauthn.data.ByteArray)[`publicKeyCose()`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RegisteredCredential.RegisteredCredentialBuilder.html#publicKeyEs256Raw(com.yubico.webauthn.data.ByteArray)[`publicKeyEs256Raw()`] + method instead of link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RegisteredCredential.RegisteredCredentialBuilder.html#publicKeyCose(com.yubico.webauthn.data.ByteArray)[`publicKeyCose()`] to set the credential public key. 6. Replace calls to the U2F @@ -639,17 +641,17 @@ provides optional additional features for working with attestation. See the module documentation for more details. Alternatively, you can use the -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/attestation/AttestationTrustSource.html[`AttestationTrustSource`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/attestation/AttestationTrustSource.html[`AttestationTrustSource`] interface to implement your own source of attestation root certificates and set it as the -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#attestationTrustSource(com.yubico.webauthn.attestation.AttestationTrustSource)[`attestationTrustSource`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#attestationTrustSource(com.yubico.webauthn.attestation.AttestationTrustSource)[`attestationTrustSource`] for your -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`] instance. Note that depending on your JCA provider configuration, you may need to set the -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/attestation/AttestationTrustSource.TrustRootsResult.TrustRootsResultBuilder.html#enableRevocationChecking(boolean)[`enableRevocationChecking`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/attestation/AttestationTrustSource.TrustRootsResult.TrustRootsResultBuilder.html#enableRevocationChecking(boolean)[`enableRevocationChecking`] and/or -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/attestation/AttestationTrustSource.TrustRootsResult.TrustRootsResultBuilder.html#policyTreeValidator(java.util.function.Predicate)[`policyTreeValidator`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/attestation/AttestationTrustSource.TrustRootsResult.TrustRootsResultBuilder.html#policyTreeValidator(java.util.function.Predicate)[`policyTreeValidator`] settings for compatibility with some authenticators' attestation certificates. See the JavaDoc for these settings for more information. diff --git a/build.gradle b/build.gradle index ff23a4ccb..3824c5cfe 100644 --- a/build.gradle +++ b/build.gradle @@ -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' } } @@ -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 diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 7dc1ca3f0..c55c2e01c 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -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") } diff --git a/webauthn-server-attestation/README.adoc b/webauthn-server-attestation/README.adoc index 65d5f6ee1..344fb70a3 100644 --- a/webauthn-server-attestation/README.adoc +++ b/webauthn-server-attestation/README.adoc @@ -9,6 +9,8 @@ https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-attestation[attestation by interfacing with the https://fidoalliance.org/metadata/[FIDO Metadata Service]. +*Table of contents* + toc::[] == Features @@ -19,7 +21,7 @@ This module does four things: - Re-download the metadata BLOB when out of date or invalid. - Provide utilities for selecting trusted metadata entries and authenticators. - Integrate with the - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`] class in the base library, to provide trust root certificates for verifying attestation statements during credential registrations. @@ -28,18 +30,18 @@ Notable *non-features* include: - *Scheduled BLOB downloads.* + The -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataDownloader.html[`FidoMetadataDownloader`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataDownloader.html[`FidoMetadataDownloader`] class will attempt to download a new BLOB only when its -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataDownloader.html#loadCachedBlob()[`loadCachedBlob()`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataDownloader.html#loadCachedBlob()[`loadCachedBlob()`] or -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataDownloader.html#refreshBlob()[`refreshBlob()`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataDownloader.html#refreshBlob()[`refreshBlob()`] method is executed. As the names suggest, `loadCachedBlob()` downloads a new BLOB only if the cache is empty or the cached BLOB is invalid or out of date, while `refreshBlob()` always downloads a new BLOB and falls back to the cached BLOB only when the new BLOB is invalid in some way. -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`] will never re-download a new BLOB once instantiated. + You should use some external scheduling mechanism to re-run `loadCachedBlob()` @@ -52,12 +54,12 @@ classes keep no internal mutable state. + The FIDO Metadata Service may from time to time report security issues with particular authenticator models. The -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`] class can be configured with a filter for which authenticators to trust, and untrusted authenticators can be rejected during registration by setting -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#allowUntrustedAttestation(boolean)[`.allowUntrustedAttestation(false)`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#allowUntrustedAttestation(boolean)[`.allowUntrustedAttestation(false)`] on -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`], +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`], but this will not affect any credentials already registered. @@ -92,7 +94,7 @@ Maven: com.yubico webauthn-server-attestation - 2.2.0 + 2.3.0 compile ---------- @@ -100,7 +102,7 @@ Maven: Gradle: ---------- -compile 'com.yubico:webauthn-server-attestation:2.2.0' +compile 'com.yubico:webauthn-server-attestation:2.3.0' ---------- @@ -109,8 +111,7 @@ compile 'com.yubico:webauthn-server-attestation:2.2.0' This library uses link:https://semver.org/[semantic versioning]. The public API consists of all public classes, methods and fields in the `com.yubico.fido.metadata` package and its subpackages, i.e., everything covered by the -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/package-summary.html[Javadoc], -*with the exception* of things annotated with `@Deprecated`. +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/package-summary.html[Javadoc]. Package-private classes and methods are NOT part of the public API. The `com.yubico:yubico-util` module is NOT part of the public API. @@ -122,23 +123,23 @@ Breaking changes to these will NOT be reflected in version numbers. Using this module consists of 4 major steps: 1. Create a - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataDownloader.html[`FidoMetadataDownloader`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataDownloader.html[`FidoMetadataDownloader`] instance to download and cache metadata BLOBs, and a - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`] instance to make use of the downloaded BLOB. See the JavaDoc for these classes for details on how to construct them. + [WARNING] ===== Unlike other classes in this module and the core library, -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataDownloader.html[`FidoMetadataDownloader`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataDownloader.html[`FidoMetadataDownloader`] is NOT THREAD SAFE since its -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataDownloader.html#loadCachedBlob()[`loadCachedBlob()`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataDownloader.html#loadCachedBlob()[`loadCachedBlob()`] and -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataDownloader.html#refreshBlob()[`refreshBlob()`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataDownloader.html#refreshBlob()[`refreshBlob()`] methods read and write caches. -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`], +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`], on the other hand, is thread safe, and `FidoMetadataDownloader` instances can be reused for subsequent `loadCachedBlob()` and `refreshBlob()` calls @@ -161,18 +162,18 @@ FidoMetadataService mds = FidoMetadataService.builder() ---------- 2. Set the - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`] as the - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#attestationTrustSource(com.yubico.webauthn.attestation.AttestationTrustSource)[`attestationTrustSource`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#attestationTrustSource(com.yubico.webauthn.attestation.AttestationTrustSource)[`attestationTrustSource`] on your - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`] instance, and set - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#attestationConveyancePreference(com.yubico.webauthn.data.AttestationConveyancePreference)[`attestationConveyancePreference(AttestationConveyancePreference.DIRECT)`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#attestationConveyancePreference(com.yubico.webauthn.data.AttestationConveyancePreference)[`attestationConveyancePreference(AttestationConveyancePreference.DIRECT)`] on `RelyingParty` to request an attestation statement for new registrations. Optionally also set - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#allowUntrustedAttestation(boolean)[`.allowUntrustedAttestation(false)`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#allowUntrustedAttestation(boolean)[`.allowUntrustedAttestation(false)`] on `RelyingParty` to require trusted attestation for new registrations. + [source,java] @@ -187,9 +188,9 @@ RelyingParty rp = RelyingParty.builder() ---------- 3. After performing registrations, inspect the - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RegistrationResult.html#isAttestationTrusted()[`isAttestationTrusted()`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RegistrationResult.html#isAttestationTrusted()[`isAttestationTrusted()`] result in - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RegistrationResult.html[`RegistrationResult`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RegistrationResult.html[`RegistrationResult`] to determine whether the authenticator presented an attestation statement that could be verified by any of the trusted attestation certificates in the FIDO Metadata Service. + @@ -206,7 +207,7 @@ if (result.isAttestationTrusted()) { ---------- 4. If needed, use the `findEntries` methods of - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`] to retrieve additional authenticator metadata for new registrations. + [source,java] @@ -218,7 +219,7 @@ Set metadata = mds.findEntries(result); ---------- By default, -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataDownloader.html[`FidoMetadataDownloader`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataDownloader.html[`FidoMetadataDownloader`] will probably use the SUN provider for the `PKIX` certificate path validation algorithm. This requires the `com.sun.security.enableCRLDP` system property set to `true` in order to verify the BLOB signature. For example, this can be done on the JVM command line using a `-Dcom.sun.security.enableCRLDP=true` option. @@ -229,19 +230,19 @@ for details. == Selecting trusted authenticators The -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`] class can be configured with filters for which authenticators to trust. When the `FidoMetadataService` is used as the -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#attestationTrustSource(com.yubico.webauthn.attestation.AttestationTrustSource)[`attestationTrustSource`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#attestationTrustSource(com.yubico.webauthn.attestation.AttestationTrustSource)[`attestationTrustSource`] in -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`], +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`], this will be reflected in the -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RegistrationResult.html#isAttestationTrusted()[`.isAttestationTrusted()`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RegistrationResult.html#isAttestationTrusted()[`.isAttestationTrusted()`] result in -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RegistrationResult.html[`RegistrationResult`]. +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RegistrationResult.html[`RegistrationResult`]. Any authenticators not trusted will also be rejected for new registrations if you set -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#allowUntrustedAttestation(boolean)[`.allowUntrustedAttestation(false)`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#allowUntrustedAttestation(boolean)[`.allowUntrustedAttestation(false)`] on `RelyingParty`. The filter has two stages: a "prefilter" which selects metadata entries to include in the data source, @@ -304,17 +305,17 @@ entry, and the default registration-time filter excludes any authenticator with a matching `ATTESTATION_KEY_COMPROMISE` status report entry. To customize the filters, configure the -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataService.FidoMetadataServiceBuilder.html#prefilter(java.util.function.Predicate)[`.prefilter(Predicate)`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataService.FidoMetadataServiceBuilder.html#prefilter(java.util.function.Predicate)[`.prefilter(Predicate)`] and -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataService.FidoMetadataServiceBuilder.html#filter(java.util.function.Predicate)[`.filter(Predicate)`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataService.FidoMetadataServiceBuilder.html#filter(java.util.function.Predicate)[`.filter(Predicate)`] settings in -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`]. +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`]. The filters are predicate functions; -each metadata entry will be trusted if and only if the prefilter predicate returns `true` for that entry. +each metadata entry will be included in the data source if and only if the prefilter predicate returns `true` for that entry. Similarly during registration or metadata lookup, the authenticator will be matched with each metadata entry only if the registration-time filter returns `true` for that pair of authenticator and metadata entry. You can also use the -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataService.Filters.html#allOf(java.util.function.Predicate\...)[`FidoMetadataService.Filters.allOf()`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataService.Filters.html#allOf(java.util.function.Predicate\...)[`FidoMetadataService.Filters.allOf()`] combinator to merge several predicates into one. [NOTE] @@ -324,10 +325,10 @@ This is true for both the prefilter and the registration-time filter. If you want to maintain the default filter in addition to the new behaviour, you must include the default condition in the new filter. For example, you can use -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataService.Filters.html#allOf(java.util.function.Predicate\...)[`FidoMetadataService.Filters.allOf()`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataService.Filters.html#allOf(java.util.function.Predicate\...)[`FidoMetadataService.Filters.allOf()`] to combine a predefined filter with a custom one. The default filters are available via static functions in -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataService.Filters.html[`FidoMetadataService.Filters`]. +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataService.Filters.html[`FidoMetadataService.Filters`]. ===== @@ -348,9 +349,9 @@ This is why any enforceable attestation policy must disallow unknown trust roots Note that unknown and untrusted attestation is allowed by default, but can be disallowed by explicitly configuring -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`] with -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#allowUntrustedAttestation(boolean)[`.allowUntrustedAttestation(false)`]. +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#allowUntrustedAttestation(boolean)[`.allowUntrustedAttestation(false)`]. == Alignment with FIDO MDS spec @@ -360,17 +361,17 @@ link:https://fidoalliance.org/specs/mds/fido-metadata-service-v3.0-ps-20210518.h The library implements these as closely as possible, but with some slight departures from the spec: * Processing rules steps 1-7 are implemented as specified, by the - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataDownloader.html[`FidoMetadataDownloader`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataDownloader.html[`FidoMetadataDownloader`] class. All "SHOULD" clauses are also respected, with some caveats: ** Step 3 states "The `nextUpdate` field of the Metadata BLOB specifies a date when the download SHOULD occur at latest". `FidoMetadataDownloader` does not automatically re-download the BLOB. Instead, each time the - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataDownloader.html#loadCachedBlob()[`loadCachedBlob()`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataDownloader.html#loadCachedBlob()[`loadCachedBlob()`] method is executed it checks whether a new BLOB should be downloaded. The - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataDownloader.html#refreshBlob()[`refreshBlob()`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataDownloader.html#refreshBlob()[`refreshBlob()`] method always attempts to download a new BLOB when executed, but also does not trigger re-downloads automatically. + @@ -382,7 +383,7 @@ until the next execution of `.loadCachedBlob()` or `.refreshBlob()`. * Metadata entries are not stored or cached individually, instead the BLOB is cached as a whole. In processing rules step 8, neither `FidoMetadataDownloader` nor - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`] performs any comparison between versions of a metadata entry. Policy for ignoring metadata entries can be configured via the filter settings in `FidoMetadataService`. See above for details. @@ -394,7 +395,7 @@ There are also some other requirements throughout the spec, which may not be obv states that "The Relying party MUST reject the Metadata Statement if the `authenticatorVersion` has not increased" in an `UPDATE_AVAILABLE` status report. Thus, - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataService.html[`FidoMetadataService`] silently ignores any `MetadataBLOBPayloadEntry` whose `metadataStatement.authenticatorVersion` is present and not greater than or equal to the `authenticatorVersion` in the respective status report. @@ -404,16 +405,16 @@ There are also some other requirements throughout the spec, which may not be obv link:https://fidoalliance.org/specs/mds/fido-metadata-service-v3.0-ps-20210518.html#info-statuses[AuthenticatorStatus section] states that "FIDO Servers MUST silently ignore all unknown AuthenticatorStatus values". Thus any unknown status values will be parsed as - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/AuthenticatorStatus.html#UNKNOWN[`AuthenticatorStatus.UNKNOWN`], + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/AuthenticatorStatus.html#UNKNOWN[`AuthenticatorStatus.UNKNOWN`], and - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/MetadataBLOBPayloadEntry.html[`MetadataBLOBPayloadEntry`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/MetadataBLOBPayloadEntry.html[`MetadataBLOBPayloadEntry`] will silently ignore any status report with that status. == Overriding certificate path validation The -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.2.0/com/yubico/fido/metadata/FidoMetadataDownloader.html[`FidoMetadataDownloader`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-attestation/2.3.0/com/yubico/fido/metadata/FidoMetadataDownloader.html[`FidoMetadataDownloader`] class uses `CertPathValidator.getInstance("PKIX")` to retrieve a `CertPathValidator` instance. If you need to override any aspect of certificate path validation, such as CRL retrieval or OCSP, you may provide a custom `CertPathValidator` provider for the `"PKIX"` algorithm. diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/AssertionResult.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/AssertionResult.java index b42ff37f9..f426943f1 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/AssertionResult.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/AssertionResult.java @@ -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; @@ -195,6 +196,20 @@ public boolean isBackedUp() { return credentialResponse.getResponse().getParsedAuthenticatorData().getFlags().BS; } + /** + * The authenticator + * attachment modality 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 getAuthenticatorAttachment() { + return credentialResponse.getAuthenticatorAttachment(); + } + /** * The new signature * count of the credential used for the assertion. diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/RegistrationResult.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/RegistrationResult.java index 88bbcfdbb..73c8d6e14 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/RegistrationResult.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/RegistrationResult.java @@ -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; @@ -175,6 +176,20 @@ public boolean isBackedUp() { return credential.getResponse().getParsedAuthenticatorData().getFlags().BS; } + /** + * The authenticator + * attachment modality 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 getAuthenticatorAttachment() { + return credential.getAuthenticatorAttachment(); + } + /** * The signature count returned with the created credential. * diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorAttachment.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorAttachment.java index 96c666659..d5d338b42 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorAttachment.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorAttachment.java @@ -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; @@ -73,18 +72,8 @@ public enum AuthenticatorAttachment { @JsonValue @Getter @NonNull private final String value; - private static Optional 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); } } diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredential.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredential.java index 3141912ca..ee0b5a38e 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredential.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredential.java @@ -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; @@ -46,7 +51,6 @@ */ @Value @Builder(toBuilder = true) -@JsonIgnoreProperties({"authenticatorAttachment"}) public class PublicKeyCredential< A extends AuthenticatorResponse, B extends ClientExtensionOutputs> { @@ -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. @@ -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) { @@ -95,6 +102,7 @@ private PublicKeyCredential( this.id = id == null ? rawId : id; this.response = response; + this.authenticatorAttachment = authenticatorAttachment; this.clientExtensionResults = clientExtensionResults; this.type = type; } @@ -102,9 +110,33 @@ private PublicKeyCredential( 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 authenticator + * attachment modality in effect at the time the credential was created or used. + * + *

If parsed from JSON, this will be present if and only if the input was a valid value of + * {@link AuthenticatorAttachment}. + * + *

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 getAuthenticatorAttachment() { + return Optional.ofNullable(authenticatorAttachment); } public static diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/package-info.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/package-info.java index e9e79f439..85057f464 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/package-info.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/package-info.java @@ -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. * *

The main entry point is the {@link com.yubico.webauthn.RelyingParty} class. It provides * methods for generating inputs to the navigator.credentials.create() and @@ -82,15 +82,22 @@ * com.yubico.webauthn.data.PublicKeyCredentialCreationOptions} which can be serialized to JSON and * passed as the publicKey argument to navigator.credentials.create(). 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)}. - * - *

After receiving the response from the client, construct a {@link - * com.yubico.webauthn.data.PublicKeyCredential}<{@link - * com.yubico.webauthn.data.AuthenticatorAttestationResponse}, {@link - * com.yubico.webauthn.data.ClientRegistrationExtensionOutputs}> 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. + * + *

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. + * + *

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 @@ -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. + *

  • 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. + *
  • 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. *
  • 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 webauthn-server-attestation for an implementation of such an + * attestation trust and metadata source. *
  • 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 @@ -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 client-side-resident + * allowCredentials} parameter will not be set. This means the user must use a client-side-discoverable * credential 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. @@ -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 publicKey argument to - * navigator.credentials.get(). Again, store the {@link - * com.yubico.webauthn.AssertionRequest} in temporary storage so it can be passed as an argument to - * {@link + * navigator.credentials.get(). 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)}. - * - *

    After receiving the response from the client, construct a {@link - * com.yubico.webauthn.data.PublicKeyCredential}<{@link - * com.yubico.webauthn.data.AuthenticatorAssertionResponse}, {@link - * com.yubico.webauthn.data.ClientAssertionExtensionOutputs}> 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. + * + *

    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 diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyAssertionSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyAssertionSpec.scala index 8f9e309c7..f817ad746 100644 --- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyAssertionSpec.scala +++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyAssertionSpec.scala @@ -31,6 +31,7 @@ import com.upokecenter.cbor.CBORObject import com.yubico.internal.util.JacksonCodecs import com.yubico.webauthn.data.AssertionExtensionInputs import com.yubico.webauthn.data.AuthenticatorAssertionResponse +import com.yubico.webauthn.data.AuthenticatorAttachment import com.yubico.webauthn.data.AuthenticatorDataFlags import com.yubico.webauthn.data.AuthenticatorTransport import com.yubico.webauthn.data.ByteArray @@ -2592,6 +2593,36 @@ class RelyingPartyAssertionSpec resultWithBeOnly.isBackedUp should be(false) resultWithBackup.isBackedUp should be(true) } + + it( + "exposes getAuthenticatorAttachment() with the authenticatorAttachment value from the PublicKeyCredential." + ) { + val pkcTemplate = + TestAuthenticator.createAssertion( + challenge = + request.getPublicKeyCredentialRequestOptions.getChallenge, + credentialKey = credentialKeypair, + credentialId = credential.getId, + ) + + forAll { authenticatorAttachment: Option[AuthenticatorAttachment] => + val pkc = pkcTemplate.toBuilder + .authenticatorAttachment(authenticatorAttachment.orNull) + .build() + + val result = rp.finishAssertion( + FinishAssertionOptions + .builder() + .request(request) + .response(pkc) + .build() + ) + + result.getAuthenticatorAttachment should equal( + pkc.getAuthenticatorAttachment + ) + } + } } } } diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyRegistrationSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyRegistrationSpec.scala index 983cd281d..a737f23b4 100644 --- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyRegistrationSpec.scala +++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyRegistrationSpec.scala @@ -42,6 +42,7 @@ import com.yubico.webauthn.attestation.AttestationTrustSource import com.yubico.webauthn.attestation.AttestationTrustSource.TrustRootsResult import com.yubico.webauthn.data.AttestationObject import com.yubico.webauthn.data.AttestationType +import com.yubico.webauthn.data.AuthenticatorAttachment import com.yubico.webauthn.data.AuthenticatorAttestationResponse import com.yubico.webauthn.data.AuthenticatorData import com.yubico.webauthn.data.AuthenticatorDataFlags @@ -4619,6 +4620,33 @@ class RelyingPartyRegistrationSpec resultWithBeOnly.isBackedUp should be(false) resultWithBackup.isBackedUp should be(true) } + + it( + "exposes getAuthenticatorAttachment() with the authenticatorAttachment value from the PublicKeyCredential." + ) { + val (pkcTemplate, _, _) = + TestAuthenticator.createUnattestedCredential(challenge = + request.getChallenge + ) + + forAll { authenticatorAttachment: Option[AuthenticatorAttachment] => + val pkc = pkcTemplate.toBuilder + .authenticatorAttachment(authenticatorAttachment.orNull) + .build() + + val result = rp.finishRegistration( + FinishRegistrationOptions + .builder() + .request(request) + .response(pkc) + .build() + ) + + result.getAuthenticatorAttachment should equal( + pkc.getAuthenticatorAttachment + ) + } + } } } diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/EnumsSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/EnumsSpec.scala index 8e35e8558..dbe5ca609 100644 --- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/EnumsSpec.scala +++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/EnumsSpec.scala @@ -61,11 +61,10 @@ class EnumsSpec describe("AuthenticatorAttachment") { describe("can be parsed from JSON") { - it("but throws IllegalArgumentException for unknown values.") { - val result = Try( + it("and ignores for unknown values.") { + val result = json.readValue("\"foo\"", classOf[AuthenticatorAttachment]) - ) - result.failed.get.getCause shouldBe an[IllegalArgumentException] + result should be(null) } } } diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/JsonIoSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/JsonIoSpec.scala index 29cfa491f..0fd026f9b 100644 --- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/JsonIoSpec.scala +++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/JsonIoSpec.scala @@ -41,12 +41,13 @@ import com.yubico.webauthn.extension.appid.Generators._ import org.junit.runner.RunWith import org.scalacheck.Arbitrary import org.scalacheck.Arbitrary.arbitrary -import org.scalacheck.Gen import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers import org.scalatestplus.junit.JUnitRunner import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks +import scala.jdk.OptionConverters.RichOptional + @RunWith(classOf[JUnitRunner]) class JsonIoSpec extends AnyFunSpec @@ -351,15 +352,16 @@ class JsonIoSpec ) } - it("allows and ignores an authenticatorAttachment attribute.") { + it( + "allows an authenticatorAttachment attribute, but ignores unknown values." + ) { def test[P <: PublicKeyCredential[_, _]](tpe: TypeReference[P])(implicit a: Arbitrary[P] ): Unit = { forAll( a.arbitrary, - Gen.oneOf( - arbitrary[AuthenticatorAttachment].map(_.getValue), - arbitrary[String], + arbitrary[String].suchThat(s => + !AuthenticatorAttachment.values.map(_.getValue).contains(s) ), ) { (value: P, authenticatorAttachment: String) => val tree: ObjectNode = json.valueToTree(value) @@ -370,8 +372,37 @@ class JsonIoSpec val encoded = json.writeValueAsString(tree) println(authenticatorAttachment) val decoded = json.readValue(encoded, tpe) + decoded.getAuthenticatorAttachment.asScala should be(None) + } + + forAll( + a.arbitrary, + arbitrary[AuthenticatorAttachment], + ) { (value: P, authenticatorAttachment: AuthenticatorAttachment) => + val tree: ObjectNode = json.valueToTree(value) + tree.set( + "authenticatorAttachment", + new TextNode(authenticatorAttachment.getValue), + ) + val encoded = json.writeValueAsString(tree) + println(authenticatorAttachment) + val decoded = json.readValue(encoded, tpe) + + decoded.getAuthenticatorAttachment.asScala should equal( + Some(authenticatorAttachment) + ) + } + + forAll( + a.arbitrary + ) { (value: P) => + val tree: ObjectNode = json.valueToTree( + value.toBuilder.authenticatorAttachment(null).build() + ) + val encoded = json.writeValueAsString(tree) + val decoded = json.readValue(encoded, tpe) - decoded should equal(value) + decoded.getAuthenticatorAttachment.asScala should be(None) } } diff --git a/webauthn-server-demo/README b/webauthn-server-demo/README index c38cc074a..06bc0e86c 100644 --- a/webauthn-server-demo/README +++ b/webauthn-server-demo/README @@ -40,7 +40,7 @@ layer. This layer manages the general architecture of the system, and is where most business logic and integration code would go. The demo server implements the "persistent" storage of users and credential registrations - the -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`] integration point - as the link:src/main/java/demo/webauthn/InMemoryRegistrationStorage.java[`InMemoryRegistrationStorage`] class, which simply keeps them stored in memory for a limited time. The @@ -54,7 +54,7 @@ would be specific to a particular Relying Party (RP) would go in this layer. - The server layer in turn calls the *library layer*, which is where the link:../webauthn-server-core/[`webauthn-server-core`] library gets involved. The entry point into the library is the - link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`] + link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/RelyingParty.html[`RelyingParty`] class. + This layer implements the Web Authentication @@ -65,11 +65,11 @@ and exposes integration points for storage of challenges and credentials. Some notable integration points are: + ** The library user must provide an implementation of the -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`] interface to use for looking up stored public keys, user handles and signature counters. ** The library user can optionally provide an instance of the -link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.2.0/com/yubico/webauthn/attestation/AttestationTrustSource.html[`AttestationTrustSource`] +link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.3.0/com/yubico/webauthn/attestation/AttestationTrustSource.html[`AttestationTrustSource`] interface to enable identification and validation of authenticator models. This instance is then used to look up trusted attestation root certificates. The link:../webauthn-server-attestation/[`webauthn-server-attestation`]