Skip to content

Commit b59d2de

Browse files
authored
GCM IV size limits (pyca#5553)
* GCM IV size limits OpenSSL 3.0.0 is going to enforce these size limits so we might as well put them in now. * fix the tests * black * these cases can't happen if we're limiting IV size already
1 parent 15771e2 commit b59d2de

8 files changed

Lines changed: 70 additions & 15 deletions

File tree

CHANGELOG.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ Changelog
1010

1111
* **BACKWARDS INCOMPATIBLE:** Support for Python 3.5 has been removed due to
1212
low usage and maintenance burden.
13+
* **BACKWARDS INCOMPATIBLE:** The
14+
:class:`~cryptography.hazmat.primitives.ciphers.modes.GCM` and
15+
:class:`~cryptography.hazmat.primitives.ciphers.aead.AESGCM` now require
16+
64-bit to 1024-bit (8 byte to 128 byte) initialization vectors. This change
17+
is to conform with an upcoming OpenSSL release that will no longer support
18+
sizes outside this window.
1319

1420
.. _v3-2-1:
1521

src/cryptography/hazmat/primitives/ciphers/aead.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,5 +170,5 @@ def _check_params(self, nonce, data, associated_data):
170170
utils._check_byteslike("nonce", nonce)
171171
utils._check_bytes("data", data)
172172
utils._check_bytes("associated_data", associated_data)
173-
if len(nonce) == 0:
174-
raise ValueError("Nonce must be at least 1 byte")
173+
if len(nonce) < 8 or len(nonce) > 128:
174+
raise ValueError("Nonce must be between 8 and 128 bytes")

src/cryptography/hazmat/primitives/ciphers/modes.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,14 @@ class GCM(object):
196196
_MAX_AAD_BYTES = (2 ** 64) // 8
197197

198198
def __init__(self, initialization_vector, tag=None, min_tag_length=16):
199-
# len(initialization_vector) must in [1, 2 ** 64), but it's impossible
200-
# to actually construct a bytes object that large, so we don't check
201-
# for it
199+
# OpenSSL 3.0.0 constrains GCM IVs to [64, 1024] bits inclusive
200+
# This is a sane limit anyway so we'll enforce it here.
202201
utils._check_byteslike("initialization_vector", initialization_vector)
203-
if len(initialization_vector) == 0:
204-
raise ValueError("initialization_vector must be at least 1 byte")
202+
if len(initialization_vector) < 8 or len(initialization_vector) > 128:
203+
raise ValueError(
204+
"initialization_vector must be between 8 and 128 bytes (64 "
205+
"and 1024 bits)."
206+
)
205207
self._initialization_vector = initialization_vector
206208
if tag is not None:
207209
utils._check_bytes("tag", tag)

tests/hazmat/primitives/test_aead.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,9 @@ def test_data_too_large(self):
380380
def test_vectors(self, backend, vector):
381381
nonce = binascii.unhexlify(vector["iv"])
382382

383+
if len(nonce) < 8:
384+
pytest.skip("GCM does not support less than 64-bit IVs")
385+
383386
if backend._fips_enabled and len(nonce) != 12:
384387
# Red Hat disables non-96-bit IV support as part of its FIPS
385388
# patches.
@@ -418,11 +421,17 @@ def test_params_not_bytes(self, nonce, data, associated_data, backend):
418421
with pytest.raises(TypeError):
419422
aesgcm.decrypt(nonce, data, associated_data)
420423

421-
def test_invalid_nonce_length(self, backend):
424+
@pytest.mark.parametrize("length", [7, 129])
425+
def test_invalid_nonce_length(self, length, backend):
426+
if backend._fips_enabled:
427+
# Red Hat disables non-96-bit IV support as part of its FIPS
428+
# patches.
429+
pytest.skip("Non-96-bit IVs unsupported in FIPS mode.")
430+
422431
key = AESGCM.generate_key(128)
423432
aesgcm = AESGCM(key)
424433
with pytest.raises(ValueError):
425-
aesgcm.encrypt(b"", b"hi", None)
434+
aesgcm.encrypt(b"\x00" * length, b"hi", None)
426435

427436
def test_bad_key(self, backend):
428437
with pytest.raises(TypeError):

tests/hazmat/primitives/test_aes_gcm.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,25 @@ def test_buffer_protocol(self, backend):
195195
dec.authenticate_additional_data(bytearray(b"foo"))
196196
pt = dec.update(ct) + dec.finalize()
197197
assert pt == data
198+
199+
@pytest.mark.parametrize("size", [8, 128])
200+
def test_gcm_min_max_iv(self, size, backend):
201+
if backend._fips_enabled:
202+
# Red Hat disables non-96-bit IV support as part of its FIPS
203+
# patches.
204+
pytest.skip("Non-96-bit IVs unsupported in FIPS mode.")
205+
206+
key = os.urandom(16)
207+
iv = b"\x00" * size
208+
209+
payload = b"data"
210+
encryptor = base.Cipher(algorithms.AES(key), modes.GCM(iv)).encryptor()
211+
ct = encryptor.update(payload)
212+
encryptor.finalize()
213+
tag = encryptor.tag
214+
215+
decryptor = base.Cipher(algorithms.AES(key), modes.GCM(iv)).decryptor()
216+
pt = decryptor.update(ct)
217+
218+
decryptor.finalize_with_tag(tag)
219+
assert pt == payload

tests/hazmat/primitives/test_ciphers.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ def test_xts_wrong_key_size(self, backend):
7272
ciphers.Cipher(AES(b"0" * 16), modes.XTS(b"0" * 16), backend)
7373

7474

75+
class TestGCM(object):
76+
@pytest.mark.parametrize("size", [7, 129])
77+
def test_gcm_min_max(self, size):
78+
with pytest.raises(ValueError):
79+
modes.GCM(b"0" * size)
80+
81+
7582
class TestCamellia(object):
7683
@pytest.mark.parametrize(
7784
("key", "keysize"),

tests/hazmat/primitives/utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ def test_aead(self, backend, params):
8686

8787

8888
def aead_test(backend, cipher_factory, mode_factory, params):
89+
if mode_factory is GCM and len(params["iv"]) < 16:
90+
# 16 because this is hex encoded data
91+
pytest.skip("Less than 64-bit IVs are no longer supported")
92+
8993
if (
9094
mode_factory is GCM
9195
and backend._fips_enabled

tests/wycheproof/test_aes.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ def test_aes_gcm(backend, wycheproof):
5454
msg = binascii.unhexlify(wycheproof.testcase["msg"])
5555
ct = binascii.unhexlify(wycheproof.testcase["ct"])
5656
tag = binascii.unhexlify(wycheproof.testcase["tag"])
57+
if len(iv) < 8 or len(iv) > 128:
58+
pytest.skip(
59+
"Less than 64-bit IVs (and greater than 1024-bit) are no longer "
60+
"supported"
61+
)
5762
if backend._fips_enabled and len(iv) != 12:
5863
# Red Hat disables non-96-bit IV support as part of its FIPS
5964
# patches.
@@ -73,9 +78,6 @@ def test_aes_gcm(backend, wycheproof):
7378
dec.authenticate_additional_data(aad)
7479
computed_msg = dec.update(ct) + dec.finalize()
7580
assert computed_msg == msg
76-
elif len(iv) == 0:
77-
with pytest.raises(ValueError):
78-
Cipher(algorithms.AES(key), modes.GCM(iv), backend)
7981
else:
8082
dec = Cipher(
8183
algorithms.AES(key),
@@ -97,6 +99,12 @@ def test_aes_gcm_aead_api(backend, wycheproof):
9799
msg = binascii.unhexlify(wycheproof.testcase["msg"])
98100
ct = binascii.unhexlify(wycheproof.testcase["ct"])
99101
tag = binascii.unhexlify(wycheproof.testcase["tag"])
102+
if len(iv) < 8 or len(iv) > 128:
103+
pytest.skip(
104+
"Less than 64-bit IVs (and greater than 1024-bit) are no longer "
105+
"supported"
106+
)
107+
100108
if backend._fips_enabled and len(iv) != 12:
101109
# Red Hat disables non-96-bit IV support as part of its FIPS
102110
# patches.
@@ -107,9 +115,6 @@ def test_aes_gcm_aead_api(backend, wycheproof):
107115
assert computed_ct == ct + tag
108116
computed_msg = aesgcm.decrypt(iv, ct + tag, aad)
109117
assert computed_msg == msg
110-
elif len(iv) == 0:
111-
with pytest.raises(ValueError):
112-
aesgcm.encrypt(iv, msg, aad)
113118
else:
114119
with pytest.raises(InvalidTag):
115120
aesgcm.decrypt(iv, ct + tag, aad)

0 commit comments

Comments
 (0)