From 2923dc0f475e0d1a7893c7ca264c95c75f8ba9dd Mon Sep 17 00:00:00 2001 From: Bulat Gaifullin Date: Sun, 21 Sep 2014 22:36:48 +0400 Subject: [PATCH 01/11] added parameter digest to select digest algorithm for CRL --- OpenSSL/crypto.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py index 313a30aed..efa1d9103 100644 --- a/OpenSSL/crypto.py +++ b/OpenSSL/crypto.py @@ -1707,7 +1707,7 @@ def add_revoked(self, revoked): _raise_current_error() - def export(self, cert, key, type=FILETYPE_PEM, days=100): + def export(self, cert, key, type=FILETYPE_PEM, days=100, digest="md5"): """ export a CRL as a string @@ -1721,6 +1721,7 @@ def export(self, cert, key, type=FILETYPE_PEM, days=100): :param days: The number of days until the next update of this CRL. :type days: :py:data:`int` + :param digest: The message digest to use :return: :py:data:`str` """ @@ -1731,6 +1732,10 @@ def export(self, cert, key, type=FILETYPE_PEM, days=100): if not isinstance(type, int): raise TypeError("type must be an integer") + digest_obj = _lib.EVP_get_digestbyname(_byte_string(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. @@ -1750,7 +1755,7 @@ def export(self, cert, key, type=FILETYPE_PEM, days=100): _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() From 1966c97cc544b70034b0c22baf216b4e65052673 Mon Sep 17 00:00:00 2001 From: Bulat Gaifullin Date: Mon, 22 Sep 2014 09:47:20 +0400 Subject: [PATCH 02/11] added tests to check crl_export digest algorithm --- OpenSSL/test/test_crypto.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/OpenSSL/test/test_crypto.py b/OpenSSL/test/test_crypto.py index 60f9e6afa..1747b7ee3 100644 --- a/OpenSSL/test/test_crypto.py +++ b/OpenSSL/test/test_crypto.py @@ -2957,6 +2957,16 @@ def test_export(self): dumped_text = crl.export(self.cert, self.pkey, type=FILETYPE_TEXT) self.assertEqual(text, dumped_text) + # sha1 digest + dumped_crl = crl.export(self.cert, self.pkey, digest='sha1') + text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text") + text.index(b('Signature Algorithm: sha1')) + + # md5 digest(by default) + dumped_crl = crl.export(self.cert, self.pkey) + text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text") + text.index(b('Signature Algorithm: md5')) + def test_export_invalid(self): """ From bb7c4cb4ce224c296254a6be657565c8a795255c Mon Sep 17 00:00:00 2001 From: Bulat Gaifullin Date: Mon, 22 Sep 2014 09:56:04 +0400 Subject: [PATCH 03/11] added test to check validation of digest --- OpenSSL/test/test_crypto.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/OpenSSL/test/test_crypto.py b/OpenSSL/test/test_crypto.py index 1747b7ee3..2e834af52 100644 --- a/OpenSSL/test/test_crypto.py +++ b/OpenSSL/test/test_crypto.py @@ -2967,6 +2967,10 @@ def test_export(self): text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text") text.index(b('Signature Algorithm: md5')) + # test that unknown digest types fail + self.assertRaises( + ValueError, crl.export, self.cert, self.pkey, digest="strange-digest") + def test_export_invalid(self): """ From 925f786703d126c684c80df2fb5bc8f4da5c274a Mon Sep 17 00:00:00 2001 From: Bulat Gaifullin Date: Mon, 22 Sep 2014 10:10:44 +0400 Subject: [PATCH 04/11] fixed test_export_wrong_args --- OpenSSL/test/test_crypto.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/OpenSSL/test/test_crypto.py b/OpenSSL/test/test_crypto.py index 2e834af52..dacbf9e3b 100644 --- a/OpenSSL/test/test_crypto.py +++ b/OpenSSL/test/test_crypto.py @@ -2967,10 +2967,6 @@ def test_export(self): text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text") text.index(b('Signature Algorithm: md5')) - # test that unknown digest types fail - self.assertRaises( - ValueError, crl.export, self.cert, self.pkey, digest="strange-digest") - def test_export_invalid(self): """ @@ -3002,7 +2998,7 @@ def test_export_wrong_args(self): 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) @@ -3020,6 +3016,11 @@ def test_export_unknown_filetype(self): self.assertRaises(ValueError, crl.export, self.cert, self.pkey, 100, 10) + def test_export_unknown_digest(self): + crl = CRL() + self.assertRaises(ValueError, crl.export, self.cert, self.pkey, FILETYPE_PEM, 10, "strange-digest") + + def test_get_revoked(self): """ Use python to create a simple CRL with two revocations. From 5f9eea4e4c5982f78deb0802610a4f965bafec43 Mon Sep 17 00:00:00 2001 From: Bulat Gaifullin Date: Tue, 23 Sep 2014 19:35:15 +0400 Subject: [PATCH 05/11] update docs --- OpenSSL/crypto.py | 2 ++ OpenSSL/test/test_crypto.py | 4 ++++ doc/api/crypto.rst | 3 ++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py index efa1d9103..95ccc1823 100644 --- a/OpenSSL/crypto.py +++ b/OpenSSL/crypto.py @@ -1721,7 +1721,9 @@ def export(self, cert, key, type=FILETYPE_PEM, days=100, digest="md5"): :param days: The number of days until the next update of this CRL. :type days: :py:data:`int` + :param digest: The message digest to use + :type digest: :py:data:`str` :return: :py:data:`str` """ diff --git a/OpenSSL/test/test_crypto.py b/OpenSSL/test/test_crypto.py index dacbf9e3b..5c4eac436 100644 --- a/OpenSSL/test/test_crypto.py +++ b/OpenSSL/test/test_crypto.py @@ -3017,6 +3017,10 @@ def test_export_unknown_filetype(self): 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, "strange-digest") diff --git a/doc/api/crypto.rst b/doc/api/crypto.rst index b360e89c0..c5b7c33c9 100644 --- a/doc/api/crypto.rst +++ b/doc/api/crypto.rst @@ -724,10 +724,11 @@ CRL objects have the following methods: 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() From 6043279aead4412ada76177012cd34949e20d38c Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Mon, 13 Apr 2015 12:26:07 -0400 Subject: [PATCH 06/11] Split up the one big CRL test and deprecate the default message digest. --- OpenSSL/crypto.py | 19 ++++++++- OpenSSL/test/test_crypto.py | 79 ++++++++++++++++++++++++++++++++----- 2 files changed, 86 insertions(+), 12 deletions(-) diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py index 95ccc1823..9f44b448c 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, @@ -24,6 +25,10 @@ TYPE_RSA = _lib.EVP_PKEY_RSA TYPE_DSA = _lib.EVP_PKEY_DSA +# A marker object to observe whether some optional arguments are passed any +# value or not. +_undefined = object() + class Error(Exception): """ @@ -1707,7 +1712,8 @@ def add_revoked(self, revoked): _raise_current_error() - def export(self, cert, key, type=FILETYPE_PEM, days=100, digest="md5"): + def export(self, cert, key, type=FILETYPE_PEM, days=100, + digest=_undefined): """ export a CRL as a string @@ -1722,7 +1728,7 @@ def export(self, cert, key, type=FILETYPE_PEM, days=100, digest="md5"): :param days: The number of days until the next update of this CRL. :type days: :py:data:`int` - :param digest: The message digest to use + :param digest: The message digest to use (eg ``"sha1"``). :type digest: :py:data:`str` :return: :py:data:`str` @@ -1734,6 +1740,15 @@ def export(self, cert, key, type=FILETYPE_PEM, days=100, digest="md5"): if not isinstance(type, int): raise TypeError("type must be an integer") + if digest is _undefined: + _warn( + "The default message digest (md5) is deprecated. " + "Pass the name of a message digest explicitly.", + category=DeprecationWarning, + stacklevel=2, + ) + digest = "md5" + digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) if digest_obj == _ffi.NULL: raise ValueError("No such digest method") diff --git a/OpenSSL/test/test_crypto.py b/OpenSSL/test/test_crypto.py index 5c4eac436..b2ad004a2 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 @@ -2925,11 +2926,9 @@ def test_construction_wrong_args(self): 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() @@ -2938,32 +2937,92 @@ def test_export(self): 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) - # sha1 digest + + 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='sha1') text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text") text.index(b('Signature Algorithm: sha1')) - # md5 digest(by default) - dumped_crl = crl.export(self.cert, self.pkey) + + 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')) From 00f84eb5710d85669f27e22b27aeea62d71ce2c7 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Mon, 13 Apr 2015 12:47:21 -0400 Subject: [PATCH 07/11] Unify the unspecified argument thing. --- OpenSSL/SSL.py | 7 +++---- OpenSSL/_util.py | 5 +++++ OpenSSL/crypto.py | 12 +++++------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index 1215526e1..6dba10cab 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -14,13 +14,12 @@ exception_from_error_queue as _exception_from_error_queue, native as _native, 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: @@ -559,7 +558,7 @@ def _raise_passphrase_exception(self): 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 @@ -570,7 +569,7 @@ def use_privatekey_file(self, keyfile, filetype=_unspecified): """ 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 1eec17e89..a4fe63a6d 100644 --- a/OpenSSL/_util.py +++ b/OpenSSL/_util.py @@ -93,3 +93,8 @@ def byte_string(s): else: def byte_string(s): return s + + +# A marker object to observe whether some optional arguments are passed any +# value or not. +UNSPECIFIED = object() diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py index 53a8dfc72..4f0e7f226 100644 --- a/OpenSSL/crypto.py +++ b/OpenSSL/crypto.py @@ -14,7 +14,9 @@ lib as _lib, exception_from_error_queue as _exception_from_error_queue, byte_string as _byte_string, - native as _native) + native as _native, + UNSPECIFIED as _UNSPECIFIED, +) FILETYPE_PEM = _lib.SSL_FILETYPE_PEM FILETYPE_ASN1 = _lib.SSL_FILETYPE_ASN1 @@ -25,10 +27,6 @@ TYPE_RSA = _lib.EVP_PKEY_RSA TYPE_DSA = _lib.EVP_PKEY_DSA -# A marker object to observe whether some optional arguments are passed any -# value or not. -_undefined = object() - class Error(Exception): @@ -1835,7 +1833,7 @@ def add_revoked(self, revoked): def export(self, cert, key, type=FILETYPE_PEM, days=100, - digest=_undefined): + digest=_UNSPECIFIED): """ export a CRL as a string @@ -1862,7 +1860,7 @@ def export(self, cert, key, type=FILETYPE_PEM, days=100, if not isinstance(type, int): raise TypeError("type must be an integer") - if digest is _undefined: + if digest is _UNSPECIFIED: _warn( "The default message digest (md5) is deprecated. " "Pass the name of a message digest explicitly.", From cce22d0b0796c2a96fcfe8f226ab8fd0fc34653b Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Mon, 13 Apr 2015 13:56:09 -0400 Subject: [PATCH 08/11] Switch the message digest name to bytes. --- OpenSSL/crypto.py | 17 ++++++++++------- OpenSSL/test/test_crypto.py | 16 +++++++++++++++- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py index 4f0e7f226..ba83efe72 100644 --- a/OpenSSL/crypto.py +++ b/OpenSSL/crypto.py @@ -1838,20 +1838,23 @@ def export(self, cert, key, type=FILETYPE_PEM, days=100, 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`. + :type key: :class:`PKey` + :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` - :param digest: The message digest to use (eg ``"sha1"``). - :type digest: :py:data:`str` + :param bytes digest: The name of the message digest to use (eg + ``b"sha1"``). - :return: :py:data:`str` + :return: :py:data:`bytes` """ if not isinstance(cert, X509): raise TypeError("cert must be an X509 instance") @@ -1867,9 +1870,9 @@ def export(self, cert, key, type=FILETYPE_PEM, days=100, category=DeprecationWarning, stacklevel=2, ) - digest = "md5" + digest = b"md5" - digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) + digest_obj = _lib.EVP_get_digestbyname(digest) if digest_obj == _ffi.NULL: raise ValueError("No such digest method") diff --git a/OpenSSL/test/test_crypto.py b/OpenSSL/test/test_crypto.py index b82728108..7f700910b 100644 --- a/OpenSSL/test/test_crypto.py +++ b/OpenSSL/test/test_crypto.py @@ -3073,11 +3073,25 @@ def test_export_custom_digest(self): signature algorithm based on that digest function. """ crl = self._get_crl() - dumped_crl = crl.export(self.cert, self.pkey, digest='sha1') + 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 From 9be8519b76c27ec26159dfc8e7747367fe609ff1 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Mon, 13 Apr 2015 21:20:51 -0400 Subject: [PATCH 09/11] Pass the correct type to CRL.export. --- OpenSSL/test/test_crypto.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/OpenSSL/test/test_crypto.py b/OpenSSL/test/test_crypto.py index 7f700910b..ae973ab59 100644 --- a/OpenSSL/test/test_crypto.py +++ b/OpenSSL/test/test_crypto.py @@ -3164,7 +3164,11 @@ def test_export_unknown_digest(self): in a :py:obj:`ValueError` being raised. """ crl = CRL() - self.assertRaises(ValueError, crl.export, self.cert, self.pkey, FILETYPE_PEM, 10, "strange-digest") + self.assertRaises( + ValueError, + crl.export, + self.cert, self.pkey, FILETYPE_PEM, 10, b"strange-digest" + ) def test_get_revoked(self): From df5140163bd4f3d11bc0acc11c1b2968bc2ec49f Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Mon, 13 Apr 2015 21:45:18 -0400 Subject: [PATCH 10/11] Fix weird spacing change. --- OpenSSL/crypto.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py index 0350537fe..50ff74fad 100644 --- a/OpenSSL/crypto.py +++ b/OpenSSL/crypto.py @@ -1839,18 +1839,15 @@ def export(self, cert, key, type=FILETYPE_PEM, days=100, 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 days: The number of days until the next update of this CRL. - :type days: :py:data:`int` + :param int days: The number of days until the next update of this CRL. :param bytes digest: The name of the message digest to use (eg ``b"sha1"``). From e011b9577fb390d59689f0c4e8430dd5070e5bb7 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone Date: Mon, 13 Apr 2015 21:46:04 -0400 Subject: [PATCH 11/11] Bytes. --- doc/api/crypto.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/crypto.rst b/doc/api/crypto.rst index fb254cf7d..57a60f3ba 100644 --- a/doc/api/crypto.rst +++ b/doc/api/crypto.rst @@ -764,7 +764,7 @@ CRL objects have the following methods: Add a Revoked object to the CRL, by value not reference. -.. py:method:: CRL.export(cert, key[, type=FILETYPE_PEM][, days=100][, digest='md5']) +.. py:method:: CRL.export(cert, key[, type=FILETYPE_PEM][, days=100][, digest=b'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.