Setting up Apple Push Notifications

Saturday, September 15, 2012

Tags: apple, programming, python, objc

While trying to figure this out I came across a great article by Matthijs Hollemans. I suggest following it if you’re interested in an in-depth tutorial. What follows is a more concise version of that article and instead of using PHP I’m using a variant of Jacky Tsoi’s Python script. This is pretty tedious and there are a lot of steps you have to get right in order for this to work properly so hang in there.

You’re going to need an iOS device, testing pushes cannot be done in the simulator. You’ll also need an iOS Developer Program membership and eventually a server but for our purposes we’ll just be using your local machine.

Payload: Pushes are just JSON sent from a server, a simple example looks like this:

{
  "aps": {
    "alert": "Hello world",
    "sound": "default",
    "badge": 1
  }
}

The max size of a push is 256 bytes so don’t get carried away—it’s good to remove all whitespace if you can.

Generate a Certificate Signing Request

  1. Open Keychain and navigate to Keychain Access > Certificate Assistant > Request a Certificate From a Certificate Authority
  2. Enter your Email, a Common Name “PushNotifs” (this can be anything you want), and check Saved to disk
  3. Click Continue and name the file “PushNotifs.certSigningRequest”
  4. Find “PushNotifs” in the Keys section of Keychain, right click on the private key and choose Export
  5. Save the private key as PushNotifsKey.p12 and choose a good passphrase

App ID and SSL Certificate

We need an App ID and SSL certificate from Apple’s iOS Provisioning Portal. Each push app needs its own App ID, you cannot use a wildcard ID.

  1. Click App IDs in the iOS Provisioning Portal sidebar and click the New App ID button
  2. Enter “PushNotifs” as the description and “com.nathanborror.PushNotifs” as the Bundle Identifier (replace ‘nathanborro’ with something more appropriate for yourself)
  3. After clicking Submit click configure next to the App ID we just made on the resulting screen
  4. Check “Enable for Apple Push Notification service” and click configure for “Development Push SSL Certificate”
  5. This first screen walks you through how to generate a Certificate Signing Request which we already did so you can click Continue
  6. On the next screen add the certificate we made earlier, “PushLook.certSigningRequest” and click Generate
  7. Wait for Apple to generate the SSL certificate then click Continue.
  8. Download the certificate and click Done

Keep these three files in a safe place. Development certificates are ephemeral so you’ll need to renew them every three months. Production certs last a year.

SSL .pem Creation

We need to create a .pem file to be used on our server. Using Terminal navigate to the folder with these three files in it and convert the .cer file to a .pem file:

$ openssl x509 -in aps_developer_identity.cer -inform DER -out PushNotifsCert.pem -outform PEM

# Now convert your private key to a .pem file:
$ openssl pkcs12 -nocerts -out PushNotifsKey.pem -in PushNotifsKey.p12

# You'll be asked to enter the password we created earlier and then you'll be prompted to enter a new pass phrase for .pem file. Now combine the certificate and key into a single file
$ cat PushNotifsCert.pem PushNotifsKey.pem > ck.pem

# To test the connection
$ openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert PushNotifsCert.pem -key PushNotifsKey.pem

You should see some output and be able to type a few characters, press enter and the server should disconnect. Openssl will let you know if there was a problem connecting.

Provisioning Profile

Head back to Apple’s Provisioning Portal, click Provisioning in the sidebar then click New Profile. I chose “PushNotifs Development” as my Profile Name, check your certificate, choose the App ID we just created, then choose the devices you plan to develop with. Refresh the page until you see a Download button next to the profile we just made and click it. Open the downloaded file so it gets added to Xcode.

Basic Push App

Start a new project in Xcode, pick the Empty Application template and name it PushNotifs (or whatever you named your App ID earlier, they should match up). in AppDelegate.m add the following:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];

    // Let the device know you want to receive push notifications
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];

    UIRemoteNotificationTypeAlert YES;
}

Now try building and running. It won’t work in the simulator because it doesn’t support push so you’ll need to run this on a device. You should get an alert asking you to allow push notifications. Before we move on add the following to AppDelegate.m:

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    NSLog(@"Token: %@", deviceToken);
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
    NSLog(@"Failed to aquire push token. Error: %@", [error localizedDescription]);
}

Now when you run this you should see a device token in the debug output. Keep this handy for the next portion.

Server Side

Now we need a simple script we can use on our server that connects to APN and sends push notifications for us. I’m going to use Python for this but you can use whatever you want. Create a new file called push.py in the same directory you put the ck.pem file we created earlier and write the following:

#!/usr/bin/env python

import ssl
import json
import socket
import struct
import binascii

TOKEN = 'YOUR_APPS_PUSH_TOKEN'
PAYLOAD = {
  'aps': {
  'alert': 'Hello Push!',
  'sound': 'default'
}

def send_push(token, payload):
  # Your certificate file
  cert = 'ck.pem'

  # APNS development server
  apns_address = ('gateway.sandbox.push.apple.com', 2195)

  # Use a socket to connect to APNS over SSL
  s = socket.socket()
  sock = ssl.wrap_socket(s, ssl_version=ssl.PROTOCOL_SSLv3, certfile=cert)
  sock.connect(apns_address)

  # Generate a notification packet
  token = binascii.unhexlify(token)
  fmt = '!cH32sH{0:d}s'.format(len(payload))
  cmd = '\x00'
  message = struct.pack(fmt, cmd, len(token), token, len(payload), payload)
  sock.write(message)
  sock.close()

if __name__ == '__main__':
  send_push(TOKEN, json.dumps(PAYLOAD))

Now you should be able to run: $ python push.py

Enter your pass phrase and you should receive a push notification on your device. If you would like to remove the pass-phrase you can do so by running: openssl rsa -in PushNotifsKey.pem -out PushNotifsKey.pem