From 11f4f36a689b6447c9898a2840418e581cb3eb11 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Tue, 28 Oct 2025 14:10:48 -0400 Subject: Use PKCS8 PEM, not raw SEC1 bytes, to store VAPID keys. The `web-push` crate's VAPID signing support requires a private key. The `p256` crate is more than capable of generating one, but the easiest way to get a key from a `p256::ecdsa::SigningKey` to a `web_push::PartialVapidSignature` is via PKCS #8 PEM, not via the bytes. Since we'll need it in that form anyways, store it that way, so that we don't have to decode it using `p256`, re-encode to PEM, then decode to `PartialVapidSignature`. The migration in this commit invalidates existing VAPID keys. We could include support for re-encoding them on read, but there's little point: this code is still in flux anyways, and only development deployments exist. By the time this is final, the schema will have settled. --- ...0f6acfdfbf9898a1a407e8a562a181542834d05eb0.json | 32 ++++++++++++++++++++++ ...c7c204a4b9a518af418cc7e7fce9a6f0a106a6d66e.json | 32 ---------------------- migrations/20251028173914_pem_vapid_keys.sql | 15 ++++++++++ src/boot/app.rs | 2 ++ src/event/app.rs | 2 ++ src/vapid/app.rs | 5 ++-- src/vapid/repo.rs | 15 ++++++---- 7 files changed, 63 insertions(+), 40 deletions(-) create mode 100644 .sqlx/query-be295f56960d083d1f4c760f6acfdfbf9898a1a407e8a562a181542834d05eb0.json delete mode 100644 .sqlx/query-edd16f1507f3b40270d652c7c204a4b9a518af418cc7e7fce9a6f0a106a6d66e.json create mode 100644 migrations/20251028173914_pem_vapid_keys.sql diff --git a/.sqlx/query-be295f56960d083d1f4c760f6acfdfbf9898a1a407e8a562a181542834d05eb0.json b/.sqlx/query-be295f56960d083d1f4c760f6acfdfbf9898a1a407e8a562a181542834d05eb0.json new file mode 100644 index 0000000..ccec274 --- /dev/null +++ b/.sqlx/query-be295f56960d083d1f4c760f6acfdfbf9898a1a407e8a562a181542834d05eb0.json @@ -0,0 +1,32 @@ +{ + "db_name": "SQLite", + "query": "\n select\n key.changed_at as \"changed_at: DateTime\",\n key.changed_sequence as \"changed_sequence: Sequence\",\n signing.key\n from vapid_key as key\n join vapid_signing_key as signing\n ", + "describe": { + "columns": [ + { + "name": "changed_at: DateTime", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "changed_sequence: Sequence", + "ordinal": 1, + "type_info": "Integer" + }, + { + "name": "key", + "ordinal": 2, + "type_info": "Text" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "be295f56960d083d1f4c760f6acfdfbf9898a1a407e8a562a181542834d05eb0" +} diff --git a/.sqlx/query-edd16f1507f3b40270d652c7c204a4b9a518af418cc7e7fce9a6f0a106a6d66e.json b/.sqlx/query-edd16f1507f3b40270d652c7c204a4b9a518af418cc7e7fce9a6f0a106a6d66e.json deleted file mode 100644 index 2481fa9..0000000 --- a/.sqlx/query-edd16f1507f3b40270d652c7c204a4b9a518af418cc7e7fce9a6f0a106a6d66e.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n select\n key.changed_at as \"changed_at: DateTime\",\n key.changed_sequence as \"changed_sequence: Sequence\",\n signing.key as \"key: Vec\"\n from vapid_key as key\n join vapid_signing_key as signing\n ", - "describe": { - "columns": [ - { - "name": "changed_at: DateTime", - "ordinal": 0, - "type_info": "Text" - }, - { - "name": "changed_sequence: Sequence", - "ordinal": 1, - "type_info": "Integer" - }, - { - "name": "key: Vec", - "ordinal": 2, - "type_info": "Blob" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false, - false, - false - ] - }, - "hash": "edd16f1507f3b40270d652c7c204a4b9a518af418cc7e7fce9a6f0a106a6d66e" -} diff --git a/migrations/20251028173914_pem_vapid_keys.sql b/migrations/20251028173914_pem_vapid_keys.sql new file mode 100644 index 0000000..6302504 --- /dev/null +++ b/migrations/20251028173914_pem_vapid_keys.sql @@ -0,0 +1,15 @@ +drop table vapid_signing_key; + +create table vapid_signing_key ( + key text + not null +); + +create unique index vapid_signing_key_singleton + on vapid_signing_key (0); + +-- Whatever key we had, if any, was just destroyed by dropping the table. Delete the metadata +-- as well so that the server will issue a new one. +delete +from + vapid_key; diff --git a/src/boot/app.rs b/src/boot/app.rs index 8da3e90..88255b0 100644 --- a/src/boot/app.rs +++ b/src/boot/app.rs @@ -78,6 +78,7 @@ pub enum Error { Database(#[from] sqlx::Error), Name(#[from] name::Error), Ecdsa(#[from] p256::ecdsa::Error), + Pkcs8(#[from] p256::pkcs8::Error), } impl From for Error { @@ -106,6 +107,7 @@ impl From for Error { match error { Error::Database(error) => error.into(), Error::Ecdsa(error) => error.into(), + Error::Pkcs8(error) => error.into(), } } } diff --git a/src/event/app.rs b/src/event/app.rs index 6c657c7..1e471f1 100644 --- a/src/event/app.rs +++ b/src/event/app.rs @@ -98,6 +98,7 @@ pub enum Error { Database(#[from] sqlx::Error), Name(#[from] name::Error), Ecdsa(#[from] p256::ecdsa::Error), + Pkcs8(#[from] p256::pkcs8::Error), } impl From for Error { @@ -126,6 +127,7 @@ impl From for Error { match error { Error::Database(error) => error.into(), Error::Ecdsa(error) => error.into(), + Error::Pkcs8(error) => error.into(), } } } diff --git a/src/vapid/app.rs b/src/vapid/app.rs index 5814ba0..b6e1bc5 100644 --- a/src/vapid/app.rs +++ b/src/vapid/app.rs @@ -86,11 +86,11 @@ impl<'a> Vapid<'a> { } #[derive(Debug, thiserror::Error)] +#[error(transparent)] pub enum Error { - #[error(transparent)] Database(#[from] sqlx::Error), - #[error(transparent)] Ecdsa(#[from] p256::ecdsa::Error), + Pkcs8(#[from] p256::pkcs8::Error), } impl From for Error { @@ -99,6 +99,7 @@ impl From for Error { match error { Error::Database(error) => error.into(), Error::Ecdsa(error) => error.into(), + Error::Pkcs8(error) => error.into(), } } } diff --git a/src/vapid/repo.rs b/src/vapid/repo.rs index 4ac5286..98b3bae 100644 --- a/src/vapid/repo.rs +++ b/src/vapid/repo.rs @@ -1,4 +1,7 @@ -use p256::{NistP256, ecdsa::SigningKey, elliptic_curve::FieldBytes}; +use p256::{ + ecdsa::SigningKey, + pkcs8::{DecodePrivateKey as _, EncodePrivateKey as _, LineEnding}, +}; use sqlx::{Sqlite, SqliteConnection, Transaction}; use super::{ @@ -76,8 +79,8 @@ impl Vapid<'_> { } pub async fn store_signing_key(&mut self, key: &SigningKey) -> Result<(), Error> { - let key = key.to_bytes(); - let key = key.as_slice(); + let key = key.to_pkcs8_pem(LineEnding::CRLF)?; + let key = key.as_str(); sqlx::query!( r#" insert into vapid_signing_key (key) @@ -97,14 +100,13 @@ impl Vapid<'_> { select key.changed_at as "changed_at: DateTime", key.changed_sequence as "changed_sequence: Sequence", - signing.key as "key: Vec" + signing.key from vapid_key as key join vapid_signing_key as signing "# ) .map(|row| { - let key = FieldBytes::::from_slice(&row.key); - let key = SigningKey::from_bytes(key)?; + let key = SigningKey::from_pkcs8_pem(&row.key)?; let key = key.verifying_key().to_owned(); let changed = Instant::new(row.changed_at, row.changed_sequence); @@ -122,6 +124,7 @@ impl Vapid<'_> { #[error(transparent)] pub enum Error { Ecdsa(#[from] p256::ecdsa::Error), + Pkcs8(#[from] p256::pkcs8::Error), Database(#[from] sqlx::Error), } -- cgit v1.2.3