diff --git a/NEWS b/NEWS
index 07649e96b..ae4c811be 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,13 @@
+== Version 1.12.2 ==
+
+Fixes:
+
+* `com.upokecenter:cbor` dependency bumped to minimum version 4.5.1 due to a
+ known vulnerability, see: https://github.com/advisories/GHSA-fj2w-wfgv-mwq6
+* Fixed crash in `AuthenticatorData` deserialization with `com.upokecenter:cbor`
+ versions later than 4.0.1
+
+
== Version 1.12.1 ==
Fixes:
diff --git a/README b/README
index 00f5eb037..109191c79 100644
--- a/README
+++ b/README
@@ -25,7 +25,7 @@ Maven:
com.yubico
webauthn-server-core
- 1.12.1
+ 1.12.2
compile
----------
@@ -33,7 +33,7 @@ Maven:
Gradle:
----------
-compile 'com.yubico:webauthn-server-core:1.12.1'
+compile 'com.yubico:webauthn-server-core:1.12.2'
----------
=== Semantic versioning
@@ -110,10 +110,7 @@ The client side involves:
passing the result from `RelyingParty.startRegistration(...)` or `.startAssertion(...)` as the argument.
2. Encode the result of the successfully resolved promise and return it to the server.
For this you need some way to encode `Uint8Array` values;
- this guide will assume use of link:https://github.com/beatgammit/base64-js[base64-js].
- However the built-in parser methods for
- link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core-minimal/latest/com/yubico/webauthn/data/PublicKeyCredential.html[`PublicKeyCredential`]
- expect URL-safe Base64 encoding, so the below examples will include this as an additional step.
+ this guide will use GitHub's link:https://github.com/github/webauthn-json[webauthn-json] library.
Example code is given below.
For more detailed example usage, see
@@ -163,6 +160,8 @@ A registration ceremony consists of 5 main steps:
4. Validate the response using `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.
+
First, generate registration parameters and send them to the client:
[source,java]
@@ -196,62 +195,23 @@ Now call the WebAuthn API on the client side:
[source,javascript]
----------
-function base64urlToUint8array(base64Bytes) {
- const padding = '===='.substring(0, (4 - (base64Bytes.length % 4)) % 4);
- return base64js.toByteArray((base64Bytes + padding).replace(/\//g, "_").replace(/\+/g, "-"));
-}
-function uint8arrayToBase64url(bytes) {
- if (bytes instanceof Uint8Array) {
- return base64js.fromByteArray(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
- } else {
- return uint8arrayToBase64url(new Uint8Array(bytes));
- }
-}
-
-fetch(/* ... */) // Make the call that returns the credentialCreateJson above
- .then(credentialCreateJson => ({ // Decode byte arrays from base64url
- publicKey: {
- ...credentialCreateJson.publicKey,
-
- challenge: base64urlToUint8array(credentialCreateJson.publicKey.challenge),
- user: {
- ...credentialCreateJson.publicKey.user,
- id: base64urlToUint8array(credentialCreateJson.publicKey.user.id),
- },
- excludeCredentials: credentialCreateJson.publicKey.excludeCredentials.map(credential => ({
- ...credential,
- id: base64urlToUint8array(credential.id),
- })),
-
- // Warning: Extension inputs could also contain binary data that needs encoding
- extensions: credentialCreateJson.publicKey.extensions,
- },
- }))
- .then(credentialCreateOptions => // Call WebAuthn ceremony
- navigator.credentials.create(credentialCreateOptions))
- .then(publicKeyCredential => ({ // Encode PublicKeyCredential for transport to server (example)
- type: publicKeyCredential.type,
- id: publicKeyCredential.id,
- response: {
- attestationObject: uint8arrayToBase64url(publicKeyCredential.response.attestationObject),
- clientDataJSON: uint8arrayToBase64url(publicKeyCredential.response.clientDataJSON),
-
- // Attempt to read transports, but recover gracefully if not supported by the browser
- transports: publicKeyCredential.response.getTransports && publicKeyCredential.response.getTransports() || [],
- },
-
- // Warning: Client extension results could also contain binary data that needs encoding
- clientExtensionResults: publicKeyCredential.getClientExtensionResults(),
- }))
- .then(encodedResult =>
- fetch(/* ... */)); // Return encoded PublicKeyCredential to server
+import * as webauthnJson from "@github/webauthn-json";
+
+// Make the call that returns the credentialCreateJson above
+const credentialCreateOptions = await fetch(/* ... */).then(resp => resp.json());
+
+// Call WebAuthn ceremony using webauthn-json wrapper
+const publicKeyCredential = await webauthnJson.create(credentialCreateOptions);
+
+// Return encoded PublicKeyCredential to server
+fetch(/* ... */, { body: JSON.stringify(publicKeyCredential) });
----------
Validate the response on the server side:
[source,java]
----------
-String publicKeyCredentialJson = /* ... */; // encodedResult from client
+String publicKeyCredentialJson = /* ... */; // publicKeyCredential from client
PublicKeyCredential pkc =
PublicKeyCredential.parseRegistrationResponseJson(publicKeyCredentialJson);
@@ -295,6 +255,8 @@ Like registration ceremonies, an authentication ceremony consists of 5 main step
4. Validate the response using `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.
+
First, generate authentication parameters and send them to the client:
[source,java]
@@ -313,58 +275,23 @@ Now call the WebAuthn API on the client side:
[source,javascript]
----------
-function base64urlToUint8array(base64Bytes) {
- const padding = '===='.substring(0, (4 - (base64Bytes.length % 4)) % 4);
- return base64js.toByteArray((base64Bytes + padding).replace(/\//g, "_").replace(/\+/g, "-"));
-}
-function uint8arrayToBase64url(bytes) {
- if (bytes instanceof Uint8Array) {
- return base64js.fromByteArray(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
- } else {
- return uint8arrayToBase64url(new Uint8Array(bytes));
- }
-}
-
-fetch(/* ... */) // Make the call that returns the credentialGetJson above
- .then(credentialGetJson => ({ // Decode byte arrays from base64url
- publicKey: {
- ...credentialGetJson.publicKey,
- allowCredentials: credentialGetJson.publicKey.allowCredentials
- && credentialGetJson.publicKey.allowCredentials.map(credential => ({
- ...credential,
- id: base64urlToUint8array(credential.id),
- })),
-
- challenge: base64urlToUint8array(credentialGetJson.publicKey.challenge),
-
- // Warning: Extension inputs could also contain binary data that needs encoding
- extensions: credentialGetJson.publicKey.extensions,
- },
- }))
- .then(credentialGetOptions => // Call WebAuthn ceremony
- navigator.credentials.get(credentialGetOptions))
- .then(publicKeyCredential => ({ // Encode PublicKeyCredential for transport to server (example)
- type: publicKeyCredential.type,
- id: publicKeyCredential.id,
- response: {
- authenticatorData: uint8arrayToBase64url(publicKeyCredential.response.authenticatorData),
- clientDataJSON: uint8arrayToBase64url(publicKeyCredential.response.clientDataJSON),
- signature: uint8arrayToBase64url(publicKeyCredential.response.signature),
- userHandle: publicKeyCredential.response.userHandle && uint8arrayToBase64url(publicKeyCredential.response.userHandle),
- },
-
- // Warning: Client extension results could also contain binary data that needs encoding
- clientExtensionResults: publicKeyCredential.getClientExtensionResults(),
- }))
- .then(encodedResult =>
- fetch(/* ... */)); // Return encoded PublicKeyCredential to server
+import * as webauthnJson from "@github/webauthn-json";
+
+// Make the call that returns the credentialGetJson above
+const credentialGetOptions = await fetch(/* ... */).then(resp => resp.json());
+
+// Call WebAuthn ceremony using webauthn-json wrapper
+const publicKeyCredential = await webauthnJson.get(credentialGetOptions);
+
+// Return encoded PublicKeyCredential to server
+fetch(/* ... */, { body: JSON.stringify(publicKeyCredential) });
----------
Validate the response on the server side:
[source,java]
----------
-String publicKeyCredentialJson = /* ... */; // encodedResult from client
+String publicKeyCredentialJson = /* ... */; // publicKeyCredential from client
PublicKeyCredential pkc =
PublicKeyCredential.parseAssertionResponseJson(publicKeyCredentialJson);
@@ -473,13 +400,13 @@ and credentials registered via the U2F API will continue to work with the WebAut
To migrate to using the WebAuthn API, you need to do the following:
-* Follow the link:#getting-started[Getting started] guide above to set up WebAuthn support in general.
+ 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-minimal/latest/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].
-* Set the
+ 2. Set the
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core-minimal/latest/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#appId(com.yubico.webauthn.extension.appid.AppId)[`appId()`]
setting on your `RelyingParty` instance.
The argument to the `appid()` setting should be the same as you used for the `appId` argument to the
@@ -489,36 +416,36 @@ This will enable the link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sc
and link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-appid-exclude-extension[`appidExclude`]
extensions and configure the `RelyingParty` to accept the given AppId when verifying authenticator signatures.
-* Generate a link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#user-handle[user handle] for each existing user
- and store it in their account,
- or decide on a method for deriving one deterministically from existing user attributes.
- For example, if your user records are assigned UUIDs, you can use that UUID as the user handle.
- You SHOULD NOT use a plain username or e-mail address, or hash of either, as the user handle -
- for more on this, see the link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-user-handle-privacy[User Handle Contents]
- privacy consideration.
-
-* When your `CredentialRepository` creates a `RegisteredCredential` for a U2F credential,
- use the U2F key handle as the
- link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core-minimal/latest/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-minimal/latest/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-minimal/latest/com/yubico/webauthn/data/ByteArray.html#fromBase64Url(java.lang.String)[`ByteArray.fromBase64Url`]
- as appropriate before passing them to the `RegisteredCredential`.
-
-* When your `CredentialRepository` creates a `RegisteredCredential` for a U2F credential,
- use the
- link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core-minimal/latest/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-minimal/latest/com/yubico/webauthn/RegisteredCredential.RegisteredCredentialBuilder.html#publicKeyCose(com.yubico.webauthn.data.ByteArray)[`publicKeyCose()`]
- to set the credential public key.
-
-* Replace calls to the U2F
- link:https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-javascript-api-v1.2-ps-20170411.html#high-level-javascript-api[`register`]
- method with calls to `navigator.credentials.create()` as described in link:#getting-started[Getting started].
-
-* Replace calls to the U2F
- link:https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-javascript-api-v1.2-ps-20170411.html#high-level-javascript-api[`sign`]
- method with calls to `navigator.credentials.get()` as described in link:#getting-started[Getting started].
+ 3. Generate a link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#user-handle[user handle]
+ for each existing user and store it in their account,
+ or decide on a method for deriving one deterministically from existing user attributes.
+ For example, if your user records are assigned UUIDs, you can use that UUID as the user handle.
+ You SHOULD NOT use a plain username or e-mail address, or hash of either, as the user handle -
+ for more on this, see the link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-user-handle-privacy[User Handle Contents]
+ privacy consideration.
+
+ 4. When your `CredentialRepository` creates a `RegisteredCredential` for a U2F credential,
+ use the U2F key handle as the
+ link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core-minimal/latest/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-minimal/latest/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-minimal/latest/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-minimal/latest/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-minimal/latest/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
+ link:https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-javascript-api-v1.2-ps-20170411.html#high-level-javascript-api[`register`]
+ method with calls to `navigator.credentials.create()` as described in link:#getting-started[Getting started].
+
+ 7. Replace calls to the U2F
+ link:https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-javascript-api-v1.2-ps-20170411.html#high-level-javascript-api[`sign`]
+ method with calls to `navigator.credentials.get()` as described in link:#getting-started[Getting started].
Existing U2F credentials should now work with the WebAuthn API.
diff --git a/build.gradle b/build.gradle
index 3bd0c4a60..f3da8fb6e 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.0.4'
+ classpath 'com.diffplug.spotless:spotless-plugin-gradle:6.2.0'
classpath 'io.github.cosmicsilence:gradle-scalafix:0.1.8'
}
}
@@ -51,7 +51,7 @@ dependencies {
api('com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:[2.11.0,3)')
api('com.fasterxml.jackson.datatype:jackson-datatype-jdk8:[2.11.0,3)')
api('com.google.guava:guava:[24.1.1,31)')
- api('com.upokecenter:cbor:[4.0.1,5)')
+ api('com.upokecenter:cbor:[4.5.1,5)')
api('javax.ws.rs:javax.ws.rs-api:[2.1,3)')
api('javax.xml.bind:jaxb-api:[2.3.0,3)')
api('junit:junit:[4.12,5)')
diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorData.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorData.java
index 699071670..f15c7fbdd 100644
--- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorData.java
+++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorData.java
@@ -25,6 +25,7 @@
package com.yubico.webauthn.data;
import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
@@ -81,9 +82,9 @@ public class AuthenticatorData {
*
* @see #flags
*/
- private final transient AttestedCredentialData attestedCredentialData;
+ @JsonIgnore private final transient AttestedCredentialData attestedCredentialData;
- private final transient CBORObject extensions;
+ @JsonIgnore private final transient CBORObject extensions;
private static final int RP_ID_HASH_INDEX = 0;
private static final int RP_ID_HASH_END = RP_ID_HASH_INDEX + 32;
diff --git a/webauthn-server-demo/src/main/webapp/index.html b/webauthn-server-demo/src/main/webapp/index.html
index 5dd90ece8..1950e074e 100644
--- a/webauthn-server-demo/src/main/webapp/index.html
+++ b/webauthn-server-demo/src/main/webapp/index.html
@@ -55,9 +55,9 @@
-
-