Skip to content

Commit 864a1dc

Browse files
authored
Merge pull request #307 from Yubico/ignore-new-json
Tolerate "publicKey" and "publicKeyAlgorithm" properties in parseRegistrationResponseJson
2 parents e7d84a7 + 0b21631 commit 864a1dc

File tree

3 files changed

+100
-46
lines changed

3 files changed

+100
-46
lines changed

NEWS

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ New features:
1717
around passkey use cases.
1818
* Added `Automatic-Module-Name` to jar manifest.
1919

20+
Fixes:
21+
22+
* `AuthenticatorAttestationResponse` now tolerates and ignores properties
23+
`"publicKey"` and `"publicKeyAlgorithm"` during JSON deserialization. These
24+
properties are emitted by the `PublicKeyCredential.toJSON()` method added in
25+
WebAuthn Level 3.
26+
27+
2028
`webauthn-server-attestation`:
2129

2230
New features:

webauthn-server-core/src/main/java/com/yubico/webauthn/data/AuthenticatorAttestationResponse.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import com.fasterxml.jackson.annotation.JsonCreator;
2828
import com.fasterxml.jackson.annotation.JsonIgnore;
29+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
2930
import com.fasterxml.jackson.annotation.JsonProperty;
3031
import com.yubico.internal.util.CollectionUtil;
3132
import com.yubico.webauthn.data.exception.Base64UrlException;
@@ -49,6 +50,7 @@
4950
* Information About Public Key Credential (interface AuthenticatorAttestationResponse) </a>
5051
*/
5152
@Value
53+
@JsonIgnoreProperties({"publicKey", "publicKeyAlgorithm"})
5254
public class AuthenticatorAttestationResponse implements AuthenticatorResponse {
5355

5456
/**

webauthn-server-core/src/test/scala/com/yubico/webauthn/data/JsonIoSpec.scala

Lines changed: 90 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@
2525
package com.yubico.webauthn.data
2626

2727
import com.fasterxml.jackson.core.`type`.TypeReference
28+
import com.fasterxml.jackson.databind.JsonNode
2829
import com.fasterxml.jackson.databind.ObjectMapper
2930
import com.fasterxml.jackson.databind.exc.ValueInstantiationException
3031
import com.fasterxml.jackson.databind.json.JsonMapper
3132
import com.fasterxml.jackson.databind.node.BooleanNode
33+
import com.fasterxml.jackson.databind.node.JsonNodeFactory
3234
import com.fasterxml.jackson.databind.node.ObjectNode
3335
import com.fasterxml.jackson.databind.node.TextNode
3436
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module
@@ -44,6 +46,7 @@ import com.yubico.webauthn.extension.appid.Generators._
4446
import org.junit.runner.RunWith
4547
import org.scalacheck.Arbitrary
4648
import org.scalacheck.Arbitrary.arbitrary
49+
import org.scalacheck.Gen
4750
import org.scalatest.funspec.AnyFunSpec
4851
import org.scalatest.matchers.should.Matchers
4952
import org.scalatestplus.junit.JUnitRunner
@@ -62,6 +65,7 @@ class JsonIoSpec
6265
.builder()
6366
.addModule(new Jdk8Module())
6467
.build()
68+
val jf: JsonNodeFactory = JsonNodeFactory.instance
6569

6670
describe("The class") {
6771

@@ -133,52 +137,6 @@ class JsonIoSpec
133137
}
134138

135139
describe("The class PublicKeyCredential") {
136-
it(
137-
"has an alternative parseRegistrationResponseJson function as an alias."
138-
) {
139-
def test[A](tpe: TypeReference[A])(implicit a: Arbitrary[A]): Unit = {
140-
forAll { value: A =>
141-
val encoded: String = json.writeValueAsString(value)
142-
val decoded: A = json.readValue(encoded, tpe)
143-
val altDecoded =
144-
PublicKeyCredential.parseRegistrationResponseJson(encoded)
145-
val altRecoded: String = json.writeValueAsString(altDecoded)
146-
147-
altDecoded should equal(decoded)
148-
altRecoded should equal(encoded)
149-
}
150-
}
151-
test(
152-
new TypeReference[PublicKeyCredential[
153-
AuthenticatorAttestationResponse,
154-
ClientRegistrationExtensionOutputs,
155-
]]() {}
156-
)
157-
}
158-
159-
it(
160-
"has an alternative parseAuthenticationResponseJson function as an alias."
161-
) {
162-
def test[A](tpe: TypeReference[A])(implicit a: Arbitrary[A]): Unit = {
163-
forAll { value: A =>
164-
val encoded: String = json.writeValueAsString(value)
165-
val decoded: A = json.readValue(encoded, tpe)
166-
val altDecoded =
167-
PublicKeyCredential.parseAssertionResponseJson(encoded)
168-
val altRecoded: String = json.writeValueAsString(altDecoded)
169-
170-
altDecoded should equal(decoded)
171-
altRecoded should equal(encoded)
172-
}
173-
}
174-
test(
175-
new TypeReference[PublicKeyCredential[
176-
AuthenticatorAssertionResponse,
177-
ClientAssertionExtensionOutputs,
178-
]]() {}
179-
)
180-
}
181-
182140
it("allows rawId to be present without id.") {
183141
def test[P <: PublicKeyCredential[_, _]](tpe: TypeReference[P])(implicit
184142
a: Arbitrary[P]
@@ -416,6 +374,92 @@ class JsonIoSpec
416374
}
417375
}
418376

377+
describe("The function PublicKeyCredential.parseRegistrationResponseJson") {
378+
it("can parse registration responses.") {
379+
def test[A](tpe: TypeReference[A])(implicit a: Arbitrary[A]): Unit = {
380+
forAll { value: A =>
381+
val encoded: String = json.writeValueAsString(value)
382+
val decoded: A = json.readValue(encoded, tpe)
383+
val altDecoded =
384+
PublicKeyCredential.parseRegistrationResponseJson(encoded)
385+
val altRecoded: String = json.writeValueAsString(altDecoded)
386+
387+
altDecoded should equal(decoded)
388+
altRecoded should equal(encoded)
389+
}
390+
}
391+
392+
test(
393+
new TypeReference[PublicKeyCredential[
394+
AuthenticatorAttestationResponse,
395+
ClientRegistrationExtensionOutputs,
396+
]]() {}
397+
)
398+
}
399+
400+
describe("""tolerates and ignores the "response" sub-attribute:""") {
401+
def test[T <: JsonNode](attrName: String, genAttrValue: Gen[T]): Unit = {
402+
type P = PublicKeyCredential[
403+
AuthenticatorAttestationResponse,
404+
ClientRegistrationExtensionOutputs,
405+
]
406+
it(s"${attrName}.") {
407+
forAll(
408+
arbitrary[P],
409+
genAttrValue,
410+
) { (value: P, attrValue: T) =>
411+
val tree: ObjectNode = json.valueToTree(value)
412+
tree
413+
.get("response")
414+
.asInstanceOf[ObjectNode]
415+
.set(attrName, attrValue)
416+
val encoded = json.writeValueAsString(tree)
417+
val decoded =
418+
PublicKeyCredential.parseRegistrationResponseJson(encoded)
419+
val recoded: ObjectNode = json.valueToTree[ObjectNode](decoded)
420+
recoded.has(attrName) should be(false)
421+
}
422+
}
423+
}
424+
425+
test(
426+
"publicKeyAlgorithm",
427+
arbitraryCOSEAlgorithmIdentifier.arbitrary.map(i =>
428+
jf.numberNode(i.getId)
429+
),
430+
)
431+
432+
test(
433+
"publicKey",
434+
arbitrary[String].map(new TextNode(_)),
435+
)
436+
}
437+
}
438+
439+
describe("The function PublicKeyCredential.parseAssertionResponseJson") {
440+
it("can parse assertion responses.") {
441+
def test[A](tpe: TypeReference[A])(implicit a: Arbitrary[A]): Unit = {
442+
forAll { value: A =>
443+
val encoded: String = json.writeValueAsString(value)
444+
val decoded: A = json.readValue(encoded, tpe)
445+
val altDecoded =
446+
PublicKeyCredential.parseAssertionResponseJson(encoded)
447+
val altRecoded: String = json.writeValueAsString(altDecoded)
448+
449+
altDecoded should equal(decoded)
450+
altRecoded should equal(encoded)
451+
}
452+
}
453+
454+
test(
455+
new TypeReference[PublicKeyCredential[
456+
AuthenticatorAssertionResponse,
457+
ClientAssertionExtensionOutputs,
458+
]]() {}
459+
)
460+
}
461+
}
462+
419463
describe("The class PublicKeyCredentialCreationOptions") {
420464
it("""has a toCredentialsCreateJson() method which returns a JSON object with the PublicKeyCredentialCreationOptions set as a top-level "publicKey" property.""") {
421465
forAll { pkcco: PublicKeyCredentialCreationOptions =>

0 commit comments

Comments
 (0)