Loading a custom key store in Google App Engine Ja

2019-01-22 18:58发布

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.

4条回答
Luminary・发光体
2楼-- · 2019-01-22 19:46

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.

查看更多
太酷不给撩
3楼-- · 2019-01-22 19:48

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.

查看更多
4楼-- · 2019-01-22 19:50

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.

查看更多
做个烂人
5楼-- · 2019-01-22 19:55

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");
查看更多
登录 后发表回答