Merge pull request #1651 from reaperhulk/x509-signature-algorithm

X509 certificate signature algorithm support
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 85c0f58..e8c2521 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -9,6 +9,9 @@
 * :func:`~cryptography.hazmat.primitives.serialization.load_ssh_public_key` can
   now load elliptic curve public keys.
 * Added
+  :attr:`~cryptography.x509.Certificate.signature_hash_algorithm` support to
+  :class:`~cryptography.x509.Certificate`.
+* Added
   :func:`~cryptography.hazmat.primitives.asymmetric.rsa.rsa_recover_prime_factors`
 * :class:`~cryptography.hazmat.primitives.kdf.KeyDerivationFunction` was moved
   from :mod:`~cryptography.hazmat.primitives.interfaces` to
diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst
index 4c048ab..2cd9faa 100644
--- a/docs/development/test-vectors.rst
+++ b/docs/development/test-vectors.rst
@@ -80,6 +80,9 @@
 * ``v1_cert.pem`` from the OpenSSL source tree (`testx509.pem`_).
 * ``ecdsa_root.pem`` - `DigiCert Global Root G3`_, a ``secp384r1`` ECDSA root
   certificate.
+* ``verisign-md2-root.pem`` - A legacy Verisign public root signed using the
+  MD2 algorithm. This is a PEM conversion of the `root data`_ in the NSS source
+  tree.
 
 Custom X.509 Vectors
 ~~~~~~~~~~~~~~~~~~~~
@@ -219,3 +222,4 @@
 .. _`NIST PKI Testing`: http://csrc.nist.gov/groups/ST/crypto_apps_infra/pki/pkitesting.html
 .. _`testx509.pem`: https://github.com/openssl/openssl/blob/master/test/testx509.pem
 .. _`DigiCert Global Root G3`: http://cacerts.digicert.com/DigiCertGlobalRootG3.crt
+.. _`root data`: https://hg.mozilla.org/projects/nss/file/25b2922cc564/security/nss/lib/ckfw/builtins/certdata.txt#l2053
diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt
index fefd26b..ddd3789 100644
--- a/docs/spelling_wordlist.txt
+++ b/docs/spelling_wordlist.txt
@@ -51,3 +51,4 @@
 unencrypted
 unpadded
 unpadding
+Verisign
diff --git a/docs/x509.rst b/docs/x509.rst
index 0298d94..27f1d54 100644
--- a/docs/x509.rst
+++ b/docs/x509.rst
@@ -182,6 +182,19 @@
 
         The :class:`Name` of the subject.
 
+    .. attribute:: signature_hash_algorithm
+
+        :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`
+
+        Returns the
+        :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` which
+        was used in signing this certificate.
+
+        .. doctest::
+
+            >>> from cryptography.hazmat.primitives import hashes
+            >>> isinstance(cert.signature_hash_algorithm, hashes.SHA256)
+            True
 
 .. class:: Name
 
@@ -266,6 +279,9 @@
 X.509 elements are frequently identified by :class:`ObjectIdentifier`
 instances. The following common OIDs are available as constants.
 
+Name OIDs
+~~~~~~~~~
+
 .. data:: OID_COMMON_NAME
 
     Corresponds to the dotted string ``"2.5.4.3"``. Historically the domain
@@ -346,6 +362,75 @@
     Corresponds to the dotted string ``"1.2.840.113549.1.9.1"``. This OID is
     typically seen in X.509 names.
 
+Signature Algorithm OIDs
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. data:: OID_RSA_WITH_MD5
+
+    Corresponds to the dotted string ``"1.2.840.113549.1.1.4"``. This is
+    an MD5 digest signed by an RSA key.
+
+.. data:: OID_RSA_WITH_SHA1
+
+    Corresponds to the dotted string ``"1.2.840.113549.1.1.5"``. This is
+    a SHA1 digest signed by an RSA key.
+
+.. data:: OID_RSA_WITH_SHA224
+
+    Corresponds to the dotted string ``"1.2.840.113549.1.1.14"``. This is
+    a SHA224 digest signed by an RSA key.
+
+.. data:: OID_RSA_WITH_SHA256
+
+    Corresponds to the dotted string ``"1.2.840.113549.1.1.11"``. This is
+    a SHA256 digest signed by an RSA key.
+
+.. data:: OID_RSA_WITH_SHA384
+
+    Corresponds to the dotted string ``"1.2.840.113549.1.1.12"``. This is
+    a SHA384 digest signed by an RSA key.
+
+.. data:: OID_RSA_WITH_SHA512
+
+    Corresponds to the dotted string ``"1.2.840.113549.1.1.13"``. This is
+    a SHA512 digest signed by an RSA key.
+
+.. data:: OID_ECDSA_WITH_SHA224
+
+    Corresponds to the dotted string ``"1.2.840.10045.4.3.1"``. This is
+    a SHA224 digest signed by an ECDSA key.
+
+.. data:: OID_ECDSA_WITH_SHA256
+
+    Corresponds to the dotted string ``"1.2.840.10045.4.3.2"``. This is
+    a SHA256 digest signed by an ECDSA key.
+
+.. data:: OID_ECDSA_WITH_SHA384
+
+    Corresponds to the dotted string ``"1.2.840.10045.4.3.3"``. This is
+    a SHA384 digest signed by an ECDSA key.
+
+.. data:: OID_ECDSA_WITH_SHA512
+
+    Corresponds to the dotted string ``"1.2.840.10045.4.3.4"``. This is
+    a SHA512 digest signed by an ECDSA key.
+
+.. data:: OID_DSA_WITH_SHA1
+
+    Corresponds to the dotted string ``"1.2.840.10040.4.3"``. This is
+    a SHA1 digest signed by a DSA key.
+
+.. data:: OID_DSA_WITH_SHA224
+
+    Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.1"``. This is
+    a SHA224 digest signed by a DSA key.
+
+.. data:: OID_DSA_WITH_SHA256
+
+    Corresponds to the dotted string ``2.16.840.1.101.3.4.3.2"``. This is
+    a SHA256 digest signed by a DSA key.
+
+
 Exceptions
 ~~~~~~~~~~
 
diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py
index 76dcf32..b712f1f 100644
--- a/src/cryptography/hazmat/backends/openssl/x509.py
+++ b/src/cryptography/hazmat/backends/openssl/x509.py
@@ -16,6 +16,7 @@
 import datetime
 
 from cryptography import utils, x509
+from cryptography.exceptions import UnsupportedAlgorithm
 from cryptography.hazmat.primitives import hashes
 
 
@@ -121,14 +122,7 @@
                 buf, lambda buf: self._backend._lib.OPENSSL_free(buf[0])
             )
             value = self._backend._ffi.buffer(buf[0], res)[:].decode('utf8')
-            # Set to 80 on the recommendation of
-            # https://www.openssl.org/docs/crypto/OBJ_nid2ln.html
-            buf_len = 80
-            buf = self._backend._ffi.new("char[]", buf_len)
-            res = self._backend._lib.OBJ_obj2txt(buf, buf_len, obj, 1)
-            assert res > 0
-            oid = self._backend._ffi.buffer(buf, res)[:].decode()
-
+            oid = self._obj2txt(obj)
             attributes.append(
                 x509.NameAttribute(
                     x509.ObjectIdentifier(oid), value
@@ -136,3 +130,22 @@
             )
 
         return x509.Name(attributes)
+
+    def _obj2txt(self, obj):
+        # Set to 80 on the recommendation of
+        # https://www.openssl.org/docs/crypto/OBJ_nid2ln.html#return_values
+        buf_len = 80
+        buf = self._backend._ffi.new("char[]", buf_len)
+        res = self._backend._lib.OBJ_obj2txt(buf, buf_len, obj, 1)
+        assert res > 0
+        return self._backend._ffi.buffer(buf, res)[:].decode()
+
+    @property
+    def signature_hash_algorithm(self):
+        oid = self._obj2txt(self._x509.sig_alg.algorithm)
+        try:
+            return x509._SIG_OIDS_TO_HASH[oid]
+        except KeyError:
+            raise UnsupportedAlgorithm(
+                "Signature algorithm OID:{0} not recognized".format(oid)
+            )
diff --git a/src/cryptography/hazmat/bindings/openssl/x509.py b/src/cryptography/hazmat/bindings/openssl/x509.py
index e30d23b..bf689e3 100644
--- a/src/cryptography/hazmat/bindings/openssl/x509.py
+++ b/src/cryptography/hazmat/bindings/openssl/x509.py
@@ -65,6 +65,7 @@
 } X509_CRL;
 
 typedef struct {
+    X509_ALGOR *sig_alg;
     X509_CINF *cert_info;
     ...;
 } X509;
diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py
index 8a6ecc8..ad7ebbe 100644
--- a/src/cryptography/x509.py
+++ b/src/cryptography/x509.py
@@ -10,6 +10,7 @@
 import six
 
 from cryptography import utils
+from cryptography.hazmat.primitives import hashes
 
 
 _OID_NAMES = {
@@ -28,6 +29,19 @@
     "2.5.4.65": "pseudonym",
     "0.9.2342.19200300.100.1.25": "domainComponent",
     "1.2.840.113549.1.9.1": "emailAddress",
+    "1.2.840.113549.1.1.4": "md5WithRSAEncryption",
+    "1.2.840.113549.1.1.5": "sha1WithRSAEncryption",
+    "1.2.840.113549.1.1.14": "sha224WithRSAEncryption",
+    "1.2.840.113549.1.1.11": "sha256WithRSAEncryption",
+    "1.2.840.113549.1.1.12": "sha384WithRSAEncryption",
+    "1.2.840.113549.1.1.13": "sha512WithRSAEncryption",
+    "1.2.840.10045.4.3.1": "ecdsa-with-SHA224",
+    "1.2.840.10045.4.3.2": "ecdsa-with-SHA256",
+    "1.2.840.10045.4.3.3": "ecdsa-with-SHA384",
+    "1.2.840.10045.4.3.4": "ecdsa-with-SHA512",
+    "1.2.840.10040.4.3": "dsa-with-sha1",
+    "2.16.840.1.101.3.4.3.1": "dsa-with-sha224",
+    "2.16.840.1.101.3.4.3.2": "dsa-with-sha256",
 }
 
 
@@ -143,6 +157,36 @@
 OID_DOMAIN_COMPONENT = ObjectIdentifier("0.9.2342.19200300.100.1.25")
 OID_EMAIL_ADDRESS = ObjectIdentifier("1.2.840.113549.1.9.1")
 
+OID_RSA_WITH_MD5 = ObjectIdentifier("1.2.840.113549.1.1.4")
+OID_RSA_WITH_SHA1 = ObjectIdentifier("1.2.840.113549.1.1.5")
+OID_RSA_WITH_SHA224 = ObjectIdentifier("1.2.840.113549.1.1.14")
+OID_RSA_WITH_SHA256 = ObjectIdentifier("1.2.840.113549.1.1.11")
+OID_RSA_WITH_SHA384 = ObjectIdentifier("1.2.840.113549.1.1.12")
+OID_RSA_WITH_SHA512 = ObjectIdentifier("1.2.840.113549.1.1.13")
+OID_ECDSA_WITH_SHA224 = ObjectIdentifier("1.2.840.10045.4.3.1")
+OID_ECDSA_WITH_SHA256 = ObjectIdentifier("1.2.840.10045.4.3.2")
+OID_ECDSA_WITH_SHA384 = ObjectIdentifier("1.2.840.10045.4.3.3")
+OID_ECDSA_WITH_SHA512 = ObjectIdentifier("1.2.840.10045.4.3.4")
+OID_DSA_WITH_SHA1 = ObjectIdentifier("1.2.840.10040.4.3")
+OID_DSA_WITH_SHA224 = ObjectIdentifier("2.16.840.1.101.3.4.3.1")
+OID_DSA_WITH_SHA256 = ObjectIdentifier("2.16.840.1.101.3.4.3.2")
+
+_SIG_OIDS_TO_HASH = {
+    OID_RSA_WITH_MD5.dotted_string: hashes.MD5(),
+    OID_RSA_WITH_SHA1.dotted_string: hashes.SHA1(),
+    OID_RSA_WITH_SHA224.dotted_string: hashes.SHA224(),
+    OID_RSA_WITH_SHA256.dotted_string: hashes.SHA256(),
+    OID_RSA_WITH_SHA384.dotted_string: hashes.SHA384(),
+    OID_RSA_WITH_SHA512.dotted_string: hashes.SHA512(),
+    OID_ECDSA_WITH_SHA224.dotted_string: hashes.SHA224(),
+    OID_ECDSA_WITH_SHA256.dotted_string: hashes.SHA256(),
+    OID_ECDSA_WITH_SHA384.dotted_string: hashes.SHA384(),
+    OID_ECDSA_WITH_SHA512.dotted_string: hashes.SHA512(),
+    OID_DSA_WITH_SHA1.dotted_string: hashes.SHA1(),
+    OID_DSA_WITH_SHA224.dotted_string: hashes.SHA224(),
+    OID_DSA_WITH_SHA256.dotted_string: hashes.SHA256()
+}
+
 
 @six.add_metaclass(abc.ABCMeta)
 class Certificate(object):
@@ -193,3 +237,10 @@
         """
         Returns the subject name object.
         """
+
+    @abc.abstractproperty
+    def signature_hash_algorithm(self):
+        """
+        Returns a HashAlgorithm corresponding to the type of the digest signed
+        in the certificate.
+        """
diff --git a/tests/test_x509.py b/tests/test_x509.py
index 55a9408..8f00eee 100644
--- a/tests/test_x509.py
+++ b/tests/test_x509.py
@@ -13,6 +13,7 @@
 import six
 
 from cryptography import x509
+from cryptography.exceptions import UnsupportedAlgorithm
 from cryptography.hazmat.backends.interfaces import (
     DSABackend, EllipticCurveBackend, RSABackend, X509Backend
 )
@@ -45,6 +46,7 @@
         assert cert.serial == 11559813051657483483
         fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1()))
         assert fingerprint == b"2b619ed04bfc9c3b08eb677d272192286a0947a8"
+        assert isinstance(cert.signature_hash_algorithm, hashes.SHA1)
 
     def test_load_der_cert(self, backend):
         cert = _load_cert(
@@ -56,6 +58,7 @@
         assert cert.serial == 2
         fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1()))
         assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d"
+        assert isinstance(cert.signature_hash_algorithm, hashes.SHA256)
 
     def test_issuer(self, backend):
         cert = _load_cert(
@@ -328,6 +331,15 @@
         with pytest.raises(ValueError):
             x509.load_der_x509_certificate(b"notacert", backend)
 
+    def test_unsupported_signature_hash_algorithm_cert(self, backend):
+        cert = _load_cert(
+            os.path.join("x509", "verisign_md2_root.pem"),
+            x509.load_pem_x509_certificate,
+            backend
+        )
+        with pytest.raises(UnsupportedAlgorithm):
+            cert.signature_hash_algorithm
+
 
 @pytest.mark.requires_backend_interface(interface=DSABackend)
 @pytest.mark.requires_backend_interface(interface=X509Backend)
@@ -338,6 +350,7 @@
             x509.load_pem_x509_certificate,
             backend
         )
+        assert isinstance(cert.signature_hash_algorithm, hashes.SHA1)
         public_key = cert.public_key()
         assert isinstance(public_key, interfaces.DSAPublicKey)
         if isinstance(public_key, interfaces.DSAPublicKeyWithNumbers):
@@ -390,6 +403,7 @@
             x509.load_pem_x509_certificate,
             backend
         )
+        assert isinstance(cert.signature_hash_algorithm, hashes.SHA384)
         public_key = cert.public_key()
         assert isinstance(public_key, interfaces.EllipticCurvePublicKey)
         if isinstance(
diff --git a/vectors/cryptography_vectors/x509/verisign_md2_root.pem b/vectors/cryptography_vectors/x509/verisign_md2_root.pem
new file mode 100644
index 0000000..87676ac
--- /dev/null
+++ b/vectors/cryptography_vectors/x509/verisign_md2_root.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
+A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
+cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
+MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
+BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
+YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
+ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
+BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
+I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
+CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
+lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
+AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
+-----END CERTIFICATE-----