Skip to content

Commit

Permalink
Merge pull request #307 from Yubico/ignore-new-json
Browse files Browse the repository at this point in the history
Tolerate "publicKey" and "publicKeyAlgorithm" properties in parseRegistrationResponseJson
  • Loading branch information
emlun authored Jun 26, 2023
2 parents e7d84a7 + 0b21631 commit 864a1dc
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 46 deletions.
8 changes: 8 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -49,6 +50,7 @@
* Information About Public Key Credential (interface AuthenticatorAttestationResponse) </a>
*/
@Value
@JsonIgnoreProperties({"publicKey", "publicKeyAlgorithm"})
public class AuthenticatorAttestationResponse implements AuthenticatorResponse {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -62,6 +65,7 @@ class JsonIoSpec
.builder()
.addModule(new Jdk8Module())
.build()
val jf: JsonNodeFactory = JsonNodeFactory.instance

describe("The class") {

Expand Down Expand Up @@ -133,52 +137,6 @@ class JsonIoSpec
}

describe("The class PublicKeyCredential") {
it(
"has an alternative parseRegistrationResponseJson function as an alias."
) {
def test[A](tpe: TypeReference[A])(implicit a: Arbitrary[A]): Unit = {
forAll { value: A =>
val encoded: String = json.writeValueAsString(value)
val decoded: A = json.readValue(encoded, tpe)
val altDecoded =
PublicKeyCredential.parseRegistrationResponseJson(encoded)
val altRecoded: String = json.writeValueAsString(altDecoded)

altDecoded should equal(decoded)
altRecoded should equal(encoded)
}
}
test(
new TypeReference[PublicKeyCredential[
AuthenticatorAttestationResponse,
ClientRegistrationExtensionOutputs,
]]() {}
)
}

it(
"has an alternative parseAuthenticationResponseJson function as an alias."
) {
def test[A](tpe: TypeReference[A])(implicit a: Arbitrary[A]): Unit = {
forAll { value: A =>
val encoded: String = json.writeValueAsString(value)
val decoded: A = json.readValue(encoded, tpe)
val altDecoded =
PublicKeyCredential.parseAssertionResponseJson(encoded)
val altRecoded: String = json.writeValueAsString(altDecoded)

altDecoded should equal(decoded)
altRecoded should equal(encoded)
}
}
test(
new TypeReference[PublicKeyCredential[
AuthenticatorAssertionResponse,
ClientAssertionExtensionOutputs,
]]() {}
)
}

it("allows rawId to be present without id.") {
def test[P <: PublicKeyCredential[_, _]](tpe: TypeReference[P])(implicit
a: Arbitrary[P]
Expand Down Expand Up @@ -416,6 +374,92 @@ class JsonIoSpec
}
}

describe("The function PublicKeyCredential.parseRegistrationResponseJson") {
it("can parse registration responses.") {
def test[A](tpe: TypeReference[A])(implicit a: Arbitrary[A]): Unit = {
forAll { value: A =>
val encoded: String = json.writeValueAsString(value)
val decoded: A = json.readValue(encoded, tpe)
val altDecoded =
PublicKeyCredential.parseRegistrationResponseJson(encoded)
val altRecoded: String = json.writeValueAsString(altDecoded)

altDecoded should equal(decoded)
altRecoded should equal(encoded)
}
}

test(
new TypeReference[PublicKeyCredential[
AuthenticatorAttestationResponse,
ClientRegistrationExtensionOutputs,
]]() {}
)
}

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") {
it("can parse assertion responses.") {
def test[A](tpe: TypeReference[A])(implicit a: Arbitrary[A]): Unit = {
forAll { value: A =>
val encoded: String = json.writeValueAsString(value)
val decoded: A = json.readValue(encoded, tpe)
val altDecoded =
PublicKeyCredential.parseAssertionResponseJson(encoded)
val altRecoded: String = json.writeValueAsString(altDecoded)

altDecoded should equal(decoded)
altRecoded should equal(encoded)
}
}

test(
new TypeReference[PublicKeyCredential[
AuthenticatorAssertionResponse,
ClientAssertionExtensionOutputs,
]]() {}
)
}
}

describe("The class PublicKeyCredentialCreationOptions") {
it("""has a toCredentialsCreateJson() method which returns a JSON object with the PublicKeyCredentialCreationOptions set as a top-level "publicKey" property.""") {
forAll { pkcco: PublicKeyCredentialCreationOptions =>
Expand Down

0 comments on commit 864a1dc

Please sign in to comment.