Skip to content

Commit

Permalink
Use aes128gcm encoding (#17)
Browse files Browse the repository at this point in the history
* Change encoding to aes128gcm from the rfc8188 spec

* Update README.md
  • Loading branch information
SherClockHolmes authored Feb 18, 2019
1 parent 52c6af1 commit 2a62cae
Show file tree
Hide file tree
Showing 15 changed files with 343 additions and 272 deletions.
4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
cmd/webpush/webpush

# gvt vendor
vendor/**
!vendor/manifest

.DS_Store
*.out
39 changes: 0 additions & 39 deletions Gopkg.lock

This file was deleted.

11 changes: 0 additions & 11 deletions Gopkg.toml

This file was deleted.

50 changes: 17 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,31 @@ go get -u github.com/SherClockHolmes/webpush-go

## Example

```golang
For a full example, refer to the code in the [example](example/) directory.

```go
package main

import (
"bytes"
"encoding/json"
"log"

webpush "github.com/SherClockHolmes/webpush-go"
)

const (
vapidPrivateKey = "<YOUR VAPID PRIVATE KEY>"
)

func main() {
subJSON := `{<YOUR SUBSCRIPTION JSON>}`

// Decode subscription
s := webpush.Subscription{}
if err := json.NewDecoder(bytes.NewBufferString(subJSON)).Decode(&s); err != nil {
log.Fatal(err)
}
s := &webpush.Subscription{}
json.Unmarshal([]byte("<YOUR_SUBSCRIPTION>"), s)

// Send Notification
_, err := webpush.SendNotification([]byte("Test"), &s, &webpush.Options{
Subscriber: "<[email protected]>",
VAPIDPrivateKey: vapidPrivateKey,
_, err := webpush.SendNotification([]byte("Test"), s, &webpush.Options{
Subscriber: "[email protected]",
VAPIDPublicKey: "<YOUR_VAPID_PUBLIC_KEY>",
VAPIDPrivateKey: "<YOUR_VAPID_PRIVATE_KEY>",
TTL: 30,
})
if err != nil {
log.Fatal(err)
// TODO: Handle error
}
}
```
Expand All @@ -53,26 +47,16 @@ Use the helper method `GenerateVAPIDKeys` to generate the VAPID key pair.
```golang
privateKey, publicKey, err := webpush.GenerateVAPIDKeys()
if err != nil {
// TODO: Handle failure!
// TODO: Handle error
}
```

## Development

1. Install [Go 1.8+](https://golang.org/) ([gvm](https://github.com/moovweb/gvm) recommended)
2. `go get -u github.com/golang/dep/cmd/dep`
3. `dep ensure`

## References

For more information visit these [Google Developers](https://developers.google.com/web) links:

[https://developers.google.com/web/updates/2016/03/web-push-encryption](https://developers.google.com/web/updates/2016/03/web-push-encryption)
[https://developers.google.com/web/updates/2016/07/web-push-interop-wins](https://developers.google.com/web/updates/2016/07/web-push-interop-wins)
1. Install [Go 1.11+](https://golang.org/)
2. `go mod vendor`
3. `go test`

### Similar Projects / Inspired By
#### For other language implementations visit:

- [Push Encryption (Go)](https://github.com/GoogleChrome/push-encryption-go)
- [go-ecdh](https://github.com/wsddn/go-ecdh)
- [WebPush Libs](https://github.com/web-push-libs)
- [Web Push: Data Encryption Test Page](https://jrconlin.github.io/WebPushDataTestPage/)
[WebPush Libs](https://github.com/web-push-libs)
21 changes: 21 additions & 0 deletions example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# example

## Access index.html

Replace the public VAPID key in index.html.

Use a tool such as SimpleHTTPServer to run a web server:

```
python -m SimpleHTTPServer 8000
```

Go to `http://localhost:8000` and copy the logged subsciption from the console.

## Test send a notification

Replace the public/private VAPID keys. Use the subscription you had from the first section.

```bash
go run main.go
```
58 changes: 58 additions & 0 deletions example/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Webpush Golang Example</title>
</head>
<body>
<script>
function subscribe() {
navigator.serviceWorker.ready
.then(function(registration) {
const vapidPublicKey = '<YOUR_VAPID_PUBLIC_KEY>';

return registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(vapidPublicKey),
});
})
.then(function(subscription) {
console.log(
JSON.stringify({
subscription: subscription,
})
);
})
.catch(err => console.error(err));
}

function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
return Uint8Array.from([...rawData].map(char => char.charCodeAt(0)));
}

if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js');
navigator.serviceWorker.ready
.then(function(registration) {
return registration.pushManager.getSubscription();
})
.then(function(subscription) {
if (!subscription) {
subscribe();
} else {
console.log(
JSON.stringify({
subscription: subscription,
})
);
}
});
}
</script>
</body>
</html>
30 changes: 30 additions & 0 deletions example/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"encoding/json"

webpush "github.com/SherClockHolmes/webpush-go"
)

const (
subscription = ``
vapidPublicKey = ""
vapidPrivateKey = ""
)

func main() {
// Decode subscription
s := &webpush.Subscription{}
json.Unmarshal([]byte(subscription), s)

// Send Notification
_, err := webpush.SendNotification([]byte("Test"), s, &webpush.Options{
Subscriber: "[email protected]", // Do not include "mailto:"
VAPIDPublicKey: vapidPublicKey,
VAPIDPrivateKey: vapidPrivateKey,
TTL: 30,
})
if err != nil {
// TODO: Handle error
}
}
11 changes: 11 additions & 0 deletions example/service-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
self.addEventListener('push', event => {
console.log('[Service Worker] Push Received.');
console.log(`[Service Worker] Push had this data: "${event.data.text()}"`);

const title = 'Test Webpush';
const options = {
body: event.data.text(),
};

event.waitUntil(self.registration.showNotification(title, options));
});
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module github.com/SherClockHolmes/webpush-go

require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 h1:MQ/ZZiDsUapFFiMS+vzwXkCTeEKaum+Do5rINYJDmxc=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
26 changes: 26 additions & 0 deletions urgency.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package webpush

// Urgency indicates to the push service how important a message is to the user.
// This can be used by the push service to help conserve the battery life of a user's device
// by only waking up for important messages when battery is low.
type Urgency string

const (
// UrgencyVeryLow requires device state: on power and Wi-Fi
UrgencyVeryLow Urgency = "very-low"
// UrgencyLow requires device state: on either power or Wi-Fi
UrgencyLow Urgency = "low"
// UrgencyNormal excludes device state: low battery
UrgencyNormal Urgency = "normal"
// UrgencyHigh admits device state: low battery
UrgencyHigh Urgency = "high"
)

// Checking allowable values for the urgency header
func isValidUrgency(urgency Urgency) bool {
switch urgency {
case UrgencyVeryLow, UrgencyLow, UrgencyNormal, UrgencyHigh:
return true
}
return false
}
52 changes: 23 additions & 29 deletions vapid.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"encoding/base64"
"fmt"
"math/big"
"net/http"
"net/url"
"time"

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

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

return &pubKey, &ecdsa.PrivateKey{
return &ecdsa.PrivateKey{
PublicKey: pubKey,
D: d,
}
}

// Sign the http.Request with the required VAPID headers
func vapid(req *http.Request, s *Subscription, options *Options) error {
// getVAPIDAuthorizationHeader
func getVAPIDAuthorizationHeader(
endpoint,
subscriber,
vapidPublicKey,
vapidPrivateKey string,
) (string, error) {
// Create the JWT token
subURL, err := url.Parse(s.Endpoint)
subURL, err := url.Parse(endpoint)
if err != nil {
return err
return "", err
}

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

// ECDSA
b64 := base64.RawURLEncoding
decodedVapidPrivateKey, err := b64.DecodeString(options.VAPIDPrivateKey)
decodedVapidPrivateKey, err := base64.RawURLEncoding.DecodeString(vapidPrivateKey)
if err != nil {
return err
return "", err
}

pubKey, privKey := generateVAPIDHeaderKeys(decodedVapidPrivateKey)
privKey := generateVAPIDHeaderKeys(decodedVapidPrivateKey)

// Sign token with key
tokenString, err := token.SignedString(privKey)
// Sign token with private key
jwtString, err := token.SignedString(privKey)
if err != nil {
return err
return "", err
}

// Set VAPID headers
req.Header.Set("Authorization", fmt.Sprintf("WebPush %s", tokenString))

vapidPublicKeyHeader := elliptic.Marshal(pubKey.Curve, pubKey.X, pubKey.Y)
req.Header.Set(
"Crypto-key",
fmt.Sprintf(
"%s;p256ecdsa=%s",
req.Header.Get("Crypto-Key"),
base64.RawURLEncoding.EncodeToString(vapidPublicKeyHeader),
),
)

return nil
return fmt.Sprintf(
"vapid t=%s, k=%s",
jwtString,
vapidPublicKey,
), nil
}
Loading

0 comments on commit 2a62cae

Please sign in to comment.