The iPhone Wiki is no longer updated. Visit this article on The Apple Wiki for current information. |
Difference between revisions of "Apple Push Service Protocol"
(Mention apnspack protocol) |
(Rewrite intro paragraph) |
||
(15 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
+ | In iPhoneOS 3, Apple introduced remote push notifications. The iPhone keeps a persistent TCP connection to the Apple push notification service ("APNS", sometimes written "APNs" in Apple docs). The server associated with an app can send a notification to Apple servers, which will then send it to the user's device to be displayed, even when the app is not running. |
||
− | iOS devices connect to Apple's push servers via port 5223. The protocol is proprietary and has nothing to do with XMPP (which uses the same port to establish SSL-encrypted client connections). The Push service protocol also uses SSL encryption. |
||
− | + | APNS uses a proprietary binary protocol for communication between the device and Apple, encrypted with TLS, over TCP port 5223 (although it can fall back to 443 if it hits firewalls). The protocol has nothing to do with XMPP, which uses the same port to establish SSL-encrypted client connections. Up to iOS 4, the protocol used command IDs <code>00</code> to <code>06</code>. In iOS 5, the protocol was changed; it now uses command IDs starting at <code>07</code> and all fields in a message (called "items") have a consistent type-length-value encoding. This is also when it was introduced into macOS (Lion). |
|
− | While every iOS version after that continued adding new |
+ | While every iOS version after that continued adding new commands and items, since iOS 10 there is ''another'' new push protocol known as "apnspack", which uses the same command and item IDs but encodes them in a completely different binary format. The client negotiates use of this new protocol with the protocol name "apns-pack-v1" in [[wikipedia:ALPN|ALPN]]. The apnspack format is not yet documented in this page. |
==Message Structure== |
==Message Structure== |
||
Line 9: | Line 9: | ||
The format of the non-packed APNS protocol is as follows: |
The format of the non-packed APNS protocol is as follows: |
||
− | *1 byte message type |
+ | *1 byte message type ("command ID") |
*4 byte payload length |
*4 byte payload length |
||
− | * |
+ | *items, all with |
**1 byte type |
**1 byte type |
||
**2 byte length |
**2 byte length |
||
**value |
**value |
||
+ | |||
+ | All integers are big-endian. |
||
Example: |
Example: |
||
− | *<code>07</code> |
+ | *<code>07</code> command ID (Connect) |
*<code>00 00 00 27</code> 39 byte payload length |
*<code>00 00 00 27</code> 39 byte payload length |
||
− | *<code>01</code> |
+ | *<code>01</code> item 1 |
**<code>00 20</code> 32 byte length |
**<code>00 20</code> 32 byte length |
||
− | **<code>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</code>value 1 (32-byte push token) |
+ | **<code>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</code> value 1 (32-byte push token) |
− | *<code>02</code> |
+ | *<code>02</code> item 2 |
**<code>00 01</code> 1 byte length |
**<code>00 01</code> 1 byte length |
||
**<code>01</code> value |
**<code>01</code> value |
||
+ | Items may be repeated (multiple items with the same ID). |
||
− | ===Messages=== |
||
+ | For example, command <code>09</code> "push topics" |
||
+ | has multiple items <code>02</code> "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. |
||
− | ====07 Connect==== |
||
*Direction: device to server |
*Direction: device to server |
||
− | * |
+ | *command ID: <code>07</code> |
− | * |
+ | *items: |
**<code>01</code> 32-byte push token |
**<code>01</code> 32-byte push token |
||
− | **<code>02</code> 1 byte |
+ | **<code>02</code> 1 byte "state" (value <code>01</code>) |
+ | **<code>05</code> 4-byte flags, bitfield (example from iOS 12: <code>00 00 02 6a</code>) |
||
+ | **<code>06</code> 1 byte interface (0: cellular, 1: Wi-Fi) |
||
+ | **<code>08</code> cellular carrier name (or the string "WiFi") |
||
+ | **<code>09</code> OS version (example: 12.4.8) |
||
+ | **<code>0a</code> OS build (example: 16G201) |
||
+ | **<code>0b</code> hardware version (example: iPhone6,1) |
||
+ | **<code>0c</code> certificate, contains the X.509 "device certificate" obtained during device activation |
||
+ | **<code>0d</code> 17-byte nonce, consisting of 1 byte fixed <code>00</code>, 8 bytes timestamp (milliseconds since Unix epoch), 8 bytes random |
||
+ | **<code>0e</code> "signature", consisting of fixed bytes 01 01, followed by RSASSA-PKCS1-SHA1 signature of the nonce (0d) using the public key in the certificate (0c) |
||
+ | **<code>10</code> 2-byte int, possibly protocol version |
||
+ | **<code>11</code> 2-byte int, "redirect count" |
||
+ | **<code>13</code> 2-byte int, "DNS resolve time" in milliseconds |
||
+ | **<code>14</code> 2-byte int, "TLS handshake time" in milliseconds |
||
− | + | ===08 Connect Response=== |
|
*Direction: server to device |
*Direction: server to device |
||
− | * |
+ | *command ID: <code>08</code> |
− | * |
+ | *items: |
**<code>01</code> status (<code>00</code> ok, <code>02</code> some error) |
**<code>01</code> status (<code>00</code> ok, <code>02</code> some error) |
||
− | **<code> |
+ | **<code>03</code> 32-byte push token (unless the device sent one in Connect) |
+ | **<code>04</code> 2-byte int, max message size (value <code>10 00</code>) |
||
**<code>05</code> unknown (value <code>00 02</code>) |
**<code>05</code> unknown (value <code>00 02</code>) |
||
− | **<code> |
+ | **<code>06</code> capabilities (bitfield) |
+ | **<code>08</code> 2-byte int, large message size |
||
+ | **<code>0a</code> 8-byte int, server time, milliseconds since unix epoch |
||
+ | **<code>0b</code> 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==== |
||
+ | |||
+ | ===09 Push Topics=== |
||
*Direction: device to server |
*Direction: device to server |
||
− | * |
+ | *command ID: <code>09</code> |
− | * |
+ | *items: |
+ | **<code>01</code> device's push token |
||
− | **<code>02</code> 20-byte ID for enabled topic (like topic for push-enabled app or a specific iCloud service like Find My iPhone) |
||
− | **<code> |
+ | **<code>02</code> 20-byte hash for enabled topics |
+ | **<code>03</code> 20-byte hash for disabled topics |
||
+ | **<code>04</code> 20-byte hash for "opportunistic" topics |
||
+ | **<code>05</code> 20-byte hash for "paused" topics |
||
+ | Note that there are multiple items with the same numeric ID, |
||
− | ====0A Push Notification==== |
||
+ | one for each topic hash. |
||
− | *Direction: server to device (for iMessage and possibly others too, also the other way round) |
||
+ | |||
− | *message type: <code>0a</code> |
||
+ | "Topic hashes" are SHA-1 hashes of the topic name (a reverse-DNS string). |
||
− | *fields: |
||
+ | This can be an internal topic name used by an Apple service, |
||
− | **<code>01</code> recipient push token |
||
+ | or for third party apps, the app bundle ID. |
||
− | **<code>02</code> topic |
||
+ | For example, iMessage notifications have topic hash <code>e4e6d952954168d0a5db02dbaf27cc35fc18d159</code>, |
||
+ | which corresponds to <code>sha1("com.apple.madrid")</code>. |
||
+ | |||
+ | ===0A Push Notification=== |
||
+ | *Direction: server to device (for iMessage and other internal services, also the other way round) |
||
+ | *command ID: <code>0a</code> |
||
+ | *items: |
||
+ | **<code>01</code> topic hash or push token (see below) |
||
+ | **<code>02</code> topic hash or push token (see below) |
||
**<code>03</code> notification payload |
**<code>03</code> notification payload |
||
− | **<code>04</code> |
+ | **<code>04</code> message ID (4 bytes) |
**<code>05</code> expiry (32-bit UNIX timestamp) |
**<code>05</code> expiry (32-bit UNIX timestamp) |
||
**<code>06</code> timestamp (64-bit UNIX timestamp in nanoseconds) |
**<code>06</code> timestamp (64-bit UNIX timestamp in nanoseconds) |
||
**<code>07</code> unknown (<code>00</code>) |
**<code>07</code> unknown (<code>00</code>) |
||
+ | Items 01 and 02 are swapped depending on the message direction |
||
− | ====0B Push Notification Response==== |
||
+ | (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) |
*Direction: server to device (for iMessage and possibly others too, also the other way round) |
||
− | * |
+ | *command ID: <code>0b</code> |
− | * |
+ | *items: |
− | **<code>04</code> |
+ | **<code>04</code> message ID of the notification being acknowledged |
**<code>08</code> status (<code>00</code> ok, <code>02</code> error) |
**<code>08</code> status (<code>00</code> ok, <code>02</code> error) |
||
− | + | ===0C Keep-Alive=== |
|
*Direction: device to server |
*Direction: device to server |
||
− | * |
+ | *command ID: <code>0c</code> |
− | * |
+ | *items: |
**<code>01</code> connection method ("WiFi" or GSM MNC like "31038" for AT&T) |
**<code>01</code> connection method ("WiFi" or GSM MNC like "31038" for AT&T) |
||
**<code>02</code> iOS version, e.g. "5.0" |
**<code>02</code> iOS version, e.g. "5.0" |
||
Line 80: | Line 139: | ||
**<code>05</code> unknown (values like <code>10</code>, <code>15</code> or <code>20</code>) |
**<code>05</code> unknown (values like <code>10</code>, <code>15</code> or <code>20</code>) |
||
− | + | ===0D Keep-Alive Confirmation=== |
|
*Direction: server to device |
*Direction: server to device |
||
− | * |
+ | *command ID: <code>0d</code> |
− | *no |
+ | *no items |
− | + | ===0E No Storage=== |
|
*Direction: server to device |
*Direction: server to device |
||
− | * |
+ | *command ID: <code>0e</code> |
− | * |
+ | *items: |
**<code>03</code> 32-byte push token |
**<code>03</code> 32-byte push token |
||
− | + | ===0F Flush=== |
|
*Direction: both |
*Direction: both |
||
− | * |
+ | *command ID: <code>0f</code> |
− | * |
+ | *items: |
**2-byte integer indicating length of padding |
**2-byte integer indicating length of padding |
||
**padding: NULL-bytes, typical lengths are 64, 128, 256, 512 |
**padding: NULL-bytes, typical lengths are 64, 128, 256, 512 |
||
Line 100: | Line 159: | ||
==References== |
==References== |
||
*[https://github.com/meeee/pushproxy/blob/master/doc/apple-push-protocol-ios5-lion.md Source of this info] |
*[https://github.com/meeee/pushproxy/blob/master/doc/apple-push-protocol-ios5-lion.md Source of this info] |
||
+ | |||
+ | [[Category:Protocols]] |
Latest revision as of 22:18, 15 August 2021
In iPhoneOS 3, Apple introduced remote push notifications. The iPhone keeps a persistent TCP connection to the Apple push notification service ("APNS", sometimes written "APNs" in Apple docs). The server associated with an app can send a notification to Apple servers, which will then send it to the user's device to be displayed, even when the app is not running.
APNS uses a proprietary binary protocol for communication between the device and Apple, encrypted with TLS, over TCP port 5223 (although it can fall back to 443 if it hits firewalls). The protocol has nothing to do with XMPP, which uses the same port to establish SSL-encrypted client connections. Up to iOS 4, the protocol used command IDs 00
to 06
. In iOS 5, the protocol was changed; it now uses command IDs starting at 07
and all fields in a message (called "items") have a consistent type-length-value encoding. This is also when it was introduced into macOS (Lion).
While every iOS version after that continued adding new commands and items, since iOS 10 there is another new push protocol known as "apnspack", which uses the same command and item IDs but encodes them in a completely different binary format. The client negotiates use of this new protocol with the protocol name "apns-pack-v1" in ALPN. The apnspack format is not yet documented in this page.
Contents
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 length01
item 100 20
32 byte length8a 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 200 01
1 byte length01
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 token02
1 byte "state" (value01
)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 activation0d
17-byte nonce, consisting of 1 byte fixed00
, 8 bytes timestamp (milliseconds since Unix epoch), 8 bytes random0e
"signature", consisting of fixed bytes 01 01, followed by RSASSA-PKCS1-SHA1 signature of the nonce (0d) using the public key in the certificate (0c)10
2-byte int, possibly protocol version11
2-byte int, "redirect count"13
2-byte int, "DNS resolve time" in milliseconds14
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 (value10 00
)05
unknown (value00 02
)06
capabilities (bitfield)08
2-byte int, large message size0a
8-byte int, server time, milliseconds since unix epoch0b
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 token02
20-byte hash for enabled topics03
20-byte hash for disabled topics04
20-byte hash for "opportunistic" topics05
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 payload04
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 acknowledged08
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 number04
device model, e.g. "iPhone2,1"05
unknown (values like10
,15
or20
)
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