Tomcat java.io.IOException: Invalid keystore format when loading keystore via Classloader

6.9k views Asked by At

I've been trying for sometime to figure out this problem. I keep getting invalid keystore format error when loading keystore via the Classloader on Tomcat. I've written some unit tests which are successful at loading my keystore via the classpath, but when running in Tomcat I get the Invalid keystore format error.

This is for a RestTemplate Spring REST client.

My Spring Configuration code... pretty much the same in the unit test and real config.

@Value("${env}")
private String env;

@Bean
public RestTemplate getRestTemplate(HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory) {
    return new RestTemplate(httpComponentsClientHttpRequestFactory);
}

@Bean
public HttpComponentsClientHttpRequestFactory getHttpComponentsClientHttpRequestFactory(HttpClient httpClient) {
    HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
    return httpComponentsClientHttpRequestFactory;
}


@Bean
public HttpClientConnectionManager getHttpClientConnectionManager() throws Exception {
    String password = environment.getProperty("keystore.password");
    InputStream cpStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(env + "/keystore.jks");

    //load the keystore
    KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
    keystore.load(cpStream, password.toCharArray());
    SSLContext sslContext  = SSLContexts.custom().loadTrustMaterial(keystore).build();
    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);

    PlainConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
    Registry<ConnectionSocketFactory> r = RegistryBuilder.<ConnectionSocketFactory>create().register("http",plainsf).register("https",sslsf).build();

    PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(r);
    cm.setMaxTotal(200);
    cm.setDefaultMaxPerRoute(50);
    return cm;
}

@Bean
public HttpClient getHttpClient(HttpClientConnectionManager httpClientConnectionManager) {
    return HttpClients.custom().setConnectionManager(httpClientConnectionManager).build();
}

This code works fine to create my RestTemplate with keystore I need for my SSLContext in my unit test. I've also tried following different ways to get the InputStream for my keystore from the class loader via my unit test and these all worked too.

InputStream cpStream = this.getClass().getClassLoader().getResourceAsStream(env + "/csaa.jks");

InputStream cpStream = this.getClass().getResourceAsStream("/" + env + "/csaa.jks");

The env variable is just a way to load a particular environments properties... env=DEV , etc.... Everything works fine with running this with JUnit SpringJUnit4ClassRunner.class but when deploying it to Tomcat I always get the Invalid keystore error. If I use a FileInputStream with a hard coded path to the keystore on the file system it works just fine in Tomcat. I really want to load it with the Classloader.

I tried it in Liberty Profile too with same results.

Relevant part of the stack trace:

Caused by: java.io.IOException: Invalid keystore format
at sun.security.provider.JavaKeyStore.engineLoad(JavaKeyStore.java:650)
at sun.security.provider.JavaKeyStore$JKS.engineLoad(JavaKeyStore.java:55)
at java.security.KeyStore.load(KeyStore.java:1445)
2

There are 2 answers

1
P. Rower On

I found my problem... my Maven filtering was corrupting the keystore. I need to add the following to my pom.xml. Sadly I don't think this the first time I had this problem. :(

pom.xml: <build> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> <includes> <include>**/*.xml</include> </includes> <excludes> <exclude>**/*.jks</exclude> </excludes> </resource> <resource> <directory>src/main/resources</directory> <filtering>false</filtering> <includes> <include>**/*.jks</include> </includes> <excludes> <exclude>**/*.xml</exclude> </excludes> </resource> </resources>

0
xiaochangbai On

This is because Maven transcodes the key file during packaging. It can be solved by adding this Maven plug-in.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-resources-plugin</artifactId>
    <version>3.1.0</version>
    <configuration>
        <encoding>UTF-8</encoding>
        <useDefaultDelimiters>false</useDefaultDelimiters>
        <delimiters>
            <delimiter>$[*]</delimiter>
        </delimiters>
        <!-- Filter file suffixes that do not require transcoding .crt/.p8/jks -->
        <nonFilteredFileExtensions>
            <nonFilteredFileExtension>crt</nonFilteredFileExtension>
            <nonFilteredFileExtension>p8</nonFilteredFileExtension>
            <nonFilteredFileExtension>jks</nonFilteredFileExtension>
        </nonFilteredFileExtensions>
    </configuration>
</plugin>