Repository: pyca/cryptography

Issue:
p must be exactly 1024, 2048, or 3072 bits long
Hi, I'm getting an error while downloading files from SFTP using pysftp. The source of the error is due to the limitation of the number of bits the parameters can take on the cryptography package.

The source of the issue in the below method in **cryptography\hazmat\primitives\asymmetric\dsa.py**

`def _check_dsa_parameters(parameters):
    if parameters.p.bit_length() not in [1024, 2048, 3072, **4096**]:
        raise ValueError("p must be exactly 1024, 2048, or 3072 bits long")
    if parameters.q.bit_length() not in [160, 224, 256]:
        raise ValueError("q must be exactly 160, 224, or 256 bits long")    
    if not (1 < parameters.g < parameters.p):
        raise ValueError("g, p don't satisfy 1 < g < p.")`

https://github.com/pyca/cryptography/blob/master/src/cryptography/hazmat/primitives/asymmetric/dsa.py ... line no 131

By adding the 4096 bits in the bit length check fixed the issue. Can you please make this fix in the library.

python 3.6
cryptography 2.9.2
pip 20.0.2
setuptools 46.1.3

Used requirements.txt to install Cryptography
`cryptography==2.9.2`

Please let me know if you need any additional details.


Discussion:
These limits were originally put into place to correspond with DSA's official specs, but a 4096 bit value here is fine so we should probably go ahead and allow it.
We'll need a set of test cases before we can allow it.

On Sat, Apr 25, 2020 at 12:08 PM Paul Kehrer <notifications@github.com>
wrote:

> These limits were originally put into place to correspond with DSA's
> official specs, but a 4096 bit value here is fine so we should probably go
> ahead and allow it.
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/pyca/cryptography/issues/5227#issuecomment-619402012>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAAAGBHWMQMBUF5HDKKZU3DROMDGVANCNFSM4MQNTTLA>
> .
>


-- 
All that is necessary for evil to succeed is for good people to do nothing.

Would you guys be amenable to an environment variable override for the allowed bit lengths? I can monkey patch to allow whatever I want but it seems to me an environment variable would be tidier, and afford users a more flexibility, at their own risk of course! In my case I have 10s of 1000s of devices with 512 bit host keys, which I can't change. I know 512 isn't really acceptable, but I have to live with it.
We'd prefer users to do the monkey patch you're doing precisely because it requires the appropriate degree of knowledge to understand what they are removing.

An environment variable becomes part of the documentation of some project with bad security practices and starts getting cargo culted from config to config. 😢 

While we don't promise the stability of our internal checks, we've never changed the name of that method so monkey patching is very likely to remain functional for the foreseeable future.

We are still willing to allow larger sizes, but we'll need to add test vectors to confirm proper functionality.
Yeah, fair enough.
Thanks for the quick reply.


Resolving patch:
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -626,8 +626,10 @@ def rsa_padding_supported(self, padding):
             return False
 
     def generate_dsa_parameters(self, key_size):
-        if key_size not in (1024, 2048, 3072):
-            raise ValueError("Key size must be 1024 or 2048 or 3072 bits.")
+        if key_size not in (1024, 2048, 3072, 4096):
+            raise ValueError(
+                "Key size must be 1024, 2048, 3072, or 4096 bits."
+            )
 
         ctx = self._lib.DSA_new()
         self.openssl_assert(ctx != self._ffi.NULL)
diff --git a/src/cryptography/hazmat/primitives/asymmetric/dsa.py b/src/cryptography/hazmat/primitives/asymmetric/dsa.py
--- a/src/cryptography/hazmat/primitives/asymmetric/dsa.py
+++ b/src/cryptography/hazmat/primitives/asymmetric/dsa.py
@@ -128,8 +128,10 @@ def generate_private_key(key_size, backend):
 
 
 def _check_dsa_parameters(parameters):
-    if parameters.p.bit_length() not in [1024, 2048, 3072]:
-        raise ValueError("p must be exactly 1024, 2048, or 3072 bits long")
+    if parameters.p.bit_length() not in [1024, 2048, 3072, 4096]:
+        raise ValueError(
+            "p must be exactly 1024, 2048, 3072, or 4096 bits long"
+        )
     if parameters.q.bit_length() not in [160, 224, 256]:
         raise ValueError("q must be exactly 160, 224, or 256 bits long")
 


Test patch:
diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py
--- a/tests/hazmat/primitives/test_dsa.py
+++ b/tests/hazmat/primitives/test_dsa.py
@@ -349,6 +349,28 @@ def test_invalid_dsa_public_key_arguments(self, p, q, g, y, backend):
                 y=y
             ).public_key(backend)
 
+    def test_large_p(self, backend):
+        key = load_vectors_from_file(
+            os.path.join("asymmetric", "PEM_Serialization", "dsa_4096.pem"),
+            lambda pemfile: serialization.load_pem_private_key(
+                pemfile.read(), None, backend
+            ), mode="rb"
+        )
+        pn = key.private_numbers()
+        assert pn.public_numbers.parameter_numbers.p.bit_length() == 4096
+        # Turn it back into a key to confirm that values this large pass
+        # verification
+        dsa.DSAPrivateNumbers(
+            public_numbers=dsa.DSAPublicNumbers(
+                parameter_numbers=dsa.DSAParameterNumbers(
+                    p=pn.public_numbers.parameter_numbers.p,
+                    q=pn.public_numbers.parameter_numbers.q,
+                    g=pn.public_numbers.parameter_numbers.g,
+                ),
+                y=pn.public_numbers.y
+            ), x=pn.x
+        ).private_key(backend)
+
 
 @pytest.mark.requires_backend_interface(interface=DSABackend)
 class TestDSAVerification(object):