Skip to content

Commit ac3322f

Browse files
committed
feat: update to http-ece 0.6.4 (with draft-06 support)
use new "content_type" argument to specify either "aesgcm" (draft-01) or "aes128gcm" (draft-04). NOTE: Not all clients yet support Draft-04. closes #33
1 parent 0e3af3c commit ac3322f

File tree

5 files changed

+76
-32
lines changed

5 files changed

+76
-32
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 0.7.0 (2017-02-14)
2+
feat: update to http-ece 0.7.0 (with draft-06 support)
3+
feat: Allow empty payloads for send()
4+
feat: Add python3 classfiers & python3.6 travis tests
5+
feat: Add README.rst
6+
bug: change long to int to support python3
7+
18
## 0.4.0 (2016-06-05)
29
feat: make python 2.7 / 3.5 polyglot
310

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
[![Build_Status](https://travis-ci.org/jrconlin/pywebpush.svg?branch=master)](https://travis-ci.org/jrconlin/pywebpush)
2+
[![Requirements
3+
Status](https://requires.io/github/web-push-libs/pywebpush/requirements.svg?branch=master)]
4+
25

36
# Webpush Data encryption library for Python
47

pywebpush/__init__.py

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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=None, 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() ).

pywebpush/tests/test_webpush.py

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -65,32 +65,44 @@ def test_init(self):
6565
eq_(push.auth_key, b'\x93\xc2U\xea\xc8\xddn\x10"\xd6}\xff,0K\xbc')
6666

6767
def test_encode(self):
68+
for content_encoding in ["aesgcm", "aes128gcm"]:
69+
recv_key = pyelliptic.ECC(curve="prime256v1")
70+
subscription_info = self._gen_subscription_info(recv_key)
71+
data = "Mary had a little lamb, with some nice mint jelly"
72+
push = WebPusher(subscription_info)
73+
encoded = push.encode(data, content_encoding=content_encoding)
74+
keyid = base64.urlsafe_b64encode(recv_key.get_pubkey()[1:])
75+
http_ece.keys[keyid] = recv_key
76+
http_ece.labels[keyid] = 'P-256'
77+
# Convert these b64 strings into their raw, binary form.
78+
raw_salt = None
79+
if 'salt' in encoded:
80+
raw_salt = base64.urlsafe_b64decode(
81+
push._repad(encoded['salt']))
82+
raw_dh = base64.urlsafe_b64decode(
83+
push._repad(encoded['crypto_key']))
84+
raw_auth = base64.urlsafe_b64decode(
85+
push._repad(subscription_info['keys']['auth']))
86+
87+
decoded = http_ece.decrypt(
88+
encoded['body'],
89+
salt=raw_salt,
90+
dh=raw_dh,
91+
keyid=keyid,
92+
authSecret=raw_auth,
93+
version=content_encoding
94+
)
95+
eq_(decoded.decode('utf8'), data)
96+
97+
def test_bad_content_encoding(self):
6898
recv_key = pyelliptic.ECC(curve="prime256v1")
6999
subscription_info = self._gen_subscription_info(recv_key)
70100
data = "Mary had a little lamb, with some nice mint jelly"
71101
push = WebPusher(subscription_info)
72-
encoded = push.encode(data)
73-
74-
keyid = base64.urlsafe_b64encode(recv_key.get_pubkey()[1:])
75-
76-
http_ece.keys[keyid] = recv_key
77-
http_ece.labels[keyid] = 'P-256'
78-
79-
# Convert these b64 strings into their raw, binary form.
80-
raw_salt = base64.urlsafe_b64decode(push._repad(encoded['salt']))
81-
raw_dh = base64.urlsafe_b64decode(push._repad(encoded['crypto_key']))
82-
raw_auth = base64.urlsafe_b64decode(
83-
push._repad(subscription_info['keys']['auth']))
84-
85-
decoded = http_ece.decrypt(
86-
encoded['body'],
87-
salt=raw_salt,
88-
dh=raw_dh,
89-
keyid=keyid,
90-
authSecret=raw_auth,
91-
)
92-
93-
eq_(decoded.decode('utf8'), data)
102+
self.assertRaises(WebPushException,
103+
push.encode,
104+
data,
105+
content_encoding="aesgcm128")
94106

95107
@patch("requests.post")
96108
def test_send(self, mock_post):

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from setuptools import find_packages, setup
55

6-
__version__ = "0.6.3"
6+
__version__ = "0.7.0"
77

88

99
def read_from(file):

0 commit comments

Comments
 (0)