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 e5d6eed5a..5b7ff7a6d 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 @@ -2,10 +2,10 @@ import com.fasterxml.jackson.annotation.JsonAlias; 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 @@ -17,7 +17,6 @@ */ @Value @Builder -@Jacksonized public class SupportedCtapOptions { /** @@ -25,137 +24,140 @@ public class SupportedCtapOptions { * href="https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#authenticatorGetInfo">Client * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04) */ - @Builder.Default boolean plat = false; + boolean plat; /** * @see Client * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04) */ - @Builder.Default boolean rk = false; + boolean rk; /** * @see Client * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04) */ - @Builder.Default boolean clientPin = false; + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + boolean clientPin; /** * @see Client * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04) */ - @Builder.Default boolean up = false; + boolean up; /** * @see Client * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04) */ - @Builder.Default boolean uv = false; + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + boolean uv; /** * @see Client * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04) */ - @JsonAlias("uvToken") - @Builder.Default - boolean pinUvAuthToken = false; + boolean pinUvAuthToken; /** * @see Client * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04) */ - @Builder.Default boolean noMcGaPermissionsWithClientPin = false; + boolean noMcGaPermissionsWithClientPin; /** * @see Client * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04) */ - @Builder.Default boolean largeBlobs = false; + boolean largeBlobs; /** * @see Client * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04) */ - @Builder.Default boolean ep = false; + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + boolean ep; /** * @see Client * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04) */ - @Builder.Default boolean bioEnroll = false; + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + boolean bioEnroll; /** * @see Client * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04) */ - @Builder.Default boolean userVerificationMgmtPreview = false; + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + boolean userVerificationMgmtPreview; /** * @see Client * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04) */ - @Builder.Default boolean uvBioEnroll = false; + boolean uvBioEnroll; /** * @see Client * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04) */ - @JsonAlias("config") - @Builder.Default - boolean authnrCfg = false; + boolean authnrCfg; /** * @see Client * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04) */ - @Builder.Default boolean uvAcfg = false; + boolean uvAcfg; /** * @see Client * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04) */ - @Builder.Default boolean credMgmt = false; + boolean credMgmt; /** * @see Client * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04) */ - @Builder.Default boolean credentialMgmtPreview = false; + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + boolean credentialMgmtPreview; /** * @see Client * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04) */ - @Builder.Default boolean setMinPINLength = false; + boolean setMinPINLength; /** * @see Client * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04) */ - @Builder.Default boolean makeCredUvNotRqd = false; + boolean makeCredUvNotRqd; /** * @see Client * to Authenticator Protocol (CTAP) §6.4. authenticatorGetInfo (0x04) */ - @Builder.Default boolean alwaysUv = false; + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + boolean alwaysUv; @JsonCreator private SupportedCtapOptions( @@ -164,24 +166,24 @@ private SupportedCtapOptions( @JsonProperty("clientPin") Boolean clientPin, @JsonProperty("up") Boolean up, @JsonProperty("uv") Boolean uv, - @JsonProperty("pinUvAuthToken") Boolean pinUvAuthToken, + @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, - @JsonProperty("authnrCfg") Boolean authnrCfg, + @JsonAlias("config") @JsonProperty("authnrCfg") Boolean authnrCfg, @JsonProperty("uvAcfg") Boolean uvAcfg, @JsonProperty("credMgmt") Boolean credMgmt, @JsonProperty("credentialMgmtPreview") Boolean credentialMgmtPreview, @JsonProperty("setMinPINLength") Boolean setMinPINLength, @JsonProperty("makeCredUvNotRqd") Boolean makeCredUvNotRqd, @JsonProperty("alwaysUv") Boolean alwaysUv) { - this.plat = plat; - this.rk = rk; + this.plat = Boolean.TRUE.equals(plat); + this.rk = Boolean.TRUE.equals(rk); this.clientPin = clientPin != null; - this.up = up; + this.up = Boolean.TRUE.equals(up); this.uv = uv != null; this.pinUvAuthToken = Boolean.TRUE.equals(pinUvAuthToken); this.noMcGaPermissionsWithClientPin = Boolean.TRUE.equals(noMcGaPermissionsWithClientPin); 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..ecae4ff50 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,59 @@ 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.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) + } + } + } }