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

Commit

Permalink
feat: add thorough jwt exp validation
Browse files Browse the repository at this point in the history
We now attempt to coerce the jwt exp value in case it wasn't an int
and verify that it is within the next 24 hours and has not already
expired.

Closes #794
  • Loading branch information
bbangert committed Feb 9, 2017
1 parent 26a4124 commit 97d4213
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 1 deletion.
64 changes: 64 additions & 0 deletions autopush/tests/test_web_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,70 @@ def test_invalid_vapid_crypto_header(self, mock_jwt):
eq_(cm.exception.status_code, 401)
eq_(cm.exception.errno, 109)

def test_invalid_too_far_exp_vapid_crypto_header(self):
schema = self._make_fut()
header = {"typ": "JWT", "alg": "ES256"}
payload = {"aud": "https://pusher_origin.example.com",
"exp": int(time.time()) + 86400 + 86400,
"sub": "mailto:admin@example.com"}

token, crypto_key = self._gen_jwt(header, payload)
auth = "WebPush %s" % token
self.fernet_mock.decrypt.return_value = ('a'*32) + \
sha256(utils.base64url_decode(crypto_key)).digest()
ckey = 'keyid="a1"; dh="foo";p256ecdsa="%s"' % crypto_key
info = self._make_test_data(
body="asdfasdfasdfasdf",
path_kwargs=dict(
api_ver="v2",
token="asdfasdf",
),
headers={
"content-encoding": "aesgcm",
"encryption": "salt=stuff",
"authorization": auth,
"crypto-key": ckey
}
)

with assert_raises(InvalidRequest) as cm:
schema.load(info)

eq_(cm.exception.status_code, 401)
eq_(cm.exception.errno, 109)

def test_invalid_bad_exp_vapid_crypto_header(self):
schema = self._make_fut()
header = {"typ": "JWT", "alg": "ES256"}
payload = {"aud": "https://pusher_origin.example.com",
"exp": "bleh",
"sub": "mailto:admin@example.com"}

token, crypto_key = self._gen_jwt(header, payload)
auth = "WebPush %s" % token
self.fernet_mock.decrypt.return_value = ('a'*32) + \
sha256(utils.base64url_decode(crypto_key)).digest()
ckey = 'keyid="a1"; dh="foo";p256ecdsa="%s"' % crypto_key
info = self._make_test_data(
body="asdfasdfasdfasdf",
path_kwargs=dict(
api_ver="v2",
token="asdfasdf",
),
headers={
"content-encoding": "aesgcm",
"encryption": "salt=stuff",
"authorization": auth,
"crypto-key": ckey
}
)

with assert_raises(InvalidRequest) as cm:
schema.load(info)

eq_(cm.exception.status_code, 401)
eq_(cm.exception.errno, 109)

@patch("autopush.web.webpush.extract_jwt")
def test_invalid_encryption_header(self, mock_jwt):
schema = self._make_fut()
Expand Down
22 changes: 21 additions & 1 deletion autopush/web/webpush.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,10 +325,30 @@ def validate_auth(self, d):
raise InvalidRequest("Invalid Authorization Header",
status_code=401, errno=109,
headers={"www-authenticate": PREF_SCHEME})
if jwt.get('exp', 0) < time.time():
if "exp" not in jwt:
raise InvalidRequest("Invalid bearer token: No expiration",
status_code=401, errno=109,
headers={"www-authenticate": PREF_SCHEME})

try:
jwt_expires = int(jwt['exp'])
except ValueError:
raise InvalidRequest("Invalid bearer token: Invalid expiration",
status_code=401, errno=109,
headers={"www-authenticate": PREF_SCHEME})

now = time.time()
jwt_has_expired = now > jwt_expires
if jwt_has_expired:
raise InvalidRequest("Invalid bearer token: Auth expired",
status_code=401, errno=109,
headers={"www-authenticate": PREF_SCHEME})
jwt_too_far_in_future = (jwt_expires - now) > (60*60*24)
if jwt_too_far_in_future:
raise InvalidRequest("Invalid bearer token: Auth > 24 hours in "
"the future",
status_code=401, errno=109,
headers={"www-authenticate": PREF_SCHEME})
jwt_crypto_key = base64url_encode(public_key)
d["jwt"] = dict(jwt_crypto_key=jwt_crypto_key, jwt_data=jwt)

Expand Down

0 comments on commit 97d4213

Please sign in to comment.