-
Notifications
You must be signed in to change notification settings - Fork 84
Expand file tree
/
Copy pathvapid.go
More file actions
139 lines (114 loc) · 3.07 KB
/
Copy pathvapid.go
File metadata and controls
139 lines (114 loc) · 3.07 KB
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package webpush
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"encoding/base64"
"math/big"
"net/url"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
)
// AuthScheme is the type for VAPID authentication schemes.
type AuthScheme string
const (
// Vapid is the original VAPID scheme.
Vapid AuthScheme = "vapid"
// WebPush is the modern VAPID scheme.
WebPush AuthScheme = "webpush"
)
// 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,
}
}
// generateVAPIDHeaders generates the VAPID headers.
// It returns a map of headers.
func generateVAPIDHeaders(
endpoint,
subscriber,
vapidPublicKey,
vapidPrivateKey string,
expiration time.Time,
authScheme AuthScheme,
) (map[string]string, error) {
// Create the JWT token
subURL, err := url.Parse(endpoint)
if err != nil {
return nil, err
}
// Unless subscriber is an HTTPS URL, assume an e-mail address
if !strings.HasPrefix(subscriber, "https:") {
subscriber = "mailto:" + subscriber
}
token := jwt.NewWithClaims(jwt.SigningMethodES256, jwt.MapClaims{
"aud": subURL.Scheme + "://" + subURL.Host,
"exp": expiration.Unix(),
"sub": subscriber,
})
// Decode the VAPID private key
decodedVapidPrivateKey, err := decodeVapidKey(vapidPrivateKey)
if err != nil {
return nil, err
}
privKey := generateVAPIDHeaderKeys(decodedVapidPrivateKey)
// Sign token with private key
jwtString, err := token.SignedString(privKey)
if err != nil {
return nil, err
}
headers := make(map[string]string)
switch authScheme {
case WebPush:
headers["Authorization"] = "WebPush " + jwtString
headers["Crypto-Key"] = "p256ecdsa=" + vapidPublicKey
default: // Default to the old scheme
pubKey, err := decodeVapidKey(vapidPublicKey)
if err != nil {
return nil, err
}
headers["Authorization"] = "vapid t=" + jwtString + ", k=" + base64.RawURLEncoding.EncodeToString(pubKey)
}
return headers, 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)
}