Merge pull request #950 from Ayrx/cmac-multibackend

CMAC multibackend
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 9e89e56..106e0ab 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -11,6 +11,8 @@
   to :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`. It will be
   removed from ``MGF1`` in two releases per our :doc:`/api-stability` policy.
 
+* Added :class:`~cryptography.hazmat.primitives.cmac.CMAC`.
+
 0.3 - 2014-03-27
 ~~~~~~~~~~~~~~~~
 
diff --git a/cryptography/hazmat/backends/multibackend.py b/cryptography/hazmat/backends/multibackend.py
index 86cded8..981a60b 100644
--- a/cryptography/hazmat/backends/multibackend.py
+++ b/cryptography/hazmat/backends/multibackend.py
@@ -16,11 +16,12 @@
 from cryptography import utils
 from cryptography.exceptions import UnsupportedAlgorithm, _Reasons
 from cryptography.hazmat.backends.interfaces import (
-    CipherBackend, DSABackend, HMACBackend, HashBackend, PBKDF2HMACBackend,
-    RSABackend
+    CMACBackend, CipherBackend, DSABackend, HMACBackend, HashBackend,
+    PBKDF2HMACBackend, RSABackend
 )
 
 
+@utils.register_interface(CMACBackend)
 @utils.register_interface(CipherBackend)
 @utils.register_interface(HashBackend)
 @utils.register_interface(HMACBackend)
@@ -156,3 +157,18 @@
             return b.generate_dsa_private_key(parameters)
         raise UnsupportedAlgorithm("DSA is not supported by the backend",
                                    _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM)
+
+    def cmac_algorithm_supported(self, algorithm):
+        return any(
+            b.cmac_algorithm_supported(algorithm)
+            for b in self._filtered_backends(CMACBackend)
+        )
+
+    def create_cmac_ctx(self, algorithm):
+        for b in self._filtered_backends(CMACBackend):
+            try:
+                return b.create_cmac_ctx(algorithm)
+            except UnsupportedAlgorithm:
+                pass
+        raise UnsupportedAlgorithm("This backend does not support CMAC",
+                                   _Reasons.UNSUPPORTED_CIPHER)
diff --git a/docs/hazmat/primitives/mac/cmac.rst b/docs/hazmat/primitives/mac/cmac.rst
index 8b88a3c..a6b048b 100644
--- a/docs/hazmat/primitives/mac/cmac.rst
+++ b/docs/hazmat/primitives/mac/cmac.rst
@@ -19,10 +19,12 @@
 
 .. class:: CMAC(algorithm, backend)
 
+    .. versionadded:: 0.4
+
     CMAC objects take a
     :class:`~cryptography.hazmat.primitives.interfaces.BlockCipherAlgorithm` provider.
 
-    .. code-block:: pycon
+    .. doctest::
 
         >>> from cryptography.hazmat.backends import default_backend
         >>> from cryptography.hazmat.primitives import cmac
diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py
index f46009d..d8c09bd 100644
--- a/tests/hazmat/backends/test_multibackend.py
+++ b/tests/hazmat/backends/test_multibackend.py
@@ -18,11 +18,11 @@
     UnsupportedAlgorithm, _Reasons
 )
 from cryptography.hazmat.backends.interfaces import (
-    CipherBackend, DSABackend, HMACBackend, HashBackend, PBKDF2HMACBackend,
-    RSABackend
+    CMACBackend, CipherBackend, DSABackend, HMACBackend, HashBackend,
+    PBKDF2HMACBackend, RSABackend
 )
 from cryptography.hazmat.backends.multibackend import MultiBackend
-from cryptography.hazmat.primitives import hashes, hmac
+from cryptography.hazmat.primitives import cmac, hashes, hmac
 from cryptography.hazmat.primitives.asymmetric import padding
 from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
 
@@ -108,6 +108,19 @@
         pass
 
 
+@utils.register_interface(CMACBackend)
+class DummyCMACBackend(object):
+    def __init__(self, supported_algorithms):
+        self._algorithms = supported_algorithms
+
+    def cmac_algorithm_supported(self, algorithm):
+        return type(algorithm) in self._algorithms
+
+    def create_cmac_ctx(self, algorithm):
+        if not self.cmac_algorithm_supported(algorithm):
+            raise UnsupportedAlgorithm("", _Reasons.UNSUPPORTED_CIPHER)
+
+
 class TestMultiBackend(object):
     def test_ciphers(self):
         backend = MultiBackend([
@@ -224,3 +237,18 @@
             _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM
         ):
             backend.generate_dsa_private_key(parameters)
+
+    def test_cmac(self):
+        backend = MultiBackend([
+            DummyCMACBackend([algorithms.AES])
+        ])
+
+        fake_key = b"\x00" * 16
+
+        assert backend.cmac_algorithm_supported(
+            algorithms.AES(fake_key)) is True
+
+        cmac.CMAC(algorithms.AES(fake_key), backend)
+
+        with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER):
+            cmac.CMAC(algorithms.TripleDES(fake_key), backend)