dbush dbush - 6 months ago 40
iOS Question

Apple pushes not working after upgrading from Java 6 to Java 7/8

I'm in the process of upgrading an application that sends push notifications to Apple devices via APNS from Java 6 to Java 8.

When running the same JAR that works under Java 6 on Java 8 using the same PKCS12 certificate file, if I attempt to send a push I get back status code 8 (invalid token).

What could be causing this?

Answer

This issues is caused by a combination of the contents of the PKCS12 file and a change in the way Java reads PKCS12 files into a KeyStore object between Java 6 and 7.

Running openssl pkcs12 -in filename on the pkcs12 file in question yields the following:

Enter Import Password:
MAC verified OK
Bag Attributes
    friendlyName: Apple Development IOS Push Services: app.id.1
    localKeyID: .... snip ....
subject=/UID=app.id.1/CN=Apple Development IOS Push Services: app.id.1/OU=PRXXXXXXXX/C=US
issuer=/C=US/O=Apple Inc./OU=Apple Worldwide Developer Relations/CN=Apple Worldwide Developer Relations Certification Authority
-----BEGIN CERTIFICATE-----
... snip ...
-----END CERTIFICATE-----
Bag Attributes
    friendlyName: Apple Development IOS Push Services: app.id.2
    localKeyID: .... snip ....
subject=/UID=app.id.2/CN=Apple Development IOS Push Services: app.id.2/OU=PRXXXXXXXX/C=US
issuer=/C=US/O=Apple Inc./OU=Apple Worldwide Developer Relations/CN=Apple Worldwide Developer Relations Certification Authority
-----BEGIN CERTIFICATE-----
... snip ...
-----END CERTIFICATE-----
Bag Attributes
    friendlyName: User Name
    localKeyID: ... snip ...
Key Attributes: <No Attributes>
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----BEGIN ENCRYPTED PRIVATE KEY-----
... snip ...
-----END ENCRYPTED PRIVATE KEY-----
Bag Attributes
    friendlyName: User Name
    localKeyID: ... snip ...
Key Attributes: <No Attributes>
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----BEGIN ENCRYPTED PRIVATE KEY-----
... snip ...
-----END ENCRYPTED PRIVATE KEY-----

You can see here that the PKCS12 file contains 2 certificates and 2 private keys (2 copies of the same key actually), each for a different App ID. The first one listed is the intended App ID, while the second is for a different application we use.

This file is read into a KeyStore which is subsequently passed to an SSLSocket to connect to Apple. This is done as follows:

    String password = "my_password";
    KeyStore.ProtectionParameter pwParam = new KeyStore.PasswordProtection(password.toCharArray());
    KeyStore keystore = KeyStore.getInstance("PKCS12");
    System.out.println("keystore classname: " + keystore.getClass().getName());
    FileInputStream fileStream = new FileInputStream("certificate_file.p12");
    keystore.load(fileStream, password.toCharArray());
    for (Enumeration<String> e = keystore.aliases(); e.hasMoreElements(); ) {
            String alias = e.nextElement();
            System.out.println("*** entry name: " + alias);
            KeyStore.Entry entry = keystore.getEntry(alias, pwParam);
            System.out.println("entry as string: " + entry.toString());
            for (Certificate cert: keystore.getCertificateChain(alias)) {
                    System.out.println("*** cert: " + cert);
            }
    }

Under Java 6, running the above gives us the following:

keystore classname: java.security.KeyStore
*** entry name: User Name
entry as string: Private key entry and certificate chain with 1 elements:
[
[
  Version: V3
  Subject: C=US, OU=PRXXXXXXXX, CN=Apple Development IOS Push Services: app.id.1, UID=app.id.1

....

Under Java 7 or 8, we get this:

keystore classname: java.security.KeyStore
*** entry name: User Name
entry as string: Private key entry and certificate chain with 1 elements:
[
[
  Version: V3
  Subject: C=US, OU=PRXXXXXXXX, CN=Apple Development IOS Push Services: app.id.2, UID=app.id.2

So because both certificates reference the same private key, one overwrites the other, so the KeyStore contains only one of the two certs. Under Java 6, the first certificate is retained, while Java 7 and 8 retain the second certificate. So when the connection is made to Apple, it sends the wrong certificate, and any pushes sent are considered invalid because the App ID for the push tokens being sent don't match the App ID of the certificate used to connect.

To fix this, the PKCS12 file should be generated with only the intended App ID. This ensures that the proper certificate is read and subsequently used to connect to Apple.