Java SSLContext and the SSLSocketFactory self-signed certificate

Problem

Often we want to connect to create a secure SSL connection to an HTTPs endpoint which is secured by a self-signed Certificate. If we do so just with a simple call we usually face an nice exception like:

Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:387)
	at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
	at sun.security.validator.Validator.validate(Validator.java:260)
	at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
	at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
	at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1479)
	... 47 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:145)
	at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:131)
	at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
	at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:382)
	... 53 more

Solution

Often I see that either somebody uses an trust all or just overwrites the default Socket Factory (HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory())).  This is maybe okay for just a test but should be used in production-ready code.

Overall it is simple to fix it. We just need to know how the stuff works. In short, we have usually always:

  • SSLSocketFactory — creates the Socket connection in case of an SSL connection
  • SSLContext — responsible for the verification of the certificate

Export Certificate

First, we need to get the certificate. Either use Firefox to download it or the command line:

openssl s_client -connect google.de:443 -showcerts

The pem file is here e.g.:

-----BEGIN CERTIFICATE-----
MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDIwNTIxMDQwMDAwWhcNMTgwODIxMDQwMDAw
WjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UE
AxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9m
OSm9BXiLnTjoBbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIu
T8rxh0PBFpVXLVDviS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6c
JmTM386DGXHKTubU1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmR
Cw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5asz
PeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo4HwMIHtMB8GA1UdIwQYMBaAFEjm
aPkr0rKV10fYIyAQTzOYkJ/UMB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrM
TjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjA6BgNVHR8EMzAxMC+g
LaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNvbS9jcmxzL3NlY3VyZWNhLmNybDBO
BgNVHSAERzBFMEMGBFUdIAAwOzA5BggrBgEFBQcCARYtaHR0cHM6Ly93d3cuZ2Vv
dHJ1c3QuY29tL3Jlc291cmNlcy9yZXBvc2l0b3J5MA0GCSqGSIb3DQEBBQUAA4GB
AHbhEm5OSxYShjAGsoEIz/AIx8dxfmbuwu3UOx//8PDITtZDOLC5MH0Y0FWDomrL
NhGc6Ehmo21/uBPUR/6LWlxz/K7ZGzIZOKuXNBSqltLroxwUCEm2u+WR74M26x1W
b8ravHNjkOR/ez4iyz0H7V84dJzjA1BOoa+Y7mHyhD8S
-----END CERTIFICATE-----

Create SSLContext including the Certificate

You can either import this pem file into your trust store or just create the trust store using the pem on the fly:

public static SSLContext buildSslContext(InputStream... inputStreams) throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
  X509Certificate cert;
  KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
  trustStore.load(null);

  for (InputStream inputStream : inputStreams) {
    try {
      CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
      cert = (X509Certificate)certificateFactory.generateCertificate(inputStream);
    } finally {
      IOUtils.closeQuietly(inputStream);
    }
    String alias = cert.getSubjectX500Principal().getName();
    trustStore.setCertificateEntry(alias, cert);
  }

  TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
  tmf.init(trustStore);
  TrustManager[] trustManagers = tmf.getTrustManagers();
  SSLContext sslContext = SSLContext.getInstance("TLS");
  sslContext.init(null, trustManagers, null);

  return sslContext;
}

Put the SSLSocketFactory together

I assume the pem file is in the source folder here, for maven src/main/resources.

SSLContext sslContext = CryptoUtil.buildSslContext(
  this.getClass().getResourceAsStream("/my-cert.pem"));
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);

Now we have the SSLSocketFactory, lets put it to use.

Use the SSLConnectionSocketFactory

Here I will use the created socket factory to configure the Apache HTTP client and use it again in the Spring RestTemplate:

// this two lines are just repeated from above
SSLContext sslContext = CryptoUtil.buildSslContext(this.getClass().getResourceAsStream("/beta-comfylight-cert.pem"));
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);

// here we create the Http Client using our SSL Socket Factory and so trust relation
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);

// create the spring REST Template using our apache HTTP client
RestTemplate restTemplate = new RestTemplate(requestFactory);

Summary

What have we achieved now?

  • We have an SSL Socket Factory which just trust our „server“ / „certificate“
  • We haven’t overwritten any default socket factory in the application, which could cause problems in other subsystems as we then just trust „us“
  • We haven’t used a trust all, which would be fine for a test but would render the SSL encryption useless in production (man in the middle attack…)

Paul Sterl has written 55 articles

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>