question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Setting up a truststore without a keystore

See original GitHub issue

I would like to be able to run a spring boot webserver that connects to other servers using the SSL protocol that uses self-signed certificates. To do this I now have to specify the javax.net.ssl.trustStore and javax.net.ssl.trustStorePassword system properties when starting the application.

I would like to be able to set this up using my application.properties, so that all configuration is in one place, and I can use classpath to locate the trust store.

I can specify the server.ssl.trust-store and server.ssl.trust-store-password but this is not picked up without also specifying server.ssl.key-store and related properties.

The main problem then becomes that the spring boot application will start with a https connector (and no http connector), while actually I have no interest to run in https mode. The spring boot server just needs to connect to other servers with https.

My feature request is that you are able to set up a trust store without having to specify properties related to running the server in https mode.

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Reactions:13
  • Comments:7 (4 by maintainers)

github_iconTop GitHub Comments

11reactions
imgx64commented, Sep 6, 2016

For what it’s worth, this is how we do it. We put the self-signed server certificate in src/main/resources, and add a custom property app.ssl.trusted-certificate-location = classpath:server.cert.pem. We use a certificate instead of a keystore because it’s easier to export from the server.

In the code, we parse the certificate and add it to a custom X509TrustManager that trusts both the default truststore and the included certificate (because we use valid certificates for production, and self-signed for staging). Then we call SSLContext.setDefault(sslContext) and HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory()). Only the former should be needed, but it seems to be ignored by IBM WebSphere, so we call the latter as well.

SslConfig.java

@Configuration
public class SslConfig {
    private static final Logger logger = LoggerFactory.getLogger(SslConfig.class);

    @Bean
    public Configured configure(SslProperties sslProperties, ResourceLoader resourceLoader)
            throws GeneralSecurityException, IOException {
        String certLocation = sslProperties.getTrustedCertificateLocation();
        if (!StringUtils.hasText(certLocation)) {
            return new Configured();
        }

        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
        Collection<Certificate> certificates = new ArrayList<Certificate>();

        Resource certificateResource = resourceLoader.getResource(certLocation);
        InputStream inputStream = null;
        try {
            inputStream = certificateResource.getInputStream();
            certificates.addAll(certificateFactory.generateCertificates(inputStream));
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException ex) {
                    logger.warn("Could not close resource InputStream for {}", certLocation, ex);
                }
            }
        }

        ExtraCertsTrustManager extraCertsTrustManager = new ExtraCertsTrustManager(certificates);
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, new TrustManager[] { extraCertsTrustManager }, null);

        SSLContext.setDefault(sslContext);
        // Required for WebSphere
        HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

        return new Configured();
    }

    /**
     * Dummy bean to notify that SSL is configured
     */
    public static class Configured {
        private Configured() {
        }
    }
}

SslProperties.java

@Component
@ConfigurationProperties(prefix = "app.ssl")
public class SslProperties {

    /**
     * Location of an X.509 certificate file. Can use classpath: prefix to use
     * certificate file from resources.
     */
    private String trustedCertificateLocation;

    public String getTrustedCertificateLocation() {
        return trustedCertificateLocation;
    }

    public void setTrustedCertificateLocation(String trustedCertificateLocation) {
        this.trustedCertificateLocation = trustedCertificateLocation;
    }
}

ExtraCertsTrustManager.java

public class ExtraCertsTrustManager implements X509TrustManager {

    private final X509TrustManager defaultX509TrustManager;
    private final X509TrustManager extraX509TrustManager;

    public ExtraCertsTrustManager(Collection<Certificate> certificates) throws GeneralSecurityException {
        defaultX509TrustManager = createX509TrustManager(null);

        KeyStore extraKeyStore = createKeyStore(certificates);
        extraX509TrustManager = createX509TrustManager(extraKeyStore);
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        CertificateException ex1 = null;
        if (defaultX509TrustManager != null) {
            try {
                defaultX509TrustManager.checkClientTrusted(chain, authType);
                // Success
                return;
            } catch (CertificateException ex) {
                ex1 = ex;
            }
        }

        CertificateException ex2 = null;
        if (extraX509TrustManager != null) {
            try {
                extraX509TrustManager.checkClientTrusted(chain, authType);
                // Success
                return;
            } catch (CertificateException ex) {
                ex2 = ex;
            }
        }

        if (ex1 != null) {
            throw ex1;
        }
        if (ex2 != null) {
            throw ex2;
        }

        throw new CertificateException("No trust managers");
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        CertificateException ex1 = null;
        if (defaultX509TrustManager != null) {
            try {
                defaultX509TrustManager.checkServerTrusted(chain, authType);
                // Success
                return;
            } catch (CertificateException ex) {
                ex1 = ex;
            }
        }

        CertificateException ex2 = null;
        if (extraX509TrustManager != null) {
            try {
                extraX509TrustManager.checkServerTrusted(chain, authType);
                // Success
                return;
            } catch (CertificateException ex) {
                ex2 = ex;
            }
        }

        if (ex1 != null) {
            throw ex1;
        }
        if (ex2 != null) {
            throw ex2;
        }

        throw new CertificateException("No trust managers");
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        Set<X509Certificate> acceptedIssuers = new HashSet<X509Certificate>();

        if (defaultX509TrustManager != null) {
            Collections.addAll(acceptedIssuers, defaultX509TrustManager.getAcceptedIssuers());
        }

        if (extraX509TrustManager != null) {
            Collections.addAll(acceptedIssuers, extraX509TrustManager.getAcceptedIssuers());
        }

        return acceptedIssuers.toArray(new X509Certificate[acceptedIssuers.size()]);
    }

    private KeyStore createKeyStore(Collection<Certificate> certificates) throws KeyStoreException {
        KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());

        try {
            keystore.load(null, null);
        } catch (IOException ex) {
            // Should never happen
            throw new RuntimeException(ex);
        } catch (GeneralSecurityException ex) {
            // Should never happen
            throw new RuntimeException(ex);
        }

        for (Certificate certificate : certificates) {
            String alias = certificate.toString();
            keystore.setCertificateEntry(alias, certificate);
        }

        return keystore;
    }

    private X509TrustManager createX509TrustManager(KeyStore keystore) throws GeneralSecurityException {
        TrustManagerFactory trustManagerFactory = //
                TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keystore);

        TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

        if (trustManagers.length == 0) {
            return null;
        }

        if (trustManagers.length > 1) {
            throw new GeneralSecurityException(String.format( //
                    "Expected 1 TrustManager from TrustManagerFactory(%s), got %s", //
                    trustManagerFactory, trustManagers.length));
        }

        TrustManager trustManager = trustManagers[0];
        if (!(trustManager instanceof X509TrustManager)) {
            throw new GeneralSecurityException(String.format( //
                    "Expected %s from TrustManagerFactory(%s), got %s", //
                    X509TrustManager.class.getCanonicalName(), trustManagerFactory,
                    trustManager.getClass().getCanonicalName()));
        }

        return (X509TrustManager) trustManager;
    }

}
1reaction
robert-gvcommented, Sep 5, 2016

I don’t think the sample code will add much to the information already provided, but here you go. You can see in the Application.java what I would like to be able to set in the application.properties

truststore-example.zip

Read more comments on GitHub >

github_iconTop Results From Across the Web

Specifying trust store information in spring boot application ...
When I add the following settings, I can get the keystore to work, but not the truststore. server.ssl.key-store=classpath:foo.jks server.ssl.key ...
Read more >
Generating a KeyStore and TrustStore
Procedure To Create a New TrustStore · Perform the following command. keytool -import -file C:\cascerts\firstCA. · Enter this command two more times, but...
Read more >
Creating a private keystore and a trust store with self-signed ...
This procedure uses keytool, which is located under installation_directory /jre64/jre/bin. Each server needs a private key and a self-signed certificate. You ...
Read more >
Difference Between Java Keystore and Truststore - Baeldung
Generally speaking, keystores hold keys that our application owns, which we can use to prove the integrity of a message and the authenticity...
Read more >
16. Security - Spring
Next, we need to create a truststore which the Shell will use: $ keytool -importcert -keystore dataflow.truststore -alias dataflow -storepass dataflow -file ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found