aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cryptography/fernet.py32
-rw-r--r--tests/test_fernet.py28
-rw-r--r--tests/vectors/fernet/invalid.json58
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="
+ }
+]