FIDO2 Yubikey registration not working after enable LDAPS

Hi everyone,
We are running Keycloak with Postgres DB both in a docker container. Our use case is to have users authenticated towards Active Directory and as an additional factor, the users needs to register a FIDO2 YubiKey Bio device. The Yubikey model was fixed by setting up an “Acceptable AAGUIDs”. On Keycloak we also setup TLS according the official guide Configuring TLS - Keycloak (Providing certificates in PEM format). The certificate was created from a own CA, as the system is isolated from internet.
All was working fine. Then I was asked to setup LDAPS for the connection between Keycloak and Active Directory. I implemented this according the official guide Configuring trusted certificates for outgoing requests - Keycloak with a SPI Truststore. All is still working for the authentication of existing users. But for some reasons, I am not able to register new Yubikeys for any users. Doing so, I am getting the error: “Failed to register your Security key. invalid cert path”
I found out, that this is just the case when having the filter for the Yubikey in place with the setting in “Authentication → Policies → Webauthn Policy → Acceptable AAGUIDs → d8522d9f-575b-4866-88a9-ba99fa02f35b” together with the setting “Authentication → Policies → Webauthn Policy → Attestation conveyance preference → Direct”. When removing this two settings, all works fine but like this, I can not define that only the specific Yubikeys should be accepted.
I have also seen this forum entry, which is somehow the same issue: WebAuthn Register Key Fails, invalid cert path
I tried the solution mentioned there without success.
And in this Red Hat KB the issue sounds the same as well: invalid cert path error while registering WebAuthn authenticator in RH-SSO - Red Hat Customer Portal
I am not sure about the solution mentioned there, as I do not have a RH-SSO truststore at all.

This is my docker compose file I run together with an environment file .env:

version: "3.8"

services:
  keycloak:
    image: "quay.io/keycloak/keycloak:${KC_VERSION:?err}"
    restart: always
    command: start-dev
    environment:
      KC_DB: postgres
      KC_DB_URL_HOST: postgres_keycloak
      KC_DB_URL_DATABASE: ${POSTGRES_DB}
      KC_DB_USERNAME: ${KC_DB_USERNAME}
      KC_DB_PASSWORD: ${KC_DB_PASSWORD}
      KEYCLOAK_ADMIN: ${KEYCLOAK_ADMIN}
      KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
      KC_HTTPS_CERTIFICATE_FILE: ${KC_HTTPS_CERTIFICATE_FILE}
      KC_HTTPS_CERTIFICATE_KEY_FILE: ${KC_HTTPS_CERTIFICATE_KEY_FILE}
      KC_SPI_TRUSTSTORE_FILE_FILE: ${KC_SPI_TRUSTSTORE_FILE_FILE}
      KC_SPI_TRUSTSTORE_FILE_PASSWORD: ${KC_SPI_TRUSTSTORE_FILE_PASSWORD}
    ports:
      - ${KC_HTTP_PORT}:8080
      - ${KC_HTTPS_PORT}:8443
    depends_on:
      postgres_keycloak:
        condition: service_healthy
    volumes:
      - ./certs:/etc/ssl/c2i-certs
    networks:
      - keycloak_network


  postgres_keycloak:
    image:  "postgres:${POSTGRES_VERSION:?err}"
    restart: always
    volumes:
      - ./data/postgresql/data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${KC_DB_USERNAME}
      POSTGRES_PASSWORD: ${KC_DB_PASSWORD}
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "${KC_DB_USERNAME}"]
      interval: 10s
      start_period: 5s
    networks:
      - keycloak_network

networks:
  keycloak_network:
    driver: bridge

I am running keycloak in version 23.0.4.

I have also tried to have TLS enabled with a Java keystore instead of setting the PEM files with the following env variables:

KC_HTTPS_KEY_STORE_FILE: ${KC_HTTPS_KEY_STORE_FILE}
KC_HTTPS_KEY_STORE_PASSWORD: ${KC_HTTPS_KEY_STORE_PASSWORD}
KC_HTTPS_KEY_STORE_TYPE: ${KC_HTTPS_KEY_STORE_TYPE}

And add an additional Java truststore for HTTPS with the following env variables:

KC_HTTPS_TRUST_STORE_FILE: ${KC_HTTPS_TRUST_STORE_FILE}
KC_HTTPS_TRUST_STORE_PASSWORD: ${KC_HTTPS_TRUST_STORE_PASSWORD}

The variables are pointing to the same Trusstore I am using for the SPI Truststore, as the CA Cert is the same that is signing the Active Directory and the keycloak https cert.

This are the logs I am getting from keycloak:

keycloak-keycloak-1           | 2024-01-22 15:14:03,524 DEBUG [org.keycloak.authentication.requiredactions.WebAuthnRegister] (executor-thread-13) invalid cert path: com.webauthn4j.validator.exception.CertificateException: invalid cert path
keycloak-keycloak-1           |         at com.webauthn4j.validator.attestation.trustworthiness.certpath.CertPathTrustworthinessValidatorBase.validate(CertPathTrustworthinessValidatorBase.java:66)
keycloak-keycloak-1           |         at com.webauthn4j.validator.AttestationValidator.validate(AttestationValidator.java:121)
keycloak-keycloak-1           |         at com.webauthn4j.validator.RegistrationDataValidator.validate(RegistrationDataValidator.java:192)
keycloak-keycloak-1           |         at com.webauthn4j.WebAuthnRegistrationManager.validate(WebAuthnRegistrationManager.java:209)
keycloak-keycloak-1           |         at org.keycloak.authentication.requiredactions.WebAuthnRegister.processAction(WebAuthnRegister.java:238)
keycloak-keycloak-1           |         at org.keycloak.services.resources.LoginActionsService.processRequireAction(LoginActionsService.java:1090)
keycloak-keycloak-1           |         at org.keycloak.services.resources.LoginActionsService.requiredActionPOST(LoginActionsService.java:1025)
keycloak-keycloak-1           |         at org.keycloak.services.resources.LoginActionsService$quarkusrestinvoker$requiredActionPOST_677a8efd4e80bfe1b3aa5a0d6fca2043252c9624.invoke(Unknown Source)
keycloak-keycloak-1           |         at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
keycloak-keycloak-1           |         at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:141)
keycloak-keycloak-1           |         at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:145)
keycloak-keycloak-1           |         at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:576)
keycloak-keycloak-1           |         at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
keycloak-keycloak-1           |         at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
keycloak-keycloak-1           |         at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
keycloak-keycloak-1           |         at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
keycloak-keycloak-1           |         at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
keycloak-keycloak-1           |         at java.base/java.lang.Thread.run(Thread.java:840)
keycloak-keycloak-1           | Caused by: java.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors
keycloak-keycloak-1           |         at java.base/sun.security.provider.certpath.PKIXCertPathValidator.validate(PKIXCertPathValidator.java:157)
keycloak-keycloak-1           |         at java.base/sun.security.provider.certpath.PKIXCertPathValidator.engineValidate(PKIXCertPathValidator.java:83)
keycloak-keycloak-1           |         at java.base/java.security.cert.CertPathValidator.validate(CertPathValidator.java:309)
keycloak-keycloak-1           |         at com.webauthn4j.validator.attestation.trustworthiness.certpath.CertPathTrustworthinessValidatorBase.validate(CertPathTrustworthinessValidatorBase.java:62)
keycloak-keycloak-1           |         ... 17 more
keycloak-keycloak-1           |
keycloak-keycloak-1           | 2024-01-22 15:14:03,525 WARN  [org.keycloak.authentication.requiredactions.WebAuthnRegister] (executor-thread-13) webauthn-error-registration
keycloak-keycloak-1           | 2024-01-22 15:14:03,525 DEBUG [org.keycloak.transaction.JtaTransactionWrapper] (executor-thread-13) new JtaTransactionWrapper
keycloak-keycloak-1           | 2024-01-22 15:14:03,526 DEBUG [org.keycloak.transaction.JtaTransactionWrapper] (executor-thread-13) was existing? true
keycloak-keycloak-1           | 2024-01-22 15:14:03,529 DEBUG [org.keycloak.transaction.JtaTransactionWrapper] (executor-thread-13) JtaTransactionWrapper  commit
keycloak-keycloak-1           | 2024-01-22 15:14:03,529 DEBUG [org.keycloak.transaction.JtaTransactionWrapper] (executor-thread-13) JtaTransactionWrapper end
keycloak-keycloak-1           | 2024-01-22 15:14:03,529 DEBUG [org.keycloak.transaction.JtaTransactionWrapper] (executor-thread-13) JtaTransactionWrapper resuming suspended
keycloak-keycloak-1           | 2024-01-22 15:14:03,529 WARN  [org.keycloak.events] (executor-thread-13) type=CUSTOM_REQUIRED_ACTION_ERROR, realmId=1973b5a6-766b-4834-b182-378e7e4d36cb, clientId=myclient, userId=f:b6aeaed9-71ca-4ebb-8d89-3b8dd7b059f2:demo.mest, ipAddress=172.16.12.3, error=invalid_registration, credential_type=webauthn, auth_method=openid-connect, web_authn_registration_error_detail='invalid cert path', custom_required_action=webauthn-register, response_type=code, web_authn_registration_error=webauthn-error-registration, redirect_uri=https://www.keycloak.org/app/#url=https://keycloak.dev.******/&realm=mest-realm&client=myclient, remember_me=false, code_id=82ac68cb-8eb6-42b7-b5c1-975540e2d632, response_mode=fragment, username=demo.mest

Has anybody some idea, what has to be done to fix this problem?
Thank you very much.

We were able to fix this problem.
The main problem here was, as soon you enable ldaps instead of ldap, keycloak enables all the security extensions. By enabling these security extensions, the certs installed on the Yubikeys are also checked towards a Yubikey CA trust anchor. To make this work, you have to import this Yubikey CA trust anchor into the SPI truststore as well (together with the Root CA that is signing the ldaps server cert). We found this out by analysing the Java class throwing the exception: com.webauthn4j.validator.attestation.trustworthiness.certpath.CertPathTrustworthinessValidatorBase.validate(CertPathTrustworthinessValidatorBase.java:66).

The Yubikey CA cert we found here: https://developers.yubico.com/U2F/yubico-metadata.json

After importing this Yubikey CA cert into SPI truststore as well and restart the keycloak container, registration of new Yubikeys (of the specific filtered “Acceptable AAGUIDs”) was possible again.