Java (and Clojure) developers sending Apple push notifications often need to do this, and it’s surprisingly under-documented.

If you search for “apns certificate java keystore” online you’ll find precious little information, even though surely there must be many, many Java-based servers talking to iOS devices via Apple’s Push Notification Service (APNs). Is everyone using some library I’m missing?

So I’m going to donate my experience in the hope that it saves at least one poor sod from dealing with the tedious, error-prone, and frustrating process of getting a certificate from Apple into a format you can use with Java and Clojure.

Dependencies And Caveats

The process described here will only work verbatim on a Mac, which is unlikely to be controversial if you’re an iOS development shop. This system was last tested on Mac OS X 10.9/Java 7, so if you have that environment, you’re good to go.

If you can get the key and certificate steps organised in another way that does not involve Keychain Access app on a Mac, then it’s likely the rest of the process will work on any Unixy platform so long as you have the openssl command line utility.

Obviously you’ll need a Java (6+) JDK (not the JRE) installed, and the JDK’s keytool utility needs to be in your path.

Layout

In your project, you’ll end up with a folder structure like so:

<project>/
  apns/
    make-apns-keystore
    apns-appstore.jks
    apns-adhoc.jks
    source-certs/
      apple_WWDR_CA.cer
      entrust_2048_ca.cer
      appstore_aps_cert.cer
      appstore_aps_key.p12
      adhoc_aps_cert.cer
      adhoc_aps_key.p12

The source-certs folder contains the certificates and keys that are pulled together into a Java keystore (.jks file), which can be used to generate an appropriate TLS context for connecting to Apple’s APNs servers. There are two keystores in this setup, one for the App Store and one for an ad hoc distribution used for beta testing.

Root Certificates

Create the apns/source-certs directory, grab Apple’s developer relations root certificate and copy it into source-certs as apple_WWDR_CA.cer. This is the certificate that Apple uses to sign the APNs push certificates that it issues, and this certificate is itself signed by Entrust’s 2048-bit root certificate2, which you should also copy into source-certs.1

Your Push Certificate

Go to the Certificates area at developer.apple.com, and create a new certificate:

  • Apple Push Notification service SSL (Sandbox): for ad hoc testing.
  • Apple Push Notification service SSL (Production): for use in apps submitted to the App Store.

Follow Apple’s instructions on how to create a Certificate Signing Request (CSR). Use a name like ‘<appname>_appstore_apns_key’ in the ‘Common Name’ field of the CSR, and make note of it. You’ll eventually get back a .cer file that you should copy into source-certs as either appstore_aps_cert.cer or adhoc_aps_cert.cer depending on its use.

Now you’ll need the private key that the certificate is signing, which was created by Keychain Access as part of the CSR. Import the certificate from Apple by double-clicking it to help identify it. Then open Keychain Access, select the ‘login’ keychain, then ‘Keys’ under Category, and find the key that has the name you entered as the Common Name in the CSR (it should be attached to the certificate you downloaded). See below for an example of Shopi’s App Store APNs key:

Keychain Access

Right click the key, select ‘Export’, and save it in the source-certs folder as appstore_aps_key.p12 or adhoc_aps_key.p12, using the password of your choice (this password will also be used for the Java keystore).

Putting It Together

You now have all the material you need:

  • The root CA (Entrust) certificate
  • Apple’s WWDR CA certificate
  • Your key and corresponding certificate from Apple’s WWDR CA

Now to put them into a Java keystore (.jks) file. This is rocket science, apparently. In theory the Java keystore tool can import PKCS12 and X.509 directly. In practice I’ve found it NPE’s out. So I’ve written the following script to convert everything to the universal PEM format that OpenSSL likes, merge everything together to a single PKCS12 bundle, and then import that into a keystore.

#!/bin/sh

# To update key stores:
#
# 1. Send cert request via Apple dev site: download cert, and export
#    private key generated by request from "keys" section of Keychain
#    Access as e.g. "adhoc_apns_key.p12", password same as set in
#    "password=" line below. Put in "tls/source_certs".
#
# 2. Run "./make-apns-keystore appstore" to build apns-xxx.jks
#    keystore.
#
# 3. Profit!
#
# End up with keystore containing:
#
# * APNS developer key chained with downloaded intermediate cert
# (which seems to have the app bundle ID in it) and the WWDR CA cert.
#
# * Entrust 2048-bit root CA cert as trusted so that we can
# * authenticate the APNs server.

usage="$1"

if [ -z $usage ]; then
  echo "Usage: make-apns-keystore <deploy-type>"
  echo ""
  echo "e.g. make-apns-keystore adhoc"

  exit 1
fi

# PKCS12 password and store password (you should change this)
password="CHANGE-ME"

keystore_p12="apns-${usage}.p12"
keystore_jks="apns-${usage}.jks"

rm -f "$keystore_p12" "$keystore_jks"

echo "* Make PEM versions"

openssl pkcs12 -in "source_certs/${usage}_aps_key.p12" \
  -out "source_certs/${usage}_aps_key.pem" -nodes \
  -passin "pass:$password"

openssl x509 -inform der \
  -in "source_certs/${usage}_aps_cert.cer" \
  -out "source_certs/${usage}_aps_cert.pem"

openssl x509 -inform der \
  -in "source_certs/apple_WWDR_CA.cer" \
  -out "source_certs/apple_WWDR_CA.pem"

# NB: we create a PKCS12 keystore with developer+app private key and
# cert chain to WWDR CA and then import to JKS because keytool NPE's
# on the key import directly. We then need the Entrust CA cert to
# verify the APNs server (the latter from
# https://www.entrust.net/downloads/root_request.cfm#)

echo "* Create PKCS12 $keystore_p12"

openssl pkcs12 -export -out "$keystore_p12" \
  -inkey "source_certs/${usage}_aps_key.pem"  \
  -in "source_certs/${usage}_aps_cert.pem" \
  -certfile "source_certs/apple_WWDR_CA.pem" \
  -passout "pass:$password"

rm -f "source_certs/${usage}_aps_key.pem" \
      "source_certs/${usage}_aps_cert.pem" \
      "source_certs/apple_WWDR_CA.pem" 

echo "* Import $keystore_p12 to $keystore_jks"

keytool -v -importkeystore \
  -srckeystore "$keystore_p12" \
  -srcstoretype pkcs12 -srcstorepass "$password" \
  -destkeystore "$keystore_jks" \
  -deststoretype jks -deststorepass "$password" -noprompt

rm -f "$keystore_p12"

keytool -importcert -keystore "$keystore_jks" -alias "entrust_2048_ca" \
  -storepass "$password" \
  -file "source_certs/entrust_2048_ca.cer" -noprompt

echo "* Show contents"

keytool -list -storepass "$password" -keystore "$keystore_jks" \
  -storetype jks

Copy this as apns/make-apns-keystore (or download it from here), and chmod +x it. Edit it and change the password= line to the one you selected earlier.

Now you should be able to cd apns && ./make-apns-keystore appstore or ./make-apns-keystore adhoc to generate the apns-appstore.jks and apns-adhoc.jks keystores ready for use in creating a TLS context for connecting to Apple’s APNs servers.

Next time you need to renew your APNs certs, just overwrite the old cert and key with the new ones, and run the script.

Unless Apple changes something before then.

  1. Depending on what cert’s are already in your Java system-wide CA list you may or may not actually need Entrust’s certificate, but I like to make the keystore self-contained. ↩︎

  2. Or you can start from here -> Download Root Certificates -> Personal Use and Secure Server Installation -> Root Certificates -> Entrust.net Certification Authority (2048). Make sure you’re secured via HTTPS before downloading. ↩︎