Skip to content

Commit

Permalink
Add support for PublicKeyCredentialHint
Browse files Browse the repository at this point in the history
  • Loading branch information
fdennis committed Sep 6, 2024
1 parent 37f32c3 commit af1dce0
Show file tree
Hide file tree
Showing 10 changed files with 368 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,25 @@

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.Optional;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;

/** Parameters for {@link RelyingParty#startAssertion(StartAssertionOptions)}. */
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
* Parameters for {@link RelyingParty#startAssertion(StartAssertionOptions)}.
*/
@Value
@Builder(toBuilder = true)
public class StartAssertionOptions {
public final class StartAssertionOptions {

private final String username;

Expand All @@ -51,7 +59,8 @@ public class StartAssertionOptions {
*
* <p>The default specifies no extension inputs.
*/
@NonNull @Builder.Default
@NonNull
@Builder.Default
private final AssertionExtensionInputs extensions = AssertionExtensionInputs.builder().build();

/**
Expand Down Expand Up @@ -79,6 +88,18 @@ public class StartAssertionOptions {
*/
private final Long timeout;

private final List<String> hints;

private StartAssertionOptions(String username, ByteArray userHandle, @NonNull AssertionExtensionInputs extensions, UserVerificationRequirement userVerification, Long timeout, List<String> 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.
*
Expand All @@ -96,10 +117,10 @@ public class StartAssertionOptions {
* <p>The default is empty (absent).
*
* @see <a
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">Client-side-discoverable
* credential</a>
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">Client-side-discoverable
* credential</a>
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
* href="https://passkeys.dev">passkeys.dev</a> reference
* href="https://passkeys.dev">passkeys.dev</a> reference
*/
public Optional<String> getUsername() {
return Optional.ofNullable(username);
Expand All @@ -124,10 +145,10 @@ public Optional<String> getUsername() {
* @see #getUsername()
* @see <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#user-handle">User Handle</a>
* @see <a
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">Client-side-discoverable
* credential</a>
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">Client-side-discoverable
* credential</a>
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
* href="https://passkeys.dev">passkeys.dev</a> reference
* href="https://passkeys.dev">passkeys.dev</a> reference
*/
public Optional<ByteArray> getUserHandle() {
return Optional.ofNullable(userHandle);
Expand Down Expand Up @@ -189,10 +210,10 @@ public static class StartAssertionOptionsBuilder {
* @see #userHandle(Optional)
* @see #userHandle(ByteArray)
* @see <a
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">Client-side-discoverable
* credential</a>
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">Client-side-discoverable
* credential</a>
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
* href="https://passkeys.dev">passkeys.dev</a> reference
* href="https://passkeys.dev">passkeys.dev</a> reference
*/
public StartAssertionOptionsBuilder username(@NonNull Optional<String> username) {
this.username = username.orElse(null);
Expand Down Expand Up @@ -223,10 +244,10 @@ public StartAssertionOptionsBuilder username(@NonNull Optional<String> username)
* @see #userHandle(Optional)
* @see #userHandle(ByteArray)
* @see <a
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">Client-side-discoverable
* credential</a>
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">Client-side-discoverable
* credential</a>
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
* href="https://passkeys.dev">passkeys.dev</a> reference
* href="https://passkeys.dev">passkeys.dev</a> reference
*/
public StartAssertionOptionsBuilder username(String username) {
return this.username(Optional.ofNullable(username));
Expand All @@ -253,12 +274,12 @@ public StartAssertionOptionsBuilder username(String username) {
* @see #username(Optional)
* @see #userHandle(ByteArray)
* @see <a href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#user-handle">User
* Handle</a>
* Handle</a>
* @see <a
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">Client-side-discoverable
* credential</a>
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">Client-side-discoverable
* credential</a>
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
* href="https://passkeys.dev">passkeys.dev</a> reference
* href="https://passkeys.dev">passkeys.dev</a> reference
*/
public StartAssertionOptionsBuilder userHandle(@NonNull Optional<ByteArray> userHandle) {
this.userHandle = userHandle.orElse(null);
Expand Down Expand Up @@ -289,10 +310,10 @@ public StartAssertionOptionsBuilder userHandle(@NonNull Optional<ByteArray> user
* @see #username(Optional)
* @see #userHandle(Optional)
* @see <a
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">Client-side-discoverable
* credential</a>
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">Client-side-discoverable
* credential</a>
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
* href="https://passkeys.dev">passkeys.dev</a> reference
* href="https://passkeys.dev">passkeys.dev</a> reference
*/
public StartAssertionOptionsBuilder userHandle(ByteArray userHandle) {
return this.userHandle(Optional.ofNullable(userHandle));
Expand All @@ -310,7 +331,7 @@ public StartAssertionOptionsBuilder userHandle(ByteArray userHandle) {
* <p>The default is {@link UserVerificationRequirement#PREFERRED}.
*/
public StartAssertionOptionsBuilder userVerification(
@NonNull Optional<UserVerificationRequirement> userVerification) {
@NonNull Optional<UserVerificationRequirement> userVerification) {
this.userVerification = userVerification.orElse(null);
return this;
}
Expand All @@ -327,7 +348,7 @@ public StartAssertionOptionsBuilder userVerification(
* <p>The default is {@link UserVerificationRequirement#PREFERRED}.
*/
public StartAssertionOptionsBuilder userVerification(
UserVerificationRequirement userVerification) {
UserVerificationRequirement userVerification) {
return this.userVerification(Optional.ofNullable(userVerification));
}

Expand Down Expand Up @@ -370,5 +391,19 @@ 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<String> hints) {
this.hints = hints;
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,31 +26,44 @@

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.Optional;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;

/** Parameters for {@link RelyingParty#startRegistration(StartRegistrationOptions)}. */
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

/**
* 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;
/**
* Identifiers for the user creating a credential.
*/
@NonNull
private final UserIdentity user;

/**
* 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.
*/
private final AuthenticatorSelectionCriteria authenticatorSelection;

/** Extension inputs for this registration operation. */
@NonNull @Builder.Default
/**
* Extension inputs for this registration operation.
*/
@NonNull
@Builder.Default
private final RegistrationExtensionInputs extensions =
RegistrationExtensionInputs.builder().build();
RegistrationExtensionInputs.builder().build();

/**
* The value for {@link PublicKeyCredentialCreationOptions#getTimeout()} for this registration
Expand All @@ -64,6 +77,17 @@ public class StartRegistrationOptions {
*/
private final Long timeout;

private final List<String> hints;

private StartRegistrationOptions(@NonNull UserIdentity user, AuthenticatorSelectionCriteria authenticatorSelection, @NonNull RegistrationExtensionInputs extensions, Long timeout, List<String> 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.
Expand Down Expand Up @@ -112,7 +136,7 @@ public StartRegistrationOptionsBuilder user(UserIdentity user) {
* credential, and on features that authenticator must or should support.
*/
public StartRegistrationOptionsBuilder authenticatorSelection(
@NonNull Optional<AuthenticatorSelectionCriteria> authenticatorSelection) {
@NonNull Optional<AuthenticatorSelectionCriteria> authenticatorSelection) {
return this.authenticatorSelection(authenticatorSelection.orElse(null));
}

Expand All @@ -121,7 +145,7 @@ public StartRegistrationOptionsBuilder authenticatorSelection(
* credential, and on features that authenticator must or should support.
*/
public StartRegistrationOptionsBuilder authenticatorSelection(
AuthenticatorSelectionCriteria authenticatorSelection) {
AuthenticatorSelectionCriteria authenticatorSelection) {
this.authenticatorSelection = authenticatorSelection;
return this;
}
Expand Down Expand Up @@ -157,5 +181,20 @@ public StartRegistrationOptionsBuilder timeout(@NonNull Optional<Long> 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<String> hints) {
this.hints = hints;
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@
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;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
Expand Down Expand Up @@ -94,6 +96,8 @@ public class PublicKeyCredentialCreationOptions {
*/
private final Long timeout;

private final List<String> 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
Expand Down Expand Up @@ -136,6 +140,7 @@ private PublicKeyCredentialCreationOptions(
@NonNull @JsonProperty("pubKeyCredParams")
List<PublicKeyCredentialParameters> pubKeyCredParams,
@JsonProperty("timeout") Long timeout,
@JsonProperty("hints") List<String> hints,
@JsonProperty("excludeCredentials") Set<PublicKeyCredentialDescriptor> excludeCredentials,
@JsonProperty("authenticatorSelection") AuthenticatorSelectionCriteria authenticatorSelection,
@JsonProperty("attestation") AttestationConveyancePreference attestation,
Expand All @@ -145,6 +150,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
Expand Down Expand Up @@ -317,6 +323,20 @@ 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<String> 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
Expand Down
Loading

0 comments on commit af1dce0

Please sign in to comment.