|
Apple Push Service Protocol - iOS4.3.3
|
|
======================================
|
|
|
|
iOS devices connect to Apple's push servers via **port 5223**. The protocol has nothing to do with XMPP which used to establish SSL-encrypted client connections to this port. The Push service protocol also uses SSL encryption. While the protocol seems **proprietary**, client authentication happens using **SSL client certificates**. These client certificates are issued to the device during activation via iTunes and stored in a keychain.
|
|
|
|
The protocol uses a **Type-Length-Value** encoding, while sometimes the length is omitted and thus seems to be defined implicitly through the Type. Each of the **seven message types** has a one-byte type. The messages are outlined below.
|
|
|
|
00 Device Hello
|
|
-----------------------
|
|
* Structure
|
|
* `00` command
|
|
* 4-Byte size (always `00 20`/32 Byte)
|
|
* 32-Byte data - some device id? - seems to be different from UDID
|
|
* begin of conversation
|
|
|
|
01 Server Hello
|
|
-----------------------
|
|
* Structure
|
|
* `01` command
|
|
* 4 unknown Bytes (were always `00 00 00 00`)
|
|
* one of the following
|
|
* 1-Byte size `00`
|
|
* 1-Byte size `20` followed by 32 Byte "Push Token"
|
|
* sent via HTTP Header when registering on `https://registration.ess.apple.com/WebObjects/VCRegistrationService.woa/wa/register`
|
|
|
|
|
|
02 Push Application IDs
|
|
-------------------------------
|
|
* Structure
|
|
* `02` Command
|
|
* two lists containing 20-Byte Strings
|
|
* begin with 4-Byte item count and 4-Byte item size(always `14`/20)
|
|
* seems to send list of Push-enabled Applications to Apple
|
|
* one list for Apps with enabled push notifications, one for disabled?
|
|
* 19 Entries, sometimes in different lists
|
|
* 16 Push Apps shown in Settings.app + 3 iOS services?
|
|
* at least 3 were in the first list of all messages
|
|
* one of them is a string received together with Find-My-iPhone Push Notifications
|
|
|
|
03 Push Notification
|
|
----------------------------
|
|
* Server -> Device
|
|
* Structure
|
|
* `03` Command
|
|
* 4-Byte size (always `14`/20)
|
|
* 20 Bytes Data
|
|
* App ID for Push Notifications, appears in list of `03` messages
|
|
* 4-Byte size
|
|
* Data, Push Notification in JSON Format
|
|
* sample JSON from Prowl App(reformatted, XXX was unknown integer like 100123456)
|
|
<pre>{
|
|
"urlhint":"XXX",
|
|
"aps":
|
|
{
|
|
"badge":1,
|
|
"sound":"elysium.caf",
|
|
"alert":"2Prowl—Website\nhttps://localhost:8080/"
|
|
}
|
|
}</pre>
|
|
* Requests from Find-My-iPhone are also issued as Push Notifications
|
|
|
|
04 Push Notification Confirmation
|
|
-----------------------------------------
|
|
* Device -> Server
|
|
* Structure
|
|
* `04` Command
|
|
* 3 unkown Bytes (were always `00 00 00`)
|
|
* confirms push notifications
|
|
|
|
05 Keep-Alive?
|
|
----------------------
|
|
* Device -> Server
|
|
* Structure
|
|
* `05` Command
|
|
* 4-Byte length(e.g. `1B`/27)
|
|
* Data, e.g.: `WiFi 4.3.3 8J2 iPhone2,1 17`
|
|
* what does 17 mean?
|
|
* seems to be keep-alive message, sent periodically
|
|
* confirmed by server with `06` Command (1-Byte)
|
|
|
|
06 Keep-Alive Answer
|
|
----------------------------
|
|
* Server -> Device
|
|
* Structure
|
|
* `06` Command
|
|
* confirms command `05`
|
|
|
|
# Apple Push Service Protocol
|
|
iOS5/OS X 10.7
|
|
|
|
Starting from **iOS5**, iOS devices use a **new push protocol**. Besides iOS devices, **Mac** computers also use this push protocol to connect to Apple, e.g. for automated iTunes downloads or iCloud changes.
|
|
|
|
While the old iOS 4.x protocol uses message types [`00` to `06`](http://ios-rev.tumblr.com/post/7727869991/apple-push-service-protocol), the new protocol uses message types `07` to `0d`. The new protocol is more structured, i.e. all commands and fields have the following **type-length-value** encoding.
|
|
|
|
## Message Structure
|
|
* 1-byte **message type**
|
|
* 4-byte **payload length**
|
|
* **fields** (any number of occurences, also multiple per type)
|
|
* 1-byte **field type** - meaning depends on message type
|
|
* 2-byte **length of field value**
|
|
* **field value**
|
|
|
|
### Example Message
|
|
Example *Connect* message
|
|
|
|
* `07` message type
|
|
* `00 00 00 27` payload length: 39 byte
|
|
* `01` field
|
|
* `00 20` value length: 32 byte
|
|
* `8b 88 72 36 9f 73 48 77 98 2d 39 f3 e2 5a 58 f5 45 9f de ba 8f 91 40 7e 87 0c 65 46 fe 20 f1 b1` value
|
|
* `02` field
|
|
`00 01` value length: 1 byte
|
|
`01` value</pre>
|
|
|
|
## Messages
|
|
### 07 Connect
|
|
* Device -> Server
|
|
* `07` message type
|
|
* fields
|
|
* `01`: push token(optional)
|
|
* 32-byte push token
|
|
* at least once the server closed the connection upon receiving a message containing a push token and sending a 08 response
|
|
* `02`: unknown
|
|
* value: `01`
|
|
* begin of conversation
|
|
|
|
### 08 Connect Response
|
|
* Server -> Device
|
|
* `08` message type
|
|
* fields
|
|
* `01`: status
|
|
* observed values:
|
|
* `00`: ok
|
|
* `02`: some error, happened when device sent push token in *Connect* message, server closed connection after this message with this status
|
|
* `04`: unknown
|
|
* value: `10 00`
|
|
* `05`: unknown
|
|
* data: `00 02`
|
|
* `03`: push token (optional)
|
|
* 32-byte push token
|
|
* actually sent after `05` field
|
|
* answer to *Connect* message, first data from server
|
|
|
|
### 09 Push Topics
|
|
* Device -> Server
|
|
* `09` message type
|
|
* fields
|
|
* `02`: enabled topic(repeated)
|
|
* 20-byte id
|
|
* e.g. topic for an push-enabled app or a specific iCloud service like Find My iPhone
|
|
* `03`: disabled topic(repeated)
|
|
* 20-byte id like field `02`
|
|
* not sure what disabled means and why disabled topics are sent to the server anyway
|
|
* sent to server several times in one session, first time directly after *Connect Response*
|
|
|
|
### 0a Push Notification
|
|
* Server -> Device
|
|
* iMessage: also Device -> Server
|
|
* possibly also for other services, e.g. Find My Friends
|
|
* `0a` message type
|
|
* fields
|
|
* `01`: recipient push token
|
|
* push token like in *Connect/Connect Response* in case of app push notifications
|
|
* iMessage topic in case of iMessage originating from Device
|
|
* `02`: topic
|
|
* see *Push Topics* message
|
|
* `03`: payload
|
|
* notification payload, e.g.
|
|
* app notification: JSON, see Apple Developer Docs
|
|
* iMessage: binary plist
|
|
* `04`: response token
|
|
* probably some unique id per notification to confirm delivery
|
|
* returned in *Push Notification Response* message
|
|
* `05`: expires
|
|
* 32-bit UNIX timestamp
|
|
* probably expiration time
|
|
* `06` timestamp
|
|
* 64-bit UNIX timestamp in nanoseconds
|
|
* time when the notification was sent
|
|
* divide by 10^9 to get standard UNIX timestamp in seconds (might result in decimal places)
|
|
* `07`: unknown
|
|
* observed value: `00`
|
|
|
|
### 0b Push Notification Response
|
|
* Server -> Device
|
|
* iMessage: also Device -> Server, see *Push Notification*
|
|
* `08` message type
|
|
* fields
|
|
* `04` response token
|
|
* same as in *Push Notification* this message responds to
|
|
* `08` status?
|
|
* observed values
|
|
* `00`: ok
|
|
* `02`: error?
|
|
|
|
### 0c Keep-Alive
|
|
* Device -> Server
|
|
* `0c` message type
|
|
* fields
|
|
* `01`: connection method
|
|
* e.g. "WiFi", "31038" for AT&T
|
|
* WiFi/Numeric GSM Mobile Operator Code/Mobile Networc Code(MNC)
|
|
* `02`: iOS version
|
|
* e.g. "5.0"
|
|
* `03`: iOS build number
|
|
* e.g. "9A5220p"
|
|
* `04`: device model
|
|
* e.g. "iPhone2,1"
|
|
* `05`: unknown
|
|
* e.g. values like `10`, `15` or `20`
|
|
* keep-alive message, sent every 15-20 minutes
|
|
|
|
### 0d Command - keep-alive confirmation
|
|
* Server -> Device
|
|
* `0d` message type
|
|
* fields
|
|
* none observed
|
|
* answer to *Keep-Alive* message, confirms keep-alive
|
|
|
|
### 0e Command - NoStorage
|
|
|
|
* Server -> Device
|
|
* `0e` message type
|
|
* fields
|
|
* destination: push token as in Connect and Connect Response messages
|
|
* Note: Appeared in 10.8, unknown purpose
|
|
|
|
### 0f Command - Flush
|
|
|
|
* Server -> Device and Device -> Server
|
|
* `0f` message type
|
|
* fields
|
|
* flushWantPadding: 2-byte integer indicating length of padding or length
|
|
padding requested for response
|
|
* padding: NULL-bytes, typical lengths: 64, 128, 256, 512 bytes
|
|
* first observed with iOS 6.1, device response only seen via cellular
|
|
(iOS 6.0 not tested)
|
|
* often 3 or 4 consecutive messages from the server, each with increasing
|
|
padding length
|
|
|
|
## Note
|
|
|
|
Mac OS X' push notification is slightly more complicated since it supports multiple users. For example the device sends multiple *Connect* messages, one for the system and one for each user, each with a different push token. This needs to be analyzed further.
|
|
|
|
|
|
# Manual apsd patch for iOS devices
|
|
|
|
## Once: get apsd entitlements
|
|
|
|
* on iOS device
|
|
<code>
|
|
cd /System/Library/PrivateFrameworks/ApplePushService.framework
|
|
ldid -e apsd
|
|
</code>
|
|
* copy the resulting entitlements to a local file, e.g. entitlements.xml
|
|
|
|
## For every patch
|
|
|
|
|
|
* copy apsd from iOS device:
|
|
<code>scp root@iphonelocal:/System/Library/PrivateFrameworks/ApplePushService.framework/apsd .</code>
|
|
* edit using hex editor, search for `push.apple.com`. Replace using own domain with length of 14 characters.
|
|
* automated: `perl -pi -e 's/push.apple.com/xxx.xxxxxx.xxx/g' apsd-patched`
|
|
* create code signature and entitlements: `codesign -f -s "iPhone Developer" --entitlements entitlements.xml apsd-patched`
|
|
* `iPhone Developer` must be valid certificate in keychain
|
|
* see [saurik's page](http://www.saurik.com/id/8) for more information
|
|
* on iOS device: remove `apsd` binary
|
|
* This is necessary to invalidate some kernel signature cache. Overwriting the file does not do this.
|
|
* see [fG!'s blog](http://reverse.put.as/2011/01/28/need-help-with-code-signing-in-ios/), especially jan0's comment
|
|
* copy `apsd-patched` to /System/Library/PrivateFrameworks/ApplePushService.framework/apsd
|
|
* `scp apsd-patched [email protected]:/System/Library/PrivateFrameworks/ApplePushService.framework/apsd`
|
|
* on iOS device: `chmod 755 apsd`
|
|
* on iOS device: `killall apsd`
|
|
|
|
Push Topics
|
|
===========
|
|
|
|
See [topics.py](../src/icl0ud/push/topics.py) for a list of Apple push topics.
|
|
# iOS 5 and OS X 10.6 and 10.7 Setup
|
|
|
|
iOS 5 and OS X before 10.8 didn't use the configuration from init-p01st.apple.com to determine which push server to connect to.
|
|
|
|
### Create CA and issue certificates
|
|
|
|
Note: This certificate creation only works for WiFi connections, see below if you want the proxy to work via 3G.
|
|
|
|
You need to create a SSL server certificate and install it on your device. It's common name should be:
|
|
|
|
courier.push.apple.com
|
|
|
|
Then place the certificate in PEM encoding at the following path:
|
|
|
|
certs/courier.push.apple.com/server.pem
|
|
|
|
### DNS redirect(WiFi only)
|
|
|
|
The simplest way for redirecting a jailbroken iOS device or a Mac is modifying the `/etc/hosts` file. The following command will generate a hosts file for you. It may generate a few entries too much, but that shouldn't hurt.
|
|
|
|
python setup/generate-hosts-file-ios5.py <server ip> > hosts
|
|
|
|
You obviously need to copy the generated hosts file to your device.
|
|
|
|
Make sure your device doesn't have network access via a phone network. In this case iOS ignores the `/etc/hosts` file and uses your carrier's DNS instead. **Disabling mobile data** should do the trick.
|
|
|
|
### Redirect via push daemon patch(WiFi+3G)
|
|
|
|
This method modifies the push daemons(apsd on iOS, applepushserviced on OS X) and replaces the string `push.apple.com` with a 14-character domain name of your choice.
|
|
|
|
#### Preparation: DNS Setup
|
|
|
|
You need two DNS entries, one wildcard A-record and a TXT record.
|
|
|
|
First, you have to choose a domain name. It must be exactly **14 characters** long like `push.apple.com`, so e.g. `ps.example.com` would work. (You could probably also use a shorter name and fill the remaining space with zero-bytes, but I haven't tried that).
|
|
|
|
The first DNS entry should be a **wildcard A-record** pointing to your servers IP, like `*.ps.example.com`.
|
|
|
|
An additional TXT record is used probably for determining the number of push domains the devices choose from. I set it to the same value `50` push.apple.com uses, but another one might also work. The content of this **TXT record** should look like ``"count=50"``.
|
|
|
|
You can verify your DNS setup using `dig`, it should show a similar answer for your server like it does for Apple's:
|
|
|
|
dig -t TXT push.apple.com
|
|
|
|
#### iOS apsd patch
|
|
|
|
This step assumes you have a codesign certificate in your keychain named `iPhone Developer`, if you prefer another name you can change `patch-apsd.sh`. You also need `ldid` on your iOS device, I'm not sure whether it comes with Cydia by default.
|
|
|
|
cd pushproxy
|
|
setup/ios/patch-apsd.sh <device hostname> <14-char DNS entry>
|
|
|
|
You can find instructions on how to do this manually in `doc/howto-patch-apsd.md`
|
|
|
|
#### OS X applepushserviced patch
|
|
|
|
Like the iOS patch step, this step assumes there is a codesign certificate in your keychain named `iPhone Developer`.
|
|
|
|
cd pushproxy
|
|
setup/osx/patch-applepushserviced <14-char DNS entry>
|
|
|
|
This modifies `/System/Library/PrivateFrameworks/ApplePushService.framework/applepushserviced` and place a backup in the same directory named `applepushserviced.orig`.
|
|
|
|
After a restart the `applepushserviced` would request a new certificate from Apple since the binary has a new signature, so Keychain doesn't allow it to access the old certificate. So just do the 'Extract OS X Certificates' step which includes a restart anyway.
|
|
# PushProxy
|
|
|
|
PushProxy is a **man-in-the-middle proxy** for **iOS and OS X Push Notifications**. It decodes the push protocol and outputs messages in a readable form. It also provides APIs for handling messages and sending push notifications directly to devices without sending them via Apple's infrastructure.
|
|
|
|
For a reference on the push protocol, see [apple-push-protocol-ios5-lion.md](doc/apple-push-protocol-ios5-lion.md). iOS4 and earlier used another version of the protocol, described in [apple-push-protocol-ios4.md](doc/apple-push-protocol-ios4.md). This proxy only supports the iOS5 protocol.
|
|
|
|
I tested only using jailbroken iOS devices, but it may be possible to use a push certificate from a jailbroken device and use it to connect a non-jailbroken device to Apple's servers. At least some apps using push notifications will be confused if you do this, but I think this was a way for hacktivated iPhones to get a push certificate.
|
|
|
|
## 'Screenshot'
|
|
|
|
2013-02-17 21:54:15+0100 [#0] New connection from 192.168.0.120:61321
|
|
2013-02-17 21:54:15+0100 [#0] SSL handshake done: Device: B481816D-4650-49EA-977C-9FCBDEB30CB1
|
|
2013-02-17 21:54:15+0100 [#0] Connecting to push server: 17.172.232.59:5223
|
|
2013-02-17 21:54:15+0100 Starting factory <icl0ud.push.intercept.InterceptClientFactory instance at 0x147d5a8>
|
|
2013-02-17 21:54:15+0100 [#0] -> APSConnect presenceFlags: 00000002 state: 01
|
|
push token: 4b5543c35b48429bbe770a8af11457f39374bd4e43e94adaa86bd979c50bdb05
|
|
2013-02-17 21:54:16+0100 [#0] <- APSConnectResponse 00 messageSize: 4096 unknown5: 0002
|
|
2013-02-17 21:54:16+0100 [#0] -> APSTopics for token <none>
|
|
enabled topics:
|
|
com.apple.itunesstored
|
|
com.apple.madrid
|
|
com.apple.mobileme.fmip
|
|
com.apple.gamed
|
|
com.apple.ess
|
|
com.me.keyvalueservice
|
|
4357ca2451b7b787caea8a603e51a1f45feaeda4
|
|
com.apple.mobileme.fmf1
|
|
com.apple.mediastream.subscription.push
|
|
disabled topics:
|
|
com.apple.store.Jolly
|
|
2013-02-17 21:54:16+0100 [#0] -> APSKeepAlive 10min carrier: 31038 iPhone4,1/6.1.1/10B145
|
|
2013-02-17 21:54:16+0100 [#0] <- APSKeepAliveResponse
|
|
2013-02-17 22:10:04+0100 [#0] <- APSNotification 4357ca2451b7b787caea8a603e51a1f45feaeda4
|
|
timestamp: 2013-02-17 22:10:16.642561 expiry: 1970-01-01 00:59:59
|
|
messageId: 00000000 storageFlags: 00
|
|
unknown9: None payload decoded (json)
|
|
{u'aps': {u'alert': u'Chrome\u2014meeee/pushproxy \xb7 GitHub\nhttps://github.com/meeee/pushproxy',
|
|
u'badge': 3,
|
|
u'sound': u'elysium.caf'},
|
|
u'urlhint': u'1234567'}
|
|
2013-02-17 22:10:06+0100 [#0] -> APSNotificationResponse message: 00000000 status: 00
|
|
|
|
## Setup
|
|
|
|
### Overview
|
|
|
|
Setup on iOS >= 6.0 and OS X >= 10.8 requires several steps:
|
|
|
|
1. [Install pushproxy including dependencies](#install-pushproxy-and-dependencies)
|
|
2. [Create a CA and issue certificates](#create-ca-and-issue-certificates)
|
|
3. [Install CA on device](#install-ca-on-device)
|
|
4. [Extract and copy device certificate](#extract-and-copy-device-certificate)
|
|
5. [Create configuration bag](#create-configuration-bag)
|
|
6. [Redirect DNS](#redirect-dns)
|
|
|
|
You can find instructions on how to redirect the push connection on iOS < 6.0 and OS X < 10.8 in [setup-ios5-10.7.md](doc/setup-ios5-10.7.md).
|
|
|
|
### Install pushproxy and dependencies
|
|
|
|
The proxy is written in Python, I recommend setting up a virtualenv and installing the requirements:
|
|
|
|
pip install -r src/requirements.txt
|
|
|
|
Setting up PushProxy requires redirecting push connections to your server and setting up X.509 certificates for SSL authentication.
|
|
|
|
The push protocol uses SSL for client and server authentication. You need to extract the device certificate and give it to the proxy so it can authenticate itself as device against Apple. You also need to make the device trust your proxy since you are impersonating Apple's servers.
|
|
|
|
### Create CA and issue certificates
|
|
|
|
Your device has to trust two certificates, so the easiest way is to create a CA and issue the certificates. This way you only need to install one CA certificate on the device.
|
|
|
|
The first hostname you need to create a SSL server certificate for:
|
|
|
|
init-p01st.push.apple.com
|
|
|
|
You will need this certificate later to sign a configuration bag.
|
|
|
|
#### iOS <= 6, OS X <= 10.8
|
|
|
|
You can choose the hostname of the second certificate, the push hostname. When connecting to the push hostname (Apple's default is courier.push.apple.com), apsd prepends a random number to the hostname, perhaps for load balancing and/or fault tolerance (e.g. 22-courier.push.apple.com). You need to configure a DNS server which responds to these hostnames. You can use a wildcard subdomain to redirect all host names with different numbers to the proxy, like `*.push.example.com`. So in this case, you would create a certificate for:
|
|
|
|
courier.push.example.com
|
|
|
|
Store the generated certificate and private key in PEM encoding at the following path:
|
|
|
|
certs/courier.push.apple.com/server.pem
|
|
|
|
|
|
#### iOS 7, OS X 10.9
|
|
|
|
Beginning with OS X 10.9 and probably iOS 7, `apsd` does certificate pinning and checks the root certificate as well as chain length and some attributes of the leaf certificate. The root certificate contained in the `apsd` binary is replaced in a later step (although the patch only works for 10.9 yet and not for iOS7).
|
|
|
|
The certificate chain needs a length of 3, so you have to use a CA certificate as well as an additional intermediary CA certificate that signs the leaf.
|
|
|
|
Create the leaf certificate with the following attributes:
|
|
|
|
* Common Name: courier.push.apple.com
|
|
* Country Name: US
|
|
* State/Province Name: California
|
|
* Locality Name: Cupertino
|
|
* Organization Name: Apple Inc.
|
|
|
|
Store the generated certificate, the intermediary CA certificate and the private key in PEM encoding at the following path:
|
|
|
|
certs/courier.push.apple.com/server.pem
|
|
|
|
|
|
### Install CA on device
|
|
|
|
You can install the CA certificate on iOS devices via Safari or iPhone Configuration Utility.
|
|
|
|
On OS X you can use keychain access to install the certificate, make sure to install it in the System keychain, not your login keychain. Mark it as trusted, Keychain Access should then display it as 'marked as trusted for all users'.
|
|
|
|
### Patch apsd (OS X 10.9 only, iOS 7 not implemented yet)
|
|
|
|
This patch replaces the pinned root certificate in the `apsd` binary with a chosen one having the same or a shorter length than the original certificate.
|
|
|
|
You can run the script with the following command. When running the script, think about making a backup and make sure to restore permissions afterwards.
|
|
|
|
setup/osx/patch_apsd.py <path to apsd> <new root CA cert> <code signing identity>
|
|
|
|
<path to apsd>: Path to the apsd binary. Usually stored in `/System/Library/PrivateFrameworks/ApplePushService.framework/apsd`. You might need to copy it/change permissions to patch as a user.
|
|
<new root ca cert>: Path to a root certificate in DER form to replace the original root certificate by Entrust. This certificate must be no longer than 1120 bytes (length of the original certificate, file size, not key length). Shorter is ok, the rest will be zero-padded and the certificate size will be adjusted in the code.
|
|
<code signing identity>: Name of a code signing certificate understood by the `codesign` utility, make sure your machine trusts this cert (root)
|
|
|
|
Make sure to do this before extracting the device certificate. Once you replaced the `apsd` binary, the keychain will not allow the apsd daemon to use the existing keychain any more and returns the following or a similar error:
|
|
|
|
The operation couldn’t be completed. (OSStatus error -25293.)
|
|
|
|
When you restart/run `apsd` afterwards (`kill` or `launchctl`), after a few failing attempts to access the keychain, apsd will request a new push certificate, which you can then extract as describe in the following section.
|
|
|
|
### Extract and copy device certificate
|
|
#### Extract iOS Certificates
|
|
|
|
First, download the [nimble](http://xs1.iphwn.org/releases/PushFix.zip) tool, extract `PushFix.zip` and place `nimble` into `setup/ios`. The following script will copy the tool to your iOS device, run it and copy the extracted certificates back to your computer. It assumes you have **SSH** running on your device. I recommend setting up key-based authentication, otherwise you will be typing your password a few times.
|
|
|
|
Make sure you are in the pushproxy root directory, otherwise the script will fail.
|
|
|
|
cd pushproxy
|
|
setup/ios/extract-and-convert-certs.sh root@<device hostname>
|
|
|
|
You can find the extracted certificates in `certs/device`. Both public and private key are in one PEM-file.
|
|
|
|
#### Extract OS X Certificates
|
|
|
|
Note: If you want to connect at least one device via a patched push daemon, you need to patch the push daemon on OS X first.
|
|
|
|
OS X stores the certificates in a keychain in `/Library/Keychains`, either in `applepushserviced.keychain` or in `apsd.keychain`.
|
|
|
|
This step extracts the push private key and certificate from the keychain. It stores them in `certs/device/<UUID>.pem`
|
|
|
|
setup/osx/extract_certificate.py -f
|
|
|
|
You can remove the -f parameter to get key and certificate on stdout instead of writing them to a file.
|
|
|
|
### Create Configuration Bag
|
|
|
|
Since iOS 6 and OS X 10.8, apsd loads a signed configuration bag. This bag contains the push domain to connect to among a number of less interesting parameters.
|
|
|
|
Apple's original bag can be found here: [http://init-p01st.push.apple.com/bag](http://init-p01st.push.apple.com/bag) (Download)
|
|
|
|
Run the following command to create a bag for your own domain:
|
|
|
|
setup/bag.py <push domain> <signing certificate> > bag
|
|
|
|
*push domain*: The domain apsd should connect to, e.g. `courier.push.example.com`. apsd then actually connects to this domain prepended with a random number between 1 and 50, e.g. 22-courier.push.example.com.
|
|
|
|
*signing certificate*: the previously created server certificate for `init-p01st.push.apple.com`. The script signs the bag using this certificate and includes it, so apsd can verify the signature.
|
|
|
|
You can either upload this bag to your own webserver or run the setup/bag.py command with a `-s` switch at the end. It starts a webserver on port 80, so you have to run the command as root. The webserver requires [flask](http://flask.pocoo.org/docs/), which can be installed via `pip install flask`.
|
|
|
|
### Redirect DNS
|
|
|
|
You can choose whatever method you want to redirect DNS, pushproxy includes a script to generate an `/etc/hosts` file. You can run it using the following command:
|
|
|
|
setup/generate-hosts-file.py <webserver ip> > hosts
|
|
|
|
*webserver ip*: IP of your webserver that serves the configuration bag. apsd uses `init-p01st.push.apple.com` as HTTP host, so you can use a vhost for that domain if you want.
|
|
|
|
When apsd fails to load the configuration bag, it uses the old iOS 5/OS X 10.7 method as fallback. Thus the generate-hosts-file command redirects all these hosts to 127.0.0.1 to ensure apsd only connects to pushproxy.
|
|
|
|
You obviously need to copy the generated hosts file to the device.
|
|
|
|
## Running
|
|
|
|
cd src
|
|
./runpush.sh
|
|
|
|
This should be enough for most cases, if you want it to run as daemon and write output to a logfile in `data/`, create an empty file in src:
|
|
|
|
touch production
|
|
|
|
If you want to change some configuration, just edit `pushserver.py.
|
|
|
|
## API
|
|
|
|
### Send notifications
|
|
|
|
PushProxy offers a [Twisted Perspective Broker](http://twistedmatrix.com/documents/current/core/howto/pb-usage.html) API for sending push notification directly to connected devices. It has the following signature:
|
|
|
|
remote_sendNotification(self, pushToken, topic, payload)
|
|
|
|
It doesn't implement the store part of the store-and-forward architecture Apple's push notifiction system implements, so notifications sent via this API for will be lost for offline devices.
|
|
|
|
See `src/icl0ud/push/notification_sender.py` for the implementation.
|
|
|
|
### Message handler
|
|
|
|
You can subclass `icl0ud.push.dispatch.BaseHandler`, look at `dispatch.py`, `pushtoken_handler.py` and `notification_sender.py` in `src/icl0ud/push`.
|
|
|
|
Handlers can be configured in `src/pushserver.py`
|
|
|
|
## Debugging
|
|
|
|
Apple provides a [document](http://developer.apple.com/library/ios/#technotes/tn2265/_index.html) on debugging push connections. Especially useful are their instructions on how to enable debug logging on iOS and OS X. You can find the download link to the configuration file for iOS in the upper right corner of the page.
|
|
|
|
The document is a bit outdated. If you want to enable debug logging on newer OS X versions like 10.8.2, you have to replace `applepushserviced` with `apsd` in the `defaults` and `killall` commands.
|
|
|
|
## Contributors
|
|
|
|
* Michael Frister
|
|
* Martin Kreichgauer
|
|
|
|
## Thanks
|
|
|
|
* [Matt Johnston](http://www.ucc.asn.au/~matt/apple/) for writing [extractkeychain](http://www.ucc.asn.au/~matt/src/extractkeychain-0.1.tar.gz) that is used for deriving the master key and decrypting keys from the OS X keychain
|
|
* Vladimir "Farcaller" Pouzanov for writing [python-bplist](https://github.com/farcaller/bplist-python) which is included and helps extracting the push private key from the OS X keychain
|
|
|
|
![pi](https://planb.frister.net/pi/piwik.php?idsite=3&rec=1)
|
|
|
|
This is a little script which will dump the secrets out of an Apple
|
|
keychain if you know the password. Look at extractkeychain.py for details.
|
|
You need to be running on OS X, since it uses Apple's 'security' command-line
|
|
util.
|
|
|
|
It uses the nifty pyDes code from Todd Whiteman
|
|
(http://home.pacific.net.au/~twhitema/des.html) for DES - if that
|
|
seems to slow, you can easily switch over to pycrypto (two lines to change
|
|
in extractkeychain.py).
|
|
|
|
Matt Johnston
|
|
matt at ucc asn au
|
|
|