Merge pull request #485 from reaperhulk/common-crypto-cipher-bindings

Add cipher bindings for CommonCrypto
diff --git a/cryptography/hazmat/backends/commoncrypto/backend.py b/cryptography/hazmat/backends/commoncrypto/backend.py
index 3d98bf6..58e57ef 100644
--- a/cryptography/hazmat/backends/commoncrypto/backend.py
+++ b/cryptography/hazmat/backends/commoncrypto/backend.py
@@ -18,7 +18,7 @@
 from cryptography import utils
 from cryptography.exceptions import UnsupportedAlgorithm
 from cryptography.hazmat.backends.interfaces import (
-    HashBackend,
+    HashBackend, HMACBackend,
 )
 from cryptography.hazmat.bindings.commoncrypto.binding import Binding
 from cryptography.hazmat.primitives import interfaces
@@ -30,6 +30,7 @@
 
 
 @utils.register_interface(HashBackend)
+@utils.register_interface(HMACBackend)
 class Backend(object):
     """
     CommonCrypto API wrapper.
@@ -68,6 +69,15 @@
             ),
         }
 
+        self._supported_hmac_algorithms = {
+            "md5": self._lib.kCCHmacAlgMD5,
+            "sha1": self._lib.kCCHmacAlgSHA1,
+            "sha224": self._lib.kCCHmacAlgSHA224,
+            "sha256": self._lib.kCCHmacAlgSHA256,
+            "sha384": self._lib.kCCHmacAlgSHA384,
+            "sha512": self._lib.kCCHmacAlgSHA512,
+        }
+
     def hash_supported(self, algorithm):
         try:
             self._hash_mapping[algorithm.name]
@@ -75,9 +85,19 @@
         except KeyError:
             return False
 
+    def hmac_supported(self, algorithm):
+        try:
+            self._supported_hmac_algorithms[algorithm.name]
+            return True
+        except KeyError:
+            return False
+
     def create_hash_ctx(self, algorithm):
         return _HashContext(self, algorithm)
 
+    def create_hmac_ctx(self, key, algorithm):
+        return _HMACContext(self, key, algorithm)
+
 
 @utils.register_interface(interfaces.HashContext)
 class _HashContext(object):
@@ -122,4 +142,43 @@
         return self._backend._ffi.buffer(buf)[:]
 
 
+@utils.register_interface(interfaces.HashContext)
+class _HMACContext(object):
+    def __init__(self, backend, key, algorithm, ctx=None):
+        self.algorithm = algorithm
+        self._backend = backend
+        if ctx is None:
+            ctx = self._backend._ffi.new("CCHmacContext *")
+            try:
+                alg = self._backend._supported_hmac_algorithms[algorithm.name]
+            except KeyError:
+                raise UnsupportedAlgorithm(
+                    "{0} is not a supported HMAC hash on this backend".format(
+                        algorithm.name)
+                )
+
+            self._backend._lib.CCHmacInit(ctx, alg, key, len(key))
+
+        self._ctx = ctx
+        self._key = key
+
+    def copy(self):
+        copied_ctx = self._backend._ffi.new("CCHmacContext *")
+        # CommonCrypto has no APIs for copying HMACs, so we have to copy the
+        # underlying struct.
+        copied_ctx[0] = self._ctx[0]
+        return _HMACContext(
+            self._backend, self._key, self.algorithm, ctx=copied_ctx
+        )
+
+    def update(self, data):
+        self._backend._lib.CCHmacUpdate(self._ctx, data, len(data))
+
+    def finalize(self):
+        buf = self._backend._ffi.new("unsigned char[]",
+                                     self.algorithm.digest_size)
+        self._backend._lib.CCHmacFinal(self._ctx, buf)
+        return self._backend._ffi.buffer(buf)[:]
+
+
 backend = Backend()
diff --git a/docs/changelog.rst b/docs/changelog.rst
index b0baf91..819b426 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -6,10 +6,11 @@
 
 **In development**
 
-* Added CommonCrypto backend with hash support.
-* Added initial CommonCrypto bindings.
+* Added :doc:`/hazmat/backends/commoncrypto` with hash and HMAC support.
+* Added initial :doc:`/hazmat/bindings/commoncrypto`.
 
 0.1 - 2014-01-08
 ~~~~~~~~~~~~~~~~
 
 * Initial release.
+
diff --git a/docs/hazmat/backends/common-crypto.rst b/docs/hazmat/backends/commoncrypto.rst
similarity index 100%
rename from docs/hazmat/backends/common-crypto.rst
rename to docs/hazmat/backends/commoncrypto.rst
diff --git a/docs/hazmat/backends/index.rst b/docs/hazmat/backends/index.rst
index 22354f6..dbc0724 100644
--- a/docs/hazmat/backends/index.rst
+++ b/docs/hazmat/backends/index.rst
@@ -31,5 +31,5 @@
     :maxdepth: 1
 
     openssl
-    common-crypto
+    commoncrypto
     interfaces