Skip to content

Commit 0a520a0

Browse files
committed
Allow unknown MDS enum values and add missing fields
Beyond adding the missing `AttachmentHint` value identified in [issue #446][1] , this adds an `UNKNOWN` constant as the default deserialization of all enums in the MDS data model; this should prevent hard crashes on new enum values in the future. To ensure that new values don't go unnoticed forever, this also adds a new integration test that attempts to deserialize the BLOB with enum defaults disabled and unknown fields forbidden. This should help us detect when new additions appear in the upstream data, as the integration test runs [once a week on GitHub Actions][2]. The new test also revealed several other new fields that were missing. I also added any other new fields defined in immediate proximity to the ones identified by the test; not all of these added fields have yet appeared in the actual data. [1]: #466 [2]: https://github.com/Yubico/java-webauthn-server/actions/workflows/integration-test.yml
2 parents f1999a7 + f5c7b9f commit 0a520a0

20 files changed

Lines changed: 706 additions & 52 deletions

NEWS

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,37 @@ Fixes:
1111
New features:
1212

1313
* Added `AuthenticatorStatus.RETIRED` and `Filters.notRetired()`.
14+
* Added `AttachmentHint.ATTACHMENT_HINT_SMART_CARD`.
15+
** Thanks to Misagh Moayyed and Erlend Nukke for the contribution, see
16+
https://github.com/Yubico/java-webauthn-server/pull/468 and
17+
https://github.com/Yubico/java-webauthn-server/pull/467
18+
* Added `UNKNOWN` constant to all enums in `com.yubico.fido.metadata`:
19+
** `AttachmentHint`
20+
** `AuthenticationAlgorithm`
21+
** `AuthenticatorAttestationType`
22+
** `CtapCertificationId`
23+
** `CtapPinUvAuthProtocolVersion`
24+
** `CtapVersion`
25+
** `ProtocolFamily`
26+
** `PublicKeyRepresentationFormat`
27+
** `TransactionConfirmationDisplayType`
28+
* Added enum constant `CtapVersion.FIDO_2_3`.
29+
* Added missing fields to FIDO MDS data model:
30+
** `AuthenticatorGetInfo`: `attestationFormats`, `longTouchForReset`,
31+
`uvCountSinceLastPinEntry`, `transportsForReset`, `pinComplexityPolicy`,
32+
`pinComplexityPolicyURL`, `maxPINLength`, `authenticatorConfigCommands`
33+
** `BiometricAccuracyDescriptor`: `iAPARThreshold`
34+
** `MetadataStatement`: `friendlyNames`, `iconDark`, `providerLogoLight`,
35+
`providerLogoDark`, `keyScope`, `multiDeviceCredentialSupport`,
36+
`cxpConfigURL`
37+
** `StatusReport`: `certificationProfiles`, `sunsetDate`, `fipsRevision`,
38+
`fipsPhysicalSecurityLevel`
1439

1540
Fixes:
1641

1742
* Added `@since` tags to `AuthenticatorStatus` and `FidoMetadataService` javadoc.
43+
* All `com.yubico.fido.metadata` enums now deserialize unknown values to
44+
`UNKOWN` instead of crashing the parser.
1845

1946

2047
== Version 2.8.1 ==
Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.yubico.fido.metadata
22

3+
import com.fasterxml.jackson.databind.DeserializationFeature
4+
import com.yubico.fido.metadata.TestCaches.cachedDefaultSettingsDownloader
35
import com.yubico.internal.util.CertificateParser
46
import com.yubico.webauthn.data.ByteArray
57
import org.junit.runner.RunWith
@@ -11,7 +13,6 @@ import org.scalatest.tags.Slow
1113
import org.scalatestplus.junit.JUnitRunner
1214

1315
import scala.jdk.CollectionConverters.ListHasAsScala
14-
import scala.jdk.OptionConverters.RichOption
1516
import scala.util.Success
1617
import scala.util.Try
1718

@@ -24,44 +25,33 @@ class FidoMetadataDownloaderIntegrationTest
2425
with BeforeAndAfter {
2526

2627
describe("FidoMetadataDownloader with default settings") {
27-
// Cache downloaded items to avoid cause unnecessary load on remote servers
28-
var trustRootCache: Option[ByteArray] = None
29-
var blobCache: Option[ByteArray] = None
30-
val downloader =
31-
FidoMetadataDownloader
32-
.builder()
33-
.expectLegalHeader(
34-
"Retrieval and use of this BLOB indicates acceptance of the appropriate agreement located at https://fidoalliance.org/metadata/metadata-legal-terms/"
35-
)
36-
.useDefaultTrustRoot()
37-
.useTrustRootCache(
38-
() => trustRootCache.toJava,
39-
trustRoot => { trustRootCache = Some(trustRoot) },
40-
)
41-
.useDefaultBlob()
42-
.useBlobCache(
43-
() => blobCache.toJava,
44-
blob => { blobCache = Some(blob) },
45-
)
46-
.build()
28+
val downloader = cachedDefaultSettingsDownloader.build()
4729

4830
it("downloads and verifies the root cert and BLOB successfully.") {
49-
val blob = Try(downloader.loadCachedBlob)
31+
val blob = Try(TestCaches.cacheSynchronized(downloader.loadCachedBlob))
5032
blob shouldBe a[Success[_]]
5133
blob.get should not be null
5234
}
5335

5436
it(
5537
"does not encounter any CRLDistributionPoints entries in unknown format."
5638
) {
57-
val blob = Try(downloader.loadCachedBlob)
39+
val blob = Try(TestCaches.cacheSynchronized(downloader.loadCachedBlob))
5840
blob shouldBe a[Success[_]]
5941
val trustRootCert =
60-
CertificateParser.parseDer(trustRootCache.get.getBytes)
61-
val certChain = downloader
62-
.fetchHeaderCertChain(
63-
trustRootCert,
64-
FidoMetadataDownloader.parseBlob(blobCache.get).getBlob.getHeader,
42+
CertificateParser.parseDer(
43+
TestCaches.getTrustRootCache.get.get.getBytes
44+
)
45+
val certChain = TestCaches
46+
.cacheSynchronized(
47+
downloader
48+
.fetchHeaderCertChain(
49+
trustRootCert,
50+
downloader
51+
.parseBlob(TestCaches.getBlobCache.get.get)
52+
.getBlob
53+
.getHeader,
54+
)
6555
)
6656
.asScala :+ trustRootCert
6757
for { cert <- certChain } {
@@ -76,4 +66,29 @@ class FidoMetadataDownloaderIntegrationTest
7666
}
7767
}
7868

69+
describe("FidoMetadataDownloader with strict JSON deserialization settings") {
70+
val downloader = cachedDefaultSettingsDownloader
71+
.headerJsonMapper(() =>
72+
com.yubico.internal.util.JacksonCodecs
73+
.json()
74+
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
75+
)
76+
.payloadJsonMapper(() =>
77+
com.yubico.internal.util.JacksonCodecs
78+
.json()
79+
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
80+
.configure(
81+
DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE,
82+
false,
83+
)
84+
)
85+
.build()
86+
87+
it("downloads and parses the BLOB successfully.") {
88+
val blob = Try(TestCaches.cacheSynchronized(downloader.loadCachedBlob))
89+
blob shouldBe a[Success[_]]
90+
blob.get should not be null
91+
}
92+
}
93+
7994
}

webauthn-server-attestation/src/integrationTest/scala/com/yubico/fido/metadata/FidoMetadataServiceIntegrationTest.scala

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.yubico.fido.metadata.AttachmentHint.ATTACHMENT_HINT_INTERNAL
55
import com.yubico.fido.metadata.AttachmentHint.ATTACHMENT_HINT_NFC
66
import com.yubico.fido.metadata.AttachmentHint.ATTACHMENT_HINT_WIRED
77
import com.yubico.fido.metadata.AttachmentHint.ATTACHMENT_HINT_WIRELESS
8+
import com.yubico.fido.metadata.TestCaches.cachedDefaultSettingsDownloader
89
import com.yubico.internal.util.CertificateParser
910
import com.yubico.webauthn.FinishRegistrationOptions
1011
import com.yubico.webauthn.RelyingParty
@@ -22,7 +23,6 @@ import org.scalatestplus.junit.JUnitRunner
2223

2324
import java.time.Clock
2425
import java.time.ZoneOffset
25-
import java.util.Optional
2626
import scala.jdk.CollectionConverters.SetHasAsJava
2727
import scala.jdk.CollectionConverters.SetHasAsScala
2828
import scala.jdk.OptionConverters.RichOptional
@@ -40,21 +40,12 @@ class FidoMetadataServiceIntegrationTest
4040
describe("FidoMetadataService") {
4141

4242
describe("downloaded with default settings") {
43-
val downloader = FidoMetadataDownloader
44-
.builder()
45-
.expectLegalHeader(
46-
"Retrieval and use of this BLOB indicates acceptance of the appropriate agreement located at https://fidoalliance.org/metadata/metadata-legal-terms/"
47-
)
48-
.useDefaultTrustRoot()
49-
.useTrustRootCache(() => Optional.empty(), _ => {})
50-
.useDefaultBlob()
51-
.useBlobCache(() => Optional.empty(), _ => {})
52-
.build()
43+
val downloader = cachedDefaultSettingsDownloader.build()
5344
val fidoMds =
5445
Try(
5546
FidoMetadataService
5647
.builder()
57-
.useBlob(downloader.loadCachedBlob())
48+
.useBlob(TestCaches.cacheSynchronized(downloader.loadCachedBlob()))
5849
.build()
5950
)
6051

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.yubico.fido.metadata
2+
3+
import com.yubico.webauthn.data.ByteArray
4+
5+
import java.util.Optional
6+
import java.util.function.Consumer
7+
import java.util.function.Supplier
8+
import scala.jdk.OptionConverters.RichOption
9+
10+
object TestCaches {
11+
12+
// Cache downloaded items to avoid unnecessary load on remote servers, and so tests don't have to wait for rate limiting
13+
14+
private var trustRootCache: Option[ByteArray] = None
15+
val getTrustRootCache: Supplier[Optional[ByteArray]] = () =>
16+
trustRootCache.toJava
17+
val setTrustRootCache: Consumer[ByteArray] = trustRoot => {
18+
trustRootCache = Some(trustRoot)
19+
}
20+
21+
private var blobCache: Option[ByteArray] = None
22+
val getBlobCache: Supplier[Optional[ByteArray]] = () => blobCache.toJava
23+
val setBlobCache: Consumer[ByteArray] = blob => { blobCache = Some(blob) }
24+
25+
def cachedDefaultSettingsDownloader
26+
: FidoMetadataDownloader.FidoMetadataDownloaderBuilder =
27+
FidoMetadataDownloader
28+
.builder()
29+
.expectLegalHeader(
30+
"Retrieval and use of this BLOB indicates acceptance of the appropriate agreement located at https://fidoalliance.org/metadata/metadata-legal-terms/"
31+
)
32+
.useDefaultTrustRoot()
33+
.useTrustRootCache(getTrustRootCache, setTrustRootCache)
34+
.useDefaultBlob()
35+
.useBlobCache(getBlobCache, setBlobCache)
36+
37+
/** Evaluate <code>expr</code> with an exclusive lock on the test cache. */
38+
def cacheSynchronized[A](expr: => A): A = {
39+
this.synchronized(expr)
40+
}
41+
42+
}

webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/AttachmentHint.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.yubico.fido.metadata;
22

3+
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
34
import com.fasterxml.jackson.annotation.JsonValue;
45

56
/**
@@ -22,6 +23,14 @@
2223
*/
2324
public enum AttachmentHint {
2425

26+
/**
27+
* (NOT DEFINED IN SPEC) Placeholder for any unknown {@link AttachmentHint} value.
28+
*
29+
* @since 2.9.0
30+
*/
31+
@JsonEnumDefaultValue
32+
UNKNOWN(0, "UNKNOWN"),
33+
2534
/**
2635
* This flag MAY be set to indicate that the authenticator is permanently attached to the FIDO
2736
* User Device.
@@ -129,7 +138,20 @@ public enum AttachmentHint {
129138
* href="https://fidoalliance.org/specs/common-specs/fido-registry-v2.2-rd-20210525.html#authenticator-attachment-hints">FIDO
130139
* Registry of Predefined Values §3.4 Authenticator Attachment Hints</a>
131140
*/
132-
ATTACHMENT_HINT_WIFI_DIRECT(0x0100, "wifi_direct");
141+
ATTACHMENT_HINT_WIFI_DIRECT(0x0100, "wifi_direct"),
142+
143+
/**
144+
* This flag MAY be set to indicate that an external authenticator is able to communicate by
145+
* ISO7816 messages with the FIDO User Device. As part of authenticator metadata, or when
146+
* reporting characteristics through discovery, if this flag is set, the {@link
147+
* #ATTACHMENT_HINT_WIRED} flag SHOULD also be set.
148+
*
149+
* @since 2.9.0
150+
* @see <a
151+
* href="https://fidoalliance.org/specs/common-specs/fido-registry-v2.3-rd-20260105.html#authenticator-attachment-hints">FIDO
152+
* Registry of Predefined Values §3.4 Authenticator Attachment Hints</a>
153+
*/
154+
ATTACHMENT_HINT_SMART_CARD(0x0200, "smart-card");
133155

134156
private final int value;
135157

webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/AuthenticationAlgorithm.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.yubico.fido.metadata;
22

3+
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
34
import com.fasterxml.jackson.annotation.JsonValue;
45

56
/**
@@ -15,6 +16,14 @@
1516
*/
1617
public enum AuthenticationAlgorithm {
1718

19+
/**
20+
* (NOT DEFINED IN SPEC) Placeholder for any unknown {@link AuthenticationAlgorithm} value.
21+
*
22+
* @since 2.9.0
23+
*/
24+
@JsonEnumDefaultValue
25+
UNKNOWN(0, "UNKNOWN"),
26+
1827
/**
1928
* @see <a
2029
* href="https://fidoalliance.org/specs/common-specs/fido-registry-v2.2-rd-20210525.html#authentication-algorithms">FIDO

webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/AuthenticatorAttestationType.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.yubico.fido.metadata;
22

3+
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
34
import com.fasterxml.jackson.annotation.JsonValue;
45

56
/**
@@ -15,6 +16,14 @@
1516
*/
1617
public enum AuthenticatorAttestationType {
1718

19+
/**
20+
* (NOT DEFINED IN SPEC) Placeholder for any unknown {@link AuthenticatorAttestationType} value.
21+
*
22+
* @since 2.9.0
23+
*/
24+
@JsonEnumDefaultValue
25+
UNKNOWN(0, "UNKNOWN"),
26+
1827
/**
1928
* Indicates full basic attestation, based on an attestation private key shared among a class of
2029
* authenticators (e.g. same model). Authenticators must provide its attestation signature during

0 commit comments

Comments
 (0)