Skip to content

Commit 9adcde5

Browse files
committed
Check hash of cached trust root cert as promised in JavaDoc
1 parent 3b38ef6 commit 9adcde5

File tree

3 files changed

+147
-9
lines changed

3 files changed

+147
-9
lines changed

NEWS

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
== Version 2.1.0 (unreleased) ==
22

3+
`webauthn-server-core`:
4+
35
Changes:
46

57
* Log messages on attestation certificate path validation failure now include
@@ -33,6 +35,16 @@ Fixes:
3335
`webauthn-server-parent` to unpublished test meta-module.
3436

3537

38+
`webauthn-server-attestation`:
39+
40+
Fixes:
41+
42+
* Fixed various typos and mistakes in JavaDocs.
43+
* `FidoMetadataDownloader` now verifies the SHA-256 hash of the cached trust
44+
root certificate, as promised in the JavaDoc of `useTrustRootCacheFile` and
45+
`useTrustRootCache`.
46+
47+
3648
== Version 2.0.0 ==
3749

3850
This release removes deprecated APIs and changes some defaults to better align

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -867,13 +867,16 @@ private X509Certificate retrieveTrustRootCert()
867867

868868
X509Certificate cert = null;
869869
if (cachedContents.isPresent()) {
870-
try {
871-
final X509Certificate cachedCert =
872-
CertificateParser.parseDer(cachedContents.get().getBytes());
873-
cachedCert.checkValidity(Date.from(clock.instant()));
874-
cert = cachedCert;
875-
} catch (CertificateException e) {
876-
// Fall through
870+
final ByteArray verifiedCachedContents = verifyHash(cachedContents.get(), trustRootSha256);
871+
if (verifiedCachedContents != null) {
872+
try {
873+
final X509Certificate cachedCert =
874+
CertificateParser.parseDer(verifiedCachedContents.getBytes());
875+
cachedCert.checkValidity(Date.from(clock.instant()));
876+
cert = cachedCert;
877+
} catch (CertificateException e) {
878+
// Fall through
879+
}
877880
}
878881
}
879882

webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/FidoMetadataDownloaderSpec.scala

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,14 @@ class FidoMetadataDownloaderSpec
449449
.expectLegalHeader(
450450
"Kom ihåg att du aldrig får snyta dig i mattan!"
451451
)
452-
.useDefaultTrustRoot()
452+
.downloadTrustRoot(
453+
new URL("https://localhost:12345/nonexistent.dev.null"),
454+
Set(
455+
TestAuthenticator.sha256(
456+
new ByteArray(trustRootCert.getEncoded)
457+
)
458+
).asJava,
459+
)
453460
.useTrustRootCacheFile(cacheFile)
454461
.useBlob(blobJwt)
455462
.clock(Clock.fixed(CertValidFrom, ZoneOffset.UTC))
@@ -626,7 +633,14 @@ class FidoMetadataDownloaderSpec
626633
.expectLegalHeader(
627634
"Kom ihåg att du aldrig får snyta dig i mattan!"
628635
)
629-
.useDefaultTrustRoot()
636+
.downloadTrustRoot(
637+
new URL("https://localhost:12345/nonexistent.dev.null"),
638+
Set(
639+
TestAuthenticator.sha256(
640+
new ByteArray(trustRootCert.getEncoded)
641+
)
642+
).asJava,
643+
)
630644
.useTrustRootCache(
631645
() => Optional.of(new ByteArray(trustRootCert.getEncoded)),
632646
newCache => {
@@ -693,6 +707,115 @@ class FidoMetadataDownloaderSpec
693707
testWithHashes(Set(goodHash)) should not be null
694708
testWithHashes(Set(badHash, goodHash)) should not be null
695709
}
710+
711+
it("The cached trust root cert must match one of the expected SHA256 hashes.") {
712+
val (cachedTrustRootCert, cachedCaKeypair, cachedCaName) =
713+
makeTrustRootCert()
714+
val (cachedRootBlobCert, cachedRootBlobKeypair, _) =
715+
makeCert(cachedCaKeypair, cachedCaName)
716+
val cachedRootBlobJwt = makeBlob(
717+
List(cachedRootBlobCert),
718+
cachedRootBlobKeypair,
719+
LocalDate.now(),
720+
)
721+
val cachedRootCrls = List[CRL](
722+
TestAuthenticator.buildCrl(
723+
cachedCaName,
724+
cachedCaKeypair.getPrivate,
725+
"SHA256withECDSA",
726+
CertValidFrom,
727+
CertValidTo,
728+
)
729+
)
730+
731+
val (downloadedTrustRootCert, downloadedCaKeypair, downloadedCaName) =
732+
makeTrustRootCert()
733+
val (downloadedRootBlobCert, downloadedRootBlobKeypair, _) =
734+
makeCert(downloadedCaKeypair, downloadedCaName)
735+
val downloadedRootBlobJwt = makeBlob(
736+
List(downloadedRootBlobCert),
737+
downloadedRootBlobKeypair,
738+
LocalDate.now(),
739+
)
740+
val downloadedRootCrls = List[CRL](
741+
TestAuthenticator.buildCrl(
742+
downloadedCaName,
743+
downloadedCaKeypair.getPrivate,
744+
"SHA256withECDSA",
745+
CertValidFrom,
746+
CertValidTo,
747+
)
748+
)
749+
750+
val (server, serverUrl, httpsCert) =
751+
makeHttpServer(
752+
"/trust-root.der",
753+
downloadedTrustRootCert.getEncoded,
754+
)
755+
startServer(server)
756+
757+
def testWithHashes(
758+
hashes: Set[ByteArray],
759+
blobJwt: String,
760+
crls: List[CRL],
761+
): (MetadataBLOB, Option[ByteArray]) = {
762+
var writtenCache: Option[ByteArray] = None
763+
764+
val blob = load(
765+
FidoMetadataDownloader
766+
.builder()
767+
.expectLegalHeader(
768+
"Kom ihåg att du aldrig får snyta dig i mattan!"
769+
)
770+
.downloadTrustRoot(
771+
new URL(s"${serverUrl}/trust-root.der"),
772+
hashes.asJava,
773+
)
774+
.useTrustRootCache(
775+
() =>
776+
Optional.of(new ByteArray(cachedTrustRootCert.getEncoded)),
777+
downloaded => { writtenCache = Some(downloaded) },
778+
)
779+
.useBlob(blobJwt)
780+
.useCrls(crls.asJava)
781+
.trustHttpsCerts(httpsCert)
782+
.clock(Clock.fixed(CertValidFrom, ZoneOffset.UTC))
783+
.build()
784+
)
785+
786+
(blob, writtenCache)
787+
}
788+
789+
{
790+
val (blob, writtenCache) = testWithHashes(
791+
Set(
792+
TestAuthenticator.sha256(
793+
new ByteArray(cachedTrustRootCert.getEncoded)
794+
)
795+
),
796+
cachedRootBlobJwt,
797+
cachedRootCrls,
798+
)
799+
blob should not be null
800+
writtenCache should be(None)
801+
}
802+
803+
{
804+
val (blob, writtenCache) = testWithHashes(
805+
Set(
806+
TestAuthenticator.sha256(
807+
new ByteArray(downloadedTrustRootCert.getEncoded)
808+
)
809+
),
810+
downloadedRootBlobJwt,
811+
downloadedRootCrls,
812+
)
813+
blob should not be null
814+
writtenCache should be(
815+
Some(new ByteArray(downloadedTrustRootCert.getEncoded))
816+
)
817+
}
818+
}
696819
}
697820

698821
describe("2. To validate the digital certificates used in the digital signature, the certificate revocation information MUST be available in the form of CRLs at the respective MDS CRL location e.g. More information can be found at https://fidoalliance.org/metadata/") {

0 commit comments

Comments
 (0)