From 270b9d46efdfdff9faea86a48ccb98147348418b Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 11 Sep 2014 18:04:48 -0500 Subject: Fix two bugs with CommonCrypto GCM that can result in invalid output. Bug #1: Call to AAD but no call to update. Get null tag bytes. Bug #2: Call to update without call to AAD. Get null ciphertext bytes. Fixes #1329 --- .../hazmat/backends/commoncrypto/ciphers.py | 10 +++++++ tests/hazmat/backends/test_commoncrypto.py | 31 ++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/cryptography/hazmat/backends/commoncrypto/ciphers.py b/cryptography/hazmat/backends/commoncrypto/ciphers.py index 525500c8..4f723487 100644 --- a/cryptography/hazmat/backends/commoncrypto/ciphers.py +++ b/cryptography/hazmat/backends/commoncrypto/ciphers.py @@ -151,6 +151,11 @@ class _GCMCipherContext(object): len(mode.initialization_vector) ) self._backend._check_cipher_response(res) + # CommonCrypto has a bug where calling update without at least one + # call to authenticate_additional_data will result in null byte output + # for ciphertext. The following empty byte string call prevents the + # issue, which is present in at least 10.8 and 10.9. + self.authenticate_additional_data(b"") def update(self, data): buf = self._backend._ffi.new("unsigned char[]", len(data)) @@ -164,6 +169,11 @@ class _GCMCipherContext(object): return self._backend._ffi.buffer(buf)[:] def finalize(self): + # CommonCrypto has a yet another bug where you must make at least one + # call to update. If you pass just AAD and call finalize without a call + # to update you'll get null bytes for tag. The following update call + # prevents this issue, which is present in at least 10.8 and 10.9. + self.update(b"") tag_size = self._cipher.block_size // 8 tag_buf = self._backend._ffi.new("unsigned char[]", tag_size) tag_len = self._backend._ffi.new("size_t *", tag_size) diff --git a/tests/hazmat/backends/test_commoncrypto.py b/tests/hazmat/backends/test_commoncrypto.py index 28d1a6ca..3ea7f016 100644 --- a/tests/hazmat/backends/test_commoncrypto.py +++ b/tests/hazmat/backends/test_commoncrypto.py @@ -13,6 +13,8 @@ from __future__ import absolute_import, division, print_function +import binascii + import pytest from cryptography import utils @@ -68,3 +70,32 @@ class TestCommonCrypto(object): ) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): cipher.encryptor() + + def test_gcm_tag_with_only_aad(self): + from cryptography.hazmat.backends.commoncrypto.backend import Backend + b = Backend() + key = binascii.unhexlify("1dde380d6b04fdcb004005b8a77bd5e3") + iv = binascii.unhexlify("5053bf901463f97decd88c33") + aad = binascii.unhexlify("f807f5f6133021d15cb6434d5ad95cf7d8488727") + tag = binascii.unhexlify("4bebf3ff2cb67bb5444dda53bd039e22") + + cipher = Cipher(AES(key), GCM(iv), backend=b) + encryptor = cipher.encryptor() + encryptor.authenticate_additional_data(aad) + encryptor.finalize() + assert encryptor.tag == tag + + def test_gcm_ciphertext_with_no_aad(self): + from cryptography.hazmat.backends.commoncrypto.backend import Backend + b = Backend() + key = binascii.unhexlify("e98b72a9881a84ca6b76e0f43e68647a") + iv = binascii.unhexlify("8b23299fde174053f3d652ba") + ct = binascii.unhexlify("5a3c1cf1985dbb8bed818036fdd5ab42") + tag = binascii.unhexlify("23c7ab0f952b7091cd324835043b5eb5") + pt = binascii.unhexlify("28286a321293253c3e0aa2704a278032") + + cipher = Cipher(AES(key), GCM(iv), backend=b) + encryptor = cipher.encryptor() + computed_ct = encryptor.update(pt) + encryptor.finalize() + assert computed_ct == ct + assert encryptor.tag == tag -- cgit v1.2.3 From ed54991dc764cb5374cc33e7b98c7284b75d4651 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 11 Sep 2014 18:34:14 -0500 Subject: byte strings are byte strings --- tests/hazmat/backends/test_commoncrypto.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/hazmat/backends/test_commoncrypto.py b/tests/hazmat/backends/test_commoncrypto.py index 3ea7f016..5f2e9aab 100644 --- a/tests/hazmat/backends/test_commoncrypto.py +++ b/tests/hazmat/backends/test_commoncrypto.py @@ -74,10 +74,10 @@ class TestCommonCrypto(object): def test_gcm_tag_with_only_aad(self): from cryptography.hazmat.backends.commoncrypto.backend import Backend b = Backend() - key = binascii.unhexlify("1dde380d6b04fdcb004005b8a77bd5e3") - iv = binascii.unhexlify("5053bf901463f97decd88c33") - aad = binascii.unhexlify("f807f5f6133021d15cb6434d5ad95cf7d8488727") - tag = binascii.unhexlify("4bebf3ff2cb67bb5444dda53bd039e22") + key = binascii.unhexlify(b"1dde380d6b04fdcb004005b8a77bd5e3") + iv = binascii.unhexlify(b"5053bf901463f97decd88c33") + aad = binascii.unhexlify(b"f807f5f6133021d15cb6434d5ad95cf7d8488727") + tag = binascii.unhexlify(b"4bebf3ff2cb67bb5444dda53bd039e22") cipher = Cipher(AES(key), GCM(iv), backend=b) encryptor = cipher.encryptor() @@ -88,11 +88,11 @@ class TestCommonCrypto(object): def test_gcm_ciphertext_with_no_aad(self): from cryptography.hazmat.backends.commoncrypto.backend import Backend b = Backend() - key = binascii.unhexlify("e98b72a9881a84ca6b76e0f43e68647a") - iv = binascii.unhexlify("8b23299fde174053f3d652ba") - ct = binascii.unhexlify("5a3c1cf1985dbb8bed818036fdd5ab42") - tag = binascii.unhexlify("23c7ab0f952b7091cd324835043b5eb5") - pt = binascii.unhexlify("28286a321293253c3e0aa2704a278032") + key = binascii.unhexlify(b"e98b72a9881a84ca6b76e0f43e68647a") + iv = binascii.unhexlify(b"8b23299fde174053f3d652ba") + ct = binascii.unhexlify(b"5a3c1cf1985dbb8bed818036fdd5ab42") + tag = binascii.unhexlify(b"23c7ab0f952b7091cd324835043b5eb5") + pt = binascii.unhexlify(b"28286a321293253c3e0aa2704a278032") cipher = Cipher(AES(key), GCM(iv), backend=b) encryptor = cipher.encryptor() -- cgit v1.2.3 From c48abb09571f7ade75612c8f254ca76df41ac80d Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Thu, 11 Sep 2014 19:52:19 -0500 Subject: moved GCM tests to be run against all backends, added radar bug numbers --- .../hazmat/backends/commoncrypto/ciphers.py | 2 ++ tests/hazmat/backends/test_commoncrypto.py | 31 ------------------- tests/hazmat/primitives/test_aes.py | 35 +++++++++++++++++++++- 3 files changed, 36 insertions(+), 32 deletions(-) diff --git a/cryptography/hazmat/backends/commoncrypto/ciphers.py b/cryptography/hazmat/backends/commoncrypto/ciphers.py index 4f723487..6d3ba863 100644 --- a/cryptography/hazmat/backends/commoncrypto/ciphers.py +++ b/cryptography/hazmat/backends/commoncrypto/ciphers.py @@ -155,6 +155,7 @@ class _GCMCipherContext(object): # call to authenticate_additional_data will result in null byte output # for ciphertext. The following empty byte string call prevents the # issue, which is present in at least 10.8 and 10.9. + # Filed as rdar://18314544 self.authenticate_additional_data(b"") def update(self, data): @@ -173,6 +174,7 @@ class _GCMCipherContext(object): # call to update. If you pass just AAD and call finalize without a call # to update you'll get null bytes for tag. The following update call # prevents this issue, which is present in at least 10.8 and 10.9. + # Filed as rdar://18314580 self.update(b"") tag_size = self._cipher.block_size // 8 tag_buf = self._backend._ffi.new("unsigned char[]", tag_size) diff --git a/tests/hazmat/backends/test_commoncrypto.py b/tests/hazmat/backends/test_commoncrypto.py index 5f2e9aab..28d1a6ca 100644 --- a/tests/hazmat/backends/test_commoncrypto.py +++ b/tests/hazmat/backends/test_commoncrypto.py @@ -13,8 +13,6 @@ from __future__ import absolute_import, division, print_function -import binascii - import pytest from cryptography import utils @@ -70,32 +68,3 @@ class TestCommonCrypto(object): ) with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): cipher.encryptor() - - def test_gcm_tag_with_only_aad(self): - from cryptography.hazmat.backends.commoncrypto.backend import Backend - b = Backend() - key = binascii.unhexlify(b"1dde380d6b04fdcb004005b8a77bd5e3") - iv = binascii.unhexlify(b"5053bf901463f97decd88c33") - aad = binascii.unhexlify(b"f807f5f6133021d15cb6434d5ad95cf7d8488727") - tag = binascii.unhexlify(b"4bebf3ff2cb67bb5444dda53bd039e22") - - cipher = Cipher(AES(key), GCM(iv), backend=b) - encryptor = cipher.encryptor() - encryptor.authenticate_additional_data(aad) - encryptor.finalize() - assert encryptor.tag == tag - - def test_gcm_ciphertext_with_no_aad(self): - from cryptography.hazmat.backends.commoncrypto.backend import Backend - b = Backend() - key = binascii.unhexlify(b"e98b72a9881a84ca6b76e0f43e68647a") - iv = binascii.unhexlify(b"8b23299fde174053f3d652ba") - ct = binascii.unhexlify(b"5a3c1cf1985dbb8bed818036fdd5ab42") - tag = binascii.unhexlify(b"23c7ab0f952b7091cd324835043b5eb5") - pt = binascii.unhexlify(b"28286a321293253c3e0aa2704a278032") - - cipher = Cipher(AES(key), GCM(iv), backend=b) - encryptor = cipher.encryptor() - computed_ct = encryptor.update(pt) + encryptor.finalize() - assert computed_ct == ct - assert encryptor.tag == tag diff --git a/tests/hazmat/primitives/test_aes.py b/tests/hazmat/primitives/test_aes.py index 5bde7d3c..13682753 100644 --- a/tests/hazmat/primitives/test_aes.py +++ b/tests/hazmat/primitives/test_aes.py @@ -18,7 +18,7 @@ import os import pytest -from cryptography.hazmat.primitives.ciphers import algorithms, modes +from cryptography.hazmat.primitives.ciphers import algorithms, base, modes from .utils import generate_aead_test, generate_encrypt_test from ...utils import load_nist_vectors @@ -228,3 +228,36 @@ class TestAESModeGCM(object): algorithms.AES, modes.GCM, ) + + def test_gcm_tag_with_only_aad(self, backend): + key = binascii.unhexlify(b"1dde380d6b04fdcb004005b8a77bd5e3") + iv = binascii.unhexlify(b"5053bf901463f97decd88c33") + aad = binascii.unhexlify(b"f807f5f6133021d15cb6434d5ad95cf7d8488727") + tag = binascii.unhexlify(b"4bebf3ff2cb67bb5444dda53bd039e22") + + cipher = base.Cipher( + algorithms.AES(key), + modes.GCM(iv), + backend=backend + ) + encryptor = cipher.encryptor() + encryptor.authenticate_additional_data(aad) + encryptor.finalize() + assert encryptor.tag == tag + + def test_gcm_ciphertext_with_no_aad(self, backend): + key = binascii.unhexlify(b"e98b72a9881a84ca6b76e0f43e68647a") + iv = binascii.unhexlify(b"8b23299fde174053f3d652ba") + ct = binascii.unhexlify(b"5a3c1cf1985dbb8bed818036fdd5ab42") + tag = binascii.unhexlify(b"23c7ab0f952b7091cd324835043b5eb5") + pt = binascii.unhexlify(b"28286a321293253c3e0aa2704a278032") + + cipher = base.Cipher( + algorithms.AES(key), + modes.GCM(iv), + backend=backend + ) + encryptor = cipher.encryptor() + computed_ct = encryptor.update(pt) + encryptor.finalize() + assert computed_ct == ct + assert encryptor.tag == tag -- cgit v1.2.3 From 9a11c00b464225f4aa3e761e103930c6b8b9115b Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Fri, 12 Sep 2014 15:03:32 -0500 Subject: resolve GCM tag issue with AAD only on OpenSSL 1.0.1 in Ubuntu 12.04 --- cryptography/hazmat/backends/openssl/ciphers.py | 8 ++++++++ tests/hazmat/primitives/test_aes.py | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/cryptography/hazmat/backends/openssl/ciphers.py b/cryptography/hazmat/backends/openssl/ciphers.py index c3a5499a..d37bb014 100644 --- a/cryptography/hazmat/backends/openssl/ciphers.py +++ b/cryptography/hazmat/backends/openssl/ciphers.py @@ -128,6 +128,14 @@ class _CipherContext(object): return self._backend._ffi.buffer(buf)[:outlen[0]] def finalize(self): + # OpenSSL 1.0.1 on Ubuntu 12.04 (and possibly other distributions) + # appears to have a bug where you must make at least one call to update + # even if you are only using authenticate_additional_data or the + # GCM tag will be wrong. An (empty) call to update resolves this + # and is harmless for all other versions of OpenSSL. + if isinstance(self._mode, GCM): + self.update(b"") + buf = self._backend._ffi.new("unsigned char[]", self._block_size) outlen = self._backend._ffi.new("int *") res = self._backend._lib.EVP_CipherFinal_ex(self._ctx, buf, outlen) diff --git a/tests/hazmat/primitives/test_aes.py b/tests/hazmat/primitives/test_aes.py index 13682753..e8e0eee4 100644 --- a/tests/hazmat/primitives/test_aes.py +++ b/tests/hazmat/primitives/test_aes.py @@ -230,10 +230,10 @@ class TestAESModeGCM(object): ) def test_gcm_tag_with_only_aad(self, backend): - key = binascii.unhexlify(b"1dde380d6b04fdcb004005b8a77bd5e3") - iv = binascii.unhexlify(b"5053bf901463f97decd88c33") - aad = binascii.unhexlify(b"f807f5f6133021d15cb6434d5ad95cf7d8488727") - tag = binascii.unhexlify(b"4bebf3ff2cb67bb5444dda53bd039e22") + key = binascii.unhexlify(b"5211242698bed4774a090620a6ca56f3") + iv = binascii.unhexlify(b"b1e1349120b6e832ef976f5d") + aad = binascii.unhexlify(b"b6d729aab8e6416d7002b9faa794c410d8d2f193") + tag = binascii.unhexlify(b"0f247e7f9c2505de374006738018493b") cipher = base.Cipher( algorithms.AES(key), -- cgit v1.2.3