0

I want to connect to my server via SSL. Therefore I generated the certificates on the server with these commands:

openssl genrsa -out server.pem 2048
openssl req -new -x509 -nodes -sha1 -days 3650 -key server.pem > server.cert

The connection works, if I trust all certificates on the client with a TrustManager like this:

X509TrustManager tm = new X509TrustManager() {

    @Override
    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
        return null;
    }

    @Override
    public void checkClientTrusted(
            X509Certificate[] certs, String authType) {
    }

    @Override
    public void checkServerTrusted(
            X509Certificate[] certs, String authType) {
    }
};

But I do not want to trust all certificates of course, but only mine. I tried several commands to import the certificates like:

keytool -import -alias ca -file server.cert -keystore cacerts

But I always get this error:

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

What do I need to do to get this working? Can someone explain the steps necessary for a person not very familiar with the cryto field?

Edit: As proposed by Donal Fellows, I tried the approach with the custom X509TrustManager and it works. But is it secure as well like that? If i just return "null" in the method "getAcceptedIssuers" its working as well and I am not quite sure why:

X509TrustManager tm = new X509TrustManager() {

    @Override
    public java.security.cert.X509Certificate[] getAcceptedIssuers() {

        X509Certificate[] trustedCerts = new X509Certificate[1];
        try{
            InputStream inStream = new FileInputStream("server.cert");
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream);
            inStream.close();
            trustedCerts[0] = cert;
        }catch(Exception e){
            e.printStackTrace();
        }

        return trustedCerts;
    }

    @Override
    public void checkClientTrusted(
            X509Certificate[] certs, String authType) {
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain,
            String authType) throws CertificateException {

        boolean match = false;
        try{


            InputStream inStream = new FileInputStream("server.cert");
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream);
            inStream.close();

            for(X509Certificate c : chain){
                if(c.equals(cert)){
                    match = true;
                }
            }
        }catch(Exception e){
            throw new CertificateException();
        }

        if(!match)
            throw new CertificateException();

    }

};
jan
  • 3,923
  • 9
  • 38
  • 78

1 Answers1

0

If you're really locking things down, you can do it by installing a custom X509TrustManager that tests to see if the certificate used is equal to the certificate you think it should be (which you know for sure; you've generated it). This is actually pretty secure, but allows no operational flexibility at all; if the server gets compromised and you have to regenerate the key, all the clients will also need updating.

Because that's really annoying (and doesn't scale to things like the WWW) it's more normal to use trust root. The trust root is a self-signed CA certificate, typically with a long life, that is used to sign the working certificate that you deploy to your server. (You can use the CA cert itself, but it's usually better to keep that offline, probably on removable media in a fireproof safe.) Then you put the CA certificate into a truststore (i.e., a keystore that just holds certificates) on the client and tell Java to use it.

In practice, you're almost there. You probably just need to tell Java to use your cacerts truststore. You do that by setting the correct system property.

// Be careful on Windows; this property (unusually!) uses “/” instead of “\”
System.setProperty("javax.net.ssl.trustStore", "/path/to/cacerts");
Community
  • 1
  • 1
Donal Fellows
  • 133,037
  • 18
  • 149
  • 215