0

I recently inherited a lab Ubuntu LAMP server that has been relatively neglected and still running a Struts 1.0 application. We are preping for a complete overhaul, but in the mean time the only error that is being thrown in the logs is a javax.net.ssl.SSLException: hostname in certificate didn't match error. It's our hope to patch this temporarily while we write the replacement. I've read several SO questions on the mater (eg 1, 2, 3, 4) and it seemed like Will Sargent's solution was the best choice.

Unfortunately I'm going into the server with utterly no documentation or returned email from the group that set it up (luckily none of the Java was obfuscated per policy). I looked for any keystore files (searching for "keystore" in the file name or .jks files), but I didn't find any. Which lead me think that I needed to make a new one and initialize it prior to calling the webClient.getPage. So far I have been able to make the .jks file just fine, it's just it doesn't change the hostname match error.

Is there a way to see what keystore if any is being used by a servlet, and what its location happens to be? Alternatively, what is the proper way to make a new one and have it be used/initialized?


Additional Details

There are a few things about this that are odd to me. The main one is I don't understand why the hostname wouldn't be correct. The site being pulled is https://www.ncbi.nlm.nih.gov/account/ which if you navigate to it in a browser certainly pulls the correct certificate. I'm wondering if it's because the WebClient(BrowserVersion.FIREFOX_17) is set to the archaic FIREFOX_17. Should I just change that from 17 to 31? There are plenty of things that could be upgraded, but as we're going to start from scratch to have documentation, I want to change as little as possible on the old instance to hopefully keep it running for a few more months. The FIREFOX version installed on the server isn't close to current ether (not that it's used), but I was thinking changing the BrowserVersion just changed how the reply was formatted.

Here is the code, ending on the line throwing the error:

  private Vector updateRDLpubs(Vector orderList, DataSource dataSource)
    throws Exception
  {
    Vector removeList = new Vector();
    Vector historyList = new Vector();
    try
    {
      SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy");
      Calendar cal = Calendar.getInstance();
      cal.add(5, -5);
      Date days5Back = cal.getTime();

      WebClient webClient = new WebClient(BrowserVersion.FIREFOX_17);
      webClient.setThrowExceptionOnFailingStatusCode(false);
      HtmlPage page = (HtmlPage)webClient.getPage("https://www.ncbi.nlm.nih.gov/account/");

And here is the stack trace on the error:

at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:227)
at org.apache.http.conn.ssl.BrowserCompatHostnameVerifier.verify(BrowserCompatHostnameVerifier.java:54)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:147)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128)
at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:572)
at com.gargoylesoftware.htmlunit.HtmlUnitSSLSocketFactory.connectSocket(HtmlUnitSSLSocketFactory.java:171)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:180)
at org.apache.http.impl.conn.ManagedClientConnectionImpl.open(ManagedClientConnectionImpl.java:294)
at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:645)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:480)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:906)
at com.gargoylesoftware.htmlunit.HttpWebConnection.getResponse(HttpWebConnection.java:172)
at com.gargoylesoftware.htmlunit.WebClient.loadWebResponseFromWebConnection(WebClient.java:1486)
at com.gargoylesoftware.htmlunit.WebClient.loadWebResponse(WebClient.java:1403)
at com.gargoylesoftware.htmlunit.WebClient.getPage(WebClient.java:305)
at com.gargoylesoftware.htmlunit.WebClient.getPage(WebClient.java:374)
at com.gargoylesoftware.htmlunit.WebClient.getPage(WebClient.java:359)
at tanklab.UpdateRDLJob2.updateRDLpubs(UpdateRDLJob2.java:241)
at tanklab.UpdateRDLJob2.execute(UpdateRDLJob2.java:79)
at org.quartz.core.JobRunShell.run(JobRunShell.java:202)
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:525)
Community
  • 1
  • 1
Atl LED
  • 656
  • 2
  • 8
  • 31
  • Do you know which versions of Ubuntu, Java (both sides), Apache HttpClient are used? – Bruno May 07 '15 at 16:09
  • Have you checked whether your server had a reverse proxy front-end, for example Apache Httpd? You should be able to see if there's something listening on port 443 using `netstat -t -p -l -n` – Bruno May 07 '15 at 16:17
  • @Bruno apache2 is listening on 443. Ubuntu 14.04; Java SE 1.7.0_55-b13 and Java Server 24.55-b03, Apache 2.4.7-1ubuntu4 .... I realize all of these can be updated. I actually played around with updates but broke far more as I did, thus the re-write – Atl LED May 07 '15 at 16:42
  • Then, you should look at the Apache config to find your certificate and private key, the SSL/TLS aspect isn't handle by Java (so it won't be in a keystore). That said, this doesn't explain why you're getting this error, since the name seems to match. – Bruno May 07 '15 at 17:09
  • Couldn't find any related information for the specific site, the HTTPD settings for the web app is ` Options Indexes FollowSymLinks AllowOverride None Require all granted` but would that effect `WebClient.getPage`? I thought that `apache2.conf` would only handle incoming? – Atl LED May 07 '15 at 17:42
  • You should certainly have other configuration files under `/etc/apache2/sites-enabled/` or perhaps someone put the configuration in other files (e.g. `mods-enabled/ssl.conf`). The Apache Httpd configuration is by no means limited to `apache2.conf`, especially on Ubuntu/Debian. You're right this is only for the incoming connections, but that's what you were asking in bold, isn't it? – Bruno May 07 '15 at 18:12
  • @Bruno folders /etc/apache2/*-enabled/ are all empty. in the /.../*-available we've got things like the default-ssl.conf which just points to the snakeoil cert made for the server. I'm probably not thinking the right way, but I thought an `getPage` request wouldn't be handled there (and I guess is incoming). – Atl LED May 07 '15 at 19:18
  • Maybe I misunderstood the question. Are you running `www.ncbi.nlm.nih.gov`, or are you trying to connect to it from another machine? – Bruno May 07 '15 at 19:37
  • Trying to connect to it from another machine. I have great hope that NIH's servers are in far better condition. – Atl LED May 08 '15 at 05:12
  • Ah, in that case your server is the client. You're not after the keystore, but the truststore (which would be `cacerts` by default). That said, your truststore seems OK. You'd need to find out which version of Apache Http client your application is using (that's where the exception occurs). – Bruno May 08 '15 at 13:21
  • @Bruno Sorry for the lag, had to go to conference. So I still don't find a truststore.jks file in `/etc/java-sun/../cacerts` or `/opt/java/../cacerts` folder. There is the cacerts file, but I don't think I should modify that. Is there a way to make/edit a trust-store to get around a specific hostname (nih.gov)? – Atl LED May 12 '15 at 14:10
  • @Bruno Also, I assume the Http client is 4.2.3 from the `httpclient-4.2.3.jar` file – Atl LED May 12 '15 at 14:13
  • `cacerts` is the truststore file (it just doesn't have any extension at all, but it's in JKS format). I'm not sure why you'd get a hostname verification problem (it normally happens after the certificate verification has been done using the truststore), that said, it might have to do with the issues described [here](http://stackoverflow.com/a/12096398/372643). It might be worth adding that particular cert to the cacerts truststore. (I'd recommend working with copies of that file of course, just in case something goes wrong.) – Bruno May 12 '15 at 14:21

1 Answers1

1

EDIT: Ahh, you are using HTMLUnit. Your problem is probably that your HTMLUnit is severely out of date -- check against the server using https://tersesystems.com/2014/03/31/testing-hostname-verification/ and then upgrade HTMLUnit to the latest if nothing shows up.

MORE EDIT: Why is HTMLUnit being used from Quartz? Are they trying to use it as a general HTTP Client? It's not designed for that.

The best reference on this is going to be Bulletproof TLS, which has a chapter about JSSE and Tomcat.

Is there a way to see what keystore if any is being used by a servlet, and what its location happens to be?

This depends on an SSLEngine being set up -- if you're running inside a servlet, most likely the app server has already set up your SSL configuration already. However, you can debug your JVM by turning on -Djavax.net.debug=ALL, but it doesn't tell you what location on the filesystem the certificate came from unless you coded a custom KeyStore and TrustManager (which is lame). Here's the debug info though:

Alternatively, what is the proper way to make a new one and have it be used/initialized?

This depends on your application server. If you just need to provide a custom SSLEngine, you can do something like this (from https://github.com/wsargent/activator-play-tls-example/blob/master/app/https/CustomSSLEngineProvider.scala):

class CustomSSLEngineProvider(appProvider: ApplicationProvider) extends SSLEngineProvider {

  def readPassword(): Array[Char] = {
    val passwordPath = FileSystems.getDefault.getPath("certs", "password")
    Files.readAllLines(passwordPath).get(0).toCharArray
  }

  def readKeyInputStream(): java.io.InputStream = {
    val keyPath = FileSystems.getDefault.getPath("certs", "example.com.jks")
    Files.newInputStream(keyPath)
  }

  def readTrustInputStream(): java.io.InputStream = {
    val keyPath = FileSystems.getDefault.getPath("certs", "clientca.jks")
    Files.newInputStream(keyPath)
  }

  def readKeyManagers(): Array[KeyManager] = {
    val password = readPassword()
    val keyInputStream = readKeyInputStream()
    try {
      val keyStore = KeyStore.getInstance(KeyStore.getDefaultType)
      keyStore.load(keyInputStream, password)
      val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm)
      kmf.init(keyStore, password)
      kmf.getKeyManagers
    } finally {
      keyInputStream.close()
    }
  }

  def readTrustManagers(): Array[TrustManager] = {
    val password = readPassword()
    val trustInputStream = readTrustInputStream()
    try {
      val keyStore = KeyStore.getInstance(KeyStore.getDefaultType)
      keyStore.load(trustInputStream, password)
      val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm)
      tmf.init(keyStore)
      tmf.getTrustManagers
    } finally {
      trustInputStream.close()
    }
  }

  def createSSLContext(applicationProvider: ApplicationProvider): SSLContext = {
    val keyManagers = readKeyManagers()
    val trustManagers = readTrustManagers()

    // Configure the SSL context to use TLS
    val sslContext = SSLContext.getInstance("TLS")
    sslContext.init(keyManagers, trustManagers, null)
    sslContext
  }

  override def createSSLEngine(): SSLEngine = {
    val sslContext = createSSLContext(appProvider)

    // Start off with a clone of the default SSL parameters...
    val sslParameters = sslContext.getDefaultSSLParameters

    // Tells the server to ignore client's cipher suite preference.
    // http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#cipher_suite_preference
    sslParameters.setUseCipherSuitesOrder(true)

    // http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SSLParameters
    val needClientAuth = java.lang.System.getProperty("play.ssl.needClientAuth")
    sslParameters.setNeedClientAuth(java.lang.Boolean.parseBoolean(needClientAuth))

    // Clone and modify the default SSL parameters.
    val engine = sslContext.createSSLEngine
    engine.setSSLParameters(sslParameters)

    engine
  }

}
Will Sargent
  • 4,346
  • 1
  • 31
  • 53
  • We actually have the new server up and running now, but thanks for this. Those are some good references ( we're but in no way programmers). We did use a custom SSLEngine in the new implementation. – Atl LED Jun 06 '16 at 18:10