diff options
author | Gabriel Gironda <gabriel@thegroundwork.com> | 2015-07-21 17:19:49 -0400 |
---|---|---|
committer | Gabriel Gironda <gabriel@thegroundwork.com> | 2015-07-21 17:19:49 -0400 |
commit | 91fe6cd68978620845e8c56f17348d0d8e042246 (patch) | |
tree | acdd036346bb4ec2cb926d699def14ec8d97acf0 | |
parent | 96c029a89f6311305ff93104db49c3c377072e1d (diff) | |
download | pyjwt-91fe6cd68978620845e8c56f17348d0d8e042246.tar.gz |
Fail on encode and decode of bad JWS header values
The JWS spec:
https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-4.1.4
States that if `kid` is present then it **MUST** be a string.
Currently, the library allows silent creation of invalid JWS (and
thus, JWT), as it allows any type for `kid`. This commit adds checks
to help ensure output meets the spec.
* Add jwt.api_jws.PyJWS._validate_headers for validating JWS headers
on encode and decode
* Add tests
-rw-r--r-- | jwt/api_jws.py | 15 | ||||
-rw-r--r-- | tests/test_api_jws.py | 27 |
2 files changed, 38 insertions, 4 deletions
diff --git a/jwt/api_jws.py b/jwt/api_jws.py index 0c61c7d..a763638 100644 --- a/jwt/api_jws.py +++ b/jwt/api_jws.py @@ -5,7 +5,7 @@ import warnings from collections import Mapping from .algorithms import Algorithm, get_default_algorithms # NOQA -from .compat import text_type +from .compat import string_types, text_type from .exceptions import DecodeError, InvalidAlgorithmError from .utils import base64url_decode, base64url_encode, merge_dict @@ -79,6 +79,7 @@ class PyJWS(object): header = {'typ': self.header_typ, 'alg': algorithm} if headers: + self._validate_headers(headers) header.update(headers) json_header = json.dumps( @@ -125,7 +126,10 @@ class PyJWS(object): Note: The signature is not verified so the header parameters should not be fully trusted until signature verification is complete """ - return self._load(jwt)[2] + headers = self._load(jwt)[2] + self._validate_headers(headers) + + return headers def _load(self, jwt): if isinstance(jwt, text_type): @@ -180,6 +184,13 @@ class PyJWS(object): except KeyError: raise InvalidAlgorithmError('Algorithm not supported') + def _validate_headers(self, headers): + if 'kid' in headers: + self._validate_kid(headers['kid']) + + def _validate_kid(self, kid): + if not isinstance(kid, string_types): + raise TypeError('Key ID header parameter must be a string') _jws_global_obj = PyJWS() encode = _jws_global_obj.encode diff --git a/tests/test_api_jws.py b/tests/test_api_jws.py index 9aa8b85..6b4b881 100644 --- a/tests/test_api_jws.py +++ b/tests/test_api_jws.py @@ -367,12 +367,24 @@ class TestJWS: def test_get_unverified_header_returns_header_values(self, jws, payload): jws_message = jws.encode(payload, key='secret', algorithm='HS256', - headers={'kid': 123}) + headers={'kid': 'toomanysecrets'}) header = jws.get_unverified_header(jws_message) assert 'kid' in header - assert header['kid'] == 123 + assert header['kid'] == 'toomanysecrets' + + def test_get_unverified_header_fails_on_bad_header_types(self, jws, payload): + # Contains a bad kid value (int 123 instead of string) + example_jws = ( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6MTIzfQ' + '.eyJzdWIiOiIxMjM0NTY3ODkwIn0' + '.vs2WY54jfpKP3JGC73Vq5YlMsqM5oTZ1ZydT77SiZSk') + + with pytest.raises(TypeError) as exc: + jws.get_unverified_header(example_jws) + + assert 'Key ID header parameter must be a string' == str(exc.value) @pytest.mark.skipif(not has_crypto, reason='Not supported without cryptography library') def test_encode_decode_with_rsa_sha256(self, jws, payload): @@ -597,3 +609,14 @@ class TestJWS: assert 'testheader' in header_obj assert header_obj['testheader'] == headers['testheader'] + + def test_encode_fails_on_invalid_kid_types(self, jws, payload): + with pytest.raises(TypeError) as exc: + jws.encode(payload, 'secret', headers={'kid': 123}) + + assert 'Key ID header parameter must be a string' == str(exc.value) + + with pytest.raises(TypeError) as exc: + jws.encode(payload, 'secret', headers={'kid': None}) + + assert 'Key ID header parameter must be a string' == str(exc.value) |