Apple Push Notification Service

From The Apple Wiki

Introduced in iPhoneOS 3, Apple Push Notification Service ("APNs", also known as "Apple Push Service Protocol") allows devices to receive and display push notifications from an app, even when the app is not running. APNs allows the device to subscribe to "topics", and only notifications matching the subscribed topics will be delivered. In addition to receiving, the protocol can also be used to send messages to other devices. This mechanism is not only used by push notifications, but also by other real-time interactions such as iMessage and HomeKit.

APNs communicates over TLS on TCP port 5223 (although it can fall back to port 443). It connects to a subdomain of push.apple.com, either dynamically determined or static, depending on the platform.

A device identifies itself to APNs by proving that it controls a certificate obtained from Albert. Initially, this was done using TCP client certificates, but this allowed fingerprinting.[1] In response, Apple introduced an upgraded protocol called apns-security-v3, which must be negotiated over ALPN.

The binary protocol used in APNs has evolved over time. The initial versions used a proprietary binary protocol in a simple type-length-value encoding. Each message was preceded by a "command". The initial version used until iOS 4 used commands 00 to 06. In iOS 5, the protocol was upgraded to use the new commands 07 and above.[2] Some commands have been added in iOS versions since then.[3]

In iOS 10, Apple introduced apns-pack-v1, which is a packed version of the existing protocol designed to be byte-efficient. However, it is not necessary to use this encoding, as several official platforms still use the old protocol. The new protocol is also negotiated over ALPN, and it is possible to force iOS to downgrade to the old protocol.[4]

Message Structure

The format of the non-packed APNS protocol is as follows:

  • 1 byte message type ("command ID")
  • 4 byte payload length
  • items, all with
    • 1 byte type
    • 2 byte length
    • value

All integers are big-endian.

Example:

  • 07 command ID (Connect)
  • 00 00 00 27 39 byte payload length
  • 01 item 1
    • 00 20 32 byte length
    • 8a 73 82 00 82 ac 91 32 88 b6 aa ef 90 91 65 ce 8a 73 82 00 82 ac 91 32 88 b6 aa ef 90 91 65 ce value 1 (32-byte push token)
  • 02 item 2
    • 00 01 1 byte length
    • 01 value

Items may be repeated (multiple items with the same ID). For example, command 09 "push topics" has multiple items 02 "enabled topic", one for each topic hash to be enabled.

Commands

Note that some items are optional, either because they're only sent depending on certain conditions, or because they were introduced in a later iOS version and earlier ones don't send it. That will be documented in more detail later.

07 Connect

First command sent after SSL handshake is completed.

When a device is first activated, it doesn't have a push token yet. This command 07 is sent without a token, and the server returns a new token in its command 08 reply. In all future connections, the device sends the token in command 07, and the server doesn't return a token in command 08.

  • Direction: device to server
  • command ID: 07
  • items:
    • 01 32-byte push token
    • 02 1 byte "state" (value 01)
    • 05 4-byte flags, bitfield (example from iOS 12: 00 00 02 6a)
    • 06 1 byte interface (0: cellular, 1: Wi-Fi)
    • 08 cellular carrier name (or the string "WiFi")
    • 09 OS version (example: 12.4.8)
    • 0a OS build (example: 16G201)
    • 0b hardware version (example: iPhone6,1)
    • 0c certificate, contains the X.509 "device certificate" obtained during device activation
    • 0d 17-byte nonce, consisting of 1 byte fixed 00, 8 bytes timestamp (milliseconds since Unix epoch), 8 bytes random
    • 0e "signature", consisting of fixed bytes 01 01, followed by RSASSA-PKCS1-SHA1 signature of the nonce (0d) using the private key corresponding to the device certificate
    • 10 2-byte int, possibly protocol version
    • 11 2-byte int, "redirect count"
    • 13 2-byte int, "DNS resolve time" in milliseconds
    • 14 2-byte int, "TLS handshake time" in milliseconds

08 Connect Response

  • Direction: server to device
  • command ID: 08
  • items:
    • 01 status (00 ok, 02 some error)
    • 03 32-byte push token (unless the device sent one in Connect)
    • 04 2-byte int, max message size (value 10 00)
    • 05 unknown (value 00 02)
    • 06 capabilities (bitfield)
    • 08 2-byte int, large message size
    • 0a 8-byte int, server time, milliseconds since unix epoch
    • 0b 2 bytes, geo region (country code)

The lowest significant bit in 'capabilities' seems to mean "dual channel support" (possibly related to the iPhone proxying Apple Watch notifications).

09 Push Topics

  • Direction: device to server
  • command ID: 09
  • items:
    • 01 device's push token
    • 02 20-byte hash for enabled topics
    • 03 20-byte hash for disabled topics
    • 04 20-byte hash for "opportunistic" topics
    • 05 20-byte hash for "paused" topics

Note that there are multiple items with the same numeric ID, one for each topic hash.

"Topic hashes" are SHA-1 hashes of the topic name (a reverse-DNS string). This can be an internal topic name used by an Apple service, or for third party apps, the app bundle ID. For example, iMessage notifications have topic hash e4e6d952954168d0a5db02dbaf27cc35fc18d159, which corresponds to sha1("com.apple.madrid").

0A Push Notification

  • Direction: server to device (for iMessage and other internal services, also the other way round)
  • command ID: 0a
  • items:
    • 01 topic hash or push token (see below)
    • 02 topic hash or push token (see below)
    • 03 notification payload
    • 04 message ID (4 bytes)
    • 05 expiry (32-bit UNIX timestamp)
    • 06 timestamp (64-bit UNIX timestamp in nanoseconds)
    • 07 unknown (00)

Items 01 and 02 are swapped depending on the message direction (I don't think there's a good reason for this, probably a mistake that now they can't fix). If this is an incoming notification, from the server to the device, item 01 is the recipient's push token, and item 02 is the topic hash. If this is an outgoing message, from the device to the server, item 01 is the topic hash and item 02 is the sender's push token.

The notification payload can be either JSON (common for App Store apps) or binary plist (common for internal services).

0B Push Notification Ack

  • Direction: server to device (for iMessage and possibly others too, also the other way round)
  • command ID: 0b
  • items:
    • 04 message ID of the notification being acknowledged
    • 08 status (00 ok, 02 error)

0C Keep-Alive

  • Direction: device to server
  • command ID: 0c
  • items:
    • 01 connection method ("WiFi" or GSM MNC like "31038" for AT&T)
    • 02 iOS version, e.g. "5.0"
    • 03 iOS build number
    • 04 device model, e.g. "iPhone2,1"
    • 05 unknown (values like 10, 15 or 20)

0D Keep-Alive Confirmation

  • Direction: server to device
  • command ID: 0d
  • no items

0E No Storage

  • Direction: server to device
  • command ID: 0e
  • items:
    • 03 32-byte push token

0F Flush

  • Direction: both
  • command ID: 0f
  • items:
    • 2-byte integer indicating length of padding
    • padding: NULL-bytes, typical lengths are 64, 128, 256, 512

References