diff --git a/NEWS b/NEWS index bce20dc26..4b68b0b29 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,19 @@ +== Version 1.1.0 == + +Changed behaviours: + +* `AssertionExtensionInputsBuilder.appid(Optional)` now fails fast if the + argument is `null` +* `ClientAssertionExtensionOutputsBuilder.appid(Optional)` now fails + fast if the argument is `null` + + +New features: + +* Public API methods that take `Optional` parameters now come with + `Optional`-less aliases. + + == Version 1.0.1 == Bugfixes: diff --git a/README b/README index b30d6f92d..2f010d683 100644 --- a/README +++ b/README @@ -27,7 +27,7 @@ Maven: com.yubico webauthn-server-core - 1.0.0 + 1.1.0 compile ---------- @@ -35,7 +35,7 @@ Maven: Gradle: ---------- -compile 'com.yubico:webauthn-server-core:1.0.0' +compile 'com.yubico:webauthn-server-core:1.1.0' ---------- diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/AssertionRequest.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/AssertionRequest.java index 28dcf304b..ff753e3bd 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/AssertionRequest.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/AssertionRequest.java @@ -59,8 +59,7 @@ public class AssertionRequest { *

*/ @NonNull - @Builder.Default - private final Optional username = Optional.empty(); + private final Optional username; @JsonCreator private AssertionRequest( @@ -75,6 +74,8 @@ public static AssertionRequestBuilder.MandatoryStages builder() { } public static class AssertionRequestBuilder { + private Optional username = Optional.empty(); + public static class MandatoryStages { private final AssertionRequestBuilder builder = new AssertionRequestBuilder(); @@ -86,6 +87,31 @@ public AssertionRequestBuilder publicKeyCredentialRequestOptions(PublicKeyCreden return builder.publicKeyCredentialRequestOptions(publicKeyCredentialRequestOptions); } } + + /** + * The username of the user to authenticate, if the user has already been identified. + *

+ * If this is absent, this indicates that this is a request for an assertion by a client-side-resident + * credential, and identification of the user has been deferred until the response is received. + *

+ */ + public AssertionRequestBuilder username(@NonNull Optional username) { + this.username = username; + return this; + } + + /** + * The username of the user to authenticate, if the user has already been identified. + *

+ * If this is absent, this indicates that this is a request for an assertion by a client-side-resident + * credential, and identification of the user has been deferred until the response is received. + *

+ */ + public AssertionRequestBuilder username(@NonNull String username) { + return this.username(Optional.of(username)); + } } } diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishAssertionOptions.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishAssertionOptions.java index 59a7b7253..4e0064081 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishAssertionOptions.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishAssertionOptions.java @@ -61,14 +61,15 @@ public class FinishAssertionOptions { * @see The Token Binding Protocol Version 1.0 */ @NonNull - @Builder.Default - private final Optional callerTokenBindingId = Optional.empty(); + private final Optional callerTokenBindingId; public static FinishAssertionOptionsBuilder.MandatoryStages builder() { return new FinishAssertionOptionsBuilder.MandatoryStages(); } public static class FinishAssertionOptionsBuilder { + private Optional callerTokenBindingId = Optional.empty(); + public static class MandatoryStages { private final FinishAssertionOptionsBuilder builder = new FinishAssertionOptionsBuilder(); @@ -83,6 +84,27 @@ public FinishAssertionOptionsBuilder response(PublicKeyCredentialtoken binding ID of the connection to the + * client, if any. + * + * @see The Token Binding Protocol Version 1.0 + */ + public FinishAssertionOptionsBuilder callerTokenBindingId(@NonNull Optional callerTokenBindingId) { + this.callerTokenBindingId = callerTokenBindingId; + return this; + } + + /** + * The token binding ID of the connection to the + * client, if any. + * + * @see The Token Binding Protocol Version 1.0 + */ + public FinishAssertionOptionsBuilder callerTokenBindingId(@NonNull ByteArray callerTokenBindingId) { + return this.callerTokenBindingId(Optional.of(callerTokenBindingId)); + } } } diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishRegistrationOptions.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishRegistrationOptions.java index d2c369c28..1cc475d44 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishRegistrationOptions.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/FinishRegistrationOptions.java @@ -62,14 +62,15 @@ public class FinishRegistrationOptions { * @see The Token Binding Protocol Version 1.0 */ @NonNull - @Builder.Default - private final Optional callerTokenBindingId = Optional.empty(); + private final Optional callerTokenBindingId; public static FinishRegistrationOptionsBuilder.MandatoryStages builder() { return new FinishRegistrationOptionsBuilder.MandatoryStages(); } public static class FinishRegistrationOptionsBuilder { + private Optional callerTokenBindingId = Optional.empty(); + public static class MandatoryStages { private final FinishRegistrationOptionsBuilder builder = new FinishRegistrationOptionsBuilder(); @@ -84,5 +85,26 @@ public FinishRegistrationOptionsBuilder response(PublicKeyCredentialtoken binding ID of the connection to the + * client, if any. + * + * @see The Token Binding Protocol Version 1.0 + */ + public FinishRegistrationOptionsBuilder callerTokenBindingId(@NonNull Optional callerTokenBindingId) { + this.callerTokenBindingId = callerTokenBindingId; + return this; + } + + /** + * The token binding ID of the connection to the + * client, if any. + * + * @see The Token Binding Protocol Version 1.0 + */ + public FinishRegistrationOptionsBuilder callerTokenBindingId(@NonNull ByteArray callerTokenBindingId) { + return this.callerTokenBindingId(Optional.of(callerTokenBindingId)); + } } } 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 bbe7dc034..f5b2c7e8c 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 @@ -145,9 +145,8 @@ public class RelyingParty { * @see §10.1. FIDO AppID Extension * (appid) */ - @Builder.Default @NonNull - private final Optional appId = Optional.empty(); + private final Optional appId; /** * The argument for the {@link PublicKeyCredentialCreationOptions#getAttestation() attestation} parameter in @@ -165,9 +164,8 @@ public class RelyingParty { * @see PublicKeyCredentialCreationOptions#getAttestation() * @see §6.4. Attestation */ - @Builder.Default @NonNull - private final Optional attestationConveyancePreference = Optional.empty(); + private final Optional attestationConveyancePreference; /** * A {@link MetadataService} instance to use for looking up device attestation metadata. This matters only if {@link @@ -180,9 +178,8 @@ public class RelyingParty { * @see PublicKeyCredentialCreationOptions#getAttestation() * @see §6.4. Attestation */ - @Builder.Default @NonNull - private final Optional metadataService = Optional.empty(); + private final Optional metadataService; /** * The argument for the {@link PublicKeyCredentialCreationOptions#getPubKeyCredParams() pubKeyCredParams} parameter @@ -291,7 +288,7 @@ public PublicKeyCredentialCreationOptions startRegistration(StartRegistrationOpt .challenge(generateChallenge()) .pubKeyCredParams(preferredPubkeyParams) .excludeCredentials( - Optional.of(credentialRepository.getCredentialIdsForUsername(startRegistrationOptions.getUser().getName())) + credentialRepository.getCredentialIdsForUsername(startRegistrationOptions.getUser().getName()) ) .authenticatorSelection(startRegistrationOptions.getAuthenticatorSelection()) .extensions(startRegistrationOptions.getExtensions()) @@ -336,7 +333,7 @@ FinishRegistrationSteps _finishRegistration( public AssertionRequest startAssertion(StartAssertionOptions startAssertionOptions) { PublicKeyCredentialRequestOptionsBuilder pkcro = PublicKeyCredentialRequestOptions.builder() .challenge(generateChallenge()) - .rpId(Optional.of(identity.getId())) + .rpId(identity.getId()) .allowCredentials( startAssertionOptions.getUsername().map(un -> new ArrayList<>(credentialRepository.getCredentialIdsForUsername(un))) @@ -404,6 +401,10 @@ public static RelyingPartyBuilder.MandatoryStages builder() { } public static class RelyingPartyBuilder { + private @NonNull Optional appId = Optional.empty(); + private @NonNull Optional attestationConveyancePreference = Optional.empty(); + private @NonNull Optional metadataService = Optional.empty(); + public static class MandatoryStages { private final RelyingPartyBuilder builder = new RelyingPartyBuilder(); @@ -429,5 +430,120 @@ public RelyingPartyBuilder credentialRepository(CredentialRepository credentialR } } } + + /** + * The extension input to set for the appid extension when initiating authentication operations. + * + *

+ * If this member is set, {@link #startAssertion(StartAssertionOptions) startAssertion} will automatically set the + * appid extension input, and {@link #finishAssertion(FinishAssertionOptions) finishAssertion} will + * adjust its verification logic to also accept this AppID as an alternative to the RP ID. + *

+ * + *

+ * By default, this is not set. + *

+ * + * @see AssertionExtensionInputs#getAppid() + * @see §10.1. FIDO AppID Extension + * (appid) + */ + public RelyingPartyBuilder appId(@NonNull Optional appId) { + this.appId = appId; + return this; + } + + /** + * The extension input to set for the appid extension when initiating authentication operations. + * + *

+ * If this member is set, {@link #startAssertion(StartAssertionOptions) startAssertion} will automatically set the + * appid extension input, and {@link #finishAssertion(FinishAssertionOptions) finishAssertion} will + * adjust its verification logic to also accept this AppID as an alternative to the RP ID. + *

+ * + *

+ * By default, this is not set. + *

+ * + * @see AssertionExtensionInputs#getAppid() + * @see §10.1. FIDO AppID Extension + * (appid) + */ + public RelyingPartyBuilder appId(@NonNull AppId appId) { + return this.appId(Optional.of(appId)); + } + + /** + * The argument for the {@link PublicKeyCredentialCreationOptions#getAttestation() attestation} parameter in + * registration operations. + * + *

+ * Unless your application has a concrete policy for authenticator attestation, it is recommended to leave this + * parameter undefined. + *

+ * + *

+ * By default, this is not set. + *

+ * + * @see PublicKeyCredentialCreationOptions#getAttestation() + * @see §6.4. Attestation + */ + public RelyingPartyBuilder attestationConveyancePreference(@NonNull Optional attestationConveyancePreference) { + this.attestationConveyancePreference = attestationConveyancePreference; + return this; + } + + /** + * The argument for the {@link PublicKeyCredentialCreationOptions#getAttestation() attestation} parameter in + * registration operations. + * + *

+ * Unless your application has a concrete policy for authenticator attestation, it is recommended to leave this + * parameter undefined. + *

+ * + *

+ * By default, this is not set. + *

+ * + * @see PublicKeyCredentialCreationOptions#getAttestation() + * @see §6.4. Attestation + */ + public RelyingPartyBuilder attestationConveyancePreference(@NonNull AttestationConveyancePreference attestationConveyancePreference) { + return this.attestationConveyancePreference(Optional.of(attestationConveyancePreference)); + } + + /** + * A {@link MetadataService} instance to use for looking up device attestation metadata. This matters only if {@link + * #getAttestationConveyancePreference()} is non-empty and not set to {@link AttestationConveyancePreference#NONE}. + * + *

+ * By default, this is not set. + *

+ * + * @see PublicKeyCredentialCreationOptions#getAttestation() + * @see §6.4. Attestation + */ + public RelyingPartyBuilder metadataService(@NonNull Optional metadataService) { + this.metadataService = metadataService; + return this; + } + + /** + * A {@link MetadataService} instance to use for looking up device attestation metadata. This matters only if {@link + * #getAttestationConveyancePreference()} is non-empty and not set to {@link AttestationConveyancePreference#NONE}. + * + *

+ * By default, this is not set. + *

+ * + * @see PublicKeyCredentialCreationOptions#getAttestation() + * @see §6.4. Attestation + */ + public RelyingPartyBuilder metadataService(@NonNull MetadataService metadataService) { + return this.metadataService(Optional.of(metadataService)); + } } } 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 5aac5060b..5d958f020 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 @@ -54,8 +54,7 @@ public class StartAssertionOptions { * credential */ @NonNull - @Builder.Default - private final Optional username = Optional.empty(); + private final Optional username; /** * Extension inputs for this authentication operation. @@ -79,7 +78,68 @@ public class StartAssertionOptions { *

*/ @NonNull - @Builder.Default - private final Optional userVerification = Optional.empty(); + private final Optional userVerification; + + public static class StartAssertionOptionsBuilder { + private @NonNull Optional username = Optional.empty(); + private @NonNull Optional userVerification = Optional.empty(); + + /** + * The username of the user to authenticate, if the user has already been identified. + *

+ * If this is absent, that implies a first-factor authentication operation - meaning identification of the user is + * deferred until after receiving the response from the client. + *

+ * + *

+ * The default is empty (absent). + *

+ * + * @see Client-side-resident + * credential + */ + public StartAssertionOptionsBuilder username(@NonNull Optional username) { + this.username = username; + return this; + } + + /** + * The username of the user to authenticate, if the user has already been identified. + *

+ * If this is absent, that implies a first-factor authentication operation - meaning identification of the user is + * deferred until after receiving the response from the client. + *

+ * + *

+ * The default is empty (absent). + *

+ * + * @see Client-side-resident + * credential + */ + public StartAssertionOptionsBuilder username(@NonNull String username) { + return this.username(Optional.of(username)); + } + + /** + * The value for {@link PublicKeyCredentialRequestOptions#getUserVerification()} for this authentication operation. + *

+ * The default is {@link UserVerificationRequirement#PREFERRED}. + *

+ */ + public StartAssertionOptionsBuilder userVerification(@NonNull Optional userVerification) { + this.userVerification = userVerification; + return this; + } + /** + * The value for {@link PublicKeyCredentialRequestOptions#getUserVerification()} for this authentication operation. + *

+ * The default is {@link UserVerificationRequirement#PREFERRED}. + *

+ */ + public StartAssertionOptionsBuilder userVerification(@NonNull UserVerificationRequirement userVerification) { + return this.userVerification(Optional.of(userVerification)); + } + } } 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 086d16420..3660c86c9 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 @@ -49,8 +49,7 @@ public class StartRegistrationOptions { * Constraints on what kind of authenticator the user is allowed to use to create the credential. */ @NonNull - @Builder.Default - private final Optional authenticatorSelection = Optional.empty(); + private final Optional authenticatorSelection; /** * Extension inputs for this registration operation. @@ -64,6 +63,8 @@ public static StartRegistrationOptionsBuilder.MandatoryStages builder() { } public static class StartRegistrationOptionsBuilder { + private @NonNull Optional authenticatorSelection = Optional.empty(); + public static class MandatoryStages { private final StartRegistrationOptionsBuilder builder = new StartRegistrationOptionsBuilder(); @@ -71,6 +72,21 @@ public StartRegistrationOptionsBuilder user(UserIdentity user) { return builder.user(user); } } + + /** + * Constraints on what kind of authenticator the user is allowed to use to create the credential. + */ + public StartRegistrationOptionsBuilder authenticatorSelection(@NonNull Optional authenticatorSelection) { + this.authenticatorSelection = authenticatorSelection; + return this; + } + + /** + * Constraints on what kind of authenticator the user is allowed to use to create the credential. + */ + public StartRegistrationOptionsBuilder authenticatorSelection(@NonNull AuthenticatorSelectionCriteria authenticatorSelection) { + return this.authenticatorSelection(Optional.of(authenticatorSelection)); + } } } diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AssertionExtensionInputs.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AssertionExtensionInputs.java index f73cdbb40..3176a9301 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AssertionExtensionInputs.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AssertionExtensionInputs.java @@ -75,8 +75,8 @@ public class AssertionExtensionInputs implements ExtensionInputs { * @see §10.1. FIDO AppID Extension * (appid) */ - @Builder.Default - private final Optional appid = Optional.empty(); + @NonNull + private final Optional appid; @JsonCreator private AssertionExtensionInputs( @@ -94,4 +94,64 @@ public Set getExtensionIds() { return ids; } + public static class AssertionExtensionInputsBuilder { + private Optional appid = Optional.empty(); + + /** + * The input to the FIDO AppID Extension (appid). + * + *

+ * This extension allows WebAuthn Relying Parties that have previously registered a credential using the legacy FIDO + * JavaScript APIs to request an assertion. The FIDO APIs use an alternative identifier for Relying Parties called + * an AppID, + * and any credentials created using those APIs will be scoped to that identifier. Without this extension, they + * would need to be re-registered in order to be scoped to an RP ID. + *

+ *

+ * This extension does not allow FIDO-compatible credentials to be created. Thus, credentials created with WebAuthn + * are not backwards compatible with the FIDO JavaScript APIs. + *

+ * + *

+ * {@link RelyingParty#startAssertion(StartAssertionOptions)} sets this extension input automatically if the {@link + * RelyingParty.RelyingPartyBuilder#appId(Optional)} parameter is given when constructing the {@link RelyingParty} + * instance. + *

+ * + * @see §10.1. FIDO AppID Extension + * (appid) + */ + public AssertionExtensionInputsBuilder appid(@NonNull Optional appid) { + this.appid = appid; + return this; + } + + /** + * The input to the FIDO AppID Extension (appid). + * + *

+ * This extension allows WebAuthn Relying Parties that have previously registered a credential using the legacy FIDO + * JavaScript APIs to request an assertion. The FIDO APIs use an alternative identifier for Relying Parties called + * an AppID, + * and any credentials created using those APIs will be scoped to that identifier. Without this extension, they + * would need to be re-registered in order to be scoped to an RP ID. + *

+ *

+ * This extension does not allow FIDO-compatible credentials to be created. Thus, credentials created with WebAuthn + * are not backwards compatible with the FIDO JavaScript APIs. + *

+ * + *

+ * {@link RelyingParty#startAssertion(StartAssertionOptions)} sets this extension input automatically if the {@link + * RelyingParty.RelyingPartyBuilder#appId(Optional)} parameter is given when constructing the {@link RelyingParty} + * instance. + *

+ * + * @see §10.1. FIDO AppID Extension + * (appid) + */ + public AssertionExtensionInputsBuilder appid(@NonNull AppId appid) { + return this.appid(Optional.of(appid)); + } + } } diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorAssertionResponse.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorAssertionResponse.java index 27dcd29b2..471d4faa4 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorAssertionResponse.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorAssertionResponse.java @@ -133,6 +133,25 @@ public AuthenticatorAssertionResponseBuilder signature(ByteArray signature) { } } } + + /** + * The user handle returned from the authenticator, or empty if the authenticator did not return a user handle. See + * §6.3.3 The authenticatorGetAssertion + * Operation. + */ + public AuthenticatorAssertionResponseBuilder userHandle(@NonNull Optional userHandle) { + this.userHandle = userHandle; + return this; + } + + /** + * The user handle returned from the authenticator, or empty if the authenticator did not return a user handle. See + * §6.3.3 The authenticatorGetAssertion + * Operation. + */ + public AuthenticatorAssertionResponseBuilder userHandle(@NonNull ByteArray userHandle) { + return this.userHandle(Optional.of(userHandle)); + } } } diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorSelectionCriteria.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorSelectionCriteria.java index 957695d8c..ff874299d 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorSelectionCriteria.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorSelectionCriteria.java @@ -52,8 +52,7 @@ public class AuthenticatorSelectionCriteria { * (enum AuthenticatorAttachment). */ @NonNull - @Builder.Default - private final Optional authenticatorAttachment = Optional.empty(); + private final Optional authenticatorAttachment; /** * Describes the Relying Party's requirements regarding resident credentials. If set to true, the @@ -82,4 +81,26 @@ private AuthenticatorSelectionCriteria( this(Optional.ofNullable(authenticatorAttachment), requireResidentKey, userVerification); } + public static class AuthenticatorSelectionCriteriaBuilder { + private @NonNull Optional authenticatorAttachment = Optional.empty(); + + /** + * If present, eligible authenticators are filtered to only authenticators attached with the specified §5.4.5 Authenticator Attachment Enumeration + * (enum AuthenticatorAttachment). + */ + public AuthenticatorSelectionCriteriaBuilder authenticatorAttachment(@NonNull Optional authenticatorAttachment) { + this.authenticatorAttachment = authenticatorAttachment; + return this; + } + + /** + * If present, eligible authenticators are filtered to only authenticators attached with the specified §5.4.5 Authenticator Attachment Enumeration + * (enum AuthenticatorAttachment). + */ + public AuthenticatorSelectionCriteriaBuilder authenticatorAttachment(@NonNull AuthenticatorAttachment authenticatorAttachment) { + return this.authenticatorAttachment(Optional.of(authenticatorAttachment)); + } + } } diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/ClientAssertionExtensionOutputs.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/ClientAssertionExtensionOutputs.java index 6cb467fc9..a7b4af055 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/ClientAssertionExtensionOutputs.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/ClientAssertionExtensionOutputs.java @@ -64,8 +64,8 @@ public class ClientAssertionExtensionOutputs implements ClientExtensionOutputs { * @see §10.1. FIDO AppID Extension * (appid) */ - @Builder.Default - private final Optional appid = Optional.empty(); + @NonNull + private final Optional appid; @JsonCreator private ClientAssertionExtensionOutputs( @@ -83,4 +83,38 @@ public Set getExtensionIds() { return ids; } + public static class ClientAssertionExtensionOutputsBuilder { + private Optional appid = Optional.empty(); + + /** + * The output from the FIDO AppID Extension (appid). + * + *

+ * This value should be ignored because its behaviour is underspecified, see: https://github.com/w3c/webauthn/issues/1034. + *

+ * + * @see §10.1. FIDO AppID Extension + * (appid) + */ + public ClientAssertionExtensionOutputsBuilder appid(@NonNull Optional appid) { + this.appid = appid; + return this; + } + + /** + * The output from the FIDO AppID Extension (appid). + * + *

+ * This value should be ignored because its behaviour is underspecified, see: https://github.com/w3c/webauthn/issues/1034. + *

+ * + * @see §10.1. FIDO AppID Extension + * (appid) + */ + public ClientAssertionExtensionOutputsBuilder appid(boolean appid) { + return this.appid(Optional.of(appid)); + } + } } 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 ce3e45715..8abadc09b 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 @@ -87,8 +87,7 @@ public class PublicKeyCredentialCreationOptions { * and MAY be overridden by the client. */ @NonNull - @Builder.Default - private final Optional timeout = Optional.empty(); + private final Optional timeout; /** * Intended for use by Relying Parties that wish to limit the creation of multiple credentials for the same account @@ -96,16 +95,14 @@ public class PublicKeyCredentialCreationOptions { * an authenticator that also contains one of the credentials enumerated in this parameter. */ @NonNull - @Builder.Default - private final Optional> excludeCredentials = Optional.empty(); + private final Optional> excludeCredentials; /** * Intended for use by Relying Parties that wish to select the appropriate authenticators to participate in the * create() operation. */ @NonNull - @Builder.Default - private final Optional authenticatorSelection = Optional.empty(); + private final Optional authenticatorSelection; /** * Intended for use by Relying Parties that wish to express their preference for attestation conveyance. The default @@ -182,6 +179,10 @@ public static PublicKeyCredentialCreationOptionsBuilder.MandatoryStages builder( } public static class PublicKeyCredentialCreationOptionsBuilder { + private @NonNull Optional timeout = Optional.empty(); + private @NonNull Optional> excludeCredentials = Optional.empty(); + private @NonNull Optional authenticatorSelection = Optional.empty(); + public static class MandatoryStages { private PublicKeyCredentialCreationOptionsBuilder builder = new PublicKeyCredentialCreationOptionsBuilder(); @@ -222,6 +223,59 @@ public PublicKeyCredentialCreationOptionsBuilder pubKeyCredParams(List timeout) { + this.timeout = timeout; + return this; + } + + /** + * A time, in milliseconds, that the caller is willing to wait for the call to complete. This is treated as a hint, + * and MAY be overridden by the client. + */ + public PublicKeyCredentialCreationOptionsBuilder timeout(long timeout) { + return this.timeout(Optional.of(timeout)); + } + + /** + * 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 new credential would be created on + * an authenticator that also contains one of the credentials enumerated in this parameter. + */ + public PublicKeyCredentialCreationOptionsBuilder excludeCredentials(@NonNull Optional> excludeCredentials) { + this.excludeCredentials = excludeCredentials; + 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 the new credential would be created on + * an authenticator that also contains one of the credentials enumerated in this parameter. + */ + public PublicKeyCredentialCreationOptionsBuilder excludeCredentials(@NonNull Set excludeCredentials) { + return this.excludeCredentials(Optional.of(excludeCredentials)); + } + + /** + * Intended for use by Relying Parties that wish to select the appropriate authenticators to participate in the + * create() operation. + */ + public PublicKeyCredentialCreationOptionsBuilder authenticatorSelection(@NonNull Optional authenticatorSelection) { + this.authenticatorSelection = authenticatorSelection; + return this; + } + + /** + * Intended for use by Relying Parties that wish to select the appropriate authenticators to participate in the + * create() operation. + */ + public PublicKeyCredentialCreationOptionsBuilder authenticatorSelection(@NonNull AuthenticatorSelectionCriteria authenticatorSelection) { + return this.authenticatorSelection(Optional.of(authenticatorSelection)); + } } } diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialDescriptor.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialDescriptor.java index 14038fb67..834be2bde 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialDescriptor.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/PublicKeyCredentialDescriptor.java @@ -66,8 +66,7 @@ public class PublicKeyCredentialDescriptor implements Comparable> transports = Optional.empty(); + private final Optional> transports; private PublicKeyCredentialDescriptor( @NonNull PublicKeyCredentialType type, @@ -118,6 +117,8 @@ public static PublicKeyCredentialDescriptorBuilder.MandatoryStages builder() { } public static class PublicKeyCredentialDescriptorBuilder { + private Optional> transports = Optional.empty(); + public static class MandatoryStages { private PublicKeyCredentialDescriptorBuilder builder = new PublicKeyCredentialDescriptorBuilder(); @@ -125,5 +126,22 @@ public PublicKeyCredentialDescriptorBuilder id(ByteArray id) { return builder.id(id); } } + + /** + * An OPTIONAL hint as to how the client might communicate with the managing authenticator of the public key + * credential the caller is referring to. + */ + public PublicKeyCredentialDescriptorBuilder transports(@NonNull Optional> transports) { + this.transports = transports; + return this; + } + + /** + * An OPTIONAL hint as to how the client might communicate with the managing authenticator of the public key + * credential the caller is referring to. + */ + public PublicKeyCredentialDescriptorBuilder transports(@NonNull Set transports) { + return this.transports(Optional.of(transports)); + } } } 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 763a208d7..61f3fe1f7 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 @@ -63,8 +63,7 @@ public class PublicKeyCredentialRequestOptions { *

*/ @NonNull - @Builder.Default - private final Optional timeout = Optional.empty(); + private final Optional timeout; /** * Specifies the relying party identifier claimed by the caller. @@ -73,8 +72,7 @@ public class PublicKeyCredentialRequestOptions { *

*/ @NonNull - @Builder.Default - private final Optional rpId = Optional.empty(); + private final Optional rpId; /** * A list of {@link PublicKeyCredentialDescriptor} objects representing public key credentials acceptable to the @@ -82,8 +80,7 @@ public class PublicKeyCredentialRequestOptions { * credential, and so on down the list). */ @NonNull - @Builder.Default - private final Optional> allowCredentials = Optional.empty(); + private final Optional> allowCredentials; /** * Describes the Relying Party's requirements regarding user @@ -147,6 +144,10 @@ public static PublicKeyCredentialRequestOptionsBuilder.MandatoryStages builder() } public static class PublicKeyCredentialRequestOptionsBuilder { + private @NonNull Optional timeout = Optional.empty(); + private @NonNull Optional rpId = Optional.empty(); + private @NonNull Optional> allowCredentials = Optional.empty(); + public static class MandatoryStages { private PublicKeyCredentialRequestOptionsBuilder builder = new PublicKeyCredentialRequestOptionsBuilder(); @@ -154,5 +155,66 @@ public PublicKeyCredentialRequestOptionsBuilder challenge(ByteArray challenge) { return builder.challenge(challenge); } } + + /** + * Specifies a time, in milliseconds, that the caller is willing to wait for the call to complete. + *

+ * This is treated as a hint, and MAY be overridden by the client. + *

+ */ + public PublicKeyCredentialRequestOptionsBuilder timeout(@NonNull Optional timeout) { + this.timeout = timeout; + return this; + } + + /** + * Specifies a time, in milliseconds, that the caller is willing to wait for the call to complete. + *

+ * This is treated as a hint, and MAY be overridden by the client. + *

+ */ + public PublicKeyCredentialRequestOptionsBuilder timeout(long timeout) { + return this.timeout(Optional.of(timeout)); + } + + /** + * Specifies the relying party identifier claimed by the caller. + *

+ * If omitted, its value will be set by the client. + *

+ */ + public PublicKeyCredentialRequestOptionsBuilder rpId(@NonNull Optional rpId) { + this.rpId = rpId; + return this; + } + + /** + * Specifies the relying party identifier claimed by the caller. + *

+ * If omitted, its value will be set by the client. + *

+ */ + public PublicKeyCredentialRequestOptionsBuilder rpId(@NonNull String rpId) { + return this.rpId(Optional.of(rpId)); + } + + /** + * A list of {@link PublicKeyCredentialDescriptor} objects representing public key credentials acceptable to the + * caller, in descending order of the caller’s preference (the first item in the list is the most preferred + * credential, and so on down the list). + */ + public PublicKeyCredentialRequestOptionsBuilder allowCredentials(@NonNull Optional> allowCredentials) { + this.allowCredentials = allowCredentials; + return this; + } + + /** + * A list of {@link PublicKeyCredentialDescriptor} objects representing public key credentials acceptable to the + * caller, in descending order of the caller’s preference (the first item in the list is the most preferred + * credential, and so on down the list). + */ + public PublicKeyCredentialRequestOptionsBuilder allowCredentials(@NonNull List allowCredentials) { + return this.allowCredentials(Optional.of(allowCredentials)); + } } } diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/RelyingPartyIdentity.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/RelyingPartyIdentity.java index 318340622..effb9a6a1 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/RelyingPartyIdentity.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/RelyingPartyIdentity.java @@ -68,10 +68,19 @@ public class RelyingPartyIdentity implements PublicKeyCredentialEntity { @NonNull private final String id; + /** + * A URL which resolves to an image associated with the entity. For example, this could be the Relying Party's + * logo. + * + *

This URL MUST be an a priori authenticated URL. Authenticators MUST accept and store a + * 128-byte minimum length for an icon member’s value. Authenticators MAY ignore an icon member’s value if its + * length is greater than 128 bytes. The URL’s scheme MAY be "data" to avoid fetches of the URL, at the cost of + * needing more storage. + *

+ */ @NonNull - @Builder.Default @Getter(onMethod = @__({ @Override })) - private final Optional icon = Optional.empty(); + private final Optional icon; @JsonCreator private RelyingPartyIdentity( @@ -87,6 +96,8 @@ public static RelyingPartyIdentityBuilder.MandatoryStages builder() { } public static class RelyingPartyIdentityBuilder { + private @NonNull Optional icon = Optional.empty(); + public static class MandatoryStages { private RelyingPartyIdentityBuilder builder = new RelyingPartyIdentityBuilder(); @@ -101,6 +112,35 @@ public RelyingPartyIdentityBuilder name(String name) { } } } + + /** + * A URL which resolves to an image associated with the entity. For example, this could be the Relying Party's + * logo. + * + *

This URL MUST be an a priori authenticated URL. Authenticators MUST accept and store a + * 128-byte minimum length for an icon member’s value. Authenticators MAY ignore an icon member’s value if its + * length is greater than 128 bytes. The URL’s scheme MAY be "data" to avoid fetches of the URL, at the cost of + * needing more storage. + *

+ */ + public RelyingPartyIdentityBuilder icon(@NonNull Optional icon) { + this.icon = icon; + return this; + } + + /** + * A URL which resolves to an image associated with the entity. For example, this could be the Relying Party's + * logo. + * + *

This URL MUST be an a priori authenticated URL. Authenticators MUST accept and store a + * 128-byte minimum length for an icon member’s value. Authenticators MAY ignore an icon member’s value if its + * length is greater than 128 bytes. The URL’s scheme MAY be "data" to avoid fetches of the URL, at the cost of + * needing more storage. + *

+ */ + public RelyingPartyIdentityBuilder icon(@NonNull URL icon) { + return this.icon(Optional.of(icon)); + } } } diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/UserIdentity.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/UserIdentity.java index 7cf55949f..2eea7716c 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/UserIdentity.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/UserIdentity.java @@ -113,10 +113,18 @@ public class UserIdentity implements PublicKeyCredentialEntity { @NonNull private final ByteArray id; + /** + * A URL which resolves to an image associated with the entity. For example, this could be the user’s avatar. + * + *

This URL MUST be an a priori authenticated URL. Authenticators MUST accept and store a + * 128-byte minimum length for an icon member’s value. Authenticators MAY ignore an icon member’s value if its + * length is greater than 128 bytes. The URL’s scheme MAY be "data" to avoid fetches of the URL, at the cost of + * needing more storage. + *

+ */ @NonNull - @Builder.Default @Getter(onMethod = @__({ @Override })) - private final Optional icon = Optional.empty(); + private final Optional icon; @JsonCreator private UserIdentity( @@ -133,6 +141,8 @@ public static UserIdentityBuilder.MandatoryStages builder() { } public static class UserIdentityBuilder { + private @NonNull Optional icon = Optional.empty(); + public static class MandatoryStages { private UserIdentityBuilder builder = new UserIdentityBuilder(); @@ -155,6 +165,33 @@ public UserIdentityBuilder id(ByteArray id) { } } + + /** + * A URL which resolves to an image associated with the entity. For example, this could be the user’s avatar. + * + *

This URL MUST be an a priori authenticated URL. Authenticators MUST accept and store a + * 128-byte minimum length for an icon member’s value. Authenticators MAY ignore an icon member’s value if its + * length is greater than 128 bytes. The URL’s scheme MAY be "data" to avoid fetches of the URL, at the cost of + * needing more storage. + *

+ */ + public UserIdentityBuilder icon(@NonNull Optional icon) { + this.icon = icon; + return this; + } + + /** + * A URL which resolves to an image associated with the entity. For example, this could be the user’s avatar. + * + *

This URL MUST be an a priori authenticated URL. Authenticators MUST accept and store a + * 128-byte minimum length for an icon member’s value. Authenticators MAY ignore an icon member’s value if its + * length is greater than 128 bytes. The URL’s scheme MAY be "data" to avoid fetches of the URL, at the cost of + * needing more storage. + *

+ */ + public UserIdentityBuilder icon(@NonNull URL icon) { + return this.icon(Optional.of(icon)); + } } } diff --git a/webauthn-server-core/src/test/java/com/yubico/webauthn/FinishAssertionOptionsTest.java b/webauthn-server-core/src/test/java/com/yubico/webauthn/FinishAssertionOptionsTest.java new file mode 100644 index 000000000..85a3fe6ba --- /dev/null +++ b/webauthn-server-core/src/test/java/com/yubico/webauthn/FinishAssertionOptionsTest.java @@ -0,0 +1,26 @@ +package com.yubico.webauthn; + +import com.yubico.webauthn.data.ByteArray; +import com.yubico.webauthn.data.exception.HexException; +import java.util.Optional; +import org.junit.Test; + +public class FinishAssertionOptionsTest { + + @Test(expected = NullPointerException.class) + public void itHasANonOptionalCallerTokenBindingIdMethod() throws HexException { + FinishAssertionOptions.builder() + .request(null) + .response(null) + .callerTokenBindingId(ByteArray.fromHex("aa")); + } + + @Test(expected = NullPointerException.class) + public void itHasAnOptionalCallerTokenBindingIdMethod() throws HexException { + FinishAssertionOptions.builder() + .request(null) + .response(null) + .callerTokenBindingId(Optional.of(ByteArray.fromHex("aa"))); + } + +} diff --git a/webauthn-server-core/src/test/java/com/yubico/webauthn/FinishRegistrationOptionsTest.java b/webauthn-server-core/src/test/java/com/yubico/webauthn/FinishRegistrationOptionsTest.java new file mode 100644 index 000000000..c6fe88494 --- /dev/null +++ b/webauthn-server-core/src/test/java/com/yubico/webauthn/FinishRegistrationOptionsTest.java @@ -0,0 +1,26 @@ +package com.yubico.webauthn; + +import com.yubico.webauthn.data.ByteArray; +import com.yubico.webauthn.data.exception.HexException; +import java.util.Optional; +import org.junit.Test; + +public class FinishRegistrationOptionsTest { + + @Test(expected = NullPointerException.class) + public void itHasANonOptionalCallerTokenBindingIdMethod() throws HexException { + FinishRegistrationOptions.builder() + .request(null) + .response(null) + .callerTokenBindingId(ByteArray.fromHex("aa")); + } + + @Test(expected = NullPointerException.class) + public void itHasAnOptionalCallerTokenBindingIdMethod() throws HexException { + FinishAssertionOptions.builder() + .request(null) + .response(null) + .callerTokenBindingId(Optional.of(ByteArray.fromHex("aa"))); + } + +} diff --git a/webauthn-server-core/src/test/java/com/yubico/webauthn/RelyingPartyTest.java b/webauthn-server-core/src/test/java/com/yubico/webauthn/RelyingPartyTest.java new file mode 100644 index 000000000..b4382b4ac --- /dev/null +++ b/webauthn-server-core/src/test/java/com/yubico/webauthn/RelyingPartyTest.java @@ -0,0 +1,41 @@ +package com.yubico.webauthn; + +import com.yubico.webauthn.attestation.Attestation; +import com.yubico.webauthn.attestation.MetadataService; +import com.yubico.webauthn.data.AttestationConveyancePreference; +import com.yubico.webauthn.extension.appid.AppId; +import com.yubico.webauthn.extension.appid.InvalidAppIdException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import org.junit.Test; + +public class RelyingPartyTest { + + @Test(expected = NullPointerException.class) + public void itHasTheseBuilderMethods() throws InvalidAppIdException { + + final MetadataService metadataService = new MetadataService() { + @Override public Attestation getAttestation(List attestationCertificateChain) throws CertificateEncodingException { return null; } + }; + + RelyingParty.builder() + .identity(null) + .credentialRepository(null) + .origins(Collections.emptySet()) + .appId(new AppId("https://example.com")) + .appId(Optional.of(new AppId("https://example.com"))) + .attestationConveyancePreference(AttestationConveyancePreference.DIRECT) + .attestationConveyancePreference(Optional.of(AttestationConveyancePreference.DIRECT)) + .metadataService(metadataService) + .metadataService(Optional.of(metadataService)) + .preferredPubkeyParams(Collections.emptyList()) + .allowUnrequestedExtensions(true) + .allowUntrustedAttestation(true) + .validateSignatureCounter(true) + ; + } + +} diff --git a/webauthn-server-core/src/test/java/com/yubico/webauthn/StartAssertionOptionsTest.java b/webauthn-server-core/src/test/java/com/yubico/webauthn/StartAssertionOptionsTest.java new file mode 100644 index 000000000..7cec0b344 --- /dev/null +++ b/webauthn-server-core/src/test/java/com/yubico/webauthn/StartAssertionOptionsTest.java @@ -0,0 +1,22 @@ +package com.yubico.webauthn; + +import com.yubico.webauthn.data.AuthenticatorSelectionCriteria; +import com.yubico.webauthn.data.ByteArray; +import com.yubico.webauthn.data.RegistrationExtensionInputs; +import com.yubico.webauthn.data.UserIdentity; +import java.util.Optional; +import org.junit.Test; + +public class StartAssertionOptionsTest { + + @Test + public void itHasTheseBuilderMethods() { + StartRegistrationOptions.builder() + .user(UserIdentity.builder().name("").displayName("").id(new ByteArray(new byte[]{})).build()) + .authenticatorSelection(AuthenticatorSelectionCriteria.builder().build()) + .authenticatorSelection(Optional.of(AuthenticatorSelectionCriteria.builder().build())) + .extensions(RegistrationExtensionInputs.builder().build()) + .build(); + } + +} diff --git a/webauthn-server-core/src/test/java/com/yubico/webauthn/StartRegistrationOptionsTest.java b/webauthn-server-core/src/test/java/com/yubico/webauthn/StartRegistrationOptionsTest.java new file mode 100644 index 000000000..fa324a702 --- /dev/null +++ b/webauthn-server-core/src/test/java/com/yubico/webauthn/StartRegistrationOptionsTest.java @@ -0,0 +1,20 @@ +package com.yubico.webauthn; + +import com.yubico.webauthn.data.AuthenticatorSelectionCriteria; +import com.yubico.webauthn.data.RegistrationExtensionInputs; +import java.util.Optional; +import org.junit.Test; + +public class StartRegistrationOptionsTest { + + @Test(expected = NullPointerException.class) + public void itHasTheseBuilderMethods() { + StartRegistrationOptions.builder() + .user(null) + .authenticatorSelection(AuthenticatorSelectionCriteria.builder().build()) + .authenticatorSelection(Optional.of(AuthenticatorSelectionCriteria.builder().build())) + .extensions(RegistrationExtensionInputs.builder().build()) + .build(); + } + +} diff --git a/webauthn-server-core/src/test/java/com/yubico/webauthn/data/AssertionExtensionInputsTest.java b/webauthn-server-core/src/test/java/com/yubico/webauthn/data/AssertionExtensionInputsTest.java new file mode 100644 index 000000000..cfa211ce7 --- /dev/null +++ b/webauthn-server-core/src/test/java/com/yubico/webauthn/data/AssertionExtensionInputsTest.java @@ -0,0 +1,18 @@ +package com.yubico.webauthn.data; + +import com.yubico.webauthn.extension.appid.AppId; +import com.yubico.webauthn.extension.appid.InvalidAppIdException; +import java.util.Optional; +import org.junit.Test; + +public class AssertionExtensionInputsTest { + + @Test + public void itHasTheseBuilderMethods() throws InvalidAppIdException { + AssertionExtensionInputs.builder() + .appid(new AppId("https://example.com")) + .appid(Optional.of(new AppId("https://example.com"))) + .build(); + } + +} diff --git a/webauthn-server-core/src/test/java/com/yubico/webauthn/data/AuthenticatorSelectionCriteriaTest.java b/webauthn-server-core/src/test/java/com/yubico/webauthn/data/AuthenticatorSelectionCriteriaTest.java new file mode 100644 index 000000000..006dbcd2e --- /dev/null +++ b/webauthn-server-core/src/test/java/com/yubico/webauthn/data/AuthenticatorSelectionCriteriaTest.java @@ -0,0 +1,18 @@ +package com.yubico.webauthn.data; + +import java.util.Optional; +import org.junit.Test; + +public class AuthenticatorSelectionCriteriaTest { + + @Test + public void itHasTheseBuilderMethods() { + AuthenticatorSelectionCriteria.builder() + .authenticatorAttachment(AuthenticatorAttachment.CROSS_PLATFORM) + .authenticatorAttachment(Optional.of(AuthenticatorAttachment.CROSS_PLATFORM)) + .requireResidentKey(false) + .userVerification(UserVerificationRequirement.PREFERRED) + .build(); + } + +} diff --git a/webauthn-server-core/src/test/java/com/yubico/webauthn/data/ClientAssertionExtensionOutputsTest.java b/webauthn-server-core/src/test/java/com/yubico/webauthn/data/ClientAssertionExtensionOutputsTest.java new file mode 100644 index 000000000..84f3b9d1e --- /dev/null +++ b/webauthn-server-core/src/test/java/com/yubico/webauthn/data/ClientAssertionExtensionOutputsTest.java @@ -0,0 +1,16 @@ +package com.yubico.webauthn.data; + +import java.util.Optional; +import org.junit.Test; + +public class ClientAssertionExtensionOutputsTest { + + @Test + public void itHasTheseBuilderMethods() { + ClientAssertionExtensionOutputs.builder() + .appid(false) + .appid(Optional.of(false)) + .build(); + } + +} diff --git a/webauthn-server-core/src/test/java/com/yubico/webauthn/data/PublicKeyCredentialCreationOptionsTest.java b/webauthn-server-core/src/test/java/com/yubico/webauthn/data/PublicKeyCredentialCreationOptionsTest.java new file mode 100644 index 000000000..848c1c5f1 --- /dev/null +++ b/webauthn-server-core/src/test/java/com/yubico/webauthn/data/PublicKeyCredentialCreationOptionsTest.java @@ -0,0 +1,27 @@ +package com.yubico.webauthn.data; + +import java.util.Collections; +import java.util.Optional; +import org.junit.Test; + +public class PublicKeyCredentialCreationOptionsTest { + + @Test(expected = NullPointerException.class) + public void itHasTheseBuilderMethods() { + PublicKeyCredentialCreationOptions.builder() + .rp(null) + .user(null) + .challenge(null) + .pubKeyCredParams(null) + .attestation(null) + .authenticatorSelection(AuthenticatorSelectionCriteria.builder().build()) + .authenticatorSelection(Optional.of(AuthenticatorSelectionCriteria.builder().build())) + .excludeCredentials(Collections.emptySet()) + .excludeCredentials(Optional.of(Collections.emptySet())) + .extensions(RegistrationExtensionInputs.builder().build()) + .timeout(0) + .timeout(Optional.of(0L)) + ; + } + +} diff --git a/webauthn-server-core/src/test/java/com/yubico/webauthn/data/PublicKeyCredentialDescriptorTest.java b/webauthn-server-core/src/test/java/com/yubico/webauthn/data/PublicKeyCredentialDescriptorTest.java new file mode 100644 index 000000000..f9fce0c3e --- /dev/null +++ b/webauthn-server-core/src/test/java/com/yubico/webauthn/data/PublicKeyCredentialDescriptorTest.java @@ -0,0 +1,18 @@ +package com.yubico.webauthn.data; + +import java.util.Collections; +import java.util.Optional; +import org.junit.Test; + +public class PublicKeyCredentialDescriptorTest { + + @Test(expected = NullPointerException.class) + public void itHasTheseBuilderMethods() { + PublicKeyCredentialDescriptor.builder() + .id(null) + .transports(Collections.emptySet()) + .transports(Optional.of(Collections.emptySet())) + ; + } + +} diff --git a/webauthn-server-core/src/test/java/com/yubico/webauthn/data/PublicKeyCredentialRequestOptionsTest.java b/webauthn-server-core/src/test/java/com/yubico/webauthn/data/PublicKeyCredentialRequestOptionsTest.java new file mode 100644 index 000000000..8fc2c8d20 --- /dev/null +++ b/webauthn-server-core/src/test/java/com/yubico/webauthn/data/PublicKeyCredentialRequestOptionsTest.java @@ -0,0 +1,23 @@ +package com.yubico.webauthn.data; + +import java.util.Collections; +import java.util.Optional; +import org.junit.Test; + +public class PublicKeyCredentialRequestOptionsTest { + + @Test(expected = NullPointerException.class) + public void itHasTheseBuilderMethods() { + PublicKeyCredentialRequestOptions.builder() + .challenge(null) + .timeout(0) + .timeout(Optional.of(0L)) + .rpId("") + .rpId(Optional.of("")) + .allowCredentials(Collections.emptyList()) + .allowCredentials(Optional.of(Collections.emptyList())) + .userVerification(UserVerificationRequirement.PREFERRED) + .extensions(AssertionExtensionInputs.builder().build()) + ; + } +} diff --git a/webauthn-server-core/src/test/java/com/yubico/webauthn/data/RelyingPartyIdentityTest.java b/webauthn-server-core/src/test/java/com/yubico/webauthn/data/RelyingPartyIdentityTest.java new file mode 100644 index 000000000..d950c5e8c --- /dev/null +++ b/webauthn-server-core/src/test/java/com/yubico/webauthn/data/RelyingPartyIdentityTest.java @@ -0,0 +1,20 @@ +package com.yubico.webauthn.data; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Optional; +import org.junit.Test; + +public class RelyingPartyIdentityTest { + + @Test + public void itHasTheseBuilderMethods() throws MalformedURLException { + RelyingPartyIdentity.builder() + .id("") + .name("") + .icon(new URL("https://example.com")) + .icon(Optional.of(new URL("https://example.com"))) + ; + } + +} diff --git a/webauthn-server-core/src/test/java/com/yubico/webauthn/data/UserIdentityTest.java b/webauthn-server-core/src/test/java/com/yubico/webauthn/data/UserIdentityTest.java new file mode 100644 index 000000000..2bb16fc83 --- /dev/null +++ b/webauthn-server-core/src/test/java/com/yubico/webauthn/data/UserIdentityTest.java @@ -0,0 +1,21 @@ +package com.yubico.webauthn.data; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Optional; +import org.junit.Test; + +public class UserIdentityTest { + + @Test + public void itHasTheseBuilderMethods() throws MalformedURLException { + UserIdentity.builder() + .name("") + .displayName("") + .id(new ByteArray(new byte[]{})) + .icon(new URL("https://example.com")) + .icon(Optional.of(new URL("https://example.com"))) + ; + } + +} 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 27d559b2e..b332a2326 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 @@ -147,7 +147,7 @@ class RelyingPartyAssertionSpec extends FunSpec with Matchers with GeneratorDriv .publicKeyCredentialRequestOptions( PublicKeyCredentialRequestOptions.builder() .challenge(challenge) - .rpId(Some(rpId.getId).asJava) + .rpId(rpId.getId) .allowCredentials(allowCredentials.asJava) .userVerification(userVerificationRequirement) .extensions(requestedExtensions) @@ -163,7 +163,7 @@ class RelyingPartyAssertionSpec extends FunSpec with Matchers with GeneratorDriv .authenticatorData(if (authenticatorData == null) null else authenticatorData) .clientDataJSON(if (clientDataJsonBytes == null) null else clientDataJsonBytes) .signature(if (signature == null) null else signature) - .userHandle(Optional.of(userHandleForResponse)) + .userHandle(userHandleForResponse) .build() ) .clientExtensionResults(clientExtensionResults) @@ -211,7 +211,7 @@ class RelyingPartyAssertionSpec extends FunSpec with Matchers with GeneratorDriv .credentialRepository(emptyCredentialRepository) .build() val request1 = rp.startAssertion(StartAssertionOptions.builder().build()) - val request2 = rp.startAssertion(StartAssertionOptions.builder().userVerification(None.asJava).build()) + val request2 = rp.startAssertion(StartAssertionOptions.builder().userVerification(Optional.empty[UserVerificationRequirement]).build()) request1.getPublicKeyCredentialRequestOptions.getUserVerification should equal (default) request2.getPublicKeyCredentialRequestOptions.getUserVerification should equal (default) @@ -224,7 +224,7 @@ class RelyingPartyAssertionSpec extends FunSpec with Matchers with GeneratorDriv .build() forAll { uv: UserVerificationRequirement => - val request = rp.startAssertion(StartAssertionOptions.builder().userVerification(Some(uv).asJava).build()) + val request = rp.startAssertion(StartAssertionOptions.builder().userVerification(uv).build()) request.getPublicKeyCredentialRequestOptions.getUserVerification should equal (uv) } @@ -780,7 +780,7 @@ class RelyingPartyAssertionSpec extends FunSpec with Matchers with GeneratorDriv describe("client extension outputs in clientExtensionResults are as expected, considering the client extension input values that were given as the extensions option in the get() call. In particular, any extension identifier values in the clientExtensionResults MUST be also be present as extension identifier values in the extensions member of options, i.e., no extensions are present that were not requested. In the general case, the meaning of \"are as expected\" is specific to the Relying Party and which extensions are in use.") { it("Fails if clientExtensionResults is not a subset of the extensions requested by the Relying Party.") { val extensionInputs = AssertionExtensionInputs.builder().build() - val clientExtensionOutputs = ClientAssertionExtensionOutputs.builder().appid(Optional.of(true)).build() + val clientExtensionOutputs = ClientAssertionExtensionOutputs.builder().appid(true).build() // forAll(unrequestedAssertionExtensions, minSuccessful(1)) { case (extensionInputs, clientExtensionOutputs) => val steps = finishAssertion( 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 a00a2468e..df7339445 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 @@ -121,7 +121,7 @@ class RelyingPartyStartOperationSpec extends FunSpec with Matchers with Generato forAll { credentials: Set[PublicKeyCredentialDescriptor] => val rp = relyingParty(credentials = credentials) val result = rp.startAssertion(StartAssertionOptions.builder() - .username(Some(userId.getName).asJava) + .username(userId.getName) .build() ) @@ -144,7 +144,7 @@ class RelyingPartyStartOperationSpec extends FunSpec with Matchers with Generato forAll { appId: Optional[AppId] => val rp = relyingParty(appId = appId) val result = rp.startAssertion(StartAssertionOptions.builder() - .username(Some(userId.getName).asJava) + .username(userId.getName) .build() ) diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyUserIdentificationSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyUserIdentificationSpec.scala index 49d47cc99..0cd5714df 100644 --- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyUserIdentificationSpec.scala +++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyUserIdentificationSpec.scala @@ -162,7 +162,7 @@ class RelyingPartyUserIdentificationSpec extends FunSpec with Matchers { it("succeeds for the default test case if a username was given.") { val request = rp.startAssertion(StartAssertionOptions.builder() - .username(Optional.of(Defaults.username)) + .username(Defaults.username) .build()) val deterministicRequest = request.toBuilder.publicKeyCredentialRequestOptions( diff --git a/webauthn-server-demo/src/main/java/demo/webauthn/WebAuthnServer.java b/webauthn-server-demo/src/main/java/demo/webauthn/WebAuthnServer.java index e4c2fd70b..c1c157032 100644 --- a/webauthn-server-demo/src/main/java/demo/webauthn/WebAuthnServer.java +++ b/webauthn-server-demo/src/main/java/demo/webauthn/WebAuthnServer.java @@ -209,10 +209,10 @@ public Either startRegistration( .id(generateRandom(32)) .build() ) - .authenticatorSelection(Optional.of(AuthenticatorSelectionCriteria.builder() + .authenticatorSelection(AuthenticatorSelectionCriteria.builder() .requireResidentKey(requireResidentKey) .build() - )) + ) .build() ) ); @@ -250,10 +250,10 @@ public Either, AssertionRequest> startAddCredential( rp.startRegistration( StartRegistrationOptions.builder() .user(existingUser) - .authenticatorSelection(Optional.of(AuthenticatorSelectionCriteria.builder() + .authenticatorSelection(AuthenticatorSelectionCriteria.builder() .requireResidentKey(requireResidentKey) .build() - )) + ) .build() ) ); diff --git a/webauthn-server-demo/src/test/scala/demo/webauthn/WebAuthnServerSpec.scala b/webauthn-server-demo/src/test/scala/demo/webauthn/WebAuthnServerSpec.scala index 073f18a30..d403aaddc 100644 --- a/webauthn-server-demo/src/test/scala/demo/webauthn/WebAuthnServerSpec.scala +++ b/webauthn-server-demo/src/test/scala/demo/webauthn/WebAuthnServerSpec.scala @@ -154,7 +154,7 @@ class WebAuthnServerSpec extends FunSpec with Matchers { .publicKeyCredentialRequestOptions( PublicKeyCredentialRequestOptions.builder() .challenge(challenge) - .rpId(Some(rpId.getId).asJava) + .rpId(rpId.getId) .build() ) .username(Some(testData.userId.getName).asJava)