Closed karmanyaahm closed 10 months ago
Links, might be useful to others or myself in the future:
https://github.com/mozilla/rust-ece/ What is used for webpush crypto in firefox. https://github.com/web-push-libs/encrypted-content-encoding python library https://github.com/web-push-libs/ecec C library
https://github.com/chromium/chromium/blob/d7da0240cae77824d1eda25745c4022757499131/chrome/browser/push_messaging/push_messaging_service_impl.cc#L333 Chromium implementation https://github.com/chromium/chromium/tree/d7da0240cae77824d1eda25745c4022757499131/third_party/blink/renderer/modules/push_messaging
It looks like we don't need a rewrite-proxy (https://www.rfc-editor.org/rfc/rfc8291.html#section-5)
Also, we wrote the registration for android for fedilab a few months ago, it would not require a long time to use it for a library
What is an example use case of this? What problem does it fix? Does web push on Android normally go through Google FCM?
I guess my answer is this (from Element chat):
(Without unifiedpush) : There are 2 ways to use webpush :
- with a lib and every app using it have a connection wayting for incoming requests
- with a lib that does queries to a single app that listen for incoming requests for all the other apps
Unifiedpush only specify how internal app sends data and how server send to distributor. Actually, it is possible to have a webpush distributor
So UP is that single app that listens for requests
IIRC, browsers use FCM for webpush. Also it is usefull for app implementing webpush (e.g. mastodon)
What is an example use case of this? What problem does it fix? Does web push on Android normally go through Google FCM?
This advantage is not necessarily for web browsers, but rather being able to use UP with application servers that already support webpush, and also benefitting from the encryption at the same time.
There's no need for a rewrite proxy.
Indeed. For webpush, salt, content-size and public key are sended following RFC8188 in the post data. source: RFC8291
+-----------+--------+-----------+---------------+
| salt (16) | rs (4) | idlen (1) | keyid (idlen) |
+-----------+--------+-----------+---------------+
Example:
POST /push/JzLQ3raZJfFBR0aqvOMsLrt54w4rJUsV HTTP/1.1
Host: push.example.net
TTL: 10
Content-Length: 145
Content-Encoding: aes128gcm
DGv6ra1nlYgDCS1FRnbzlwAAEABBBP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27ml
mlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A_yl95bQpu6cVPT
pK4Mqgkf1CXztLVBSt2Ks3oZwbuwXPXLWyouBWLVWGNWQexSgSxsj_Qulcy4a-fN
Salt: DGv6ra1nlYgDCS1FRnbzlw
Application server public key: BP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27mlmlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A8
Without a rewrite proxy, we only miss :
So I checked the following libraries, and they accept all 2XX status codes, don't check response headers, and don't support any advanced features (delivery receipts) of webpush based on my quick survey:
webpush
and Mastodon's own implementationweb-push
on NPMThe only limitation with pywebpush is >202 response code is marked as failing.
So from my rough survey, not adhering to rfc8030 super tightly (with response headers and extra but mandatory features such as receipt notifications) should be fine for most applications. i.e. another need for a reverse proxy - push response - is alleviated
So, in terms of library API, we have two main options (if there are more good options, you can comment them below). This broadly applies to all libraries Android, Flutter, Linux
The old API:
func Register(instance string, distributor string)
func onNewEndpoint(endpoint string)
func Unregister(instance string)
the new API
func Register(instance string, distributor string)
func RegisterWithWebpush(instance string, distributor string)
func onNewEndpoint(endpoint string, p256dh string, auth string) // if using webpush, p256dh = auth = "" and can be ignored
func Unregister(instance string)
Here, onMessage
will automatically provide the decrypted payload if registration is done with WebPush
In an onNewEndpoint
call, devs will call
func (webpush) generateKeys(instance string) (p256dh string, auth string) //this instance doesn't have to be the same as the UP library, but probably will be for simplicity
// it should save the private key automatically
Then in onMessage
, devs will call
func (webpush) decrypt(instance string, ciphertext []byte) (plaintext []byte)
//uses the stored keys for that instance and decrypts the text
I think integrated will be simpler for devs (fewer imports, automatic decryption), but might result in more code to maintain for the core UP libraries - vice-versa for a separate library
Also, if we want to encourage WebPush/encryption adoption for all UP apps, I think integrated is the way to go (since then there's one less step to take for encryption by app devs)
Comments are welcome from everyone :), which do you prefer?
The old API should be :
func Register(instance string, distributor string)
func onNewEndpoint(instance string, endpoint string)
func Unregister(instance string)
On my side, I see a 3rd option : a "unifiedpush_webpush" library that depends on the unifiedpush library (connector) :
func Register(instance string, distributor string)
func onNewEndpoint(instance string, endpoint string, p256dh string, auth string) // if using webpush, p256dh = auth = "" and can be ignored
func Unregister(instance string)
Just doing some reading.
This was the change in (the future) RFC8188 that resulted in moving keys from headers to the body: https://github.com/httpwg/http-extensions/pull/252
This is where it started affecting WebPush: https://mailarchive.ietf.org/arch/msg/webpush/aLfZBx6wZRKMZ7X2AG_t1X2TlIE/ https://github.com/webpush-wg/webpush-encryption/pull/9
https://mailarchive.ietf.org/arch/msg/webpush/19pz0qIZBNN0GheAmKu72cYLr9k/
it may be layered over Webcoket, newly proposed WiSH, etc - and in many cases the implementation will be greatly simplified if the binary blob can be sent as-is (after any protocol-specific authentication ).
totally agree with Costin
ok so, gateway idea:
Turn {"unifiedpush":{"version":1}}
into {"unifiedpush":{"version":1,"cryptokey":1}}
if this gateway is supported.
If Content-Encoding == "aesgcm", convert the data in "crypto-key" and "encryption" to the following body header used in aes128gcm WebPush.
+-----------+--------+-----------+---------------+ | salt (16) | rs (4) | idlen (1) | keyid (idlen) | +-----------+--------+-----------+---------------+
If "crypto-key" and "encryption" headers exist, simply append them to the message body. This trades server complexity for client library complexity, which then has to parse and decrypt both formats.
If the "cryptokey" gateway is not supported by the endpoint, fall back to https://cryptokey.gateway.unifiedpush.org/?url=https://oldversion.ntfy.sh/1234
or something. If every push server natively supports this, the backup gateway should be rarely used, avoiding centralization risks. In a few months (to allow for everyone to update), Tusky and Fedilab can avoid wake+poll.
Like Matrix, this is probably simple and non-intrusive enough to be included natively in push servers. I will experiment with this stuff as soon as I have some time.
also, a ton of application servers still use the old protocol (aesgcm) (image from March 2021, but I doubt it's changed that much)
https://github.com/mozilla/rust-ece/issues/53#issuecomment-802371008
- Gateway operation
So, aesgcm
and aes128gcm
use different nonces and context strings and stuff, so it is not possible to transparently convert between the two formats on the server...
I spent way too long figuring this out :frowning_face:.
So to appending headers it is.
UP-Example now supports WebPush: https://github.com/UnifiedPush/android-example/tree/1.5.2
I don't think it is useful to have yet another lib for WebPush over UnifiedPush.
What do you think about leaving flutter-connector-webpush too ? We probably can just write an example.
Yeah, it's probably fine if we make a docs page explaining how to use the Store and WebPush classes. Maybe the actual decryption could also be a function in the library, just to create a strong boundary between the 'scary cryptography' parts and app logic?
Hi @jrconlin 👋
UnifiedPush is an open source protocol for Android (and Linux) push notifications. As we're working on increased WebPush compatibility, I was curious as to what that graph looks like nowadays? Is aesgcm still dominant?
Thanks for all your open source WebPush tooling.
Is aesgcm still dominant?
Sigh. If anything, it's gotten worse.
Apparently, if you provide libraries for an early version of a protocol, expect that version to live forever.
Just to recap, aesgcm
is based on a draft of the webpush encryption RFC, where the Diffie Hellman public key is URL safe, unpadded, base64 encoded into the Crypto-Key: dh=
header parameter, and the Salt is encoded into the Encryption: salt=
header parameter. For that content type, the recipient decryption system expects those values to be included as part of the delivered message.
For aes128gcm
encoding, those values are prefixed to the encrypted body. Granted, there are additional complications if you're dealing with a multi-part push message, but those are fairly rare because they're very complicated to implement. (Basically, at some point, it becomes TCP over WebPush.)
Theoretically, you might be able to convert between aesgcm
and aes128gcm
, since the values produced are the same, but I've never tried doing it, and I long since learned not to invoke the wrath of the cryptography gods.
Apparently, if you provide libraries for an early version of a protocol, expect that version to live forever.
🙁. Yeah, part of the issue we've noticed is on the app side too. For example, Mastodon has various WebPush -> FCM/APNS proxies that use aesgcm
and so is unable to exclusively switch to aes128gcm
. Worse, the ruby webpush
library v1.0 only supports aes128gcm
(not both), so it would be an ambitious endeavor to even get it to support both.
I kinda hoped Apple, having the market power, would only support aes128gcm
when they started supporting WebPush, but that did not happen.
For that content type, the recipient decryption system expects those values to be included as part of the delivered message. For
aes128gcm
encoding, those values are prefixed to the encrypted body.
Yes, handling headers is the main complication in adding aesgcm
to UP. But I guess if it's that popular we have to do it anyway.
Theoretically, you might be able to convert between
aesgcm
andaes128gcm
, since the values produced are the same, but I've never tried doing it, and I long since learned not to invoke the wrath of the cryptography gods.
I tried doing that, but realized that the nonces for key derivation are different on the two standards. So, unfortunately it won't be possible to adapt on the push server (without private keys).
Thanks for the information though!
Ah, crap, you're right. I forgot about the nonces.
The silly thing is that I reached out to a bunch of the library authors a couple of years ago and encouraged them to switch to aes128gcm
, for a LOT of reasons. A number of them did, indeed, make the default aes128gcm
. Sadly, the folk actually implementing things, apparently, either never update their libraries or actively set the encryption to the older standard. It's a point of confusion and frustration for me.
(Extra fun fact: supporting both on the message server is also a serious pain. Technically, there were three standards for a while but we dropped the oldest. I would love to be able to work with the other folk at Google, Apple and Microsoft to declare aesgcm
unsupported as of some future date. I'm going to guess that they'd probably be on board with that idea since it would greatly simplify things on their end, but the problem is that it's surprisingly hard to get in touch with those teams.)
To summarize:
IMO, it is better to stay with the webpush specification, sync on push is sufficient for applications that only support the draft specification. What do you think ?
Concerning Mastodon, I think the best thing is to publish a draft-webpush gem, based on webpush gem so they can support both
FWIW, I'm absolutely fine with only working against the RFC spec. Feel free to note that the RFC has been out for 7 years and that most major WebPush libraries support it. Make using aesgcm
a pain point for folk. (Granted, email with "+" in the local part has been a thing even longer than that and still there are tons of sites that think foo+sample@example.com
is invalid, but that's a different problem.)
Needless to say, I'll be cheering for y'all so that folk finally stop using the old specification.
IMO, it is better to stay with the webpush specification, sync on push is sufficient for applications that only support the draft specification. What do you think?
Considering that currently there is no WebPush support, I strongly agree with progress towards at least standards compatible WebPush support.
The draft version support will hopefully become less relevant over time, and if it doesn't it can be reconsidered to be solved later?
The standard support hopefully becomes more relevant over time.
UnifiedPush is compatible with WebPush. Its support is limited (e.g. urgency is ignored) but since an accepted push message returns a 201 with TTL: 0
, application servers shouldn't try to use WebPush's unimplemented features (for instance, the app server won't try to delete a pending message).
What do you think about closing this issue ? UP supports (in a limited way, with TTL: 0) WebPush. This opened issue seems to add some confusion
If someone feels the need to, maybe we can open an issue to Extend WebPush support to add TTL>0, the ability to delete messages, even urgency etc.
I'm closing it then.
If someone feels the need to, maybe we can open an issue to Extend WebPush support to add TTL>0, the ability to delete messages, even urgency etc.
A compatibility layer. Needs implementations in connectors and a rewrite proxy, afaict. It would be very useful to be able to rely on all the existing crypto for an e2ee UnifiedPush experience.