Uploaded image for project: 'Java Driver'
  1. Java Driver
  2. JAVA-1173

SSL connections do not verify certificate hostnames

    • Type: Icon: Improvement Improvement
    • Resolution: Done
    • Priority: Icon: Major - P3 Major - P3
    • 3.0.0
    • Affects Version/s: 2.11.2, 2.12.0
    • Component/s: Connection Management
    • Labels:
      None

      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))

            Assignee:
            jeff.yemin@mongodb.com Jeffrey Yemin
            Reporter:
            john.morales@mongodb.com John Morales (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated:
              Resolved: