Skip to content

Commit d51d029

Browse files
authored
Merge pull request #64 from pusher/base64-master-key
Base64 master key
2 parents 4c7ea63 + 81a5b42 commit d51d029

9 files changed

+355
-75
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
next
2+
====
3+
4+
* Added EncryptionMasterKeyBase64 parameter
5+
* Deprecated EncryptionMasterKey parameter
6+
17
4.0.0 / 2019-05-31
28
==================
39
* This release modifies the entire repo to respect the go linter. This is a significant API breaking change and will likely require you to correct references to the names that were changed in your code. All future releases will respect the linter. A summary of the changes:

README.md

+25-16
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ By default, this is `"api.pusherapp.com"`.
138138

139139
Setting the `pusher.Client`'s `Cluster` property will make sure requests are sent to the cluster where you created your app.
140140

141-
*NOTE! If `Host` is set then `Cluster` will be ignored.
141+
*NOTE! If `Host` is set then `Cluster` will be ignored.*
142142

143143
```go
144144
pusherClient.Cluster = "eu" // in this case requests will be made to api-eu.pusher.com.
@@ -149,26 +149,37 @@ This library supports end to end encryption of your private channels. This means
149149

150150
1. You should first set up Private channels. This involves [creating an authentication endpoint on your server](https://pusher.com/docs/authenticating_users).
151151

152-
2. Next, Specify your 32 character `EncryptionMasterKey`. This is secret and you should never share this with anyone. Not even Pusher.
152+
2. Next, generate a 32 byte master encryption key, base64 encode it and store
153+
it securely.
153154

154-
```go
155-
pusherClient := pusher.Client{
156-
AppID: "APP_ID",
157-
Key: "APP_KEY",
158-
Secret: "APP_SECRET",
159-
Cluster: "APP_CLUSTER",
160-
EncryptionMasterKey "abcdefghijklmnopqrstuvwxyzabcdef",
161-
}
162-
```
163-
3. Channels where you wish to use end to end encryption should be prefixed with `private-encrypted-`.
155+
This is secret and you should never share this with anyone. Not even Pusher.
156+
157+
To generate a suitable key from a secure random source, you could use:
164158

165-
4. Subscribe to these channels in your client, and you're done! You can verify it is working by checking out the debug console on the https://dashboard.pusher.com/ and seeing the scrambled ciphertext.
159+
```bash
160+
openssl rand -base64 32
161+
```
162+
163+
3. Pass the encoded key when constructing your pusher.Client
164+
165+
```go
166+
pusherClient := pusher.Client{
167+
AppID: "APP_ID",
168+
Key: "APP_KEY",
169+
Secret: "APP_SECRET",
170+
Cluster: "APP_CLUSTER",
171+
EncryptionMasterKeyBase64 "<output from command above>",
172+
}
173+
```
174+
4. Channels where you wish to use end to end encryption should be prefixed with `private-encrypted-`.
175+
176+
5. Subscribe to these channels in your client, and you're done! You can verify it is working by checking out the debug console on the https://dashboard.pusher.com/ and seeing the scrambled ciphertext.
166177

167178
**Important note: This will not encrypt messages on channels that are not prefixed by private-encrypted-.**
168179

169180
### Google App Engine
170181

171-
As of version 1.0.0, this library is compatible with Google App Engine's urlfetch library. Simply pass in the HTTP client returned by `urlfetch.Client` to your Pusher Channels initialization struct.
182+
As of version 1.0.0, this library is compatible with Google App Engine's urlfetch library. Pass in the HTTP client returned by `urlfetch.Client` to your Pusher Channels initialization struct.
172183

173184
```go
174185
package helloworldapp
@@ -613,8 +624,6 @@ Feel more than free to fork this repo, improve it in any way you'd prefer, and s
613624

614625
### Running the tests
615626

616-
Simply type:
617-
618627
```sh
619628
$ go test
620629
```

client.go

+82-30
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ There easiest way to configure the library is by creating a `Pusher` instance:
2727
Secret: "your_app_secret",
2828
}
2929
30-
To ensure requests occur over HTTPS, set the `Encrypted` property of a
30+
To ensure requests occur over HTTPS, set the `Secure` property of a
3131
`pusher.Client` to `true`.
3232
3333
client.Secure = true // false by default
@@ -44,14 +44,16 @@ to your specified host.
4444
4545
*/
4646
type Client struct {
47-
AppID string
48-
Key string
49-
Secret string
50-
Host string // host or host:port pair
51-
Secure bool // true for HTTPS
52-
Cluster string
53-
HTTPClient *http.Client
54-
EncryptionMasterKey string //for E2E
47+
AppID string
48+
Key string
49+
Secret string
50+
Host string // host or host:port pair
51+
Secure bool // true for HTTPS
52+
Cluster string
53+
HTTPClient *http.Client
54+
EncryptionMasterKey string // deprecated
55+
EncryptionMasterKeyBase64 string // for E2E
56+
validatedEncryptionMasterKey *[]byte // parsed key for use
5557
}
5658

5759
/*
@@ -168,30 +170,32 @@ func (c *Client) TriggerMultiExclusive(channels []string, eventName string, data
168170
}
169171

170172
func (c *Client) trigger(channels []string, eventName string, data interface{}, socketID *string) error {
173+
if len(channels) > maxTriggerableChannels {
174+
return fmt.Errorf("You cannot trigger on more than %d channels at once", maxTriggerableChannels)
175+
}
176+
if !channelsAreValid(channels) {
177+
return errors.New("At least one of your channels' names are invalid")
178+
}
179+
171180
hasEncryptedChannel := false
172181
for _, channel := range channels {
173182
if isEncryptedChannel(channel) {
174183
hasEncryptedChannel = true
175184
}
176185
}
177-
if len(channels) > maxTriggerableChannels {
178-
return fmt.Errorf("You cannot trigger on more than %d channels at once", maxTriggerableChannels)
179-
}
180186
if hasEncryptedChannel && len(channels) > 1 {
181187
// For rationale, see limitations of end-to-end encryption in the README
182188
return errors.New("You cannot trigger to multiple channels when using encrypted channels")
183-
184189
}
185-
if !channelsAreValid(channels) {
186-
return errors.New("At least one of your channels' names are invalid")
187-
}
188-
if hasEncryptedChannel && !validEncryptionKey(c.EncryptionMasterKey) {
189-
return errors.New("Your encryptionMasterKey is not of the correct format")
190+
masterKey, keyErr := c.encryptionMasterKey()
191+
if hasEncryptedChannel && keyErr != nil {
192+
return keyErr
190193
}
191194
if err := validateSocketID(socketID); err != nil {
192195
return err
193196
}
194-
payload, err := encodeTriggerBody(channels, eventName, data, socketID, c.EncryptionMasterKey)
197+
198+
payload, err := encodeTriggerBody(channels, eventName, data, socketID, masterKey)
195199
if err != nil {
196200
return err
197201
}
@@ -236,13 +240,12 @@ func (c *Client) TriggerBatch(batch []Event) error {
236240
hasEncryptedChannel = true
237241
}
238242
}
239-
if hasEncryptedChannel {
240-
// validate EncryptionMasterKey
241-
if !validEncryptionKey(c.EncryptionMasterKey) {
242-
return errors.New("Your encryptionMasterKey is not of the correct format")
243-
}
243+
masterKey, keyErr := c.encryptionMasterKey()
244+
if hasEncryptedChannel && keyErr != nil {
245+
return keyErr
244246
}
245-
payload, err := encodeTriggerBatchBody(batch, c.EncryptionMasterKey)
247+
248+
payload, err := encodeTriggerBatchBody(batch, masterKey)
246249
if err != nil {
247250
return err
248251
}
@@ -405,7 +408,6 @@ func (c *Client) AuthenticatePresenceChannel(params []byte, member MemberData) (
405408
}
406409

407410
func (c *Client) authenticateChannel(params []byte, member *MemberData) (response []byte, err error) {
408-
409411
channelName, socketID, err := parseAuthRequestParams(params)
410412
if err != nil {
411413
return
@@ -433,7 +435,11 @@ func (c *Client) authenticateChannel(params []byte, member *MemberData) (respons
433435
var _response map[string]string
434436

435437
if isEncryptedChannel(channelName) {
436-
sharedSecret := generateSharedSecret(channelName, c.EncryptionMasterKey)
438+
masterKey, err := c.encryptionMasterKey()
439+
if err != nil {
440+
return nil, err
441+
}
442+
sharedSecret := generateSharedSecret(channelName, masterKey)
437443
sharedSecretB64 := base64.StdEncoding.EncodeToString(sharedSecret[:])
438444
_response = createAuthMap(c.Key, c.Secret, stringToSign, sharedSecretB64)
439445
} else {
@@ -480,11 +486,57 @@ func (c *Client) Webhook(header http.Header, body []byte) (*Webhook, error) {
480486
if token == c.Key && checkSignature(header.Get("X-Pusher-Signature"), c.Secret, body) {
481487
unmarshalledWebhooks, err := unmarshalledWebhook(body)
482488
if err != nil {
483-
return unmarshalledWebhooks, err
489+
return nil, err
484490
}
485-
decryptedWebhooks, err := decryptEvents(*unmarshalledWebhooks, c.EncryptionMasterKey)
486-
return decryptedWebhooks, err
491+
492+
hasEncryptedChannel := false
493+
for _, event := range unmarshalledWebhooks.Events {
494+
if isEncryptedChannel(event.Channel) {
495+
hasEncryptedChannel = true
496+
}
497+
}
498+
masterKey, keyErr := c.encryptionMasterKey()
499+
if hasEncryptedChannel && keyErr != nil {
500+
return nil, keyErr
501+
}
502+
503+
return decryptEvents(*unmarshalledWebhooks, masterKey)
487504
}
488505
}
489506
return nil, errors.New("Invalid webhook")
490507
}
508+
509+
func (c *Client) encryptionMasterKey() ([]byte, error) {
510+
if c.validatedEncryptionMasterKey != nil {
511+
return *(c.validatedEncryptionMasterKey), nil
512+
}
513+
514+
if c.EncryptionMasterKey != "" && c.EncryptionMasterKeyBase64 != "" {
515+
return nil, errors.New("Do not specify both EncryptionMasterKey and EncryptionMasterKeyBase64. EncryptionMasterKey is deprecated, specify only EncryptionMasterKeyBase64")
516+
}
517+
518+
if c.EncryptionMasterKey != "" {
519+
if len(c.EncryptionMasterKey) != 32 {
520+
return nil, errors.New("EncryptionMasterKey must be 32 bytes. It is also deprecated, use EncryptionMasterKeyBase64")
521+
}
522+
523+
keyBytes := []byte(c.EncryptionMasterKey)
524+
c.validatedEncryptionMasterKey = &keyBytes
525+
return keyBytes, nil
526+
}
527+
528+
if c.EncryptionMasterKeyBase64 != "" {
529+
keyBytes, err := base64.StdEncoding.DecodeString(c.EncryptionMasterKeyBase64)
530+
if err != nil {
531+
return nil, errors.New("EncryptionMasterKeyBase64 must be valid base64")
532+
}
533+
if len(keyBytes) != 32 {
534+
return nil, errors.New("EncryptionMasterKeyBase64 must encode 32 bytes")
535+
}
536+
537+
c.validatedEncryptionMasterKey = &keyBytes
538+
return keyBytes, nil
539+
}
540+
541+
return nil, errors.New("No master encryption key supplied")
542+
}

0 commit comments

Comments
 (0)