merge master
diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py
index 39a2bca..cfe705a 100644
--- a/OpenSSL/SSL.py
+++ b/OpenSSL/SSL.py
@@ -16,13 +16,12 @@
     native as _native,
     text_to_bytes_and_warn as _text_to_bytes_and_warn,
     path_string as _path_string,
+    UNSPECIFIED as _UNSPECIFIED,
 )
 
 from OpenSSL.crypto import (
     FILETYPE_PEM, _PassphraseHelper, PKey, X509Name, X509, X509Store)
 
-_unspecified = object()
-
 try:
     _memoryview = memoryview
 except NameError:
@@ -629,7 +628,7 @@
             raise exception
 
 
-    def use_privatekey_file(self, keyfile, filetype=_unspecified):
+    def use_privatekey_file(self, keyfile, filetype=_UNSPECIFIED):
         """
         Load a private key from a file
 
@@ -640,7 +639,7 @@
         """
         keyfile = _path_string(keyfile)
 
-        if filetype is _unspecified:
+        if filetype is _UNSPECIFIED:
             filetype = FILETYPE_PEM
         elif not isinstance(filetype, integer_types):
             raise TypeError("filetype must be an integer")
diff --git a/OpenSSL/_util.py b/OpenSSL/_util.py
index 8d59252..0cc34d8 100644
--- a/OpenSSL/_util.py
+++ b/OpenSSL/_util.py
@@ -95,6 +95,11 @@
     def byte_string(s):
         return s
 
+
+# A marker object to observe whether some optional arguments are passed any
+# value or not.
+UNSPECIFIED = object()
+
 _TEXT_WARNING = (
     text_type.__name__ + " for {0} is no longer accepted, use bytes"
 )
diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py
index f9e189c..0350537 100644
--- a/OpenSSL/crypto.py
+++ b/OpenSSL/crypto.py
@@ -2,6 +2,7 @@
 from base64 import b16encode
 from functools import partial
 from operator import __eq__, __ne__, __lt__, __le__, __gt__, __ge__
+from warnings import warn as _warn
 
 from six import (
     integer_types as _integer_types,
@@ -14,6 +15,7 @@
     exception_from_error_queue as _exception_from_error_queue,
     byte_string as _byte_string,
     native as _native,
+    UNSPECIFIED as _UNSPECIFIED,
     text_to_bytes_and_warn as _text_to_bytes_and_warn,
 )
 
@@ -1831,22 +1833,29 @@
             _raise_current_error()
 
 
-    def export(self, cert, key, type=FILETYPE_PEM, days=100):
+    def export(self, cert, key, type=FILETYPE_PEM, days=100,
+               digest=_UNSPECIFIED):
         """
         export a CRL as a string
 
         :param cert: Used to sign CRL.
+
         :type cert: :class:`X509`
 
         :param key: Used to sign CRL.
+
         :type key: :class:`PKey`
 
-        :param type: The export format, either :py:data:`FILETYPE_PEM`, :py:data:`FILETYPE_ASN1`, or :py:data:`FILETYPE_TEXT`.
-
+        :param type: The export format, either :py:data:`FILETYPE_PEM`,
+            :py:data:`FILETYPE_ASN1`, or :py:data:`FILETYPE_TEXT`.
         :param days: The number of days until the next update of this CRL.
+
         :type days: :py:data:`int`
 
-        :return: :py:data:`str`
+        :param bytes digest: The name of the message digest to use (eg
+            ``b"sha1"``).
+
+        :return: :py:data:`bytes`
         """
         if not isinstance(cert, X509):
             raise TypeError("cert must be an X509 instance")
@@ -1855,6 +1864,19 @@
         if not isinstance(type, int):
             raise TypeError("type must be an integer")
 
+        if digest is _UNSPECIFIED:
+            _warn(
+                "The default message digest (md5) is deprecated.  "
+                "Pass the name of a message digest explicitly.",
+                category=DeprecationWarning,
+                stacklevel=2,
+            )
+            digest = b"md5"
+
+        digest_obj = _lib.EVP_get_digestbyname(digest)
+        if digest_obj == _ffi.NULL:
+            raise ValueError("No such digest method")
+
         bio = _lib.BIO_new(_lib.BIO_s_mem())
         if bio == _ffi.NULL:
             # TODO: This is untested.
@@ -1874,7 +1896,7 @@
 
         _lib.X509_CRL_set_issuer_name(self._crl, _lib.X509_get_subject_name(cert._x509))
 
-        sign_result = _lib.X509_CRL_sign(self._crl, key._pkey, _lib.EVP_md5())
+        sign_result = _lib.X509_CRL_sign(self._crl, key._pkey, digest_obj)
         if not sign_result:
             _raise_current_error()
 
diff --git a/OpenSSL/test/test_crypto.py b/OpenSSL/test/test_crypto.py
index dea5858..f6f0751 100644
--- a/OpenSSL/test/test_crypto.py
+++ b/OpenSSL/test/test_crypto.py
@@ -6,6 +6,7 @@
 """
 
 from unittest import main
+from warnings import catch_warnings, simplefilter
 
 import base64
 import os
@@ -3043,11 +3044,9 @@
         self.assertRaises(TypeError, CRL, None)
 
 
-    def test_export(self):
+    def _get_crl(self):
         """
-        Use python to create a simple CRL with a revocation, and export
-        the CRL in formats of PEM, DER and text.  Those outputs are verified
-        with the openssl program.
+        Get a new ``CRL`` with a revocation.
         """
         crl = CRL()
         revoked = Revoked()
@@ -3056,26 +3055,110 @@
         revoked.set_serial(b('3ab'))
         revoked.set_reason(b('sUpErSeDEd'))
         crl.add_revoked(revoked)
+        return crl
 
+
+    def test_export_pem(self):
+        """
+        If not passed a format, ``CRL.export`` returns a "PEM" format string
+        representing a serial number, a revoked reason, and certificate issuer
+        information.
+        """
+        crl = self._get_crl()
         # PEM format
         dumped_crl = crl.export(self.cert, self.pkey, days=20)
         text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text")
+
+        # These magic values are based on the way the CRL above was constructed
+        # and with what certificate it was exported.
         text.index(b('Serial Number: 03AB'))
         text.index(b('Superseded'))
-        text.index(b('Issuer: /C=US/ST=IL/L=Chicago/O=Testing/CN=Testing Root CA'))
+        text.index(
+            b('Issuer: /C=US/ST=IL/L=Chicago/O=Testing/CN=Testing Root CA')
+        )
+
+
+    def test_export_der(self):
+        """
+        If passed ``FILETYPE_ASN1`` for the format, ``CRL.export`` returns a
+        "DER" format string representing a serial number, a revoked reason, and
+        certificate issuer information.
+        """
+        crl = self._get_crl()
 
         # DER format
         dumped_crl = crl.export(self.cert, self.pkey, FILETYPE_ASN1)
-        text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text", b"-inform", b"DER")
+        text = _runopenssl(
+            dumped_crl, b"crl", b"-noout", b"-text", b"-inform", b"DER"
+        )
         text.index(b('Serial Number: 03AB'))
         text.index(b('Superseded'))
-        text.index(b('Issuer: /C=US/ST=IL/L=Chicago/O=Testing/CN=Testing Root CA'))
+        text.index(
+            b('Issuer: /C=US/ST=IL/L=Chicago/O=Testing/CN=Testing Root CA')
+        )
+
+
+    def test_export_text(self):
+        """
+        If passed ``FILETYPE_TEXT`` for the format, ``CRL.export`` returns a
+        text format string like the one produced by the openssl command line
+        tool.
+        """
+        crl = self._get_crl()
+
+        dumped_crl = crl.export(self.cert, self.pkey, FILETYPE_ASN1)
+        text = _runopenssl(
+            dumped_crl, b"crl", b"-noout", b"-text", b"-inform", b"DER"
+        )
 
         # text format
         dumped_text = crl.export(self.cert, self.pkey, type=FILETYPE_TEXT)
         self.assertEqual(text, dumped_text)
 
 
+    def test_export_custom_digest(self):
+        """
+        If passed the name of a digest function, ``CRL.export`` uses a
+        signature algorithm based on that digest function.
+        """
+        crl = self._get_crl()
+        dumped_crl = crl.export(self.cert, self.pkey, digest=b"sha1")
+        text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text")
+        text.index(b('Signature Algorithm: sha1'))
+
+
+    def test_export_md5_digest(self):
+        """
+        If passed md5 as the digest function, ``CRL.export`` uses md5 and does
+        not emit a deprecation warning.
+        """
+        crl = self._get_crl()
+        with catch_warnings(record=True) as catcher:
+            simplefilter("always")
+            self.assertEqual(0, len(catcher))
+        dumped_crl = crl.export(self.cert, self.pkey, digest=b"md5")
+        text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text")
+        text.index(b('Signature Algorithm: md5'))
+
+
+    def test_export_default_digest(self):
+        """
+        If not passed the name of a digest function, ``CRL.export`` uses a
+        signature algorithm based on MD5 and emits a deprecation warning.
+        """
+        crl = self._get_crl()
+        with catch_warnings(record=True) as catcher:
+            simplefilter("always")
+            dumped_crl = crl.export(self.cert, self.pkey)
+            self.assertEqual(
+                "The default message digest (md5) is deprecated.  "
+                "Pass the name of a message digest explicitly.",
+                str(catcher[0].message),
+            )
+        text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text")
+        text.index(b('Signature Algorithm: md5'))
+
+
     def test_export_invalid(self):
         """
         If :py:obj:`CRL.export` is used with an uninitialized :py:obj:`X509`
@@ -3106,7 +3189,7 @@
         crl = CRL()
         self.assertRaises(TypeError, crl.export)
         self.assertRaises(TypeError, crl.export, self.cert)
-        self.assertRaises(TypeError, crl.export, self.cert, self.pkey, FILETYPE_PEM, 10, "foo")
+        self.assertRaises(TypeError, crl.export, self.cert, self.pkey, FILETYPE_PEM, 10, "md5", "foo")
 
         self.assertRaises(TypeError, crl.export, None, self.pkey, FILETYPE_PEM, 10)
         self.assertRaises(TypeError, crl.export, self.cert, None, FILETYPE_PEM, 10)
@@ -3124,6 +3207,19 @@
         self.assertRaises(ValueError, crl.export, self.cert, self.pkey, 100, 10)
 
 
+    def test_export_unknown_digest(self):
+        """
+        Calling :py:obj:`OpenSSL.CRL.export` with a unsupported digest results
+        in a :py:obj:`ValueError` being raised.
+        """
+        crl = CRL()
+        self.assertRaises(
+            ValueError,
+            crl.export,
+            self.cert, self.pkey, FILETYPE_PEM, 10, b"strange-digest"
+        )
+
+
     def test_get_revoked(self):
         """
         Use python to create a simple CRL with two revocations.
diff --git a/doc/api/crypto.rst b/doc/api/crypto.rst
index ee261c5..fb254cf 100644
--- a/doc/api/crypto.rst
+++ b/doc/api/crypto.rst
@@ -764,10 +764,11 @@
     Add a Revoked object to the CRL, by value not reference.
 
 
-.. py:method:: CRL.export(cert, key[, type=FILETYPE_PEM][, days=100])
+.. py:method:: CRL.export(cert, key[, type=FILETYPE_PEM][, days=100][, digest='md5'])
 
     Use *cert* and *key* to sign the CRL and return the CRL as a string.
     *days* is the number of days before the next CRL is due.
+    *digest* is the algorithm that will be used to sign CRL.
 
 
 .. py:method:: CRL.get_revoked()