Web Push: Data Encryption Test Page

Introduction

The Push APIs (also known as Web Push) provide the ability for Application Servers (App Servers) to send up to 4 Kilobites of data to applications (apps). In order to do this securely, that data would first need to be encrypted so that it wouldn't be exposed to the push servers in transit. The browser decrypts the data later before delivering it to your app.

Unfortunately, implementing this kind of security is complicated and encryption may be confusing to those not familiar with it.

Purpose

This page attempts to transparently show the keys and values used to encrypt data for delivery by a push service. It also provides a "working example" of how developers might successfully implement encryption of push notifications for their application or library. Following the steps below, you can see all the "moving parts" in one place and get started adding encryption to your own App Server or library.

Pre-requisites

This page will presume you're familiar with using push notifications and want to start using the experimental encrypted data payloads. NOTE: This feature is currently experimental, there is very limited library support!

Before getting started, I strongly recommend you first consult: Using the Push APIon MDN (the Mozilla Developer Network). This will help explain how push basic notifications function without data payloads.

Getting Started

Emulating an App Server, this page allows you to do the following:

  1. Register a Service Worker
  2. Create a Web Push Subscription
  3. Generate some dummy content
  4. Generate a local encryption key
  5. Encrypt some dummy content
  6. Add optional VAPID data
  7. Send the message

NOTE: It may be useful to open the Web Debugging Console (Ctrl+Shift+K on Firefox) and note the messages being generated in the console log.

1. Create a Web PushSubscription:

These elements are provided to your web app from the PushManager. call. These values should be relayed directly to your back-end App Server via your app. Never send this information through the Push Servers!

endpoint
The subscription.endpoint is the URI where you should POST the encrypted message content. To make things easier, the page will fetch one for itself. Feel free to replace this with a different endpoint and key if you wish, and press the "Send Data" button below to try it out.
p256dh Key
The "p256dh" key is the ECDH curve p256 Diffie Hellman key returned as part of the subscription. You use subscription.getKey('p256dh') to retrieve this value
Authentication Secret
The auth secret is provided by the browser as subscription.getKey('auth') and is extra data for the HKDF function, which generates the Shared Key

2. Generate the Content

These are normally the things you enter for each encrypted push message.

Salt:
The salt is an array of 16 random bytes that the App Server generates for each outbound encrypted request. This should change for every request, but it can be helpful for library authors to see the same salt value.
Data:
This is your data. For this demonstration, we're going to presume that it's a string, but the data can be any format. Just be sure that the service worker 'push' event handler knows how to read it.
Ok, we're ready to

3. Generate the Local Encryption Key:

These items (plus the Salt) are unique per encryption, and usually generated "on the fly" by the various algorithms. Key negotiation requires the generation of a Local Key. The Local Key is an arbitrary key pair made for this specific exchange and used to derive bits from the p256dh key.

Local Key (private):
The private portion of the Local Key. Unfortunately, it's not easy to read this from a provided value.
Local Key (public):
This key is temporary, but the public key needs to be sent as the Encryption Key
Local Key (public, raw):
This is the same public local key, only formatted in "raw" form. This is the byte array that is used for deriving the nonce

4. Encrypt the Message:

These items are the various intermediary and final elements used to generate the encryption key.

Shared Key:
AES-GCM key material derived from the private local key, the Authentication Secret, and the public p256dh key. This comes from a WebCrypto.deriveBits call in webpush.js
Encryption Key
HKDF generated bits derived from the shared key material, which is a static string run through the KDF which has been seeded from the salt and the senderKey.
Derived Nonce:
The "nonce" here is derived from a static string, run through the KDF which has been seeded from the salt and the senderKey.
Initialization Vector:
The AES-GCM Initialization Vector. This is a bit more complex, as it comprises a known chunk created through the same KDF that created the nonce, as well the index number of the chunk of data we're encoding.

5. Add VAPID data

VAPID is an optional header block that allows senders to "self identify". It consists of a JSON Web Token that contains identification claims.

VAPID claims






Optionally
VAPID Headers
Add these headers to your outbound request. The headers are "Authorization" and "Crypto-Key". "Authorization" is a JWT containing the signed claims. "Crypto-Key" contains a "p256ecdsa" element that specifies the "raw" public key used to verify the JWT signature. Note that "Crypto-Key" may contain multiple elements separated by a comma. If you specify VAPID headers, we'll append them to the Crypto-Key header.
VAPID private key
Normally, you retain your VAPID key pair on your server, and reuse the pair for all subsequent calls. You can create a new VAPID key pair at any time, but things like the dashboard won't associate information for the old key pair with a new one. In essence, the new key is a new ID for you.
VAPID public key
VAPID always uses its own set of keys. You sign with the private half and include the public key with the Crypto-Key header. This key is in the "uncompressed" or "raw" key point format.
Push Dashboard

To monitor your push messages, you will add the VAPID public key to your app in the push dashboard. To validate you own the app, you will also need to sign a token with your VAPID private key, and paste the signed token back into the dashboard.

If you are using this page to test, you can paste this test VAPID public key into the dashboard:

Then paste the app token here to get the signed token value:

6. Send the Message:

These are the outputs. You can cut and paste the Curl content and watch the Web Console to see the message be received and handled by the Service Worker.

Curl:
This is a proxy for your library. Please note that the input for this is a binary blob. In this case, I'm attempting to write the data out to a file, then read it in with curl. Transcoding the data to base64 or some other encoding mechanism will not work.

If you don't have curl, or don't feel like cutting and pasting, you can and this page will do it for you.

Salt:
The salt value sent in the "Encryption" header.
dh:
Diffie-Hellman key sent in the "Encryption-Key" header.
data:
This is the raw, encrypted data represented as hexidecimal byte pairs. If you wish, you can compare these with whatever byte editor you have (e.g. xxd on Linux systems) to verify that the data is correct.