Yeah, the need to provide a private key to an outside service in this scenario would seem to entirely defeat the purpose of using asymmetric crypto. I got suspicious and dug into the specs.
So. Long story short: no, Firebase is not required, you can generate the keys locally and theyâll just work. You need an elliptic-curve key of type prime256v1
, urlsafely-base64-encoded without padding (example code below).
The way this works is: your browser may have a certain push service configured, and when asked via Push API (MDN), it returns, among other things, an endpoint that should be used for sending notifications to that device.
You can see those in the html5_push_registrations.conf
file next to your config once you enable push notifications somewhere. Chrome returns endpoints at fcm.googleapis.com
, Firefox at updates.push.services.mozilla.com
. Such a service should be possible to self-host, but havenât looked into that.
I generated my own keys using this snippet of Ruby, guided by this Google Developers post and the RFC8292 on VAPID:
require "openssl"
require "base64"
key = OpenSSL::PKey::EC.generate("prime256v1")
format = proc { |k| Base64.urlsafe_encode64(k.to_bn.to_s(2), padding: false) }
print "vapid_prv_key: \"", format.(key.private_key), "\"\n"
print "vapid_pub_key: \"", format.(key.public_key), "\"\n"
(You donât have to use Ruby, itâs probably easier in NodeJS using the examples in the Google Devs post, I just wanted a different implementation to validate my understanding; it probably too uses OpenSSL underneath, but to me thatâs good enough)
Just inserted these into Home Assistantâs config as per the documentation, without registering it anywhere else.
And it worked!
âŚmostly.
Oddly enough, it worked only with Chrome â but not Firefox, neither on WIndows or Android. But that may be breakage on my end â the fact that it works with Chrome would confirm that itâs configured correctly.
Bromite (a fork of Chromium-Android with privacy enhancements) doesnât seem to have a push service configured at all, or maybe my device is missing something that it needs.
PS: key generation via Node.js:
function format(k) {
// + and / replacements are all that's needed to produce urlsafe-base64
// out of normal base64. And they're available natively in NodeJS v14!
// I had v12 đ
return k.toString('base64').replace("+", "-").replace("/", "_").replace("=", "");
}
var curve = crypto.createECDH('prime256v1');
curve.generateKeys();
format(curve.getPublicKey());
format(curve.getPrivateKey());