add support for prehashing in ECDSA sign/verify (#3267)

* add support for prehashing in ECDSA sign/verify

* move signature_algorithm check to its own function
diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst
index 27debfa..99abcc6 100644
--- a/docs/hazmat/primitives/asymmetric/ec.rst
+++ b/docs/hazmat/primitives/asymmetric/ec.rst
@@ -384,12 +384,16 @@
 .. class:: EllipticCurveSignatureAlgorithm
 
     .. versionadded:: 0.5
+    .. versionchanged:: 1.6
+        :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed`
+        can now be used as an ``algorithm``.
 
     A signature algorithm for use with elliptic curve keys.
 
     .. attribute:: algorithm
 
-        :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`
+        :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` or
+            :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed`
 
         The digest algorithm to be used with the signature scheme.
 
diff --git a/src/cryptography/hazmat/backends/openssl/ec.py b/src/cryptography/hazmat/backends/openssl/ec.py
index 0c8716f..5969f2a 100644
--- a/src/cryptography/hazmat/backends/openssl/ec.py
+++ b/src/cryptography/hazmat/backends/openssl/ec.py
@@ -8,7 +8,9 @@
 from cryptography.exceptions import (
     InvalidSignature, UnsupportedAlgorithm, _Reasons
 )
-from cryptography.hazmat.backends.openssl.utils import _truncate_digest
+from cryptography.hazmat.backends.openssl.utils import (
+    _calculate_digest_and_algorithm, _truncate_digest
+)
 from cryptography.hazmat.primitives import hashes, serialization
 from cryptography.hazmat.primitives.asymmetric import (
     AsymmetricSignatureContext, AsymmetricVerificationContext, ec
@@ -40,6 +42,13 @@
     return _truncate_digest(digest, order_bits)
 
 
+def _check_signature_algorithm(signature_algorithm):
+    if not isinstance(signature_algorithm, ec.ECDSA):
+        raise UnsupportedAlgorithm(
+            "Unsupported elliptic curve signature algorithm.",
+            _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM)
+
+
 def _ec_key_curve_sn(backend, ec_key):
     group = backend._lib.EC_KEY_get0_group(ec_key)
     backend.openssl_assert(group != backend._ffi.NULL)
@@ -159,14 +168,10 @@
     curve = utils.read_only_property("_curve")
 
     def signer(self, signature_algorithm):
-        if isinstance(signature_algorithm, ec.ECDSA):
-            return _ECDSASignatureContext(
-                self._backend, self, signature_algorithm.algorithm
-            )
-        else:
-            raise UnsupportedAlgorithm(
-                "Unsupported elliptic curve signature algorithm.",
-                _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM)
+        _check_signature_algorithm(signature_algorithm)
+        return _ECDSASignatureContext(
+            self._backend, self, signature_algorithm.algorithm
+        )
 
     def exchange(self, algorithm, peer_public_key):
         if not (
@@ -238,9 +243,14 @@
         )
 
     def sign(self, data, signature_algorithm):
-        signer = self.signer(signature_algorithm)
-        signer.update(data)
-        return signer.finalize()
+        _check_signature_algorithm(signature_algorithm)
+        data, algorithm = _calculate_digest_and_algorithm(
+            self._backend, data, signature_algorithm._algorithm
+        )
+        data = _truncate_digest_for_ecdsa(
+            self._ec_key, data, self._backend
+        )
+        return _ecdsa_sig_sign(self._backend, self, data)
 
 
 @utils.register_interface(ec.EllipticCurvePublicKeyWithSerialization)
@@ -260,14 +270,10 @@
         if not isinstance(signature, bytes):
             raise TypeError("signature must be bytes.")
 
-        if isinstance(signature_algorithm, ec.ECDSA):
-            return _ECDSAVerificationContext(
-                self._backend, self, signature, signature_algorithm.algorithm
-            )
-        else:
-            raise UnsupportedAlgorithm(
-                "Unsupported elliptic curve signature algorithm.",
-                _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM)
+        _check_signature_algorithm(signature_algorithm)
+        return _ECDSAVerificationContext(
+            self._backend, self, signature, signature_algorithm.algorithm
+        )
 
     def public_numbers(self):
         set_func, get_func, group = (
@@ -307,6 +313,11 @@
         )
 
     def verify(self, signature, data, signature_algorithm):
-        verifier = self.verifier(signature, signature_algorithm)
-        verifier.update(data)
-        verifier.verify()
+        _check_signature_algorithm(signature_algorithm)
+        data, algorithm = _calculate_digest_and_algorithm(
+            self._backend, data, signature_algorithm._algorithm
+        )
+        data = _truncate_digest_for_ecdsa(
+            self._ec_key, data, self._backend
+        )
+        return _ecdsa_sig_verify(self._backend, self, signature, data)
diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py
index 523f3f4..d2b570d 100644
--- a/tests/hazmat/primitives/test_ec.py
+++ b/tests/hazmat/primitives/test_ec.py
@@ -19,7 +19,7 @@
 from cryptography.hazmat.primitives import hashes, serialization
 from cryptography.hazmat.primitives.asymmetric import ec
 from cryptography.hazmat.primitives.asymmetric.utils import (
-    encode_dss_signature
+    Prehashed, encode_dss_signature
 )
 
 from .fixtures_ec import EC_KEY_SECP384R1
@@ -387,8 +387,20 @@
         with raises_unsupported_algorithm(
             exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM
         ):
+            key.sign(b"somedata", DummySignatureAlgorithm())
+
+        with raises_unsupported_algorithm(
+            exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM
+        ):
             key.public_key().verifier(b"", DummySignatureAlgorithm())
 
+        with raises_unsupported_algorithm(
+            exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM
+        ):
+            key.public_key().verify(
+                b"signature", b"data", DummySignatureAlgorithm()
+            )
+
         assert backend.elliptic_curve_signature_algorithm_supported(
             DummySignatureAlgorithm(),
             ec.SECP192R1()
@@ -540,6 +552,31 @@
         verifier.update(message)
         verifier.verify()
 
+    def test_sign_prehashed(self, backend):
+        _skip_curve_unsupported(backend, ec.SECP256R1())
+        message = b"one little message"
+        h = hashes.Hash(hashes.SHA1(), backend)
+        h.update(message)
+        data = h.finalize()
+        algorithm = ec.ECDSA(Prehashed(hashes.SHA1()))
+        private_key = ec.generate_private_key(ec.SECP256R1(), backend)
+        signature = private_key.sign(data, algorithm)
+        public_key = private_key.public_key()
+        verifier = public_key.verifier(signature, ec.ECDSA(hashes.SHA1()))
+        verifier.update(message)
+        verifier.verify()
+
+    def test_sign_prehashed_digest_mismatch(self, backend):
+        _skip_curve_unsupported(backend, ec.SECP256R1())
+        message = b"one little message"
+        h = hashes.Hash(hashes.SHA1(), backend)
+        h.update(message)
+        data = h.finalize()
+        algorithm = ec.ECDSA(Prehashed(hashes.SHA256()))
+        private_key = ec.generate_private_key(ec.SECP256R1(), backend)
+        with pytest.raises(ValueError):
+            private_key.sign(data, algorithm)
+
     def test_verify(self, backend):
         _skip_curve_unsupported(backend, ec.SECP256R1())
         message = b"one little message"
@@ -551,6 +588,35 @@
         public_key = private_key.public_key()
         public_key.verify(signature, message, algorithm)
 
+    def test_verify_prehashed(self, backend):
+        _skip_curve_unsupported(backend, ec.SECP256R1())
+        message = b"one little message"
+        algorithm = ec.ECDSA(hashes.SHA1())
+        private_key = ec.generate_private_key(ec.SECP256R1(), backend)
+        signer = private_key.signer(algorithm)
+        signer.update(message)
+        signature = signer.finalize()
+        h = hashes.Hash(hashes.SHA1(), backend)
+        h.update(message)
+        data = h.finalize()
+        public_key = private_key.public_key()
+        public_key.verify(
+            signature, data, ec.ECDSA(Prehashed(hashes.SHA1()))
+        )
+
+    def test_verify_prehashed_digest_mismatch(self, backend):
+        _skip_curve_unsupported(backend, ec.SECP256R1())
+        message = b"one little message"
+        private_key = ec.generate_private_key(ec.SECP256R1(), backend)
+        h = hashes.Hash(hashes.SHA1(), backend)
+        h.update(message)
+        data = h.finalize()
+        public_key = private_key.public_key()
+        with pytest.raises(ValueError):
+            public_key.verify(
+                b"\x00" * 32, data, ec.ECDSA(Prehashed(hashes.SHA256()))
+            )
+
 
 class TestECNumbersEquality(object):
     def test_public_numbers_eq(self):