5

On App Engine, I need a p12 file to create signed URLs:

https://developers.google.com/storage/docs/accesscontrol#Signing-Strings

Google does not describe what are best practices about keeping this file.

Can I use the WEB-INF directory to store the file? It would then be part of the source code and kept together with the password to open it.

What are best practices here? Or other approaches?

--

What about performance? Is it efficient to load the file over and over again? Does App Engine automatically cache the file across calls (on the same instance)? Or will I need to load the file once using a servlet and then keep it in a static variable somehow? Are there better ways to achieve this, like storing the file in a datastore record and then keep it in memcache? How secure would that approach be? Probably no good, right?

Oliver Hausler
  • 4,900
  • 4
  • 35
  • 70
  • 1
    Usually I use WEB-INF (not accesible from external sources) or the classpath (the classes folder, where the compiled java are stored) – Deviling Master Aug 21 '14 at 18:42

1 Answers1

6

In App Engine specifically there are a number of unusual security limitations around File storage. I have found the best place to store resources securely is by using the bundle itself. If you're using the default Maven setup as produced by the appengine maven skeleton project this is as simple as placing the file inside of the appropriate resources directory

Resources Directory Structure

Once the p12 is in the correct location, you'll need to load it using the class loader's GetResourceAsStream function. Then when building the GoogleCredentials, don't use the documented setServiceAccountPrivateKeyFromP12File() function, but instead use the setServiceAccountPrivateKey() function and pass in the PrivateKey that you just constructed.

Additionally, you will most likely not want to use any of this functionality with a live appengine instance since Appengine already provides you with a much easier to use AppIdentityCredentials function in that case so you will probably want to detect whether or not your app is in production mode and only use the ServiceAccount when testing using localhost.

Putting all of these functions together yields the following function which works for me:

public static HttpRequestInitializer getDefaultCredentials() throws IOException
  {
      List<String> scopes = Arrays.asList(new String[] {DEVSTORAGE_FULL_CONTROL});
      if (SystemProperty.environment.value() == SystemProperty.Environment.Value.Production)
          return new AppIdentityCredential(scopes);
      else
      {

          GoogleCredential credential;

          try {
              String p12Password = "notasecret";

              ClassLoader classLoader = ServiceUtils.class.getClassLoader();

              KeyStore keystore = KeyStore.getInstance("PKCS12");
              InputStream keyFileStream = classLoader.getResourceAsStream("key.p12");

              if (keyFileStream == null){
                  throw new Exception("Key File Not Found.");
              }

              keystore.load(keyFileStream, p12Password.toCharArray());
              PrivateKey key = (PrivateKey)keystore.getKey("privatekey", p12Password.toCharArray());

              credential = new GoogleCredential.Builder()
                      .setTransport(HTTP_TRANSPORT)
                      .setJsonFactory(JSON_FACTORY)
                      .setServiceAccountId("YOUR_SERVICE_ACCOUNT_EMAIL@developer.gserviceaccount.com")
                      .setServiceAccountPrivateKey(key)
                      .setServiceAccountScopes(scopes)
                      .build();
          } catch (GeneralSecurityException e) {
              e.printStackTrace();
              return null;
          } catch (Exception e) {
              e.printStackTrace();
              return null;
          }

          return credential;

      }

  }
OverclockedTim
  • 1,713
  • 2
  • 12
  • 22
  • 1
    [Thanks Tim. This is a relatively old issue, but I will definitely look into this again when I go back to server development in a few weeks. Then I'll comment.] – Oliver Hausler Nov 21 '14 at 05:02
  • What you suggest for getting credentials in production is cool, but I can't use it because I need the PKCS12 for url signing, not Google credentials. I was trying to implement the static resources piece of your suggestion, but I ran into two problems: I do not see the Resources folder in my folder structure (it's a project based on a demo, Android Studio), and the ServiceUtils class does not exist. I was reading that the resource folder can be configured and I can tell app engine in appengine-web.xml that I want to treat a .p12 file as static, so this can be solved. Any ideas? – Oliver Hausler Nov 30 '14 at 04:47
  • Based on this idea, I left my .p12 file at its current location, but added to appengine-web.xml, which according to https://cloud.google.com/appengine/docs/java/config/appconfig#Java_appengine_web_xml_Static_files_and_resource_files would serve the p12 from a static file server. I could not see any reduction in cost, though. – Oliver Hausler Nov 30 '14 at 05:17
  • Ah good, I'm glad you got this working. I agree, the method I mentioned is mainly focused on security, not efficiency of access. I apologize for missing the second half of your question. I also agree that memcache is a bad place to put it as Google does not guarantee the security of memcache. Have you tried putting the .p12 loading function in a singleton? That way at least you would generally only be loading the file once per instance. That could still add up of course if you're running a lot of instances but it will probably save you some cycles. – OverclockedTim Dec 01 '14 at 05:31
  • Regarding the ServiceUtils class, you probably figured this out already, but ServiceUtils just happened to be the name of the class that this function was in. Any class is just fine - you're just trying to get to the underyling classloader so that you can call its getResourceAsStream function. – OverclockedTim Dec 01 '14 at 05:33
  • Yes, I also removed the directive already as I was concerned about security. I thought about the singleton for a moment, but then pulled up an article on the internet which said this would not work (don't find it anymore). Further reading today showed that you are probably right and it works as long as I load it once per instance. I'll run some tests and then get back with results. – Oliver Hausler Dec 01 '14 at 14:29
  • 1
    Wow, very nice, Tim. The singleton was a great idea. I ended up using just a private static variable for the key which I load when null, and got a performance increase of more than 400%. With all these optimizations I could reduce CPU time from 250 msec to almost 50 msec. Signing urls is still expensive, but much better now. – Oliver Hausler Dec 01 '14 at 16:20
  • "Then when building the GoogleCredentials, don't use the documented setServiceAccountPrivateKeyFromP12File() function, but instead use the setServiceAccountPrivateKey() function and pass in the PrivateKey that you just constructed." This actually helped me work around an issue I had with FileNotFoundExcpetion that I had when I used the standard way. Why is that? – k.liakos May 16 '16 at 11:37