pingidentity / ldapsdk

UnboundID LDAP SDK for Java
Other
334 stars 81 forks source link

Handshake failed despite cipher being present and protocol being enabled #115

Open GeluOltean opened 3 years ago

GeluOltean commented 3 years ago

Hey everyone. I recently ran into an issue when switching from using Java's naming directory to implementing LDAP integration via the UnboundID ldapsdk. I've extracted the parts that seem to throw the exception into a separate class and can provide it if required, so please let me know. Your help is greatly appreciated.

Specs

OS: Ubuntu 20.04.2 LTS x86_64 JDK version: Adopt OpenJDK 8.0.272 Hotspot TLS 1 and 1.1 via OpenSSL manually enabled system-wide UnboundID LDAPsdk version: 4.0.11, 4.0.13 and 6.0.0

Description

TLS handshake fails consistently when trying to connect to a server with the following protocol and ciphers. Details via OpenSSL:

---
No client certificate CA names sent
Peer signing digest: MD5-SHA1
Peer signature type: RSA
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 5328 bytes and written 280 bytes
Verification: OK
---
New, TLSv1.0, Cipher is ECDHE-RSA-AES128-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1
    Cipher    : ECDHE-RSA-AES128-SHA
    Session-ID: <ID>
    Session-ID-ctx: 
    Master-Key: <KEY>
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1626243347
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: yes

Besides the default enabled cipher suites, I added some additional ones, as well as older protocols. The code is Scala, but it's descriptive (if you'd like, I can convert it to Java).

val ciphers = Set(
    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
    "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
    "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384",
    "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
    "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256")
val suites = SSLUtil.getEnabledSSLCipherSuites
suites.addAll(ciphers.asJava)
SSLUtil.setEnabledSSLCipherSuites(suites)

SSLUtil.setEnabledSSLProtocols(List(SSLUtil.SSL_PROTOCOL_SSL_3, SSLUtil.SSL_PROTOCOL_TLS_1, SSLUtil.SSL_PROTOCOL_TLS_1_1, SSLUtil.SSL_PROTOCOL_TLS_1_2, SSLUtil.SSL_PROTOCOL_TLS_1_3).asJava)

Then, for the SSLUtil and LDAPConnection instantiation:

val ssl = new SSLUtil(new TrustAllTrustManager())
val conn = new LDAPConnection(ssl.createSSLSocketFactory(SSLUtil.SSL_PROTOCOL_TLS_1_1), ldapUri.getHost, ldapUri.getPort, ldapPrincipal, ldapPass)
conn.bind(new SimpleBindRequest(ldapPrincipal, ldapPass))

This fails when instantiating the LDAPConnection with the following stack trace:

Exception in thread "main" LDAPException(resultCode=91 (connect error), errorMessage='An error occurred while attempting to connect to server <LDAPURL>:  IOException(LDAPException(resultCode=91 (connect error), errorMessage='An error occurred while attempting to establish a connection to server <LDAPURL>:  SSLHandshakeException(No appropriate protocol (protocol is disabled or cipher suites are inappropriate)), ldapSDKVersion=6.0.0, revision=524c20f3bbcc0d83fb56b9e136a2fd3a7f60437d'))')
    at com.unboundid.ldap.sdk.LDAPConnection.connect(LDAPConnection.java:915)
    at com.unboundid.ldap.sdk.LDAPConnection.connect(LDAPConnection.java:802)
    at com.unboundid.ldap.sdk.LDAPConnection.connect(LDAPConnection.java:740)
    at com.unboundid.ldap.sdk.LDAPConnection.<init>(LDAPConnection.java:560)
    at com.unboundid.ldap.sdk.LDAPConnection.<init>(LDAPConnection.java:696)
    at com.unboundid.ldap.sdk.LDAPConnection.<init>(LDAPConnection.java:658)
    at NewScratcher$.delayedEndpoint$NewScratcher$1(Scratcher.scala:144)
    at NewScratcher$delayedInit$body.apply(Scratcher.scala:102)
    at scala.Function0.apply$mcV$sp(Function0.scala:39)
    at scala.Function0.apply$mcV$sp$(Function0.scala:39)
    at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:17)
    at scala.App.$anonfun$main$1(App.scala:73)
    at scala.App.$anonfun$main$1$adapted(App.scala:73)
    at scala.collection.IterableOnceOps.foreach(IterableOnce.scala:553)
    at scala.collection.IterableOnceOps.foreach$(IterableOnce.scala:551)
    at scala.collection.AbstractIterable.foreach(Iterable.scala:920)
    at scala.App.main(App.scala:73)
    at scala.App.main$(App.scala:71)
    at NewScratcher$.main(Scratcher.scala:102)
    at NewScratcher.main(Scratcher.scala)
Caused by: java.io.IOException: LDAPException(resultCode=91 (connect error), errorMessage='An error occurred while attempting to establish a connection to server <LDAPURL>:  SSLHandshakeException(No appropriate protocol (protocol is disabled or cipher suites are inappropriate)), ldapSDKVersion=6.0.0, revision=524c20f3bbcc0d83fb56b9e136a2fd3a7f60437d')
    at com.unboundid.ldap.sdk.LDAPConnectionInternals.<init>(LDAPConnectionInternals.java:204)
    at com.unboundid.ldap.sdk.LDAPConnection.connect(LDAPConnection.java:904)
    ... 19 more
Caused by: LDAPException(resultCode=91 (connect error), errorMessage='An error occurred while attempting to establish a connection to server <LDAPURL>:  SSLHandshakeException(No appropriate protocol (protocol is disabled or cipher suites are inappropriate)), ldapSDKVersion=6.0.0, revision=524c20f3bbcc0d83fb56b9e136a2fd3a7f60437d')
    at com.unboundid.ldap.sdk.ConnectThread.getConnectedSocket(ConnectThread.java:287)
    at com.unboundid.ldap.sdk.LDAPConnectionInternals.<init>(LDAPConnectionInternals.java:185)
    ... 20 more
Caused by: javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
    at sun.security.ssl.HandshakeContext.<init>(HandshakeContext.java:171)
    at sun.security.ssl.ClientHandshakeContext.<init>(ClientHandshakeContext.java:98)
    at sun.security.ssl.TransportContext.kickstart(TransportContext.java:220)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:428)
    at com.unboundid.util.ssl.SetEnabledProtocolsAndCipherSuitesSocket.startHandshake(SetEnabledProtocolsAndCipherSuitesSocket.java:926)
    at com.unboundid.ldap.sdk.ConnectThread.run(ConnectThread.java:173)

Process finished with exit code 1

When starting up the app, I added some debug options for the VM, specifically -Djdk.tls.client.protocols="TLSv1.1" -Dhttps.protocols="TLSv1.1" -Djavax.net.debug=all. With the debugging options on, there's some more interesting bits which I've added to the log_ldap.txt file. Oddly enough the last print of com.unboundid.util.ssl.TLSCipherSuiteSelector Results: only includes TLSv1.2 and TLSv1.3, but when calling SSLUtil.getEnabledSSLProtocols the result includes SSLv3, TLSv1, TLSv1.1, TLSv1.2, TLSv1.3.

Please let me know if there is any other information that I can provide. log_ldap.txt

dirmgr commented 3 years ago

I’ve looked into this, but haven’t been able to reproduce the problem that you’re describing. I can write a simple Java program that uses TLSv1 or TLSv1.1 to interact with a server that only supports those protocol versions.

The 6.0.0 release of the LDAP SDK did disable support for TLSv1 and TLSv1.1 by default because those protocol versions are no longer considered secure or recommended as per RFC 8996. It also disabled support for TLS cipher suites that rely on the SHA-1 message digest algorithm (which is also no longer considered secure) and RSA key exchange (which doesn’t support forward secrecy). However, while we certainly don’t recommend using those TLS protocol versions or cipher suites, it should still be possible to explicitly enable support for them through the SSLUtil.setEnabledSSLProtocols and SSLUtil.setEnabledSSLCipherSuites methods, and it looks like you’re using those in your code.

It’s definitely weird that your output shows that the later invocation of TLSCipherSuiteSelector only has TLSv1.2 and TLSv1.3 enabled. That comes directly from SSLUtil.getEnabledSSLProtocols, so I have no idea why that output would not be aligned.

There are only a couple of things that I can think of that might potentially be responsible for this:

If none of the above help, then I would recommend trying a couple of other things:

GeluOltean commented 3 years ago

Sorry for the delay. I will try out the suggestions and report back with the code examples by the end of tomorrow at the latest. Thank you for the detailed response!

GeluOltean commented 3 years ago

It turns out we had to manually enable TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA. I've added two runnable files, one Scala and one Java, with the .txt extension to be able to upload to Github.

Not sure if this is an issue, however, the JVM debug options will list only TLSv1.2 and TLSv1.3 for both the Java and Scala versions in all the following scenarios:

Providing them as system properties to the JVM does work as expected, and we'll be using this option going forward.

Thank you for the support!

LdapTestScala.txt LdapTestScala.log LdapTestJava.txt LdapTestJava.log

mircea-cm commented 3 years ago

Hi @GeluOltean We had the same problem, and by using your approach it works.

private void addTLS1Protocols() { String[] missingCiphers = new String[]{ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_GCM_SHA384", "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA" }; Set ciphers = new HashSet(); ciphers.addAll(SSLUtil.getEnabledSSLCipherSuites()); ciphers.addAll(Arrays.asList(missingCiphers)); SSLUtil.setEnabledSSLCipherSuites(ciphers); SSLUtil.setEnabledSSLProtocols(Arrays.asList(SSLUtil.SSL_PROTOCOL_SSL_3, SSLUtil.SSL_PROTOCOL_TLS_1, SSLUtil.SSL_PROTOCOL_TLS_1_1, SSLUtil.SSL_PROTOCOL_TLS_1_2, SSLUtil.SSL_PROTOCOL_TLS_1_3)); }

and then addTLS1Protocols(); SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager()); SSLSocketFactory socketFactory = sslUtil.createSSLSocketFactory(SSLUtil.SSL_PROTOCOL_TLS_1_1); template = new LDAPConnection(socketFactory, this.serverAddress, this.serverPort);

It worked out of the box, without the need to enable them via system properties.

EDIT: code markup doesn't seem to work

GeluOltean commented 3 years ago

This might be something related to how the .jar files are generated by SBT. Anyway, since Mircea mentioned the Java approach works well, and on our side the system property resolves the problem, I think the issue can be closed. Thank you for the support, Neil!