mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-01-10 16:40:12 +00:00
118 lines
2.4 KiB
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)
|
||
|
}
|