diff options
author | Alex Gaynor <alex.gaynor@gmail.com> | 2013-10-31 14:50:00 -0700 |
---|---|---|
committer | Alex Gaynor <alex.gaynor@gmail.com> | 2013-10-31 14:50:00 -0700 |
commit | 38f34557e432f98cc8a023e621b5efe525ef886c (patch) | |
tree | 27b90a83eb65c3df4d6c8c9ca874b15abd25d604 | |
parent | fb8adfcb2f0a67519ee81cad0c50d2e359ff3a20 (diff) | |
download | cryptography-38f34557e432f98cc8a023e621b5efe525ef886c.tar.gz cryptography-38f34557e432f98cc8a023e621b5efe525ef886c.tar.bz2 cryptography-38f34557e432f98cc8a023e621b5efe525ef886c.zip |
Started working on the invalid cases
-rw-r--r-- | cryptography/fernet.py | 32 | ||||
-rw-r--r-- | tests/test_fernet.py | 28 | ||||
-rw-r--r-- | tests/vectors/fernet/invalid.json | 58 |
3 files changed, 107 insertions, 11 deletions
diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 064aceec..880d96f0 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -10,6 +10,10 @@ from cryptography.hazmat.primitives.hmac import HMAC from cryptography.hazmat.primitives.block import BlockCipher, ciphers, modes +class InvalidToken(Exception): + pass + + class Fernet(object): def __init__(self, key): super(Fernet, self).__init__() @@ -23,6 +27,9 @@ class Fernet(object): return self._encrypt_from_parts(data, current_time, iv) def _encrypt_from_parts(self, data, current_time, iv): + if isinstance(data, six.text_type): + raise TypeError("Unicode-objects must be encoded before encryption") + padder = padding.PKCS7(ciphers.AES.block_size).padder() padded_data = padder.update(data) + padder.finalize() encryptor = BlockCipher( @@ -41,28 +48,43 @@ class Fernet(object): ) def decrypt(self, data, ttl=None, current_time=None): - # TODO: whole function is a giant hack job with no error checking + if isinstance(data, six.text_type): + raise TypeError("Unicode-objects must be encoded before decryption") + if current_time is None: current_time = int(time.time()) - data = base64.urlsafe_b64decode(data) + + try: + data = base64.urlsafe_b64decode(data) + except TypeError: + raise InvalidToken + assert six.indexbytes(data, 0) == 0x80 timestamp = data[1:9] iv = data[9:25] ciphertext = data[25:-32] if ttl is not None: if struct.unpack(">Q", timestamp)[0] + ttl < current_time: - raise ValueError + raise InvalidToken h = HMAC(self.signing_key, digestmod=hashes.SHA256) h.update(data[:-32]) hmac = h.digest() + if not constant_time_compare(hmac, data[-32:]): - raise ValueError + raise InvalidToken + decryptor = BlockCipher( ciphers.AES(self.encryption_key), modes.CBC(iv) ).decryptor() plaintext_padded = decryptor.update(ciphertext) + decryptor.finalize() unpadder = padding.PKCS7(ciphers.AES.block_size).unpadder() - return unpadder.update(plaintext_padded) + unpadder.finalize() + + unpadded = unpadder.update(plaintext_padded) + try: + unpadded += unpadder.finalize() + except ValueError: + raise InvalidToken + return unpadded def constant_time_compare(a, b): diff --git a/tests/test_fernet.py b/tests/test_fernet.py index 382a232c..15071718 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -9,10 +9,11 @@ import pytest import six -from cryptography.fernet import Fernet +from cryptography.fernet import Fernet, InvalidToken -def json_parametrize(keys, path): +def json_parametrize(keys, fname): + path = os.path.join(os.path.dirname(__file__), "vectors", "fernet", fname) with open(path) as f: data = json.load(f) return pytest.mark.parametrize(keys, [ @@ -23,8 +24,7 @@ def json_parametrize(keys, path): class TestFernet(object): @json_parametrize( - ("secret", "now", "iv", "src", "token"), - os.path.join(os.path.dirname(__file__), "vectors", "fernet", "generate.json") + ("secret", "now", "iv", "src", "token"), "generate.json", ) def test_generate(self, secret, now, iv, src, token): f = Fernet(base64.urlsafe_b64decode(secret.encode("ascii"))) @@ -36,8 +36,7 @@ class TestFernet(object): assert actual_token == token @json_parametrize( - ("secret", "now", "src", "ttl_sec", "token"), - os.path.join(os.path.dirname(__file__), "vectors", "fernet", "verify.json") + ("secret", "now", "src", "ttl_sec", "token"), "verify.json", ) def test_verify(self, secret, now, src, ttl_sec, token): f = Fernet(base64.urlsafe_b64decode(secret.encode("ascii"))) @@ -47,3 +46,20 @@ class TestFernet(object): current_time=calendar.timegm(iso8601.parse_date(now).utctimetuple()) ) assert payload == src + + @json_parametrize(("secret", "token", "now", "ttl_sec"), "invalid.json") + def test_invalid(self, secret, token, now, ttl_sec): + f = Fernet(base64.urlsafe_b64decode(secret.encode("ascii"))) + with pytest.raises(InvalidToken): + f.decrypt( + token.encode("ascii"), + ttl=ttl_sec, + current_time=calendar.timegm(iso8601.parse_date(now).utctimetuple()) + ) + + def test_unicode(self): + f = Fernet(b"\x00" * 32) + with pytest.raises(TypeError): + f.encrypt(six.u("")) + with pytest.raises(TypeError): + f.decrypt(six.u("")) diff --git a/tests/vectors/fernet/invalid.json b/tests/vectors/fernet/invalid.json new file mode 100644 index 00000000..d80e7b4a --- /dev/null +++ b/tests/vectors/fernet/invalid.json @@ -0,0 +1,58 @@ +[ + { + "desc": "incorrect mac", + "token": "gAAAAAAdwJ6xAAECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPAl1-szkFVzXTuGb4hR8AKtwcaX1YdykQUFBQUFBQUFBQQ==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "too short", + "token": "gAAAAAAdwJ6xAAECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPA==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "invalid base64", + "token": "%%%%%%%%%%%%%AECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPAl1-szkFVzXTuGb4hR8AKtwcaX1YdykRtfsH-p1YsUD2Q==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "payload size not multiple of block size", + "token": "gAAAAAAdwJ6xAAECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPOm73QeoCk9uGib28Xe5vz6oxq5nmxbx_v7mrfyudzUm", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "payload padding error", + "token": "gAAAAAAdwJ6xAAECAwQFBgcICQoLDA0ODz4LEpdELGQAad7aNEHbf-JkLPIpuiYRLQ3RtXatOYREu2FWke6CnJNYIbkuKNqOhw==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "far-future TS (unacceptable clock skew)", + "token": "gAAAAAAdwStRAAECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPAnja1xKYyhd-Y6mSkTOyTGJmw2Xc2a6kBd-iX9b_qXQcw==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "expired TTL", + "token": "gAAAAAAdwJ6xAAECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPAl1-szkFVzXTuGb4hR8AKtwcaX1YdykRtfsH-p1YsUD2Q==", + "now": "1985-10-26T01:21:31-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + }, + { + "desc": "incorrect IV (causes padding error)", + "token": "gAAAAAAdwJ6xBQECAwQFBgcICQoLDA0OD3HkMATM5lFqGaerZ-fWPAkLhFLHpGtDBRLRTZeUfWgHSv49TF2AUEZ1TIvcZjK1zQ==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + } +] |