From 02fad008d3e99a49871144b56a692c2237a0d396 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 30 Oct 2013 14:16:13 -0700 Subject: Started implementating encryption for fernet --- cryptography/fernet.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 cryptography/fernet.py diff --git a/cryptography/fernet.py b/cryptography/fernet.py new file mode 100644 index 00000000..a0996afc --- /dev/null +++ b/cryptography/fernet.py @@ -0,0 +1,36 @@ +import base64 +import os +import struct +import time + +from cryptography.hazmat.primitives import padding, hashes +from cryptography.hazmat.primitives.hmac import HMAC +from cryptography.hazmat.primitives.block import BlockCipher, ciphers, modes + + +class Fernet(object): + def __init__(self, key): + super(Fernet, self).__init__() + self.signing_key = key[:16] + self.encryption_key = key[16:] + + def encrypt(self, data): + current_time = int(time.time()) + iv = os.urandom(16) + return self._encrypt_from_parts(data, current_time, iv) + + def _encrypt_from_parts(self, data, current_time, iv): + padder = padding.PKCS7(ciphers.AES.block_size).padder() + padded_data = padder.update(data) + padder.finalize() + encryptor = BlockCipher(ciphers.AES(self.encryption_key), modes.CBC(iv)).encryptor() + ciphertext = encryptor.update(padded_data) + encryptor.finalize() + + h = HMAC(self.signing_key, digestmod=hashes.SHA256) + h.update(b"\x80") + h.update(struct.pack(">Q", current_time)) + h.update(iv) + h.update(ciphertext) + hmac = h.digest() + return base64.urlsafe_b64encode( + b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext + hmac + ) -- cgit v1.2.3 From bbeba7176d77df0ca47e2bad8a4f66915f07609d Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 30 Oct 2013 14:29:58 -0700 Subject: Implemented decryption --- cryptography/fernet.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index a0996afc..549abb36 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -3,6 +3,8 @@ import os import struct import time +import six + from cryptography.hazmat.primitives import padding, hashes from cryptography.hazmat.primitives.hmac import HMAC from cryptography.hazmat.primitives.block import BlockCipher, ciphers, modes @@ -34,3 +36,30 @@ class Fernet(object): return base64.urlsafe_b64encode( b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext + hmac ) + + def decrypt(self, data, ttl=None): + # TODO: whole function is a giant hack job with no error checking + data = base64.urlsafe_b64decode(data) + assert data[0] == b"\x80" + if ttl is not None: + if struct.unpack(">Q", data[1:9])[0] + ttl > int(time.time()): + raise ValueError + 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 + unencryptor = BlockCipher(ciphers.AES(self.encryption_key), modes.CBC(data[9:25])).unencryptor() + plaintext_padded = unencryptor.update(data[25:-32]) + unencryptor.finalize() + unpadder = padding.PKCS7(ciphers.AES.block_size).unpadder() + return unpadder.update(plaintext_padded) + unpadder.finalize() + +def constant_time_compare(a, b): + # TOOD: replace with a cffi function + assert isinstance(a, bytes) and isinstance(b, bytes) + if len(a) != len(b): + return False + result = 0 + for i in xrange(len(a)): + result |= six.indexbytes(a, i) ^ six.indexbytes(b, i) + return result == 0 -- cgit v1.2.3 From f593848419b0d871509df388dadef8c1c98d9a99 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 30 Oct 2013 14:34:55 -0700 Subject: Slightly cleaner --- cryptography/fernet.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 549abb36..59d8ad0c 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -41,16 +41,20 @@ class Fernet(object): # TODO: whole function is a giant hack job with no error checking data = base64.urlsafe_b64decode(data) assert data[0] == b"\x80" + timestamp = data[1:9] + iv = data[9:25] + ciphertext = data[25:-32] + hmac = data[-32:] if ttl is not None: - if struct.unpack(">Q", data[1:9])[0] + ttl > int(time.time()): + if struct.unpack(">Q", timestamp)[0] + ttl > int(time.time()): raise ValueError 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 - unencryptor = BlockCipher(ciphers.AES(self.encryption_key), modes.CBC(data[9:25])).unencryptor() - plaintext_padded = unencryptor.update(data[25:-32]) + unencryptor.finalize() + unencryptor = BlockCipher(ciphers.AES(self.encryption_key), modes.CBC(iv)).unencryptor() + plaintext_padded = unencryptor.update(ciphertext) + unencryptor.finalize() unpadder = padding.PKCS7(ciphers.AES.block_size).unpadder() return unpadder.update(plaintext_padded) + unpadder.finalize() -- cgit v1.2.3 From 2b21b12d337e65c59b5d18e42f1927c64565945d Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 09:39:25 -0700 Subject: Added test cases, fixed a bug --- cryptography/fernet.py | 4 ++-- tests/test_fernet.py | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 tests/test_fernet.py diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 59d8ad0c..2c134bbd 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -53,8 +53,8 @@ class Fernet(object): hmac = h.digest() if not constant_time_compare(hmac, data[-32:]): raise ValueError - unencryptor = BlockCipher(ciphers.AES(self.encryption_key), modes.CBC(iv)).unencryptor() - plaintext_padded = unencryptor.update(ciphertext) + unencryptor.finalize() + 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() diff --git a/tests/test_fernet.py b/tests/test_fernet.py new file mode 100644 index 00000000..e9d07f81 --- /dev/null +++ b/tests/test_fernet.py @@ -0,0 +1,23 @@ +import base64 + +from cryptography.fernet import Fernet + + +class TestFernet(object): + def test_generate(self): + f = Fernet(base64.urlsafe_b64decode( + b"cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + )) + token = f._encrypt_from_parts( + b"hello", + 499162800, + b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F", + ) + assert token == b"gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM4BLLF_5CV9dOPmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA==" + + def test_verify(self): + f = Fernet(base64.urlsafe_b64decode( + b"cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + )) + payload = f.decrypt(b"gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM4BLLF_5CV9dOPmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA==", 60) + assert payload == b"hello" -- cgit v1.2.3 From 5e87dfdd7f9853d4072efa6dd0e0515141ab7eb2 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 09:46:03 -0700 Subject: Fixed test and implementation --- cryptography/fernet.py | 6 ++++-- tests/test_fernet.py | 10 ++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 2c134bbd..ef747b7c 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -37,8 +37,10 @@ class Fernet(object): b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext + hmac ) - def decrypt(self, data, ttl=None): + def decrypt(self, data, ttl=None, current_time=None): # TODO: whole function is a giant hack job with no error checking + if current_time is None: + current_time = int(time.time()) data = base64.urlsafe_b64decode(data) assert data[0] == b"\x80" timestamp = data[1:9] @@ -46,7 +48,7 @@ class Fernet(object): ciphertext = data[25:-32] hmac = data[-32:] if ttl is not None: - if struct.unpack(">Q", timestamp)[0] + ttl > int(time.time()): + if struct.unpack(">Q", timestamp)[0] + ttl < current_time: raise ValueError h = HMAC(self.signing_key, digestmod=hashes.SHA256) h.update(data[:-32]) diff --git a/tests/test_fernet.py b/tests/test_fernet.py index e9d07f81..f7c06b95 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -13,11 +13,17 @@ class TestFernet(object): 499162800, b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F", ) - assert token == b"gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM4BLLF_5CV9dOPmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA==" + assert token == (b"gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM" + "4BLLF_5CV9dOPmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA==") def test_verify(self): f = Fernet(base64.urlsafe_b64decode( b"cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" )) - payload = f.decrypt(b"gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM4BLLF_5CV9dOPmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA==", 60) + payload = f.decrypt( + (b"gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM4BLLF_5CV9dO" + "PmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA=="), + ttl=60, + current_time=499162801 + ) assert payload == b"hello" -- cgit v1.2.3 From cd47c4ae14444ba0802d16fdb2960f7665dc9cbd Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 09:46:27 -0700 Subject: Extra assert --- cryptography/fernet.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index ef747b7c..e4ee059c 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -13,6 +13,7 @@ from cryptography.hazmat.primitives.block import BlockCipher, ciphers, modes class Fernet(object): def __init__(self, key): super(Fernet, self).__init__() + assert len(key) == 32 self.signing_key = key[:16] self.encryption_key = key[16:] -- cgit v1.2.3 From 05b94a6d5ea020ef1fec9d6192935687d2db7ebb Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 09:49:35 -0700 Subject: Unused --- cryptography/fernet.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index e4ee059c..4220e0cb 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -47,7 +47,6 @@ class Fernet(object): timestamp = data[1:9] iv = data[9:25] ciphertext = data[25:-32] - hmac = data[-32:] if ttl is not None: if struct.unpack(">Q", timestamp)[0] + ttl < current_time: raise ValueError -- cgit v1.2.3 From de36e90815f31ec39fe160bf69a81f1bb42b92d2 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 10:10:44 -0700 Subject: Address pep8 concerns --- cryptography/fernet.py | 9 +++++++-- tests/test_fernet.py | 8 +++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 4220e0cb..2de4a622 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -25,7 +25,9 @@ class Fernet(object): def _encrypt_from_parts(self, data, current_time, iv): padder = padding.PKCS7(ciphers.AES.block_size).padder() padded_data = padder.update(data) + padder.finalize() - encryptor = BlockCipher(ciphers.AES(self.encryption_key), modes.CBC(iv)).encryptor() + encryptor = BlockCipher( + ciphers.AES(self.encryption_key), modes.CBC(iv) + ).encryptor() ciphertext = encryptor.update(padded_data) + encryptor.finalize() h = HMAC(self.signing_key, digestmod=hashes.SHA256) @@ -55,11 +57,14 @@ class Fernet(object): hmac = h.digest() if not constant_time_compare(hmac, data[-32:]): raise ValueError - decryptor = BlockCipher(ciphers.AES(self.encryption_key), modes.CBC(iv)).decryptor() + 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() + def constant_time_compare(a, b): # TOOD: replace with a cffi function assert isinstance(a, bytes) and isinstance(b, bytes) diff --git a/tests/test_fernet.py b/tests/test_fernet.py index f7c06b95..27d24182 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -1,5 +1,7 @@ import base64 +import six + from cryptography.fernet import Fernet @@ -11,10 +13,10 @@ class TestFernet(object): token = f._encrypt_from_parts( b"hello", 499162800, - b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F", + b"".join(map(six.int2byte, range(16))), ) assert token == (b"gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM" - "4BLLF_5CV9dOPmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA==") + "4BLLF_5CV9dOPmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA==") def test_verify(self): f = Fernet(base64.urlsafe_b64decode( @@ -22,7 +24,7 @@ class TestFernet(object): )) payload = f.decrypt( (b"gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM4BLLF_5CV9dO" - "PmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA=="), + "PmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA=="), ttl=60, current_time=499162801 ) -- cgit v1.2.3 From 333fb1024e20fa10ec3e85cbd196cbdff059000d Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 10:27:35 -0700 Subject: Docs --- docs/fernet.rst | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 1 + 2 files changed, 49 insertions(+) create mode 100644 docs/fernet.rst diff --git a/docs/fernet.rst b/docs/fernet.rst new file mode 100644 index 00000000..938ba0cb --- /dev/null +++ b/docs/fernet.rst @@ -0,0 +1,48 @@ +Fernet +====== + +.. currentmodule:: cryptography.fernet + +.. testsetup:: + + import binascii + key = binascii.unhexlify(b"0" * 32) + + +`Fernet`_ is an implementation of symmetric (also known as "secret key") +authenticated cryptography. Fernet provides guarntees that a message encrypted +using it cannot be manipulated or read without the key. + +.. class:: Fernet(key) + + This class provides both encryption and decryption facilities. + + .. doctest:: + + >>> from cryptography.fernet import Fernet + >>> f = Fernet(key) + >>> ciphertext = f.encrypt(b"my deep dark secret") + >>> f.decrypt(ciphertext) + 'my deep dark secret' + + :param bytes key: A 32-byte key. This **must** be kept secret. Anyone with + this key is able to create and read messages. + + + .. method:: encrypt(plaintext) + + :param bytes plaintext: The message you would like to encrypt. + :returns bytes: A secure message which cannot be read or altered + without the key. + + .. method:: decrypt(ciphertext, ttl=None) + + :param bytes ciphertext: An encrypted message. + :param int ttl: Optionally, the number of seconds old a message may be + for it to be valid. If the message is older than + ``ttl`` seconds (from the time it was originally + created) an exception will be raised. + :returns bytes: The original plaintext. + + +.. _`Fernet`: https://github.com/fernet/spec/ diff --git a/docs/index.rst b/docs/index.rst index 4fd5d3be..b9c5b5fb 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -30,6 +30,7 @@ Contents .. toctree:: :maxdepth: 2 + fernet architecture contributing security -- cgit v1.2.3 From de475eb9f56a34868c7debb707427ab5678eda6c Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 10:35:19 -0700 Subject: Improve the docs --- docs/fernet.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/fernet.rst b/docs/fernet.rst index 938ba0cb..ac610eb8 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -22,6 +22,9 @@ using it cannot be manipulated or read without the key. >>> from cryptography.fernet import Fernet >>> f = Fernet(key) >>> ciphertext = f.encrypt(b"my deep dark secret") + # Secret bytes. + >>> ciphertext + '...' >>> f.decrypt(ciphertext) 'my deep dark secret' @@ -33,7 +36,7 @@ using it cannot be manipulated or read without the key. :param bytes plaintext: The message you would like to encrypt. :returns bytes: A secure message which cannot be read or altered - without the key. + without the key. It is URL safe base64-encoded. .. method:: decrypt(ciphertext, ttl=None) -- cgit v1.2.3 From 13e0d54510d3f939c749d3efc810bad675f4f908 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 10:38:04 -0700 Subject: Be explicit --- docs/fernet.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/fernet.rst b/docs/fernet.rst index ac610eb8..d44e737b 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -44,7 +44,9 @@ using it cannot be manipulated or read without the key. :param int ttl: Optionally, the number of seconds old a message may be for it to be valid. If the message is older than ``ttl`` seconds (from the time it was originally - created) an exception will be raised. + created) an exception will be raised. If ``ttl`` is not + provided (or is ``None``), the age of the message is + not considered. :returns bytes: The original plaintext. -- cgit v1.2.3 From 36e2df0955aa1c6534049be21868c24e93569b8b Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 10:40:17 -0700 Subject: Fixed keylength in example --- docs/fernet.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/fernet.rst b/docs/fernet.rst index d44e737b..33488891 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -6,7 +6,7 @@ Fernet .. testsetup:: import binascii - key = binascii.unhexlify(b"0" * 32) + key = binascii.unhexlify(b"0" * 64) `Fernet`_ is an implementation of symmetric (also known as "secret key") -- cgit v1.2.3 From 413bd8b45a51dc9c9afe3262534abba2d8528457 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 11:22:11 -0700 Subject: py3k syntax fix --- tests/test_fernet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_fernet.py b/tests/test_fernet.py index 27d24182..7bdfa3fa 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -16,7 +16,7 @@ class TestFernet(object): b"".join(map(six.int2byte, range(16))), ) assert token == (b"gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM" - "4BLLF_5CV9dOPmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA==") + b"4BLLF_5CV9dOPmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA==") def test_verify(self): f = Fernet(base64.urlsafe_b64decode( @@ -24,7 +24,7 @@ class TestFernet(object): )) payload = f.decrypt( (b"gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM4BLLF_5CV9dO" - "PmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA=="), + b"PmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA=="), ttl=60, current_time=499162801 ) -- cgit v1.2.3 From 5c5342ec17aba6f6f10ab2136192be97180e2225 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 11:25:54 -0700 Subject: fix for py3k --- cryptography/fernet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 2de4a622..d6a94424 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -45,7 +45,7 @@ class Fernet(object): if current_time is None: current_time = int(time.time()) data = base64.urlsafe_b64decode(data) - assert data[0] == b"\x80" + assert six.indexbytes(data, 0) == 0x80 timestamp = data[1:9] iv = data[9:25] ciphertext = data[25:-32] -- cgit v1.2.3 From 5ac6524f790713090754572fb775405f64a87df2 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 11:28:13 -0700 Subject: fix --- docs/fernet.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/fernet.rst b/docs/fernet.rst index 33488891..02b99705 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -22,7 +22,6 @@ using it cannot be manipulated or read without the key. >>> from cryptography.fernet import Fernet >>> f = Fernet(key) >>> ciphertext = f.encrypt(b"my deep dark secret") - # Secret bytes. >>> ciphertext '...' >>> f.decrypt(ciphertext) -- cgit v1.2.3 From 139cf462ca0126d6ed161a4b32e6b6c889c77318 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 11:36:01 -0700 Subject: py3k fix --- cryptography/fernet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index d6a94424..064aceec 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -71,6 +71,6 @@ def constant_time_compare(a, b): if len(a) != len(b): return False result = 0 - for i in xrange(len(a)): + for i in range(len(a)): result |= six.indexbytes(a, i) ^ six.indexbytes(b, i) return result == 0 -- cgit v1.2.3 From fb8adfcb2f0a67519ee81cad0c50d2e359ff3a20 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 14:16:24 -0700 Subject: Use raw vector files --- dev-requirements.txt | 3 +- tests/test_fernet.py | 56 +++++++++++++++++++++++++------------- tests/vectors/fernet/generate.json | 9 ++++++ tests/vectors/fernet/verify.json | 9 ++++++ tox.ini | 3 +- 5 files changed, 59 insertions(+), 21 deletions(-) create mode 100644 tests/vectors/fernet/generate.json create mode 100644 tests/vectors/fernet/verify.json diff --git a/dev-requirements.txt b/dev-requirements.txt index 752517dd..530ada91 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,6 +1,7 @@ +coverage flake8 +iso8601 pretend pytest -coverage sphinx tox diff --git a/tests/test_fernet.py b/tests/test_fernet.py index 7bdfa3fa..382a232c 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -1,31 +1,49 @@ import base64 +import calendar +import json +import os + +import iso8601 + +import pytest import six from cryptography.fernet import Fernet +def json_parametrize(keys, path): + with open(path) as f: + data = json.load(f) + return pytest.mark.parametrize(keys, [ + tuple([entry[k] for k in keys]) + for entry in data + ]) + + class TestFernet(object): - def test_generate(self): - f = Fernet(base64.urlsafe_b64decode( - b"cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" - )) - token = f._encrypt_from_parts( - b"hello", - 499162800, - b"".join(map(six.int2byte, range(16))), + @json_parametrize( + ("secret", "now", "iv", "src", "token"), + os.path.join(os.path.dirname(__file__), "vectors", "fernet", "generate.json") + ) + def test_generate(self, secret, now, iv, src, token): + f = Fernet(base64.urlsafe_b64decode(secret.encode("ascii"))) + actual_token = f._encrypt_from_parts( + src.encode("ascii"), + calendar.timegm(iso8601.parse_date(now).utctimetuple()), + b"".join(map(six.int2byte, iv)) ) - assert token == (b"gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM" - b"4BLLF_5CV9dOPmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA==") + assert actual_token == token - def test_verify(self): - f = Fernet(base64.urlsafe_b64decode( - b"cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" - )) + @json_parametrize( + ("secret", "now", "src", "ttl_sec", "token"), + os.path.join(os.path.dirname(__file__), "vectors", "fernet", "verify.json") + ) + def test_verify(self, secret, now, src, ttl_sec, token): + f = Fernet(base64.urlsafe_b64decode(secret.encode("ascii"))) payload = f.decrypt( - (b"gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM4BLLF_5CV9dO" - b"PmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA=="), - ttl=60, - current_time=499162801 + token.encode("ascii"), + ttl=ttl_sec, + current_time=calendar.timegm(iso8601.parse_date(now).utctimetuple()) ) - assert payload == b"hello" + assert payload == src diff --git a/tests/vectors/fernet/generate.json b/tests/vectors/fernet/generate.json new file mode 100644 index 00000000..d1f3e083 --- /dev/null +++ b/tests/vectors/fernet/generate.json @@ -0,0 +1,9 @@ +[ + { + "token": "gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM4BLLF_5CV9dOPmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA==", + "now": "1985-10-26T01:20:00-07:00", + "iv": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + "src": "hello", + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + } +] diff --git a/tests/vectors/fernet/verify.json b/tests/vectors/fernet/verify.json new file mode 100644 index 00000000..08c480f5 --- /dev/null +++ b/tests/vectors/fernet/verify.json @@ -0,0 +1,9 @@ +[ + { + "token": "gAAAAAAdwJ6wAAECAwQFBgcICQoLDA0ODy021cpGVWKZ_eEwCGM4BLLF_5CV9dOPmrhuVUPgJobwOz7JcbmrR64jVmpU4IwqDA==", + "now": "1985-10-26T01:20:01-07:00", + "ttl_sec": 60, + "src": "hello", + "secret": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=" + } +] diff --git a/tox.ini b/tox.ini index 92bcb75b..57b42412 100644 --- a/tox.ini +++ b/tox.ini @@ -3,9 +3,10 @@ envlist = py26,py27,pypy,py32,py33,docs,pep8 [testenv] deps = - pytest coverage + iso8601 pretend + pytest commands = coverage run --source=cryptography/,tests/ -m pytest coverage report -m --fail-under 100 -- cgit v1.2.3 From 38f34557e432f98cc8a023e621b5efe525ef886c Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 14:50:00 -0700 Subject: Started working on the invalid cases --- cryptography/fernet.py | 32 +++++++++++++++++---- tests/test_fernet.py | 28 +++++++++++++++---- tests/vectors/fernet/invalid.json | 58 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 11 deletions(-) create mode 100644 tests/vectors/fernet/invalid.json 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=" + } +] -- cgit v1.2.3 From c1ea0a0d23115bb0586230a139bcb2b60adb6262 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 15:03:53 -0700 Subject: Fixed pep8 issues --- cryptography/fernet.py | 8 ++++++-- tests/test_fernet.py | 10 ++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 880d96f0..ef64b7e9 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -28,7 +28,9 @@ class Fernet(object): def _encrypt_from_parts(self, data, current_time, iv): if isinstance(data, six.text_type): - raise TypeError("Unicode-objects must be encoded before encryption") + raise TypeError( + "Unicode-objects must be encoded before encryption" + ) padder = padding.PKCS7(ciphers.AES.block_size).padder() padded_data = padder.update(data) + padder.finalize() @@ -49,7 +51,9 @@ class Fernet(object): def decrypt(self, data, ttl=None, current_time=None): if isinstance(data, six.text_type): - raise TypeError("Unicode-objects must be encoded before decryption") + raise TypeError( + "Unicode-objects must be encoded before decryption" + ) if current_time is None: current_time = int(time.time()) diff --git a/tests/test_fernet.py b/tests/test_fernet.py index 15071718..b0f22f0c 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -40,21 +40,19 @@ class TestFernet(object): ) def test_verify(self, secret, now, src, ttl_sec, token): f = Fernet(base64.urlsafe_b64decode(secret.encode("ascii"))) + current_time = calendar.timegm(iso8601.parse_date(now).utctimetuple()) payload = f.decrypt( - token.encode("ascii"), - ttl=ttl_sec, - current_time=calendar.timegm(iso8601.parse_date(now).utctimetuple()) + token.encode("ascii"), ttl=ttl_sec, current_time=current_time ) 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"))) + current_time = calendar.timegm(iso8601.parse_date(now).utctimetuple()) with pytest.raises(InvalidToken): f.decrypt( - token.encode("ascii"), - ttl=ttl_sec, - current_time=calendar.timegm(iso8601.parse_date(now).utctimetuple()) + token.encode("ascii"), ttl=ttl_sec, current_time=current_time ) def test_unicode(self): -- cgit v1.2.3 From ce8f9a4e2a5d159356a06147f65e221dbdf43171 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 15:23:15 -0700 Subject: A test for roundtripping --- tests/test_fernet.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_fernet.py b/tests/test_fernet.py index b0f22f0c..a42011a6 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -61,3 +61,10 @@ class TestFernet(object): f.encrypt(six.u("")) with pytest.raises(TypeError): f.decrypt(six.u("")) + + @pytest.mark.parametrize("message", [b"", b"Abc!", b"\x00\xFF\x00\x80"]) + def test_roundtrips(self, message): + f = Fernet(b"\x00" * 32) + ciphertext = f.encrypt(message) + plaintext = f.decrypt(ciphertext) + assert plaintext == message -- cgit v1.2.3 From 69ab59e59670c3865fb8240aa81ca9195a96e003 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 15:26:43 -0700 Subject: py3k uses a different exception here --- cryptography/fernet.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index ef64b7e9..f923911a 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -1,4 +1,5 @@ import base64 +import binascii import os import struct import time @@ -60,7 +61,7 @@ class Fernet(object): try: data = base64.urlsafe_b64decode(data) - except TypeError: + except (TypeError, binascii.Error): raise InvalidToken assert six.indexbytes(data, 0) == 0x80 -- cgit v1.2.3 From 3d5041cda29f8201418a4c2d340059f68c7bb41d Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 15:55:33 -0700 Subject: Moved constant_time_compare to be C --- cryptography/fernet.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index f923911a..214b9374 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -4,6 +4,8 @@ import os import struct import time +import cffi + import six from cryptography.hazmat.primitives import padding, hashes @@ -15,6 +17,25 @@ class InvalidToken(Exception): pass +ffi = cffi.FFI() +ffi.cdef(""" +bool constant_time_compare(uint8_t *, size_t, uint8_t *, size_t); +""") +lib = ffi.verify(""" +#include + +bool constant_time_compare(uint8_t *a, size_t len_a, uint8_t *b, size_t len_b) { + if (len_a != len_b) { + return false; + } + int result = 0; + for (size_t i = 0; i < len_a; i++) { + result |= a[i] ^ b[i]; + } + return result == 0; +} +""") + class Fernet(object): def __init__(self, key): super(Fernet, self).__init__() @@ -75,7 +96,7 @@ class Fernet(object): h.update(data[:-32]) hmac = h.digest() - if not constant_time_compare(hmac, data[-32:]): + if not lib.constant_time_compare(hmac, len(hmac), data[-32:], 32): raise InvalidToken decryptor = BlockCipher( @@ -90,14 +111,3 @@ class Fernet(object): except ValueError: raise InvalidToken return unpadded - - -def constant_time_compare(a, b): - # TOOD: replace with a cffi function - assert isinstance(a, bytes) and isinstance(b, bytes) - if len(a) != len(b): - return False - result = 0 - for i in range(len(a)): - result |= six.indexbytes(a, i) ^ six.indexbytes(b, i) - return result == 0 -- cgit v1.2.3 From 6b9770b159b920cccddc8e3c3d1b0fb0287a0c15 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 16:07:35 -0700 Subject: write more readably --- tests/test_fernet.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_fernet.py b/tests/test_fernet.py index a42011a6..baae36d2 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -65,6 +65,4 @@ class TestFernet(object): @pytest.mark.parametrize("message", [b"", b"Abc!", b"\x00\xFF\x00\x80"]) def test_roundtrips(self, message): f = Fernet(b"\x00" * 32) - ciphertext = f.encrypt(message) - plaintext = f.decrypt(ciphertext) - assert plaintext == message + assert f.decrypt(f.encrypt(message)) == message -- cgit v1.2.3 From 38db2140e270c19d781aa35c5aa098a107179141 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 16:25:25 -0700 Subject: Be C99 --- cryptography/fernet.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 214b9374..10640d1b 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -25,11 +25,12 @@ lib = ffi.verify(""" #include bool constant_time_compare(uint8_t *a, size_t len_a, uint8_t *b, size_t len_b) { + size_t i = 0; + int result = 0; if (len_a != len_b) { return false; } - int result = 0; - for (size_t i = 0; i < len_a; i++) { + for (i = 0; i < len_a; i++) { result |= a[i] ^ b[i]; } return result == 0; -- cgit v1.2.3 From 7ecd3148acc35668bf679be5a603ed4bd7313148 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 16:29:18 -0700 Subject: py3k fixes --- tests/test_fernet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_fernet.py b/tests/test_fernet.py index baae36d2..ca8e4ccd 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -33,7 +33,7 @@ class TestFernet(object): calendar.timegm(iso8601.parse_date(now).utctimetuple()), b"".join(map(six.int2byte, iv)) ) - assert actual_token == token + assert actual_token == token.encode("ascii") @json_parametrize( ("secret", "now", "src", "ttl_sec", "token"), "verify.json", @@ -44,7 +44,7 @@ class TestFernet(object): payload = f.decrypt( token.encode("ascii"), ttl=ttl_sec, current_time=current_time ) - assert payload == src + assert payload == src.encode("ascii") @json_parametrize(("secret", "token", "now", "ttl_sec"), "invalid.json") def test_invalid(self, secret, token, now, ttl_sec): -- cgit v1.2.3 From 2c58bbe5fa222fed3d917252a64868171443def9 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 31 Oct 2013 16:31:38 -0700 Subject: flake8 --- cryptography/fernet.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 10640d1b..f9d5e93f 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -24,7 +24,8 @@ bool constant_time_compare(uint8_t *, size_t, uint8_t *, size_t); lib = ffi.verify(""" #include -bool constant_time_compare(uint8_t *a, size_t len_a, uint8_t *b, size_t len_b) { +bool constant_time_compare(uint8_t *a, size_t len_a, uint8_t *b, + size_t len_b) { size_t i = 0; int result = 0; if (len_a != len_b) { @@ -37,6 +38,7 @@ bool constant_time_compare(uint8_t *a, size_t len_a, uint8_t *b, size_t len_b) { } """) + class Fernet(object): def __init__(self, key): super(Fernet, self).__init__() -- cgit v1.2.3 From fa7081a6275b876c0fd734d0ac3a27b591b661f6 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 1 Nov 2013 16:38:25 -0700 Subject: Update for new API --- cryptography/fernet.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index f9d5e93f..f907a363 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -64,12 +64,12 @@ class Fernet(object): ).encryptor() ciphertext = encryptor.update(padded_data) + encryptor.finalize() - h = HMAC(self.signing_key, digestmod=hashes.SHA256) + h = HMAC(self.signing_key, hashes.SHA256()) h.update(b"\x80") h.update(struct.pack(">Q", current_time)) h.update(iv) h.update(ciphertext) - hmac = h.digest() + hmac = h.finalize() return base64.urlsafe_b64encode( b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext + hmac ) @@ -95,9 +95,9 @@ class Fernet(object): if ttl is not None: if struct.unpack(">Q", timestamp)[0] + ttl < current_time: raise InvalidToken - h = HMAC(self.signing_key, digestmod=hashes.SHA256) + h = HMAC(self.signing_key, hashes.SHA256()) h.update(data[:-32]) - hmac = h.digest() + hmac = h.finalize() if not lib.constant_time_compare(hmac, len(hmac), data[-32:], 32): raise InvalidToken -- cgit v1.2.3 From 8912d3afde05ff6f91a508f0a54b5c24960eb09a Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 2 Nov 2013 14:04:19 -0700 Subject: Include the license --- cryptography/fernet.py | 13 +++++++++++++ tests/test_fernet.py | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index f907a363..d1138cb2 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -1,3 +1,16 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import base64 import binascii import os diff --git a/tests/test_fernet.py b/tests/test_fernet.py index ca8e4ccd..922f7223 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -1,3 +1,16 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import base64 import calendar import json -- cgit v1.2.3 From 105e8137799a2ef7ec8275e3c01d61a04884413b Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 7 Nov 2013 14:25:42 -0800 Subject: Pass around a backend --- cryptography/fernet.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index d1138cb2..668958be 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -53,11 +53,12 @@ bool constant_time_compare(uint8_t *a, size_t len_a, uint8_t *b, class Fernet(object): - def __init__(self, key): + def __init__(self, key, backend=None): super(Fernet, self).__init__() assert len(key) == 32 self.signing_key = key[:16] self.encryption_key = key[16:] + self.backend = backend def encrypt(self, data): current_time = int(time.time()) @@ -73,11 +74,11 @@ class Fernet(object): padder = padding.PKCS7(ciphers.AES.block_size).padder() padded_data = padder.update(data) + padder.finalize() encryptor = BlockCipher( - ciphers.AES(self.encryption_key), modes.CBC(iv) + ciphers.AES(self.encryption_key), modes.CBC(iv), self.backend ).encryptor() ciphertext = encryptor.update(padded_data) + encryptor.finalize() - h = HMAC(self.signing_key, hashes.SHA256()) + h = HMAC(self.signing_key, hashes.SHA256(), self.backend) h.update(b"\x80") h.update(struct.pack(">Q", current_time)) h.update(iv) @@ -108,7 +109,7 @@ class Fernet(object): if ttl is not None: if struct.unpack(">Q", timestamp)[0] + ttl < current_time: raise InvalidToken - h = HMAC(self.signing_key, hashes.SHA256()) + h = HMAC(self.signing_key, hashes.SHA256(), self.backend) h.update(data[:-32]) hmac = h.finalize() @@ -116,7 +117,7 @@ class Fernet(object): raise InvalidToken decryptor = BlockCipher( - ciphers.AES(self.encryption_key), modes.CBC(iv) + ciphers.AES(self.encryption_key), modes.CBC(iv), self.backend ).decryptor() plaintext_padded = decryptor.update(ciphertext) + decryptor.finalize() unpadder = padding.PKCS7(ciphers.AES.block_size).unpadder() -- cgit v1.2.3 From dcc3f668c590bd0da898f331b968ed56d46936cf Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 7 Nov 2013 14:28:16 -0800 Subject: Fixes for the module renaming --- cryptography/fernet.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 668958be..2a9f6a96 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -23,7 +23,7 @@ import six from cryptography.hazmat.primitives import padding, hashes from cryptography.hazmat.primitives.hmac import HMAC -from cryptography.hazmat.primitives.block import BlockCipher, ciphers, modes +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes class InvalidToken(Exception): @@ -71,10 +71,10 @@ class Fernet(object): "Unicode-objects must be encoded before encryption" ) - padder = padding.PKCS7(ciphers.AES.block_size).padder() + padder = padding.PKCS7(algorithms.AES.block_size).padder() padded_data = padder.update(data) + padder.finalize() - encryptor = BlockCipher( - ciphers.AES(self.encryption_key), modes.CBC(iv), self.backend + encryptor = Cipher( + algorithms.AES(self.encryption_key), modes.CBC(iv), self.backend ).encryptor() ciphertext = encryptor.update(padded_data) + encryptor.finalize() @@ -116,11 +116,11 @@ class Fernet(object): if not lib.constant_time_compare(hmac, len(hmac), data[-32:], 32): raise InvalidToken - decryptor = BlockCipher( - ciphers.AES(self.encryption_key), modes.CBC(iv), self.backend + decryptor = Cipher( + algorithms.AES(self.encryption_key), modes.CBC(iv), self.backend ).decryptor() plaintext_padded = decryptor.update(ciphertext) + decryptor.finalize() - unpadder = padding.PKCS7(ciphers.AES.block_size).unpadder() + unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() unpadded = unpadder.update(plaintext_padded) try: -- cgit v1.2.3 From 286433de03ad1f54f1f563922a327d22945b0733 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 18 Nov 2013 10:15:20 -0800 Subject: Make it more constant time --- cryptography/fernet.py | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 2a9f6a96..abd5e251 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -30,24 +30,39 @@ class InvalidToken(Exception): pass -ffi = cffi.FFI() -ffi.cdef(""" -bool constant_time_compare(uint8_t *, size_t, uint8_t *, size_t); +_ffi = cffi.FFI() +_ffi.cdef(""" +bool Cryptography_constant_time_compare(uint8_t *, size_t, uint8_t *, size_t); """) -lib = ffi.verify(""" +_lib = _ffi.verify(""" #include -bool constant_time_compare(uint8_t *a, size_t len_a, uint8_t *b, - size_t len_b) { + +/* Returns the value of the input with the most-significant-bit copied to all + of the bits. This relies on implementation details of computers with 2's + complement representations of integers, which is not required by the C + standard. */ +static uint8_t Cryptography_DUPLICATE_MSB_TO_ALL(uint8_t a) { + return (uint8_t)((int8_t)(a) >> (sizeof(int8_t) * 8 - 1)); +} + +bool Cryptography_constant_time_compare(uint8_t *a, size_t len_a, uint8_t *b, + size_t len_b) { size_t i = 0; - int result = 0; + uint8_t mismatch = 0; if (len_a != len_b) { return false; } for (i = 0; i < len_a; i++) { - result |= a[i] ^ b[i]; + mismatch |= a[i] ^ b[i]; } - return result == 0; + + /* Make sure any bits set are copied to the lowest bit */ + mismatch |= mismatch >> 4; + mismatch |= mismatch >> 2; + mismatch |= mismatch >> 1; + /* Now check the low bit to see if it's set */ + return (mismatch & 1) == 0; } """) @@ -112,8 +127,10 @@ class Fernet(object): h = HMAC(self.signing_key, hashes.SHA256(), self.backend) h.update(data[:-32]) hmac = h.finalize() - - if not lib.constant_time_compare(hmac, len(hmac), data[-32:], 32): + valid = _lib.Cryptography_constant_time_compare( + hmac, len(hmac), data[-32:], 32 + ) + if not valid: raise InvalidToken decryptor = Cipher( -- cgit v1.2.3 From 867acfa0300ca75f2c11c15490e04b556d8bfe99 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 18 Nov 2013 10:19:31 -0800 Subject: This is unused --- cryptography/fernet.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index abd5e251..32bd35d5 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -37,15 +37,6 @@ bool Cryptography_constant_time_compare(uint8_t *, size_t, uint8_t *, size_t); _lib = _ffi.verify(""" #include - -/* Returns the value of the input with the most-significant-bit copied to all - of the bits. This relies on implementation details of computers with 2's - complement representations of integers, which is not required by the C - standard. */ -static uint8_t Cryptography_DUPLICATE_MSB_TO_ALL(uint8_t a) { - return (uint8_t)((int8_t)(a) >> (sizeof(int8_t) * 8 - 1)); -} - bool Cryptography_constant_time_compare(uint8_t *a, size_t len_a, uint8_t *b, size_t len_b) { size_t i = 0; -- cgit v1.2.3 From 9626b5a50460d2f90baa1f1b8c6a09ccc900c178 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 19 Nov 2013 16:49:26 -0800 Subject: Validate the IV/nonce length for a given algorithm. Fixes #159 --- cryptography/hazmat/primitives/ciphers/base.py | 3 +++ cryptography/hazmat/primitives/ciphers/modes.py | 21 +++++++++++++++++++++ cryptography/hazmat/primitives/interfaces.py | 7 +++++++ docs/hazmat/primitives/interfaces.rst | 12 ++++++++++++ tests/hazmat/bindings/test_openssl.py | 3 ++- tests/hazmat/primitives/test_block.py | 17 ++++++++++++++++- 6 files changed, 61 insertions(+), 2 deletions(-) diff --git a/cryptography/hazmat/primitives/ciphers/base.py b/cryptography/hazmat/primitives/ciphers/base.py index 3d733afc..d046a012 100644 --- a/cryptography/hazmat/primitives/ciphers/base.py +++ b/cryptography/hazmat/primitives/ciphers/base.py @@ -28,6 +28,9 @@ class Cipher(object): if not isinstance(algorithm, interfaces.CipherAlgorithm): raise TypeError("Expected interface of interfaces.CipherAlgorithm") + if mode is not None: + mode.validate_for_algorithm(algorithm) + self.algorithm = algorithm self.mode = mode self._backend = backend diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py index 1d0de689..597b4e3e 100644 --- a/cryptography/hazmat/primitives/ciphers/modes.py +++ b/cryptography/hazmat/primitives/ciphers/modes.py @@ -25,11 +25,20 @@ class CBC(object): def __init__(self, initialization_vector): self.initialization_vector = initialization_vector + def validate_for_algorithm(self, algorithm): + if len(self.initialization_vector) * 8 != algorithm.block_size: + raise ValueError("Invalid iv size ({0}) for {1}".format( + len(self.initialization_vector), self.name + )) + @utils.register_interface(interfaces.Mode) class ECB(object): name = "ECB" + def validate_for_algorithm(self, algorithm): + pass + @utils.register_interface(interfaces.Mode) @utils.register_interface(interfaces.ModeWithInitializationVector) @@ -39,6 +48,12 @@ class OFB(object): def __init__(self, initialization_vector): self.initialization_vector = initialization_vector + def validate_for_algorithm(self, algorithm): + if len(self.initialization_vector) * 8 != algorithm.block_size: + raise ValueError("Invalid iv size ({0}) for {1}".format( + len(self.initialization_vector), self.name + )) + @utils.register_interface(interfaces.Mode) @utils.register_interface(interfaces.ModeWithInitializationVector) @@ -48,6 +63,12 @@ class CFB(object): def __init__(self, initialization_vector): self.initialization_vector = initialization_vector + def validate_for_algorithm(self, algorithm): + if len(self.initialization_vector) * 8 != algorithm.block_size: + raise ValueError("Invalid iv size ({0}) for {1}".format( + len(self.initialization_vector), self.name + )) + @utils.register_interface(interfaces.Mode) @utils.register_interface(interfaces.ModeWithNonce) diff --git a/cryptography/hazmat/primitives/interfaces.py b/cryptography/hazmat/primitives/interfaces.py index 8cc9d42c..672ac96a 100644 --- a/cryptography/hazmat/primitives/interfaces.py +++ b/cryptography/hazmat/primitives/interfaces.py @@ -39,6 +39,13 @@ class Mode(six.with_metaclass(abc.ABCMeta)): A string naming this mode. (e.g. ECB, CBC) """ + @abc.abstractmethod + def validate_for_algorithm(self, algorithm): + """ + Checks that all the necessary invariants of this (mode, algorithm) + combination are met. + """ + class ModeWithInitializationVector(six.with_metaclass(abc.ABCMeta)): @abc.abstractproperty diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst index 11cff51a..e798c0e6 100644 --- a/docs/hazmat/primitives/interfaces.rst +++ b/docs/hazmat/primitives/interfaces.rst @@ -56,6 +56,18 @@ Interfaces used by the symmetric cipher modes described in The name may be used by a backend to influence the operation of a cipher in conjunction with the algorithm's name. + .. method:: validate_for_algorithm(algorithm) + + :param CipherAlgorithm algorithm: + + Checks that the combination of this mode with the provided algorithm + meets any necessary invariants. This should raise an exception if they + are not met. + + For example, the :class:`~cryptography.hazmat.primitives.modes.CBC` + mode uses this method to check that the provided initialization + vector's length matches the block size of the algorithm. + .. class:: ModeWithInitializationVector diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py index 9f27aab7..1cadc75c 100644 --- a/tests/hazmat/bindings/test_openssl.py +++ b/tests/hazmat/bindings/test_openssl.py @@ -23,7 +23,8 @@ from cryptography.hazmat.primitives.ciphers.modes import CBC class DummyMode(object): - pass + def validate_for_algorithm(self, algorithm): + pass @utils.register_interface(interfaces.CipherAlgorithm) diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py index 9460c53d..b41f8922 100644 --- a/tests/hazmat/primitives/test_block.py +++ b/tests/hazmat/primitives/test_block.py @@ -30,6 +30,11 @@ class DummyCipher(object): pass +class DummyMode(object): + def validate_for_algorithm(self, algorithm): + pass + + class TestCipher(object): def test_instantiate_without_backend(self): Cipher( @@ -101,10 +106,20 @@ class TestCipherContext(object): def test_nonexistent_cipher(self, backend): cipher = Cipher( - DummyCipher(), object(), backend + DummyCipher(), DummyMode(), backend ) with pytest.raises(UnsupportedAlgorithm): cipher.encryptor() with pytest.raises(UnsupportedAlgorithm): cipher.decryptor() + + +class TestModeValidation(object): + def test_cbc(self, backend): + with pytest.raises(ValueError): + Cipher( + algorithms.AES(b"\x00" * 16), + modes.CBC(b"abc"), + backend, + ) -- cgit v1.2.3 From 707253283ec2921bf76e2c8049eb47388d9da580 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 19 Nov 2013 16:52:32 -0800 Subject: Implement this for CTR --- cryptography/hazmat/primitives/ciphers/modes.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py index 597b4e3e..f357dcf7 100644 --- a/cryptography/hazmat/primitives/ciphers/modes.py +++ b/cryptography/hazmat/primitives/ciphers/modes.py @@ -77,3 +77,10 @@ class CTR(object): def __init__(self, nonce): self.nonce = nonce + + + def validate_for_algorithm(self, algorithm): + if len(self.nonce) * 8 != algorithm.block_size: + raise ValueError("Invalid nonce size ({0}) for {1}".format( + len(self.nonce), self.name + )) -- cgit v1.2.3 From 26ebea2c5bde18aaecee5f03291606cc5799d0cc Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 19 Nov 2013 16:53:36 -0800 Subject: Tests for OFB and CFB --- tests/hazmat/primitives/test_block.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py index b41f8922..ad56f77e 100644 --- a/tests/hazmat/primitives/test_block.py +++ b/tests/hazmat/primitives/test_block.py @@ -123,3 +123,19 @@ class TestModeValidation(object): modes.CBC(b"abc"), backend, ) + + def test_ofb(self, backend): + with pytest.raises(ValueError): + Cipher( + algorithms.AES(b"\x00" * 16), + modes.OFB(b"abc"), + backend, + ) + + def test_cfb(self, backend): + with pytest.raises(ValueError): + Cipher( + algorithms.AES(b"\x00" * 16), + modes.CFB(b"abc"), + backend, + ) -- cgit v1.2.3 From 18f2c8f5da97e430387a78d6e7fe20de1c1e6ada Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 19 Nov 2013 16:57:08 -0800 Subject: test for ctr --- tests/hazmat/primitives/test_block.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py index ad56f77e..52221cb6 100644 --- a/tests/hazmat/primitives/test_block.py +++ b/tests/hazmat/primitives/test_block.py @@ -139,3 +139,11 @@ class TestModeValidation(object): modes.CFB(b"abc"), backend, ) + + def test_ctr(self, backend): + with pytest.raises(ValueError): + Cipher( + algorithms.AES(b"\x00" * 16), + modes.CFB(b"abc"), + backend, + ) -- cgit v1.2.3 From 51cd5a1534fe4d9f84563f09cf95fb4a547cf143 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 19 Nov 2013 17:03:35 -0800 Subject: flake8 --- cryptography/hazmat/primitives/ciphers/modes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py index f357dcf7..63fa163c 100644 --- a/cryptography/hazmat/primitives/ciphers/modes.py +++ b/cryptography/hazmat/primitives/ciphers/modes.py @@ -78,7 +78,6 @@ class CTR(object): def __init__(self, nonce): self.nonce = nonce - def validate_for_algorithm(self, algorithm): if len(self.nonce) * 8 != algorithm.block_size: raise ValueError("Invalid nonce size ({0}) for {1}".format( -- cgit v1.2.3 From 3c25f61c18c6f8f9a2210fb2124654023bcec775 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 19 Nov 2013 17:10:14 -0800 Subject: fixed typo --- tests/hazmat/primitives/test_block.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py index 52221cb6..e0deb36b 100644 --- a/tests/hazmat/primitives/test_block.py +++ b/tests/hazmat/primitives/test_block.py @@ -144,6 +144,6 @@ class TestModeValidation(object): with pytest.raises(ValueError): Cipher( algorithms.AES(b"\x00" * 16), - modes.CFB(b"abc"), + modes.CTR(b"abc"), backend, ) -- cgit v1.2.3 From 898fe0f899eb3ec744acaaa0a8641644fc6cf219 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 20 Nov 2013 16:38:32 -0800 Subject: Key in the right place --- cryptography/fernet.py | 1 + tests/test_fernet.py | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 32bd35d5..ae3a8bfa 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -61,6 +61,7 @@ bool Cryptography_constant_time_compare(uint8_t *a, size_t len_a, uint8_t *b, class Fernet(object): def __init__(self, key, backend=None): super(Fernet, self).__init__() + key = base64.urlsafe_b64decode(key) assert len(key) == 32 self.signing_key = key[:16] self.encryption_key = key[16:] diff --git a/tests/test_fernet.py b/tests/test_fernet.py index 922f7223..4080bd2d 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -40,7 +40,7 @@ class TestFernet(object): ("secret", "now", "iv", "src", "token"), "generate.json", ) def test_generate(self, secret, now, iv, src, token): - f = Fernet(base64.urlsafe_b64decode(secret.encode("ascii"))) + f = Fernet(secret.encode("ascii")) actual_token = f._encrypt_from_parts( src.encode("ascii"), calendar.timegm(iso8601.parse_date(now).utctimetuple()), @@ -52,7 +52,7 @@ class TestFernet(object): ("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"))) + f = Fernet(secret.encode("ascii")) current_time = calendar.timegm(iso8601.parse_date(now).utctimetuple()) payload = f.decrypt( token.encode("ascii"), ttl=ttl_sec, current_time=current_time @@ -61,7 +61,7 @@ class TestFernet(object): @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"))) + f = Fernet(secret.encode("ascii")) current_time = calendar.timegm(iso8601.parse_date(now).utctimetuple()) with pytest.raises(InvalidToken): f.decrypt( @@ -69,7 +69,7 @@ class TestFernet(object): ) def test_unicode(self): - f = Fernet(b"\x00" * 32) + f = Fernet(base64.b64encode(b"\x00" * 32)) with pytest.raises(TypeError): f.encrypt(six.u("")) with pytest.raises(TypeError): @@ -77,5 +77,5 @@ class TestFernet(object): @pytest.mark.parametrize("message", [b"", b"Abc!", b"\x00\xFF\x00\x80"]) def test_roundtrips(self, message): - f = Fernet(b"\x00" * 32) + f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32)) assert f.decrypt(f.encrypt(message)) == message -- cgit v1.2.3 From 43307c7b57b5d2cbee01f1a89eae212d2325ca40 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 21 Nov 2013 21:50:14 -0800 Subject: Fixed a typo --- docs/fernet.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/fernet.rst b/docs/fernet.rst index 02b99705..e4756c09 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -5,12 +5,13 @@ Fernet .. testsetup:: + import base64 import binascii - key = binascii.unhexlify(b"0" * 64) + key = base64.urlsafe_b64encode(binascii.unhexlify(b"0" * 64)) `Fernet`_ is an implementation of symmetric (also known as "secret key") -authenticated cryptography. Fernet provides guarntees that a message encrypted +authenticated cryptography. Fernet provides guarantees that a message encrypted using it cannot be manipulated or read without the key. .. class:: Fernet(key) @@ -27,8 +28,9 @@ using it cannot be manipulated or read without the key. >>> f.decrypt(ciphertext) 'my deep dark secret' - :param bytes key: A 32-byte key. This **must** be kept secret. Anyone with - this key is able to create and read messages. + :param bytes key: A base64 encoded 32-byte key. This **must** be kept + secret. Anyone with this key is able to create and read + messages. .. method:: encrypt(plaintext) -- cgit v1.2.3 From 1d2901cae193cb965480835aaf96696f8eecfaab Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 22 Nov 2013 10:12:05 -0800 Subject: Hide the dangerous bits --- cryptography/fernet.py | 18 ++++++++---------- tests/test_fernet.py | 15 +++++++-------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index ae3a8bfa..1c6cb5dd 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -85,24 +85,22 @@ class Fernet(object): ).encryptor() ciphertext = encryptor.update(padded_data) + encryptor.finalize() + basic_parts = ( + b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext + ) + h = HMAC(self.signing_key, hashes.SHA256(), self.backend) - h.update(b"\x80") - h.update(struct.pack(">Q", current_time)) - h.update(iv) - h.update(ciphertext) + h.update(basic_parts) hmac = h.finalize() - return base64.urlsafe_b64encode( - b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext + hmac - ) + return base64.urlsafe_b64encode(basic_parts + hmac) - def decrypt(self, data, ttl=None, current_time=None): + def decrypt(self, data, ttl=None): 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()) + current_time = int(time.time()) try: data = base64.urlsafe_b64decode(data) diff --git a/tests/test_fernet.py b/tests/test_fernet.py index 4080bd2d..c1caaa05 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -15,6 +15,7 @@ import base64 import calendar import json import os +import time import iso8601 @@ -51,22 +52,20 @@ class TestFernet(object): @json_parametrize( ("secret", "now", "src", "ttl_sec", "token"), "verify.json", ) - def test_verify(self, secret, now, src, ttl_sec, token): + def test_verify(self, secret, now, src, ttl_sec, token, monkeypatch): f = Fernet(secret.encode("ascii")) current_time = calendar.timegm(iso8601.parse_date(now).utctimetuple()) - payload = f.decrypt( - token.encode("ascii"), ttl=ttl_sec, current_time=current_time - ) + monkeypatch.setattr(time, "time", lambda: current_time) + payload = f.decrypt(token.encode("ascii"), ttl=ttl_sec) assert payload == src.encode("ascii") @json_parametrize(("secret", "token", "now", "ttl_sec"), "invalid.json") - def test_invalid(self, secret, token, now, ttl_sec): + def test_invalid(self, secret, token, now, ttl_sec, monkeypatch): f = Fernet(secret.encode("ascii")) current_time = calendar.timegm(iso8601.parse_date(now).utctimetuple()) + monkeypatch.setattr(time, "time", lambda: current_time) with pytest.raises(InvalidToken): - f.decrypt( - token.encode("ascii"), ttl=ttl_sec, current_time=current_time - ) + f.decrypt(token.encode("ascii"), ttl=ttl_sec) def test_unicode(self): f = Fernet(base64.b64encode(b"\x00" * 32)) -- cgit v1.2.3 From 56bcade581e68ad0dd82dbabe97c75a9f0701fed Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 22 Nov 2013 10:15:14 -0800 Subject: fix, technically --- tests/test_fernet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_fernet.py b/tests/test_fernet.py index c1caaa05..8759229a 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -68,7 +68,7 @@ class TestFernet(object): f.decrypt(token.encode("ascii"), ttl=ttl_sec) def test_unicode(self): - f = Fernet(base64.b64encode(b"\x00" * 32)) + f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32)) with pytest.raises(TypeError): f.encrypt(six.u("")) with pytest.raises(TypeError): -- cgit v1.2.3 From 7a121fce784efb6d436816d84ed01e873f251490 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 22 Nov 2013 10:18:30 -0800 Subject: More info in the docs --- docs/fernet.rst | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/fernet.rst b/docs/fernet.rst index e4756c09..c95077bb 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -28,16 +28,16 @@ using it cannot be manipulated or read without the key. >>> f.decrypt(ciphertext) 'my deep dark secret' - :param bytes key: A base64 encoded 32-byte key. This **must** be kept - secret. Anyone with this key is able to create and read - messages. + :param bytes key: A URL-safe base64-encoded 32-byte key. This **must** be + kept secret. Anyone with this key is able to create and + read messages. .. method:: encrypt(plaintext) :param bytes plaintext: The message you would like to encrypt. :returns bytes: A secure message which cannot be read or altered - without the key. It is URL safe base64-encoded. + without the key. It is URL-safe base64-encoded. .. method:: decrypt(ciphertext, ttl=None) @@ -49,6 +49,16 @@ using it cannot be manipulated or read without the key. provided (or is ``None``), the age of the message is not considered. :returns bytes: The original plaintext. + :raises InvalidToken: If the ``ciphertext`` is in any way invalid, this + exception is raised. A ciphertext may be invalid + for a number of reasons: it is older than the + ``ttl``, it is malformed, or it does not have a + valid signature. + + +.. class:: InvalidToken + + See :meth:`Fernet.decrypt` for more information. .. _`Fernet`: https://github.com/fernet/spec/ -- cgit v1.2.3 From 36597b4379bd62e520b9076072a030c73b85f471 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 22 Nov 2013 10:25:13 -0800 Subject: An API for generating keys --- cryptography/fernet.py | 4 ++++ docs/fernet.rst | 13 ++++++------- tests/test_fernet.py | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 1c6cb5dd..ba2ff4e3 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -67,6 +67,10 @@ class Fernet(object): self.encryption_key = key[16:] self.backend = backend + @classmethod + def generate_key(cls): + return base64.urlsafe_b64encode(os.urandom(32)) + def encrypt(self, data): current_time = int(time.time()) iv = os.urandom(16) diff --git a/docs/fernet.rst b/docs/fernet.rst index c95077bb..241bf1ea 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -3,13 +3,6 @@ Fernet .. currentmodule:: cryptography.fernet -.. testsetup:: - - import base64 - import binascii - key = base64.urlsafe_b64encode(binascii.unhexlify(b"0" * 64)) - - `Fernet`_ is an implementation of symmetric (also known as "secret key") authenticated cryptography. Fernet provides guarantees that a message encrypted using it cannot be manipulated or read without the key. @@ -21,6 +14,7 @@ using it cannot be manipulated or read without the key. .. doctest:: >>> from cryptography.fernet import Fernet + >>> key = Fernet.generate_key() >>> f = Fernet(key) >>> ciphertext = f.encrypt(b"my deep dark secret") >>> ciphertext @@ -32,6 +26,11 @@ using it cannot be manipulated or read without the key. kept secret. Anyone with this key is able to create and read messages. + .. classmethod:: generate_key() + + Generates a fresh fernet key. Keep this some place safe! If you lose it + you'll no longer be able to decrypt messages; if anyone else gains + access to it, they'll be able to decrypt all of your messages. .. method:: encrypt(plaintext) diff --git a/tests/test_fernet.py b/tests/test_fernet.py index 8759229a..af64175e 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -76,5 +76,5 @@ class TestFernet(object): @pytest.mark.parametrize("message", [b"", b"Abc!", b"\x00\xFF\x00\x80"]) def test_roundtrips(self, message): - f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32)) + f = Fernet(Fernet.generate_key()) assert f.decrypt(f.encrypt(message)) == message -- cgit v1.2.3 From 033af15d5f8d98007834be4aac4f260327e3c0c1 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 22 Nov 2013 10:32:56 -0800 Subject: removed silly line --- cryptography/fernet.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index ba2ff4e3..efa13b8f 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -60,7 +60,6 @@ bool Cryptography_constant_time_compare(uint8_t *a, size_t len_a, uint8_t *b, class Fernet(object): def __init__(self, key, backend=None): - super(Fernet, self).__init__() key = base64.urlsafe_b64decode(key) assert len(key) == 32 self.signing_key = key[:16] -- cgit v1.2.3 From 09bff867916af9694d66c2fea917d192f7dd1a25 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 22 Nov 2013 17:27:08 -0800 Subject: Handle another error case --- cryptography/fernet.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index efa13b8f..aa46b36f 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -129,7 +129,11 @@ class Fernet(object): decryptor = Cipher( algorithms.AES(self.encryption_key), modes.CBC(iv), self.backend ).decryptor() - plaintext_padded = decryptor.update(ciphertext) + decryptor.finalize() + plaintext_padded = decryptor.update(ciphertext) + try: + plaintext_padded += decryptor.finalize() + except ValueError: + raise InvalidToken unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() unpadded = unpadder.update(plaintext_padded) -- cgit v1.2.3 From 3417656d6e35bd5c6e4687bb7d008be6d8c73b43 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 23 Nov 2013 07:47:23 -0800 Subject: Handle the clock skew check --- cryptography/fernet.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index aa46b36f..2ae0ae8b 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -57,6 +57,8 @@ bool Cryptography_constant_time_compare(uint8_t *a, size_t len_a, uint8_t *b, } """) +_MAX_CLOCK_SKEW = 60 + class Fernet(object): def __init__(self, key, backend=None): @@ -117,6 +119,8 @@ class Fernet(object): if ttl is not None: if struct.unpack(">Q", timestamp)[0] + ttl < current_time: raise InvalidToken + if current_time + _MAX_CLOCK_SKEW < struct.unpack(">Q", timestamp)[0]: + raise InvalidToken h = HMAC(self.signing_key, hashes.SHA256(), self.backend) h.update(data[:-32]) hmac = h.finalize() -- cgit v1.2.3 From ebf3428ebddb7587b5f25497bed73489386126b5 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 27 Nov 2013 08:46:58 -0600 Subject: For now always use the default backend --- cryptography/fernet.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 2ae0ae8b..6220e9c7 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -21,6 +21,7 @@ import cffi import six +from cryptography.hazmat.bindings import default_backend from cryptography.hazmat.primitives import padding, hashes from cryptography.hazmat.primitives.hmac import HMAC from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes @@ -61,12 +62,12 @@ _MAX_CLOCK_SKEW = 60 class Fernet(object): - def __init__(self, key, backend=None): + def __init__(self, key): key = base64.urlsafe_b64decode(key) assert len(key) == 32 self.signing_key = key[:16] self.encryption_key = key[16:] - self.backend = backend + self.backend = default_backend() @classmethod def generate_key(cls): -- cgit v1.2.3 From 022bc3afd8b8901b940a6bf0aa4f863914557c7e Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 14 Dec 2013 08:35:36 -0800 Subject: Replace our constant time check with the new module --- cryptography/fernet.py | 36 ++---------------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 6220e9c7..3fcd187c 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -17,12 +17,10 @@ import os import struct import time -import cffi - import six from cryptography.hazmat.bindings import default_backend -from cryptography.hazmat.primitives import padding, hashes +from cryptography.hazmat.primitives import padding, hashes, constant_time from cryptography.hazmat.primitives.hmac import HMAC from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes @@ -31,33 +29,6 @@ class InvalidToken(Exception): pass -_ffi = cffi.FFI() -_ffi.cdef(""" -bool Cryptography_constant_time_compare(uint8_t *, size_t, uint8_t *, size_t); -""") -_lib = _ffi.verify(""" -#include - -bool Cryptography_constant_time_compare(uint8_t *a, size_t len_a, uint8_t *b, - size_t len_b) { - size_t i = 0; - uint8_t mismatch = 0; - if (len_a != len_b) { - return false; - } - for (i = 0; i < len_a; i++) { - mismatch |= a[i] ^ b[i]; - } - - /* Make sure any bits set are copied to the lowest bit */ - mismatch |= mismatch >> 4; - mismatch |= mismatch >> 2; - mismatch |= mismatch >> 1; - /* Now check the low bit to see if it's set */ - return (mismatch & 1) == 0; -} -""") - _MAX_CLOCK_SKEW = 60 @@ -125,10 +96,7 @@ class Fernet(object): h = HMAC(self.signing_key, hashes.SHA256(), self.backend) h.update(data[:-32]) hmac = h.finalize() - valid = _lib.Cryptography_constant_time_compare( - hmac, len(hmac), data[-32:], 32 - ) - if not valid: + if not constant_time.bytes_eq(hmac, data[-32:]): raise InvalidToken decryptor = Cipher( -- cgit v1.2.3 From badee1b5faf9c6bcc099aee0c6c1d9d29af8b7c7 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 14 Dec 2013 08:43:51 -0800 Subject: Reduce duplication --- cryptography/fernet.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 3fcd187c..3cab5479 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -85,13 +85,13 @@ class Fernet(object): raise InvalidToken assert six.indexbytes(data, 0) == 0x80 - timestamp = data[1:9] + timestamp = struct.unpack(">Q", data[1:9])[0] iv = data[9:25] ciphertext = data[25:-32] if ttl is not None: - if struct.unpack(">Q", timestamp)[0] + ttl < current_time: + if timestamp + ttl < current_time: raise InvalidToken - if current_time + _MAX_CLOCK_SKEW < struct.unpack(">Q", timestamp)[0]: + if current_time + _MAX_CLOCK_SKEW < timestamp: raise InvalidToken h = HMAC(self.signing_key, hashes.SHA256(), self.backend) h.update(data[:-32]) -- cgit v1.2.3 From c3f7c37f5718b5deb08edb41125d8c7fb3733cfc Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 14 Dec 2013 08:58:35 -0800 Subject: flake8 --- cryptography/fernet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 3cab5479..f2dd9341 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -89,7 +89,7 @@ class Fernet(object): iv = data[9:25] ciphertext = data[25:-32] if ttl is not None: - if timestamp + ttl < current_time: + if timestamp + ttl < current_time: raise InvalidToken if current_time + _MAX_CLOCK_SKEW < timestamp: raise InvalidToken -- cgit v1.2.3 From f272c14e7d41d84e095f61410fc8006e99d802e7 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 15 Dec 2013 22:18:49 -0800 Subject: Update import location --- cryptography/fernet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index f2dd9341..8bcaa40a 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -19,7 +19,7 @@ import time import six -from cryptography.hazmat.bindings import default_backend +from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import padding, hashes, constant_time from cryptography.hazmat.primitives.hmac import HMAC from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -- cgit v1.2.3 From 62aefffb1396190930074bf04c91459d1536bd0e Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 16 Dec 2013 09:15:13 -0800 Subject: So the tests don't all explode --- cryptography/hazmat/primitives/ciphers/modes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py index 63a69ac4..51a1047c 100644 --- a/cryptography/hazmat/primitives/ciphers/modes.py +++ b/cryptography/hazmat/primitives/ciphers/modes.py @@ -94,3 +94,7 @@ class GCM(object): def __init__(self, initialization_vector, tag=None): self.initialization_vector = initialization_vector self.tag = tag + + def validate_for_algorithm(self, algorithm): + # TODO: figure out what this should do + pass -- cgit v1.2.3 From 6cf242bee212b5b6069865a48c6bdc4836f78ff6 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 16 Dec 2013 11:17:07 -0800 Subject: Document the other consequence of losing your key --- docs/contributing.rst | 3 ++- docs/fernet.rst | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/contributing.rst b/docs/contributing.rst index cb9c7283..036043f5 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -244,7 +244,8 @@ Use `tox`_ to build the documentation. For example: docs: commands succeeded congratulations :) -The HTML documentation index can now be found at ``docs/_build/html/index.html`` +The HTML documentation index can now be found at +``docs/_build/html/index.html``. .. _`GitHub`: https://github.com/pyca/cryptography diff --git a/docs/fernet.rst b/docs/fernet.rst index 241bf1ea..3f0cdded 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -30,7 +30,9 @@ using it cannot be manipulated or read without the key. Generates a fresh fernet key. Keep this some place safe! If you lose it you'll no longer be able to decrypt messages; if anyone else gains - access to it, they'll be able to decrypt all of your messages. + access to it, they'll be able to decrypt all of your messages, and + they'll also be able forge arbitrary messages which will be + authenticated and decrypted. .. method:: encrypt(plaintext) -- cgit v1.2.3 From fae20715b85e84297f01b60fc153cde93a7549c7 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 16 Dec 2013 15:29:30 -0800 Subject: Address dreid's comments --- cryptography/fernet.py | 20 ++++++++++++-------- tests/test_fernet.py | 26 ++++++++++++++++---------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 8bcaa40a..c0c5631f 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -33,12 +33,16 @@ _MAX_CLOCK_SKEW = 60 class Fernet(object): - def __init__(self, key): + def __init__(self, key, backend=None): + if backend is None: + backend = default_backend() + key = base64.urlsafe_b64decode(key) assert len(key) == 32 - self.signing_key = key[:16] - self.encryption_key = key[16:] - self.backend = default_backend() + + self._signing_key = key[:16] + self._encryption_key = key[16:] + self._backend = backend @classmethod def generate_key(cls): @@ -58,7 +62,7 @@ class Fernet(object): padder = padding.PKCS7(algorithms.AES.block_size).padder() padded_data = padder.update(data) + padder.finalize() encryptor = Cipher( - algorithms.AES(self.encryption_key), modes.CBC(iv), self.backend + algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend ).encryptor() ciphertext = encryptor.update(padded_data) + encryptor.finalize() @@ -66,7 +70,7 @@ class Fernet(object): b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext ) - h = HMAC(self.signing_key, hashes.SHA256(), self.backend) + h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend) h.update(basic_parts) hmac = h.finalize() return base64.urlsafe_b64encode(basic_parts + hmac) @@ -93,14 +97,14 @@ class Fernet(object): raise InvalidToken if current_time + _MAX_CLOCK_SKEW < timestamp: raise InvalidToken - h = HMAC(self.signing_key, hashes.SHA256(), self.backend) + h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend) h.update(data[:-32]) hmac = h.finalize() if not constant_time.bytes_eq(hmac, data[-32:]): raise InvalidToken decryptor = Cipher( - algorithms.AES(self.encryption_key), modes.CBC(iv), self.backend + algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend ).decryptor() plaintext_padded = decryptor.update(ciphertext) try: diff --git a/tests/test_fernet.py b/tests/test_fernet.py index af64175e..48df867c 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -24,6 +24,7 @@ import pytest import six from cryptography.fernet import Fernet, InvalidToken +from cryptography.hazmat.backends import default_backend def json_parametrize(keys, fname): @@ -40,8 +41,8 @@ class TestFernet(object): @json_parametrize( ("secret", "now", "iv", "src", "token"), "generate.json", ) - def test_generate(self, secret, now, iv, src, token): - f = Fernet(secret.encode("ascii")) + def test_generate(self, secret, now, iv, src, token, backend): + f = Fernet(secret.encode("ascii"), backend=backend) actual_token = f._encrypt_from_parts( src.encode("ascii"), calendar.timegm(iso8601.parse_date(now).utctimetuple()), @@ -52,29 +53,34 @@ class TestFernet(object): @json_parametrize( ("secret", "now", "src", "ttl_sec", "token"), "verify.json", ) - def test_verify(self, secret, now, src, ttl_sec, token, monkeypatch): - f = Fernet(secret.encode("ascii")) + def test_verify(self, secret, now, src, ttl_sec, token, backend, + monkeypatch): + f = Fernet(secret.encode("ascii"), backend=backend) current_time = calendar.timegm(iso8601.parse_date(now).utctimetuple()) monkeypatch.setattr(time, "time", lambda: current_time) payload = f.decrypt(token.encode("ascii"), ttl=ttl_sec) assert payload == src.encode("ascii") @json_parametrize(("secret", "token", "now", "ttl_sec"), "invalid.json") - def test_invalid(self, secret, token, now, ttl_sec, monkeypatch): - f = Fernet(secret.encode("ascii")) + def test_invalid(self, secret, token, now, ttl_sec, backend, monkeypatch): + f = Fernet(secret.encode("ascii"), backend=backend) current_time = calendar.timegm(iso8601.parse_date(now).utctimetuple()) monkeypatch.setattr(time, "time", lambda: current_time) with pytest.raises(InvalidToken): f.decrypt(token.encode("ascii"), ttl=ttl_sec) - def test_unicode(self): - f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32)) + def test_unicode(self, backend): + f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) with pytest.raises(TypeError): f.encrypt(six.u("")) with pytest.raises(TypeError): f.decrypt(six.u("")) @pytest.mark.parametrize("message", [b"", b"Abc!", b"\x00\xFF\x00\x80"]) - def test_roundtrips(self, message): - f = Fernet(Fernet.generate_key()) + def test_roundtrips(self, message, backend): + f = Fernet(Fernet.generate_key(), backend=backend) assert f.decrypt(f.encrypt(message)) == message + + def test_default_backend(self): + f = Fernet(Fernet.generate_key()) + assert f._backend is default_backend() -- cgit v1.2.3 From a8f0b63dddc6a22a1a982c6217d4cef2f598b781 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 16 Dec 2013 15:44:06 -0800 Subject: Replace assertions with real error checks --- cryptography/fernet.py | 9 +++++++-- tests/test_fernet.py | 9 +++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index c0c5631f..c5474af4 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -38,7 +38,10 @@ class Fernet(object): backend = default_backend() key = base64.urlsafe_b64decode(key) - assert len(key) == 32 + if len(key) != 32: + raise ValueError( + "Fernet key must be 32 url-safe base64-encoded bytes" + ) self._signing_key = key[:16] self._encryption_key = key[16:] @@ -88,7 +91,9 @@ class Fernet(object): except (TypeError, binascii.Error): raise InvalidToken - assert six.indexbytes(data, 0) == 0x80 + if six.indexbytes(data, 0) != 0x80: + raise InvalidToken + timestamp = struct.unpack(">Q", data[1:9])[0] iv = data[9:25] ciphertext = data[25:-32] diff --git a/tests/test_fernet.py b/tests/test_fernet.py index 48df867c..77661180 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -69,6 +69,11 @@ class TestFernet(object): with pytest.raises(InvalidToken): f.decrypt(token.encode("ascii"), ttl=ttl_sec) + def test_invalid_start_byte(self, backend): + f = Fernet(Fernet.generate_key(), backend=backend) + with pytest.raises(InvalidToken): + f.decrypt(base64.urlsafe_b64encode(b"\x81")) + def test_unicode(self, backend): f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) with pytest.raises(TypeError): @@ -84,3 +89,7 @@ class TestFernet(object): def test_default_backend(self): f = Fernet(Fernet.generate_key()) assert f._backend is default_backend() + + def test_bad_key(self, backend): + with pytest.raises(ValueError): + Fernet(base64.urlsafe_b64encode(b"abc"), backend=backend) -- cgit v1.2.3 From e9083291b9dac1c1ab7b0a2da38f9455536a807d Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 17 Dec 2013 16:56:29 -0800 Subject: Include more info in the title --- docs/fernet.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/fernet.rst b/docs/fernet.rst index 3f0cdded..287c991b 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -1,5 +1,5 @@ -Fernet -====== +Fernet (Symmetric encryption) +============================= .. currentmodule:: cryptography.fernet -- cgit v1.2.3 From 0d0896319f59fe7b03d8ef6a153275f87816976b Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 17 Dec 2013 20:23:43 -0800 Subject: Use the term fernet token --- cryptography/fernet.py | 6 +++--- docs/fernet.rst | 24 +++++++++++++----------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index c5474af4..10698f29 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -78,8 +78,8 @@ class Fernet(object): hmac = h.finalize() return base64.urlsafe_b64encode(basic_parts + hmac) - def decrypt(self, data, ttl=None): - if isinstance(data, six.text_type): + def decrypt(self, token, ttl=None): + if isinstance(token, six.text_type): raise TypeError( "Unicode-objects must be encoded before decryption" ) @@ -87,7 +87,7 @@ class Fernet(object): current_time = int(time.time()) try: - data = base64.urlsafe_b64decode(data) + data = base64.urlsafe_b64decode(token) except (TypeError, binascii.Error): raise InvalidToken diff --git a/docs/fernet.rst b/docs/fernet.rst index 287c991b..0122e364 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -16,10 +16,10 @@ using it cannot be manipulated or read without the key. >>> from cryptography.fernet import Fernet >>> key = Fernet.generate_key() >>> f = Fernet(key) - >>> ciphertext = f.encrypt(b"my deep dark secret") - >>> ciphertext + >>> token = f.encrypt(b"my deep dark secret") + >>> token '...' - >>> f.decrypt(ciphertext) + >>> f.decrypt(token) 'my deep dark secret' :param bytes key: A URL-safe base64-encoded 32-byte key. This **must** be @@ -38,11 +38,13 @@ using it cannot be manipulated or read without the key. :param bytes plaintext: The message you would like to encrypt. :returns bytes: A secure message which cannot be read or altered - without the key. It is URL-safe base64-encoded. + without the key. It is URL-safe base64-encoded. This is + refered to as a "Fernet token". - .. method:: decrypt(ciphertext, ttl=None) + .. method:: decrypt(token, ttl=None) - :param bytes ciphertext: An encrypted message. + :param bytes token: The Fernet token. This is the result of calling + :meth:`encrypt`. :param int ttl: Optionally, the number of seconds old a message may be for it to be valid. If the message is older than ``ttl`` seconds (from the time it was originally @@ -50,11 +52,11 @@ using it cannot be manipulated or read without the key. provided (or is ``None``), the age of the message is not considered. :returns bytes: The original plaintext. - :raises InvalidToken: If the ``ciphertext`` is in any way invalid, this - exception is raised. A ciphertext may be invalid - for a number of reasons: it is older than the - ``ttl``, it is malformed, or it does not have a - valid signature. + :raises InvalidToken: If the ``token`` is in any way invalid, this + exception is raised. A token may be invalid for a + number of reasons: it is older than the ``ttl``, + it is malformed, or it does not have a valid + signature. .. class:: InvalidToken -- cgit v1.2.3 From 05515723738870170b05b47ee260564b9ebe62f9 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 17 Dec 2013 20:43:59 -0800 Subject: Mention that the timestamp is plaintext --- docs/fernet.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/fernet.rst b/docs/fernet.rst index 0122e364..a47ae2e3 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -39,7 +39,10 @@ using it cannot be manipulated or read without the key. :param bytes plaintext: The message you would like to encrypt. :returns bytes: A secure message which cannot be read or altered without the key. It is URL-safe base64-encoded. This is - refered to as a "Fernet token". + refered to as a "Fernet token". Note that this *does* + contain the current time when it was generated in + plaintext, the time a message was created will + therefore be visible to a possible attacker. .. method:: decrypt(token, ttl=None) -- cgit v1.2.3 From e78960fa8c2a210484695bf2e20c4847313cf5a0 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 20 Dec 2013 11:02:33 -0800 Subject: Handle invalid timestamp length --- cryptography/fernet.py | 5 ++++- tests/test_fernet.py | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 10698f29..b59f6a94 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -94,7 +94,10 @@ class Fernet(object): if six.indexbytes(data, 0) != 0x80: raise InvalidToken - timestamp = struct.unpack(">Q", data[1:9])[0] + try: + timestamp, = struct.unpack(">Q", data[1:9]) + except struct.error: + raise InvalidToken iv = data[9:25] ciphertext = data[25:-32] if ttl is not None: diff --git a/tests/test_fernet.py b/tests/test_fernet.py index 77661180..45188c47 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -74,6 +74,11 @@ class TestFernet(object): with pytest.raises(InvalidToken): f.decrypt(base64.urlsafe_b64encode(b"\x81")) + def test_timestamp_too_short(self, backend): + f = Fernet(Fernet.generate_key(), backend=backend) + with pytest.raises(InvalidToken): + f.decrypt(base64.urlsafe_b64encode(b"\x80abc")) + def test_unicode(self, backend): f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) with pytest.raises(TypeError): -- cgit v1.2.3 From d66f3726a5e945e74a32d10895b0f6acf5676f91 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 20 Dec 2013 11:05:13 -0800 Subject: Don't look at other material until the signature is validated --- cryptography/fernet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index b59f6a94..9f4294f0 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -98,8 +98,6 @@ class Fernet(object): timestamp, = struct.unpack(">Q", data[1:9]) except struct.error: raise InvalidToken - iv = data[9:25] - ciphertext = data[25:-32] if ttl is not None: if timestamp + ttl < current_time: raise InvalidToken @@ -111,6 +109,8 @@ class Fernet(object): if not constant_time.bytes_eq(hmac, data[-32:]): raise InvalidToken + iv = data[9:25] + ciphertext = data[25:-32] decryptor = Cipher( algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend ).decryptor() -- cgit v1.2.3 From 3ef458ac7dc021378d8ca14bfcf654c0d51d9a0d Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 20 Dec 2013 13:19:43 -0800 Subject: Reword slightly --- docs/fernet.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/fernet.rst b/docs/fernet.rst index a47ae2e3..4e618f59 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -39,10 +39,10 @@ using it cannot be manipulated or read without the key. :param bytes plaintext: The message you would like to encrypt. :returns bytes: A secure message which cannot be read or altered without the key. It is URL-safe base64-encoded. This is - refered to as a "Fernet token". Note that this *does* - contain the current time when it was generated in - plaintext, the time a message was created will - therefore be visible to a possible attacker. + refered to as a "Fernet token". Note that this contains + the current time when it was generated in *plaintext*, + the time a message was created will therefore be + visible to a possible attacker. .. method:: decrypt(token, ttl=None) -- cgit v1.2.3 From 32dc4e4e9f3036f04598134369af50fd70143dae Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 20 Dec 2013 13:26:12 -0800 Subject: Make into a warning as suggested by @dstufft --- docs/fernet.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/fernet.rst b/docs/fernet.rst index 4e618f59..68184b3a 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -39,10 +39,13 @@ using it cannot be manipulated or read without the key. :param bytes plaintext: The message you would like to encrypt. :returns bytes: A secure message which cannot be read or altered without the key. It is URL-safe base64-encoded. This is - refered to as a "Fernet token". Note that this contains - the current time when it was generated in *plaintext*, - the time a message was created will therefore be - visible to a possible attacker. + refered to as a "Fernet token". + + .. warning:: + + The encrypted message contains the current time when it was + generated in *plaintext*, the time a message was created will + therefore be visible to a possible attacker. .. method:: decrypt(token, ttl=None) -- cgit v1.2.3 From 719eb6a412b5d3eab3ca84a9d4e8af76955bcbcc Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 20 Dec 2013 13:35:57 -0800 Subject: Linkify this --- docs/fernet.rst | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/fernet.rst b/docs/fernet.rst index 68184b3a..2fe2b860 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -41,7 +41,7 @@ using it cannot be manipulated or read without the key. without the key. It is URL-safe base64-encoded. This is refered to as a "Fernet token". - .. warning:: + .. note:: The encrypted message contains the current time when it was generated in *plaintext*, the time a message was created will @@ -58,11 +58,14 @@ using it cannot be manipulated or read without the key. provided (or is ``None``), the age of the message is not considered. :returns bytes: The original plaintext. - :raises InvalidToken: If the ``token`` is in any way invalid, this - exception is raised. A token may be invalid for a - number of reasons: it is older than the ``ttl``, - it is malformed, or it does not have a valid - signature. + :raises cryptography.fernet.InvalidToken: If the ``token`` is in any + way invalid, this exception + is raised. A token may be + invalid for a number of + reasons: it is older than the + ``ttl``, it is malformed, or + it does not have a valid + signature. .. class:: InvalidToken -- cgit v1.2.3 From 2724ff6af8ba5f8dfd1f0f511ed95fab5cd8abd8 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 20 Dec 2013 13:51:42 -0800 Subject: Link from symmetric encryption to fernet --- docs/cryptography-docs.py | 17 +++++++++++++++-- docs/hazmat/primitives/symmetric-encryption.rst | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/cryptography-docs.py b/docs/cryptography-docs.py index ea7e8eef..f07c18bb 100644 --- a/docs/cryptography-docs.py +++ b/docs/cryptography-docs.py @@ -6,17 +6,30 @@ from sphinx.util.compat import Directive, make_admonition DANGER_MESSAGE = """ This is a "Hazardous Materials" module. You should **ONLY** use it if you're 100% absolutely sure that you know what you're doing because this module is -full of land mines, dragons, and dinosaurs with laser guns. """ +full of land mines, dragons, and dinosaurs with laser guns. +""" + +DANGER_ALTERNATE = """ + +You may instead be interested in :doc:`{alternate}`. +""" + class HazmatDirective(Directive): + has_content = True + def run(self): + message = DANGER_MESSAGE + if self.content: + message += DANGER_ALTERNATE.format(alternate=self.content[0]) + ad = make_admonition( Hazmat, self.name, [], self.options, - nodes.paragraph("", DANGER_MESSAGE), + nodes.paragraph("", message), self.lineno, self.content_offset, self.block_text, diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index f4d0457a..7b012975 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -1,4 +1,4 @@ -.. hazmat:: +.. hazmat:: /fernet Symmetric Encryption -- cgit v1.2.3 From 3ac297e4c9b655b3222da1830e9677c9d03a3926 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 20 Dec 2013 14:16:34 -0800 Subject: flake8 fix --- docs/cryptography-docs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/cryptography-docs.py b/docs/cryptography-docs.py index f07c18bb..0252d693 100644 --- a/docs/cryptography-docs.py +++ b/docs/cryptography-docs.py @@ -15,7 +15,6 @@ You may instead be interested in :doc:`{alternate}`. """ - class HazmatDirective(Directive): has_content = True -- cgit v1.2.3 From b9bc6c3e4c9b647de1a1a2dd852ab591e9a69b01 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 27 Dec 2013 08:23:14 -0800 Subject: Switch to the newer, safer, API --- cryptography/fernet.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cryptography/fernet.py b/cryptography/fernet.py index 9f4294f0..c19309d5 100644 --- a/cryptography/fernet.py +++ b/cryptography/fernet.py @@ -19,8 +19,9 @@ import time import six +from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import padding, hashes, constant_time +from cryptography.hazmat.primitives import padding, hashes from cryptography.hazmat.primitives.hmac import HMAC from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes @@ -105,8 +106,9 @@ class Fernet(object): raise InvalidToken h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend) h.update(data[:-32]) - hmac = h.finalize() - if not constant_time.bytes_eq(hmac, data[-32:]): + try: + h.verify(data[-32:]) + except InvalidSignature: raise InvalidToken iv = data[9:25] -- cgit v1.2.3 From be60d7065c5a55a06a1e3f301de8f1dbfc7c8eb3 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 30 Dec 2013 21:08:57 -0800 Subject: alphabetize --- dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 1ec04223..0f52900b 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -4,6 +4,6 @@ iso8601 pretend pytest sphinx -tox sphinx_rtd_theme +tox -e . -- cgit v1.2.3 From 681fca8f43f9cbed97ce2df0b871447953c7edda Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 31 Dec 2013 14:13:39 -0800 Subject: Rearange sentence on recommendation of @jacobian --- docs/cryptography-docs.py | 3 +++ docs/fernet.rst | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/cryptography-docs.py b/docs/cryptography-docs.py index 0252d693..f27a8467 100644 --- a/docs/cryptography-docs.py +++ b/docs/cryptography-docs.py @@ -23,6 +23,9 @@ class HazmatDirective(Directive): if self.content: message += DANGER_ALTERNATE.format(alternate=self.content[0]) + import pdb + pdb.set_trace() + ad = make_admonition( Hazmat, self.name, diff --git a/docs/fernet.rst b/docs/fernet.rst index 2fe2b860..4e94e212 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -3,9 +3,9 @@ Fernet (Symmetric encryption) .. currentmodule:: cryptography.fernet -`Fernet`_ is an implementation of symmetric (also known as "secret key") -authenticated cryptography. Fernet provides guarantees that a message encrypted -using it cannot be manipulated or read without the key. +Fernet provides guarantees that a message encrypted using it cannot be +manipulated or read without the key. `Fernet`_ is an implementation of +symmetric (also known as "secret key") authenticated cryptography. .. class:: Fernet(key) -- cgit v1.2.3 From 09aa74635f54ace5480a6d502b0da92651f516b6 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 31 Dec 2013 15:18:34 -0800 Subject: Remove this one weird debugger --- docs/cryptography-docs.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/cryptography-docs.py b/docs/cryptography-docs.py index f27a8467..0252d693 100644 --- a/docs/cryptography-docs.py +++ b/docs/cryptography-docs.py @@ -23,9 +23,6 @@ class HazmatDirective(Directive): if self.content: message += DANGER_ALTERNATE.format(alternate=self.content[0]) - import pdb - pdb.set_trace() - ad = make_admonition( Hazmat, self.name, -- cgit v1.2.3 From 71e8ca0d79d8599f1f00d6ec18cb19a2ffabfc8d Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 1 Jan 2014 12:19:47 -0800 Subject: Explanatory comment --- cryptography/hazmat/primitives/ciphers/modes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cryptography/hazmat/primitives/ciphers/modes.py b/cryptography/hazmat/primitives/ciphers/modes.py index 51a1047c..31af5d6e 100644 --- a/cryptography/hazmat/primitives/ciphers/modes.py +++ b/cryptography/hazmat/primitives/ciphers/modes.py @@ -92,9 +92,11 @@ class GCM(object): name = "GCM" def __init__(self, initialization_vector, tag=None): + # len(initialization_vector) must in [1, 2 ** 64), but it's impossible + # to actually construct a bytes object that large, so we don't check + # for it self.initialization_vector = initialization_vector self.tag = tag def validate_for_algorithm(self, algorithm): - # TODO: figure out what this should do pass -- cgit v1.2.3 From 1225270be8af862a82e45342f086419dbedce0fb Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 2 Jan 2014 11:10:35 -0800 Subject: Correct spelling, fix phrasing, line wrap. --- docs/hazmat/bindings/index.rst | 2 +- docs/hazmat/primitives/hmac.rst | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/hazmat/bindings/index.rst b/docs/hazmat/bindings/index.rst index 809eddfc..e2a17591 100644 --- a/docs/hazmat/bindings/index.rst +++ b/docs/hazmat/bindings/index.rst @@ -6,7 +6,7 @@ Bindings .. currentmodule:: cryptography.hazmat.bindings ``cryptography`` aims to provide low-level CFFI based bindings to multiple -native C libraries. These provide no automatic initialisation of the library +native C libraries. These provide no automatic initialization of the library and may not provide complete wrappers for its API. Using these functions directly is likely to require you to be careful in diff --git a/docs/hazmat/primitives/hmac.rst b/docs/hazmat/primitives/hmac.rst index b8f94fd2..dc5c54f8 100644 --- a/docs/hazmat/primitives/hmac.rst +++ b/docs/hazmat/primitives/hmac.rst @@ -74,8 +74,11 @@ message. .. method:: verify(signature) - Finalize the current context and securely compare digest to ``signature``. + Finalize the current context and securely compare digest to + ``signature``. - :param bytes signature: The bytes of the HMAC signature recieved. + :param bytes signature: The bytes to compare the current digest + against. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` - :raises cryptography.exceptions.InvalidSignature: If signature does not match digest + :raises cryptography.exceptions.InvalidSignature: If signature does not + match digest -- cgit v1.2.3 From 9a00f0539d5ab9b03b84625791663f11d7bed75c Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 2 Jan 2014 13:09:34 -0800 Subject: All the necessary things for setup.py to build cffi stuff --- cryptography/hazmat/bindings/openssl/binding.py | 1 + cryptography/hazmat/primitives/constant_time.py | 4 +++- cryptography/hazmat/primitives/padding.py | 4 +++- setup.py | 20 ++++++++++++++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py index 8b5e3449..ccda368e 100644 --- a/cryptography/hazmat/bindings/openssl/binding.py +++ b/cryptography/hazmat/bindings/openssl/binding.py @@ -135,6 +135,7 @@ class Binding(object): customizations ), libraries=["crypto", "ssl"], + ext_package="cryptography", ) for name in cls._modules: diff --git a/cryptography/hazmat/primitives/constant_time.py b/cryptography/hazmat/primitives/constant_time.py index 6502803e..c3d81221 100644 --- a/cryptography/hazmat/primitives/constant_time.py +++ b/cryptography/hazmat/primitives/constant_time.py @@ -42,7 +42,9 @@ uint8_t Cryptography_constant_time_bytes_eq(uint8_t *a, size_t len_a, /* Now check the low bit to see if it's set */ return (mismatch & 1) == 0; } -""") +""", + ext_package="cryptography", +) def bytes_eq(a, b): diff --git a/cryptography/hazmat/primitives/padding.py b/cryptography/hazmat/primitives/padding.py index e517dee0..7c5271cb 100644 --- a/cryptography/hazmat/primitives/padding.py +++ b/cryptography/hazmat/primitives/padding.py @@ -59,7 +59,9 @@ uint8_t Cryptography_check_pkcs7_padding(const uint8_t *data, /* Now check the low bit to see if it's set */ return (mismatch & 1) == 0; } -""") +""", + ext_package="cryptography", +) class PKCS7(object): diff --git a/setup.py b/setup.py index 1856cadb..0776ba6e 100644 --- a/setup.py +++ b/setup.py @@ -10,6 +10,8 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. +from distutils.command.build import build + from setuptools import setup, find_packages @@ -30,6 +32,20 @@ setup_requires = [ CFFI_DEPENDENCY, ] + +class cffi_build(build): + def finalize_options(self): + from cryptography.hazmat.bindings.openssl.binding import Binding + from cryptography.hazmat.primitives import constant_time, padding + + self.distribution.ext_modules = [ + Binding().ffi.verifier.get_extension(), + constant_time._ffi.verifier.get_extension(), + padding._ffi.verifier.get_extension() + ] + build.finalize_options(self) + + setup( name=about["__title__"], version=about["__version__"], @@ -70,4 +86,8 @@ setup( # for cffi zip_safe=False, + ext_package="cryptography", + cmdclass={ + "build": cffi_build, + } ) -- cgit v1.2.3 From 91f119e75865e72d66e8c8b4f24164988e5b8d20 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 2 Jan 2014 13:12:59 -0800 Subject: six is now required at build time --- setup.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 0776ba6e..2ff57e97 100644 --- a/setup.py +++ b/setup.py @@ -23,15 +23,11 @@ with open("cryptography/__about__.py") as fp: CFFI_DEPENDENCY = "cffi>=0.6" SIX_DEPENDENCY = "six>=1.4.1" -install_requires = [ +requirements = [ CFFI_DEPENDENCY, SIX_DEPENDENCY ] -setup_requires = [ - CFFI_DEPENDENCY, -] - class cffi_build(build): def finalize_options(self): @@ -81,8 +77,8 @@ setup( packages=find_packages(exclude=["tests", "tests.*"]), - install_requires=install_requires, - setup_requires=setup_requires, + install_requires=requirements, + setup_requires=requirements, # for cffi zip_safe=False, -- cgit v1.2.3 From eba623f88260a9a076ce3a4a71d1d61755327e05 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 2 Jan 2014 13:47:38 -0800 Subject: Some flake8 fixes --- cryptography/hazmat/primitives/constant_time.py | 3 ++- cryptography/hazmat/primitives/padding.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cryptography/hazmat/primitives/constant_time.py b/cryptography/hazmat/primitives/constant_time.py index c3d81221..e88a0d95 100644 --- a/cryptography/hazmat/primitives/constant_time.py +++ b/cryptography/hazmat/primitives/constant_time.py @@ -23,7 +23,8 @@ _ffi.cdef(""" uint8_t Cryptography_constant_time_bytes_eq(uint8_t *, size_t, uint8_t *, size_t); """) -_lib = _ffi.verify(""" +_lib = _ffi.verify( + """ uint8_t Cryptography_constant_time_bytes_eq(uint8_t *a, size_t len_a, uint8_t *b, size_t len_b) { size_t i = 0; diff --git a/cryptography/hazmat/primitives/padding.py b/cryptography/hazmat/primitives/padding.py index 7c5271cb..ddb2c63c 100644 --- a/cryptography/hazmat/primitives/padding.py +++ b/cryptography/hazmat/primitives/padding.py @@ -23,7 +23,8 @@ _ffi = cffi.FFI() _ffi.cdef(""" uint8_t Cryptography_check_pkcs7_padding(const uint8_t *, uint8_t); """) -_lib = _ffi.verify(""" +_lib = _ffi.verify( + """ /* Returns the value of the input with the most-significant-bit copied to all of the bits. */ static uint8_t Cryptography_DUPLICATE_MSB_TO_ALL(uint8_t a) { -- cgit v1.2.3 From f51f2c1c5f24f627cfe57c2ea6d821c87f7a6c20 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 3 Jan 2014 07:33:01 -0800 Subject: Include a long description in our setup.py --- setup.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/setup.py b/setup.py index 1856cadb..e2df1c46 100644 --- a/setup.py +++ b/setup.py @@ -10,6 +10,8 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. +import os + from setuptools import setup, find_packages @@ -30,11 +32,16 @@ setup_requires = [ CFFI_DEPENDENCY, ] +with open(os.path.join(os.path.dirname(__file__), "README.rst")) as f: + long_description = f.read() + + setup( name=about["__title__"], version=about["__version__"], description=about["__summary__"], + long_description=long_description, license=about["__license__"], url=about["__uri__"], -- cgit v1.2.3 From 7630d6c50b3112eae69de606760cfb3e3b070c26 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 3 Jan 2014 07:34:43 -0800 Subject: Also use an absolute path for the about file --- setup.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index e2df1c46..3202f843 100644 --- a/setup.py +++ b/setup.py @@ -15,9 +15,11 @@ import os from setuptools import setup, find_packages +base_dir = os.path.dirname(__file__) + about = {} -with open("cryptography/__about__.py") as fp: - exec(fp.read(), about) +with open(os.path.join(base_dir, "cryptography", "__about__.py")) as f: + exec(f.read(), about) CFFI_DEPENDENCY = "cffi>=0.6" @@ -32,7 +34,7 @@ setup_requires = [ CFFI_DEPENDENCY, ] -with open(os.path.join(os.path.dirname(__file__), "README.rst")) as f: +with open(os.path.join(base_dir, "README.rst")) as f: long_description = f.read() -- cgit v1.2.3 From 0421ff0201dae6380bddae53c247801789a235a9 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 3 Jan 2014 10:13:20 -0600 Subject: add to author list --- cryptography/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography/__about__.py b/cryptography/__about__.py index 46212bff..54a9dbe6 100644 --- a/cryptography/__about__.py +++ b/cryptography/__about__.py @@ -26,7 +26,7 @@ __version__ = "0.1.dev1" __author__ = ("Alex Gaynor, Hynek Schlawack, Donald Stufft, " "Laurens Van Houtven, Jean-Paul Calderone, Christian Heimes, " - "and individual contributors.") + "Paul Kehrer, and individual contributors.") __email__ = "cryptography-dev@python.org" __license__ = "Apache License, Version 2.0" -- cgit v1.2.3 From f03334e25c3c31094015d1421feef7bcec9a9c1f Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 2 Jan 2014 23:16:14 -0600 Subject: backend support check now lists which backend caused the skip --- tests/test_utils.py | 2 +- tests/utils.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index c640367e..e3e53d63 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -50,7 +50,7 @@ def test_check_backend_support_skip(): funcargs={"backend": True}) with pytest.raises(pytest.skip.Exception) as exc_info: check_backend_support(item) - assert exc_info.value.args[0] == "Nope" + assert exc_info.value.args[0] == "Nope (True)" def test_check_backend_support_no_skip(): diff --git a/tests/utils.py b/tests/utils.py index beb2ca5d..693a0c8f 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -28,7 +28,9 @@ def check_backend_support(item): supported = item.keywords.get("supported") if supported and "backend" in item.funcargs: if not supported.kwargs["only_if"](item.funcargs["backend"]): - pytest.skip(supported.kwargs["skip_message"]) + pytest.skip("{0} ({1})".format( + supported.kwargs["skip_message"], item.funcargs["backend"] + )) elif supported: raise ValueError("This mark is only available on methods that take a " "backend") -- cgit v1.2.3 From 4969751fde0ef09cd72c738a80c32851c1b1f21d Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 3 Jan 2014 15:08:45 -0800 Subject: Explanatory comment --- setup.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/setup.py b/setup.py index fe37d9fb..0bdad485 100644 --- a/setup.py +++ b/setup.py @@ -33,6 +33,15 @@ requirements = [ class cffi_build(build): + """ + This class exists, instead of just providing ``ext_modules=[...]`` directly + in ``setup()`` because importing cryptography requires we have several + packages installed first. + + By doing the imports here we ensure that packages listed in + ``setup_requires`` are already installed. + """ + def finalize_options(self): from cryptography.hazmat.bindings.openssl.binding import Binding from cryptography.hazmat.primitives import constant_time, padding -- cgit v1.2.3 From 8d3857221ba0055e625e78783a98c27fcb192199 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 4 Jan 2014 11:45:18 -0600 Subject: Instantiate our hash objects used for supported checks --- tests/hazmat/primitives/test_hash_vectors.py | 16 ++++++++-------- tests/hazmat/primitives/test_hashes.py | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/hazmat/primitives/test_hash_vectors.py b/tests/hazmat/primitives/test_hash_vectors.py index 13ffc3fd..ca97fc11 100644 --- a/tests/hazmat/primitives/test_hash_vectors.py +++ b/tests/hazmat/primitives/test_hash_vectors.py @@ -24,7 +24,7 @@ from ...utils import load_hash_vectors @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA1), + only_if=lambda backend: backend.hash_supported(hashes.SHA1()), skip_message="Does not support SHA1", ) @pytest.mark.hash @@ -41,7 +41,7 @@ class TestSHA1(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA224), + only_if=lambda backend: backend.hash_supported(hashes.SHA224()), skip_message="Does not support SHA224", ) @pytest.mark.hash @@ -58,7 +58,7 @@ class TestSHA224(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA256), + only_if=lambda backend: backend.hash_supported(hashes.SHA256()), skip_message="Does not support SHA256", ) @pytest.mark.hash @@ -75,7 +75,7 @@ class TestSHA256(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA384), + only_if=lambda backend: backend.hash_supported(hashes.SHA384()), skip_message="Does not support SHA384", ) @pytest.mark.hash @@ -92,7 +92,7 @@ class TestSHA384(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA512), + only_if=lambda backend: backend.hash_supported(hashes.SHA512()), skip_message="Does not support SHA512", ) @pytest.mark.hash @@ -109,7 +109,7 @@ class TestSHA512(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160), + only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160()), skip_message="Does not support RIPEMD160", ) @pytest.mark.hash @@ -130,7 +130,7 @@ class TestRIPEMD160(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.Whirlpool), + only_if=lambda backend: backend.hash_supported(hashes.Whirlpool()), skip_message="Does not support Whirlpool", ) @pytest.mark.hash @@ -153,7 +153,7 @@ class TestWhirlpool(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.MD5), + only_if=lambda backend: backend.hash_supported(hashes.MD5()), skip_message="Does not support MD5", ) @pytest.mark.hash diff --git a/tests/hazmat/primitives/test_hashes.py b/tests/hazmat/primitives/test_hashes.py index c907ef61..9ca2feee 100644 --- a/tests/hazmat/primitives/test_hashes.py +++ b/tests/hazmat/primitives/test_hashes.py @@ -70,7 +70,7 @@ class TestHashContext(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA1), + only_if=lambda backend: backend.hash_supported(hashes.SHA1()), skip_message="Does not support SHA1", ) @pytest.mark.hash @@ -83,7 +83,7 @@ class TestSHA1(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA224), + only_if=lambda backend: backend.hash_supported(hashes.SHA224()), skip_message="Does not support SHA224", ) @pytest.mark.hash @@ -96,7 +96,7 @@ class TestSHA224(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA256), + only_if=lambda backend: backend.hash_supported(hashes.SHA256()), skip_message="Does not support SHA256", ) @pytest.mark.hash @@ -109,7 +109,7 @@ class TestSHA256(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA384), + only_if=lambda backend: backend.hash_supported(hashes.SHA384()), skip_message="Does not support SHA384", ) @pytest.mark.hash @@ -122,7 +122,7 @@ class TestSHA384(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.SHA512), + only_if=lambda backend: backend.hash_supported(hashes.SHA512()), skip_message="Does not support SHA512", ) @pytest.mark.hash @@ -135,7 +135,7 @@ class TestSHA512(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160), + only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160()), skip_message="Does not support RIPEMD160", ) @pytest.mark.hash @@ -148,7 +148,7 @@ class TestRIPEMD160(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.Whirlpool), + only_if=lambda backend: backend.hash_supported(hashes.Whirlpool()), skip_message="Does not support Whirlpool", ) @pytest.mark.hash @@ -161,7 +161,7 @@ class TestWhirlpool(object): @pytest.mark.supported( - only_if=lambda backend: backend.hash_supported(hashes.MD5), + only_if=lambda backend: backend.hash_supported(hashes.MD5()), skip_message="Does not support MD5", ) @pytest.mark.hash -- cgit v1.2.3 From 24684cababdd46dbc715087ff6a6fdb7f1cec8ec Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 4 Jan 2014 11:51:40 -0600 Subject: instantiate hash objects for hmac checks too --- tests/hazmat/primitives/test_hmac.py | 2 +- tests/hazmat/primitives/test_hmac_vectors.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/hazmat/primitives/test_hmac.py b/tests/hazmat/primitives/test_hmac.py index 04913af6..dd9cdaab 100644 --- a/tests/hazmat/primitives/test_hmac.py +++ b/tests/hazmat/primitives/test_hmac.py @@ -34,7 +34,7 @@ class UnsupportedDummyHash(object): @pytest.mark.supported( - only_if=lambda backend: backend.hmac_supported(hashes.MD5), + only_if=lambda backend: backend.hmac_supported(hashes.MD5()), skip_message="Does not support MD5", ) @pytest.mark.hmac diff --git a/tests/hazmat/primitives/test_hmac_vectors.py b/tests/hazmat/primitives/test_hmac_vectors.py index c5644459..0792080b 100644 --- a/tests/hazmat/primitives/test_hmac_vectors.py +++ b/tests/hazmat/primitives/test_hmac_vectors.py @@ -22,7 +22,7 @@ from ...utils import load_hash_vectors @pytest.mark.supported( - only_if=lambda backend: backend.hmac_supported(hashes.MD5), + only_if=lambda backend: backend.hmac_supported(hashes.MD5()), skip_message="Does not support MD5", ) @pytest.mark.hmac @@ -38,7 +38,7 @@ class TestHMAC_MD5(object): @pytest.mark.supported( - only_if=lambda backend: backend.hmac_supported(hashes.SHA1), + only_if=lambda backend: backend.hmac_supported(hashes.SHA1()), skip_message="Does not support SHA1", ) @pytest.mark.hmac @@ -54,7 +54,7 @@ class TestHMAC_SHA1(object): @pytest.mark.supported( - only_if=lambda backend: backend.hmac_supported(hashes.SHA224), + only_if=lambda backend: backend.hmac_supported(hashes.SHA224()), skip_message="Does not support SHA224", ) @pytest.mark.hmac @@ -70,7 +70,7 @@ class TestHMAC_SHA224(object): @pytest.mark.supported( - only_if=lambda backend: backend.hmac_supported(hashes.SHA256), + only_if=lambda backend: backend.hmac_supported(hashes.SHA256()), skip_message="Does not support SHA256", ) @pytest.mark.hmac @@ -86,7 +86,7 @@ class TestHMAC_SHA256(object): @pytest.mark.supported( - only_if=lambda backend: backend.hmac_supported(hashes.SHA384), + only_if=lambda backend: backend.hmac_supported(hashes.SHA384()), skip_message="Does not support SHA384", ) @pytest.mark.hmac @@ -102,7 +102,7 @@ class TestHMAC_SHA384(object): @pytest.mark.supported( - only_if=lambda backend: backend.hmac_supported(hashes.SHA512), + only_if=lambda backend: backend.hmac_supported(hashes.SHA512()), skip_message="Does not support SHA512", ) @pytest.mark.hmac @@ -118,7 +118,7 @@ class TestHMAC_SHA512(object): @pytest.mark.supported( - only_if=lambda backend: backend.hmac_supported(hashes.RIPEMD160), + only_if=lambda backend: backend.hmac_supported(hashes.RIPEMD160()), skip_message="Does not support RIPEMD160", ) @pytest.mark.hmac -- cgit v1.2.3 From f38074703c6fddabbd8cc76bec91d976d029e5ec Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 4 Jan 2014 12:28:02 -0600 Subject: refactor bindings to reduce code duplication with multiple backends --- cryptography/hazmat/bindings/openssl/binding.py | 74 ++++++------------------- cryptography/hazmat/bindings/utils.py | 65 ++++++++++++++++++++++ 2 files changed, 81 insertions(+), 58 deletions(-) create mode 100644 cryptography/hazmat/bindings/utils.py diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py index 8b5e3449..208bbdca 100644 --- a/cryptography/hazmat/bindings/openssl/binding.py +++ b/cryptography/hazmat/bindings/openssl/binding.py @@ -13,9 +13,7 @@ from __future__ import absolute_import, division, print_function -import sys - -import cffi +from cryptography.hazmat.bindings import utils _OSX_PRE_INCLUDE = """ #ifdef __APPLE__ @@ -36,6 +34,19 @@ _OSX_POST_INCLUDE = """ """ +def verify_kwargs(includes, functions, customizations): + return { + "source": "\n".join( + [_OSX_PRE_INCLUDE] + + includes + + [_OSX_POST_INCLUDE] + + functions + + customizations + ), + "libraries": ["crypto", "ssl"], + } + + class Binding(object): """ OpenSSL API wrapper. @@ -92,58 +103,5 @@ class Binding(object): if cls.ffi is not None and cls.lib is not None: return - ffi = cffi.FFI() - includes = [] - functions = [] - macros = [] - customizations = [] - for name in cls._modules: - module_name = cls._module_prefix + name - __import__(module_name) - module = sys.modules[module_name] - - ffi.cdef(module.TYPES) - - macros.append(module.MACROS) - functions.append(module.FUNCTIONS) - includes.append(module.INCLUDES) - customizations.append(module.CUSTOMIZATIONS) - - # loop over the functions & macros after declaring all the types - # so we can set interdependent types in different files and still - # have them all defined before we parse the funcs & macros - for func in functions: - ffi.cdef(func) - for macro in macros: - ffi.cdef(macro) - - # We include functions here so that if we got any of their definitions - # wrong, the underlying C compiler will explode. In C you are allowed - # to re-declare a function if it has the same signature. That is: - # int foo(int); - # int foo(int); - # is legal, but the following will fail to compile: - # int foo(int); - # int foo(short); - - lib = ffi.verify( - source="\n".join( - [_OSX_PRE_INCLUDE] + - includes + - [_OSX_POST_INCLUDE] + - functions + - customizations - ), - libraries=["crypto", "ssl"], - ) - - for name in cls._modules: - module_name = cls._module_prefix + name - module = sys.modules[module_name] - for condition, names in module.CONDITIONAL_NAMES.items(): - if not getattr(lib, condition): - for name in names: - delattr(lib, name) - - cls.ffi = ffi - cls.lib = lib + cls.ffi, cls.lib = utils.build_ffi(cls._modules, cls._module_prefix, + verify_kwargs) diff --git a/cryptography/hazmat/bindings/utils.py b/cryptography/hazmat/bindings/utils.py new file mode 100644 index 00000000..02ba1f26 --- /dev/null +++ b/cryptography/hazmat/bindings/utils.py @@ -0,0 +1,65 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +import sys + +import cffi + + +def build_ffi(modules, module_prefix, verify_kwargs): + ffi = cffi.FFI() + includes = [] + functions = [] + macros = [] + customizations = [] + for name in modules: + module_name = module_prefix + name + __import__(module_name) + module = sys.modules[module_name] + + ffi.cdef(module.TYPES) + + macros.append(module.MACROS) + functions.append(module.FUNCTIONS) + includes.append(module.INCLUDES) + customizations.append(module.CUSTOMIZATIONS) + + # loop over the functions & macros after declaring all the types + # so we can set interdependent types in different files and still + # have them all defined before we parse the funcs & macros + for func in functions: + ffi.cdef(func) + for macro in macros: + ffi.cdef(macro) + + # We include functions here so that if we got any of their definitions + # wrong, the underlying C compiler will explode. In C you are allowed + # to re-declare a function if it has the same signature. That is: + # int foo(int); + # int foo(int); + # is legal, but the following will fail to compile: + # int foo(int); + # int foo(short); + lib = ffi.verify(**verify_kwargs(includes, functions, customizations)) + + for name in modules: + module_name = module_prefix + name + module = sys.modules[module_name] + for condition, names in module.CONDITIONAL_NAMES.items(): + if not getattr(lib, condition): + for name in names: + delattr(lib, name) + + return ffi, lib -- cgit v1.2.3 From 24ff720b4e9b4d6299a8c7c4d5a0bbc3f377fea0 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 4 Jan 2014 12:52:20 -0600 Subject: remove verify_kwargs and replace with pre_include/post_include/libraries --- cryptography/hazmat/bindings/openssl/binding.py | 32 +++---------------------- cryptography/hazmat/bindings/utils.py | 28 ++++++++++++++++++++-- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py index 208bbdca..3fb3dc8b 100644 --- a/cryptography/hazmat/bindings/openssl/binding.py +++ b/cryptography/hazmat/bindings/openssl/binding.py @@ -34,36 +34,9 @@ _OSX_POST_INCLUDE = """ """ -def verify_kwargs(includes, functions, customizations): - return { - "source": "\n".join( - [_OSX_PRE_INCLUDE] + - includes + - [_OSX_POST_INCLUDE] + - functions + - customizations - ), - "libraries": ["crypto", "ssl"], - } - - class Binding(object): """ OpenSSL API wrapper. - - Modules listed in the ``_modules`` listed should have the following - attributes: - - * ``INCLUDES``: A string containg C includes. - * ``TYPES``: A string containing C declarations for types. - * ``FUNCTIONS``: A string containing C declarations for functions. - * ``MACROS``: A string containing C declarations for any macros. - * ``CUSTOMIZATIONS``: A string containing arbitrary top-level C code, this - can be used to do things like test for a define and provide an - alternate implementation based on that. - * ``CONDITIONAL_NAMES``: A dict mapping strings of condition names from the - library to a list of names which will not be present without the - condition. """ _module_prefix = "cryptography.hazmat.bindings.openssl." _modules = [ @@ -103,5 +76,6 @@ class Binding(object): if cls.ffi is not None and cls.lib is not None: return - cls.ffi, cls.lib = utils.build_ffi(cls._modules, cls._module_prefix, - verify_kwargs) + cls.ffi, cls.lib = utils.build_ffi(cls._module_prefix, cls._modules, + _OSX_PRE_INCLUDE, _OSX_POST_INCLUDE, + ["crypto", "ssl"]) diff --git a/cryptography/hazmat/bindings/utils.py b/cryptography/hazmat/bindings/utils.py index 02ba1f26..67f58795 100644 --- a/cryptography/hazmat/bindings/utils.py +++ b/cryptography/hazmat/bindings/utils.py @@ -18,7 +18,22 @@ import sys import cffi -def build_ffi(modules, module_prefix, verify_kwargs): +def build_ffi(module_prefix, modules, pre_include, post_include, libraries): + """ + Modules listed in the ``modules`` listed should have the following + attributes: + + * ``INCLUDES``: A string containing C includes. + * ``TYPES``: A string containing C declarations for types. + * ``FUNCTIONS``: A string containing C declarations for functions. + * ``MACROS``: A string containing C declarations for any macros. + * ``CUSTOMIZATIONS``: A string containing arbitrary top-level C code, this + can be used to do things like test for a define and provide an + alternate implementation based on that. + * ``CONDITIONAL_NAMES``: A dict mapping strings of condition names from the + library to a list of names which will not be present without the + condition. + """ ffi = cffi.FFI() includes = [] functions = [] @@ -52,7 +67,16 @@ def build_ffi(modules, module_prefix, verify_kwargs): # is legal, but the following will fail to compile: # int foo(int); # int foo(short); - lib = ffi.verify(**verify_kwargs(includes, functions, customizations)) + lib = ffi.verify( + source="\n".join( + [pre_include] + + includes + + [post_include] + + functions + + customizations + ), + libraries=libraries + ) for name in modules: module_name = module_prefix + name -- cgit v1.2.3 From ac4209a01e380abf5b0f985efdb9cb7524b512b7 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 4 Jan 2014 12:52:28 -0600 Subject: import build_ffi directly --- cryptography/hazmat/bindings/openssl/binding.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py index 3fb3dc8b..e6dde954 100644 --- a/cryptography/hazmat/bindings/openssl/binding.py +++ b/cryptography/hazmat/bindings/openssl/binding.py @@ -13,7 +13,7 @@ from __future__ import absolute_import, division, print_function -from cryptography.hazmat.bindings import utils +from cryptography.hazmat.bindings.utils import build_ffi _OSX_PRE_INCLUDE = """ #ifdef __APPLE__ @@ -76,6 +76,6 @@ class Binding(object): if cls.ffi is not None and cls.lib is not None: return - cls.ffi, cls.lib = utils.build_ffi(cls._module_prefix, cls._modules, - _OSX_PRE_INCLUDE, _OSX_POST_INCLUDE, - ["crypto", "ssl"]) + cls.ffi, cls.lib = build_ffi(cls._module_prefix, cls._modules, + _OSX_PRE_INCLUDE, _OSX_POST_INCLUDE, + ["crypto", "ssl"]) -- cgit v1.2.3 From 982d8a5f3c617106d8db767863d7da46143487d9 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 4 Jan 2014 12:54:39 -0600 Subject: fix indentation mistake --- cryptography/hazmat/bindings/openssl/binding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py index e6dde954..4f6b99ae 100644 --- a/cryptography/hazmat/bindings/openssl/binding.py +++ b/cryptography/hazmat/bindings/openssl/binding.py @@ -78,4 +78,4 @@ class Binding(object): cls.ffi, cls.lib = build_ffi(cls._module_prefix, cls._modules, _OSX_PRE_INCLUDE, _OSX_POST_INCLUDE, - ["crypto", "ssl"]) + ["crypto", "ssl"]) -- cgit v1.2.3 From 3366d8bab493386c2d10fdc6c7eca5f82ea5b515 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 4 Jan 2014 12:55:28 -0600 Subject: improve docstring --- cryptography/hazmat/bindings/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cryptography/hazmat/bindings/utils.py b/cryptography/hazmat/bindings/utils.py index 67f58795..9e1d3937 100644 --- a/cryptography/hazmat/bindings/utils.py +++ b/cryptography/hazmat/bindings/utils.py @@ -20,8 +20,7 @@ import cffi def build_ffi(module_prefix, modules, pre_include, post_include, libraries): """ - Modules listed in the ``modules`` listed should have the following - attributes: + Modules listed in ``modules`` should have the following attributes: * ``INCLUDES``: A string containing C includes. * ``TYPES``: A string containing C declarations for types. -- cgit v1.2.3 From 6078221d25fd3eddef83a63ce026efd24c0a2107 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 4 Jan 2014 12:20:32 -0800 Subject: Re-add the ext_package decleration to the moved verify() call --- cryptography/hazmat/bindings/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cryptography/hazmat/bindings/utils.py b/cryptography/hazmat/bindings/utils.py index 9e1d3937..69290eb3 100644 --- a/cryptography/hazmat/bindings/utils.py +++ b/cryptography/hazmat/bindings/utils.py @@ -74,7 +74,8 @@ def build_ffi(module_prefix, modules, pre_include, post_include, libraries): functions + customizations ), - libraries=libraries + libraries=libraries, + ext_package="cryptography", ) for name in modules: -- cgit v1.2.3 From 108605b01873c4176275cc6bf2ea0d0b7c447a0e Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 4 Jan 2014 13:37:00 -0600 Subject: add commoncrypto mark to skip on non-OS X platforms --- cryptography/hazmat/bindings/openssl/binding.py | 8 ++++++- cryptography/hazmat/bindings/utils.py | 8 +++++++ pytest.ini | 1 + tests/conftest.py | 5 +++- tests/hazmat/bindings/test_bindings.py | 31 +++++++++++++++++++++++++ tests/hazmat/bindings/test_openssl.py | 3 +++ tests/test_utils.py | 31 ++++++++++++++++++++++++- tests/utils.py | 6 +++++ 8 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 tests/hazmat/bindings/test_bindings.py diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py index 4f6b99ae..2a1e1184 100644 --- a/cryptography/hazmat/bindings/openssl/binding.py +++ b/cryptography/hazmat/bindings/openssl/binding.py @@ -13,7 +13,9 @@ from __future__ import absolute_import, division, print_function -from cryptography.hazmat.bindings.utils import build_ffi +from cryptography.hazmat.bindings.utils import ( + build_ffi, binding_available +) _OSX_PRE_INCLUDE = """ #ifdef __APPLE__ @@ -79,3 +81,7 @@ class Binding(object): cls.ffi, cls.lib = build_ffi(cls._module_prefix, cls._modules, _OSX_PRE_INCLUDE, _OSX_POST_INCLUDE, ["crypto", "ssl"]) + + @classmethod + def is_available(cls): + return binding_available(cls._ensure_ffi_initialized) diff --git a/cryptography/hazmat/bindings/utils.py b/cryptography/hazmat/bindings/utils.py index 9e1d3937..9141c155 100644 --- a/cryptography/hazmat/bindings/utils.py +++ b/cryptography/hazmat/bindings/utils.py @@ -86,3 +86,11 @@ def build_ffi(module_prefix, modules, pre_include, post_include, libraries): delattr(lib, name) return ffi, lib + + +def binding_available(initializer): + try: + initializer() + return True + except cffi.VerificationError: + return False diff --git a/pytest.ini b/pytest.ini index 36d4edc4..4bb9b0f9 100644 --- a/pytest.ini +++ b/pytest.ini @@ -5,3 +5,4 @@ markers = cipher: this test requires a backend providing CipherBackend hash: this test requires a backend providing HashBackend supported: parametrized test requiring only_if and skip_message + binding_available: verifies a given binding is available diff --git a/tests/conftest.py b/tests/conftest.py index 0ddc3338..3ba2425d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,9 @@ from cryptography.hazmat.backends.interfaces import ( HMACBackend, CipherBackend, HashBackend ) -from .utils import check_for_iface, check_backend_support +from .utils import ( + check_for_iface, check_backend_support, check_binding_available +) def pytest_generate_tests(metafunc): @@ -20,3 +22,4 @@ def pytest_runtest_setup(item): check_for_iface("cipher", CipherBackend, item) check_for_iface("hash", HashBackend, item) check_backend_support(item) + check_binding_available(item) diff --git a/tests/hazmat/bindings/test_bindings.py b/tests/hazmat/bindings/test_bindings.py new file mode 100644 index 00000000..927bc8a1 --- /dev/null +++ b/tests/hazmat/bindings/test_bindings.py @@ -0,0 +1,31 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +import cffi + +from cryptography.hazmat.bindings.utils import binding_available +from cryptography.hazmat.bindings.openssl.binding import Binding + + +def dummy_initializer(): + raise cffi.VerificationError + + +def test_binding_available(): + assert binding_available(Binding._ensure_ffi_initialized) is True + + +def test_binding_unavailable(): + assert binding_available(dummy_initializer) is False diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py index 31f736ab..d1e85058 100644 --- a/tests/hazmat/bindings/test_openssl.py +++ b/tests/hazmat/bindings/test_openssl.py @@ -20,3 +20,6 @@ class TestOpenSSL(object): assert binding assert binding.lib assert binding.ffi + + def test_is_available(self): + assert Binding.is_available() is True diff --git a/tests/test_utils.py b/tests/test_utils.py index e3e53d63..917e87f0 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -14,14 +14,18 @@ import os import textwrap +import cffi + import pretend import pytest +from cryptography.hazmat.bindings.utils import binding_available + from .utils import ( load_nist_vectors, load_vectors_from_file, load_cryptrec_vectors, load_openssl_vectors, load_hash_vectors, check_for_iface, - check_backend_support + check_backend_support, check_binding_available ) @@ -72,6 +76,31 @@ def test_check_backend_support_no_backend(): check_backend_support(item) +def test_check_binding_available(): + from cryptography.hazmat.bindings.openssl.binding import Binding + kwargs = pretend.stub(kwargs={"binding": Binding}) + item = pretend.stub(keywords={"binding_available": kwargs}) + assert check_binding_available(item) is None + + +def test_check_binding_unavailable(): + class FakeBinding(object): + @classmethod + def _ensure_ffi_initialized(cls): + raise cffi.VerificationError + + @classmethod + def is_available(cls): + return binding_available(cls._ensure_ffi_initialized) + + kwargs = pretend.stub(kwargs={"binding": FakeBinding}) + item = pretend.stub(keywords={"binding_available": kwargs}) + with pytest.raises(pytest.skip.Exception) as exc_info: + check_binding_available(item) + assert exc_info.value.args[0] == ("" + " is not available") + + def test_load_nist_vectors(): vector_data = textwrap.dedent(""" # CAVS 11.1 diff --git a/tests/utils.py b/tests/utils.py index 693a0c8f..6d47a398 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -36,6 +36,12 @@ def check_backend_support(item): "backend") +def check_binding_available(item): + ba = item.keywords.get("binding_available") + if ba and not ba.kwargs["binding"].is_available(): + pytest.skip("{0} is not available".format(ba.kwargs["binding"])) + + def load_vectors_from_file(filename, loader): base = os.path.join( os.path.dirname(__file__), "hazmat", "primitives", "vectors", -- cgit v1.2.3 From 3ae13fc662435d69c7d93e937a675059164a495c Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 4 Jan 2014 17:43:35 -0600 Subject: fix py3 --- tests/test_utils.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index 917e87f0..6f938f58 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -33,6 +33,16 @@ class FakeInterface(object): pass +class FakeBinding(object): + @classmethod + def _ensure_ffi_initialized(cls): + raise cffi.VerificationError + + @classmethod + def is_available(cls): + return binding_available(cls._ensure_ffi_initialized) + + def test_check_for_iface(): item = pretend.stub(keywords=["fake_name"], funcargs={"backend": True}) with pytest.raises(pytest.skip.Exception) as exc_info: @@ -84,15 +94,6 @@ def test_check_binding_available(): def test_check_binding_unavailable(): - class FakeBinding(object): - @classmethod - def _ensure_ffi_initialized(cls): - raise cffi.VerificationError - - @classmethod - def is_available(cls): - return binding_available(cls._ensure_ffi_initialized) - kwargs = pretend.stub(kwargs={"binding": FakeBinding}) item = pretend.stub(keywords={"binding_available": kwargs}) with pytest.raises(pytest.skip.Exception) as exc_info: -- cgit v1.2.3 From 2dd21fec484c85647d73145bd9957fd5326495c3 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 4 Jan 2014 18:13:40 -0600 Subject: remove unneeded mark now that is_available is there --- pytest.ini | 1 - tests/conftest.py | 5 +---- tests/test_utils.py | 32 +------------------------------- tests/utils.py | 6 ------ 4 files changed, 2 insertions(+), 42 deletions(-) diff --git a/pytest.ini b/pytest.ini index 4bb9b0f9..36d4edc4 100644 --- a/pytest.ini +++ b/pytest.ini @@ -5,4 +5,3 @@ markers = cipher: this test requires a backend providing CipherBackend hash: this test requires a backend providing HashBackend supported: parametrized test requiring only_if and skip_message - binding_available: verifies a given binding is available diff --git a/tests/conftest.py b/tests/conftest.py index 3ba2425d..0ddc3338 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,9 +4,7 @@ from cryptography.hazmat.backends.interfaces import ( HMACBackend, CipherBackend, HashBackend ) -from .utils import ( - check_for_iface, check_backend_support, check_binding_available -) +from .utils import check_for_iface, check_backend_support def pytest_generate_tests(metafunc): @@ -22,4 +20,3 @@ def pytest_runtest_setup(item): check_for_iface("cipher", CipherBackend, item) check_for_iface("hash", HashBackend, item) check_backend_support(item) - check_binding_available(item) diff --git a/tests/test_utils.py b/tests/test_utils.py index 6f938f58..e3e53d63 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -14,18 +14,14 @@ import os import textwrap -import cffi - import pretend import pytest -from cryptography.hazmat.bindings.utils import binding_available - from .utils import ( load_nist_vectors, load_vectors_from_file, load_cryptrec_vectors, load_openssl_vectors, load_hash_vectors, check_for_iface, - check_backend_support, check_binding_available + check_backend_support ) @@ -33,16 +29,6 @@ class FakeInterface(object): pass -class FakeBinding(object): - @classmethod - def _ensure_ffi_initialized(cls): - raise cffi.VerificationError - - @classmethod - def is_available(cls): - return binding_available(cls._ensure_ffi_initialized) - - def test_check_for_iface(): item = pretend.stub(keywords=["fake_name"], funcargs={"backend": True}) with pytest.raises(pytest.skip.Exception) as exc_info: @@ -86,22 +72,6 @@ def test_check_backend_support_no_backend(): check_backend_support(item) -def test_check_binding_available(): - from cryptography.hazmat.bindings.openssl.binding import Binding - kwargs = pretend.stub(kwargs={"binding": Binding}) - item = pretend.stub(keywords={"binding_available": kwargs}) - assert check_binding_available(item) is None - - -def test_check_binding_unavailable(): - kwargs = pretend.stub(kwargs={"binding": FakeBinding}) - item = pretend.stub(keywords={"binding_available": kwargs}) - with pytest.raises(pytest.skip.Exception) as exc_info: - check_binding_available(item) - assert exc_info.value.args[0] == ("" - " is not available") - - def test_load_nist_vectors(): vector_data = textwrap.dedent(""" # CAVS 11.1 diff --git a/tests/utils.py b/tests/utils.py index 6d47a398..693a0c8f 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -36,12 +36,6 @@ def check_backend_support(item): "backend") -def check_binding_available(item): - ba = item.keywords.get("binding_available") - if ba and not ba.kwargs["binding"].is_available(): - pytest.skip("{0} is not available".format(ba.kwargs["binding"])) - - def load_vectors_from_file(filename, loader): base = os.path.join( os.path.dirname(__file__), "hazmat", "primitives", "vectors", -- cgit v1.2.3 From fefe3c224353e4e37b02f1df0fc9558f68f8c464 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 4 Jan 2014 18:43:19 -0600 Subject: make the dummy_initializer fail with an actual verify call --- tests/hazmat/bindings/test_bindings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/hazmat/bindings/test_bindings.py b/tests/hazmat/bindings/test_bindings.py index 927bc8a1..5b13d543 100644 --- a/tests/hazmat/bindings/test_bindings.py +++ b/tests/hazmat/bindings/test_bindings.py @@ -20,7 +20,8 @@ from cryptography.hazmat.bindings.openssl.binding import Binding def dummy_initializer(): - raise cffi.VerificationError + ffi = cffi.FFI() + ffi.verify(source="include ") def test_binding_available(): -- cgit v1.2.3 From 02ed961c963f0d27fe23e9608223ccc8dd3be7f6 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 4 Jan 2014 19:36:39 -0600 Subject: missing # --- tests/hazmat/bindings/test_bindings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hazmat/bindings/test_bindings.py b/tests/hazmat/bindings/test_bindings.py index 5b13d543..7af1d581 100644 --- a/tests/hazmat/bindings/test_bindings.py +++ b/tests/hazmat/bindings/test_bindings.py @@ -21,7 +21,7 @@ from cryptography.hazmat.bindings.openssl.binding import Binding def dummy_initializer(): ffi = cffi.FFI() - ffi.verify(source="include ") + ffi.verify(source="#include ") def test_binding_available(): -- cgit v1.2.3 From 008b7a5f650466d106e27ed2e92b76e0d6346952 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sat, 4 Jan 2014 20:55:23 -0800 Subject: Revert "Revert "Travis now has an up to date pypy"" This reverts commit a5982a5db4ff22feeec0a977853b63d4976872f7. --- .travis/install.sh | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/.travis/install.sh b/.travis/install.sh index fdd71907..4aa39799 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -5,24 +5,8 @@ set -x if [[ "${OPENSSL}" == "0.9.8" ]]; then sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu/ lucid main" -fi - -if [[ "${TOX_ENV}" == "pypy" ]]; then - sudo add-apt-repository -y ppa:pypy/ppa -fi - -sudo apt-get -y update - -if [[ "${OPENSSL}" == "0.9.8" ]]; then + sudo apt-get -y update sudo apt-get install -y --force-yes libssl-dev/lucid fi -if [[ "${TOX_ENV}" == "pypy" ]]; then - sudo apt-get install -y pypy - - # This is required because we need to get rid of the Travis installed PyPy - # or it'll take precedence over the PPA installed one. - sudo rm -rf /usr/local/pypy/bin -fi - pip install tox coveralls -- cgit v1.2.3 From 50f233efe4d37a20b4372cf21f1c462914ea5d40 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 6 Jan 2014 11:10:40 -0800 Subject: Check to see if a binding is available before trying to install it --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0bdad485..514fdab7 100644 --- a/setup.py +++ b/setup.py @@ -47,10 +47,12 @@ class cffi_build(build): from cryptography.hazmat.primitives import constant_time, padding self.distribution.ext_modules = [ - Binding().ffi.verifier.get_extension(), constant_time._ffi.verifier.get_extension(), padding._ffi.verifier.get_extension() ] + if Binding.is_available(): + self.distribution.ext_modules.append(Binding().ffi.verifier.get_extension()) + build.finalize_options(self) -- cgit v1.2.3 From ffa8b4d5fedad8f9c1fdbd05a2da8fb2679168af Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 6 Jan 2014 11:19:11 -0800 Subject: flake8 --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 514fdab7..a053fe61 100644 --- a/setup.py +++ b/setup.py @@ -51,7 +51,9 @@ class cffi_build(build): padding._ffi.verifier.get_extension() ] if Binding.is_available(): - self.distribution.ext_modules.append(Binding().ffi.verifier.get_extension()) + self.distribution.ext_modules.append( + Binding().ffi.verifier.get_extension() + ) build.finalize_options(self) -- cgit v1.2.3 From 6bc3af75b89fdae5534b65527453c183645ad394 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 6 Jan 2014 12:04:53 -0800 Subject: Write release automation software. Fixes #375 --- tasks.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tasks.py diff --git a/tasks.py b/tasks.py new file mode 100644 index 00000000..4cbbe1a3 --- /dev/null +++ b/tasks.py @@ -0,0 +1,38 @@ +import os +import re + +import invoke + + +def update_version(filename, identifier, version): + path = os.path.join(os.path.dirname(__file__), filename) + with open(path) as f: + contents = f.read() + contents = re.sub( + r"^{} = .*?$".format(identifier), + '{} = "{}"'.format(identifier, version), + contents + ) + with open(path, "w") as f: + f.write(contents) + + +@invoke.task +def release(version): + """ + ``version`` should be a string like '0.4' or '1.0'. + """ + # This checks for changes in the repo. + invoke.run("git diff-index --quiet HEAD") + + update_version("cryptography/__about__.py", "__version__") + update_version("docs/conf.py", "version") + update_version("docs/conf.py", "release") + + invoke.run("git commit -am 'Bump version numbers for release.'") + invoke.run("git push") + invoke.run("git tag -s {}".format(version)) + invoke.run("git push --tags") + + invoke.run("python setup.py sdist bdist_wheel") + invoke.run("twine upload -s dist/cryptography-{}*".format(version)) -- cgit v1.2.3 From 3aa243cddc5cbe4e4205b019946dc6c4f271f589 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 6 Jan 2014 13:13:18 -0800 Subject: Spell a word correctly --- docs/fernet.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/fernet.rst b/docs/fernet.rst index 4e94e212..13295c0c 100644 --- a/docs/fernet.rst +++ b/docs/fernet.rst @@ -39,7 +39,7 @@ symmetric (also known as "secret key") authenticated cryptography. :param bytes plaintext: The message you would like to encrypt. :returns bytes: A secure message which cannot be read or altered without the key. It is URL-safe base64-encoded. This is - refered to as a "Fernet token". + referred to as a "Fernet token". .. note:: -- cgit v1.2.3 From 0e10f57d1b70ada1c4c0a6463d91dc0510a6b1d0 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 6 Jan 2014 13:17:31 -0800 Subject: Boilerplate woo! --- tasks.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tasks.py b/tasks.py index 4cbbe1a3..27eed304 100644 --- a/tasks.py +++ b/tasks.py @@ -1,3 +1,17 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import, division, print_function + import os import re -- cgit v1.2.3 From 2b22fae990513eeb4026cd0883bc2e244af8b56a Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 6 Jan 2014 13:19:33 -0800 Subject: Compute the version in the same way as setup.py does --- docs/conf.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 5dbcdab8..00660314 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,10 +60,13 @@ copyright = '2013-2014, Individual Contributors' # |version| and |release|, also used in various other places throughout the # built documents. # -# The short X.Y version. -version = '0.1dev' -# The full version, including alpha/beta/rc tags. -release = '0.1dev' + +base_dir = os.path.join(os.path.dirname(__file__), os.pardir) +about = {} +with open(os.path.join(base_dir, "cryptography", "__about__.py")) as f: + exec(f.read(), about) + +version = release = about["__version__"] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -- cgit v1.2.3 From 7b8ef39ebe77b7686c34a2ffdef2c39e387760cb Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 6 Jan 2014 15:16:49 -0800 Subject: Fix --- tasks.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tasks.py b/tasks.py index 27eed304..5db7a617 100644 --- a/tasks.py +++ b/tasks.py @@ -39,9 +39,9 @@ def release(version): # This checks for changes in the repo. invoke.run("git diff-index --quiet HEAD") - update_version("cryptography/__about__.py", "__version__") - update_version("docs/conf.py", "version") - update_version("docs/conf.py", "release") + update_version("cryptography/__about__.py", "__version__", version) + update_version("docs/conf.py", "version", version) + update_version("docs/conf.py", "release", version) invoke.run("git commit -am 'Bump version numbers for release.'") invoke.run("git push") -- cgit v1.2.3 From d42fd94fa20d90691c4f63e7f299000ba9bb5b0e Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 6 Jan 2014 15:27:28 -0800 Subject: Suggestion from @dreid --- tasks.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tasks.py b/tasks.py index 5db7a617..bb30194a 100644 --- a/tasks.py +++ b/tasks.py @@ -44,9 +44,8 @@ def release(version): update_version("docs/conf.py", "release", version) invoke.run("git commit -am 'Bump version numbers for release.'") - invoke.run("git push") invoke.run("git tag -s {}".format(version)) - invoke.run("git push --tags") + invoke.run("git push --all --tags") invoke.run("python setup.py sdist bdist_wheel") invoke.run("twine upload -s dist/cryptography-{}*".format(version)) -- cgit v1.2.3 From 6b1235a3ab4b1f93d3b5e2b78b77658013637aa2 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 6 Jan 2014 15:28:59 -0800 Subject: Simplify --- tasks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index bb30194a..5db7a617 100644 --- a/tasks.py +++ b/tasks.py @@ -44,8 +44,9 @@ def release(version): update_version("docs/conf.py", "release", version) invoke.run("git commit -am 'Bump version numbers for release.'") + invoke.run("git push") invoke.run("git tag -s {}".format(version)) - invoke.run("git push --all --tags") + invoke.run("git push --tags") invoke.run("python setup.py sdist bdist_wheel") invoke.run("twine upload -s dist/cryptography-{}*".format(version)) -- cgit v1.2.3 From 8755dbd309ae4eab754385853ac6959bfade588d Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 6 Jan 2014 15:35:18 -0800 Subject: Fix --- tasks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index 5db7a617..dba760ca 100644 --- a/tasks.py +++ b/tasks.py @@ -25,7 +25,8 @@ def update_version(filename, identifier, version): contents = re.sub( r"^{} = .*?$".format(identifier), '{} = "{}"'.format(identifier, version), - contents + contents, + flags=re.MULTILINE ) with open(path, "w") as f: f.write(contents) -- cgit v1.2.3 From 89063f687893417e1e5dac2e854a02d92037b6a0 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 6 Jan 2014 15:52:38 -0800 Subject: Update procedure --- docs/doing-a-release.rst | 27 +++++++++++++++++++++++++++ docs/index.rst | 1 + tasks.py | 26 -------------------------- 3 files changed, 28 insertions(+), 26 deletions(-) create mode 100644 docs/doing-a-release.rst diff --git a/docs/doing-a-release.rst b/docs/doing-a-release.rst new file mode 100644 index 00000000..81349a70 --- /dev/null +++ b/docs/doing-a-release.rst @@ -0,0 +1,27 @@ +Doing a Release +=============== + +Doing a release of ``cryptography`` is a two part process. + +Bumping the version number +-------------------------- + +The first step in doing a release is bumping the version number in the +software. + +* Update the version number in ``cryptography/__about__.py`` and + ``docs/conf.py``. +* Do a commit indicating this. +* Send a pull request with this. +* Wait for it to be merged. + +Performing the release +---------------------- + +The commit which merged the version number bump is now the official release +commit for this release. Once this has happened: + +* Run ``invoke release {version}``. + +That's all, the release should now be available on PyPI and a tag should be +available in the repository. diff --git a/docs/index.rst b/docs/index.rst index 5eb3de7d..24d6d204 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -78,4 +78,5 @@ The ``cryptography`` open source project contributing security api-stability + doing-a-release community diff --git a/tasks.py b/tasks.py index dba760ca..5fe28718 100644 --- a/tasks.py +++ b/tasks.py @@ -12,40 +12,14 @@ # limitations under the License. from __future__ import absolute_import, division, print_function -import os -import re - import invoke -def update_version(filename, identifier, version): - path = os.path.join(os.path.dirname(__file__), filename) - with open(path) as f: - contents = f.read() - contents = re.sub( - r"^{} = .*?$".format(identifier), - '{} = "{}"'.format(identifier, version), - contents, - flags=re.MULTILINE - ) - with open(path, "w") as f: - f.write(contents) - - @invoke.task def release(version): """ ``version`` should be a string like '0.4' or '1.0'. """ - # This checks for changes in the repo. - invoke.run("git diff-index --quiet HEAD") - - update_version("cryptography/__about__.py", "__version__", version) - update_version("docs/conf.py", "version", version) - update_version("docs/conf.py", "release", version) - - invoke.run("git commit -am 'Bump version numbers for release.'") - invoke.run("git push") invoke.run("git tag -s {}".format(version)) invoke.run("git push --tags") -- cgit v1.2.3 From ce0b5a3a8a5d2bb9de1680a9e9ea6e488d33da27 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Mon, 6 Jan 2014 16:53:31 -0800 Subject: Update release docs --- docs/doing-a-release.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/doing-a-release.rst b/docs/doing-a-release.rst index 81349a70..d790523b 100644 --- a/docs/doing-a-release.rst +++ b/docs/doing-a-release.rst @@ -9,8 +9,7 @@ Bumping the version number The first step in doing a release is bumping the version number in the software. -* Update the version number in ``cryptography/__about__.py`` and - ``docs/conf.py``. +* Update the version number in ``cryptography/__about__.py``. * Do a commit indicating this. * Send a pull request with this. * Wait for it to be merged. -- cgit v1.2.3 From e72c2531fd4ac774654daf895dbd30fa2d1ad13a Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 7 Jan 2014 09:17:06 -0800 Subject: This is a dev dep --- dev-requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/dev-requirements.txt b/dev-requirements.txt index 0f52900b..1634de9b 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -6,4 +6,5 @@ pytest sphinx sphinx_rtd_theme tox +twine -e . -- cgit v1.2.3 From b3794dbe97a6f4e088244adfdd6a06b2d4e185e0 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 7 Jan 2014 09:25:54 -0800 Subject: You need a gpg key to do a release --- docs/doing-a-release.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/doing-a-release.rst b/docs/doing-a-release.rst index d790523b..77582a48 100644 --- a/docs/doing-a-release.rst +++ b/docs/doing-a-release.rst @@ -18,7 +18,8 @@ Performing the release ---------------------- The commit which merged the version number bump is now the official release -commit for this release. Once this has happened: +commit for this release. You will need to have ``gpg`` installed and a ``gpg`` +key in order to do a release. Once this has happened: * Run ``invoke release {version}``. -- cgit v1.2.3 From fea893c7060c57fe5ed9e0f9df58fee5c306681b Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 7 Jan 2014 11:06:51 -0800 Subject: More stuff --- dev-requirements.txt | 1 + docs/doing-a-release.rst | 5 +++-- tasks.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 1634de9b..b2a6c79c 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,5 +1,6 @@ coverage flake8 +invoke iso8601 pretend pytest diff --git a/docs/doing-a-release.rst b/docs/doing-a-release.rst index 77582a48..e52c2728 100644 --- a/docs/doing-a-release.rst +++ b/docs/doing-a-release.rst @@ -23,5 +23,6 @@ key in order to do a release. Once this has happened: * Run ``invoke release {version}``. -That's all, the release should now be available on PyPI and a tag should be -available in the repository. +The release should now be available on PyPI and a tag should be available in +the repository. You should verify that ``pip install cryptography`` works +correctly. diff --git a/tasks.py b/tasks.py index 5fe28718..ca967f0d 100644 --- a/tasks.py +++ b/tasks.py @@ -23,5 +23,5 @@ def release(version): invoke.run("git tag -s {}".format(version)) invoke.run("git push --tags") - invoke.run("python setup.py sdist bdist_wheel") + invoke.run("python setup.py sdist") invoke.run("twine upload -s dist/cryptography-{}*".format(version)) -- cgit v1.2.3 From 4345c0d3e65132985afb3f5a7fee04ea212811a0 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 7 Jan 2014 11:12:47 -0800 Subject: Python 2.6 support --- tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tasks.py b/tasks.py index ca967f0d..f72f43ba 100644 --- a/tasks.py +++ b/tasks.py @@ -20,8 +20,8 @@ def release(version): """ ``version`` should be a string like '0.4' or '1.0'. """ - invoke.run("git tag -s {}".format(version)) + invoke.run("git tag -s {0}".format(version)) invoke.run("git push --tags") invoke.run("python setup.py sdist") - invoke.run("twine upload -s dist/cryptography-{}*".format(version)) + invoke.run("twine upload -s dist/cryptography-{0}*".format(version)) -- cgit v1.2.3 From 41c14d55ea2d17e3e9968acfa93d442615f7cda0 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 7 Jan 2014 11:19:08 -0800 Subject: How to verify that your released correctly --- docs/doing-a-release.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/doing-a-release.rst b/docs/doing-a-release.rst index e52c2728..0f382064 100644 --- a/docs/doing-a-release.rst +++ b/docs/doing-a-release.rst @@ -25,4 +25,12 @@ key in order to do a release. Once this has happened: The release should now be available on PyPI and a tag should be available in the repository. You should verify that ``pip install cryptography`` works -correctly. +correctly: + +.. code-block:: pycon + + >>> import cryptography + >>> cryptography.__version__ + '...' + +Verify that this is the version you just released. -- cgit v1.2.3 From 5e5316de9ca968f646aa04301fcf3109aef0473f Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 7 Jan 2014 12:27:08 -0800 Subject: This is dangerous --- setup.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/setup.py b/setup.py index a053fe61..feed0cd8 100644 --- a/setup.py +++ b/setup.py @@ -47,13 +47,10 @@ class cffi_build(build): from cryptography.hazmat.primitives import constant_time, padding self.distribution.ext_modules = [ + Binding().ffi.verifier.get_extension(), constant_time._ffi.verifier.get_extension(), padding._ffi.verifier.get_extension() ] - if Binding.is_available(): - self.distribution.ext_modules.append( - Binding().ffi.verifier.get_extension() - ) build.finalize_options(self) -- cgit v1.2.3 From e222010029298bb3d9d88c35b77fb56efc582ea0 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 7 Jan 2014 15:16:35 -0800 Subject: Fixed #408 -- cleanup how is_available works --- cryptography/hazmat/bindings/openssl/binding.py | 7 +++--- cryptography/hazmat/bindings/utils.py | 8 ------- tests/hazmat/bindings/test_bindings.py | 32 ------------------------- 3 files changed, 3 insertions(+), 44 deletions(-) delete mode 100644 tests/hazmat/bindings/test_bindings.py diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py index 2a1e1184..19d4d8a9 100644 --- a/cryptography/hazmat/bindings/openssl/binding.py +++ b/cryptography/hazmat/bindings/openssl/binding.py @@ -13,9 +13,8 @@ from __future__ import absolute_import, division, print_function -from cryptography.hazmat.bindings.utils import ( - build_ffi, binding_available -) +from cryptography.hazmat.bindings.utils import build_ffi + _OSX_PRE_INCLUDE = """ #ifdef __APPLE__ @@ -84,4 +83,4 @@ class Binding(object): @classmethod def is_available(cls): - return binding_available(cls._ensure_ffi_initialized) + return True diff --git a/cryptography/hazmat/bindings/utils.py b/cryptography/hazmat/bindings/utils.py index 40fd07f8..69290eb3 100644 --- a/cryptography/hazmat/bindings/utils.py +++ b/cryptography/hazmat/bindings/utils.py @@ -87,11 +87,3 @@ def build_ffi(module_prefix, modules, pre_include, post_include, libraries): delattr(lib, name) return ffi, lib - - -def binding_available(initializer): - try: - initializer() - return True - except cffi.VerificationError: - return False diff --git a/tests/hazmat/bindings/test_bindings.py b/tests/hazmat/bindings/test_bindings.py deleted file mode 100644 index 7af1d581..00000000 --- a/tests/hazmat/bindings/test_bindings.py +++ /dev/null @@ -1,32 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import absolute_import, division, print_function - -import cffi - -from cryptography.hazmat.bindings.utils import binding_available -from cryptography.hazmat.bindings.openssl.binding import Binding - - -def dummy_initializer(): - ffi = cffi.FFI() - ffi.verify(source="#include ") - - -def test_binding_available(): - assert binding_available(Binding._ensure_ffi_initialized) is True - - -def test_binding_unavailable(): - assert binding_available(dummy_initializer) is False -- cgit v1.2.3 From 9a95dad8df43fdc9149fac3e0a6302a3f5b61b6e Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 7 Jan 2014 15:20:42 -0800 Subject: Explanatory comment --- cryptography/hazmat/bindings/openssl/binding.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py index 19d4d8a9..8a4e1dd3 100644 --- a/cryptography/hazmat/bindings/openssl/binding.py +++ b/cryptography/hazmat/bindings/openssl/binding.py @@ -83,4 +83,5 @@ class Binding(object): @classmethod def is_available(cls): + # OpenSSL is the only binding so for now it must always be available return True -- cgit v1.2.3 From 0f72bdadc4de8b764967ea66d376da65e5bf3281 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 7 Jan 2014 17:24:58 -0600 Subject: remove CAST5 for first release --- cryptography/hazmat/backends/openssl/backend.py | 7 +--- .../hazmat/primitives/ciphers/algorithms.py | 15 -------- tests/hazmat/primitives/test_cast5.py | 41 ---------------------- tests/hazmat/primitives/test_ciphers.py | 15 +------- 4 files changed, 2 insertions(+), 76 deletions(-) delete mode 100644 tests/hazmat/primitives/test_cast5.py diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 49066466..284fa989 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -22,7 +22,7 @@ from cryptography.hazmat.backends.interfaces import ( ) from cryptography.hazmat.primitives import interfaces from cryptography.hazmat.primitives.ciphers.algorithms import ( - AES, Blowfish, Camellia, CAST5, TripleDES, ARC4, + AES, Blowfish, Camellia, TripleDES, ARC4, ) from cryptography.hazmat.primitives.ciphers.modes import ( CBC, CTR, ECB, OFB, CFB, GCM, @@ -107,11 +107,6 @@ class Backend(object): mode_cls, GetCipherByName("bf-{mode.name}") ) - self.register_cipher_adapter( - CAST5, - ECB, - GetCipherByName("cast5-ecb") - ) self.register_cipher_adapter( ARC4, type(None), diff --git a/cryptography/hazmat/primitives/ciphers/algorithms.py b/cryptography/hazmat/primitives/ciphers/algorithms.py index a5cfce92..19cf1920 100644 --- a/cryptography/hazmat/primitives/ciphers/algorithms.py +++ b/cryptography/hazmat/primitives/ciphers/algorithms.py @@ -90,21 +90,6 @@ class Blowfish(object): return len(self.key) * 8 -@utils.register_interface(interfaces.BlockCipherAlgorithm) -@utils.register_interface(interfaces.CipherAlgorithm) -class CAST5(object): - name = "CAST5" - block_size = 64 - key_sizes = frozenset(range(40, 129, 8)) - - def __init__(self, key): - self.key = _verify_key_size(self, key) - - @property - def key_size(self): - return len(self.key) * 8 - - @utils.register_interface(interfaces.CipherAlgorithm) class ARC4(object): name = "RC4" diff --git a/tests/hazmat/primitives/test_cast5.py b/tests/hazmat/primitives/test_cast5.py deleted file mode 100644 index d65a86b2..00000000 --- a/tests/hazmat/primitives/test_cast5.py +++ /dev/null @@ -1,41 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import absolute_import, division, print_function - -import binascii -import os - -import pytest - -from cryptography.hazmat.primitives.ciphers import algorithms, modes - -from .utils import generate_encrypt_test -from ...utils import load_nist_vectors - - -@pytest.mark.supported( - only_if=lambda backend: backend.cipher_supported( - algorithms.CAST5("\x00" * 16), modes.ECB() - ), - skip_message="Does not support CAST5 ECB", -) -@pytest.mark.cipher -class TestCAST5(object): - test_ECB = generate_encrypt_test( - load_nist_vectors, - os.path.join("ciphers", "CAST5"), - ["cast5-ecb.txt"], - lambda key, **kwargs: algorithms.CAST5(binascii.unhexlify((key))), - lambda **kwargs: modes.ECB(), - ) diff --git a/tests/hazmat/primitives/test_ciphers.py b/tests/hazmat/primitives/test_ciphers.py index 653f7ce6..6a7b2f93 100644 --- a/tests/hazmat/primitives/test_ciphers.py +++ b/tests/hazmat/primitives/test_ciphers.py @@ -18,7 +18,7 @@ import binascii import pytest from cryptography.hazmat.primitives.ciphers.algorithms import ( - AES, Camellia, TripleDES, Blowfish, CAST5, ARC4 + AES, Camellia, TripleDES, Blowfish, ARC4 ) @@ -80,19 +80,6 @@ class TestBlowfish(object): Blowfish(binascii.unhexlify(b"0" * 6)) -class TestCAST5(object): - @pytest.mark.parametrize(("key", "keysize"), [ - (b"0" * (keysize // 4), keysize) for keysize in range(40, 129, 8) - ]) - def test_key_size(self, key, keysize): - cipher = CAST5(binascii.unhexlify(key)) - assert cipher.key_size == keysize - - def test_invalid_key_size(self): - with pytest.raises(ValueError): - CAST5(binascii.unhexlify(b"0" * 34)) - - class TestARC4(object): @pytest.mark.parametrize(("key", "keysize"), [ (b"0" * 10, 40), -- cgit v1.2.3 From bb996d7e06fe539cbddee880a1af22df334cd5db Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 7 Jan 2014 17:34:48 -0600 Subject: also remove CAST5 docs --- docs/hazmat/primitives/symmetric-encryption.rst | 9 --------- 1 file changed, 9 deletions(-) diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index e05248ff..a1a3ec0d 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -189,15 +189,6 @@ Algorithms ``56`` bits long), they can simply be concatenated to produce the full key. This must be kept secret. -.. class:: CAST5(key) - - CAST5 (also known as CAST-128) is a block cipher approved for use in the - Canadian government by their Communications Security Establishment. It is a - variable key length cipher and supports keys from 40-128 bits in length. - - :param bytes key: The secret key, 40-128 bits in length (in increments of - 8). This must be kept secret. - Weak Ciphers ------------ -- cgit v1.2.3 From 78569d68de24bc56dd799c262f3dd2d522bcdcd1 Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 7 Jan 2014 15:42:17 -0800 Subject: Try making the AEAD examples less dense. --- docs/hazmat/primitives/symmetric-encryption.rst | 51 ++++++++++++++++++------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index e05248ff..d3ba731a 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -365,20 +365,45 @@ Modes :param bytes tag: The tag bytes to verify during decryption. When encrypting this must be None. - .. doctest:: + .. code-block:: python - >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes - >>> from cryptography.hazmat.backends import default_backend - >>> cipher = Cipher(algorithms.AES(key), modes.GCM(iv), backend=default_backend()) - >>> encryptor = cipher.encryptor() - >>> encryptor.authenticate_additional_data(b"authenticated but not encrypted payload") - >>> ct = encryptor.update(b"a secret message") + encryptor.finalize() - >>> tag = encryptor.tag - >>> cipher = Cipher(algorithms.AES(key), modes.GCM(iv, tag), backend) - >>> decryptor = cipher.decryptor() - >>> decryptor.authenticate_additional_data(b"authenticated but not encrypted payload") - >>> decryptor.update(ct) + decryptor.finalize() - 'a secret message' + def encrypt(key, plaintext, associated_data): + iv = os.urandom(12) + cipher = Cipher( + algorithms.AES(key), + modes.GCM(iv), + backend=default_backend() + ) + + encryptor = cipher.encryptor() + encryptor.authenticate_additional_data(associated_data) + ciphertext = encryptor.update(plaintext) + encryptor.finalize() + + return (associated_data, iv, ciphertext, encryptor.tag) + + def decrypt(key, associated_data, iv, ciphertext, tag): + cipher = Cipher( + algorithms.AES(key), + modes.GCM(iv, tag), + backend=default_backend() + ) + + decryptor = cipher.decryptor() + decryptor.authenticate_additional_data(associated_data) + + return decryptor.update(ciphertext) + decryptor.finalize() + + associated_data, iv, ciphertext, tag = encrypt( + key, + b"a secret message", + b"authenticated but not encrypted payload" + ) + + print(decrypt(key, associated_data, iv, ciphertext, tag)) + + .. testoutput:: + + a secret message Insecure Modes -- cgit v1.2.3 From abb72d23118fb63a8601d2036b7c2cef2598f408 Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 7 Jan 2014 16:06:18 -0800 Subject: Make the example more complete and add some comments walking the user through some stuff. --- docs/hazmat/primitives/symmetric-encryption.rst | 63 +++++++++++++++++++------ 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index d3ba731a..59aff99b 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -365,45 +365,80 @@ Modes :param bytes tag: The tag bytes to verify during decryption. When encrypting this must be None. - .. code-block:: python + .. testcode:: + + import os + + from cryptography.hazmat.primitives.ciphers import ( + Cipher, algorithms, modes + ) + + from cryptography.hazmat.primitives.padding import PKCS7 def encrypt(key, plaintext, associated_data): + # Generate a random 96-bit IV. iv = os.urandom(12) - cipher = Cipher( + + # Construct a AES-GCM Cipher object with the given and our randomly + # generated IV. + encryptor = Cipher( algorithms.AES(key), modes.GCM(iv), backend=default_backend() - ) + ).encryptor() - encryptor = cipher.encryptor() + # We have to pad our plaintext because it may not be a + # multiple of the block size. + padder = PKCS7(algorithms.AES.block_size).padder() + padded_plaintext = padder.update(plaintext) + padder.finalize() + + # associated_data will be authenticated but not encrypted, + # it must also be passed in on decryption. encryptor.authenticate_additional_data(associated_data) - ciphertext = encryptor.update(plaintext) + encryptor.finalize() - return (associated_data, iv, ciphertext, encryptor.tag) + # Encrypt the plaintext and get the associated ciphertext. + ciphertext = encryptor.update(padded_plaintext) + encryptor.finalize() + + return (iv, ciphertext, encryptor.tag) def decrypt(key, associated_data, iv, ciphertext, tag): - cipher = Cipher( + # Construct a Cipher object, with the key, iv, and additionally the + # GCM tag used for authenticating the message. + decryptor = Cipher( algorithms.AES(key), modes.GCM(iv, tag), backend=default_backend() - ) + ).decryptor() + + # We will need to unpad the plaintext. + unpadder = PKCS7(algorithms.AES.block_size).unpadder() - decryptor = cipher.decryptor() + # We put associated_data back in or the tag will fail to verify + # when we finalize the decryptor. decryptor.authenticate_additional_data(associated_data) - return decryptor.update(ciphertext) + decryptor.finalize() + # Decryption gets us the authenticated padded plaintext. + padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize() - associated_data, iv, ciphertext, tag = encrypt( + return unpadder.update(padded_plaintext) + unpadder.finalize() + + iv, ciphertext, tag = encrypt( key, - b"a secret message", + b"a secret message!", b"authenticated but not encrypted payload" ) - print(decrypt(key, associated_data, iv, ciphertext, tag)) + print(decrypt( + key, + b"authenticated but not encrypted payload", + iv, + ciphertext, + tag + )) .. testoutput:: - a secret message + a secret message! Insecure Modes -- cgit v1.2.3 From ad6d164a93352a9f6ddb57fd98152ba929e35d34 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 7 Jan 2014 19:10:12 -0600 Subject: move ciphercontext/aeadciphercontext to bottom of symmetric encryption Add a bit of additional text to try to make the convoluted AEAD explanation better. --- docs/hazmat/primitives/symmetric-encryption.rst | 156 +++++++++++++----------- 1 file changed, 83 insertions(+), 73 deletions(-) diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index a683bb98..c1f7bb64 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -74,79 +74,6 @@ an "encrypt-then-MAC" formulation as `described by Colin Percival`_. and ``mode`` an :class:`cryptography.exceptions.UnsupportedAlgorithm` will be raised. - -.. currentmodule:: cryptography.hazmat.primitives.interfaces - -.. class:: CipherContext - - When calling ``encryptor()`` or ``decryptor()`` on a ``Cipher`` object - you will receive a return object conforming to the ``CipherContext`` - interface. You can then call ``update(data)`` with data until you have fed - everything into the context. Once that is done call ``finalize()`` to - finish the operation and obtain the remainder of the data. - - Block ciphers require that plaintext or ciphertext always be a multiple of - their block size, because of that **padding** is often required to make a - message the correct size. ``CipherContext`` will not automatically apply - any padding; you'll need to add your own. For block ciphers the recommended - padding is :class:`cryptography.hazmat.primitives.padding.PKCS7`. If you - are using a stream cipher mode (such as - :class:`cryptography.hazmat.primitives.modes.CTR`) you don't have to worry - about this. - - .. method:: update(data) - - :param bytes data: The data you wish to pass into the context. - :return bytes: Returns the data that was encrypted or decrypted. - :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` - - When the ``Cipher`` was constructed in a mode that turns it into a - stream cipher (e.g. - :class:`cryptography.hazmat.primitives.ciphers.modes.CTR`), this will - return bytes immediately, however in other modes it will return chunks, - whose size is determined by the cipher's block size. - - .. method:: finalize() - - :return bytes: Returns the remainder of the data. - :raises ValueError: This is raised when the data provided isn't - correctly padded to be a multiple of the - algorithm's block size. - - Once ``finalize`` is called this object can no longer be used and - :meth:`update` and :meth:`finalize` will raise - :class:`~cryptography.exceptions.AlreadyFinalized`. - -.. class:: AEADCipherContext - - When calling ``encryptor()`` or ``decryptor()`` on a ``Cipher`` object - with an AEAD mode you will receive a return object conforming to the - ``AEADCipherContext`` interface (in addition to the ``CipherContext`` - interface). If it is an encryption context it will additionally be an - ``AEADEncryptionContext`` interface. ``AEADCipherContext`` contains an - additional method ``authenticate_additional_data`` for adding additional - authenticated but unencrypted data. You should call this before calls to - ``update``. When you are done call ``finalize()`` to finish the operation. - - .. method:: authenticate_additional_data(data) - - :param bytes data: The data you wish to authenticate but not encrypt. - :raises: :class:`~cryptography.exceptions.AlreadyFinalized` - -.. class:: AEADEncryptionContext - - When creating an encryption context using ``encryptor()`` on a ``Cipher`` - object with an AEAD mode you will receive a return object conforming to the - ``AEADEncryptionContext`` interface (as well as ``AEADCipherContext``). - This interface provides one additional attribute ``tag``. ``tag`` can only - be obtained after ``finalize()``. - - .. attribute:: tag - - :return bytes: Returns the tag value as bytes. - :raises: :class:`~cryptography.exceptions.NotYetFinalized` if called - before the context is finalized. - .. _symmetric-encryption-algorithms: Algorithms @@ -448,6 +375,89 @@ Insecure Modes identical plaintext blocks will always result in identical ciphertext blocks, and thus result in information leakage +Interfaces +---------- + +.. class:: CipherContext + + When calling ``encryptor()`` or ``decryptor()`` on a ``Cipher`` object + you will receive a return object conforming to the ``CipherContext`` + interface. You can then call ``update(data)`` with data until you have fed + everything into the context. Once that is done call ``finalize()`` to + finish the operation and obtain the remainder of the data. + + Block ciphers require that plaintext or ciphertext always be a multiple of + their block size, because of that **padding** is often required to make a + message the correct size. ``CipherContext`` will not automatically apply + any padding; you'll need to add your own. For block ciphers the recommended + padding is :class:`cryptography.hazmat.primitives.padding.PKCS7`. If you + are using a stream cipher mode (such as + :class:`cryptography.hazmat.primitives.modes.CTR`) you don't have to worry + about this. + + .. method:: update(data) + + :param bytes data: The data you wish to pass into the context. + :return bytes: Returns the data that was encrypted or decrypted. + :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` + + When the ``Cipher`` was constructed in a mode that turns it into a + stream cipher (e.g. + :class:`cryptography.hazmat.primitives.ciphers.modes.CTR`), this will + return bytes immediately, however in other modes it will return chunks, + whose size is determined by the cipher's block size. + + .. method:: finalize() + + :return bytes: Returns the remainder of the data. + :raises ValueError: This is raised when the data provided isn't + correctly padded to be a multiple of the + algorithm's block size. + + Once ``finalize`` is called this object can no longer be used and + :meth:`update` and :meth:`finalize` will raise + :class:`~cryptography.exceptions.AlreadyFinalized`. + +.. class:: AEADCipherContext + + When calling ``encryptor()`` or ``decryptor()`` on a ``Cipher`` object + with an AEAD mode (e.g. + :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM`) you will receive + a return object conforming to the ``AEADCipherContext`` and + ``CipherContext`` interfaces. If it is an encryption context it will + additionally be an ``AEADEncryptionContext`` interface. + ``AEADCipherContext`` contains an additional method + ``authenticate_additional_data`` for adding additional authenticated but + unencrypted data (see note below). You should call this before calls to + ``update``. When you are done call ``finalize()`` to finish the operation. + + .. note:: + + In AEAD modes all data passed to ``update()`` will be both encrypted + and authenticated. Do not pass encrypted data to the + ``authenticate_additional_data()`` method. It is meant solely for + additional data you may want to authenticate but leave unencrypted. + + .. method:: authenticate_additional_data(data) + + :param bytes data: Any data you wish to authenticate but not encrypt. + :raises: :class:`~cryptography.exceptions.AlreadyFinalized` + +.. class:: AEADEncryptionContext + + When creating an encryption context using ``encryptor()`` on a ``Cipher`` + object with an AEAD mode (e.g. + :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM`) you will receive + a return object conforming to the ``AEADEncryptionContext`` interface (as + well as ``AEADCipherContext``). This interface provides one additional + attribute ``tag``. ``tag`` can only be obtained after ``finalize()``. + + .. attribute:: tag + + :return bytes: Returns the tag value as bytes. + :raises: :class:`~cryptography.exceptions.NotYetFinalized` if called + before the context is finalized. + .. _`described by Colin Percival`: http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html .. _`recommends 96-bit IV length`: http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf -- cgit v1.2.3 From af0b9f56e761353593a0b33b1f4797169a716dec Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 7 Jan 2014 19:21:49 -0600 Subject: GCM does not require padding (removing from docs example) --- docs/hazmat/primitives/symmetric-encryption.rst | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index a683bb98..86267a25 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -364,8 +364,6 @@ Modes Cipher, algorithms, modes ) - from cryptography.hazmat.primitives.padding import PKCS7 - def encrypt(key, plaintext, associated_data): # Generate a random 96-bit IV. iv = os.urandom(12) @@ -378,17 +376,13 @@ Modes backend=default_backend() ).encryptor() - # We have to pad our plaintext because it may not be a - # multiple of the block size. - padder = PKCS7(algorithms.AES.block_size).padder() - padded_plaintext = padder.update(plaintext) + padder.finalize() - # associated_data will be authenticated but not encrypted, # it must also be passed in on decryption. encryptor.authenticate_additional_data(associated_data) # Encrypt the plaintext and get the associated ciphertext. - ciphertext = encryptor.update(padded_plaintext) + encryptor.finalize() + # GCM does not require padding. + ciphertext = encryptor.update(plaintext) + encryptor.finalize() return (iv, ciphertext, encryptor.tag) @@ -401,17 +395,13 @@ Modes backend=default_backend() ).decryptor() - # We will need to unpad the plaintext. - unpadder = PKCS7(algorithms.AES.block_size).unpadder() - # We put associated_data back in or the tag will fail to verify # when we finalize the decryptor. decryptor.authenticate_additional_data(associated_data) - # Decryption gets us the authenticated padded plaintext. - padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize() - - return unpadder.update(padded_plaintext) + unpadder.finalize() + # Decryption gets us the authenticated plaintext. + # If the tag does not match an InvalidTag exception will be raised. + return decryptor.update(ciphertext) + decryptor.finalize() iv, ciphertext, tag = encrypt( key, -- cgit v1.2.3 From c80ea854e3041a0b8e22ccbd2276a8230f8e38f0 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Tue, 7 Jan 2014 18:38:44 -0800 Subject: These eggs are sometimes files. We still don't care. --- .gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 9b8c49ba..1425b3b3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,8 @@ _build/ .cache/ *.egg-info/ .coverage -cffi-*.egg/ -pycparser-*.egg/ -pytest-*.egg/ +cffi-*.egg +pycparser-*.egg +pytest-*.egg dist/ htmlcov/ -- cgit v1.2.3 From fe2e3c2827f2776e8e4116b3aec50d4409476cd9 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 7 Jan 2014 20:55:20 -0600 Subject: add padding info to docs --- docs/hazmat/primitives/symmetric-encryption.rst | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst index 2233d525..83165690 100644 --- a/docs/hazmat/primitives/symmetric-encryption.rst +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -169,6 +169,8 @@ Modes CBC (Cipher block chaining) is a mode of operation for block ciphers. It is considered cryptographically strong. + **Padding is required when using this mode.** + :param bytes initialization_vector: Must be random bytes. They do not need to be kept secret (they can be included in a transmitted message). Must be the @@ -211,6 +213,8 @@ Modes cryptographically strong. It transforms a block cipher into a stream cipher. + **This mode does not require padding.** + :param bytes nonce: Should be random bytes. It is critical to never reuse a ``nonce`` with a given key. Any reuse of a nonce with the same key compromises the security of every @@ -224,6 +228,8 @@ Modes OFB (Output Feedback) is a mode of operation for block ciphers. It transforms a block cipher into a stream cipher. + **This mode does not require padding.** + :param bytes initialization_vector: Must be random bytes. They do not need to be kept secret (they can be included in a transmitted message). Must be the @@ -237,6 +243,8 @@ Modes CFB (Cipher Feedback) is a mode of operation for block ciphers. It transforms a block cipher into a stream cipher. + **This mode does not require padding.** + :param bytes initialization_vector: Must be random bytes. They do not need to be kept secret (they can be included in a transmitted message). Must be the @@ -261,6 +269,8 @@ Modes Additional means of verifying integrity (like :doc:`HMAC `) are not necessary. + **This mode does not require padding.** + :param bytes initialization_vector: Must be random bytes. They do not need to be kept secret (they can be included in a transmitted message). NIST @@ -365,6 +375,8 @@ Insecure Modes identical plaintext blocks will always result in identical ciphertext blocks, and thus result in information leakage + **Padding is required when using this mode.** + Interfaces ---------- @@ -377,8 +389,8 @@ Interfaces finish the operation and obtain the remainder of the data. Block ciphers require that plaintext or ciphertext always be a multiple of - their block size, because of that **padding** is often required to make a - message the correct size. ``CipherContext`` will not automatically apply + their block size, because of that **padding** is sometimes required to make + a message the correct size. ``CipherContext`` will not automatically apply any padding; you'll need to add your own. For block ciphers the recommended padding is :class:`cryptography.hazmat.primitives.padding.PKCS7`. If you are using a stream cipher mode (such as -- cgit v1.2.3 From 3f23040fca700b9e15c528ebdebd67764e7cec2c Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 8 Jan 2014 09:21:57 -0800 Subject: Fixed #428 -- added a changelog --- docs/changelog.rst | 9 +++++++++ docs/contributing.rst | 2 ++ docs/doing-a-release.rst | 1 + docs/index.rst | 1 + 4 files changed, 13 insertions(+) create mode 100644 docs/changelog.rst diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 00000000..e46d8c9b --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1,9 @@ +Changelog +========= + +0.1 +~~~ + +**Released on XXX** + +* Initial released. diff --git a/docs/contributing.rst b/docs/contributing.rst index 657c4359..8e32c368 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -29,6 +29,8 @@ devastating, ``cryptography`` has a strict code review policy: backwards incompatible release of a dependency) no pull requests may be merged until this is rectified. * All merged patches must have 100% test coverage. +* New features and significant bug fixes should be documented in the + :doc:`/changelog`. The purpose of these policies is to minimize the chances we merge a change which jeopardizes our users' security. diff --git a/docs/doing-a-release.rst b/docs/doing-a-release.rst index 0f382064..194e82f4 100644 --- a/docs/doing-a-release.rst +++ b/docs/doing-a-release.rst @@ -10,6 +10,7 @@ The first step in doing a release is bumping the version number in the software. * Update the version number in ``cryptography/__about__.py``. +* Set the release date in the :doc:`/changelog`. * Do a commit indicating this. * Send a pull request with this. * Wait for it to be merged. diff --git a/docs/index.rst b/docs/index.rst index 4bbfe7fd..9967075a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -80,4 +80,5 @@ The ``cryptography`` open source project security api-stability doing-a-release + changelog community -- cgit v1.2.3 From 7c6d7d6b71743457fe7d7c7f5cd607ca46b187f8 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 8 Jan 2014 09:46:27 -0800 Subject: Typo fix --- docs/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index e46d8c9b..52fcbfe3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,4 +6,4 @@ Changelog **Released on XXX** -* Initial released. +* Initial release. -- cgit v1.2.3 From 924599e267250bb5f1bb80d6b0345918e3d52094 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 8 Jan 2014 09:47:05 -0800 Subject: Reformat --- docs/changelog.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 52fcbfe3..4d782216 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,9 +1,7 @@ Changelog ========= -0.1 -~~~ - -**Released on XXX** +0.1 - YYYY-MM-DD +~~~~~~~~~~~~~~~~ * Initial release. -- cgit v1.2.3 From d873b4cf3f53f7dee8ed68722e9c413d9f60881c Mon Sep 17 00:00:00 2001 From: David Reid Date: Wed, 8 Jan 2014 11:42:45 -0800 Subject: Prepare to release. --- cryptography/__about__.py | 2 +- docs/changelog.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cryptography/__about__.py b/cryptography/__about__.py index 54a9dbe6..95f85749 100644 --- a/cryptography/__about__.py +++ b/cryptography/__about__.py @@ -22,7 +22,7 @@ __summary__ = ("cryptography is a package designed to expose cryptographic " "recipes and primitives to Python developers.") __uri__ = "https://github.com/pyca/cryptography" -__version__ = "0.1.dev1" +__version__ = "0.1" __author__ = ("Alex Gaynor, Hynek Schlawack, Donald Stufft, " "Laurens Van Houtven, Jean-Paul Calderone, Christian Heimes, " diff --git a/docs/changelog.rst b/docs/changelog.rst index 4d782216..b2d24c58 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,7 +1,7 @@ Changelog ========= -0.1 - YYYY-MM-DD +0.1 - 2014-01-08 ~~~~~~~~~~~~~~~~ * Initial release. -- cgit v1.2.3 From 232189f3c209e74897d4ac41a09db221a9707a8c Mon Sep 17 00:00:00 2001 From: David Reid Date: Wed, 8 Jan 2014 13:24:26 -0800 Subject: I guess I should take some of the blame. --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 953ca55b..ad27cec6 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -11,3 +11,4 @@ PGP key fingerprints are enclosed in parentheses. * Paul Kehrer * Jarret Raim * Alex Stapleton (A1C7 E50B 66DE 39ED C847 9665 8E3C 20D1 9BD9 5C4C) +* David Reid (0F83 CC87 B32F 482B C726 B58A 9FBF D8F4 DA89 6D74) -- cgit v1.2.3 From d24af7e0d8940b40b1be4e07a7134317b4c7c034 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Wed, 8 Jan 2014 15:36:09 -0600 Subject: Ignore six eggs, not a full dozen --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1425b3b3..df99ee0b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,6 @@ _build/ cffi-*.egg pycparser-*.egg pytest-*.egg +six-*.egg dist/ htmlcov/ -- cgit v1.2.3 From 292902e259029cea91bdf2e725861462db223057 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 8 Jan 2014 15:18:52 -0800 Subject: Document that we're on PyPI --- docs/index.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 9967075a..e17b4f9f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,12 +8,11 @@ needs in Python. Installing ---------- -We don't yet have a release on PyPI, for now you can install ``cryptography`` -directly from Github: +You can install ``cryptography`` with ``pip``: .. code-block:: console - $ pip install git+https://github.com/pyca/cryptography + $ pip install cryptography Why a new crypto library for Python? ------------------------------------ -- cgit v1.2.3 From 08754db41c8affe27310d26147546bde5baca9cc Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 8 Jan 2014 17:46:32 -0600 Subject: update version to 0.2.dev1 in master --- cryptography/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography/__about__.py b/cryptography/__about__.py index 95f85749..aa1878da 100644 --- a/cryptography/__about__.py +++ b/cryptography/__about__.py @@ -22,7 +22,7 @@ __summary__ = ("cryptography is a package designed to expose cryptographic " "recipes and primitives to Python developers.") __uri__ = "https://github.com/pyca/cryptography" -__version__ = "0.1" +__version__ = "0.2.dev1" __author__ = ("Alex Gaynor, Hynek Schlawack, Donald Stufft, " "Laurens Van Houtven, Jean-Paul Calderone, Christian Heimes, " -- cgit v1.2.3 From 254258323c0f978ddfcac6d7aeb7e24ff8534a9b Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Wed, 8 Jan 2014 19:39:55 -0600 Subject: Ignore all eggs --- .gitignore | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index df99ee0b..3fe3c942 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,6 @@ _build/ .cache/ *.egg-info/ .coverage -cffi-*.egg -pycparser-*.egg -pytest-*.egg -six-*.egg +*.egg dist/ htmlcov/ -- cgit v1.2.3 From 2b64fe2222e383bd6f68203b9bbcd9d8af7ec1fd Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 8 Jan 2014 19:59:28 -0600 Subject: add 0.2 changelog section --- docs/changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index b2d24c58..41db635e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,5 +1,9 @@ Changelog ========= +0.2 - 2014-XX-XX +~~~~~~~~~~~~~~~~ + +* In development. 0.1 - 2014-01-08 ~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From 0b06e554725585b8accb68e70bc95452d5bacc94 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Tue, 7 Jan 2014 21:41:15 -0600 Subject: support osx and linux on travis --- .travis.yml | 96 ++++++++++++++++++++++++++++++++++++++++++++++-------- .travis/install.sh | 67 ++++++++++++++++++++++++++++++++++++- 2 files changed, 149 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0a8771ef..c7846194 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,26 +1,21 @@ -language: python -python: 2.7 +language: c +os: + - linux + - osx +compiler: + - clang + - gcc env: - TOX_ENV=py26 - TOX_ENV=py27 - TOX_ENV=py32 - TOX_ENV=py33 - TOX_ENV=pypy - - TOX_ENV=py26 CC=clang - - TOX_ENV=py27 CC=clang - - TOX_ENV=py32 CC=clang - - TOX_ENV=py33 CC=clang - - TOX_ENV=pypy CC=clang - TOX_ENV=py26 OPENSSL=0.9.8 - TOX_ENV=py27 OPENSSL=0.9.8 - TOX_ENV=py32 OPENSSL=0.9.8 - TOX_ENV=py33 OPENSSL=0.9.8 - TOX_ENV=pypy OPENSSL=0.9.8 - - TOX_ENV=py26 CC=clang OPENSSL=0.9.8 - - TOX_ENV=py27 CC=clang OPENSSL=0.9.8 - - TOX_ENV=py32 CC=clang OPENSSL=0.9.8 - - TOX_ENV=py33 CC=clang OPENSSL=0.9.8 - - TOX_ENV=pypy CC=clang OPENSSL=0.9.8 - TOX_ENV=docs - TOX_ENV=pep8 - TOX_ENV=py3pep8 @@ -29,7 +24,7 @@ install: - ./.travis/install.sh script: - - tox -e $TOX_ENV + - if [[ "$(uname -s)" == "Darwin" ]]; then eval "$(pyenv init -)";fi && source ~/.venv/bin/activate && tox -e $TOX_ENV after_success: - coveralls @@ -40,3 +35,78 @@ notifications: - "irc.freenode.org#cryptography-dev" use_notice: true skip_join: true + +matrix: + exclude: + - os: osx + env: TOX_ENV=py26 + compiler: gcc + - os: osx + env: TOX_ENV=py27 + compiler: gcc + - os: osx + env: TOX_ENV=py32 + compiler: gcc + - os: osx + env: TOX_ENV=py33 + compiler: gcc + - os: osx + env: TOX_ENV=pypy + compiler: gcc + - os: osx + env: TOX_ENV=py26 + compiler: clang + - os: osx + env: TOX_ENV=py27 + compiler: clang + - os: osx + env: TOX_ENV=py32 + compiler: clang + - os: osx + env: TOX_ENV=py33 + compiler: clang + - os: osx + env: TOX_ENV=pypy + compiler: clang + - os: osx + env: TOX_ENV=py26 OPENSSL=0.9.8 + compiler: gcc + - os: osx + env: TOX_ENV=py27 OPENSSL=0.9.8 + compiler: gcc + - os: osx + env: TOX_ENV=py32 OPENSSL=0.9.8 + compiler: gcc + - os: osx + env: TOX_ENV=py33 OPENSSL=0.9.8 + compiler: gcc + - os: osx + env: TOX_ENV=pypy OPENSSL=0.9.8 + compiler: gcc + - os: osx + env: TOX_ENV=docs + compiler: gcc + - os: osx + env: TOX_ENV=pep8 + compiler: gcc + - os: osx + env: TOX_ENV=py3pep8 + compiler: gcc + - os: osx + env: TOX_ENV=docs + compiler: clang + - os: osx + env: TOX_ENV=pep8 + compiler: clang + - os: osx + env: TOX_ENV=py3pep8 + compiler: clang + - os: linux + env: TOX_ENV=docs + compiler: clang + - os: linux + env: TOX_ENV=pep8 + compiler: clang + - os: linux + env: TOX_ENV=py3pep8 + compiler: clang diff --git a/.travis/install.sh b/.travis/install.sh index 4aa39799..d25ed941 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -3,10 +3,75 @@ set -e set -x -if [[ "${OPENSSL}" == "0.9.8" ]]; then +if [[ "${OPENSSL}" == "0.9.8" && "$(uname -s)" != "Darwin" ]]; then sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu/ lucid main" sudo apt-get -y update sudo apt-get install -y --force-yes libssl-dev/lucid fi +if [[ "$(uname -s)" == "Darwin" ]]; then + brew update + brew install pyenv + if which pyenv > /dev/null; then eval "$(pyenv init -)"; fi + case "${TOX_ENV}" in + py26) + sudo easy_install pip + sudo pip install setuptools --no-use-wheel --upgrade + sudo pip install virtualenv + ;; + py27) + sudo easy_install pip + sudo pip install setuptools --no-use-wheel --upgrade + sudo pip install virtualenv + ;; + pypy) + pyenv install pypy-2.2.1 + pyenv global pypy-2.2.1 + pip install virtualenv + ;; + py32) + pyenv install 3.2.5 + pyenv global 3.2.5 + pip install virtualenv + ;; + py33) + pyenv install 3.3.2 + pyenv global 3.3.2 + pip install virtualenv + ;; + esac + pyenv rehash +else + # add mega-python ppa + sudo add-apt-repository -y ppa:fkrull/deadsnakes + sudo apt-get -y update + + case "${TOX_ENV}" in + py26) + sudo apt-get install python2.6 python2.6-dev + ;; + py32) + sudo apt-get install python3.2 python3.2-dev + ;; + py33) + sudo apt-get install python3.3 python3.3-dev + ;; + py3pep8) + sudo apt-get install python3.3 python3.3-dev + ;; + pypy) + sudo add-apt-repository -y ppa:pypy/ppa + sudo apt-get -y update + sudo apt-get install -y --force-yes pypy pypy-dev + ;; + esac + sudo pip install virtualenv +fi + +virtualenv ~/.venv +source ~/.venv/bin/activate pip install tox coveralls + +if [[ "$(uname -s)" == "Darwin" ]]; then + pyenv rehash +fi -- cgit v1.2.3 From 5bbcf7660b07f5a77ce9ff5666bede3c3377dfc7 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Wed, 8 Jan 2014 18:36:57 -0800 Subject: Make verify on HMAC more prominent --- docs/hazmat/primitives/hmac.rst | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/docs/hazmat/primitives/hmac.rst b/docs/hazmat/primitives/hmac.rst index dc5c54f8..f4f5daa1 100644 --- a/docs/hazmat/primitives/hmac.rst +++ b/docs/hazmat/primitives/hmac.rst @@ -37,6 +37,17 @@ message. If the backend doesn't support the requested ``algorithm`` an :class:`~cryptography.exceptions.UnsupportedAlgorithm` will be raised. + If you've been given a signature and need to check that it's correct, this + can be done with the :meth:`verify` method, you'll get an exception if the + signature is wrong: + + .. code-block:: pycon + + >>> h.verify(b"an incorrect signature") + Traceback (most recent call last): + ... + cryptography.exceptions.InvalidSignature: Signature did not match digest. + :param key: Secret key as ``bytes``. :param algorithm: A :class:`~cryptography.hazmat.primitives.interfaces.HashAlgorithm` @@ -61,17 +72,6 @@ message. and finalized independently of the original instance. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` - .. method:: finalize() - - Finalize the current context and return the message digest as bytes. - - Once ``finalize`` is called this object can no longer be used and - :meth:`update`, :meth:`copy`, and :meth:`finalize` will raise - :class:`~cryptography.exceptions.AlreadyFinalized`. - - :return bytes: The message digest as bytes. - :raises cryptography.exceptions.AlreadyFinalized: - .. method:: verify(signature) Finalize the current context and securely compare digest to @@ -82,3 +82,14 @@ message. :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` :raises cryptography.exceptions.InvalidSignature: If signature does not match digest + + .. method:: finalize() + + Finalize the current context and return the message digest as bytes. + + Once ``finalize`` is called this object can no longer be used and + :meth:`update`, :meth:`copy`, and :meth:`finalize` will raise + :class:`~cryptography.exceptions.AlreadyFinalized`. + + :return bytes: The message digest as bytes. + :raises cryptography.exceptions.AlreadyFinalized: -- cgit v1.2.3 From d1d952de521d66de81f5c6a12cc00a837eb929a2 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 8 Jan 2014 21:42:57 -0600 Subject: add comment about why the travis exclude matrix is so large --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index c7846194..92ee221b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,6 +36,10 @@ notifications: use_notice: true skip_join: true +# When building an exclude matrix on Travis you must supply +# the exact variable combinations you want to exclude from +# your build matrix. There is no (current) way to make this +# less verbose. matrix: exclude: - os: osx -- cgit v1.2.3 From 288bae7ede180812c1229fc82eda76e47aa73f58 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 8 Jan 2014 21:46:18 -0600 Subject: rewrite some text about the hmac verify method --- docs/hazmat/primitives/hmac.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/hazmat/primitives/hmac.rst b/docs/hazmat/primitives/hmac.rst index f4f5daa1..a21799be 100644 --- a/docs/hazmat/primitives/hmac.rst +++ b/docs/hazmat/primitives/hmac.rst @@ -37,9 +37,8 @@ message. If the backend doesn't support the requested ``algorithm`` an :class:`~cryptography.exceptions.UnsupportedAlgorithm` will be raised. - If you've been given a signature and need to check that it's correct, this - can be done with the :meth:`verify` method, you'll get an exception if the - signature is wrong: + To check that a given signature is correct use the :meth:`verify` method. + You will receive an exception if the signature is wrong: .. code-block:: pycon -- cgit v1.2.3 From dd5d28d223a63ab31490db3ac42867c9fa6b0817 Mon Sep 17 00:00:00 2001 From: Alex Stapleton Date: Thu, 9 Jan 2014 10:23:54 +0000 Subject: Add myself to __authors__ --- cryptography/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography/__about__.py b/cryptography/__about__.py index aa1878da..169e2ff5 100644 --- a/cryptography/__about__.py +++ b/cryptography/__about__.py @@ -26,7 +26,7 @@ __version__ = "0.2.dev1" __author__ = ("Alex Gaynor, Hynek Schlawack, Donald Stufft, " "Laurens Van Houtven, Jean-Paul Calderone, Christian Heimes, " - "Paul Kehrer, and individual contributors.") + "Paul Kehrer, Alex Stapleton, and individual contributors.") __email__ = "cryptography-dev@python.org" __license__ = "Apache License, Version 2.0" -- cgit v1.2.3 From 747beee6bb5af2f280ef15b1ec6f96c806cd3b19 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 9 Jan 2014 09:19:47 -0800 Subject: Simplify the authors section --- cryptography/__about__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cryptography/__about__.py b/cryptography/__about__.py index 169e2ff5..ee1d8a05 100644 --- a/cryptography/__about__.py +++ b/cryptography/__about__.py @@ -24,9 +24,7 @@ __uri__ = "https://github.com/pyca/cryptography" __version__ = "0.2.dev1" -__author__ = ("Alex Gaynor, Hynek Schlawack, Donald Stufft, " - "Laurens Van Houtven, Jean-Paul Calderone, Christian Heimes, " - "Paul Kehrer, Alex Stapleton, and individual contributors.") +__author__ = "The cryptography developers" __email__ = "cryptography-dev@python.org" __license__ = "Apache License, Version 2.0" -- cgit v1.2.3 From cc4a42dbc56974c10bf8dc0aca67ab29cccd2f77 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 4 Jan 2014 13:32:56 -0600 Subject: Add initial CommonCrypto bindings (no backend yet) --- .../hazmat/bindings/commoncrypto/__init__.py | 12 ++++ .../hazmat/bindings/commoncrypto/binding.py | 40 +++++++++++++ .../hazmat/bindings/commoncrypto/common_digest.py | 67 ++++++++++++++++++++++ tests/hazmat/bindings/test_commoncrypto.py | 25 ++++++++ 4 files changed, 144 insertions(+) create mode 100644 cryptography/hazmat/bindings/commoncrypto/__init__.py create mode 100644 cryptography/hazmat/bindings/commoncrypto/binding.py create mode 100644 cryptography/hazmat/bindings/commoncrypto/common_digest.py create mode 100644 tests/hazmat/bindings/test_commoncrypto.py diff --git a/cryptography/hazmat/bindings/commoncrypto/__init__.py b/cryptography/hazmat/bindings/commoncrypto/__init__.py new file mode 100644 index 00000000..55c925c6 --- /dev/null +++ b/cryptography/hazmat/bindings/commoncrypto/__init__.py @@ -0,0 +1,12 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/cryptography/hazmat/bindings/commoncrypto/binding.py b/cryptography/hazmat/bindings/commoncrypto/binding.py new file mode 100644 index 00000000..796135fc --- /dev/null +++ b/cryptography/hazmat/bindings/commoncrypto/binding.py @@ -0,0 +1,40 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +from cryptography.hazmat.bindings.utils import build_ffi + + +class Binding(object): + """ + CommonCrypto API wrapper. + """ + _module_prefix = "cryptography.hazmat.bindings.commoncrypto." + _modules = [ + "common_digest", + ] + + ffi = None + lib = None + + def __init__(self): + self._ensure_ffi_initialized() + + @classmethod + def _ensure_ffi_initialized(cls): + if cls.ffi is not None and cls.lib is not None: + return + + cls.ffi, cls.lib = build_ffi(cls._module_prefix, cls._modules, + "", "", []) diff --git a/cryptography/hazmat/bindings/commoncrypto/common_digest.py b/cryptography/hazmat/bindings/commoncrypto/common_digest.py new file mode 100644 index 00000000..ec0fcc92 --- /dev/null +++ b/cryptography/hazmat/bindings/commoncrypto/common_digest.py @@ -0,0 +1,67 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef uint32_t CC_LONG; +typedef uint64_t CC_LONG64; +typedef struct CC_MD5state_st { + ...; +} CC_MD5_CTX; +typedef struct CC_SHA1state_st { + ...; +} CC_SHA1_CTX; +typedef struct CC_SHA256state_st { + ...; +} CC_SHA256_CTX; +typedef struct CC_SHA512state_st { + ...; +} CC_SHA512_CTX; +""" + +FUNCTIONS = """ +int CC_MD5_Init(CC_MD5_CTX *); +int CC_MD5_Update(CC_MD5_CTX *, const void *, CC_LONG); +int CC_MD5_Final(unsigned char *, CC_MD5_CTX *); + +int CC_SHA1_Init(CC_SHA1_CTX *); +int CC_SHA1_Update(CC_SHA1_CTX *, const void *, CC_LONG); +int CC_SHA1_Final(unsigned char *, CC_SHA1_CTX *); + +int CC_SHA224_Init(CC_SHA256_CTX *); +int CC_SHA224_Update(CC_SHA256_CTX *, const void *, CC_LONG); +int CC_SHA224_Final(unsigned char *, CC_SHA256_CTX *); + +int CC_SHA256_Init(CC_SHA256_CTX *); +int CC_SHA256_Update(CC_SHA256_CTX *, const void *, CC_LONG); +int CC_SHA256_Final(unsigned char *, CC_SHA256_CTX *); + +int CC_SHA384_Init(CC_SHA512_CTX *); +int CC_SHA384_Update(CC_SHA512_CTX *, const void *, CC_LONG); +int CC_SHA384_Final(unsigned char *, CC_SHA512_CTX *); + +int CC_SHA512_Init(CC_SHA512_CTX *); +int CC_SHA512_Update(CC_SHA512_CTX *, const void *, CC_LONG); +int CC_SHA512_Final(unsigned char *, CC_SHA512_CTX *); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" + +CONDITIONAL_NAMES = {} diff --git a/tests/hazmat/bindings/test_commoncrypto.py b/tests/hazmat/bindings/test_commoncrypto.py new file mode 100644 index 00000000..385eeeb6 --- /dev/null +++ b/tests/hazmat/bindings/test_commoncrypto.py @@ -0,0 +1,25 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from cryptography.hazmat.bindings.commoncrypto.binding import Binding + + +@pytest.mark.commoncrypto +class TestCommonCrypto(object): + def test_binding_loads(self): + binding = Binding() + assert binding + assert binding.lib + assert binding.ffi -- cgit v1.2.3 From 5001c3f7479ff28457948a582e5e7446ac838ca6 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 4 Jan 2014 13:56:19 -0600 Subject: cover a missing branch in the commoncrypto bindings --- tests/hazmat/bindings/test_commoncrypto.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/hazmat/bindings/test_commoncrypto.py b/tests/hazmat/bindings/test_commoncrypto.py index 385eeeb6..1eb71151 100644 --- a/tests/hazmat/bindings/test_commoncrypto.py +++ b/tests/hazmat/bindings/test_commoncrypto.py @@ -13,13 +13,19 @@ import pytest -from cryptography.hazmat.bindings.commoncrypto.binding import Binding - @pytest.mark.commoncrypto class TestCommonCrypto(object): def test_binding_loads(self): + from cryptography.hazmat.bindings.commoncrypto.binding import Binding binding = Binding() assert binding assert binding.lib assert binding.ffi + + def test_binding_returns_same_lib(self): + from cryptography.hazmat.bindings.commoncrypto.binding import Binding + binding = Binding() + binding2 = Binding() + assert binding.lib == binding2.lib + assert binding.ffi == binding2.ffi -- cgit v1.2.3 From 5e612d0ac078bae569dece5f718166a834fa9f7e Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 4 Jan 2014 19:42:36 -0600 Subject: add is_available() to CommonCrypto binding, use it for skipif --- cryptography/hazmat/bindings/commoncrypto/binding.py | 8 +++++++- tests/hazmat/bindings/test_commoncrypto.py | 7 ++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/cryptography/hazmat/bindings/commoncrypto/binding.py b/cryptography/hazmat/bindings/commoncrypto/binding.py index 796135fc..ac5997ea 100644 --- a/cryptography/hazmat/bindings/commoncrypto/binding.py +++ b/cryptography/hazmat/bindings/commoncrypto/binding.py @@ -13,7 +13,9 @@ from __future__ import absolute_import, division, print_function -from cryptography.hazmat.bindings.utils import build_ffi +from cryptography.hazmat.bindings.utils import ( + build_ffi, binding_available +) class Binding(object): @@ -38,3 +40,7 @@ class Binding(object): cls.ffi, cls.lib = build_ffi(cls._module_prefix, cls._modules, "", "", []) + + @classmethod + def is_available(cls): + return binding_available(cls._ensure_ffi_initialized) diff --git a/tests/hazmat/bindings/test_commoncrypto.py b/tests/hazmat/bindings/test_commoncrypto.py index 1eb71151..db3d1b74 100644 --- a/tests/hazmat/bindings/test_commoncrypto.py +++ b/tests/hazmat/bindings/test_commoncrypto.py @@ -13,18 +13,19 @@ import pytest +from cryptography.hazmat.bindings.commoncrypto.binding import Binding -@pytest.mark.commoncrypto + +@pytest.mark.skipif(not Binding.is_available(), + reason="CommonCrypto not available") class TestCommonCrypto(object): def test_binding_loads(self): - from cryptography.hazmat.bindings.commoncrypto.binding import Binding binding = Binding() assert binding assert binding.lib assert binding.ffi def test_binding_returns_same_lib(self): - from cryptography.hazmat.bindings.commoncrypto.binding import Binding binding = Binding() binding2 = Binding() assert binding.lib == binding2.lib -- cgit v1.2.3 From 7fccf4c38e1bb63066232198b9aa4551e4da660f Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 4 Jan 2014 21:51:31 -0600 Subject: add CommonCrypto binding docs --- docs/hazmat/bindings/commoncrypto.rst | 28 ++++++++++++++++++++++++++++ docs/hazmat/bindings/index.rst | 1 + 2 files changed, 29 insertions(+) create mode 100644 docs/hazmat/bindings/commoncrypto.rst diff --git a/docs/hazmat/bindings/commoncrypto.rst b/docs/hazmat/bindings/commoncrypto.rst new file mode 100644 index 00000000..a4423365 --- /dev/null +++ b/docs/hazmat/bindings/commoncrypto.rst @@ -0,0 +1,28 @@ +.. hazmat:: + +CommonCrypto Binding +==================== + +.. currentmodule:: cryptography.hazmat.bindings.commoncrypto.binding + +These are `CFFI`_ bindings to the `CommonCrypto`_ C library. It is available on +Mac OS X. + +.. class:: cryptography.hazmat.bindings.commoncrypto.binding.Binding() + + This is the exposed API for the CommonCrypto bindings. It has two public + attributes: + + .. attribute:: ffi + + This is a :class:`cffi.FFI` instance. It can be used to allocate and + otherwise manipulate OpenSSL structures. + + .. attribute:: lib + + This is a ``cffi`` library. It can be used to call OpenSSL functions, + and access constants. + + +.. _`CFFI`: https://cffi.readthedocs.org/ +.. _`CommonCrypto`: https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/Common%20Crypto.3cc.html#//apple_ref/doc/man/3cc/CommonCrypto diff --git a/docs/hazmat/bindings/index.rst b/docs/hazmat/bindings/index.rst index e2a17591..caab8d6a 100644 --- a/docs/hazmat/bindings/index.rst +++ b/docs/hazmat/bindings/index.rst @@ -20,3 +20,4 @@ Individual Bindings :maxdepth: 1 openssl + commoncrypto -- cgit v1.2.3 From 79a230f81ff702d590ebc36169dc11392019839f Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Wed, 8 Jan 2014 22:40:12 -0600 Subject: refactor commoncrypto's is_available to check platform for now --- cryptography/hazmat/bindings/commoncrypto/binding.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cryptography/hazmat/bindings/commoncrypto/binding.py b/cryptography/hazmat/bindings/commoncrypto/binding.py index ac5997ea..e0cd61f7 100644 --- a/cryptography/hazmat/bindings/commoncrypto/binding.py +++ b/cryptography/hazmat/bindings/commoncrypto/binding.py @@ -13,9 +13,9 @@ from __future__ import absolute_import, division, print_function -from cryptography.hazmat.bindings.utils import ( - build_ffi, binding_available -) +import sys + +from cryptography.hazmat.bindings.utils import build_ffi class Binding(object): @@ -43,4 +43,4 @@ class Binding(object): @classmethod def is_available(cls): - return binding_available(cls._ensure_ffi_initialized) + return sys.platform == "darwin" -- cgit v1.2.3 From dcbc911263a33b36ed630d6f8f3b5deadaf40bfc Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 9 Jan 2014 10:07:30 -0800 Subject: easy_install is awful, don't use it --- .travis/install.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis/install.sh b/.travis/install.sh index d25ed941..8d6840f2 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -15,12 +15,14 @@ if [[ "$(uname -s)" == "Darwin" ]]; then if which pyenv > /dev/null; then eval "$(pyenv init -)"; fi case "${TOX_ENV}" in py26) - sudo easy_install pip + curl -O https://raw.github.com/pypa/pip/master/contrib/get-pip.py + sudo python get-pip.py sudo pip install setuptools --no-use-wheel --upgrade sudo pip install virtualenv ;; py27) - sudo easy_install pip + curl -O https://raw.github.com/pypa/pip/master/contrib/get-pip.py + sudo python get-pip.py sudo pip install setuptools --no-use-wheel --upgrade sudo pip install virtualenv ;; -- cgit v1.2.3 From 4af88912575473a02c590680ff25402a87a3b758 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 9 Jan 2014 10:10:26 -0800 Subject: Style nit --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 92ee221b..d28fef5b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ install: - ./.travis/install.sh script: - - if [[ "$(uname -s)" == "Darwin" ]]; then eval "$(pyenv init -)";fi && source ~/.venv/bin/activate && tox -e $TOX_ENV + - if [[ "$(uname -s)" == "Darwin" ]]; then eval "$(pyenv init -)"; fi && source ~/.venv/bin/activate && tox -e $TOX_ENV after_success: - coveralls -- cgit v1.2.3 From 3914dd0c27e4f52b8b3a173912bb1a558384484a Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 9 Jan 2014 18:23:12 -0600 Subject: temporary workaround for travis-ci bug bug: https://github.com/travis-ci/travis-ci/issues/1844 --- .travis.yml | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index d28fef5b..babea99b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,19 +6,25 @@ compiler: - clang - gcc env: - - TOX_ENV=py26 - - TOX_ENV=py27 - - TOX_ENV=py32 - - TOX_ENV=py33 - - TOX_ENV=pypy - - TOX_ENV=py26 OPENSSL=0.9.8 - - TOX_ENV=py27 OPENSSL=0.9.8 - - TOX_ENV=py32 OPENSSL=0.9.8 - - TOX_ENV=py33 OPENSSL=0.9.8 - - TOX_ENV=pypy OPENSSL=0.9.8 - - TOX_ENV=docs - - TOX_ENV=pep8 - - TOX_ENV=py3pep8 + # this global section can be removed when + # https://github.com/travis-ci/travis-ci/issues/1844 is fixed + global: + - CI=true + - TRAVIS=true + matrix: + - TOX_ENV=py26 + - TOX_ENV=py27 + - TOX_ENV=py32 + - TOX_ENV=py33 + - TOX_ENV=pypy + - TOX_ENV=py26 OPENSSL=0.9.8 + - TOX_ENV=py27 OPENSSL=0.9.8 + - TOX_ENV=py32 OPENSSL=0.9.8 + - TOX_ENV=py33 OPENSSL=0.9.8 + - TOX_ENV=pypy OPENSSL=0.9.8 + - TOX_ENV=docs + - TOX_ENV=pep8 + - TOX_ENV=py3pep8 install: - ./.travis/install.sh -- cgit v1.2.3 From 9180ba525c440ef7c8ef814f59aef4e57bc241b0 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 9 Jan 2014 18:06:52 -0800 Subject: Fix up a mistaken copy paste --- docs/hazmat/bindings/commoncrypto.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/hazmat/bindings/commoncrypto.rst b/docs/hazmat/bindings/commoncrypto.rst index a4423365..25535e02 100644 --- a/docs/hazmat/bindings/commoncrypto.rst +++ b/docs/hazmat/bindings/commoncrypto.rst @@ -16,12 +16,12 @@ Mac OS X. .. attribute:: ffi This is a :class:`cffi.FFI` instance. It can be used to allocate and - otherwise manipulate OpenSSL structures. + otherwise manipulate CommonCrypto structures. .. attribute:: lib - This is a ``cffi`` library. It can be used to call OpenSSL functions, - and access constants. + This is a ``cffi`` library. It can be used to call CommonCrypto + functions, and access constants. .. _`CFFI`: https://cffi.readthedocs.org/ -- cgit v1.2.3 From 0abdf870fa213684198b0e8965e6b71b289b0348 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 9 Jan 2014 22:21:14 -0600 Subject: add test marks to fernet so backends without cipher (or AES/CBC) will skip --- tests/test_fernet.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/test_fernet.py b/tests/test_fernet.py index 45188c47..bd4d90a5 100644 --- a/tests/test_fernet.py +++ b/tests/test_fernet.py @@ -25,6 +25,7 @@ import six from cryptography.fernet import Fernet, InvalidToken from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.ciphers import algorithms, modes def json_parametrize(keys, fname): @@ -37,7 +38,14 @@ def json_parametrize(keys, fname): ]) +@pytest.mark.cipher class TestFernet(object): + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES("\x00" * 32), modes.CBC("\x00" * 16) + ), + skip_message="Does not support AES CBC", + ) @json_parametrize( ("secret", "now", "iv", "src", "token"), "generate.json", ) @@ -50,6 +58,12 @@ class TestFernet(object): ) assert actual_token == token.encode("ascii") + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES("\x00" * 32), modes.CBC("\x00" * 16) + ), + skip_message="Does not support AES CBC", + ) @json_parametrize( ("secret", "now", "src", "ttl_sec", "token"), "verify.json", ) @@ -61,6 +75,12 @@ class TestFernet(object): payload = f.decrypt(token.encode("ascii"), ttl=ttl_sec) assert payload == src.encode("ascii") + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES("\x00" * 32), modes.CBC("\x00" * 16) + ), + skip_message="Does not support AES CBC", + ) @json_parametrize(("secret", "token", "now", "ttl_sec"), "invalid.json") def test_invalid(self, secret, token, now, ttl_sec, backend, monkeypatch): f = Fernet(secret.encode("ascii"), backend=backend) @@ -69,16 +89,34 @@ class TestFernet(object): with pytest.raises(InvalidToken): f.decrypt(token.encode("ascii"), ttl=ttl_sec) + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES("\x00" * 32), modes.CBC("\x00" * 16) + ), + skip_message="Does not support AES CBC", + ) def test_invalid_start_byte(self, backend): f = Fernet(Fernet.generate_key(), backend=backend) with pytest.raises(InvalidToken): f.decrypt(base64.urlsafe_b64encode(b"\x81")) + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES("\x00" * 32), modes.CBC("\x00" * 16) + ), + skip_message="Does not support AES CBC", + ) def test_timestamp_too_short(self, backend): f = Fernet(Fernet.generate_key(), backend=backend) with pytest.raises(InvalidToken): f.decrypt(base64.urlsafe_b64encode(b"\x80abc")) + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES("\x00" * 32), modes.CBC("\x00" * 16) + ), + skip_message="Does not support AES CBC", + ) def test_unicode(self, backend): f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) with pytest.raises(TypeError): @@ -86,6 +124,12 @@ class TestFernet(object): with pytest.raises(TypeError): f.decrypt(six.u("")) + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES("\x00" * 32), modes.CBC("\x00" * 16) + ), + skip_message="Does not support AES CBC", + ) @pytest.mark.parametrize("message", [b"", b"Abc!", b"\x00\xFF\x00\x80"]) def test_roundtrips(self, message, backend): f = Fernet(Fernet.generate_key(), backend=backend) @@ -95,6 +139,12 @@ class TestFernet(object): f = Fernet(Fernet.generate_key()) assert f._backend is default_backend() + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES("\x00" * 32), modes.CBC("\x00" * 16) + ), + skip_message="Does not support AES CBC", + ) def test_bad_key(self, backend): with pytest.raises(ValueError): Fernet(base64.urlsafe_b64encode(b"abc"), backend=backend) -- cgit v1.2.3 From 7d954bd5065138dec04a85d7d3be46f2cb3c1dae Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Fri, 10 Jan 2014 12:15:15 +0100 Subject: Expose ERR_load_SSL_strings --- cryptography/hazmat/bindings/openssl/err.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cryptography/hazmat/bindings/openssl/err.py b/cryptography/hazmat/bindings/openssl/err.py index 6b2a77b1..918c06d3 100644 --- a/cryptography/hazmat/bindings/openssl/err.py +++ b/cryptography/hazmat/bindings/openssl/err.py @@ -39,6 +39,7 @@ static const int ASN1_R_BAD_PASSWORD_READ; FUNCTIONS = """ void ERR_load_crypto_strings(void); +void ERR_load_SSL_strings(void); void ERR_free_strings(void); char* ERR_error_string(unsigned long, char *); void ERR_error_string_n(unsigned long, char *, size_t); -- cgit v1.2.3 From 9cd4b21fd91016040658f1ee0fb095fc11541651 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 10 Jan 2014 06:54:21 -0800 Subject: Use a normal quote here, not sure where the smart quote came from --- docs/security.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/security.rst b/docs/security.rst index 88959709..2b96d589 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -7,6 +7,6 @@ identified a security issue in it, please report it to fingerprint ``E27D 4AA0 1651 72CB C5D2 AF2B 125F 5C67 DFE9 4084`` (this public key is available from most commonly-used key servers). -Once you’ve submitted an issue via email, you should receive an acknowledgment +Once you've submitted an issue via email, you should receive an acknowledgment within 48 hours, and depending on the action to be taken, you may receive further followup emails. -- cgit v1.2.3 From fc4e2b740feba8d38ad9900e6203f619a31b71f3 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 10 Jan 2014 07:30:51 -0800 Subject: Try to run the spellchecker on travis --- docs/conf.py | 1 + docs/spelling_wordlist.txt | 28 ++++++++++++++++++++++++++++ tox.ini | 1 + 3 files changed, 30 insertions(+) create mode 100644 docs/spelling_wordlist.txt diff --git a/docs/conf.py b/docs/conf.py index 00660314..a42dcb22 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -38,6 +38,7 @@ extensions = [ 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', 'cryptography-docs', + 'sphinxcontrib.spelling', ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt new file mode 100644 index 00000000..97356c24 --- /dev/null +++ b/docs/spelling_wordlist.txt @@ -0,0 +1,28 @@ +backend +backends +boolean +ciphertext +committer +crypto +cryptographic +cryptographically +decrypt +decrypted +decrypting +fernet +hazmat +indistinguishability +introspectability +invariants +pickleable +plaintext +testability +unencrypted +unpadded +unpadding +Backends +Blowfish +Changelog +Docstrings +Fernet +Schneier diff --git a/tox.ini b/tox.ini index ce2f5398..9cd0699d 100644 --- a/tox.ini +++ b/tox.ini @@ -21,6 +21,7 @@ commands = sphinx-build -W -b latex -d {envtmpdir}/doctrees docs docs/_build/latex sphinx-build -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html sphinx-build -W -b linkcheck docs docs/_build/html + sphinx-build -W -b spelling docs docs/_build/html # Temporarily disable coverage on pypy because of performance problems with # coverage.py on pypy. -- cgit v1.2.3 From 5c0ec7c85e3623630a55e4da4b954f2fb594bf47 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 10 Jan 2014 08:08:58 -0800 Subject: Actuall install a thing --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 9cd0699d..a34ad633 100644 --- a/tox.ini +++ b/tox.ini @@ -14,6 +14,7 @@ commands = [testenv:docs] deps = sphinx + sphinxcontrib-spelling sphinx_rtd_theme basepython = python2.7 commands = -- cgit v1.2.3 From 2153c57164c0307ce864554136db15d1d201c739 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 10 Jan 2014 10:42:27 -0600 Subject: init the ssl library in the backend Also add some comments since this is mildly convoluted. --- cryptography/hazmat/backends/openssl/backend.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cryptography/hazmat/backends/openssl/backend.py b/cryptography/hazmat/backends/openssl/backend.py index 284fa989..07ee58c1 100644 --- a/cryptography/hazmat/backends/openssl/backend.py +++ b/cryptography/hazmat/backends/openssl/backend.py @@ -43,7 +43,11 @@ class Backend(object): self._ffi = self._binding.ffi self._lib = self._binding.lib + # adds all ciphers/digests for EVP self._lib.OpenSSL_add_all_algorithms() + # registers available SSL/TLS ciphers and digests + self._lib.SSL_library_init() + # loads error strings for libcrypto and libssl functions self._lib.SSL_load_error_strings() self._cipher_registry = {} -- cgit v1.2.3 From 29b40eaa062af0e1d6eb9733958bfd497dd8dc4d Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 10 Jan 2014 09:00:12 -0800 Subject: This is a dep --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index a34ad633..ff5df360 100644 --- a/tox.ini +++ b/tox.ini @@ -13,6 +13,7 @@ commands = [testenv:docs] deps = + pyenchant sphinx sphinxcontrib-spelling sphinx_rtd_theme -- cgit v1.2.3 From 50e58d4472f6c5a599ca683dcf1b532c310a2d7a Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 10 Jan 2014 09:59:55 -0800 Subject: Nonsense I think we need. --- .travis/install.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis/install.sh b/.travis/install.sh index 8d6840f2..e6ea2537 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -9,6 +9,11 @@ if [[ "${OPENSSL}" == "0.9.8" && "$(uname -s)" != "Darwin" ]]; then sudo apt-get install -y --force-yes libssl-dev/lucid fi +if [[ "${TOX_ENV}" == "docs" && "$(name -s)" != "Darwin" ]]; then + sudo apt-get -y update + sudo apt-get install libenchant-dev +fi + if [[ "$(uname -s)" == "Darwin" ]]; then brew update brew install pyenv -- cgit v1.2.3 From cf77d3ad5390e6e00bbb38f379effe8df401fabb Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 10 Jan 2014 12:13:05 -0600 Subject: add tests to the openssl backend to verify that we've registered evp ciphers and ssl ciphers --- tests/hazmat/backends/test_openssl.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index ad399594..71250592 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -95,3 +95,11 @@ class TestOpenSSL(object): backend._lib.EVP_F_EVP_DECRYPTFINAL_EX, 0 ) + + def test_ssl_ciphers_registered(self): + meth = backend._lib.TLSv1_method() + assert backend._lib.SSL_CTX_new(meth) != backend._ffi.NULL + + def test_evp_ciphers_registered(self): + cipher = backend._lib.EVP_get_cipherbyname("aes-256-cbc") + assert cipher != backend._ffi.NULL -- cgit v1.2.3 From 82128826bb0a92779a9029645525a7dc280671be Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 10 Jan 2014 12:27:14 -0600 Subject: don't leak a context in the test --- tests/hazmat/backends/test_openssl.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 71250592..a212df4a 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -98,7 +98,9 @@ class TestOpenSSL(object): def test_ssl_ciphers_registered(self): meth = backend._lib.TLSv1_method() - assert backend._lib.SSL_CTX_new(meth) != backend._ffi.NULL + ctx = backend._lib.SSL_CTX_new(meth) + assert ctx != backend._ffi.NULL + backend._lib.SSL_CTX_free(ctx) def test_evp_ciphers_registered(self): cipher = backend._lib.EVP_get_cipherbyname("aes-256-cbc") -- cgit v1.2.3 From 44957cde537a85ad8dba524cb352f784b07fc307 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 10 Jan 2014 12:36:14 -0600 Subject: oops, bytes plz --- tests/hazmat/backends/test_openssl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index a212df4a..c70446b0 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -103,5 +103,5 @@ class TestOpenSSL(object): backend._lib.SSL_CTX_free(ctx) def test_evp_ciphers_registered(self): - cipher = backend._lib.EVP_get_cipherbyname("aes-256-cbc") + cipher = backend._lib.EVP_get_cipherbyname(b"aes-256-cbc") assert cipher != backend._ffi.NULL -- cgit v1.2.3 From 59075dfd1bc18ad778d04425d8941e07352d7bba Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 10 Jan 2014 11:40:03 -0800 Subject: Spelling! --- docs/security.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/security.rst b/docs/security.rst index 2b96d589..4dadc847 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -9,4 +9,4 @@ key is available from most commonly-used key servers). Once you've submitted an issue via email, you should receive an acknowledgment within 48 hours, and depending on the action to be taken, you may receive -further followup emails. +further follow-up emails. -- cgit v1.2.3 From 11a007ac544fd8754c29d0b06fc1be380d59a02f Mon Sep 17 00:00:00 2001 From: Alex Stapleton Date: Fri, 10 Jan 2014 22:19:05 +0000 Subject: Bind all the PEM errors --- cryptography/hazmat/bindings/openssl/err.py | 49 +++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/cryptography/hazmat/bindings/openssl/err.py b/cryptography/hazmat/bindings/openssl/err.py index 918c06d3..1b66bd2a 100644 --- a/cryptography/hazmat/bindings/openssl/err.py +++ b/cryptography/hazmat/bindings/openssl/err.py @@ -22,19 +22,62 @@ struct ERR_string_data_st { }; typedef struct ERR_string_data_st ERR_STRING_DATA; +static const int ASN1_R_BAD_PASSWORD_READ; + static const int ERR_LIB_EVP; static const int ERR_LIB_PEM; -static const int EVP_F_EVP_ENCRYPTFINAL_EX; static const int EVP_F_EVP_DECRYPTFINAL_EX; +static const int EVP_F_EVP_ENCRYPTFINAL_EX; static const int EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH; -static const int PEM_F_PEM_READ_BIO_PRIVATEKEY; static const int PEM_F_D2I_PKCS8PRIVATEKEY_BIO; +static const int PEM_F_D2I_PKCS8PRIVATEKEY_BIO; +static const int PEM_F_D2I_PKCS8PRIVATEKEY_FP; +static const int PEM_F_DO_PK8PKEY; +static const int PEM_F_DO_PK8PKEY_FP; +static const int PEM_F_LOAD_IV; +static const int PEM_F_PEM_ASN1_READ; +static const int PEM_F_PEM_ASN1_READ_BIO; +static const int PEM_F_PEM_ASN1_WRITE; +static const int PEM_F_PEM_ASN1_WRITE_BIO; +static const int PEM_F_PEM_DEF_CALLBACK; +static const int PEM_F_PEM_DO_HEADER; +static const int PEM_F_PEM_F_PEM_WRITE_PKCS8PRIVATEKEY; +static const int PEM_F_PEM_GET_EVP_CIPHER_INFO; +static const int PEM_F_PEM_PK8PKEY; +static const int PEM_F_PEM_READ; +static const int PEM_F_PEM_READ_BIO; +static const int PEM_F_PEM_READ_BIO_PRIVATEKEY; +static const int PEM_F_PEM_READ_BIO_PRIVATEKEY; +static const int PEM_F_PEM_READ_PRIVATEKEY; +static const int PEM_F_PEM_SEALFINAL; +static const int PEM_F_PEM_SEALINIT; +static const int PEM_F_PEM_SIGNFINAL; +static const int PEM_F_PEM_WRITE; +static const int PEM_F_PEM_WRITE_BIO; +static const int PEM_F_PEM_X509_INFO_READ; +static const int PEM_F_PEM_X509_INFO_READ_BIO; +static const int PEM_F_PEM_X509_INFO_WRITE_BIO; +static const int PEM_R_BAD_BASE64_DECODE; +static const int PEM_R_BAD_DECRYPT; +static const int PEM_R_BAD_END_LINE; +static const int PEM_R_BAD_IV_CHARS; static const int PEM_R_BAD_PASSWORD_READ; -static const int ASN1_R_BAD_PASSWORD_READ; +static const int PEM_R_BAD_PASSWORD_READ; +static const int PEM_R_ERROR_CONVERTING_PRIVATE_KEY; +static const int PEM_R_NOT_DEK_INFO; +static const int PEM_R_NOT_ENCRYPTED; +static const int PEM_R_NOT_PROC_TYPE; +static const int PEM_R_NO_START_LINE; +static const int PEM_R_PROBLEMS_GETTING_PASSWORD; +static const int PEM_R_PUBLIC_KEY_NO_RSA; +static const int PEM_R_READ_KEY; +static const int PEM_R_SHORT_HEADER; +static const int PEM_R_UNSUPPORTED_CIPHER; +static const int PEM_R_UNSUPPORTED_ENCRYPTION; """ FUNCTIONS = """ -- cgit v1.2.3 From ab2cfc70a63e49ed385f9bb9c4e44bc86025c3a5 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 10 Jan 2014 21:44:05 -0600 Subject: add check to confirm we've loaded error strings --- tests/hazmat/backends/test_openssl.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index c70446b0..421bb530 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -105,3 +105,10 @@ class TestOpenSSL(object): def test_evp_ciphers_registered(self): cipher = backend._lib.EVP_get_cipherbyname(b"aes-256-cbc") assert cipher != backend._ffi.NULL + + def test_error_strings_loaded(self): + err = backend._lib.ERR_error_string(101183626, backend._ffi.NULL) + assert backend._ffi.string(err) == ( + "error:0607F08A:digital envelope routines:EVP_EncryptFinal_ex:" + "data not multiple of block length" + ) -- cgit v1.2.3 From 985d99d2934befe8bcf6257cbd9036dee1934ed9 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 10 Jan 2014 22:01:04 -0600 Subject: bytes byte back --- tests/hazmat/backends/test_openssl.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py index 421bb530..2a329920 100644 --- a/tests/hazmat/backends/test_openssl.py +++ b/tests/hazmat/backends/test_openssl.py @@ -107,8 +107,9 @@ class TestOpenSSL(object): assert cipher != backend._ffi.NULL def test_error_strings_loaded(self): + # returns a value in a static buffer err = backend._lib.ERR_error_string(101183626, backend._ffi.NULL) assert backend._ffi.string(err) == ( - "error:0607F08A:digital envelope routines:EVP_EncryptFinal_ex:" - "data not multiple of block length" + b"error:0607F08A:digital envelope routines:EVP_EncryptFinal_ex:" + b"data not multiple of block length" ) -- cgit v1.2.3 From d1de411a6339671b7a1b03fa621d2f61fc3780cb Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 10 Jan 2014 22:32:51 -0600 Subject: add hmac to commoncrypto binding --- .../hazmat/bindings/commoncrypto/binding.py | 1 + .../hazmat/bindings/commoncrypto/common_hmac.py | 46 ++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 cryptography/hazmat/bindings/commoncrypto/common_hmac.py diff --git a/cryptography/hazmat/bindings/commoncrypto/binding.py b/cryptography/hazmat/bindings/commoncrypto/binding.py index e0cd61f7..9c1af40a 100644 --- a/cryptography/hazmat/bindings/commoncrypto/binding.py +++ b/cryptography/hazmat/bindings/commoncrypto/binding.py @@ -25,6 +25,7 @@ class Binding(object): _module_prefix = "cryptography.hazmat.bindings.commoncrypto." _modules = [ "common_digest", + "common_hmac", ] ffi = None diff --git a/cryptography/hazmat/bindings/commoncrypto/common_hmac.py b/cryptography/hazmat/bindings/commoncrypto/common_hmac.py new file mode 100644 index 00000000..a4abcb04 --- /dev/null +++ b/cryptography/hazmat/bindings/commoncrypto/common_hmac.py @@ -0,0 +1,46 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef struct { + ...; +} CCHmacContext; +enum CCHmacAlgorithm { + kCCHmacAlgSHA1, + kCCHmacAlgMD5, + kCCHmacAlgSHA256, + kCCHmacAlgSHA384, + kCCHmacAlgSHA512, + kCCHmacAlgSHA224 +}; +typedef uint32_t CCHmacAlgorithm; +""" + +FUNCTIONS = """ +void CCHmacInit(CCHmacContext *, CCHmacAlgorithm, const void *, size_t); +void CCHmacUpdate( CCHmacContext *, const void *, size_t); +void CCHmacFinal( CCHmacContext *, void *); + +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" + +CONDITIONAL_NAMES = {} -- cgit v1.2.3 From 8e306b793abe07e631779510def8da659db9e040 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 10 Jan 2014 22:39:54 -0600 Subject: remove extraneous spaces --- cryptography/hazmat/bindings/commoncrypto/common_hmac.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cryptography/hazmat/bindings/commoncrypto/common_hmac.py b/cryptography/hazmat/bindings/commoncrypto/common_hmac.py index a4abcb04..72945364 100644 --- a/cryptography/hazmat/bindings/commoncrypto/common_hmac.py +++ b/cryptography/hazmat/bindings/commoncrypto/common_hmac.py @@ -32,8 +32,8 @@ typedef uint32_t CCHmacAlgorithm; FUNCTIONS = """ void CCHmacInit(CCHmacContext *, CCHmacAlgorithm, const void *, size_t); -void CCHmacUpdate( CCHmacContext *, const void *, size_t); -void CCHmacFinal( CCHmacContext *, void *); +void CCHmacUpdate(CCHmacContext *, const void *, size_t); +void CCHmacFinal(CCHmacContext *, void *); """ -- cgit v1.2.3 From a8dcf8428fed7d66309959b64d5bd55794c25386 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 10 Jan 2014 23:34:20 -0600 Subject: require cffi >= 0.8.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index feed0cd8..d03e5ff8 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ with open(os.path.join(base_dir, "cryptography", "__about__.py")) as f: exec(f.read(), about) -CFFI_DEPENDENCY = "cffi>=0.6" +CFFI_DEPENDENCY = "cffi>=0.8.1" SIX_DEPENDENCY = "six>=1.4.1" requirements = [ -- cgit v1.2.3 From c54c762c93eb5bc23c36ccfe511344f2cd499364 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 10 Jan 2014 23:37:13 -0600 Subject: change to anonymous enum --- cryptography/hazmat/bindings/commoncrypto/common_hmac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptography/hazmat/bindings/commoncrypto/common_hmac.py b/cryptography/hazmat/bindings/commoncrypto/common_hmac.py index 72945364..a4bf9009 100644 --- a/cryptography/hazmat/bindings/commoncrypto/common_hmac.py +++ b/cryptography/hazmat/bindings/commoncrypto/common_hmac.py @@ -19,7 +19,7 @@ TYPES = """ typedef struct { ...; } CCHmacContext; -enum CCHmacAlgorithm { +enum { kCCHmacAlgSHA1, kCCHmacAlgMD5, kCCHmacAlgSHA256, -- cgit v1.2.3 From 7fcaa3715f1b3afde3256b6a01232c8a71fea891 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 10 Jan 2014 23:39:58 -0600 Subject: drop to >= 0.8 to make pypy happy --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d03e5ff8..e8bcc11f 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ with open(os.path.join(base_dir, "cryptography", "__about__.py")) as f: exec(f.read(), about) -CFFI_DEPENDENCY = "cffi>=0.8.1" +CFFI_DEPENDENCY = "cffi>=0.8" SIX_DEPENDENCY = "six>=1.4.1" requirements = [ -- cgit v1.2.3 From 0d58373aac5bdbd8f4b72a9bed02fc6a1e58b0b3 Mon Sep 17 00:00:00 2001 From: Alex Stapleton Date: Fri, 10 Jan 2014 22:39:12 +0000 Subject: Use pytest.fixture for backends This lets you chain in additional fixtures that vary by backend easily. --- tests/conftest.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0ddc3338..1d9f96ed 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ import pytest +from cryptography.hazmat.backends import _ALL_BACKENDS from cryptography.hazmat.backends.interfaces import ( HMACBackend, CipherBackend, HashBackend ) @@ -7,11 +8,9 @@ from cryptography.hazmat.backends.interfaces import ( from .utils import check_for_iface, check_backend_support -def pytest_generate_tests(metafunc): - from cryptography.hazmat.backends import _ALL_BACKENDS - - if "backend" in metafunc.fixturenames: - metafunc.parametrize("backend", _ALL_BACKENDS) +@pytest.fixture(params=_ALL_BACKENDS) +def backend(request): + return request.param @pytest.mark.trylast -- cgit v1.2.3 From 8d0434332075768668331abd568ad5a69bb81df4 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Sat, 11 Jan 2014 20:20:07 -0500 Subject: Make just one call to ffi.cdef for most of the definitions --- cryptography/hazmat/bindings/utils.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/cryptography/hazmat/bindings/utils.py b/cryptography/hazmat/bindings/utils.py index 69290eb3..9cc05506 100644 --- a/cryptography/hazmat/bindings/utils.py +++ b/cryptography/hazmat/bindings/utils.py @@ -34,6 +34,7 @@ def build_ffi(module_prefix, modules, pre_include, post_include, libraries): condition. """ ffi = cffi.FFI() + types = [] includes = [] functions = [] macros = [] @@ -43,8 +44,7 @@ def build_ffi(module_prefix, modules, pre_include, post_include, libraries): __import__(module_name) module = sys.modules[module_name] - ffi.cdef(module.TYPES) - + types.append(module.TYPES) macros.append(module.MACROS) functions.append(module.FUNCTIONS) includes.append(module.INCLUDES) @@ -53,10 +53,7 @@ def build_ffi(module_prefix, modules, pre_include, post_include, libraries): # loop over the functions & macros after declaring all the types # so we can set interdependent types in different files and still # have them all defined before we parse the funcs & macros - for func in functions: - ffi.cdef(func) - for macro in macros: - ffi.cdef(macro) + ffi.cdef("\n".join(types + functions + macros)) # We include functions here so that if we got any of their definitions # wrong, the underlying C compiler will explode. In C you are allowed -- cgit v1.2.3