Blame SOURCES/0003-switch-to-using-EVP_PKEY_derive-instead-of-DH_comput.patch

37a582
From 29cf9b8d63ef3437ba11aa29502af8773faa17a7 Mon Sep 17 00:00:00 2001
37a582
From: Paul Kehrer <paul.l.kehrer@gmail.com>
37a582
Date: Wed, 14 Apr 2021 13:15:57 -0500
37a582
Subject: [PATCH 3/3] switch to using EVP_PKEY_derive instead of DH_compute_key
37a582
 in DH (#5972)
37a582
37a582
* switch to using EVP_PKEY_derive instead of DH_compute_key in DH
37a582
37a582
Where checks are occurring is changing in OpenSSL 3.0 and this makes it
37a582
easier to be consistent (and is the API we should be using anyway). The
37a582
tests change because EVP_PKEY_derive now verifies that we have shared
37a582
parameters, which the test previously only verified by asserting that
37a582
the derived keys didn't match
37a582
37a582
* review feedback
37a582
37a582
* type ignores required for typeerror tests. some day i will remember this
37a582
---
37a582
 src/_cffi_src/openssl/dh.py                   |  1 -
37a582
 .../hazmat/backends/openssl/dh.py             | 57 ++++++++++++-------
37a582
 tests/hazmat/primitives/test_dh.py            | 19 ++++---
37a582
 3 files changed, 45 insertions(+), 32 deletions(-)
37a582
37a582
diff --git a/src/_cffi_src/openssl/dh.py b/src/_cffi_src/openssl/dh.py
37a582
index 979dafa9..50989e45 100644
37a582
--- a/src/_cffi_src/openssl/dh.py
37a582
+++ b/src/_cffi_src/openssl/dh.py
37a582
@@ -18,7 +18,6 @@ DH *DH_new(void);
37a582
 void DH_free(DH *);
37a582
 int DH_size(const DH *);
37a582
 int DH_generate_key(DH *);
37a582
-int DH_compute_key(unsigned char *, const BIGNUM *, DH *);
37a582
 DH *DHparams_dup(DH *);
37a582
 
37a582
 /* added in 1.1.0 when the DH struct was opaqued */
37a582
diff --git a/src/cryptography/hazmat/backends/openssl/dh.py b/src/cryptography/hazmat/backends/openssl/dh.py
37a582
index 65ddaeec..b928f024 100644
37a582
--- a/src/cryptography/hazmat/backends/openssl/dh.py
37a582
+++ b/src/cryptography/hazmat/backends/openssl/dh.py
37a582
@@ -127,35 +127,48 @@ class _DHPrivateKey(dh.DHPrivateKey):
37a582
         )
37a582
 
37a582
     def exchange(self, peer_public_key: dh.DHPublicKey) -> bytes:
37a582
-        buf = self._backend._ffi.new("unsigned char[]", self._key_size_bytes)
37a582
-        pub_key = self._backend._ffi.new("BIGNUM **")
37a582
-        self._backend._lib.DH_get0_key(
37a582
-            peer_public_key._dh_cdata,  # type: ignore[attr-defined]
37a582
-            pub_key,
37a582
-            self._backend._ffi.NULL,
37a582
+        if not isinstance(peer_public_key, _DHPublicKey):
37a582
+            raise TypeError("peer_public_key must be a DHPublicKey")
37a582
+
37a582
+        ctx = self._backend._lib.EVP_PKEY_CTX_new(
37a582
+            self._evp_pkey, self._backend._ffi.NULL
37a582
         )
37a582
-        self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL)
37a582
-        res = self._backend._lib.DH_compute_key(
37a582
-            buf, pub_key[0], self._dh_cdata
37a582
+        self._backend.openssl_assert(ctx != self._backend._ffi.NULL)
37a582
+        ctx = self._backend._ffi.gc(ctx, self._backend._lib.EVP_PKEY_CTX_free)
37a582
+        res = self._backend._lib.EVP_PKEY_derive_init(ctx)
37a582
+        self._backend.openssl_assert(res == 1)
37a582
+        res = self._backend._lib.EVP_PKEY_derive_set_peer(
37a582
+            ctx, peer_public_key._evp_pkey
37a582
+        )
37a582
+        # Invalid kex errors here in OpenSSL 3.0 because checks were moved
37a582
+        # to EVP_PKEY_derive_set_peer
37a582
+        self._exchange_assert(res == 1)
37a582
+        keylen = self._backend._ffi.new("size_t *")
37a582
+        res = self._backend._lib.EVP_PKEY_derive(
37a582
+            ctx, self._backend._ffi.NULL, keylen
37a582
         )
37a582
+        # Invalid kex errors here in OpenSSL < 3
37a582
+        self._exchange_assert(res == 1)
37a582
+        self._backend.openssl_assert(keylen[0] > 0)
37a582
+        buf = self._backend._ffi.new("unsigned char[]", keylen[0])
37a582
+        res = self._backend._lib.EVP_PKEY_derive(ctx, buf, keylen)
37a582
+        self._backend.openssl_assert(res == 1)
37a582
 
37a582
-        if res == -1:
37a582
+        key = self._backend._ffi.buffer(buf, keylen[0])[:]
37a582
+        pad = self._key_size_bytes - len(key)
37a582
+
37a582
+        if pad > 0:
37a582
+            key = (b"\x00" * pad) + key
37a582
+
37a582
+        return key
37a582
+
37a582
+    def _exchange_assert(self, ok):
37a582
+        if not ok:
37a582
             errors_with_text = self._backend._consume_errors_with_text()
37a582
             raise ValueError(
37a582
-                "Error computing shared key. Public key is likely invalid "
37a582
-                "for this exchange.",
37a582
+                "Error computing shared key.",
37a582
                 errors_with_text,
37a582
             )
37a582
-        else:
37a582
-            self._backend.openssl_assert(res >= 1)
37a582
-
37a582
-            key = self._backend._ffi.buffer(buf)[:res]
37a582
-            pad = self._key_size_bytes - len(key)
37a582
-
37a582
-            if pad > 0:
37a582
-                key = (b"\x00" * pad) + key
37a582
-
37a582
-            return key
37a582
 
37a582
     def public_key(self) -> dh.DHPublicKey:
37a582
         dh_cdata = _dh_params_dup(self._dh_cdata, self._backend)
37a582
diff --git a/tests/hazmat/primitives/test_dh.py b/tests/hazmat/primitives/test_dh.py
37a582
index bb29919f..2914f7e7 100644
37a582
--- a/tests/hazmat/primitives/test_dh.py
37a582
+++ b/tests/hazmat/primitives/test_dh.py
37a582
@@ -296,6 +296,12 @@ class TestDH(object):
37a582
         assert isinstance(key.private_numbers(), dh.DHPrivateNumbers)
37a582
         assert isinstance(key.parameters(), dh.DHParameters)
37a582
 
37a582
+    def test_exchange_wrong_type(self, backend):
37a582
+        parameters = FFDH3072_P.parameters(backend)
37a582
+        key1 = parameters.generate_private_key()
37a582
+        with pytest.raises(TypeError):
37a582
+            key1.exchange(b"invalidtype")  # type: ignore[arg-type]
37a582
+
37a582
     def test_exchange(self, backend):
37a582
         parameters = FFDH3072_P.parameters(backend)
37a582
         assert isinstance(parameters, dh.DHParameters)
37a582
@@ -386,16 +392,11 @@ class TestDH(object):
37a582
         key2 = private2.private_key(backend)
37a582
         pub_key2 = key2.public_key()
37a582
 
37a582
-        if pub_key2.public_numbers().y >= parameters1.p:
37a582
-            with pytest.raises(ValueError):
37a582
-                key1.exchange(pub_key2)
37a582
-        else:
37a582
-            symkey1 = key1.exchange(pub_key2)
37a582
-            assert symkey1
37a582
-
37a582
-            symkey2 = key2.exchange(pub_key1)
37a582
+        with pytest.raises(ValueError):
37a582
+            key1.exchange(pub_key2)
37a582
 
37a582
-            assert symkey1 != symkey2
37a582
+        with pytest.raises(ValueError):
37a582
+            key2.exchange(pub_key1)
37a582
 
37a582
     @pytest.mark.skip_fips(reason="key_size too small for FIPS")
37a582
     @pytest.mark.supported(
37a582
-- 
37a582
2.30.2
37a582