From cab8c13d5809abc179d3f43186c39968c6b6a0ef Mon Sep 17 00:00:00 2001 From: Dennis Fokin Date: Fri, 6 Sep 2024 08:53:38 +0200 Subject: [PATCH] Add support for PublicKeyCredentialHint --- .../com/yubico/webauthn/RelyingParty.java | 3 +- .../com/yubico/webauthn/RelyingPartyV2.java | 6 +- .../webauthn/StartAssertionOptions.java | 38 +++++- .../webauthn/StartRegistrationOptions.java | 36 +++++- .../PublicKeyCredentialCreationOptions.java | 21 ++++ .../data/PublicKeyCredentialHint.java | 110 ++++++++++++++++++ .../PublicKeyCredentialRequestOptions.java | 22 ++++ .../com/yubico/webauthn/Generators.scala | 11 ++ .../RelyingPartyStartOperationSpec.scala | 34 ++++++ .../com/yubico/webauthn/data/Generators.scala | 48 ++++++-- 10 files changed, 312 insertions(+), 17 deletions(-) create mode 100644 webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialHint.java diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/RelyingParty.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/RelyingParty.java index f8e588eb6..be6a2dc22 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/RelyingParty.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/RelyingParty.java @@ -493,7 +493,8 @@ public PublicKeyCredentialCreationOptions startRegistration( .appidExclude(appId) .credProps() .build())) - .timeout(startRegistrationOptions.getTimeout()); + .timeout(startRegistrationOptions.getTimeout()) + .hints(startRegistrationOptions.getHints()); attestationConveyancePreference.ifPresent(builder::attestation); return builder.build(); } diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/RelyingPartyV2.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/RelyingPartyV2.java index 23a71c5bf..9ce2883c3 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/RelyingPartyV2.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/RelyingPartyV2.java @@ -452,7 +452,8 @@ public PublicKeyCredentialCreationOptions startRegistration( .appidExclude(appId) .credProps() .build())) - .timeout(startRegistrationOptions.getTimeout()); + .timeout(startRegistrationOptions.getTimeout()) + .hints(startRegistrationOptions.getHints()); attestationConveyancePreference.ifPresent(builder::attestation); return builder.build(); } @@ -509,7 +510,8 @@ public AssertionRequest startAssertion(StartAssertionOptions startAssertionOptio startAssertionOptions .getExtensions() .merge(startAssertionOptions.getExtensions().toBuilder().appid(appId).build())) - .timeout(startAssertionOptions.getTimeout()); + .timeout(startAssertionOptions.getTimeout()) + .hints(startAssertionOptions.getHints()); startAssertionOptions.getUserVerification().ifPresent(pkcro::userVerification); diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/StartAssertionOptions.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/StartAssertionOptions.java index 461f31228..df119e0fa 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/StartAssertionOptions.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/StartAssertionOptions.java @@ -26,8 +26,12 @@ import com.yubico.webauthn.data.AssertionExtensionInputs; import com.yubico.webauthn.data.ByteArray; +import com.yubico.webauthn.data.PublicKeyCredentialHint; import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions; import com.yubico.webauthn.data.UserVerificationRequirement; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Optional; import lombok.Builder; import lombok.NonNull; @@ -36,7 +40,7 @@ /** Parameters for {@link RelyingParty#startAssertion(StartAssertionOptions)}. */ @Value @Builder(toBuilder = true) -public class StartAssertionOptions { +public final class StartAssertionOptions { private final String username; @@ -79,6 +83,23 @@ public class StartAssertionOptions { */ private final Long timeout; + private final List hints; + + private StartAssertionOptions( + String username, + ByteArray userHandle, + @NonNull AssertionExtensionInputs extensions, + UserVerificationRequirement userVerification, + Long timeout, + List hints) { + this.username = username; + this.userHandle = userHandle; + this.extensions = extensions; + this.userVerification = userVerification; + this.timeout = timeout; + this.hints = hints == null ? Collections.emptyList() : Collections.unmodifiableList(hints); + } + /** * The username of the user to authenticate, if the user has already been identified. * @@ -370,5 +391,20 @@ public StartAssertionOptionsBuilder timeout(long timeout) { private StartAssertionOptionsBuilder timeout(Long timeout) { return this.timeout(Optional.ofNullable(timeout)); } + + public StartAssertionOptionsBuilder hints(@NonNull String... hints) { + this.hints = Arrays.asList(hints); + return this; + } + + public StartAssertionOptionsBuilder hints(@NonNull PublicKeyCredentialHint... hints) { + return this.hints( + Arrays.stream(hints).map(PublicKeyCredentialHint::getValue).toArray(String[]::new)); + } + + public StartAssertionOptionsBuilder hints(@NonNull List hints) { + this.hints = hints; + return this; + } } } diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/StartRegistrationOptions.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/StartRegistrationOptions.java index e78184fb5..a8d51d766 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/StartRegistrationOptions.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/StartRegistrationOptions.java @@ -26,8 +26,12 @@ import com.yubico.webauthn.data.AuthenticatorSelectionCriteria; import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions; +import com.yubico.webauthn.data.PublicKeyCredentialHint; import com.yubico.webauthn.data.RegistrationExtensionInputs; import com.yubico.webauthn.data.UserIdentity; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Optional; import lombok.Builder; import lombok.NonNull; @@ -36,7 +40,7 @@ /** Parameters for {@link RelyingParty#startRegistration(StartRegistrationOptions)}. */ @Value @Builder(toBuilder = true) -public class StartRegistrationOptions { +public final class StartRegistrationOptions { /** Identifiers for the user creating a credential. */ @NonNull private final UserIdentity user; @@ -64,6 +68,21 @@ public class StartRegistrationOptions { */ private final Long timeout; + private final List hints; + + private StartRegistrationOptions( + @NonNull UserIdentity user, + AuthenticatorSelectionCriteria authenticatorSelection, + @NonNull RegistrationExtensionInputs extensions, + Long timeout, + List hints) { + this.user = user; + this.authenticatorSelection = authenticatorSelection; + this.extensions = extensions; + this.timeout = timeout; + this.hints = hints == null ? Collections.emptyList() : Collections.unmodifiableList(hints); + } + /** * Constraints on what kind of authenticator the user is allowed to use to create the credential, * and on features that authenticator must or should support. @@ -157,5 +176,20 @@ public StartRegistrationOptionsBuilder timeout(@NonNull Optional timeout) public StartRegistrationOptionsBuilder timeout(long timeout) { return this.timeout(Optional.of(timeout)); } + + public StartRegistrationOptionsBuilder hints(@NonNull String... hints) { + this.hints = Arrays.asList(hints); + return this; + } + + public StartRegistrationOptionsBuilder hints(@NonNull PublicKeyCredentialHint... hints) { + return this.hints( + Arrays.stream(hints).map(PublicKeyCredentialHint::getValue).toArray(String[]::new)); + } + + public StartRegistrationOptionsBuilder hints(@NonNull List hints) { + this.hints = hints; + return this; + } } } diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialCreationOptions.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialCreationOptions.java index 3d2b6033f..105586a41 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialCreationOptions.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialCreationOptions.java @@ -36,6 +36,7 @@ import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.Signature; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -94,6 +95,8 @@ public class PublicKeyCredentialCreationOptions { */ private final Long timeout; + private final List hints; + /** * Intended for use by Relying Parties that wish to limit the creation of multiple credentials for * the same account on a single authenticator. The client is requested to return an error if the @@ -136,6 +139,7 @@ private PublicKeyCredentialCreationOptions( @NonNull @JsonProperty("pubKeyCredParams") List pubKeyCredParams, @JsonProperty("timeout") Long timeout, + @JsonProperty("hints") List hints, @JsonProperty("excludeCredentials") Set excludeCredentials, @JsonProperty("authenticatorSelection") AuthenticatorSelectionCriteria authenticatorSelection, @JsonProperty("attestation") AttestationConveyancePreference attestation, @@ -145,6 +149,7 @@ private PublicKeyCredentialCreationOptions( this.challenge = challenge; this.pubKeyCredParams = filterAvailableAlgorithms(pubKeyCredParams); this.timeout = timeout; + this.hints = hints == null ? Collections.emptyList() : Collections.unmodifiableList(hints); this.excludeCredentials = excludeCredentials == null ? null @@ -317,6 +322,22 @@ public PublicKeyCredentialCreationOptionsBuilder timeout(long timeout) { return this.timeout(Optional.of(timeout)); } + public PublicKeyCredentialCreationOptionsBuilder hints(@NonNull String... hints) { + this.hints = Arrays.asList(hints); + return this; + } + + public PublicKeyCredentialCreationOptionsBuilder hints( + @NonNull PublicKeyCredentialHint... hints) { + return this.hints( + Arrays.stream(hints).map(PublicKeyCredentialHint::getValue).toArray(String[]::new)); + } + + public PublicKeyCredentialCreationOptionsBuilder hints(List hints) { + this.hints = hints; + return this; + } + /** * Intended for use by Relying Parties that wish to limit the creation of multiple credentials * for the same account on a single authenticator. The client is requested to return an error if diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialHint.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialHint.java new file mode 100644 index 000000000..86cfe359a --- /dev/null +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialHint.java @@ -0,0 +1,110 @@ +// Copyright (c) 2018, Yubico AB +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package com.yubico.webauthn.data; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.stream.Stream; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.NonNull; +import lombok.Value; + +/** + * Authenticators may communicate with Clients using a variety of transports. This enumeration + * defines a hint as to how Clients might communicate with a particular Authenticator in order to + * obtain an assertion for a specific credential. Note that these hints represent the Relying + * Party's best belief as to how an Authenticator may be reached. A Relying Party may obtain a list + * of transports hints from some attestation statement formats or via some out-of-band mechanism; it + * is outside the scope of this specification to define that mechanism. + * + *

Authenticators may implement various transports for communicating with clients. This + * enumeration defines hints as to how clients might communicate with a particular authenticator in + * order to obtain an assertion for a specific credential. Note that these hints represent the + * WebAuthn Relying Party's best belief as to how an authenticator may be reached. A Relying Party + * may obtain a list of transports hints from some attestation statement formats or via some + * out-of-band mechanism; it is outside the scope of the Web Authentication specification to define + * that mechanism. + * + * @see ยง5.10.4. + * Authenticator Transport Enumeration (enum AuthenticatorTransport) + */ +@Value +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class PublicKeyCredentialHint { + + @JsonValue @NonNull private final String value; + + public static final PublicKeyCredentialHint SECURITY_KEY = + new PublicKeyCredentialHint("security-key"); + + public static final PublicKeyCredentialHint CLIENT_DEVICE = + new PublicKeyCredentialHint("client-device"); + + public static final PublicKeyCredentialHint HYBRID = new PublicKeyCredentialHint("hybrid"); + + /** + * @return An array containing all predefined values of {@link PublicKeyCredentialHint} known by + * this implementation. + */ + public static PublicKeyCredentialHint[] values() { + return new PublicKeyCredentialHint[] {SECURITY_KEY, CLIENT_DEVICE, HYBRID}; + } + + /** + * @return If value is the same as that of any of {@link #SECURITY_KEY}, {@link + * #CLIENT_DEVICE} or {@link #HYBRID}, returns that constant instance. Otherwise returns a new + * instance containing value. + * @see #valueOf(String) + */ + @JsonCreator + public static PublicKeyCredentialHint of(@NonNull String value) { + return Stream.of(values()) + .filter(v -> v.getValue().equals(value)) + .findAny() + .orElseGet(() -> new PublicKeyCredentialHint(value)); + } + + /** + * @return If name equals "SECURITY_KEY", "CLIENT_DEVICE" + * or "HYBRID", returns the constant by that name. + * @throws IllegalArgumentException if name is anything else. + * @see #of(String) + */ + public static PublicKeyCredentialHint valueOf(String name) { + switch (name) { + case "SECURITY_KEY": + return SECURITY_KEY; + case "CLIENT_DEVICE": + return CLIENT_DEVICE; + case "HYBRID": + return HYBRID; + default: + throw new IllegalArgumentException( + "No constant com.yubico.webauthn.data.PublicKeyCredentialHint." + name); + } + } +} diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialRequestOptions.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialRequestOptions.java index 4834d81a4..61c7c46d9 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialRequestOptions.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialRequestOptions.java @@ -31,6 +31,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.yubico.internal.util.CollectionUtil; import com.yubico.internal.util.JacksonCodecs; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; import lombok.Builder; @@ -66,6 +68,8 @@ public class PublicKeyCredentialRequestOptions { */ private final Long timeout; + private final List hints; + /** * Specifies the relying party identifier claimed by the caller. * @@ -112,12 +116,14 @@ public class PublicKeyCredentialRequestOptions { private PublicKeyCredentialRequestOptions( @NonNull @JsonProperty("challenge") ByteArray challenge, @JsonProperty("timeout") Long timeout, + @JsonProperty("hints") List hints, @JsonProperty("rpId") String rpId, @JsonProperty("allowCredentials") List allowCredentials, @JsonProperty("userVerification") UserVerificationRequirement userVerification, @NonNull @JsonProperty("extensions") AssertionExtensionInputs extensions) { this.challenge = challenge; this.timeout = timeout; + this.hints = hints == null ? Collections.emptyList() : Collections.unmodifiableList(hints); this.rpId = rpId; this.allowCredentials = allowCredentials == null ? null : CollectionUtil.immutableList(allowCredentials); @@ -213,6 +219,22 @@ public PublicKeyCredentialRequestOptionsBuilder timeout(long timeout) { return this.timeout(Optional.of(timeout)); } + public PublicKeyCredentialRequestOptionsBuilder hints(@NonNull String... hints) { + this.hints = Arrays.asList(hints); + return this; + } + + public PublicKeyCredentialRequestOptionsBuilder hints( + @NonNull PublicKeyCredentialHint... hints) { + return this.hints( + Arrays.stream(hints).map(PublicKeyCredentialHint::getValue).toArray(String[]::new)); + } + + public PublicKeyCredentialRequestOptionsBuilder hints(List hints) { + this.hints = hints; + return this; + } + /** * Specifies the relying party identifier claimed by the caller. * diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/Generators.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/Generators.scala index bcad72216..c9655c7db 100644 --- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/Generators.scala +++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/Generators.scala @@ -10,6 +10,7 @@ import com.yubico.webauthn.data.ClientAssertionExtensionOutputs import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs import com.yubico.webauthn.data.Generators._ import com.yubico.webauthn.data.PublicKeyCredential +import com.yubico.webauthn.data.PublicKeyCredentialHint import com.yubico.webauthn.data.UserVerificationRequirement import org.bouncycastle.asn1.x500.X500Name import org.scalacheck.Arbitrary @@ -97,12 +98,22 @@ object Generators { for { extensions <- arbitrary[Option[AssertionExtensionInputs]] timeout <- Gen.option(Gen.posNum[Long]) + hints <- + arbitrary[Option[Either[List[String], List[PublicKeyCredentialHint]]]] usernameOrUserHandle <- arbitrary[Option[Either[String, ByteArray]]] userVerification <- arbitrary[Option[UserVerificationRequirement]] } yield { val b = StartAssertionOptions.builder() extensions.foreach(b.extensions) timeout.foreach(b.timeout) + hints.foreach { + case Left(h) => { + b.hints(h.asJava) + } + case Right(h) => { + b.hints(h: _*) + } + } usernameOrUserHandle.foreach { case Left(username) => b.username(username) case Right(userHandle) => b.userHandle(userHandle) diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyStartOperationSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyStartOperationSpec.scala index fdec0b5c8..52aa8d7b7 100644 --- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyStartOperationSpec.scala +++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyStartOperationSpec.scala @@ -36,6 +36,7 @@ import com.yubico.webauthn.data.Generators.Extensions.registrationExtensionInput import com.yubico.webauthn.data.Generators._ import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions import com.yubico.webauthn.data.PublicKeyCredentialDescriptor +import com.yubico.webauthn.data.PublicKeyCredentialHint import com.yubico.webauthn.data.PublicKeyCredentialParameters import com.yubico.webauthn.data.RegistrationExtensionInputs import com.yubico.webauthn.data.RelyingPartyIdentity @@ -981,6 +982,39 @@ class RelyingPartyStartOperationSpec } } + it("allows setting the hints to a value not in the spec.") { + val pkcco = relyingParty(userId = userId).startRegistration( + StartRegistrationOptions + .builder() + .user(userId) + .hints("hej") + .build() + ) + pkcco.getHints.asScala should equal(List("hej")) + } + + it("allows setting the hints to a value in the spec.") { + val pkcco = relyingParty(userId = userId).startRegistration( + StartRegistrationOptions + .builder() + .user(userId) + .hints(PublicKeyCredentialHint.SECURITY_KEY) + .build() + ) + pkcco.getHints.asScala should equal(List("security-key")) + } + + it("allows setting the hints to empty") { + val pkcco = relyingParty(userId = userId).startRegistration( + StartRegistrationOptions + .builder() + .user(userId) + .hints("") + .build() + ) + pkcco.getHints.asScala should equal(List("")) + } + it("allows setting the timeout to empty.") { val pkcco = relyingParty(userId = userId).startRegistration( StartRegistrationOptions diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/Generators.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/Generators.scala index e1a32f6e6..0baea9816 100644 --- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/Generators.scala +++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/Generators.scala @@ -1071,19 +1071,33 @@ object Generators { arbitrary[java.util.List[PublicKeyCredentialParameters]] rp <- arbitrary[RelyingPartyIdentity] timeout <- arbitrary[Optional[java.lang.Long]] + hints <- + arbitrary[Option[Either[List[String], List[PublicKeyCredentialHint]]]] user <- arbitrary[UserIdentity] - } yield PublicKeyCredentialCreationOptions - .builder() - .rp(rp) - .user(user) - .challenge(challenge) - .pubKeyCredParams(pubKeyCredParams) - .attestation(attestation) - .authenticatorSelection(authenticatorSelection) - .excludeCredentials(excludeCredentials) - .extensions(extensions) - .timeout(timeout) - .build() + } yield { + val b = PublicKeyCredentialCreationOptions + .builder() + .rp(rp) + .user(user) + .challenge(challenge) + .pubKeyCredParams(pubKeyCredParams) + .attestation(attestation) + .authenticatorSelection(authenticatorSelection) + .excludeCredentials(excludeCredentials) + .extensions(extensions) + .timeout(timeout) + + hints.foreach { + case Left(h) => { + b.hints(h.asJava) + } + case Right(h) => { + b.hints(h: _*) + } + } + + b.build() + } ) ) @@ -1103,6 +1117,14 @@ object Generators { ) ) + implicit val arbitraryPublicKeyCredentialHint + : Arbitrary[PublicKeyCredentialHint] = Arbitrary( + Gen.oneOf( + Gen.oneOf(PublicKeyCredentialHint.values()), + Gen.alphaNumStr.map(PublicKeyCredentialHint.of), + ) + ) + implicit val arbitraryPublicKeyCredentialParameters : Arbitrary[PublicKeyCredentialParameters] = Arbitrary( halfsized( @@ -1127,6 +1149,7 @@ object Generators { extensions <- arbitrary[AssertionExtensionInputs] rpId <- arbitrary[Optional[String]] timeout <- arbitrary[Optional[java.lang.Long]] + hints <- arbitrary[Option[List[String]]] userVerification <- arbitrary[UserVerificationRequirement] } yield PublicKeyCredentialRequestOptions .builder() @@ -1135,6 +1158,7 @@ object Generators { .extensions(extensions) .rpId(rpId) .timeout(timeout) + .hints(hints.map(_.asJava).orNull) .userVerification(userVerification) .build() )