aboutsummaryrefslogtreecommitdiffstats
path: root/tests/hazmat/primitives/test_aead.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/hazmat/primitives/test_aead.py')
-rw-r--r--tests/hazmat/primitives/test_aead.py446
1 files changed, 446 insertions, 0 deletions
diff --git a/tests/hazmat/primitives/test_aead.py b/tests/hazmat/primitives/test_aead.py
new file mode 100644
index 00000000..4f6bc7f4
--- /dev/null
+++ b/tests/hazmat/primitives/test_aead.py
@@ -0,0 +1,446 @@
+# This file is dual licensed under the terms of the Apache License, Version
+# 2.0, and the BSD License. See the LICENSE file in the root of this repository
+# for complete details.
+
+from __future__ import absolute_import, division, print_function
+
+import binascii
+import os
+
+import pytest
+
+from cryptography.exceptions import InvalidTag, UnsupportedAlgorithm, _Reasons
+from cryptography.hazmat.backends.interfaces import CipherBackend
+from cryptography.hazmat.primitives.ciphers.aead import (
+ AESCCM, AESGCM, ChaCha20Poly1305
+)
+
+from .utils import _load_all_params
+from ...utils import (
+ load_nist_ccm_vectors, load_nist_vectors, load_vectors_from_file,
+ raises_unsupported_algorithm
+)
+
+
+class FakeData(object):
+ def __len__(self):
+ return 2 ** 32 + 1
+
+
+def _aead_supported(cls):
+ try:
+ cls(b"0" * 32)
+ return True
+ except UnsupportedAlgorithm:
+ return False
+
+
+@pytest.mark.skipif(
+ _aead_supported(ChaCha20Poly1305),
+ reason="Requires OpenSSL without ChaCha20Poly1305 support"
+)
+@pytest.mark.requires_backend_interface(interface=CipherBackend)
+def test_chacha20poly1305_unsupported_on_older_openssl(backend):
+ with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER):
+ ChaCha20Poly1305(ChaCha20Poly1305.generate_key())
+
+
+@pytest.mark.skipif(
+ not _aead_supported(ChaCha20Poly1305),
+ reason="Does not support ChaCha20Poly1305"
+)
+@pytest.mark.requires_backend_interface(interface=CipherBackend)
+class TestChaCha20Poly1305(object):
+ def test_data_too_large(self):
+ key = ChaCha20Poly1305.generate_key()
+ chacha = ChaCha20Poly1305(key)
+ nonce = b"0" * 12
+
+ with pytest.raises(OverflowError):
+ chacha.encrypt(nonce, FakeData(), b"")
+
+ with pytest.raises(OverflowError):
+ chacha.encrypt(nonce, b"", FakeData())
+
+ def test_generate_key(self):
+ key = ChaCha20Poly1305.generate_key()
+ assert len(key) == 32
+
+ def test_bad_key(self, backend):
+ with pytest.raises(TypeError):
+ ChaCha20Poly1305(object())
+
+ with pytest.raises(ValueError):
+ ChaCha20Poly1305(b"0" * 31)
+
+ @pytest.mark.parametrize(
+ ("nonce", "data", "associated_data"),
+ [
+ [object(), b"data", b""],
+ [b"0" * 12, object(), b""],
+ [b"0" * 12, b"data", object()]
+ ]
+ )
+ def test_params_not_bytes_encrypt(self, nonce, data, associated_data,
+ backend):
+ key = ChaCha20Poly1305.generate_key()
+ chacha = ChaCha20Poly1305(key)
+ with pytest.raises(TypeError):
+ chacha.encrypt(nonce, data, associated_data)
+
+ with pytest.raises(TypeError):
+ chacha.decrypt(nonce, data, associated_data)
+
+ def test_nonce_not_12_bytes(self, backend):
+ key = ChaCha20Poly1305.generate_key()
+ chacha = ChaCha20Poly1305(key)
+ with pytest.raises(ValueError):
+ chacha.encrypt(b"00", b"hello", b"")
+
+ with pytest.raises(ValueError):
+ chacha.decrypt(b"00", b"hello", b"")
+
+ def test_decrypt_data_too_short(self, backend):
+ key = ChaCha20Poly1305.generate_key()
+ chacha = ChaCha20Poly1305(key)
+ with pytest.raises(InvalidTag):
+ chacha.decrypt(b"0" * 12, b"0", None)
+
+ def test_associated_data_none_equal_to_empty_bytestring(self, backend):
+ key = ChaCha20Poly1305.generate_key()
+ chacha = ChaCha20Poly1305(key)
+ nonce = os.urandom(12)
+ ct1 = chacha.encrypt(nonce, b"some_data", None)
+ ct2 = chacha.encrypt(nonce, b"some_data", b"")
+ assert ct1 == ct2
+ pt1 = chacha.decrypt(nonce, ct1, None)
+ pt2 = chacha.decrypt(nonce, ct2, b"")
+ assert pt1 == pt2
+
+ @pytest.mark.parametrize(
+ "vector",
+ load_vectors_from_file(
+ os.path.join("ciphers", "ChaCha20Poly1305", "openssl.txt"),
+ load_nist_vectors
+ )
+ )
+ def test_openssl_vectors(self, vector, backend):
+ key = binascii.unhexlify(vector["key"])
+ nonce = binascii.unhexlify(vector["iv"])
+ aad = binascii.unhexlify(vector["aad"])
+ tag = binascii.unhexlify(vector["tag"])
+ pt = binascii.unhexlify(vector["plaintext"])
+ ct = binascii.unhexlify(vector["ciphertext"])
+ chacha = ChaCha20Poly1305(key)
+ if vector.get("result") == b"CIPHERFINAL_ERROR":
+ with pytest.raises(InvalidTag):
+ chacha.decrypt(nonce, ct + tag, aad)
+ else:
+ computed_pt = chacha.decrypt(nonce, ct + tag, aad)
+ assert computed_pt == pt
+ computed_ct = chacha.encrypt(nonce, pt, aad)
+ assert computed_ct == ct + tag
+
+ @pytest.mark.parametrize(
+ "vector",
+ load_vectors_from_file(
+ os.path.join("ciphers", "ChaCha20Poly1305", "boringssl.txt"),
+ load_nist_vectors
+ )
+ )
+ def test_boringssl_vectors(self, vector, backend):
+ key = binascii.unhexlify(vector["key"])
+ nonce = binascii.unhexlify(vector["nonce"])
+ if vector["ad"].startswith(b'"'):
+ aad = vector["ad"][1:-1]
+ else:
+ aad = binascii.unhexlify(vector["ad"])
+ tag = binascii.unhexlify(vector["tag"])
+ if vector["in"].startswith(b'"'):
+ pt = vector["in"][1:-1]
+ else:
+ pt = binascii.unhexlify(vector["in"])
+ ct = binascii.unhexlify(vector["ct"].strip(b'"'))
+ chacha = ChaCha20Poly1305(key)
+ computed_pt = chacha.decrypt(nonce, ct + tag, aad)
+ assert computed_pt == pt
+ computed_ct = chacha.encrypt(nonce, pt, aad)
+ assert computed_ct == ct + tag
+
+ def test_buffer_protocol(self, backend):
+ key = ChaCha20Poly1305.generate_key()
+ chacha = ChaCha20Poly1305(key)
+ pt = b"encrypt me"
+ ad = b"additional"
+ nonce = os.urandom(12)
+ ct = chacha.encrypt(nonce, pt, ad)
+ computed_pt = chacha.decrypt(nonce, ct, ad)
+ assert computed_pt == pt
+ chacha2 = ChaCha20Poly1305(bytearray(key))
+ ct2 = chacha2.encrypt(bytearray(nonce), pt, ad)
+ assert ct2 == ct
+ computed_pt2 = chacha2.decrypt(bytearray(nonce), ct2, ad)
+ assert computed_pt2 == pt
+
+
+@pytest.mark.requires_backend_interface(interface=CipherBackend)
+class TestAESCCM(object):
+ def test_data_too_large(self):
+ key = AESCCM.generate_key(128)
+ aesccm = AESCCM(key)
+ nonce = b"0" * 12
+
+ with pytest.raises(OverflowError):
+ aesccm.encrypt(nonce, FakeData(), b"")
+
+ with pytest.raises(OverflowError):
+ aesccm.encrypt(nonce, b"", FakeData())
+
+ def test_default_tag_length(self, backend):
+ key = AESCCM.generate_key(128)
+ aesccm = AESCCM(key)
+ nonce = os.urandom(12)
+ pt = b"hello"
+ ct = aesccm.encrypt(nonce, pt, None)
+ assert len(ct) == len(pt) + 16
+
+ def test_invalid_tag_length(self, backend):
+ key = AESCCM.generate_key(128)
+ with pytest.raises(ValueError):
+ AESCCM(key, tag_length=7)
+
+ with pytest.raises(ValueError):
+ AESCCM(key, tag_length=2)
+
+ with pytest.raises(TypeError):
+ AESCCM(key, tag_length="notanint")
+
+ def test_invalid_nonce_length(self, backend):
+ key = AESCCM.generate_key(128)
+ aesccm = AESCCM(key)
+ pt = b"hello"
+ nonce = os.urandom(14)
+ with pytest.raises(ValueError):
+ aesccm.encrypt(nonce, pt, None)
+
+ with pytest.raises(ValueError):
+ aesccm.encrypt(nonce[:6], pt, None)
+
+ @pytest.mark.parametrize(
+ "vector",
+ _load_all_params(
+ os.path.join("ciphers", "AES", "CCM"),
+ [
+ "DVPT128.rsp", "DVPT192.rsp", "DVPT256.rsp",
+ "VADT128.rsp", "VADT192.rsp", "VADT256.rsp",
+ "VNT128.rsp", "VNT192.rsp", "VNT256.rsp",
+ "VPT128.rsp", "VPT192.rsp", "VPT256.rsp",
+ ],
+ load_nist_ccm_vectors
+ )
+ )
+ def test_vectors(self, vector, backend):
+ key = binascii.unhexlify(vector["key"])
+ nonce = binascii.unhexlify(vector["nonce"])
+ adata = binascii.unhexlify(vector["adata"])[:vector["alen"]]
+ ct = binascii.unhexlify(vector["ct"])
+ pt = binascii.unhexlify(vector["payload"])[:vector["plen"]]
+ aesccm = AESCCM(key, vector["tlen"])
+ if vector.get('fail'):
+ with pytest.raises(InvalidTag):
+ aesccm.decrypt(nonce, ct, adata)
+ else:
+ computed_pt = aesccm.decrypt(nonce, ct, adata)
+ assert computed_pt == pt
+ assert aesccm.encrypt(nonce, pt, adata) == ct
+
+ def test_roundtrip(self, backend):
+ key = AESCCM.generate_key(128)
+ aesccm = AESCCM(key)
+ pt = b"encrypt me"
+ ad = b"additional"
+ nonce = os.urandom(12)
+ ct = aesccm.encrypt(nonce, pt, ad)
+ computed_pt = aesccm.decrypt(nonce, ct, ad)
+ assert computed_pt == pt
+
+ def test_nonce_too_long(self, backend):
+ key = AESCCM.generate_key(128)
+ aesccm = AESCCM(key)
+ pt = b"encrypt me" * 6600
+ # pt can be no more than 65536 bytes when nonce is 13 bytes
+ nonce = os.urandom(13)
+ with pytest.raises(ValueError):
+ aesccm.encrypt(nonce, pt, None)
+
+ @pytest.mark.parametrize(
+ ("nonce", "data", "associated_data"),
+ [
+ [object(), b"data", b""],
+ [b"0" * 12, object(), b""],
+ [b"0" * 12, b"data", object()],
+ ]
+ )
+ def test_params_not_bytes(self, nonce, data, associated_data, backend):
+ key = AESCCM.generate_key(128)
+ aesccm = AESCCM(key)
+ with pytest.raises(TypeError):
+ aesccm.encrypt(nonce, data, associated_data)
+
+ def test_bad_key(self, backend):
+ with pytest.raises(TypeError):
+ AESCCM(object())
+
+ with pytest.raises(ValueError):
+ AESCCM(b"0" * 31)
+
+ def test_bad_generate_key(self, backend):
+ with pytest.raises(TypeError):
+ AESCCM.generate_key(object())
+
+ with pytest.raises(ValueError):
+ AESCCM.generate_key(129)
+
+ def test_associated_data_none_equal_to_empty_bytestring(self, backend):
+ key = AESCCM.generate_key(128)
+ aesccm = AESCCM(key)
+ nonce = os.urandom(12)
+ ct1 = aesccm.encrypt(nonce, b"some_data", None)
+ ct2 = aesccm.encrypt(nonce, b"some_data", b"")
+ assert ct1 == ct2
+ pt1 = aesccm.decrypt(nonce, ct1, None)
+ pt2 = aesccm.decrypt(nonce, ct2, b"")
+ assert pt1 == pt2
+
+ def test_decrypt_data_too_short(self, backend):
+ key = AESCCM.generate_key(128)
+ aesccm = AESCCM(key)
+ with pytest.raises(InvalidTag):
+ aesccm.decrypt(b"0" * 12, b"0", None)
+
+ def test_buffer_protocol(self, backend):
+ key = AESCCM.generate_key(128)
+ aesccm = AESCCM(key)
+ pt = b"encrypt me"
+ ad = b"additional"
+ nonce = os.urandom(12)
+ ct = aesccm.encrypt(nonce, pt, ad)
+ computed_pt = aesccm.decrypt(nonce, ct, ad)
+ assert computed_pt == pt
+ aesccm2 = AESCCM(bytearray(key))
+ ct2 = aesccm2.encrypt(bytearray(nonce), pt, ad)
+ assert ct2 == ct
+ computed_pt2 = aesccm2.decrypt(bytearray(nonce), ct2, ad)
+ assert computed_pt2 == pt
+
+
+def _load_gcm_vectors():
+ vectors = _load_all_params(
+ os.path.join("ciphers", "AES", "GCM"),
+ [
+ "gcmDecrypt128.rsp",
+ "gcmDecrypt192.rsp",
+ "gcmDecrypt256.rsp",
+ "gcmEncryptExtIV128.rsp",
+ "gcmEncryptExtIV192.rsp",
+ "gcmEncryptExtIV256.rsp",
+ ],
+ load_nist_vectors
+ )
+ return [x for x in vectors if len(x["tag"]) == 32]
+
+
+@pytest.mark.requires_backend_interface(interface=CipherBackend)
+class TestAESGCM(object):
+ def test_data_too_large(self):
+ key = AESGCM.generate_key(128)
+ aesgcm = AESGCM(key)
+ nonce = b"0" * 12
+
+ with pytest.raises(OverflowError):
+ aesgcm.encrypt(nonce, FakeData(), b"")
+
+ with pytest.raises(OverflowError):
+ aesgcm.encrypt(nonce, b"", FakeData())
+
+ @pytest.mark.parametrize("vector", _load_gcm_vectors())
+ def test_vectors(self, vector):
+ key = binascii.unhexlify(vector["key"])
+ nonce = binascii.unhexlify(vector["iv"])
+ aad = binascii.unhexlify(vector["aad"])
+ ct = binascii.unhexlify(vector["ct"])
+ pt = binascii.unhexlify(vector.get("pt", b""))
+ tag = binascii.unhexlify(vector["tag"])
+ aesgcm = AESGCM(key)
+ if vector.get("fail") is True:
+ with pytest.raises(InvalidTag):
+ aesgcm.decrypt(nonce, ct + tag, aad)
+ else:
+ computed_ct = aesgcm.encrypt(nonce, pt, aad)
+ assert computed_ct[:-16] == ct
+ assert computed_ct[-16:] == tag
+ computed_pt = aesgcm.decrypt(nonce, ct + tag, aad)
+ assert computed_pt == pt
+
+ @pytest.mark.parametrize(
+ ("nonce", "data", "associated_data"),
+ [
+ [object(), b"data", b""],
+ [b"0" * 12, object(), b""],
+ [b"0" * 12, b"data", object()]
+ ]
+ )
+ def test_params_not_bytes(self, nonce, data, associated_data, backend):
+ key = AESGCM.generate_key(128)
+ aesgcm = AESGCM(key)
+ with pytest.raises(TypeError):
+ aesgcm.encrypt(nonce, data, associated_data)
+
+ with pytest.raises(TypeError):
+ aesgcm.decrypt(nonce, data, associated_data)
+
+ def test_invalid_nonce_length(self, backend):
+ key = AESGCM.generate_key(128)
+ aesgcm = AESGCM(key)
+ with pytest.raises(ValueError):
+ aesgcm.encrypt(b"", b"hi", None)
+
+ def test_bad_key(self, backend):
+ with pytest.raises(TypeError):
+ AESGCM(object())
+
+ with pytest.raises(ValueError):
+ AESGCM(b"0" * 31)
+
+ def test_bad_generate_key(self, backend):
+ with pytest.raises(TypeError):
+ AESGCM.generate_key(object())
+
+ with pytest.raises(ValueError):
+ AESGCM.generate_key(129)
+
+ def test_associated_data_none_equal_to_empty_bytestring(self, backend):
+ key = AESGCM.generate_key(128)
+ aesgcm = AESGCM(key)
+ nonce = os.urandom(12)
+ ct1 = aesgcm.encrypt(nonce, b"some_data", None)
+ ct2 = aesgcm.encrypt(nonce, b"some_data", b"")
+ assert ct1 == ct2
+ pt1 = aesgcm.decrypt(nonce, ct1, None)
+ pt2 = aesgcm.decrypt(nonce, ct2, b"")
+ assert pt1 == pt2
+
+ def test_buffer_protocol(self, backend):
+ key = AESGCM.generate_key(128)
+ aesgcm = AESGCM(key)
+ pt = b"encrypt me"
+ ad = b"additional"
+ nonce = os.urandom(12)
+ ct = aesgcm.encrypt(nonce, pt, ad)
+ computed_pt = aesgcm.decrypt(nonce, ct, ad)
+ assert computed_pt == pt
+ aesgcm2 = AESGCM(bytearray(key))
+ ct2 = aesgcm2.encrypt(bytearray(nonce), pt, ad)
+ assert ct2 == ct
+ computed_pt2 = aesgcm2.decrypt(bytearray(nonce), ct2, ad)
+ assert computed_pt2 == pt