gotosocial/vendor/github.com/SherClockHolmes/webpush-go/vapid.go

118 lines
2.4 KiB
Go

package webpush
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"encoding/base64"
"fmt"
"math/big"
"net/url"
"time"
"github.com/golang-jwt/jwt"
)
// GenerateVAPIDKeys will create a private and public VAPID key pair
func GenerateVAPIDKeys() (privateKey, publicKey string, err error) {
// Get the private key from the P256 curve
curve := elliptic.P256()
private, x, y, err := elliptic.GenerateKey(curve, rand.Reader)
if err != nil {
return
}
public := elliptic.Marshal(curve, x, y)
// Convert to base64
publicKey = base64.RawURLEncoding.EncodeToString(public)
privateKey = base64.RawURLEncoding.EncodeToString(private)
return
}
// Generates the ECDSA public and private keys for the JWT encryption
func generateVAPIDHeaderKeys(privateKey []byte) *ecdsa.PrivateKey {
// Public key
curve := elliptic.P256()
px, py := curve.ScalarMult(
curve.Params().Gx,
curve.Params().Gy,
privateKey,
)
pubKey := ecdsa.PublicKey{
Curve: curve,
X: px,
Y: py,
}
// Private key
d := &big.Int{}
d.SetBytes(privateKey)
return &ecdsa.PrivateKey{
PublicKey: pubKey,
D: d,
}
}
// getVAPIDAuthorizationHeader
func getVAPIDAuthorizationHeader(
endpoint,
subscriber,
vapidPublicKey,
vapidPrivateKey string,
expiration time.Time,
) (string, error) {
// Create the JWT token
subURL, err := url.Parse(endpoint)
if err != nil {
return "", err
}
token := jwt.NewWithClaims(jwt.SigningMethodES256, jwt.MapClaims{
"aud": fmt.Sprintf("%s://%s", subURL.Scheme, subURL.Host),
"exp": expiration.Unix(),
"sub": fmt.Sprintf("mailto:%s", subscriber),
})
// Decode the VAPID private key
decodedVapidPrivateKey, err := decodeVapidKey(vapidPrivateKey)
if err != nil {
return "", err
}
privKey := generateVAPIDHeaderKeys(decodedVapidPrivateKey)
// Sign token with private key
jwtString, err := token.SignedString(privKey)
if err != nil {
return "", err
}
// Decode the VAPID public key
pubKey, err := decodeVapidKey(vapidPublicKey)
if err != nil {
return "", err
}
return fmt.Sprintf(
"vapid t=%s, k=%s",
jwtString,
base64.RawURLEncoding.EncodeToString(pubKey),
), nil
}
// Need to decode the vapid private key in multiple base64 formats
// Solution from: https://github.com/SherClockHolmes/webpush-go/issues/29
func decodeVapidKey(key string) ([]byte, error) {
bytes, err := base64.URLEncoding.DecodeString(key)
if err == nil {
return bytes, nil
}
return base64.RawURLEncoding.DecodeString(key)
}