diff --git a/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/SupportedCtapOptions.java b/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/SupportedCtapOptions.java
index cda8898e9..88fa473b0 100644
--- a/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/SupportedCtapOptions.java
+++ b/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/SupportedCtapOptions.java
@@ -1,160 +1,226 @@
package com.yubico.fido.metadata;
import com.fasterxml.jackson.annotation.JsonAlias;
-import lombok.AccessLevel;
-import lombok.AllArgsConstructor;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Value;
-import lombok.extern.jackson.Jacksonized;
/**
* A fixed-keys map of CTAP2 option names to Boolean values representing whether an authenticator
* supports the respective option.
*
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
@Value
@Builder
-@Jacksonized
-@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class SupportedCtapOptions {
/**
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
- @Builder.Default boolean plat = false;
+ boolean plat;
/**
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
- @Builder.Default boolean rk = false;
+ boolean rk;
/**
+ * If set to true
the device is capable of accepting PIN.
+ *
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
- @Builder.Default boolean clientPin = false;
+ @JsonInclude(JsonInclude.Include.NON_DEFAULT)
+ boolean clientPin;
/**
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
- @Builder.Default boolean up = false;
+ boolean up;
/**
+ * If set to true
the device is capable of built-in user verification.
+ *
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
- @Builder.Default boolean uv = false;
+ @JsonInclude(JsonInclude.Include.NON_DEFAULT)
+ boolean uv;
/**
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
- @JsonAlias("uvToken")
- @Builder.Default
- boolean pinUvAuthToken = false;
+ boolean pinUvAuthToken;
/**
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
- @Builder.Default boolean noMcGaPermissionsWithClientPin = false;
+ boolean noMcGaPermissionsWithClientPin;
/**
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
- @Builder.Default boolean largeBlobs = false;
+ boolean largeBlobs;
/**
+ * If set to true
the authenticator is enterprise attestation capable.
+ *
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
- @Builder.Default boolean ep = false;
+ @JsonInclude(JsonInclude.Include.NON_DEFAULT)
+ boolean ep;
/**
+ * If set to true
the authenticator supports the authenticatorBioEnrollment commands.
+ *
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
- @Builder.Default boolean bioEnroll = false;
+ @JsonInclude(JsonInclude.Include.NON_DEFAULT)
+ boolean bioEnroll;
/**
+ * If set to true
the authenticator supports the Prototype authenticatorBioEnrollment
+ * (0x40) commands.
+ *
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
- @Builder.Default boolean userVerificationMgmtPreview = false;
+ @JsonInclude(JsonInclude.Include.NON_DEFAULT)
+ boolean userVerificationMgmtPreview;
/**
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
- @Builder.Default boolean uvBioEnroll = false;
+ boolean uvBioEnroll;
/**
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
- @JsonAlias("config")
- @Builder.Default
- boolean authnrCfg = false;
+ boolean authnrCfg;
/**
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
- @Builder.Default boolean uvAcfg = false;
+ boolean uvAcfg;
/**
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
- @Builder.Default boolean credMgmt = false;
+ boolean credMgmt;
/**
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
- @Builder.Default boolean credentialMgmtPreview = false;
+ boolean perCredMgmtRO;
/**
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
- @Builder.Default boolean setMinPINLength = false;
+ @JsonInclude(JsonInclude.Include.NON_DEFAULT)
+ boolean credentialMgmtPreview;
/**
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
- @Builder.Default boolean makeCredUvNotRqd = false;
+ boolean setMinPINLength;
/**
* @see Client
+ * href="https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#authenticatorGetInfo">Client
* to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
*/
- @Builder.Default boolean alwaysUv = false;
+ boolean makeCredUvNotRqd;
+
+ /**
+ * If set to true
the authenticator supports the Always Require User Verification
+ * feature.
+ *
+ * @see Client
+ * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04)
+ */
+ @JsonInclude(JsonInclude.Include.NON_DEFAULT)
+ boolean alwaysUv;
+
+ @JsonCreator
+ private SupportedCtapOptions(
+ @JsonProperty("plat") Boolean plat,
+ @JsonProperty("rk") Boolean rk,
+ @JsonProperty("clientPin") Boolean clientPin,
+ @JsonProperty("up") Boolean up,
+ @JsonProperty("uv") Boolean uv,
+ @JsonAlias("uvToken") @JsonProperty("pinUvAuthToken") Boolean pinUvAuthToken,
+ @JsonProperty("noMcGaPermissionsWithClientPin") Boolean noMcGaPermissionsWithClientPin,
+ @JsonProperty("largeBlobs") Boolean largeBlobs,
+ @JsonProperty("ep") Boolean ep,
+ @JsonProperty("bioEnroll") Boolean bioEnroll,
+ @JsonProperty("userVerificationMgmtPreview") Boolean userVerificationMgmtPreview,
+ @JsonProperty("uvBioEnroll") Boolean uvBioEnroll,
+ @JsonAlias("config") @JsonProperty("authnrCfg") Boolean authnrCfg,
+ @JsonProperty("uvAcfg") Boolean uvAcfg,
+ @JsonProperty("credMgmt") Boolean credMgmt,
+ @JsonProperty("perCredMgmtRO") Boolean perCredMgmtRO,
+ @JsonProperty("credentialMgmtPreview") Boolean credentialMgmtPreview,
+ @JsonProperty("setMinPINLength") Boolean setMinPINLength,
+ @JsonProperty("makeCredUvNotRqd") Boolean makeCredUvNotRqd,
+ @JsonProperty("alwaysUv") Boolean alwaysUv) {
+ this.plat = Boolean.TRUE.equals(plat);
+ this.rk = Boolean.TRUE.equals(rk);
+ this.clientPin = clientPin != null;
+ this.up = Boolean.TRUE.equals(up);
+ this.uv = uv != null;
+ this.pinUvAuthToken = Boolean.TRUE.equals(pinUvAuthToken);
+ this.noMcGaPermissionsWithClientPin = Boolean.TRUE.equals(noMcGaPermissionsWithClientPin);
+ this.largeBlobs = Boolean.TRUE.equals(largeBlobs);
+ this.ep = ep != null;
+ this.bioEnroll = bioEnroll != null;
+ this.userVerificationMgmtPreview = userVerificationMgmtPreview != null;
+ this.uvBioEnroll = Boolean.TRUE.equals(uvBioEnroll);
+ this.authnrCfg = Boolean.TRUE.equals(authnrCfg);
+ this.uvAcfg = Boolean.TRUE.equals(uvAcfg);
+ this.credMgmt = Boolean.TRUE.equals(credMgmt);
+ this.perCredMgmtRO = Boolean.TRUE.equals(perCredMgmtRO);
+ this.credentialMgmtPreview = Boolean.TRUE.equals(credentialMgmtPreview);
+ this.setMinPINLength = Boolean.TRUE.equals(setMinPINLength);
+ this.makeCredUvNotRqd = Boolean.TRUE.equals(makeCredUvNotRqd);
+ this.alwaysUv = alwaysUv != null;
+ }
}
diff --git a/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/MetadataBlobSpec.scala b/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/MetadataBlobSpec.scala
index ed0d126a9..3de744451 100644
--- a/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/MetadataBlobSpec.scala
+++ b/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/MetadataBlobSpec.scala
@@ -1,11 +1,17 @@
package com.yubico.fido.metadata
+import com.yubico.fido.metadata.Generators.arbitrarySupportedCtapOptions
import com.yubico.internal.util.JacksonCodecs
import com.yubico.webauthn.data.ByteArray
+import org.scalacheck.Arbitrary
+import org.scalacheck.Gen
import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers
import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks
+import scala.jdk.CollectionConverters.SetHasAsScala
+import scala.jdk.OptionConverters.RichOptional
+
class MetadataBlobSpec
extends AnyFunSpec
with Matchers
@@ -50,4 +56,60 @@ class MetadataBlobSpec
}
}
+ describe("SupportedCtapOptions") {
+ it("can be parsed from an empty JSON object.") {
+ val options = JacksonCodecs
+ .json()
+ .readValue("{}", classOf[SupportedCtapOptions])
+ options should not be null
+ options.isPlat should be(false)
+ options.isRk should be(false)
+ options.isUp should be(false)
+ options.isUv should be(false)
+ options.isPinUvAuthToken should be(false)
+ options.isNoMcGaPermissionsWithClientPin should be(false)
+ options.isLargeBlobs should be(false)
+ options.isEp should be(false)
+ options.isBioEnroll should be(false)
+ options.isUserVerificationMgmtPreview should be(false)
+ options.isUvBioEnroll should be(false)
+ options.isAuthnrCfg should be(false)
+ options.isUvAcfg should be(false)
+ options.isCredMgmt should be(false)
+ options.isPerCredMgmtRO should be(false)
+ options.isCredentialMgmtPreview should be(false)
+ options.isSetMinPINLength should be(false)
+ options.isMakeCredUvNotRqd should be(false)
+ options.isAlwaysUv should be(false)
+ }
+
+ it(
+ "are structurally identical after multiple (de)serialization round-trips."
+ ) {
+ val json = JacksonCodecs.json()
+ val blob = json
+ .readValue(
+ ByteArray
+ .fromBase64Url(FidoMds3Examples.BlobPayloadBase64url)
+ .getBytes,
+ classOf[MetadataBLOBPayload],
+ )
+ val blobOptions = blob.getEntries.asScala
+ .flatMap(entry => entry.getMetadataStatement.toScala)
+ .flatMap(statement => statement.getAuthenticatorGetInfo.toScala)
+ .flatMap(info => info.getOptions.toScala)
+ forAll(Gen.oneOf(Arbitrary.arbitrary, Gen.oneOf(blobOptions))) {
+ (options1: SupportedCtapOptions) =>
+ val encoded1 = json.writeValueAsBytes(options1)
+ val options2 = json.readValue(encoded1, classOf[SupportedCtapOptions])
+ val encoded2 = json.writeValueAsBytes(options2)
+ val options3 = json.readValue(encoded2, classOf[SupportedCtapOptions])
+
+ options2 should not be null
+ options2 should equal(options1)
+ options3 should not be null
+ options3 should equal(options1)
+ }
+ }
+ }
}