[JAVA-1173] SSL connections do not verify certificate hostnames Created: 18/Apr/14  Updated: 13/Apr/16  Resolved: 16/Mar/15

Status: Closed
Project: Java Driver
Component/s: Connection Management
Affects Version/s: 2.11.2, 2.12.0
Fix Version/s: 3.0.0

Type: Improvement Priority: Major - P3
Reporter: John Morales Assignee: Jeffrey Yemin
Resolution: Done Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Related

 Description   

Overview
By default x509 communication should validate the MongoDB's server certificate's hostname against the hostname that me the client used to connect. Roughly speaking, similar to the hostname verification that's performed by your browser; if I visit google.com and I get from the web server a certificate for acmewidgets.com, then my browser displays a warning (or outright refuses).

Currently this hostname verification is not performed when communicating with SSL-enabled MongoDB.

Details
I looked into this a bit, and it appears the issue is that the driver's SSL connections do not specify an identificationAlgorithm on the SSLSocket.

One apparent option might be to modify the SSLParameters of the SSLContext used to create your SSLSocketFactory and specify an endpoint identification algorithm. Unfortunately, however, it appears the JDK's implementation only respects a subset of the SSLParameters at the factory level, and identificationAlgorithm is not one of them:

SSLSocketImpl.java (line 2408).

Another option would be to set the algorithm on each socket created by the factory.

I did try a quick POC of this option by adding the snippet below to DBPort.java on 2.11.2.

_socket = _options.socketFactory.createSocket();
if (_socket instanceof SSLSocket) {
    final SSLSocket sslSocket = (SSLSocket) _socket;
    final SSLParameters currentParams = sslSocket.getSSLParameters();
 
    final SSLParameters newParams = new SSLParameters();
    newParams.setEndpointIdentificationAlgorithm("HTTPS");
    newParams.setCipherSuites(sslSocket.getEnabledCipherSuites());
    newParams.setProtocols(sslSocket.getEnabledProtocols());
    if (currentParams.getNeedClientAuth()) {
        newParams.setNeedClientAuth(true);
    } else if (currentParams.getWantClientAuth()) {
        newParams.setWantClientAuth(true);
    }
    ((SSLSocket)_socket).setSSLParameters(newParams);
}
_socket.connect( _addr , _options.connectTimeout );
...

While this wouldn't be the only place requiring the code change, this did do the trick for my standalone MongoDB test. In that connecting with the matching hostname was allowed, while using an alias of the hostname was rejected:

Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No name matching alias-of-the-actual-hostname.com found
	at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
	at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1886)
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:276)
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:270)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1341)
	at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:153)
	at sun.security.ssl.Handshaker.processLoop(Handshaker.java:868)
	at sun.security.ssl.Handshaker.process_record(Handshaker.java:804)
	at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1016)
	at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)
	at sun.security.ssl.SSLSocketImpl.writeRecord(SSLSocketImpl.java:702)
	at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:122)
	at org.bson.io.PoolOutputBuffer.pipe(PoolOutputBuffer.java:129)
	at com.mongodb.OutMessage.pipe(OutMessage.java:236)
	at com.mongodb.DBPort.go(DBPort.java:135)
	at com.mongodb.DBPort.call(DBPort.java:94)
	at com.mongodb.DBTCPConnector.innerCall(DBTCPConnector.java:244)
	... 33 more
Caused by: java.security.cert.CertificateException: No name matching alias-of-the-actual-hostname.com found
	at sun.security.util.HostnameChecker.matchDNS(HostnameChecker.java:208)
	at sun.security.util.HostnameChecker.match(HostnameChecker.java:93)
	at sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:347)
	at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:203)
	at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:126)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1323)
	... 45 more

(IMO, I think an argument could be make this is a JDK bug. Given my (limited) understanding of x.509 I would've expected the verification to be handled automatically behind the scenes and by default. Especially given the proposed workaround above relies on using the identification algorithm of "HTTPS", which is clearly out of place in this context.

Relevant JDK source:
X509TrustManagerImpl.java (line 346))



 Comments   
Comment by Jeffrey Yemin [ 31/Mar/15 ]

Closing all resolved 3.0.0 issues, as 3.0.0 has been tagged and released.

Comment by Githook User [ 17/Mar/15 ]

Author:

{u'username': u'jyemin', u'name': u'Jeff Yemin', u'email': u'jeff.yemin@10gen.com'}

Message: Fixed SSL hostname verification for Netty

JAVA-1173
Branch: master
https://github.com/mongodb/mongo-java-driver/commit/ea0cb06c2a84fc7199c768e7da3a001b2edb4e0a

Comment by Githook User [ 16/Mar/15 ]

Author:

{u'username': u'jyemin', u'name': u'Jeff Yemin', u'email': u'jeff.yemin@10gen.com'}

Message: Temporarily allowing invalid SSL host names during test runs.

JAVA-1173
Branch: master
https://github.com/mongodb/mongo-java-driver/commit/02db69ec02dee0391ceedde43c8b498332e96e36

Comment by Githook User [ 16/Mar/15 ]

Author:

{u'username': u'jyemin', u'name': u'Jeff Yemin', u'email': u'jeff.yemin@10gen.com'}

Message: Enabled HTTPS endpoint identification for SSL sockets by default, which introduces a dependency on Java 7,
and allowed for it to be disabled via the invalidHostNameAllowed option

JAVA-1173
Branch: master
https://github.com/mongodb/mongo-java-driver/commit/1334ad142774b527549873663d3e581e2fa79f68

Comment by Jeffrey Yemin [ 12/Dec/14 ]

Since the driver allows the SocketFactory to be configured, clients can work around this by wrapping SSLSocketFactory and implementing the required checks there (presumably in a non-portable fashion, as in the suggestion in the description).

Comment by Jeffrey Yemin [ 18/Apr/14 ]

We'll have to think about this. The use of "HTTPS" clearly is relying on the implementation of a particular version of a particular JDK. There's nothing in the spec that dictates what the behavior of this is required to be.

I'm thinking we need something more general. If we had a generic event system, we could do something like this:

_socket = _options.socketFactory.createSocket();
_connectionEvents.onConnect(_socket);

But figuring out just the right set of events will be a project in and of itself.

Generated at Thu Feb 08 08:53:57 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.