Skip to content

Commit fc2fff3

Browse files
committed
Release 2.9.0
`webauthn-server-core`: Security fixes: - Fixed issue where `RelyingParty.finishAssertion` and `RelyingPartyV2.finishAssertion` could return a successful authentication result even though the authenticated credential is owned by a different user than `StartAssertionOptions.username`. For details see CVE-[TBD] or YSA-2026-02: https://www.yubico.com/support/security-advisories/ysa-2026-02/ - This fix is forward-ported from version 2.8.2 since the issue is also present in pre-release 2.9.0-alpha1. Fixes: - Added `@since` tags to `AttestationTrustSource` javadoc. `webauthn-server-attestation`: Changes: - `FidoMetadataDownloader` builder method `.downloadBlob(URL)` now logs a warning if the given URL is not an HTTPS URL. Javadoc relaxed to not describe HTTPS as required since this was never enforced. New features: - Added `AuthenticatorStatus.RETIRED` and `Filters.notRetired()`. - Added `AttachmentHint.ATTACHMENT_HINT_SMART_CARD`. - Thanks to Misagh Moayyed and Erlend Nukke for the contribution, see #468 and #467 - Added `UNKNOWN` constant to all enums in `com.yubico.fido.metadata`: - `AttachmentHint` - `AuthenticationAlgorithm` - `AuthenticatorAttestationType` - `CtapCertificationId` - `CtapPinUvAuthProtocolVersion` - `CtapVersion` - `ProtocolFamily` - `PublicKeyRepresentationFormat` - `TransactionConfirmationDisplayType` - Added enum constant `CtapVersion.FIDO_2_3`. - Added missing fields to FIDO MDS data model: - `AuthenticatorGetInfo`: `attestationFormats`, `longTouchForReset`, `uvCountSinceLastPinEntry`, `transportsForReset`, `pinComplexityPolicy`, `pinComplexityPolicyURL`, `maxPINLength`, `authenticatorConfigCommands` - `BiometricAccuracyDescriptor`: `iAPARThreshold` - `MetadataStatement`: `friendlyNames`, `iconDark`, `providerLogoLight`, `providerLogoDark`, `keyScope`, `multiDeviceCredentialSupport`, `cxpConfigURL` - `StatusReport`: `certificationProfiles`, `sunsetDate`, `fipsRevision`, `fipsPhysicalSecurityLevel` - `FidoMetadataDownloader` now sends the `If-None-Match` request header set to the `"no"` of the cached BLOB, if any, and handles `304 Not Modified` responses appropriately. - In `FidoMetadataDownloader` if a BLOB download request returns an HTTP failure status, but returns an `ETag` response header matching the `"no"` of the cached BLOB, if any, this is now interpreted like a successful `304 Not Modified` response. - Added `.cachePolicy` setting to `FidoMetadataDownloader` to allow dynamically opting out of falling back to cache when a BLOB download fails. Fixes: - Added `@since` tags to `AuthenticatorStatus` and `FidoMetadataService` javadoc. - All `com.yubico.fido.metadata` enums now deserialize unknown values to `UNKOWN` instead of crashing the parser.
2 parents 6f92b2a + ada34a4 commit fc2fff3

31 files changed

Lines changed: 1559 additions & 245 deletions

NEWS

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,74 @@
1+
== Version 2.9.0 ==
2+
3+
`webauthn-server-core`:
4+
5+
Security fixes:
6+
7+
* Fixed issue where `RelyingParty.finishAssertion` and
8+
`RelyingPartyV2.finishAssertion` could return a successful authentication
9+
result even though the authenticated credential is owned by a different user
10+
than `StartAssertionOptions.username`. For details see CVE-[TBD] or
11+
YSA-2026-02: https://www.yubico.com/support/security-advisories/ysa-2026-02/
12+
** This fix is forward-ported from version 2.8.2 since the issue is also
13+
present in pre-release 2.9.0-alpha1.
14+
15+
Fixes:
16+
17+
* Added `@since` tags to `AttestationTrustSource` javadoc.
18+
19+
`webauthn-server-attestation`:
20+
21+
Changes:
22+
23+
* `FidoMetadataDownloader` builder method `.downloadBlob(URL)` now logs a
24+
warning if the given URL is not an HTTPS URL. Javadoc relaxed to not describe
25+
HTTPS as required since this was never enforced.
26+
27+
New features:
28+
29+
* Added `AuthenticatorStatus.RETIRED` and `Filters.notRetired()`.
30+
* Added `AttachmentHint.ATTACHMENT_HINT_SMART_CARD`.
31+
** Thanks to Misagh Moayyed and Erlend Nukke for the contribution, see
32+
https://github.com/Yubico/java-webauthn-server/pull/468 and
33+
https://github.com/Yubico/java-webauthn-server/pull/467
34+
* Added `UNKNOWN` constant to all enums in `com.yubico.fido.metadata`:
35+
** `AttachmentHint`
36+
** `AuthenticationAlgorithm`
37+
** `AuthenticatorAttestationType`
38+
** `CtapCertificationId`
39+
** `CtapPinUvAuthProtocolVersion`
40+
** `CtapVersion`
41+
** `ProtocolFamily`
42+
** `PublicKeyRepresentationFormat`
43+
** `TransactionConfirmationDisplayType`
44+
* Added enum constant `CtapVersion.FIDO_2_3`.
45+
* Added missing fields to FIDO MDS data model:
46+
** `AuthenticatorGetInfo`: `attestationFormats`, `longTouchForReset`,
47+
`uvCountSinceLastPinEntry`, `transportsForReset`, `pinComplexityPolicy`,
48+
`pinComplexityPolicyURL`, `maxPINLength`, `authenticatorConfigCommands`
49+
** `BiometricAccuracyDescriptor`: `iAPARThreshold`
50+
** `MetadataStatement`: `friendlyNames`, `iconDark`, `providerLogoLight`,
51+
`providerLogoDark`, `keyScope`, `multiDeviceCredentialSupport`,
52+
`cxpConfigURL`
53+
** `StatusReport`: `certificationProfiles`, `sunsetDate`, `fipsRevision`,
54+
`fipsPhysicalSecurityLevel`
55+
* `FidoMetadataDownloader` now sends the `If-None-Match` request header set to
56+
the `"no"` of the cached BLOB, if any, and handles `304 Not Modified`
57+
responses appropriately.
58+
* In `FidoMetadataDownloader` if a BLOB download request returns an HTTP failure
59+
status, but returns an `ETag` response header matching the `"no"` of the
60+
cached BLOB, if any, this is now interpreted like a successful `304 Not
61+
Modified` response.
62+
* Added `.cachePolicy` setting to `FidoMetadataDownloader` to allow dynamically
63+
opting out of falling back to cache when a BLOB download fails.
64+
65+
Fixes:
66+
67+
* Added `@since` tags to `AuthenticatorStatus` and `FidoMetadataService` javadoc.
68+
* All `com.yubico.fido.metadata` enums now deserialize unknown values to
69+
`UNKOWN` instead of crashing the parser.
70+
71+
172
== Version 2.8.2 ==
273

374
Security fixes:

README

Lines changed: 55 additions & 55 deletions
Large diffs are not rendered by default.

buildSrc/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ dependencies {
1616
// Spotless dropped Java 8 support in version 2.33.0
1717
// spotless-plugin-gradle dropped Java <17 support in version 8.0.0
1818
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)) {
19-
implementation("com.diffplug.spotless:spotless-plugin-gradle:8.1.0")
19+
implementation("com.diffplug.spotless:spotless-plugin-gradle:8.2.0")
2020
implementation("io.github.cosmicsilence:gradle-scalafix:0.2.2")
2121
}
2222
}

webauthn-server-attestation/README.adoc

Lines changed: 47 additions & 47 deletions
Large diffs are not rendered by default.

webauthn-server-attestation/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ val integrationTest = task<Test>("integrationTest") {
6161
testClassesDirs = sourceSets["integrationTest"].output.classesDirs
6262
classpath = sourceSets["integrationTest"].runtimeClasspath
6363
shouldRunAfter(tasks.test)
64+
val mdsCacheDir = project.layout.buildDirectory.dir("fido-mds-cache").get().asFile
65+
mdsCacheDir.mkdir()
66+
environment("FIDO_MDS_CACHE_DIR", mdsCacheDir.absolutePath)
6467
}
6568
tasks["check"].dependsOn(integrationTest)
6669

Lines changed: 44 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,34 @@ 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.trustRootCache.get.getBytes
44+
)
45+
46+
val certChain = TestCaches
47+
.cacheSynchronized(
48+
downloader
49+
.fetchHeaderCertChain(
50+
trustRootCert,
51+
downloader
52+
.parseBlob(TestCaches.blobCache.get)
53+
.getBlob
54+
.getHeader,
55+
)
6556
)
6657
.asScala :+ trustRootCert
6758
for { cert <- certChain } {
@@ -76,4 +67,29 @@ class FidoMetadataDownloaderIntegrationTest
7667
}
7768
}
7869

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

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

Lines changed: 10 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,19 @@ 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(
49+
TestCaches.cacheSynchronized(
50+
// Since the integration tests cache downloads to file:
51+
// Use refreshBlob() here to always exercise the HTTP part of the downloader,
52+
// and loadCachedBlob() elsewhere to not cause unnecessary server load.
53+
downloader.refreshBlob()
54+
)
55+
)
5856
.build()
5957
)
6058

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

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

0 commit comments

Comments
 (0)