summaryrefslogtreecommitdiff
path: root/src/vapid/ser.rs
diff options
context:
space:
mode:
authorojacobson <ojacobson@noreply.codeberg.org>2025-11-07 23:17:15 +0100
committerojacobson <ojacobson@noreply.codeberg.org>2025-11-07 23:17:15 +0100
commit9e6f19f0f188eaa7f8b6be21c8405786cfb0dddd (patch)
treeb2999341645dec61e8143d7bb1b8a9d0056e0db1 /src/vapid/ser.rs
parent3c588861ef5814de329743147398dbae22c1aeeb (diff)
parent78d901328261d2306cf59c8e83fc217a63aa4a64 (diff)
Set up infrastructure for push message subscriptions.
A subscription allows an application server (here, the Pilcrow server) to send web push messages to a user agent. On the server, Pilcrow records subscriptions verbatim, in the clear. Each subscription has an associated key, which will be used to encrypt messages for the corresponding client, but we store them in the clear, for the same broad reason that we store the VAPID key in the clear. They allow anyone who obtains them to impersonate the server and send push messages to clients, but they're rotated regularly - clients must rotate them whenever the server's VAPID key changes. On the client, we monitor VAPID key change events to drive automatic subscription management, once the user sets up an initial subscription manually (which we must do as it can involve a user-interaction-only prompt for permission to send notifications). This isn't the final UI, but rather a bare-minimum version to let us move on with testing push notifications. Merges push-subscribe into push-notify.
Diffstat (limited to 'src/vapid/ser.rs')
-rw-r--r--src/vapid/ser.rs63
1 files changed, 63 insertions, 0 deletions
diff --git a/src/vapid/ser.rs b/src/vapid/ser.rs
new file mode 100644
index 0000000..02c77e1
--- /dev/null
+++ b/src/vapid/ser.rs
@@ -0,0 +1,63 @@
+pub mod key {
+ use std::fmt;
+
+ use base64::{Engine as _, engine::general_purpose::URL_SAFE};
+ use p256::ecdsa::VerifyingKey;
+ use serde::{Deserializer, Serialize as _, de};
+
+ // This serialization - to a URL-safe base-64-encoded string and back - is based on my best
+ // understanding of RFC 8292 and the corresponding browser APIs. Particularly, it's based on
+ // section 3.2:
+ //
+ // > The "k" parameter includes an ECDSA public key [FIPS186] in uncompressed form [X9.62] that
+ // > is encoded using base64url encoding [RFC7515].
+ //
+ // <https://datatracker.ietf.org/doc/html/rfc8292#section-3.2>
+ //
+ // I believe this is also supported by MDN's explanation:
+ //
+ // > `applicationServerKey`
+ // >
+ // > A Base64-encoded string or ArrayBuffer containing an ECDSA P-256 public key that the push
+ // > server will use to authenticate your application server. If specified, all messages from
+ // > your application server must use the VAPID authentication scheme, and include a JWT signed
+ // > with the corresponding private key. This key IS NOT the same ECDH key that you use to
+ // > encrypt the data. For more information, see "Using VAPID with WebPush".
+ //
+ // <https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#applicationserverkey>
+
+ pub fn serialize<S>(key: &VerifyingKey, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ let key = key.to_sec1_bytes();
+ let key = URL_SAFE.encode(key);
+ key.serialize(serializer)
+ }
+
+ pub fn deserialize<'de, D>(deserializer: D) -> Result<VerifyingKey, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ deserializer.deserialize_str(Visitor)
+ }
+
+ struct Visitor;
+ impl de::Visitor<'_> for Visitor {
+ type Value = VerifyingKey;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("a string containing a VAPID key")
+ }
+
+ fn visit_str<E>(self, key: &str) -> Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ let key = URL_SAFE.decode(key).map_err(E::custom)?;
+ let key = VerifyingKey::from_sec1_bytes(&key).map_err(E::custom)?;
+
+ Ok(key)
+ }
+ }
+}