1
+ require 'base64'
2
+
1
3
require 'pusher-signature'
2
4
3
5
module Pusher
4
6
class Client
5
- attr_accessor :scheme , :host , :port , :app_id , :key , :secret , :notification_host , :notification_scheme
7
+ attr_accessor :scheme , :host , :port , :app_id , :key , :secret , :notification_host , :notification_scheme , :encryption_master_key
6
8
attr_reader :http_proxy , :proxy
7
9
attr_writer :connect_timeout , :send_timeout , :receive_timeout ,
8
10
:keep_alive_timeout
@@ -55,6 +57,11 @@ def initialize(options = {})
55
57
:scheme , :host , :port , :app_id , :key , :secret , :notification_host , :notification_scheme
56
58
)
57
59
60
+ if options . has_key? ( :encryption_master_key_base64 )
61
+ @encryption_master_key =
62
+ Base64 . decode64 ( options [ :encryption_master_key_base64 ] )
63
+ end
64
+
58
65
@http_proxy = nil
59
66
self . http_proxy = options [ :http_proxy ] if options [ :http_proxy ]
60
67
@@ -138,6 +145,12 @@ def timeout=(value)
138
145
@connect_timeout , @send_timeout , @receive_timeout = value , value , value
139
146
end
140
147
148
+ # Set an encryption_master_key to use with private-encrypted channels from
149
+ # a base64 encoded string.
150
+ def encryption_master_key_base64 = ( s )
151
+ @encryption_master_key = s ? Base64 . decode64 ( s ) : nil
152
+ end
153
+
141
154
## INTERACT WITH THE API ##
142
155
143
156
def resource ( path )
@@ -413,10 +426,17 @@ def trigger_params(channels, event_name, data, params)
413
426
channels = Array ( channels ) . map ( &:to_s )
414
427
raise Pusher ::Error , "Too many channels (#{ channels . length } ), max 10" if channels . length > 10
415
428
429
+ encoded_data = if channels . any? ( /^private-encrypted-/ ) then
430
+ raise Pusher ::Error , "Cannot trigger to multiple channels if any are encrypted" if channels . length > 1
431
+ encrypt ( channels [ 0 ] , encode_data ( data ) )
432
+ else
433
+ encode_data ( data )
434
+ end
435
+
416
436
params . merge ( {
417
437
name : event_name ,
418
438
channels : channels ,
419
- data : encode_data ( data ) ,
439
+ data : encoded_data ,
420
440
} )
421
441
end
422
442
@@ -436,6 +456,28 @@ def encode_data(data)
436
456
MultiJson . encode ( data )
437
457
end
438
458
459
+ # Encrypts a message with a key derived from the master key and channel
460
+ # name
461
+ def encrypt ( channel , encoded_data )
462
+ raise ConfigurationError , :encryption_master_key unless @encryption_master_key
463
+
464
+ # Only now load rbnacl, so that people that aren't using it don't need to
465
+ # install libsodium
466
+ require 'rbnacl'
467
+
468
+ secret_box = RbNaCl ::SecretBox . new (
469
+ RbNaCl ::Hash . sha256 ( channel + @encryption_master_key )
470
+ )
471
+
472
+ nonce = RbNaCl ::Random . random_bytes ( secret_box . nonce_bytes )
473
+ ciphertext = secret_box . encrypt ( nonce , encoded_data )
474
+
475
+ MultiJson . encode ( {
476
+ "nonce" => Base64 ::encode64 ( nonce ) ,
477
+ "ciphertext" => Base64 ::encode64 ( ciphertext ) ,
478
+ } )
479
+ end
480
+
439
481
def configured?
440
482
host && scheme && key && secret && app_id
441
483
end
0 commit comments