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

Commit

Permalink
bug: Normalize padding handling for restricted subscriptions
Browse files Browse the repository at this point in the history
Since fernet passes it's content through base64 handling, it's just as
picky about padding as other base64 instances. For now, strip padding
from outbound endpoints and reapply it (if needed) to inbound endpoints.

Closes #466
  • Loading branch information
jrconlin committed May 11, 2016
1 parent 7eba4e9 commit 17e885b
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 10 deletions.
14 changes: 9 additions & 5 deletions autopush/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@
SimpleRouter,
WebPushRouter,
)
from autopush.utils import canonical_url, resolve_ip, base64url_decode
from autopush.utils import (
canonical_url,
resolve_ip,
repad,
base64url_decode
)
from autopush.senderids import SENDERID_EXPRY, DEFAULT_BUCKET
from autopush.crypto_key import (CryptoKey, CryptoKeyException)

Expand Down Expand Up @@ -303,8 +308,8 @@ def make_endpoint(self, uaid, chid, key=None):
return root + 'v1/' + self.fernet.encrypt(base).strip('=')

raw_key = base64url_decode(key.encode('utf8'))
return root + 'v2/' + self.fernet.encrypt(base +
sha256(raw_key).digest())
ep = self.fernet.encrypt(base + sha256(raw_key).digest()).strip('=')
return root + 'v2/' + ep

def parse_endpoint(self, token, version="v0", ckey_header=None):
"""Parse an endpoint into component elements of UAID, CHID and optional
Expand All @@ -320,8 +325,7 @@ def parse_endpoint(self, token, version="v0", ckey_header=None):
:returns: a dict containing (uaid=UAID, chid=CHID, public_key=KEY)
"""

token = self.fernet.decrypt(token.encode('utf8'))
token = self.fernet.decrypt(repad(token).encode('utf8'))
public_key = None
if ckey_header:
try:
Expand Down
27 changes: 26 additions & 1 deletion autopush/tests/test_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import time
import uuid
import random
import base64

from hashlib import sha256

Expand Down Expand Up @@ -178,6 +179,7 @@ def setUp(self, t):
hostname="localhost",
statsd_host=None,
)
self.old_fernet = settings.fernet
self.fernet_mock = settings.fernet = Mock(spec=Fernet)
self.metrics_mock = settings.metrics = Mock(spec=Metrics)
self.agent_mock = settings.agent = Mock(spec=Agent)
Expand Down Expand Up @@ -775,7 +777,7 @@ def _gen_jwt(self, header, payload):
sk256p = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p)
vk = sk256p.get_verifying_key()
sig = jws.sign(payload, sk256p, algorithm="ES256").strip('=')
crypto_key = utils.base64url_encode(vk.to_string())
crypto_key = utils.base64url_encode(vk.to_string()).strip('=')
return (sig, crypto_key)

def test_post_webpush_with_vapid_auth(self):
Expand Down Expand Up @@ -1230,6 +1232,29 @@ def test_padding(self):
eq_(utils.base64url_decode("abc="), "\x69\xb7")
eq_(utils.base64url_decode("abcd"), "\x69\xb7\x1d")

def test_v2_padded_fernet_decode(self):
# Generate a previously valid URL that would cause the #466 issue
self.endpoint.ap_settings.fernet = self.old_fernet
# stripped, but valid crypto_key
dummy_key = ("BHolMkL36ucQsRe_0KRS70JyHB55H4C5Igv2YQEVNzCILN"
"nedxFHSPtzI4KhzNtN2YPqHe7-mWW6_uvaIc5yEDk")
# Generate an endpoint, since the fernet key is not locked during
# testing.
while True:
ep = self.endpoint.ap_settings.make_endpoint(
dummy_uaid,
dummy_chid,
dummy_key)
token = ep.split("/")[-1]
if len(token) % 4:
break
reply = self.settings.parse_endpoint(token, "v2",
"p256ecdsa=" + dummy_key)
eq_(reply, {'public_key': base64.urlsafe_b64decode(
utils.repad(dummy_key)),
'chid': dummy_chid.replace('-', ''),
'uaid': dummy_uaid.replace('-', '')})

def test_parse_endpoint(self):
v0_valid = dummy_uaid + ":" + dummy_chid
uaid_strip = dummy_uaid.replace('-', '')
Expand Down
1 change: 0 additions & 1 deletion autopush/tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -1301,7 +1301,6 @@ def test_with_key(self):
yield client.connect()
yield client.hello()
yield client.register(chid=chid, key=pk_hex)
# check that the client actually registered the key.

# Send an update with a properly formatted key.
yield client.send_notification(vapid=vapid)
Expand Down
12 changes: 9 additions & 3 deletions autopush/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,22 @@ def base64url_encode(string):
return base64.urlsafe_b64encode(string).strip('=')


def repad(string):
"""Adds padding to strings for base64 decoding"""

if len(string) % 4:
string = string + '===='[len(string) % 4:]
return string


def base64url_decode(string):
"""Decodes a Base64 URL-encoded string per RFC 7515.
RFC 7515 (used for Encrypted Content-Encoding and JWT) requires unpadded
encoded strings, but Python's ``urlsafe_b64decode`` only accepts padded
strings.
"""
if len(string) % 4:
string = string + '===='[len(string) % 4:]
return base64.urlsafe_b64decode(string)
return base64.urlsafe_b64decode(repad(string))


def decipher_public_key(key_data):
Expand Down

0 comments on commit 17e885b

Please sign in to comment.