summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOwen Jacobson <owen@grimoire.ca>2025-10-24 19:03:02 -0400
committerOwen Jacobson <owen@grimoire.ca>2025-11-05 17:22:18 -0500
commite2a851f68aacd74a248e925ab334c3cf9eabba18 (patch)
tree5cc735543fbf75b1d6810e2ca8c0bc197269de3c
parente08c3fa46aa48ac5cbeb75f52fc27d4061e2fa62 (diff)
Move the VAPID public key encoding into a serde-compatible encoding module.
The [Serde attribute docs][serde-attr] don't spell out that this will work, but experimentally, it looks like a module used with `#[serde(with)]` only needs to have the `encode`/`decode` functions if they're actually used, and can be "incomplete" if the missing ones are also unused in your code. That's the case here: we serialize VAPID keys, but never deserialize them. [serde-attr]: https://serde.rs/field-attrs.html#with This improves organization a bit in my view, but more importantly it also sets us up for a coming change where we _will_ start deserializing VAPID keys, and where I'd like to use the same logic: giving it its own module will make that easier to organize.
-rw-r--r--src/vapid/event.rs13
-rw-r--r--src/vapid/mod.rs1
-rw-r--r--src/vapid/ser.rs35
3 files changed, 37 insertions, 12 deletions
diff --git a/src/vapid/event.rs b/src/vapid/event.rs
index af70ac2..cf3be77 100644
--- a/src/vapid/event.rs
+++ b/src/vapid/event.rs
@@ -1,6 +1,4 @@
-use base64::{Engine, engine::general_purpose::URL_SAFE};
use p256::ecdsa::VerifyingKey;
-use serde::Serialize;
use crate::event::{Instant, Sequenced};
@@ -22,7 +20,7 @@ impl Sequenced for Event {
pub struct Changed {
#[serde(flatten)]
pub instant: Instant,
- #[serde(serialize_with = "as_vapid_key")]
+ #[serde(with = "crate::vapid::ser::key")]
pub key: VerifyingKey,
}
@@ -37,12 +35,3 @@ impl Sequenced for Changed {
self.instant
}
}
-
-fn as_vapid_key<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)
-}
diff --git a/src/vapid/mod.rs b/src/vapid/mod.rs
index 9798654..364f602 100644
--- a/src/vapid/mod.rs
+++ b/src/vapid/mod.rs
@@ -3,6 +3,7 @@ pub mod event;
mod history;
mod middleware;
pub mod repo;
+pub mod ser;
pub use event::Event;
pub use history::History;
diff --git a/src/vapid/ser.rs b/src/vapid/ser.rs
new file mode 100644
index 0000000..f5372c8
--- /dev/null
+++ b/src/vapid/ser.rs
@@ -0,0 +1,35 @@
+pub mod key {
+ use base64::{Engine as _, engine::general_purpose::URL_SAFE};
+ use p256::ecdsa::VerifyingKey;
+ use serde::Serialize as _;
+
+ // 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)
+ }
+}