HTTPS testing with jWebUnit
A common problem with testing HTTPS is having to deal with self-signed certificates. While Java has made it pretty easy to add certificates to your local keystore, for development purposes it always seemed like more trouble than it was worth. While some of the standard solutions worked well in the past, trying to use jWebUnit which builds upon HttpUnit resurfaced many of the same issues. It seems HttpUnit knows best and forces using the old school Sun SSL provider in com.meterware.httpunit.HttpsProtocolSupport. As a result the typical Trust All Certificates hack doesn't work. To complicate matters connecting to localhost, 127.0.0.1, and whatever other name your machine might have, can cause validation issues. This can be worked around with a custom HostnameVerifier. Alas, again HttpUnit makes it hard to get around that problem too.
A simple jWebUnit test making an HTTPS connection to your local machine produces an error like:
java.lang.RuntimeException: java.io.IOException
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:602)
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:272)
at com.sun.net.ssl.internal.www.protocol.https.HttpsURLConnectionOldImpl.getResponseCode(DashoA12275)
at com.meterware.httpunit.HttpWebResponse.readResponseHeader(HttpWebResponse.java:162)
at com.meterware.httpunit.HttpWebResponse.readHeaders(HttpWebResponse.java:200)
at com.meterware.httpunit.HttpWebResponse.
at com.meterware.httpunit.HttpWebResponse.
at com.meterware.httpunit.WebConversation.newResponse(WebConversation.java:76)
at com.meterware.httpunit.WebWindow.getResource(WebWindow.java:164)
at com.meterware.httpunit.WebWindow.getSubframeResponse(WebWindow.java:128)
at com.meterware.httpunit.WebWindow.getResponse(WebWindow.java:121)
at com.meterware.httpunit.WebWindow.updateWindow(WebWindow.java:144)
at com.meterware.httpunit.WebWindow.getSubframeResponse(WebWindow.java:130)
at com.meterware.httpunit.WebWindow.getResponse(WebWindow.java:121)
at com.meterware.httpunit.WebClient.getResponse(WebClient.java:113)
at net.sourceforge.jwebunit.HttpUnitDialog.
at net.sourceforge.jwebunit.WebTester.beginAt(WebTester.java:55)
at net.sourceforge.jwebunit.WebTestCase.beginAt(WebTestCase.java:46)
(... additional lines deleted ...)
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: No trusted certificate found
at com.sun.net.ssl.internal.ssl.BaseSSLSocketImpl.a(DashoA12275)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.a(DashoA12275)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.a(DashoA12275)
at com.sun.net.ssl.internal.ssl.SunJSSE_az.a(DashoA12275)
at com.sun.net.ssl.internal.ssl.SunJSSE_az.a(DashoA12275)
at com.sun.net.ssl.internal.ssl.SunJSSE_ax.a(DashoA12275)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.a(DashoA12275)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.j(DashoA12275)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(DashoA12275)
at sun.net.www.protocol.https.HttpsClient.afterConnect(DashoA12275)
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(DashoA12275)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:626)
at sun.net.www.protocol.http.HttpURLConnection.getHeaderFieldKey(HttpURLConnection.java:1504)
at com.sun.net.ssl.internal.www.protocol.https.HttpsURLConnectionOldImpl.getHeaderFieldKey(DashoA12275)
at com.meterware.httpunit.HttpWebResponse.loadHeaders(HttpWebResponse.java:216)
at com.meterware.httpunit.HttpWebResponse.readHeaders(HttpWebResponse.java:198)
... 31 more
Caused by: sun.security.validator.ValidatorException: No trusted certificate found
at sun.security.validator.SimpleValidator.buildTrustedChain(SimpleValidator.java:304)
at sun.security.validator.SimpleValidator.engineValidate(SimpleValidator.java:107)
at sun.security.validator.Validator.validate(Validator.java:202)
at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(DashoA12275)
at com.sun.net.ssl.internal.ssl.JsseX509TrustManager.checkServerTrusted(DashoA12275)
... 44 more
at net.sourceforge.jwebunit.HttpUnitDialog.
at net.sourceforge.jwebunit.WebTester.beginAt(WebTester.java:55)
at net.sourceforge.jwebunit.WebTestCase.beginAt(WebTestCase.java:46)
(... additional lines deleted ...)
Plugging in the standard trust all solution from above does nothing. A co-worker pointed me at a different solution to the problem. That gets around the SSL certificate check issue. However, problems still arise if you try to connect to 127.0.0.1 when your certificate is signed as localhost:
java.lang.RuntimeException: java.io.IOException
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:602)
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:272)
at com.sun.net.ssl.internal.www.protocol.https.HttpsURLConnectionOldImpl.getResponseCode(DashoA12275)
at com.meterware.httpunit.HttpWebResponse.readResponseHeader(HttpWebResponse.java:162)
at com.meterware.httpunit.HttpWebResponse.readHeaders(HttpWebResponse.java:200)
at com.meterware.httpunit.HttpWebResponse.
at com.meterware.httpunit.HttpWebResponse.
at com.meterware.httpunit.WebConversation.newResponse(WebConversation.java:76)
at com.meterware.httpunit.WebWindow.getResource(WebWindow.java:164)
at com.meterware.httpunit.WebWindow.getSubframeResponse(WebWindow.java:128)
at com.meterware.httpunit.WebWindow.getResponse(WebWindow.java:121)
at com.meterware.httpunit.WebWindow.updateWindow(WebWindow.java:144)
at com.meterware.httpunit.WebWindow.getSubframeResponse(WebWindow.java:130)
at com.meterware.httpunit.WebWindow.getResponse(WebWindow.java:121)
at com.meterware.httpunit.WebClient.getResponse(WebClient.java:113)
at net.sourceforge.jwebunit.HttpUnitDialog.
at net.sourceforge.jwebunit.WebTester.beginAt(WebTester.java:55)
at net.sourceforge.jwebunit.WebTestCase.beginAt(WebTestCase.java:46)
(... additional lines deleted ...)
Caused by: java.io.IOException: HTTPS hostname wrong: should be <127.0.0.1>
at sun.net.www.protocol.https.HttpsClient.b(DashoA12275)
at sun.net.www.protocol.https.HttpsClient.afterConnect(DashoA12275)
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(DashoA12275)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:626)
at sun.net.www.protocol.http.HttpURLConnection.getHeaderFieldKey(HttpURLConnection.java:1504)
at com.sun.net.ssl.internal.www.protocol.https.HttpsURLConnectionOldImpl.getHeaderFieldKey(DashoA12275)
at com.meterware.httpunit.HttpWebResponse.loadHeaders(HttpWebResponse.java:216)
at com.meterware.httpunit.HttpWebResponse.readHeaders(HttpWebResponse.java:198)
... 31 more
at net.sourceforge.jwebunit.HttpUnitDialog.
at net.sourceforge.jwebunit.WebTester.beginAt(WebTester.java:55)
at net.sourceforge.jwebunit.WebTestCase.beginAt(WebTestCase.java:46)
at net.ruckus.test.jwebunit.AbstractWebTestCase.login(AbstractWebTestCase.java:154)
at net.ruckus.test.jwebunit.AbstractWebTestCase.login(AbstractWebTestCase.java:143)
at net.ruckus.ui.web.SiteWebTest.testGet(SiteWebTest.java:20)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at junit.framework.TestCase.runTest(TestCase.java:154)
at junit.framework.TestCase.runBare(TestCase.java:127)
at junit.framework.TestResult$1.protect(TestResult.java:106)
at junit.framework.TestResult.runProtected(TestResult.java:124)
at junit.framework.TestResult.run(TestResult.java:109)
at junit.framework.TestCase.run(TestCase.java:118)
at junit.framework.TestSuite.runTest(TestSuite.java:208)
at junit.framework.TestSuite.run(TestSuite.java:203)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:478)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:344)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
(... additional lines deleted ...)
Because HttpUnit is forcing the old Sun protocols you need to use the Sun specific classes when creating a custom HostnameVerifier instead of the more generic javax.net.ssl classes. You'll have to live with the deprecated warnings:
public class DeprecatedNullHostnameVerifier implements com.sun.net.ssl.HostnameVerifier
{
private static boolean INITIALIZED = false;
public boolean verify(String arg0, String arg1)
{
return true;
}
public static void install()
{
if (!INITIALIZED)
{
com.sun.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(new DeprecatedNullHostnameVerifier());
INITIALIZED = true;
}
}
}
Combiing the XTrustProvider and the DeprecatedNullHostnameVerifier into a run-once JUnit setup method will let you easily test any HTTPS site.
Comments
Posted by: stephendv | October 22, 2005 3:15 AM
Posted by: J-webbian | November 9, 2005 9:43 AM
Posted by: Jude | March 12, 2008 10:22 PM
Posted by: webguy1 | April 4, 2008 10:44 AM