From 94e45575a34e8672f8b405c54cabd8e524281fef Mon Sep 17 00:00:00 2001 From: Claudomator Agent Date: Tue, 17 Mar 2026 02:12:31 +0000 Subject: fix: swap VAPID key return order in GenerateVAPIDKeys wrapper webpush.GenerateVAPIDKeys() returns (privateKey, publicKey) but the claudomator wrapper declared (publicKey, privateKey), causing the 32-byte private key to be sent to browsers as the applicationServerKey. Browsers require a 65-byte uncompressed P256 point, so they rejected it with "The provided applicationServerKey is not valid." Adds a regression test that asserts public key is 87 chars/65 bytes with 0x04 prefix and private key is 43 chars/32 bytes. Co-Authored-By: Claude Sonnet 4.6 --- internal/notify/vapid.go | 4 +++- internal/notify/vapid_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 internal/notify/vapid_test.go (limited to 'internal') diff --git a/internal/notify/vapid.go b/internal/notify/vapid.go index 90d535b..d93a090 100644 --- a/internal/notify/vapid.go +++ b/internal/notify/vapid.go @@ -4,6 +4,8 @@ import webpush "github.com/SherClockHolmes/webpush-go" // GenerateVAPIDKeys generates a VAPID key pair for web push notifications. // Returns the base64url-encoded public and private keys. +// Note: webpush.GenerateVAPIDKeys returns (privateKey, publicKey) — we swap here. func GenerateVAPIDKeys() (publicKey, privateKey string, err error) { - return webpush.GenerateVAPIDKeys() + privateKey, publicKey, err = webpush.GenerateVAPIDKeys() + return } diff --git a/internal/notify/vapid_test.go b/internal/notify/vapid_test.go new file mode 100644 index 0000000..6157854 --- /dev/null +++ b/internal/notify/vapid_test.go @@ -0,0 +1,43 @@ +package notify + +import ( + "encoding/base64" + "testing" +) + +// TestGenerateVAPIDKeys_PublicKeyIs65Bytes verifies that the public key returned +// by GenerateVAPIDKeys is a 65-byte uncompressed P256 EC point (base64url, no padding = 87 chars) +// and the private key is 32 bytes (43 chars). Previously the return values were swapped. +func TestGenerateVAPIDKeys_PublicKeyIs65Bytes(t *testing.T) { + pub, priv, err := GenerateVAPIDKeys() + if err != nil { + t.Fatalf("GenerateVAPIDKeys: %v", err) + } + + // Public key: 65 bytes → 87 base64url chars (no padding). + if len(pub) != 87 { + t.Errorf("public key: want 87 chars (65 bytes), got %d chars (%q)", len(pub), pub) + } + pubBytes, err := base64.RawURLEncoding.DecodeString(pub) + if err != nil { + t.Fatalf("public key base64url decode: %v", err) + } + if len(pubBytes) != 65 { + t.Errorf("public key bytes: want 65, got %d", len(pubBytes)) + } + if pubBytes[0] != 0x04 { + t.Errorf("public key first byte: want 0x04 (uncompressed point), got 0x%02x", pubBytes[0]) + } + + // Private key: 32 bytes → 43 base64url chars (no padding). + if len(priv) != 43 { + t.Errorf("private key: want 43 chars (32 bytes), got %d chars (%q)", len(priv), priv) + } + privBytes, err := base64.RawURLEncoding.DecodeString(priv) + if err != nil { + t.Fatalf("private key base64url decode: %v", err) + } + if len(privBytes) != 32 { + t.Errorf("private key bytes: want 32, got %d", len(privBytes)) + } +} -- cgit v1.2.3