summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Kehrer <paul.l.kehrer@gmail.com>2020-11-29 10:01:16 -0600
committerGitHub <noreply@github.com>2020-11-29 11:01:16 -0500
commit4645f02c25d7d336a6d922e428c72beb55fb04cb (patch)
tree8e84d507468e4a81d50a34a0afc4f6d9a641b6e5
parentfd7ed6704087f1d71781e48e6c268341429b3abc (diff)
downloadcryptography-4645f02c25d7d336a6d922e428c72beb55fb04cb.tar.gz
disallow p less than 512-bit on DH (#5592)
* disallow p less than 512-bit on DH OpenSSL 3.0.0 enforces this so we'll go ahead and enforce it everywhere that's practical for us. (Note that we do not enforce on deserializing PKCS1/PKCS8 keys in < 3.0.0, but this PR adds a test so that in the 3.0.0 support branch we can test an error path) * missing test * black * _MIN_MODULUS_SIZE is now a thing * skip on fips
-rw-r--r--CHANGELOG.rst5
-rw-r--r--docs/development/test-vectors.rst2
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py9
-rw-r--r--src/cryptography/hazmat/primitives/asymmetric/dh.py8
-rw-r--r--tests/hazmat/primitives/test_dh.py84
-rw-r--r--vectors/cryptography_vectors/asymmetric/DH/dh_key_256.pem4
6 files changed, 84 insertions, 28 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 1f1ffd995..e9ea92a84 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -20,6 +20,11 @@ Changelog
raise ``ValueError`` rather than ``UnsupportedAlgorithm`` when an
unsupported cipher is used. This change is to conform with an upcoming
OpenSSL release that will no longer distinguish between error types.
+* **BACKWARDS INCOMPATIBLE:** We no longer allow loading of finite field
+ Diffie-Hellman parameters of less than 512 bits in length. This change is to
+ conform with an upcoming OpenSSL release that no longer supports smaller
+ sizes. These keys were already wildly insecure and should not have been used
+ in any application outside of testing.
* Python 2 support is deprecated in ``cryptography``. This is the last release
that will support Python 2.
diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst
index 6146c9c13..f952337e2 100644
--- a/docs/development/test-vectors.rst
+++ b/docs/development/test-vectors.rst
@@ -184,6 +184,8 @@ Key exchange
``vectors/cryptography_vectors/asymmetric/DH/dhkey_rfc5114_2.der`` and
``vectors/cryptography_vectors/asymmetric/DH/dhpub_rfc5114_2.der`` contains
are the above parameters and keys in DER format.
+* ``vectors/cryptography_vectors/asymmetric/DH/dh_key_256.pem`` contains
+ a PEM PKCS8 encoded DH key with a 256-bit key size.
* ``vectors/cryptoraphy_vectors/asymmetric/ECDH/brainpool.txt`` contains
Brainpool vectors from :rfc:`7027`.
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index 7f4ff2a3e..45d4a1a1e 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -117,6 +117,7 @@ from cryptography.hazmat.backends.openssl.x509 import (
from cryptography.hazmat.bindings.openssl import binding
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import (
+ dh,
dsa,
ec,
ed25519,
@@ -2086,8 +2087,12 @@ class Backend(object):
return self._read_mem_bio(bio)
def generate_dh_parameters(self, generator, key_size):
- if key_size < 512:
- raise ValueError("DH key_size must be at least 512 bits")
+ if key_size < dh._MIN_MODULUS_SIZE:
+ raise ValueError(
+ "DH key_size must be at least {} bits".format(
+ dh._MIN_MODULUS_SIZE
+ )
+ )
if generator not in (2, 5):
raise ValueError("DH generator must be 2 or 5")
diff --git a/src/cryptography/hazmat/primitives/asymmetric/dh.py b/src/cryptography/hazmat/primitives/asymmetric/dh.py
index cd9fbfab4..74a311d50 100644
--- a/src/cryptography/hazmat/primitives/asymmetric/dh.py
+++ b/src/cryptography/hazmat/primitives/asymmetric/dh.py
@@ -12,6 +12,9 @@ from cryptography import utils
from cryptography.hazmat.backends import _get_backend
+_MIN_MODULUS_SIZE = 512
+
+
def generate_parameters(generator, key_size, backend=None):
backend = _get_backend(backend)
return backend.generate_dh_parameters(generator, key_size)
@@ -95,6 +98,11 @@ class DHParameterNumbers(object):
if g < 2:
raise ValueError("DH generator must be 2 or greater")
+ if p.bit_length() < _MIN_MODULUS_SIZE:
+ raise ValueError(
+ "p (modulus) must be at least {}-bit".format(_MIN_MODULUS_SIZE)
+ )
+
self._p = p
self._g = g
self._q = q
diff --git a/tests/hazmat/primitives/test_dh.py b/tests/hazmat/primitives/test_dh.py
index 63a7c642e..4c2ee1a63 100644
--- a/tests/hazmat/primitives/test_dh.py
+++ b/tests/hazmat/primitives/test_dh.py
@@ -23,6 +23,19 @@ from .fixtures_dh import FFDH3072_P
from ...doubles import DummyKeySerializationEncryption
from ...utils import load_nist_vectors, load_vectors_from_file
+# RFC 3526
+P_1536 = int(
+ "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+ "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF",
+ 16,
+)
+
def _skip_dhx_unsupported(backend, is_dhx):
if not is_dhx:
@@ -32,35 +45,39 @@ def _skip_dhx_unsupported(backend, is_dhx):
def test_dh_parameternumbers():
- params = dh.DHParameterNumbers(65537, 2)
+ params = dh.DHParameterNumbers(P_1536, 2)
- assert params.p == 65537
+ assert params.p == P_1536
assert params.g == 2
with pytest.raises(TypeError):
dh.DHParameterNumbers(None, 2)
with pytest.raises(TypeError):
- dh.DHParameterNumbers(65537, None)
+ dh.DHParameterNumbers(P_1536, None)
with pytest.raises(TypeError):
dh.DHParameterNumbers(None, None)
with pytest.raises(ValueError):
- dh.DHParameterNumbers(65537, 1)
+ dh.DHParameterNumbers(P_1536, 1)
- params = dh.DHParameterNumbers(65537, 7, 1245)
+ # p too small
+ with pytest.raises(ValueError):
+ dh.DHParameterNumbers(65537, 2)
- assert params.p == 65537
+ params = dh.DHParameterNumbers(P_1536, 7, 1245)
+
+ assert params.p == P_1536
assert params.g == 7
assert params.q == 1245
with pytest.raises(TypeError):
- dh.DHParameterNumbers(65537, 2, "hello")
+ dh.DHParameterNumbers(P_1536, 2, "hello")
def test_dh_numbers():
- params = dh.DHParameterNumbers(65537, 2)
+ params = dh.DHParameterNumbers(P_1536, 2)
public = dh.DHPublicNumbers(1, params)
@@ -86,20 +103,22 @@ def test_dh_numbers():
def test_dh_parameter_numbers_equality():
- assert dh.DHParameterNumbers(65537, 2) == dh.DHParameterNumbers(65537, 2)
- assert dh.DHParameterNumbers(65537, 7, 12345) == dh.DHParameterNumbers(
- 65537, 7, 12345
+ assert dh.DHParameterNumbers(P_1536, 2) == dh.DHParameterNumbers(P_1536, 2)
+ assert dh.DHParameterNumbers(P_1536, 7, 12345) == dh.DHParameterNumbers(
+ P_1536, 7, 12345
+ )
+ assert dh.DHParameterNumbers(P_1536 + 2, 2) != dh.DHParameterNumbers(
+ P_1536, 2
)
- assert dh.DHParameterNumbers(6, 2) != dh.DHParameterNumbers(65537, 2)
- assert dh.DHParameterNumbers(65537, 2, 123) != dh.DHParameterNumbers(
- 65537, 2, 456
+ assert dh.DHParameterNumbers(P_1536, 2, 123) != dh.DHParameterNumbers(
+ P_1536, 2, 456
)
- assert dh.DHParameterNumbers(65537, 5) != dh.DHParameterNumbers(65537, 2)
- assert dh.DHParameterNumbers(65537, 2) != object()
+ assert dh.DHParameterNumbers(P_1536, 5) != dh.DHParameterNumbers(P_1536, 2)
+ assert dh.DHParameterNumbers(P_1536, 2) != object()
def test_dh_private_numbers_equality():
- params = dh.DHParameterNumbers(65537, 2)
+ params = dh.DHParameterNumbers(P_1536, 2)
public = dh.DHPublicNumbers(1, params)
private = dh.DHPrivateNumbers(2, public)
@@ -107,18 +126,18 @@ def test_dh_private_numbers_equality():
assert private != dh.DHPrivateNumbers(0, public)
assert private != dh.DHPrivateNumbers(2, dh.DHPublicNumbers(0, params))
assert private != dh.DHPrivateNumbers(
- 2, dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 5))
+ 2, dh.DHPublicNumbers(1, dh.DHParameterNumbers(P_1536, 5))
)
assert private != object()
def test_dh_public_numbers_equality():
- params = dh.DHParameterNumbers(65537, 2)
+ params = dh.DHParameterNumbers(P_1536, 2)
public = dh.DHPublicNumbers(1, params)
assert public == dh.DHPublicNumbers(1, params)
assert public != dh.DHPublicNumbers(0, params)
- assert public != dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 5))
+ assert public != dh.DHPublicNumbers(1, dh.DHParameterNumbers(P_1536, 5))
assert public != object()
@@ -209,12 +228,11 @@ class TestDH(object):
)
def test_numbers_unsupported_parameters(self, backend):
- # p is set to 21 because when calling private_key we want it to
- # fail the DH_check call OpenSSL does. Originally this was 23, but
- # we are allowing p % 24 to == 23 with this PR (see #3768 for more)
- # By setting it to 21 it fails later in DH_check in a primality check
- # which triggers the code path we want to test
- params = dh.DHParameterNumbers(21, 2)
+ # p is set to P_1536 + 1 because when calling private_key we want it to
+ # fail the DH_check call OpenSSL does, but we specifically want it to
+ # fail such that we don't get a DH_NOT_SUITABLE_GENERATOR. We can cause
+ # this by making sure p is not prime.
+ params = dh.DHParameterNumbers(P_1536 + 1, 2)
public = dh.DHPublicNumbers(1, params)
private = dh.DHPrivateNumbers(2, public)
@@ -363,6 +381,16 @@ class TestDH(object):
assert symkey1 != symkey2
+ @pytest.mark.skip_fips(reason="key_size too small for FIPS")
+ def test_load_256bit_key_from_pkcs8(self, backend):
+ data = load_vectors_from_file(
+ os.path.join("asymmetric", "DH", "dh_key_256.pem"),
+ lambda pemfile: pemfile.read(),
+ mode="rb",
+ )
+ key = serialization.load_pem_private_key(data, None, backend)
+ assert key.key_size == 256
+
@pytest.mark.parametrize(
"vector",
load_vectors_from_file(
@@ -375,6 +403,10 @@ class TestDH(object):
and int(vector["p"]) < backend._fips_dh_min_modulus
):
pytest.skip("modulus too small for FIPS mode")
+
+ if int(vector["p"]).bit_length() < 512:
+ pytest.skip("DH keys less than 512 bits are unsupported")
+
parameters = dh.DHParameterNumbers(int(vector["p"]), int(vector["g"]))
public = dh.DHPublicNumbers(int(vector["y"]), parameters)
private = dh.DHPrivateNumbers(int(vector["x"]), public)
diff --git a/vectors/cryptography_vectors/asymmetric/DH/dh_key_256.pem b/vectors/cryptography_vectors/asymmetric/DH/dh_key_256.pem
new file mode 100644
index 000000000..1c01dd3ea
--- /dev/null
+++ b/vectors/cryptography_vectors/asymmetric/DH/dh_key_256.pem
@@ -0,0 +1,4 @@
+-----BEGIN PRIVATE KEY-----
+MFwCAQAwMwYJKoZIhvcNAQMBMCYCIQCBPg6BS+5nbb09nSjtc9NnNdIf9kVyNvaN
+PWFFVgwPqwIBAgQiAiBmJ3qBbu72ZnUxnCrr8ujWFU7jWTcOjhsZSqobmiD6vA==
+-----END PRIVATE KEY-----