Ldap mTLS connection problem on Base Command Manager 11 Ubuntu 24.04

Hi,

I’m new to keycloak and having hard time connecting the realm to Base Command Manager’s LDAP server. I’m running version 26.4.7 with native OpenJDK installation in /opt on Ubuntu 24.04.

It requires mTLS to connect, and “Test connection” succeeds with ldaps:// protocol and seems it recognizes the ca.pem file I put into the conf/truststore.
However, “Test authentication” always fails.
I found in the documentation that I might need to set the following paramaters at least.

spi-connections-http-client–default–client-keystore
spi-connections-http-client–default–client-keystore-password

I tried several ways to pass parameters to the server, such as kc.conf parameter lines, and kc.sh command line options, but no luck.
It seems keycloak server is ignoring the parameter, since even if I set a wrong password, it does not report any errors. Simply the authentication connection fails. Java debug message tells me that I need to present client cert to the server.
How do I enable outgoing TLS connection to the server?

I’d appreciate help. Thanks!

Hi dasniko,
Thanks for your message.
I believe I have no problem accepting the remote certificate. The problem is, the local keycloak server cannot present its certs of itself to remote server.
I referred to this page.

Do you have any idea?

Here is the excerpt of java error message. I’d appreciate if you could take a look. Looks like we trust the server but they do not trust us.
Just to make sure, I am using the same ldap client key/cert files that working nslcd is using. I just created a p12 format file for java to make use of. We can authenticate user login through ldap with no problem. Only keycloak does not follow the rule. So I think I am not currently passing correct parameters to the keycloak server.

Thanks!

javax.net.ssl|DEBUG|30 2E|executor-thread-3|2025-12-09 11:17:29.899 JST|SSLExtensions.java:219|Ignore unavailable extension: signature_algorithms_cert
javax.net.ssl|DEBUG|30 2E|executor-thread-3|2025-12-09 11:17:29.899 JST|CertificateMessage.java:1144|Consuming server Certificate handshake message (

###server certificate here

javax.net.ssl|DEBUG|30 2E|executor-thread-3|2025-12-09 11:17:29.899 JST|SSLExtensions.java:185|Ignore unavailable extension: status_request
javax.net.ssl|DEBUG|30 2E|executor-thread-3|2025-12-09 11:17:29.899 JST|SSLExtensions.java:185|Ignore unavailable extension: status_request
javax.net.ssl|DEBUG|30 2E|executor-thread-3|2025-12-09 11:17:29.899 JST|X509TrustManagerImpl.java:246|Found trusted certificate (

###server certificate here

javax.net.ssl|DEBUG|30 2E|executor-thread-3|2025-12-09 11:17:29.900 JST|CertificateVerify.java:1177|Consuming CertificateVerify handshake message (

javax.net.ssl|DEBUG|30 2E|executor-thread-3|2025-12-09 11:17:29.900 JST|Finished.java:925|Consuming server Finished handshake message (

javax.net.ssl|DEBUG|30 2E|executor-thread-3|2025-12-09 11:17:29.900 JST|SSLCipher.java:1836|KeyLimit read side: algorithm = AES/GCM/NoPadding:KEYUPDATE
countdown value = 137438953472
javax.net.ssl|ALL|30 2E|executor-thread-3|2025-12-09 11:17:29.900 JST|X509Authentication.java:205|X509KeyManager class: sun.security.ssl.DummyX509KeyManager
javax.net.ssl|ALL|30 2E|executor-thread-3|2025-12-09 11:17:29.900 JST|X509Authentication.java:225|No X.509 cert selected for \[EC, EdDSA, RSASSA-PSS, RSA\]
javax.net.ssl|WARNING|30 2E|executor-thread-3|2025-12-09 11:17:29.900 JST|CertificateMessage.java:1057|No available authentication scheme
javax.net.ssl|DEBUG|30 2E|executor-thread-3|2025-12-09 11:17:29.900 JST|CertificateMessage.java:1070|No available client authentication scheme
javax.net.ssl|DEBUG|30 2E|executor-thread-3|2025-12-09 11:17:29.900 JST|CertificateMessage.java:1103|Produced client Certificate message (
“Certificate”: {
“certificate_request_context”: “”,
“certificate_list”: \[
\]
javax.net.ssl|DEBUG|30 2E|executor-thread-3|2025-12-09 11:17:29.900 JST|CertificateVerify.java:1096|No X.509 credentials negotiated for CertificateVerify
javax.net.ssl|DEBUG|30 2E|executor-thread-3|2025-12-09 11:17:29.900 JST|Finished.java:687|Produced client Finished handshake message (
“Finished”: {

javax.net.ssl|DEBUG|30 2E|executor-thread-3|2025-12-09 11:17:29.900 JST|SSLCipher.java:1987|KeyLimit write side: algorithm = AES/GCM/NoPadding:KEYUPDATE
countdown value = 137438953472
javax.net.ssl|DEBUG|70 1D|Thread-47|2025-12-09 11:17:29.939 JST|Alert.java:232|Received alert message (
“Alert”: {
“level”      : “fatal”,
“description”: “certificate_required”
}
)
javax.net.ssl|ERROR|70 1D|Thread-47|2025-12-09 11:17:29.939 JST|TransportContext.java:375|Fatal (CERTIFICATE_REQUIRED): ved fatal alert: certificate_required (
“throwable” : {
javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_required
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:130)
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:370)
at java.base/sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:287)
at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:209)
at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172)
at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1509)
at java.base/sun.security.ssl.SSLSocketImpl.readApplicationRecord(SSLSocketImpl.java:1480)
at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:1066)
at java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:291)
at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:347)
at java.base/java.io.BufferedInputStream.implRead(BufferedInputStream.java:420)
at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:399)
at java.naming/com.sun.jndi.ldap.Connection.run(Connection.java:899)
at java.base/java.lang.Thread.run(Thread.java:1583)}

)
javax.net.ssl|DEBUG|70 1D|Thread-47|2025-12-09 11:17:29.939 JST|SSLSocketImpl.java:1749|close the underlying socket
javax.net.ssl|DEBUG|70 1D|Thread-47|2025-12-09 11:17:29.939 JST|SSLSocketImpl.java:1775|close the SSL connection (initiative)
javax.net.ssl|WARNING|70 1D|Thread-47|2025-12-09 11:17:29.940 JST|SSLSocketImpl.java:1672|handling exception (
“throwable” : {
javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_required
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:130)
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:370)
at java.base/sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:287)
at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:209)
at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:172)
at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1509)
at java.base/sun.security.ssl.SSLSocketImpl.readApplicationRecord(SSLSocketImpl.java:1480)
at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:1066)
at java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:291)
at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:347)
at java.base/java.io.BufferedInputStream.implRead(BufferedInputStream.java:420)
at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:399)
at java.naming/com.sun.jndi.ldap.Connection.run(Connection.java:899)
at java.base/java.lang.Thread.run(Thread.java:1583)}

)
javax.net.ssl|ALL|70 1D|Thread-47|2025-12-09 11:17:29.940 JST|SSLSocketImpl.java:1330|Closing output stream
2025-12-09 11:17:29,940 ERROR \[org.keycloak.services\] (executor-thread-3) KC-SERVICES0055: Error when authenticating to LDAP: LDAP connection has been closed: javax.naming.CommunicationException: LDAP connection has been closed \[Root exception is java.io.IOException: LDAP connection has been closed\]
at java.naming/com.sun.jndi.ldap.Connection.readReply(Connection.java:471)
at java.naming/com.sun.jndi.ldap.LdapClient.ldapBind(LdapClient.java:369)
at java.naming/com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:218)
at java.naming/com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2896)
at java.naming/com.sun.jndi.ldap.LdapCtx.(LdapCtx.java:349)
at java.naming/com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxFromUrl(LdapCtxFactory.java:229)
at java.naming/com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:189)
at java.naming/com.sun.jndi.ldap.LdapCtxFactory.getUsingURLs(LdapCtxFactory.java:247)
at java.naming/com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(LdapCtxFactory.java:154)
at java.naming/com.sun.jndi.ldap.LdapCtxFactory.getInitialContext(LdapCtxFactory.java:84)
at java.naming/javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:520)
at java.naming/javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:305)
at java.naming/javax.naming.InitialContext.init(InitialContext.java:236)
at java.naming/javax.naming.ldap.InitialLdapContext.(InitialLdapContext.java:154)
at org.keycloak.storage.ldap.idm.store.ldap.SessionBoundInitialLdapContext.(SessionBoundInitialLdapContext.java:34)
at org.keycloak.storage.ldap.idm.store.ldap.LDAPContextManager.createLdapContext(LDAPContextManager.java:80)
at org.keycloak.storage.ldap.idm.store.ldap.LDAPContextManager.getLdapContext(LDAPContextManager.java:105)
at org.keycloak.services.managers.LDAPServerCapabilitiesManager.testLDAP(LDAPServerCapabilitiesManager.java:202)
at org.keycloak.services.resources.admin.TestLdapConnectionResource.testLDAPConnection(TestLdapConnectionResource.java:92)
at org.keycloak.services.resources.admin.TestLdapConnectionResource$quarkusrestinvoker$testLDAPConnection_c31f9cb262cabd2403483720b840b8d28e8baa95.invoke(Unknown Source)
at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:183)
at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:147)
at io.quarkus.vertx.core.runtime.VertxCoreRecorder$15.runWith(VertxCoreRecorder.java:645)
at org.jboss.threads.EnhancedQueueExecutor$Task.doRunWith(EnhancedQueueExecutor.java:2651)
at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2630)
at org.jboss.threads.EnhancedQueueExecutor.runThreadBody(EnhancedQueueExecutor.java:1622)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1589)
at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:11)
at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:11)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: java.io.IOException: LDAP connection has been closed
at java.naming/com.sun.jndi.ldap.LdapRequest.getReplyBer(LdapRequest.java:133)
at java.naming/com.sun.jndi.ldap.Connection.readReply(Connection.java:447)
… 31 more

javax.net.ssl|DEBUG|10 74|vert.x-eventloop-thread-129|2025-12-09 11:20:22.225 JST|Alert.java:232|Received alert message (
“Alert”: {
“level”      : “warning”,
“description”: “close_notify”
}
)
javax.net.ssl|ALL|10 74|vert.x-eventloop-thread-129|2025-12-09 11:20:22.225 JST|SSLEngineImpl.java:825|Closing outbound of SSLEngine

Hi dasniko,

I think I read the document and took the meaning the way I wanted it to be. But the reality is not what I thought/wished and Keycloak cannot act as a ldap client to a ldap server which demands mTLS, correct?

I guessed this as generic(?) mechanism of TLS, hence ldaps client access would be possible against ldap. But I realized that this really specifically means https access to a web server that demands mTLS. ldaps is not applicable, right?

mTLS is IMHO used for authenticating the “other” side, not just for transport encryption.
Keycloak only support TLS secured transport encryption for outgoing requests. For “authenticating” against the LDAP server, there is only the “Bind user DN” with the “simple” bind type, no other authentication against an LDAP server is supported.

dasniko,

Thanks for the clarification.
I just found this thread and it looks like a workaround for my situation as well.