0

I have a Spring Boot application. Its application.yml is fetched from an online resource. It contains the following config for mutual SSL:

server:
  ssl:
    enabled: true
    client-auth: need
    key-store-type: PKCS12
    key-store: http://{config server url}/keystore.p12
    key-store-password: {keystore password}
    trust-store-type: JKS
    trust-store: http://{config server url}/truststore.jks
    trust-store-password: {truststore password}
  port: 8443

When running the application, I have this error:

java.io.IOException: DerInputStream.getLength(): lengthTag=111, too big.
    at sun.security.util.DerInputStream.getLength(DerInputStream.java:599)
    at sun.security.util.DerValue.init(DerValue.java:391)
    at sun.security.util.DerValue.<init>(DerValue.java:332)
    at sun.security.util.DerValue.<init>(DerValue.java:345)
    at sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:1938)
    at java.security.KeyStore.load(KeyStore.java:1445)
    at org.apache.tomcat.util.security.KeyStoreUtil.load(KeyStoreUtil.java:69)
    at org.apache.tomcat.util.net.SSLUtilBase.getStore(SSLUtilBase.java:209)
    at org.apache.tomcat.util.net.SSLHostConfigCertificate.getCertificateKeystore(SSLHostConfigCertificate.java:206)
    at org.apache.tomcat.util.net.SSLUtilBase.getKeyManagers(SSLUtilBase.java:272)
    at org.apache.tomcat.util.net.SSLUtilBase.createSSLContext(SSLUtilBase.java:239)
    at org.apache.tomcat.util.net.AbstractJsseEndpoint.createSSLContext(AbstractJsseEndpoint.java:97)
    ... 23 common frames omitted

But I can open the keystore and truststore just fine by doing it programatically. (Shown below)

private KeyStore getKeyStore() {
    KeyStore keyStore = KeyStore.getInstance("PKCS12");
    try (InputStream is = readCertificateFromURL(new URI(keyStoreURL))) {
        keyStore.load(is, keyStorePassword.toCharArray());
    }
    return keyStore();
}

private InputStream readCertificateFromURL(URI uri) throws IOException {
    RequestEntity<Void> requestEntity = RequestEntity.get(uri)
         .accept(org.springframework.http.MediaType.APPLICATION_OCTET_STREAM)
         .build();
    Resource resource = new RestTemplate().exchange(requestEntity,Resource.class).getBody();
    return resource.getInputStream();
}

How can I tell Spring boot to pick these key/truststores instead of the ones defined in application.yml?

The only things I found online were about overriding the keystore/truststore paths, and not the KeyStore objects themselves.

jerthiry
  • 11
  • 1
  • 5

2 Answers2

1

It indeed works on local files as @Toby asked. The thing is that the stores were downloaded from a config server and were not downloaded as binary. I ended up implementing a custom SslStoreProvider:

@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatSslCustomizer() {
    return tomcat -> tomcat.setSslStoreProvider(new SslStoreProvider() {

        @Override
        public KeyStore getKeyStore() throws Exception {
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            try (InputStream is = readStoreFromURL(new URI(keyStoreURL))) {
                keyStore.load(is, keyStorePassword.toCharArray());
            }
            return keyStore;
        }

        @Override
        public KeyStore getTrustStore() throws Exception {
            KeyStore truststore = KeyStore.getInstance(trustStoreType);
            try (InputStream is = readStoreFromURL(new URI(trustStoreURL))) {
                truststore.load(is, trustStorePassword.toCharArray());
            }
            return truststore;
        }

        private InputStream readStoreFromURL(URI uri) {
            // Download the stores as binary from the config server
        }

    });
}
jerthiry
  • 11
  • 1
  • 5
0

The values for javax.net.ssl.trustStore and javax.net.ssl.keyStore and their corresponding values in Spring properties need to be file system paths. Does your approach work if you copy them to the local file system and referene those instead?

Toby
  • 186
  • 3