Merge pull request #335 from exarkun/pyopenssl-test_crypto-with-optionals

Pyopenssl test crypto with optionals
diff --git a/cryptography/exceptions.py b/cryptography/exceptions.py
index e9d8819..44363c2 100644
--- a/cryptography/exceptions.py
+++ b/cryptography/exceptions.py
@@ -30,3 +30,7 @@
 
 class InvalidTag(Exception):
     pass
+
+
+class InvalidSignature(Exception):
+    pass
diff --git a/cryptography/hazmat/primitives/hmac.py b/cryptography/hazmat/primitives/hmac.py
index 618bccc..76d658a 100644
--- a/cryptography/hazmat/primitives/hmac.py
+++ b/cryptography/hazmat/primitives/hmac.py
@@ -16,8 +16,8 @@
 import six
 
 from cryptography import utils
-from cryptography.exceptions import AlreadyFinalized
-from cryptography.hazmat.primitives import interfaces
+from cryptography.exceptions import AlreadyFinalized, InvalidSignature
+from cryptography.hazmat.primitives import constant_time, interfaces
 
 
 @utils.register_interface(interfaces.HashContext)
@@ -57,3 +57,10 @@
         digest = self._ctx.finalize()
         self._ctx = None
         return digest
+
+    def verify(self, signature):
+        if isinstance(signature, six.text_type):
+            raise TypeError("Unicode-objects must be encoded before verifying")
+        digest = self.finalize()
+        if not constant_time.bytes_eq(digest, signature):
+            raise InvalidSignature("Signature did not match digest.")
diff --git a/docs/exceptions.rst b/docs/exceptions.rst
index 087066b..1fbd326 100644
--- a/docs/exceptions.rst
+++ b/docs/exceptions.rst
@@ -8,6 +8,12 @@
     This is raised when a context is used after being finalized.
 
 
+.. class:: InvalidSignature
+
+    This is raised when the verify method of a hash context does not
+    compare equal.
+
+
 .. class:: NotYetFinalized
 
     This is raised when the AEAD tag property is accessed on a context
diff --git a/docs/hazmat/primitives/hmac.rst b/docs/hazmat/primitives/hmac.rst
index 0547b7d..b8f94fd 100644
--- a/docs/hazmat/primitives/hmac.rst
+++ b/docs/hazmat/primitives/hmac.rst
@@ -71,3 +71,11 @@
 
         :return bytes: The message digest as bytes.
         :raises cryptography.exceptions.AlreadyFinalized:
+
+    .. method:: verify(signature)
+
+        Finalize the current context and securely compare digest to ``signature``.
+
+        :param bytes signature: The bytes of the HMAC signature recieved.
+        :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`
+        :raises cryptography.exceptions.InvalidSignature: If signature does not match digest
diff --git a/tests/hazmat/primitives/test_hmac.py b/tests/hazmat/primitives/test_hmac.py
index 6d8cc27..7acb78b 100644
--- a/tests/hazmat/primitives/test_hmac.py
+++ b/tests/hazmat/primitives/test_hmac.py
@@ -20,7 +20,9 @@
 import six
 
 from cryptography import utils
-from cryptography.exceptions import AlreadyFinalized, UnsupportedAlgorithm
+from cryptography.exceptions import (
+    AlreadyFinalized, UnsupportedAlgorithm, InvalidSignature
+)
 from cryptography.hazmat.primitives import hashes, hmac, interfaces
 
 from .utils import generate_base_hmac_test
@@ -71,6 +73,29 @@
         with pytest.raises(AlreadyFinalized):
             h.finalize()
 
+    def test_verify(self, backend):
+        h = hmac.HMAC(b'', hashes.SHA1(), backend=backend)
+        digest = h.finalize()
+
+        h = hmac.HMAC(b'', hashes.SHA1(), backend=backend)
+        h.verify(digest)
+
+        with pytest.raises(AlreadyFinalized):
+            h.verify(b'')
+
+    def test_invalid_verify(self, backend):
+        h = hmac.HMAC(b'', hashes.SHA1(), backend=backend)
+        with pytest.raises(InvalidSignature):
+            h.verify(b'')
+
+        with pytest.raises(AlreadyFinalized):
+            h.verify(b'')
+
+    def test_verify_reject_unicode(self, backend):
+        h = hmac.HMAC(b'', hashes.SHA1(), backend=backend)
+        with pytest.raises(TypeError):
+            h.verify(six.u(''))
+
     def test_unsupported_hash(self, backend):
         with pytest.raises(UnsupportedAlgorithm):
             hmac.HMAC(b"key", UnsupportedDummyHash(), backend)