Skip to content
This repository has been archived by the owner on Jul 13, 2023. It is now read-only.

feat: accept aes128gcm content encoding #932

Merged
merged 1 commit into from
Jun 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion autopush/router/fcm.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,9 @@ def _route(self, notification, router_data):

data['body'] = notification.data
data['con'] = notification.headers['encoding']
data['enc'] = notification.headers['encryption']

if 'encryption' in notification.headers:
data['enc'] = notification.headers['encryption']
if 'crypto_key' in notification.headers:
data['cryptokey'] = notification.headers['crypto_key']
elif 'encryption_key' in notification.headers:
Expand Down
3 changes: 2 additions & 1 deletion autopush/router/gcm.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ def _route(self, notification, uaid_data):

data['body'] = notification.data
data['con'] = notification.headers['encoding']
data['enc'] = notification.headers['encryption']

if 'encryption' in notification.headers:
data['enc'] = notification.headers.get('encryption')
if 'crypto_key' in notification.headers:
data['cryptokey'] = notification.headers['crypto_key']
elif 'encryption_key' in notification.headers:
Expand Down
94 changes: 94 additions & 0 deletions autopush/tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -1865,6 +1865,100 @@ def test_registration(self):
eq_(ca_data['enc'], salt)
eq_(ca_data['body'], base64url_encode(data))

@inlineCallbacks
def test_registration_aes128gcm(self):
self._add_router()
# get the senderid
url = "{}/v1/{}/{}/registration".format(
self.ep.settings.endpoint_url,
"gcm",
self.senderID,
)
response, body = yield _agent('POST', url, body=json.dumps(
{"chid": str(uuid.uuid4()),
"token": uuid.uuid4().hex,
}
))
eq_(response.code, 200)
jbody = json.loads(body)

# Send a fake message
data = ("\xa2\xa5\xbd\xda\x40\xdc\xd1\xa5\xf9\x6a\x60\xa8\x57\x7b\x48"
"\xe4\x43\x02\x5a\x72\xe0\x64\x69\xcd\x29\x6f\x65\x44\x53\x78"
"\xe1\xd9\xf6\x46\x26\xce\x69")
content_encoding = "aes128gcm"

response, body = yield _agent(
'POST',
str(jbody['endpoint']),
headers=Headers({
"ttl": ["0"],
"content-encoding": [content_encoding],
}),
body=data
)

ca_data = self._mock_send.call_args[0][0].data
eq_(response.code, 201)
# ChannelID here MUST match what we got from the registration call.
# Currently, this is a lowercase, hex UUID without dashes.
eq_(ca_data['chid'], jbody['channelID'])
eq_(ca_data['con'], content_encoding)
eq_(ca_data['body'], base64url_encode(data))
ok_('enc' not in ca_data)

@inlineCallbacks
def test_registration_aes128gcm_bad_(self):
self._add_router()
# get the senderid
url = "{}/v1/{}/{}/registration".format(
self.ep.settings.endpoint_url,
"gcm",
self.senderID,
)
response, body = yield _agent('POST', url, body=json.dumps(
{"chid": str(uuid.uuid4()),
"token": uuid.uuid4().hex,
}
))
eq_(response.code, 200)
jbody = json.loads(body)

# Send a fake message
data = ("\xa2\xa5\xbd\xda\x40\xdc\xd1\xa5\xf9\x6a\x60\xa8\x57\x7b\x48"
"\xe4\x43\x02\x5a\x72\xe0\x64\x69\xcd\x29\x6f\x65\x44\x53\x78"
"\xe1\xd9\xf6\x46\x26\xce\x69")
crypto_key = ("keyid=p256dh;dh=BAFJxCIaaWyb4JSkZopERL9MjXBeh3WdBxew"
"SYP0cZWNMJaT7YNaJUiSqBuGUxfRj-9vpTPz5ANmUYq3-u-HWOI")
salt = "keyid=p256dh;salt=S82AseB7pAVBJ2143qtM3A"
content_encoding = "aes128gcm"

response, body = yield _agent(
'POST',
str(jbody['endpoint']),
headers=Headers({
"ttl": ["0"],
"content-encoding": [content_encoding],
"crypto-key": [crypto_key]
}),
body=data
)

eq_(response.code, 400)
ok_("do not include 'dh' in " in body.lower())
response, body = yield _agent(
'POST',
str(jbody['endpoint']),
headers=Headers({
"ttl": ["0"],
"content-encoding": [content_encoding],
"encryption": [salt]
}),
body=data
)
eq_(response.code, 400)
ok_("do not include 'salt' in " in body.lower())

@inlineCallbacks
def test_registration_no_token(self):
self._add_router()
Expand Down
44 changes: 40 additions & 4 deletions autopush/web/webpush.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
validates_schema,
)
from marshmallow_polyfield import PolyField
from marshmallow.validate import OneOf
from marshmallow.validate import Equal
from twisted.logger import Logger # noqa
from twisted.internet.defer import Deferred # noqa
from twisted.internet.defer import maybeDeferred
Expand Down Expand Up @@ -170,7 +170,7 @@ class WebPushCrypto01HeaderSchema(Schema):
content_encoding = fields.String(
required=True,
load_from="content-encoding",
validate=OneOf(["aesgcm128"])
validate=Equal("aesgcm128")
)
encryption = fields.String(required=True)
encryption_key = fields.String(
Expand Down Expand Up @@ -213,13 +213,13 @@ def validate_encryption_key(self, value):
class WebPushCrypto04HeaderSchema(Schema):
"""Validates WebPush Message Encryption

Uses draft-ietf-webpush-encryption-04 rules for validation.
Uses draft-ietf-httpbis-encryption-encoding-04 rules for validation.

"""
content_encoding = fields.String(
required=True,
load_from="content-encoding",
validate=OneOf(["aesgcm"])
validate=Equal("aesgcm")
)
encryption = fields.String(required=True)
crypto_key = fields.String(
Expand Down Expand Up @@ -255,6 +255,40 @@ def reject_encryption_key(self, data, original_data):
)


class WebPushCrypto06HeaderSchema(Schema):
"""Validates WebPush Message Encryption

Uses draft-ietf-httpbis-encryption-encoding-06 rules for validation

"""

content_encoding = fields.String(
required=True,
load_from="content-encoding",
validate=Equal("aes128gcm")
)

encryption = fields.String(required=False)
crypto_key = fields.String(required=False,
load_from="crypto-key")

@validates("encryption")
def validate_encryption(self, value):
if CryptoKey.parse_and_get_label(value, "salt"):
raise InvalidRequest("Do not include 'salt' in aes128gcm "
"Encryption header",
status_code=400,
errno=110)

@validates("crypto_key")
def validate_crypto_key(self, value):
if CryptoKey.parse_and_get_label(value, "dh"):
raise InvalidRequest("Do not include 'dh' in aes128gcm "
"Crypto-Key header",
status_code=400,
errno=110)


class WebPushInvalidContentEncodingSchema(Schema):
"""Returned to raise an Invalid Content-encoding error"""
@validates_schema
Expand All @@ -275,6 +309,8 @@ def conditional_crypto_deserialize(object_dict, parent_object_dict):
return WebPushCrypto01HeaderSchema()
elif encoding == "aesgcm":
return WebPushCrypto04HeaderSchema()
elif encoding == "aes128gcm":
return WebPushCrypto06HeaderSchema()
else:
return WebPushInvalidContentEncodingSchema()
else:
Expand Down