Skip to content

Commit 2a62cae

Browse files
Use aes128gcm encoding (#17)
* Change encoding to aes128gcm from the rfc8188 spec * Update README.md
1 parent 52c6af1 commit 2a62cae

File tree

15 files changed

+343
-272
lines changed

15 files changed

+343
-272
lines changed

.gitignore

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
cmd/webpush/webpush
2-
3-
# gvt vendor
41
vendor/**
5-
!vendor/manifest
62

73
.DS_Store
84
*.out

Gopkg.lock

Lines changed: 0 additions & 39 deletions
This file was deleted.

Gopkg.toml

Lines changed: 0 additions & 11 deletions
This file was deleted.

README.md

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,31 @@ go get -u github.com/SherClockHolmes/webpush-go
1111

1212
## Example
1313

14-
```golang
14+
For a full example, refer to the code in the [example](example/) directory.
15+
16+
```go
1517
package main
1618

1719
import (
18-
"bytes"
1920
"encoding/json"
20-
"log"
2121

2222
webpush "github.com/SherClockHolmes/webpush-go"
2323
)
2424

25-
const (
26-
vapidPrivateKey = "<YOUR VAPID PRIVATE KEY>"
27-
)
28-
2925
func main() {
30-
subJSON := `{<YOUR SUBSCRIPTION JSON>}`
31-
3226
// Decode subscription
33-
s := webpush.Subscription{}
34-
if err := json.NewDecoder(bytes.NewBufferString(subJSON)).Decode(&s); err != nil {
35-
log.Fatal(err)
36-
}
27+
s := &webpush.Subscription{}
28+
json.Unmarshal([]byte("<YOUR_SUBSCRIPTION>"), s)
3729

3830
// Send Notification
39-
_, err := webpush.SendNotification([]byte("Test"), &s, &webpush.Options{
40-
Subscriber: "<[email protected]>",
41-
VAPIDPrivateKey: vapidPrivateKey,
31+
_, err := webpush.SendNotification([]byte("Test"), s, &webpush.Options{
32+
Subscriber: "[email protected]",
33+
VAPIDPublicKey: "<YOUR_VAPID_PUBLIC_KEY>",
34+
VAPIDPrivateKey: "<YOUR_VAPID_PRIVATE_KEY>",
35+
TTL: 30,
4236
})
4337
if err != nil {
44-
log.Fatal(err)
38+
// TODO: Handle error
4539
}
4640
}
4741
```
@@ -53,26 +47,16 @@ Use the helper method `GenerateVAPIDKeys` to generate the VAPID key pair.
5347
```golang
5448
privateKey, publicKey, err := webpush.GenerateVAPIDKeys()
5549
if err != nil {
56-
// TODO: Handle failure!
50+
// TODO: Handle error
5751
}
5852
```
5953

6054
## Development
6155

62-
1. Install [Go 1.8+](https://golang.org/) ([gvm](https://github.com/moovweb/gvm) recommended)
63-
2. `go get -u github.com/golang/dep/cmd/dep`
64-
3. `dep ensure`
65-
66-
## References
67-
68-
For more information visit these [Google Developers](https://developers.google.com/web) links:
69-
70-
[https://developers.google.com/web/updates/2016/03/web-push-encryption](https://developers.google.com/web/updates/2016/03/web-push-encryption)
71-
[https://developers.google.com/web/updates/2016/07/web-push-interop-wins](https://developers.google.com/web/updates/2016/07/web-push-interop-wins)
56+
1. Install [Go 1.11+](https://golang.org/)
57+
2. `go mod vendor`
58+
3. `go test`
7259

73-
### Similar Projects / Inspired By
60+
#### For other language implementations visit:
7461

75-
- [Push Encryption (Go)](https://github.com/GoogleChrome/push-encryption-go)
76-
- [go-ecdh](https://github.com/wsddn/go-ecdh)
77-
- [WebPush Libs](https://github.com/web-push-libs)
78-
- [Web Push: Data Encryption Test Page](https://jrconlin.github.io/WebPushDataTestPage/)
62+
[WebPush Libs](https://github.com/web-push-libs)

example/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# example
2+
3+
## Access index.html
4+
5+
Replace the public VAPID key in index.html.
6+
7+
Use a tool such as SimpleHTTPServer to run a web server:
8+
9+
```
10+
python -m SimpleHTTPServer 8000
11+
```
12+
13+
Go to `http://localhost:8000` and copy the logged subsciption from the console.
14+
15+
## Test send a notification
16+
17+
Replace the public/private VAPID keys. Use the subscription you had from the first section.
18+
19+
```bash
20+
go run main.go
21+
```

example/index.html

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Webpush Golang Example</title>
6+
</head>
7+
<body>
8+
<script>
9+
function subscribe() {
10+
navigator.serviceWorker.ready
11+
.then(function(registration) {
12+
const vapidPublicKey = '<YOUR_VAPID_PUBLIC_KEY>';
13+
14+
return registration.pushManager.subscribe({
15+
userVisibleOnly: true,
16+
applicationServerKey: urlBase64ToUint8Array(vapidPublicKey),
17+
});
18+
})
19+
.then(function(subscription) {
20+
console.log(
21+
JSON.stringify({
22+
subscription: subscription,
23+
})
24+
);
25+
})
26+
.catch(err => console.error(err));
27+
}
28+
29+
function urlBase64ToUint8Array(base64String) {
30+
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
31+
const base64 = (base64String + padding)
32+
.replace(/\-/g, '+')
33+
.replace(/_/g, '/');
34+
const rawData = window.atob(base64);
35+
return Uint8Array.from([...rawData].map(char => char.charCodeAt(0)));
36+
}
37+
38+
if ('serviceWorker' in navigator) {
39+
navigator.serviceWorker.register('service-worker.js');
40+
navigator.serviceWorker.ready
41+
.then(function(registration) {
42+
return registration.pushManager.getSubscription();
43+
})
44+
.then(function(subscription) {
45+
if (!subscription) {
46+
subscribe();
47+
} else {
48+
console.log(
49+
JSON.stringify({
50+
subscription: subscription,
51+
})
52+
);
53+
}
54+
});
55+
}
56+
</script>
57+
</body>
58+
</html>

example/main.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
6+
webpush "github.com/SherClockHolmes/webpush-go"
7+
)
8+
9+
const (
10+
subscription = ``
11+
vapidPublicKey = ""
12+
vapidPrivateKey = ""
13+
)
14+
15+
func main() {
16+
// Decode subscription
17+
s := &webpush.Subscription{}
18+
json.Unmarshal([]byte(subscription), s)
19+
20+
// Send Notification
21+
_, err := webpush.SendNotification([]byte("Test"), s, &webpush.Options{
22+
Subscriber: "[email protected]", // Do not include "mailto:"
23+
VAPIDPublicKey: vapidPublicKey,
24+
VAPIDPrivateKey: vapidPrivateKey,
25+
TTL: 30,
26+
})
27+
if err != nil {
28+
// TODO: Handle error
29+
}
30+
}

example/service-worker.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
self.addEventListener('push', event => {
2+
console.log('[Service Worker] Push Received.');
3+
console.log(`[Service Worker] Push had this data: "${event.data.text()}"`);
4+
5+
const title = 'Test Webpush';
6+
const options = {
7+
body: event.data.text(),
8+
};
9+
10+
event.waitUntil(self.registration.showNotification(title, options));
11+
});

go.mod

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module github.com/SherClockHolmes/webpush-go
2+
3+
require (
4+
github.com/dgrijalva/jwt-go v3.2.0+incompatible
5+
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613
6+
)

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
2+
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
3+
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 h1:MQ/ZZiDsUapFFiMS+vzwXkCTeEKaum+Do5rINYJDmxc=
4+
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=

urgency.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package webpush
2+
3+
// Urgency indicates to the push service how important a message is to the user.
4+
// This can be used by the push service to help conserve the battery life of a user's device
5+
// by only waking up for important messages when battery is low.
6+
type Urgency string
7+
8+
const (
9+
// UrgencyVeryLow requires device state: on power and Wi-Fi
10+
UrgencyVeryLow Urgency = "very-low"
11+
// UrgencyLow requires device state: on either power or Wi-Fi
12+
UrgencyLow Urgency = "low"
13+
// UrgencyNormal excludes device state: low battery
14+
UrgencyNormal Urgency = "normal"
15+
// UrgencyHigh admits device state: low battery
16+
UrgencyHigh Urgency = "high"
17+
)
18+
19+
// Checking allowable values for the urgency header
20+
func isValidUrgency(urgency Urgency) bool {
21+
switch urgency {
22+
case UrgencyVeryLow, UrgencyLow, UrgencyNormal, UrgencyHigh:
23+
return true
24+
}
25+
return false
26+
}

vapid.go

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"encoding/base64"
88
"fmt"
99
"math/big"
10-
"net/http"
1110
"net/url"
1211
"time"
1312

@@ -34,7 +33,7 @@ func GenerateVAPIDKeys() (privateKey, publicKey string, err error) {
3433
}
3534

3635
// Generates the ECDSA public and private keys for the JWT encryption
37-
func generateVAPIDHeaderKeys(privateKey []byte) (*ecdsa.PublicKey, *ecdsa.PrivateKey) {
36+
func generateVAPIDHeaderKeys(privateKey []byte) *ecdsa.PrivateKey {
3837
// Public key
3938
curve := elliptic.P256()
4039
px, py := curve.ScalarMult(
@@ -53,53 +52,48 @@ func generateVAPIDHeaderKeys(privateKey []byte) (*ecdsa.PublicKey, *ecdsa.Privat
5352
d := &big.Int{}
5453
d.SetBytes(privateKey)
5554

56-
return &pubKey, &ecdsa.PrivateKey{
55+
return &ecdsa.PrivateKey{
5756
PublicKey: pubKey,
5857
D: d,
5958
}
6059
}
6160

62-
// Sign the http.Request with the required VAPID headers
63-
func vapid(req *http.Request, s *Subscription, options *Options) error {
61+
// getVAPIDAuthorizationHeader
62+
func getVAPIDAuthorizationHeader(
63+
endpoint,
64+
subscriber,
65+
vapidPublicKey,
66+
vapidPrivateKey string,
67+
) (string, error) {
6468
// Create the JWT token
65-
subURL, err := url.Parse(s.Endpoint)
69+
subURL, err := url.Parse(endpoint)
6670
if err != nil {
67-
return err
71+
return "", err
6872
}
6973

7074
token := jwt.NewWithClaims(jwt.SigningMethodES256, jwt.MapClaims{
7175
"aud": fmt.Sprintf("%s://%s", subURL.Scheme, subURL.Host),
7276
"exp": time.Now().Add(time.Hour * 12).Unix(),
73-
"sub": fmt.Sprintf("mailto:%s", options.Subscriber),
77+
"sub": fmt.Sprintf("mailto:%s", subscriber),
7478
})
7579

7680
// ECDSA
77-
b64 := base64.RawURLEncoding
78-
decodedVapidPrivateKey, err := b64.DecodeString(options.VAPIDPrivateKey)
81+
decodedVapidPrivateKey, err := base64.RawURLEncoding.DecodeString(vapidPrivateKey)
7982
if err != nil {
80-
return err
83+
return "", err
8184
}
8285

83-
pubKey, privKey := generateVAPIDHeaderKeys(decodedVapidPrivateKey)
86+
privKey := generateVAPIDHeaderKeys(decodedVapidPrivateKey)
8487

85-
// Sign token with key
86-
tokenString, err := token.SignedString(privKey)
88+
// Sign token with private key
89+
jwtString, err := token.SignedString(privKey)
8790
if err != nil {
88-
return err
91+
return "", err
8992
}
9093

91-
// Set VAPID headers
92-
req.Header.Set("Authorization", fmt.Sprintf("WebPush %s", tokenString))
93-
94-
vapidPublicKeyHeader := elliptic.Marshal(pubKey.Curve, pubKey.X, pubKey.Y)
95-
req.Header.Set(
96-
"Crypto-key",
97-
fmt.Sprintf(
98-
"%s;p256ecdsa=%s",
99-
req.Header.Get("Crypto-Key"),
100-
base64.RawURLEncoding.EncodeToString(vapidPublicKeyHeader),
101-
),
102-
)
103-
104-
return nil
94+
return fmt.Sprintf(
95+
"vapid t=%s, k=%s",
96+
jwtString,
97+
vapidPublicKey,
98+
), nil
10599
}

0 commit comments

Comments
 (0)