Repository: pyca/cryptography

Issue:
dsa_private_key.pem vector has p and q whose lengths we don't normally allow
We currently enforce that `p` and `q` have lengths which are one of:
- `(1024, 160)`
- `(2048, 256)`
- `(3072, 256)`

However, this vector has `(p, q)` with lengths of `(2048, 160)`. Do we need to be less restrictive, use a different vector?

This was discovered in the process of writing a pure python PEM loader.



Discussion:
I think the pairs we chose were based on FIPS?

Although FIPS actually specifies 4 pairs. (1024,160), (2048,224), (2048,256), and (3072,256). I'm not sure why we are missing the 224-bit one.

@skeuomorf Do you remember? :)

I think we chose our pairs according to FIPS then adjusted it to match OpenSSL's capabilities? Perhaps it was because of this? http://git.openssl.org/gitweb/?p=openssl.git;a=blob;f=crypto/dsa/dsa_gen.c;h=9e3e57a828599e24c4d088319672c9575292bb41;hb=HEAD#l103

So, where did this vector come from!

On Wed, Oct 8, 2014 at 6:21 PM, Mohammed Attia notifications@github.com
wrote:

> I think we chose our pairs according to FIPS then adjusted it to match
> OpenSSL's capabilities? Perhaps it was because of this?
> http://git.openssl.org/gitweb/?p=openssl.git;a=blob;f=crypto/dsa/dsa_gen.c;h=9e3e57a828599e24c4d088319672c9575292bb41;hb=HEAD#l103
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/pyca/cryptography/issues/1392#issuecomment-58451907.

## 

"I disapprove of what you say, but I will defend to the death your right to
say it." -- Evelyn Beatrice Hall (summarizing Voltaire)
"The people's good is the highest law." -- Cicero
GPG Key fingerprint: 125F 5C67 DFE9 4084

It was created by @michael-hart using OpenSSL and merged in #1341 

> 1. dsa_private_key.pem - Contains a DSA 2048 bit key generated using OpenSSL from the parameters in dsaparam.pem, protected by the secret "123456" with DES3 encryption.

So to sort of sum this up: I think we should relax the constrains so that we'll be able to load real keys that exist. Is that OK with everyone? What constraints _should_ we use?

The product of (1024, 2048, 3072, 4096) and (128, 160, 224, 256, 384, 512)?

So really just validate each of them individuall?

On Thu, Oct 9, 2014 at 7:38 AM, Alex Stapleton notifications@github.com
wrote:

> The product of (1024, 2048, 3072, 4096) and (128, 160, 224, 256, 384, 512)?
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/pyca/cryptography/issues/1392#issuecomment-58518973.

## 

"I disapprove of what you say, but I will defend to the death your right to
say it." -- Evelyn Beatrice Hall (summarizing Voltaire)
"The people's good is the highest law." -- Cicero
GPG Key fingerprint: 125F 5C67 DFE9 4084

Shouldn't we display warnings when loading insecure keys?

On Thursday, October 9, 2014, Alex Gaynor notifications@github.com wrote:

> So really just validate each of them individuall?
> 
> On Thu, Oct 9, 2014 at 7:38 AM, Alex Stapleton <notifications@github.com
> <javascript:_e(%7B%7D,'cvml','notifications@github.com');>>
> wrote:
> 
> > The product of (1024, 2048, 3072, 4096) and (128, 160, 224, 256, 384,
> > 512)?
> > 
> > —
> > Reply to this email directly or view it on GitHub
> > https://github.com/pyca/cryptography/issues/1392#issuecomment-58518973.
> 
> ## 
> 
> "I disapprove of what you say, but I will defend to the death your right
> to
> say it." -- Evelyn Beatrice Hall (summarizing Voltaire)
> "The people's good is the highest law." -- Cicero
> GPG Key fingerprint: 125F 5C67 DFE9 4084
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/pyca/cryptography/issues/1392#issuecomment-58519112.

So a) we don't do that for any other key types, b) I think value-dependent
warnings are a bit of anti-pattern (as opposed to type-dependent warnings);
data dependent warnings tend to only be hit in production, and very few
people look for python warnings there.

On Thu, Oct 9, 2014 at 7:41 AM, Mohammed Attia notifications@github.com
wrote:

> Shouldn't we display warnings when loading insecure keys?
> 
> On Thursday, October 9, 2014, Alex Gaynor notifications@github.com
> wrote:
> 
> > So really just validate each of them individuall?
> > 
> > On Thu, Oct 9, 2014 at 7:38 AM, Alex Stapleton <notifications@github.com
> > <javascript:_e(%7B%7D,'cvml','notifications@github.com');>>
> > wrote:
> > 
> > > The product of (1024, 2048, 3072, 4096) and (128, 160, 224, 256, 384,
> > > 512)?
> > > 
> > > —
> > > Reply to this email directly or view it on GitHub
> > > <
> > > https://github.com/pyca/cryptography/issues/1392#issuecomment-58518973>.
> > 
> > ## 
> > 
> > "I disapprove of what you say, but I will defend to the death your right
> > to
> > say it." -- Evelyn Beatrice Hall (summarizing Voltaire)
> > "The people's good is the highest law." -- Cicero
> > GPG Key fingerprint: 125F 5C67 DFE9 4084
> > 
> > —
> > Reply to this email directly or view it on GitHub
> > https://github.com/pyca/cryptography/issues/1392#issuecomment-58519112.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/pyca/cryptography/issues/1392#issuecomment-58519479.

## 

"I disapprove of what you say, but I will defend to the death your right to
say it." -- Evelyn Beatrice Hall (summarizing Voltaire)
"The people's good is the highest law." -- Cicero
GPG Key fingerprint: 125F 5C67 DFE9 4084

Makes sense.

If OpenSSL is willing to generate (2048, 160) then I'd agree we should just validate each individually. DSA is terrible.


Resolving patch:
diff --git a/cryptography/hazmat/primitives/asymmetric/dsa.py b/cryptography/hazmat/primitives/asymmetric/dsa.py
--- a/cryptography/hazmat/primitives/asymmetric/dsa.py
+++ b/cryptography/hazmat/primitives/asymmetric/dsa.py
@@ -27,17 +27,10 @@ def generate_private_key(key_size, backend):
 
 
 def _check_dsa_parameters(parameters):
-    if (utils.bit_length(parameters.p),
-        utils.bit_length(parameters.q)) not in (
-            (1024, 160),
-            (2048, 256),
-            (3072, 256)):
-        raise ValueError(
-            "p and q's bit-lengths must be one of these pairs (1024, 160), "
-            "(2048, 256), or (3072, 256). Not ({0:d}, {1:d})".format(
-                utils.bit_length(parameters.p), utils.bit_length(parameters.q)
-            )
-        )
+    if utils.bit_length(parameters.p) not in [1024, 2048, 3072]:
+        raise ValueError("p must be exactly 1024, 2048, or 3072 bits long")
+    if utils.bit_length(parameters.q) not in [160, 256]:
+        raise ValueError("q must be exactly 160 or 256 bits long")
 
     if not (1 < parameters.g < parameters.p):
         raise ValueError("g, p don't satisfy 1 < g < p.")


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
@@ -144,30 +144,6 @@ def test_invalid_parameters_values(self, backend):
                 g=DSA_KEY_3072.public_numbers.parameter_numbers.g,
             ).parameters(backend)
 
-        # Test a p, q pair of (1024, 256) bit lengths
-        with pytest.raises(ValueError):
-            dsa.DSAParameterNumbers(
-                p=DSA_KEY_1024.public_numbers.parameter_numbers.p,
-                q=DSA_KEY_2048.public_numbers.parameter_numbers.q,
-                g=DSA_KEY_1024.public_numbers.parameter_numbers.g,
-            ).parameters(backend)
-
-        # Test a p, q pair of (2048, 160) bit lengths
-        with pytest.raises(ValueError):
-            dsa.DSAParameterNumbers(
-                p=DSA_KEY_2048.public_numbers.parameter_numbers.p,
-                q=DSA_KEY_1024.public_numbers.parameter_numbers.q,
-                g=DSA_KEY_2048.public_numbers.parameter_numbers.g
-            ).parameters(backend)
-
-        # Test a p, q pair of (3072, 160) bit lengths
-        with pytest.raises(ValueError):
-            dsa.DSAParameterNumbers(
-                p=DSA_KEY_3072.public_numbers.parameter_numbers.p,
-                q=DSA_KEY_1024.public_numbers.parameter_numbers.q,
-                g=DSA_KEY_3072.public_numbers.parameter_numbers.g,
-            ).parameters(backend)
-
         # Test a g < 1
         with pytest.raises(ValueError):
             dsa.DSAParameterNumbers(
@@ -291,48 +267,6 @@ def test_invalid_dsa_private_key_arguments(self, backend):
                 x=DSA_KEY_3072.x,
             ).private_key(backend)
 
-        # Test a p, q pair of (1024, 256) bit lengths
-        with pytest.raises(ValueError):
-            dsa.DSAPrivateNumbers(
-                public_numbers=dsa.DSAPublicNumbers(
-                    parameter_numbers=dsa.DSAParameterNumbers(
-                        p=DSA_KEY_1024.public_numbers.parameter_numbers.p,
-                        q=DSA_KEY_2048.public_numbers.parameter_numbers.q,
-                        g=DSA_KEY_1024.public_numbers.parameter_numbers.g,
-                    ),
-                    y=DSA_KEY_1024.public_numbers.y
-                ),
-                x=DSA_KEY_1024.x,
-            ).private_key(backend)
-
-        # Test a p, q pair of (2048, 160) bit lengths
-        with pytest.raises(ValueError):
-            dsa.DSAPrivateNumbers(
-                public_numbers=dsa.DSAPublicNumbers(
-                    parameter_numbers=dsa.DSAParameterNumbers(
-                        p=DSA_KEY_2048.public_numbers.parameter_numbers.p,
-                        q=DSA_KEY_1024.public_numbers.parameter_numbers.q,
-                        g=DSA_KEY_2048.public_numbers.parameter_numbers.g,
-                    ),
-                    y=DSA_KEY_2048.public_numbers.y
-                ),
-                x=DSA_KEY_2048.x,
-            ).private_key(backend)
-
-        # Test a p, q pair of (3072, 160) bit lengths
-        with pytest.raises(ValueError):
-            dsa.DSAPrivateNumbers(
-                public_numbers=dsa.DSAPublicNumbers(
-                    parameter_numbers=dsa.DSAParameterNumbers(
-                        p=DSA_KEY_3072.public_numbers.parameter_numbers.p,
-                        q=DSA_KEY_1024.public_numbers.parameter_numbers.q,
-                        g=DSA_KEY_3072.public_numbers.parameter_numbers.g,
-                    ),
-                    y=DSA_KEY_3072.public_numbers.y
-                ),
-                x=DSA_KEY_3072.x,
-            ).private_key(backend)
-
         # Test a g < 1
         with pytest.raises(ValueError):
             dsa.DSAPrivateNumbers(
@@ -551,39 +485,6 @@ def test_invalid_dsa_public_key_arguments(self, backend):
                 y=DSA_KEY_3072.public_numbers.y
             ).public_key(backend)
 
-        # Test a p, q pair of (1024, 256) bit lengths
-        with pytest.raises(ValueError):
-            dsa.DSAPublicNumbers(
-                parameter_numbers=dsa.DSAParameterNumbers(
-                    p=DSA_KEY_1024.public_numbers.parameter_numbers.p,
-                    q=DSA_KEY_2048.public_numbers.parameter_numbers.q,
-                    g=DSA_KEY_1024.public_numbers.parameter_numbers.g,
-                ),
-                y=DSA_KEY_1024.public_numbers.y
-            ).public_key(backend)
-
-        # Test a p, q pair of (2048, 160) bit lengths
-        with pytest.raises(ValueError):
-            dsa.DSAPublicNumbers(
-                parameter_numbers=dsa.DSAParameterNumbers(
-                    p=DSA_KEY_2048.public_numbers.parameter_numbers.p,
-                    q=DSA_KEY_1024.public_numbers.parameter_numbers.q,
-                    g=DSA_KEY_2048.public_numbers.parameter_numbers.g,
-                ),
-                y=DSA_KEY_2048.public_numbers.y
-            ).public_key(backend)
-
-        # Test a p, q pair of (3072, 160) bit lengths
-        with pytest.raises(ValueError):
-            dsa.DSAPublicNumbers(
-                parameter_numbers=dsa.DSAParameterNumbers(
-                    p=DSA_KEY_3072.public_numbers.parameter_numbers.p,
-                    q=DSA_KEY_1024.public_numbers.parameter_numbers.q,
-                    g=DSA_KEY_3072.public_numbers.parameter_numbers.g,
-                ),
-                y=DSA_KEY_3072.public_numbers.y
-            ).public_key(backend)
-
         # Test a g < 1
         with pytest.raises(ValueError):
             dsa.DSAPublicNumbers(