diff --git a/NEWS b/NEWS index 0746178c5..069bb2c08 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,14 @@ New features: around passkey use cases. * Added `Automatic-Module-Name` to jar manifest. +Fixes: + +* `AuthenticatorAttestationResponse` now tolerates and ignores properties + `"publicKey"` and `"publicKeyAlgorithm"` during JSON deserialization. These + properties are emitted by the `PublicKeyCredential.toJSON()` method added in + WebAuthn Level 3. + + `webauthn-server-attestation`: New features: diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorAttestationResponse.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorAttestationResponse.java index 0a33448db..29ba7e3ec 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorAttestationResponse.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorAttestationResponse.java @@ -26,6 +26,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.yubico.internal.util.CollectionUtil; import com.yubico.webauthn.data.exception.Base64UrlException; @@ -49,6 +50,7 @@ * Information About Public Key Credential (interface AuthenticatorAttestationResponse) */ @Value +@JsonIgnoreProperties({"publicKey", "publicKeyAlgorithm"}) public class AuthenticatorAttestationResponse implements AuthenticatorResponse { /** diff --git a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/JsonIoSpec.scala b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/JsonIoSpec.scala index 64c397857..3fe9a73c5 100644 --- a/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/JsonIoSpec.scala +++ b/webauthn-server-core/src/test/scala/com/yubico/webauthn/data/JsonIoSpec.scala @@ -25,10 +25,12 @@ package com.yubico.webauthn.data import com.fasterxml.jackson.core.`type`.TypeReference +import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.exc.ValueInstantiationException import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.databind.node.BooleanNode +import com.fasterxml.jackson.databind.node.JsonNodeFactory import com.fasterxml.jackson.databind.node.ObjectNode import com.fasterxml.jackson.databind.node.TextNode import com.fasterxml.jackson.datatype.jdk8.Jdk8Module @@ -44,6 +46,7 @@ import com.yubico.webauthn.extension.appid.Generators._ import org.junit.runner.RunWith import org.scalacheck.Arbitrary import org.scalacheck.Arbitrary.arbitrary +import org.scalacheck.Gen import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers import org.scalatestplus.junit.JUnitRunner @@ -62,6 +65,7 @@ class JsonIoSpec .builder() .addModule(new Jdk8Module()) .build() + val jf: JsonNodeFactory = JsonNodeFactory.instance describe("The class") { @@ -392,6 +396,44 @@ class JsonIoSpec ]]() {} ) } + + describe("""tolerates and ignores the "response" sub-attribute:""") { + def test[T <: JsonNode](attrName: String, genAttrValue: Gen[T]): Unit = { + type P = PublicKeyCredential[ + AuthenticatorAttestationResponse, + ClientRegistrationExtensionOutputs, + ] + it(s"${attrName}.") { + forAll( + arbitrary[P], + genAttrValue, + ) { (value: P, attrValue: T) => + val tree: ObjectNode = json.valueToTree(value) + tree + .get("response") + .asInstanceOf[ObjectNode] + .set(attrName, attrValue) + val encoded = json.writeValueAsString(tree) + val decoded = + PublicKeyCredential.parseRegistrationResponseJson(encoded) + val recoded: ObjectNode = json.valueToTree[ObjectNode](decoded) + recoded.has(attrName) should be(false) + } + } + } + + test( + "publicKeyAlgorithm", + arbitraryCOSEAlgorithmIdentifier.arbitrary.map(i => + jf.numberNode(i.getId) + ), + ) + + test( + "publicKey", + arbitrary[String].map(new TextNode(_)), + ) + } } describe("The function PublicKeyCredential.parseAssertionResponseJson") {