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)
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With