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) }