Skip to content

Commit 0ae9fb1

Browse files
committed
Fix JSON encoding of credProtect extension inputs
1 parent 37010ca commit 0ae9fb1

File tree

2 files changed

+84
-12
lines changed

2 files changed

+84
-12
lines changed

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

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
package com.yubico.webauthn.data;
2626

2727
import com.fasterxml.jackson.annotation.JsonCreator;
28+
import com.fasterxml.jackson.annotation.JsonIgnore;
2829
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
2930
import com.fasterxml.jackson.annotation.JsonProperty;
3031
import com.yubico.webauthn.RelyingParty;
@@ -60,15 +61,13 @@ public final class RegistrationExtensionInputs implements ExtensionInputs {
6061
private final Extensions.Prf.PrfRegistrationInput prf;
6162
private final Boolean uvm;
6263

63-
@JsonCreator
6464
private RegistrationExtensionInputs(
65-
@JsonProperty("appidExclude") AppId appidExclude,
66-
@JsonProperty("credProps") Boolean credProps,
67-
@JsonProperty("credProtect")
68-
Extensions.CredentialProtection.CredentialProtectionInput credProtect,
69-
@JsonProperty("largeBlob") Extensions.LargeBlob.LargeBlobRegistrationInput largeBlob,
70-
@JsonProperty("prf") Extensions.Prf.PrfRegistrationInput prf,
71-
@JsonProperty("uvm") Boolean uvm) {
65+
AppId appidExclude,
66+
Boolean credProps,
67+
Extensions.CredentialProtection.CredentialProtectionInput credProtect,
68+
Extensions.LargeBlob.LargeBlobRegistrationInput largeBlob,
69+
Extensions.Prf.PrfRegistrationInput prf,
70+
Boolean uvm) {
7271
this.appidExclude = appidExclude;
7372
this.credProps = credProps;
7473
this.credProtect = credProtect;
@@ -77,6 +76,32 @@ private RegistrationExtensionInputs(
7776
this.uvm = uvm;
7877
}
7978

79+
@JsonCreator
80+
private RegistrationExtensionInputs(
81+
@JsonProperty("appidExclude") AppId appidExclude,
82+
@JsonProperty("credProps") Boolean credProps,
83+
@JsonProperty("credentialProtectionPolicy")
84+
Extensions.CredentialProtection.CredentialProtectionPolicy credProtectPolicy,
85+
@JsonProperty("enforceCredentialProtectionPolicy") Boolean enforceCredProtectPolicy,
86+
@JsonProperty("largeBlob") Extensions.LargeBlob.LargeBlobRegistrationInput largeBlob,
87+
@JsonProperty("prf") Extensions.Prf.PrfRegistrationInput prf,
88+
@JsonProperty("uvm") Boolean uvm) {
89+
this(
90+
appidExclude,
91+
credProps,
92+
Optional.ofNullable(credProtectPolicy)
93+
.map(
94+
policy -> {
95+
return enforceCredProtectPolicy != null && enforceCredProtectPolicy
96+
? Extensions.CredentialProtection.CredentialProtectionInput.require(policy)
97+
: Extensions.CredentialProtection.CredentialProtectionInput.prefer(policy);
98+
})
99+
.orElse(null),
100+
largeBlob,
101+
prf,
102+
uvm);
103+
}
104+
80105
/**
81106
* Merge <code>other</code> into <code>this</code>. Non-null field values from <code>this</code>
82107
* take precedence.
@@ -133,10 +158,36 @@ private Boolean getCredPropsJson() {
133158
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-authenticator-credential-properties-extension">§10.4.
134159
* Credential Properties Extension (credProps)</a>
135160
*/
161+
@JsonIgnore
136162
public Optional<Extensions.CredentialProtection.CredentialProtectionInput> getCredProtect() {
137163
return Optional.ofNullable(credProtect);
138164
}
139165

166+
/**
167+
* For JSON serialization, because credProtect does not group all inputs under the "credProtect"
168+
* key.
169+
*/
170+
@JsonProperty("credentialProtectionPolicy")
171+
private Optional<Extensions.CredentialProtection.CredentialProtectionPolicy>
172+
getCredProtectPolicy() {
173+
return getCredProtect()
174+
.map(
175+
Extensions.CredentialProtection.CredentialProtectionInput
176+
::getCredentialProtectionPolicy);
177+
}
178+
179+
/**
180+
* For JSON serialization, because credProtect does not group all inputs under the "credProtect"
181+
* key.
182+
*/
183+
@JsonProperty("enforceCredentialProtectionPolicy")
184+
private Optional<Boolean> getEnforceCredProtectPolicy() {
185+
return getCredProtect()
186+
.map(
187+
Extensions.CredentialProtection.CredentialProtectionInput
188+
::isEnforceCredentialProtectionPolicy);
189+
}
190+
140191
/**
141192
* @return The value of the Large blob storage extension (<code>largeBlob</code>) input if
142193
* configured, empty otherwise.

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

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package com.yubico.webauthn.data
33
import com.fasterxml.jackson.databind.node.ObjectNode
44
import com.yubico.internal.util.JacksonCodecs
55
import com.yubico.scalacheck.gen.JacksonGenerators.arbitraryObjectNode
6+
import com.yubico.webauthn.data.Extensions.CredentialProtection.CredentialProtectionInput
7+
import com.yubico.webauthn.data.Extensions.CredentialProtection.CredentialProtectionPolicy
68
import com.yubico.webauthn.data.Extensions.LargeBlob.LargeBlobAuthenticationInput
79
import com.yubico.webauthn.data.Extensions.LargeBlob.LargeBlobAuthenticationOutput
810
import com.yubico.webauthn.data.Extensions.LargeBlob.LargeBlobRegistrationInput
@@ -38,14 +40,23 @@ class ExtensionsSpec
3840

3941
describe("RegistrationExtensionInputs") {
4042
describe("has a getExtensionIds() method which") {
41-
it("contains exactly the names of contained extensions.") {
43+
it("contains exactly the names of contained extensions, except for credProtect.") {
4244
forAll { input: RegistrationExtensionInputs =>
45+
val expectedJsonKeys = input.getExtensionIds.asScala.flatMap(id => {
46+
if (id == "credProtect") {
47+
// credProtect does not gather all inputs under the extension ID as a map key.
48+
List(
49+
"credentialProtectionPolicy",
50+
"enforceCredentialProtectionPolicy",
51+
)
52+
} else {
53+
List(id)
54+
}
55+
})
4356
val json = JacksonCodecs.json().valueToTree[ObjectNode](input)
4457
val jsonKeyNames = json.fieldNames.asScala.toList
45-
val extensionIds = input.getExtensionIds
4658

47-
jsonKeyNames.length should equal(extensionIds.size)
48-
jsonKeyNames.toSet should equal(extensionIds.asScala)
59+
jsonKeyNames.toSet should equal(expectedJsonKeys)
4960
}
5061
}
5162
}
@@ -74,6 +85,8 @@ class ExtensionsSpec
7485
"""{
7586
|"appidExclude": "https://example.org",
7687
|"credProps": true,
88+
|"credentialProtectionPolicy": "userVerificationRequired",
89+
|"enforceCredentialProtectionPolicy": true,
7790
|"largeBlob": {
7891
| "support": "required"
7992
|},
@@ -98,6 +111,7 @@ class ExtensionsSpec
98111
Set(
99112
"appidExclude",
100113
"credProps",
114+
"credProtect",
101115
"largeBlob",
102116
"prf",
103117
"uvm",
@@ -107,6 +121,13 @@ class ExtensionsSpec
107121
Some(new AppId("https://example.org"))
108122
)
109123
decoded.getCredProps should equal(true)
124+
decoded.getCredProtect.toScala should equal(
125+
Some(
126+
CredentialProtectionInput.require(
127+
CredentialProtectionPolicy.UV_REQUIRED
128+
)
129+
)
130+
)
110131
decoded.getLargeBlob.toScala should equal(
111132
Some(new LargeBlobRegistrationInput(LargeBlobSupport.REQUIRED))
112133
)

0 commit comments

Comments
 (0)