@@ -82,6 +82,11 @@ class WebPusher:
8282
8383 """
8484 subscription_info = {}
85+ valid_encodings = [
86+ # "aesgcm128", # this is draft-0, but DO NOT USE.
87+ "aesgcm" , # draft-httpbis-encryption-encoding-01
88+ "aes128gcm" # draft-httpbis-encryption-encoding-04
89+ ]
8590
8691 def __init__ (self , subscription_info ):
8792 """Initialize using the info provided by the client PushSubscription
@@ -113,16 +118,28 @@ def _repad(self, data):
113118 """Add base64 padding to the end of a string, if required"""
114119 return data + b"====" [:len (data ) % 4 ]
115120
116- def encode (self , data ):
121+ def encode (self , data , content_encoding = "aesgcm" ):
117122 """Encrypt the data.
118123
119124 :param data: A serialized block of byte data (String, JSON, bit array,
120125 etc.) Make sure that whatever you send, your client knows how
121126 to understand it.
127+ :type data: str
128+ :param content_encoding: The content_encoding type to use to encrypt
129+ the data. Defaults to draft-01 "aesgcm". Latest draft-04 is
130+ "aes128gcm", however not all clients may be able to use this
131+ format.
132+ :type content_encoding: enum("aesgcm", "aes128gcm")
122133
123134 """
124135 # Salt is a random 16 byte array.
125- salt = os .urandom (16 )
136+ salt = None
137+ if content_encoding not in self .valid_encodings :
138+ raise WebPushException ("Invalid content encoding specified. "
139+ "Select from " +
140+ json .dumps (self .valid_encodings ))
141+ if (content_encoding == "aesgcm" ):
142+ salt = os .urandom (16 )
126143 # The server key is an ephemeral ECDH key used only for this
127144 # transaction
128145 server_key = pyelliptic .ECC (curve = "prime256v1" )
@@ -133,26 +150,31 @@ def encode(self, data):
133150 if isinstance (data , six .string_types ):
134151 data = bytes (data .encode ('utf8' ))
135152
153+ key_id = server_key_id .decode ('utf8' )
136154 # http_ece requires that these both be set BEFORE encrypt or
137155 # decrypt is called if you specify the key as "dh".
138- http_ece .keys [server_key_id ] = server_key
139- http_ece .labels [server_key_id ] = "P-256"
156+ http_ece .keys [key_id ] = server_key
157+ http_ece .labels [key_id ] = "P-256"
140158
141159 encrypted = http_ece .encrypt (
142160 data ,
143161 salt = salt ,
144- keyid = server_key_id ,
162+ keyid = key_id ,
145163 dh = self .receiver_key ,
146- authSecret = self .auth_key )
164+ authSecret = self .auth_key ,
165+ version = content_encoding )
147166
148- return CaseInsensitiveDict ({
167+ reply = CaseInsensitiveDict ({
149168 'crypto_key' : base64 .urlsafe_b64encode (
150169 server_key .get_pubkey ()).strip (b'=' ),
151- 'salt' : base64 .urlsafe_b64encode (salt ).strip (b'=' ),
152170 'body' : encrypted ,
153171 })
172+ if salt :
173+ reply ['salt' ] = base64 .urlsafe_b64encode (salt ).strip (b'=' )
174+ return reply
154175
155- def send (self , data , headers = None , ttl = 0 , gcm_key = None , reg_id = None ):
176+ def send (self , data = None , headers = None , ttl = 0 , gcm_key = None , reg_id = None ,
177+ content_encoding = "aesgcm" ):
156178 """Encode and send the data to the Push Service.
157179
158180 :param data: A serialized block of data (see encode() ).
@@ -169,22 +191,25 @@ def send(self, data, headers=None, ttl=0, gcm_key=None, reg_id=None):
169191 # Encode the data.
170192 if headers is None :
171193 headers = dict ()
172- encoded = self .encode (data )
173- # Append the p256dh to the end of any existing crypto-key
194+ encoded = {}
174195 headers = CaseInsensitiveDict (headers )
175- crypto_key = headers .get ("crypto-key" , "" )
176- if crypto_key :
177- # due to some confusion by a push service provider, we should
178- # use ';' instead of ',' to append the headers.
179- # see https://github.com/webpush-wg/webpush-encryption/issues/6
180- crypto_key += ';'
181- crypto_key += "keyid=p256dh;dh=" + encoded ["crypto_key" ].decode ('utf8' )
182- headers .update ({
183- 'crypto-key' : crypto_key ,
184- 'content-encoding' : 'aesgcm' ,
185- 'encryption' : "keyid=p256dh;salt=" +
186- encoded ['salt' ].decode ('utf8' ),
187- })
196+ if data :
197+ encoded = self .encode (data )
198+ # Append the p256dh to the end of any existing crypto-key
199+ crypto_key = headers .get ("crypto-key" , "" )
200+ if crypto_key :
201+ # due to some confusion by a push service provider, we should
202+ # use ';' instead of ',' to append the headers.
203+ # see https://github.com/webpush-wg/webpush-encryption/issues/6
204+ crypto_key += ';'
205+ crypto_key += (
206+ "keyid=p256dh;dh=" + encoded ["crypto_key" ].decode ('utf8' ))
207+ headers .update ({
208+ 'crypto-key' : crypto_key ,
209+ 'content-encoding' : 'aesgcm' ,
210+ 'encryption' : "keyid=p256dh;salt=" +
211+ encoded ['salt' ].decode ('utf8' ),
212+ })
188213 gcm_endpoint = 'https://android.googleapis.com/gcm/send'
189214 if self .subscription_info ['endpoint' ].startswith (gcm_endpoint ):
190215 if not gcm_key :
@@ -194,12 +219,14 @@ def send(self, data, headers=None, ttl=0, gcm_key=None, reg_id=None):
194219 if not reg_id :
195220 reg_id = self .subscription_info ['endpoint' ].rsplit ('/' , 1 )[- 1 ]
196221 reg_ids .append (reg_id )
197- data = dict ()
198- data ['registration_ids' ] = reg_ids
199- data ['raw_data' ] = base64 .b64encode (
200- encoded .get ('body' )).decode ('utf8' )
201- data ['time_to_live' ] = int (headers ['ttl' ] if 'ttl' in headers else ttl )
202- encoded_data = json .dumps (data )
222+ gcm_data = dict ()
223+ gcm_data ['registration_ids' ] = reg_ids
224+ if data :
225+ gcm_data ['raw_data' ] = base64 .b64encode (
226+ encoded .get ('body' )).decode ('utf8' )
227+ gcm_data ['time_to_live' ] = int (
228+ headers ['ttl' ] if 'ttl' in headers else ttl )
229+ encoded_data = json .dumps (gcm_data )
203230 headers .update ({
204231 'Authorization' : 'key=' + gcm_key ,
205232 'Content-Type' : 'application/json' ,
0 commit comments