I want to open a HTTPS connection in a Google App Engine app using the URLFetch service. To be able to verify the SSL certificate of the server my app is talking to, I am using my own keystore file. I want to read this file in a warmup request when my app is loaded i.e. before any HTTPS requests are performed. The keystore file is part of my WAR file.
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(ClassLoader.getSystemResourceAsStream("myKeystoreFile"), "password".toCharArray());
trustManagerFactory.init(keystore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustManagers, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
I cannot use this approach, however, because while HttpURLConnection is on the GAE's JRE whitelist, HttpsUrlConnection is not.
Is there another way to use a custom keystore in GAE? I did not find any information on this in the GAE docs. It looks like while Google's URLFetch service supports HTTPS, the keystore cannot be customized. Is this correct?
If this isn't possible, is the approach still valid in general? Or is there a different approach that does still allow me to verify the SSL certificate?
UPDATE
In 2009, App Engine developer Nick Johnson from Google said on https://groups.google.com/d/topic/google-appengine-python/C9RSDGeIraE/discussion:
The urlfetch API doesn't allow you to specify your own client
certificates, so unfortunately what you want to achieve is not
currently possible.
Is this still correct? If every HTTP(s) request in App Engine relies on URLFetch this would mean that custom certificates just cannot be used at all in GAE.
I was recently facing the same issue and using the HttpClient implementation packaged with appengine-api-stubs worked for me.
Maven Dependency:
<dependency>
<groupId>com.google.appengine</groupId>
<artifactId>appengine-api-stubs</artifactId>
<version>1.9.18</version>
</dependency>
Code:
// create SSL Context which trusts your self-signed certificate
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(ClassLoader.getSystemResourceAsStream("myKeystoreFile"), "password".toCharArray());
trustManagerFactory.init(keystore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustManagers, null);
// register your trusting SSL context
Protocol.registerProtocol("https",
new Protocol("https", (ProtocolSocketFactory) new SocketFactoryWrapper(sslContext.getSocketFactory()), 443));
// make the https call
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("https://myendpoint.com");
httpclient.executeMethod(httpget);
System.out.println(httpget.getStatusLine());
This does essentially the same thing as
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
But for one reason or another app engine doesn't block it.
You should sign up as a trusted tester for Outbound sockets support. Under Desired Features they enlist SSL. If more parts of the JDK SSL API would be supported building something like this would be simple.
I have had a similar problem... I needed a keystore.p12
to call a foreign web service but had no chance loading it from the classpath. The only way I was able to use it was to put it in e.g. /WEB-INF/keystore.p12
and load it from there.
ServletContext context = getContext();
URL resourceUrl = context.getResource("/WEB-INF/keystore.p12");
I am using Appengine API 1.9.56 and what Dima suggested with
Protocol.registerProtocol("https",
new Protocol("https", (ProtocolSocketFactory) new SocketFactoryWrapper(sslContext.getSocketFactory()), 443));
is not working anymore. Therefore I do not need the appengine-api-stubs
library, either.
However, HttpsURLConnection.setDefaultSSLSocketFactory
just works fine.
Here is my complete solution which solved my problem:
KeyStore keystore = KeyStore.getInstance("JKS");
InputStream in = getClass().getResourceAsStream("/mytruststore.jks");
keystore.load(in, "password".toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keystore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustManagers, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
Luckily HttpsURLConnection
is not blacklisted anymore, as it was in the time the question was made.