CI / ci (push) Successful in 8m31s
Co-authored-by: orchestrator-agent <20+orchestrator-agent@code.cubecraftcreations.com>
88 lines
2.5 KiB
Go
88 lines
2.5 KiB
Go
// Package vapid generates and validates the VAPID key pair Web Push signs
|
|
// with (RFC 8292): a P-256 (ES256) key pair in the encoding webpush-go
|
|
// reads. Both keys are unpadded base64url: the public key is the 65-byte
|
|
// uncompressed point and the private key the 32-byte scalar.
|
|
package vapid
|
|
|
|
import (
|
|
"crypto/ecdh"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
// encoding is unpadded base64url. Strict refuses non-zero trailing bits, so
|
|
// every key has exactly one accepted spelling.
|
|
var encoding = base64.RawURLEncoding.Strict()
|
|
|
|
const (
|
|
publicKeySize = 65 // 0x04 || X || Y
|
|
privateKeySize = 32
|
|
)
|
|
|
|
// KeyPair is a VAPID key pair in its stored encoding.
|
|
type KeyPair struct {
|
|
PublicKey string
|
|
PrivateKey string
|
|
}
|
|
|
|
// Generate returns a new random key pair.
|
|
func Generate() (KeyPair, error) {
|
|
k, err := ecdh.P256().GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
return KeyPair{}, fmt.Errorf("vapid: generate key: %w", err)
|
|
}
|
|
return KeyPair{
|
|
PublicKey: encoding.EncodeToString(k.PublicKey().Bytes()),
|
|
PrivateKey: encoding.EncodeToString(k.Bytes()),
|
|
}, nil
|
|
}
|
|
|
|
// ParsePublicKey parses an encoded public key, which must be a point on
|
|
// P-256.
|
|
func ParsePublicKey(s string) (*ecdh.PublicKey, error) {
|
|
b, err := encoding.DecodeString(s)
|
|
if err != nil {
|
|
return nil, errors.New("not unpadded base64url")
|
|
}
|
|
if len(b) != publicKeySize || b[0] != 0x04 {
|
|
return nil, fmt.Errorf("want the %d-byte uncompressed P-256 point, got %d bytes", publicKeySize, len(b))
|
|
}
|
|
k, err := ecdh.P256().NewPublicKey(b)
|
|
if err != nil {
|
|
return nil, errors.New("not a point on P-256")
|
|
}
|
|
return k, nil
|
|
}
|
|
|
|
// ParsePrivateKey parses an encoded private key. Its errors never quote the
|
|
// key.
|
|
func ParsePrivateKey(s string) (*ecdh.PrivateKey, error) {
|
|
b, err := encoding.DecodeString(s)
|
|
if err != nil {
|
|
return nil, errors.New("not unpadded base64url")
|
|
}
|
|
if len(b) != privateKeySize {
|
|
return nil, fmt.Errorf("want a %d-byte P-256 scalar, got %d bytes", privateKeySize, len(b))
|
|
}
|
|
k, err := ecdh.P256().NewPrivateKey(b)
|
|
if err != nil {
|
|
return nil, errors.New("not a valid P-256 scalar")
|
|
}
|
|
return k, nil
|
|
}
|
|
|
|
// ErrMismatch is returned by CheckPair when the private key's public key is
|
|
// not the given public key.
|
|
var ErrMismatch = errors.New("private key does not belong to the public key")
|
|
|
|
// CheckPair reports whether private is the private key of public, by
|
|
// deriving the public point from private and comparing.
|
|
func CheckPair(public *ecdh.PublicKey, private *ecdh.PrivateKey) error {
|
|
if !private.PublicKey().Equal(public) {
|
|
return ErrMismatch
|
|
}
|
|
return nil
|
|
}
|