-
Notifications
You must be signed in to change notification settings - Fork 70
/
vapid.go
117 lines (97 loc) · 2.43 KB
/
vapid.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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)
}