1

I am trying to use SSL with Tomcat and an APR connector. I can make changes to the server, but not to the existing Java client.

For some reason, whenever a Java client connects to the Tomcat server using SSL, it is not able to connect - the connection is reset by the server. However, the exact same Java code can connect to the same server on a different port hosted by Apache with SSL. In addition, non-Java code (like curl) can connect to the Tomcat SSL connection.

I have gotten this to work by forcing the Java client to use the TLSv1 protocol (-Dhttps.protocols=TLSv1). However, this is not a practical solution, because I cannot release an update at this time for our Java clients.

Since this works with Apache on the server, it seems to me that I should be able to make some sort of configuration change on the server to also work with Tomcat, without needing to change the Java clients.

In summary: Java connecting to Tomcat SSL = FAIL

curl connecting to Tomcat SSL = good

Java connecting to Apache SSL = good

curl connecting to Apache SSL = good

Here is some example Java code that illustrates the problem. I'm running with Java 6 on Mac OS X.

public class SSLConnectTest {
    public static void main(String[] args) throws Exception {
        System.setProperty( "javax.net.debug", "all" );

        testConnection( "https://secure2.360works.com" ); //Apache running SSL. This works.
        testConnection( "https://secure2.360works.com:8443/" ); //Tomcat running SSL and APR. This fails.
    }

    private static void testConnection( String urlString ) throws IOException {
        new URL( urlString ).openStream().close();
    }
}

Here is what happens in the SSL handshake:

trigger seeding of SecureRandom
done seeding SecureRandom
Allow unsafe renegotiation: false
Allow legacy hello messages: true
Is initial handshake: true
Is secure renegotiation: false
%% No cached client session
*** ClientHello, TLSv1
RandomCookie:  GMT: 1377233856 bytes = { 69, 128, 29, 114, 252, 186, 13, 192, 212, 243, 179, 208, 124, 196, 220, 137, 23, 124, 30, 226, 98, 148, 243, 6, 188, 230, 109, 119 }
Session ID:  {}
Cipher Suites: [SSL_RSA_WITH_RC4_128_MD5, SSL_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_DES_CBC_SHA, SSL_DHE_RSA_WITH_DES_CBC_SHA, SSL_DHE_DSS_WITH_DES_CBC_SHA, SSL_RSA_EXPORT_WITH_RC4_40_MD5, SSL_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
Compression Methods:  { 0 }
***
main, WRITE: TLSv1 Handshake, length = 81
main, WRITE: SSLv2 client hello message, length = 110
main, handling exception: java.net.SocketException: Connection reset
main, SEND TLSv1 ALERT:  fatal, description = unexpected_message
main, WRITE: TLSv1 Alert, length = 2
main, Exception sending alert: java.net.SocketException: Broken pipe
main, called closeSocket()
Disconnected from the target VM, address: '127.0.0.1:62146', transport: 'socket'
Exception in thread "main" java.net.SocketException: Connection reset
    at java.net.SocketInputStream.read(SocketInputStream.java:168)
    at com.sun.net.ssl.internal.ssl.InputRecord.readFully(InputRecord.java:422)
    at com.sun.net.ssl.internal.ssl.InputRecord.read(InputRecord.java:460)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:863)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1188)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1215)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1199)
    at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:434)
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:166)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1172)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:234)
    at java.net.URL.openStream(URL.java:1010)
    at com.prosc.license.client.network.SSLConnectTest.testConnection(SSLConnectTest.java:22)
    at com.prosc.license.client.network.SSLConnectTest.main(SSLConnectTest.java:18)

Here is the connector configuration in server.xml. I'm hoping that some change here will fix the problem:

<Connector port="8443" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
sslProtocol="SSLv2+TLSv1+SSLv3"
SSLHonorCipherOrder="true"
protocol="org.apache.coyote.http11.Http11AprProtocol"
clientAuth="false" SSLCertificateFile="/etc/apache2/ssl.crt/secure2.360works.com.crt"
SSLCertificateKeyFile="/etc/apache2/ssl.crt/secure2.360works.com.key"
SSLCertificateChainFile="/etc/apache2/ssl.crt/secure2.360works.com.chcrt" />
Jesse Barnum
  • 6,507
  • 6
  • 40
  • 69

1 Answers1

3
main, WRITE: TLSv1 Handshake, length = 81
main, WRITE: SSLv2 client hello message, length = 110

I presume this is coming from a Java 6 environment (unless the client has explicitly enabled the SSLv2Hello protocol). Have you tried to see if the client was able to connect when run on a Java 7 JRE (which doesn't enable SSLv2Hello by default).

I would guess this problem happens because the APR connector might not like receiving an SSLv2 Client Hello (which, coming from Java, isn't really an SSLv2 Client Hello, but a v3 wrapped into a v2, see EJP's answer).

A couple of suggestions that may fix this:

  • Try to make APR accept SSLv2 connections, at least SSLv3 or above wrapped into a v2 Hello. (Using SSLv2 isn't a good idea, but the SSLv2Hello with SSLv3/TLS shouldn't be a problem.)
  • Switch APR in Apache Tomcat for a pure Java connector (BIO or NIO, see comparative table). You might need to convert the certificate and key into a keystore, but this shouldn't be too difficult, especially into a PKCS12 keystore.
Community
  • 1
  • 1
Bruno
  • 119,590
  • 31
  • 270
  • 376
  • Thanks for the reply. I've updated my question to show the connector element in the server.xml file. I am currently enabling SSLv2, SSLv3, and TLSv1 (which is all supported protocols according to the docs). – Jesse Barnum Aug 24 '13 at 01:08
  • I dont' want to switch to BIO or NIO because all of my research indicates that for supporting SSL, APR is much faster. The reason I'm switching to APR is to improve performance. – Jesse Barnum Aug 24 '13 at 01:09
  • I tested it with Java 7 on the client, and that did work! However, 90% of my clients are on Java 6, so I'm still looking for a server-side solution to fix their problem. – Jesse Barnum Aug 24 '13 at 01:12
  • Sorry, I'm not sure how to make APR accept SSLv2Hello. I suspect the possible small loss of performance shouldn't be your primary concern compared with losing the connection altogether. Any actual benchmarks to back up your performance concerns, by the way (especially against NIO)? – Bruno Aug 24 '13 at 01:28
  • I must admit I don't know if `SSLv2+TLSv1+SSLv3` includes the case of an SSLv3 ClientHello wrapped into an SSLv2 Hello. – Bruno Aug 24 '13 at 01:30
  • I did some benchmarking with APR vs NIO, and when using SSL with 20 simultaneous clients, the APR connection is getting 95 req/s, while NIO is only getting 55 req/s. The traditional approach of running SSL though Apache and then forwarding to Tomcat is 60 req/s, so I guess I will just stick with that. It's a shame that Java 6 seems incompatible with APR. – Jesse Barnum Aug 24 '13 at 16:00