views:

1489

answers:

6

Using the Java URL class, I can connect to an external HTTPS server (such as our production site), but using a local URL I get "SunCertPathBuilderException: unable to find valid certification path to requested target".

How do I get a valid certification path?

EDIT: I'm not using this URL to directly create a connection, I am passing the URL to an itext PDFReader, which is then having the connection issue.

A: 

The problem it's complaining about is that when you create an SSL connection, the server must present a valid certificate to the client. You can write an appropriate endpoint in Java (HTTPServerSocket will do it I think) but it would require some hacking about to set it up. It's probably easier to set up a local web server with anything that handles SSL correctly --- Apache, lighttp, whatever --- and create a self-signed cert using the openssl tools.

Updated

Here's an example from the Java Almanac. http://www.exampledepot.com/egs/javax.net.ssl/Server.html

An SSL server socket requires certificates that it will send to clients for authentication. The certificates must be contained in a keystore whose location must be explicitly specified (there is no default). Following the example we describe how to create and specify a keystore for the SSL server socket to use.

try {
    int port = 443;
    ServerSocketFactory ssocketFactory = SSLServerSocketFactory.getDefault();
    ServerSocket ssocket = ssocketFactory.createServerSocket(port);

    // Listen for connections
    Socket socket = ssocket.accept();

    // Create streams to securely send and receive data to the client
    InputStream in = socket.getInputStream();
    OutputStream out = socket.getOutputStream();

    // Read from in and write to out...

    // Close the socket
    in.close();
    out.close();
} catch(IOException e) {
}

Specify the keystore of certificates using the javax.net.ssl.keyStore system property:

> java -Djavax.net.ssl.keyStore=mySrvKeystore -Djavax.net.ssl.keyStorePassword=123456 MyServer
Charlie Martin
I have a working HTTPS server running, I just can't connect to it with the java URL class (updating main post to reflect this)
RodeoClown
+2  A: 

You probably need to setup a HostnameVerifier. Before connecting, you need add it to the connection object

HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setHostnameVerifier(new HostnameVerifier() {
  public boolean verify(String hostname, SSLSession session) {
    // check hostname/session
    return true;
  }
});
conn.connect();
// read/write...

There certainly are some implementations out there, if you need one. You might want to look at HttpClient project too.

sblundy
I've used this, thanks!
RodeoClown
+2  A: 

Another thing to look at is the TrustManager you are using. The error message suggests that the certificate presented by the server is not signed by a trusted root. Since you don't have direct control over the SSL socket that is created, I think your best bet is to initialize your own SSLContext with a TrustManager that's been setup with the root CA of the server's certificate chain. Then set this context as the default.

This is assuming you are using Java 6. The API is more limited in Java 5. You can get a default SSLSocketFactory, but there's no standard way to set it.

erickson
A: 

It may also help you to add the certificate that the localhost server is using (I assume it's self-signed) to the JVM's keystore, using the "keytool" utility. This should have the effect of telling the JVM "you can trust this certificate".

matt b
A: 

I have ended up running a static method (only on dev) that installs a very trusting TrustManager (accepts everything), and also added a hostnameVerifier that always returns true (thanks sblundy).

Details on setting up a trust manager

RodeoClown
+2  A: 

Here was my solution that incorporates some of the ideas in this thread and peiced together with code from around the net. All I do call this function and it sets the default Trust Manager and HostName Verifier for HttpsURLConnection. This might be undesirable for some because it will effect all HttpsURLConnections but I'm just writing a simple proxy so it worked for me.

private void setTrustAllCerts() throws Exception
{
 TrustManager[] trustAllCerts = new TrustManager[]{
  new X509TrustManager() {
   public java.security.cert.X509Certificate[] getAcceptedIssuers() {
    return null;
   }
   public void checkClientTrusted( java.security.cert.X509Certificate[] certs, String authType ) { }
   public void checkServerTrusted( java.security.cert.X509Certificate[] certs, String authType ) { }
  }
 };

 // Install the all-trusting trust manager
 try {
  SSLContext sc = SSLContext.getInstance( "SSL" );
  sc.init( null, trustAllCerts, new java.security.SecureRandom() );
  HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
  HttpsURLConnection.setDefaultHostnameVerifier( 
   new HostnameVerifier() {
    public boolean verify(String urlHostName, SSLSession session) {
     return true;
    }
   });
 }
 catch ( Exception e ) {
  //We can not recover from this exception.
  e.printStackTrace();
 }
}
CornPuff
Thanks CornPuff - that is pretty much exactly the code I ended up writing (although I did it a while ago now).
RodeoClown