Find or initialize the keystore needed to solve “h

2019-09-08 18:05发布

问题:

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)

回答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:

  • https://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/ReadDebug.html
  • https://docs.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html#Debug

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
  }

}