Adventures in “Decentralized” Push Notifications

Pelle Braendgaard
uPort
Published in
9 min readOct 23, 2017

TLDR: It is impossible to create truly decentralized push notifications on iOS. Our 1st attempt mostly worked using a minimum of trusted third parties operating securely together through the principles of Separation of Control. We had issues due to the strict data privacy settings in our app. Our 2nd attempt solves this using a different, but equally safe approach.

When we set out building the first mobile app to let you manage and control your own identity nearly 2 years ago, I could not have imagined half the problems we have had to solve.

Building a modern mobile app with the features that both users and developers are used to, in a way that still protects the user, is surprisingly hard. Often what makes it more difficult are features specifically created by device manufacturers to protect the privacy of their users.

From the outset our goal was to use only decentralized technology such as Ethereum and IPFS. We specifically never want to be the holders of our users’ private data.

It isn’t always possible in today’s world of Android and iOS to be 100% decentralized. Whenever we have had a problem that needs solving, we have always been very careful to make our solutions as decentralized as possible, to avoid sitting on lots of private data.

It is with this goal of providing truly decentralized services in mind that we wanted to explain how we think about these issues and how we attempt at solving them. This article is going to be fairly long as privacy and self-sovereignty is really important and the solutions are sometimes complicated.

Case in point: Push Notifications on iOS

This seemingly simple feature that forms the basis for most of the apps we use today and take for granted is by nature centralized and protected by a trusted third party called Apple.

The way a typical app like WhatsApp or Facebook works is:

  • Mobile App requests permission from the user to send push notifications
  • A device token is issued by iOS specific to the app and device
  • The device token is stored on a central server run by the app provider
  • When a notification needs to be pushed to the phone the server sends it using the device token to Apple’s servers
  • Apple’s server sends notification to your phone

This works well as you can always turn off push notifications for the app. If an app provider acts in bad faith, Apple can, and does, turn off their push notification rights.

The problem from a data control and privacy part is that you have to trust both the app provider and Apple to not do anything bad with your data.

Flow of a typical centralized platform

Apps like Whatsapp, Signal, and iMessage encrypt the data, but still store data on their server on your behalf.

Why Push Notifications for an Ethereum Identity app?

With Uport we thought push notifications would be a great way to solve the problem of sending a message from a desktop browser app or server side app to the users Uport app for approval and signing.

Our first solution for this was for the user to scan a QR code containing the request to be approved. We still support this and it works well. However our users found it cumbersome to sign multiple QR codes in one session. We wanted something faster and more natural.

Developers have also requested that they be able to send a request for transaction signing after a certain event, or automatically send users a weekly verification of account balance to their users that can be used for credit or other applications.

“Decentralized” Push v1

On iOS there is no way of getting around the trusted third party of Apple. So there is no way we can create a truly decentralized solution at the moment for iOS. Android allows more flexibility, which we are exploring. For Android our architecture is similar but not exactly the same as what I will describe here.

Our first thought was that we can have the apps interact directly with Apple, thus cutting out the need for another middleman. Unfortunately Apple requires requests signed by a private key mapped to a certificate issued by them. If we included this in our apps, anyone jailbreaking their phone would have access to this and could send unlimited push notifications on our behalf.

There are many centralized services that will do this for you, but most of them still require you to have some sort of centralized service communicating with them.

Amazon Web Services on the other hand provides their SNS service, which allows device self-registration. This enables a phone to register its device token with SNS without sharing it directly with us. In return, the phone receives another token that can be used by us to issue push notifications.

They don’t share this token directly with us, but instead, when an app logs a user in, they can ask for Push Notification rights. If the user accepts, this creates a signed token containing this SNS token. Now instead of showing a QR code the app can send transactions, verifications etc directly to the user via a push notification.

We still have a central service that communicates with SNS. But the primary reason is to be able to switch off malicious apps.

So the first version of our “Decentralized” Push Notification worked like this:

Data flow of v1 of Uport Push Notification Architecture

Device registration:

  • Uport app asks user for push notification permissions (only once)
  • A device token is issued by iOS specific to the Uport app and device
  • Device token is self-registered on AWS SNS and an SNS token is issued to user’s Uport app

Note: No Uport controlled servers are part of this phase

App asks permission from the user:

  • Client App requests that the user logs in using Uport
  • User scans a QR code on their site
  • Uport app creates a signed JWT containing the SNS token issued to the Client App developer. We call this the Push Token
  • This PushToken is included in another signed JWT containing the user’s identity and login information and is returned to Client App

Note: No Uport controlled servers are part of this phase either

Send requests to the user:

  • Create signed request using uport library
  • Send request to Uport push notification server authenticating using the PushToken from the permissions step
  • Uport push server uses SNS token embedded in Push Token to send message directly to phone in background mode
  • User views request and accepts, rejects, or ignores it

Privacy risks from the above approach mainly included interception of messages along the way. Messages are never at rest on our push notification server for these styles of messages, so someone would have to intercept our servers and change the code. The messages are handed over, first to Amazon’s SNS servers, and then to Apple’s Push Notification Servers.

Note the above flow did not encrypt message payloads. Of course, all these messages are sent using encrypted TLS connections, but we prefer that the raw messages are encrypted so they can’t be intercepted by our servers either. We deemed this OK at this early stage as we were mainly trying to confirm the usefulness of the flow and we were in non-public test mode.

Encryption is something that needs to be carefully thought through to get right, so we decided to spend some time and implement this later before we went into full production mode with real apps and data.

Intermission: Regular vs Background Push and server side storage of messages

Regular push notifications are fast, but not guaranteed to reach the app. The app receives them if the app is open or if a user specifically opens it from the notification setting.

Most apps such as WhatsApp or Facebook poll a centralized server when the user opens it for the latest messages, so nothing is ever lost.

We thought long and hard about whether we can skip this server part and if the loss of some messages was acceptable. This would be fine in some circumstances, but some of the developers working on the most interesting apps using Uport and Ethereum really wanted to explore this new frontier of being able to interact in real time with their users.

iOS push notifications have a background mode that ensures that the app receives the message. There are certain restrictions for it, but now alerts can be shown to the user, unlike normal push notifications. Whenever a background push notification was received we parsed it, verified the signature, and presented an alert to the user.

This worked great and developers were initially happy with the solution.

Apple’s Data Protection vs background push

Almost immediately after releasing the feature to developers we started receiving complaints about messages intermittently not being received.

We dutifully set up maximum logging on our devices and saw all messages arrive perfectly. Yet oddly when we weren’t logging it we saw the same problem. This seemed to be a classic Heisenbug. The bug that never appears when you are looking for it.

Then we finally discovered that the problem was actually Apple’s own data privacy features that were interfering with our ingenious push notification service.

Apple allows you to provide automatic data protection in your app. This requires the phone to be unlocked and the app open to be able to access any data storage. Most apps do not use this, but since we are always focused on data privacy we picked the strongest setting.

When a background push notification was received by the Uport app, it dutifully launches in the background and attempts to process the message. The only thing is that the app does not have access to the data, so we are unable to process it.

“Decentralized” Push v2

Back at the drawing board we realized the only way to really implement push notifications for iOS and guarantee that messages arrive on the device is for the use case it was actually designed to mitigate: a centralized server component that holds messages.

Nothing changes in the way we ask for permissions from the user and how the app requests permission from the user. The change now happens when sending the message.

Data flow of v2 of Uport Push Notification Architecture

Send requests to the user:

  • Create signed and encrypted request using uport library
  • Send request to Uport push notification server authenticating using the PushToken from the permissions step
  • Uport push server stores the request and creates a message ID
  • Uport push server uses SNS token embedded in Push Token and sends a normal push notification to the user containing the message ID (no more background mode)
  • Phone receives message and only starts up if user opens notification
  • Once user opens app, the specific message and others queued on the push notification server are fetched from server using yet another JWT, authenticating the user and then gets securely decrypted on the user’s device
  • Messages are deleted from push notification server
  • User views request and accepts, rejects, or ignores it

Since this approach required storing messages temporarily on our servers, we knew we had to solve encryption before pushing this version out.

The messages are automatically encrypted by our library and sent to a queue on our servers. This allows for a much more reliable experience, while at the same time protecting your personal data and not requiring the app to be open.

Another notable improvement here is that the message payload is actually no longer sent (even in encrypted form) to Amazon SNS and Apple’s Push Notification Servers.

Data Encryption Side Note

Encryption is incredibly important and forms an important part of communicating securely on the internet. It is also difficult to get right. We went with TweetNACL’s crypto box public key encryption scheme. TweetNACL is a light weight version of NACL the simple cryptographic toolbox designed by Daniel J Bernstein, one of the most trusted names in the cryptography community.

Many projects immediately go to AES — the US government’s favorite solution. But AES, while secure, is notably tricky to get right. We also wanted to eliminate the key-sharing requirement of a secret key solution like plain AES.

To implement public key encryption, we had to create a new key pair for encryption use on the device. This is now stored in your public Uport profile as the key publicEncKey. See this article for more about how the Uport Identity platform works.

On the Client App side the encryption is often performed in a web browser, so rather than build a complex additional key management scheme, we simply create a new throw away encryption Key Pair on the fly in the browser. We can do this since this key is only used to encrypt the specific message sent to the user.

Separation of Control FTW

As I mentioned in the beginning, it is not really possible to create a truly decentralized push notification system for iOS. However, we are able to receive many of the same benefits by separating each element of the push notification process to separate services that are each only able to do one thing.

This principle of Separation of Control is one of the oldest forms of security architecture and has been used in banks, governments and militaries for centuries before the first digital computer was invented. For example it forms the basis of the US Constitution.

We will continue to iterate on making this better. Stay tuned for v3.

--

--

Pelle Braendgaard
uPort
Writer for

Engineering Lead for uPort. Opinionated about ethereum, bitcoin, payments and financial services.