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. iOS4 and earlier used another version of the protocol, described in 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.
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 on iOS >= 6.0 and OS X >= 10.8 requires several steps:
- Install pushproxy including dependencies
- Create a CA and issue certificates
- Install CA on device
- Extract and copy device certificate
- Create configuration bag
- 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.
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.
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.
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 this certificate in PEM encoding at the following path:
certs/courier.push.apple.com/server.pem
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'.
First, download the nimble 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.
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.
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 (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, which can be installed via pip install flask
.
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.
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.
PushProxy offers a Twisted Perspective Broker 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.
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
Apple provides a document 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.
- Michael Frister
- Martin Kreichgauer
- Matt Johnston for writing extractkeychain that is used for deriving the master key and decrypting keys from the OS X keychain
- Vladimir "Farcaller" Pouzanov for writing python-bplist which is included and helps extracting the push private key from the OS X keychain