An XEP-0357: Push Notifications app server that relays push messages between the user’s server and push services like Google Firebase Cloud Messaging (FCM) or Apple Push Notification Service (APNS).
Note: p2 is open source and closed contribution. It’s purpose is to document the privacy implications of using push services. Enthusiast users who are building Conversations from source are recommended to build Conversations without push capabilities. The app will work fine without push (The State of Mobile XMPP in 2016).
Commercial support for running p2 is available from the developer Daniel Gultsch.
Due to restrictions in Firebase Cloud Messaging (and most other push services), only the developer can create push notifications for their apps. For this reason a user’s server wouldn’t be able to wake up a user’s device directly but has to proxy that wake up signal through the infrastructure of the app developer.
Here is a quick description of how this relationship is set up.
All examples below use real world values from my personal device with a test account. The fact that I’m comfortable making that publicly available can act as an indication on how non privacy sensitive that data is.
-
FCM Token: The FCM token is essentially the address of the device that will be used to address push notifications to. That address can change over time. The address is generated by the Google Play Service on the user’s phone. Conversations has no influence over how and when this is (re)generated and can only request it.
-
Secure Device Id: Since the FCM token can change over time the app server needs a second unique ID per device so when it receives a new FCM token it knows whether this one replaces an existing device or is for an entirely new device. Conversations uses the Android ID which is a unique string that is generated by the Android operating system and persists over the life time of the app. In older versions of Android this used to be shared across all apps. Nowadays every app gets its own ID generated by the OS.
-
The app server then generates a random string (Node ID in the language of the XEP) and stores a combination of the FCM Token, the node id, the domain of the account (not the entire account) and a hashed string of the account jid + the secure device id. Since the account jid is essentially salted with the secure device id the app server operator won’t be able to reverse the account jids by looking at the database. The node id is sent back to the client.
<iq from="xiaomia1@jabber.de/Conversations.sAdA" id="cXo5bNCF6wgD" to="p2.siacs.eu" type="set">
<command xmlns="http://jabber.org/protocol/commands" action="execute" node="register-push-fcm">
<x xmlns="jabber:x:data" type="submit">
<field var="token">
<value>eeDXSJjASJY:APA91bEKxhXK54-vHhY9O55JmU2R0nDJL2rRENm-W9uPY6x3jHi0i0OyvPu6js9jVPZqDeX9ZQydZCBZE19o7a0kK4_n88fCgufXjaOlvalh9VibB2zOI7dQRTaDNB3H5s4dicpWD0m4</value>
</field>
<field var="android-id">
<value>92afd7a91cdba9a0</value>
</field>
</x>
</command>
</iq>
<iq from="p2.siacs.eu" id="cXo5bNCF6wgD" to="xiaomia1@jabber.de/Conversations.sAdA" type="result">
<command xmlns="http://jabber.org/protocol/commands" action="complete" node="register-push-fcm" sessionid="1526463999190">
<x xmlns="jabber:x:data" type="form">
<field type="jid-single" var="jid">
<value>p2.siacs.eu</value>
</field>
<field type="text-single" var="node">
<value>eKxTS5n3bOe0</value>
</field>
<field type="text-single" var="secret">
<value>KK4+H5WMfpRG/2zCbuKq6wpX</value>
</field>
</x>
</command>
</iq>
After registering with the App server, Conversations sends the node ID and the jid of the app server (p2.siacs.eu) to the user's server.
<iq type='set' id='x42'>
<enable xmlns='urn:xmpp:push:0' jid='p2.siacs.eu' node='eKxTS5n3bOe0'>
<x xmlns='jabber:x:data' type='submit'>
<field var='FORM_TYPE'><value>http://jabber.org/protocol/pubsub#publish-options</value></field>
<field var='secret'><value>KK4+H5WMfpRG/2zCbuKq6wpX</value></field>
</x>
</enable>
</iq>
device | domain | token | node | secret
ec164939a8485ee6b7f7871071a11c7bb18aead5 | jabber.de | eeDXSJjASJY:APA91bEKxhXK54-vHhY9O55JmU2R0nDJL2rRENm-W9uPY6x3jHi0i0OyvPu6js9jVPZqDeX9ZQydZCBZE19o7a0kK4_n88fCgufXjaOlvalh9VibB2zOI7dQRTaDNB3H5s4dicpWD0m4 | eKxTS5n3bOe0 | KK4+H5WMfpRG/2zCbuKq6wpX
When a push is required (this is determined with internal logic of the user’s server that is out of scope of this document) the user’s server will send the node id to the app server. The user’s server can also add additonal information like number of messages pending, the sender jid of the last message and even the body of the last message. Whether or not this information is included is up to the implementation and the configuration of the user’s server and is out of scope for this document as well. Whether or not the app server receives that additional information it will just ignore it and not process it. The sender jid for the push is the the jid of the server. Since the app server didn’t store the account jid and since the account jid is not included in the push it won’t know for which account the push is actually for. It will just be able to look up the corresponding FCM token based on the node id.
An example of that communication can be found in XEP-0357 Section 7.
Upon receiving a push request from a XMPP server, the app server looks up the hashed account jid + secure device id combination and the FCM token. It then sends the hash via Google to the user's device, using the token (remember: 'token' means 'device address' in FCM language).
The hash can then be used by Conversations to determine which of a number of accounts should be woken up. The hash is not reversible but since Conversations has a limited number of accounts and also knows the secure device id it can just calculate all hashes. Google only sees that hash and nothing else.
{"to":"fcm-token","data":{"account":"ec164939a8485ee6b7f7871071a11c7bb18aead5"}}
An alternative design omits registration with the app server and instead stores the token on the user’s server. For each push the XMPP server includes all information the app server needs to execute the push. The app server becomes a true proxy that doesn’t require its own database.