summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Sully <daniel-github@electricrain.com>2016-06-04 18:05:27 -0700
committerHynek Schlawack <hs@ox.cx>2016-06-04 18:05:27 -0700
commit44e767ae1e7c458fb039c3a711a562839f68ccaa (patch)
tree2f4f2d77a236ccd0602c26bc9a566be108b445d5
parentbf012875e787b9b898df2b85493fa2bb93f5a789 (diff)
downloadpyopenssl-44e767ae1e7c458fb039c3a711a562839f68ccaa.tar.gz
Enable use of CRL (and more) in verify context. (#483)
* Enable use of CRL (and more) in verify context. * flake8 fixes. * Comments from Hynek & Paul. * More updates from Paul & Hynek. * Fix closing backtick. * Remove commented out constants. * More doc updates. Changelog update. * Getting closer? * Sphinx fix. * Indent docstring. * More Sphinx updates. * Type updates for docstrings. * Remove Enum subclassing. * Docstring update. * Docstrings updates. * Round and round it goes. Where it ends, nobody knows. * Remove TODO comment. Need @sholsapp to follow up. * Time for lots of alcohol.
-rw-r--r--CHANGELOG.rst3
-rw-r--r--doc/api/crypto.rst19
-rw-r--r--src/OpenSSL/crypto.py343
-rw-r--r--tests/test_crypto.py89
4 files changed, 343 insertions, 111 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index aeec693..b6401aa 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -23,7 +23,8 @@ Deprecations:
Changes:
^^^^^^^^
-*none*
+- Enable use of CRL (and more) in verify context.
+ `#483 <https://github.com/pyca/pyopenssl/pull/483>`_
----
diff --git a/doc/api/crypto.rst b/doc/api/crypto.rst
index 7597d7f..5f7df24 100644
--- a/doc/api/crypto.rst
+++ b/doc/api/crypto.rst
@@ -206,6 +206,25 @@ X509StoreContext objects
.. _openssl-pkey:
+X509StoreFlags constants
+------------------------
+
+.. autoclass:: X509StoreFlags
+
+ .. data:: CRL_CHECK
+ .. data:: CRL_CHECK_ALL
+ .. data:: IGNORE_CRITICAL
+ .. data:: X509_STRICT
+ .. data:: ALLOW_PROXY_CERTS
+ .. data:: POLICY_CHECK
+ .. data:: EXPLICIT_POLICY
+ .. data:: INHIBIT_MAP
+ .. data:: NOTIFY_POLICY
+ .. data:: CHECK_SS_SIGNATURE
+ .. data:: CB_ISSUER_CHECK
+
+.. _openssl-x509storeflags:
+
PKey objects
------------
diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py
index 4c1ce87..9b0b3c9 100644
--- a/src/OpenSSL/crypto.py
+++ b/src/OpenSSL/crypto.py
@@ -183,7 +183,7 @@ class PKey(object):
of the appropriate type.
:raises ValueError: If the number of bits isn't an integer of
the appropriate size.
- :return: :py:const:`None`
+ :return: ``None``
"""
if not isinstance(type, int):
raise TypeError("type must be an integer")
@@ -816,7 +816,7 @@ class X509Req(object):
:param pkey: The public key to use.
:type pkey: :py:class:`PKey`
- :return: :py:const:`None`
+ :return: ``None``
"""
set_result = _lib.X509_REQ_set_pubkey(self._req, pkey._pkey)
if not set_result:
@@ -845,7 +845,7 @@ class X509Req(object):
request.
:param int version: The version number.
- :return: :py:const:`None`
+ :return: ``None``
"""
set_result = _lib.X509_REQ_set_version(self._req, version)
if not set_result:
@@ -891,7 +891,7 @@ class X509Req(object):
:param extensions: The X.509 extensions to add.
:type extensions: iterable of :py:class:`X509Extension`
- :return: :py:const:`None`
+ :return: ``None``
"""
stack = _lib.sk_X509_EXTENSION_new_null()
if stack == _ffi.NULL:
@@ -938,7 +938,7 @@ class X509Req(object):
:param digest: The name of the message digest to use for the signature,
e.g. :py:data:`b"sha1"`.
:type digest: :py:class:`bytes`
- :return: :py:const:`None`
+ :return: ``None``
"""
if pkey._only_public:
raise ValueError("Key has only public part")
@@ -996,7 +996,7 @@ class X509(object):
:param version: The version number of the certificate.
:type version: :py:class:`int`
- :return: :py:const:`None`
+ :return: ``None``
"""
if not isinstance(version, int):
raise TypeError("version must be an integer")
@@ -1174,7 +1174,7 @@ class X509(object):
Return the serial number of this certificate.
:return: The serial number.
- :rtype: :py:class:`int`
+ :rtype: int
"""
asn1_serial = _lib.X509_get_serialNumber(self._x509)
bignum_serial = _lib.ASN1_INTEGER_to_BN(asn1_serial, _ffi.NULL)
@@ -1193,10 +1193,9 @@ class X509(object):
"""
Adjust the time stamp on which the certificate stops being valid.
- :param amount: The number of seconds by which to adjust the timestamp.
- :type amount: :py:class:`int`
-
- :return: :py:const:`None`
+ :param int amount: The number of seconds by which to adjust the
+ timestamp.
+ :return: ``None``
"""
if not isinstance(amount, int):
raise TypeError("amount must be an integer")
@@ -1209,7 +1208,7 @@ class X509(object):
Adjust the timestamp on which the certificate starts being valid.
:param amount: The number of seconds by which to adjust the timestamp.
- :return: :py:const:`None`
+ :return: ``None``
"""
if not isinstance(amount, int):
raise TypeError("amount must be an integer")
@@ -1221,9 +1220,8 @@ class X509(object):
"""
Check whether the certificate has expired.
- :return: :py:const:`True` if the certificate has expired,
- :py:const:`False` otherwise.
- :rtype: :py:class:`bool`
+ :return: ``True`` if the certificate has expired, ``False`` otherwise.
+ :rtype: bool
"""
time_string = _native(self.get_notAfter())
not_after = datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
@@ -1243,8 +1241,8 @@ class X509(object):
YYYYMMDDhhmmss+hhmm
YYYYMMDDhhmmss-hhmm
- :return: A timestamp string, or :py:const:`None` if there is none.
- :rtype: :py:class:`bytes` or :py:const:`None`
+ :return: A timestamp string, or ``None`` if there is none.
+ :rtype: bytes or NoneType
"""
return self._get_boundary_time(_lib.X509_get_notBefore)
@@ -1261,10 +1259,8 @@ class X509(object):
YYYYMMDDhhmmss+hhmm
YYYYMMDDhhmmss-hhmm
- :param when: A timestamp string.
- :type when: :py:class:`bytes`
-
- :return: :py:const:`None`
+ :param bytes when: A timestamp string.
+ :return: ``None``
"""
return self._set_boundary_time(_lib.X509_get_notBefore, when)
@@ -1278,8 +1274,8 @@ class X509(object):
YYYYMMDDhhmmss+hhmm
YYYYMMDDhhmmss-hhmm
- :return: A timestamp string, or :py:const:`None` if there is none.
- :rtype: :py:class:`bytes` or :py:const:`None`
+ :return: A timestamp string, or ``None`` if there is none.
+ :rtype: bytes or NoneType
"""
return self._get_boundary_time(_lib.X509_get_notAfter)
@@ -1293,10 +1289,8 @@ class X509(object):
YYYYMMDDhhmmss+hhmm
YYYYMMDDhhmmss-hhmm
- :param when: A timestamp string.
- :type when: :py:class:`bytes`
-
- :return: :py:const:`None`
+ :param bytes when: A timestamp string.
+ :return: ``None``
"""
return self._set_boundary_time(_lib.X509_get_notAfter, when)
@@ -1342,7 +1336,7 @@ class X509(object):
:param issuer: The issuer.
:type issuer: :py:class:`X509Name`
- :return: :py:const:`None`
+ :return: ``None``
"""
return self._set_name(_lib.X509_set_issuer_name, issuer)
@@ -1367,7 +1361,7 @@ class X509(object):
:param subject: The subject.
:type subject: :py:class:`X509Name`
- :return: :py:const:`None`
+ :return: ``None``
"""
return self._set_name(_lib.X509_set_subject_name, subject)
@@ -1388,7 +1382,7 @@ class X509(object):
:param extensions: The extensions to add.
:type extensions: An iterable of :py:class:`X509Extension` objects.
- :return: :py:const:`None`
+ :return: ``None``
"""
for ext in extensions:
if not isinstance(ext, X509Extension):
@@ -1425,9 +1419,41 @@ class X509(object):
X509Type = X509
+class X509StoreFlags(object):
+ """
+ Flags for X509 verification, used to change the behavior of
+ :class:`X509Store`.
+
+ See `OpenSSL Verification Flags`_ for details.
+
+ .. _OpenSSL Verification Flags:
+ https://www.openssl.org/docs/manmaster/crypto/X509_VERIFY_PARAM_set_flags.html
+ """
+ CRL_CHECK = _lib.X509_V_FLAG_CRL_CHECK
+ CRL_CHECK_ALL = _lib.X509_V_FLAG_CRL_CHECK_ALL
+ IGNORE_CRITICAL = _lib.X509_V_FLAG_IGNORE_CRITICAL
+ X509_STRICT = _lib.X509_V_FLAG_X509_STRICT
+ ALLOW_PROXY_CERTS = _lib.X509_V_FLAG_ALLOW_PROXY_CERTS
+ POLICY_CHECK = _lib.X509_V_FLAG_POLICY_CHECK
+ EXPLICIT_POLICY = _lib.X509_V_FLAG_EXPLICIT_POLICY
+ INHIBIT_MAP = _lib.X509_V_FLAG_INHIBIT_MAP
+ NOTIFY_POLICY = _lib.X509_V_FLAG_NOTIFY_POLICY
+ CHECK_SS_SIGNATURE = _lib.X509_V_FLAG_CHECK_SS_SIGNATURE
+ CB_ISSUER_CHECK = _lib.X509_V_FLAG_CB_ISSUER_CHECK
+
+
class X509Store(object):
"""
- An X509 certificate store.
+ An X.509 store.
+
+ An X.509 store is used to describe a context in which to verify a
+ certificate. A description of a context may include a set of certificates
+ to trust, a set of certificate revocation lists, verification flags and
+ more.
+
+ An X.509 store, being only a description, cannot be used by itself to
+ verify a certificate. To carry out the actual verification process, see
+ :class:`X509StoreContext`.
"""
def __init__(self):
@@ -1436,21 +1462,60 @@ class X509Store(object):
def add_cert(self, cert):
"""
- Adds the certificate :py:data:`cert` to this store.
+ Adds a trusted certificate to this store.
- This is the Python equivalent of OpenSSL's ``X509_STORE_add_cert``.
+ Adding a certificate with this method adds this certificate as a
+ *trusted* certificate.
:param X509 cert: The certificate to add to this store.
- :raises TypeError: If the certificate is not an :py:class:`X509`.
+ :raises TypeError: If the certificate is not an :class:`X509`.
:raises Error: If OpenSSL was unhappy with your certificate.
- :return: :py:data:`None` if the certificate was added successfully.
+ :return: ``None`` if the certificate was added successfully.
"""
if not isinstance(cert, X509):
raise TypeError()
- result = _lib.X509_STORE_add_cert(self._store, cert._x509)
- if not result:
- _raise_current_error()
+ _openssl_assert(_lib.X509_STORE_add_cert(self._store, cert._x509) != 0)
+
+ def add_crl(self, crl):
+ """
+ Add a certificate revocation list to this store.
+
+ The certificate revocation lists added to a store will only be used if
+ the associated flags are configured to check certificate revocation
+ lists.
+
+ .. versionadded:: 16.1.0
+
+ :param CRL crl: The certificate revocation list to add to this store.
+ :return: ``None`` if the certificate revocation list was added
+ successfully.
+ """
+ _openssl_assert(_lib.X509_STORE_add_crl(self._store, crl._crl) != 0)
+
+ def set_flags(self, flags):
+ """
+ Set verification flags to this store.
+
+ Verification flags can be combined by oring them together.
+
+ .. note::
+
+ Setting a verification flag sometimes requires clients to add
+ additional information to the store, otherwise a suitable error will
+ be raised.
+
+ For example, in setting flags to enable CRL checking a
+ suitable CRL must be added to the store otherwise an error will be
+ raised.
+
+ .. versionadded:: 16.1.0
+
+ :param int flags: The verification flags to set on this store.
+ See :class:`X509StoreFlags` for available constants.
+ :return: ``None`` if the verification flags were successfully set.
+ """
+ _openssl_assert(_lib.X509_STORE_set_flags(self._store, flags) != 0)
X509StoreType = X509Store
@@ -1474,28 +1539,17 @@ class X509StoreContext(object):
"""
An X.509 store context.
- An :py:class:`X509StoreContext` is used to define some of the criteria for
- certificate verification. The information encapsulated in this object
- includes, but is not limited to, a set of trusted certificates,
- verification parameters, and revoked certificates.
-
- .. note::
-
- Currently, one can only set the trusted certificates on an
- :py:class:`X509StoreContext`. Future versions of pyOpenSSL will expose
- verification parameters and certificate revocation lists.
+ An X.509 store context is used to carry out the actual verification process
+ of a certificate in a described context. For describing such a context, see
+ :class:`X509Store`.
:ivar _store_ctx: The underlying X509_STORE_CTX structure used by this
instance. It is dynamically allocated and automatically garbage
collected.
-
:ivar _store: See the ``store`` ``__init__`` parameter.
-
:ivar _cert: See the ``certificate`` ``__init__`` parameter.
-
:param X509Store store: The certificates which will be trusted for the
purposes of any verifications.
-
:param X509 certificate: The certificate to be verified.
"""
@@ -1506,7 +1560,7 @@ class X509StoreContext(object):
self._cert = certificate
# Make the store context available for use after instantiating this
# class by initializing it now. Per testing, subsequent calls to
- # :py:meth:`_init` have no adverse affect.
+ # :meth:`_init` have no adverse affect.
self._init()
def _init(self):
@@ -1523,8 +1577,7 @@ class X509StoreContext(object):
"""
Internally cleans up the store context.
- The store context can then be reused with a new call to
- :py:meth:`_init`.
+ The store context can then be reused with a new call to :meth:`_init`.
"""
_lib.X509_STORE_CTX_cleanup(self._store_ctx)
@@ -1552,12 +1605,12 @@ class X509StoreContext(object):
def set_store(self, store):
"""
- Set the context's trust store.
+ Set the context's X.509 store.
.. versionadded:: 0.15
- :param X509Store store: The certificates which will be trusted for the
- purposes of any *future* verifications.
+ :param X509Store store: The store description which will be used for
+ the purposes of any *future* verifications.
"""
self._store = store
@@ -1567,14 +1620,12 @@ class X509StoreContext(object):
.. versionadded:: 0.15
- :param store_ctx: The :py:class:`X509StoreContext` to verify.
-
:raises X509StoreContextError: If an error occurred when validating a
certificate in the context. Sets ``certificate`` attribute to
indicate which certificate caused the error.
"""
# Always re-initialize the store context in case
- # :py:meth:`verify_certificate` is called multiple times.
+ # :meth:`verify_certificate` is called multiple times.
self._init()
ret = _lib.X509_verify_cert(self._store_ctx)
self._cleanup()
@@ -1588,8 +1639,7 @@ def load_certificate(type, buffer):
:param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)
- :param buffer: The buffer the certificate is stored in
- :type buffer: :py:class:`bytes`
+ :param bytes buffer: The buffer the certificate is stored in
:return: The X509 object
"""
@@ -1678,7 +1728,7 @@ def dump_privatekey(type, pkey, cipher=None, passphrase=None):
the passphrase to use, or a callback for providing the
passphrase.
:return: The buffer with the dumped key in
- :rtype: :py:data:`bytes`
+ :rtype: bytes
"""
bio = _new_mem_buf()
@@ -1746,10 +1796,9 @@ class Revoked(object):
The serial number is formatted as a hexadecimal number encoded in
ASCII.
- :param hex_str: The new serial number.
- :type hex_str: :py:class:`bytes`
+ :param bytes hex_str: The new serial number.
- :return: :py:const:`None`
+ :return: ``None``
"""
bignum_serial = _ffi.gc(_lib.BN_new(), _lib.BN_free)
bignum_ptr = _ffi.new("BIGNUM**")
@@ -1771,7 +1820,7 @@ class Revoked(object):
ASCII.
:return: The serial number.
- :rtype: :py:class:`bytes`
+ :rtype: bytes
"""
bio = _new_mem_buf()
@@ -1794,16 +1843,16 @@ class Revoked(object):
"""
Set the reason of this revocation.
- If :py:data:`reason` is :py:const:`None`, delete the reason instead.
+ If :data:`reason` is ``None``, delete the reason instead.
:param reason: The reason string.
- :type reason: :py:class:`bytes` or :py:class:`NoneType`
+ :type reason: :class:`bytes` or :class:`NoneType`
- :return: :py:const:`None`
+ :return: ``None``
.. seealso::
- :py:meth:`all_reasons`, which gives you a list of all supported
+ :meth:`all_reasons`, which gives you a list of all supported
reasons which you might pass to this method.
"""
if reason is None:
@@ -1837,12 +1886,12 @@ class Revoked(object):
"""
Get the reason of this revocation.
- :return: The reason, or :py:const:`None` if there is none.
- :rtype: :py:class:`bytes` or :py:class:`NoneType`
+ :return: The reason, or ``None`` if there is none.
+ :rtype: bytes or NoneType
.. seealso::
- :py:meth:`all_reasons`, which gives you a list of all supported
+ :meth:`all_reasons`, which gives you a list of all supported
reasons this method might return.
"""
for i in range(_lib.X509_REVOKED_get_ext_count(self._revoked)):
@@ -1870,7 +1919,7 @@ class Revoked(object):
strings.
:return: A list of reason strings.
- :rtype: :py:class:`list` of :py:class:`bytes`
+ :rtype: :class:`list` of :class:`bytes`
"""
return self._crl_reasons[:]
@@ -1878,9 +1927,9 @@ class Revoked(object):
"""
Set the revocation timestamp.
- :param when: The timestamp of the revocation, as ASN.1 GENERALIZEDTIME.
- :type when: :py:class:`bytes`
- :return: :py:const:`None`
+ :param bytes when: The timestamp of the revocation,
+ as ASN.1 GENERALIZEDTIME.
+ :return: ``None``
"""
dt = _lib.X509_REVOKED_get0_revocationDate(self._revoked)
return _set_asn1_time(dt, when)
@@ -1890,7 +1939,7 @@ class Revoked(object):
Get the revocation timestamp.
:return: The timestamp of the revocation, as ASN.1 GENERALIZEDTIME.
- :rtype: :py:class:`bytes`
+ :rtype: bytes
"""
dt = _lib.X509_REVOKED_get0_revocationDate(self._revoked)
return _get_asn1_time(dt)
@@ -1902,9 +1951,6 @@ class CRL(object):
"""
def __init__(self):
- """
- Create a new empty certificate revocation list.
- """
crl = _lib.X509_CRL_new()
self._crl = _ffi.gc(crl, _lib.X509_CRL_free)
@@ -1916,7 +1962,7 @@ class CRL(object):
That means it's okay to mutate them: it won't affect this CRL.
:return: The revocations in this CRL.
- :rtype: :py:class:`tuple` of :py:class:`Revocation`
+ :rtype: :class:`tuple` of :class:`Revocation`
"""
results = []
revoked_stack = _lib.X509_CRL_get_REVOKED(self._crl)
@@ -1937,10 +1983,8 @@ class CRL(object):
means it's okay to mutate it after adding: it won't affect
this CRL.
- :param revoked: The new revocation.
- :type revoked: :class:`Revoked`
-
- :return: :py:const:`None`
+ :param Revoked revoked: The new revocation.
+ :return: ``None``
"""
copy = _lib.Cryptography_X509_REVOKED_dup(revoked._revoked)
if copy == _ffi.NULL:
@@ -1952,27 +1996,109 @@ class CRL(object):
# TODO: This is untested.
_raise_current_error()
- def export(self, cert, key, type=FILETYPE_PEM, days=100,
- digest=_UNSPECIFIED):
+ def get_issuer(self):
"""
- Export a CRL as a string.
+ Get the CRL's issuer.
- :param cert: The certificate used to sign the CRL.
- :type cert: :py:class:`X509`
+ .. versionadded:: 16.1.0
- :param key: The key used to sign the CRL.
- :type key: :py:class:`PKey`
+ :rtype: X509Name
+ """
+ _issuer = _lib.X509_NAME_dup(_lib.X509_CRL_get_issuer(self._crl))
+ _openssl_assert(_issuer != _ffi.NULL)
+ _issuer = _ffi.gc(_issuer, _lib.X509_NAME_free)
+ issuer = X509Name.__new__(X509Name)
+ issuer._name = _issuer
+ return issuer
+
+ def set_version(self, version):
+ """
+ Set the CRL version.
- :param type: The export format, either :py:data:`FILETYPE_PEM`,
- :py:data:`FILETYPE_ASN1`, or :py:data:`FILETYPE_TEXT`.
+ .. versionadded:: 16.1.0
- :param int days: The number of days until the next update of this CRL.
+ :param int version: The version of the CRL.
+ :return: ``None``
+ """
+ _openssl_assert(_lib.X509_CRL_set_version(self._crl, version) != 0)
+
+ def _set_boundary_time(self, which, when):
+ return _set_asn1_time(which(self._crl), when)
+
+ def set_lastUpdate(self, when):
+ """
+ Set when the CRL was last updated.
+
+ The timestamp is formatted as an ASN.1 GENERALIZEDTIME::
+
+ YYYYMMDDhhmmssZ
+ YYYYMMDDhhmmss+hhmm
+ YYYYMMDDhhmmss-hhmm
+
+ .. versionadded:: 16.1.0
+
+ :param bytes when: A timestamp string.
+ :return: ``None``
+ """
+ return self._set_boundary_time(_lib.X509_CRL_get_lastUpdate, when)
+
+ def set_nextUpdate(self, when):
+ """
+ Set when the CRL will next be udpated.
+
+ The timestamp is formatted as an ASN.1 GENERALIZEDTIME::
+ YYYYMMDDhhmmssZ
+ YYYYMMDDhhmmss+hhmm
+ YYYYMMDDhhmmss-hhmm
+
+ .. versionadded:: 16.1.0
+
+ :param bytes when: A timestamp string.
+ :return: ``None``
+ """
+ return self._set_boundary_time(_lib.X509_CRL_get_nextUpdate, when)
+
+ def sign(self, issuer_cert, issuer_key, digest):
+ """
+ Sign the CRL.
+
+ Signing a CRL enables clients to associate the CRL itself with an
+ issuer. Before a CRL is meaningful to other OpenSSL functions, it must
+ be signed by an issuer.
+
+ This method implicitly sets the issuer's name based on the issuer
+ certificate and private key used to sign the CRL.
+
+ .. versionadded:: 16.1.0
+
+ :param X509 issuer_cert: The issuer's certificate.
+ :param PKey issuer_key: The issuer's private key.
+ :param bytes digest: The digest method to sign the CRL with.
+ """
+ digest_obj = _lib.EVP_get_digestbyname(digest)
+ _openssl_assert(digest_obj != _ffi.NULL)
+ _lib.X509_CRL_set_issuer_name(
+ self._crl, _lib.X509_get_subject_name(issuer_cert._x509))
+ _lib.X509_CRL_sort(self._crl)
+ result = _lib.X509_CRL_sign(self._crl, issuer_key._pkey, digest_obj)
+ _openssl_assert(result != 0)
+
+ def export(self, cert, key, type=FILETYPE_PEM, days=100,
+ digest=_UNSPECIFIED):
+ """
+ Export the CRL as a string.
+
+ :param X509 cert: The certificate used to sign the CRL.
+ :param PKey key: The key used to sign the CRL.
+ :param int type: The export format, either :data:`FILETYPE_PEM`,
+ :data:`FILETYPE_ASN1`, or :data:`FILETYPE_TEXT`.
+ :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"``).
-
- :return: :py:data:`bytes`
+ :rtype: bytes
"""
+
if not isinstance(cert, X509):
raise TypeError("cert must be an X509 instance")
if not isinstance(key, PKey):
@@ -2106,7 +2232,7 @@ class PKCS12(object):
:param cert: The new certificate, or :py:const:`None` to unset it.
:type cert: :py:class:`X509` or :py:const:`None`
- :return: :py:const:`None`
+ :return: ``None``
"""
if not isinstance(cert, X509):
raise TypeError("cert must be an X509 instance")
@@ -2128,7 +2254,7 @@ class PKCS12(object):
:param pkey: The new private key, or :py:const:`None` to unset it.
:type pkey: :py:class:`PKey` or :py:const:`None`
- :return: :py:const:`None`
+ :return: ``None``
"""
if not isinstance(pkey, PKey):
raise TypeError("pkey must be a PKey instance")
@@ -2153,7 +2279,7 @@ class PKCS12(object):
them.
:type cacerts: An iterable of :py:class:`X509` or :py:const:`None`
- :return: :py:const:`None`
+ :return: ``None``
"""
if cacerts is None:
self._cacerts = None
@@ -2173,7 +2299,7 @@ class PKCS12(object):
:param name: The new friendly name, or :py:const:`None` to unset.
:type name: :py:class:`bytes` or :py:const:`None`
- :return: :py:const:`None`
+ :return: ``None``
"""
if name is None:
self._friendlyname = None
@@ -2275,7 +2401,7 @@ class NetscapeSPKI(object):
:param digest: The message digest to use.
:type digest: :py:class:`bytes`
- :return: :py:const:`None`
+ :return: ``None``
"""
if pkey._only_public:
raise ValueError("Key has only public part")
@@ -2345,7 +2471,7 @@ class NetscapeSPKI(object):
Set the public key of the certificate
:param pkey: The public key
- :return: :py:const:`None`
+ :return: ``None``
"""
set_result = _lib.NETSCAPE_SPKI_set_pubkey(self._spki, pkey._pkey)
if not set_result:
@@ -2589,8 +2715,7 @@ def verify(cert, signature, data, digest):
:param signature: signature returned by sign function
:param data: data to be verified
:param digest: message digest to use
- :return: :py:const:`None` if the signature is correct, raise exception
- otherwise
+ :return: ``None`` if the signature is correct, raise exception otherwise.
"""
data = _text_to_bytes_and_warn("data", data)
@@ -2626,7 +2751,7 @@ def dump_crl(type, crl):
:param CRL crl: The CRL to dump.
:return: The buffer with the CRL.
- :rtype: :data:`bytes`
+ :rtype: bytes
"""
bio = _new_mem_buf()
diff --git a/tests/test_crypto.py b/tests/test_crypto.py
index eeea306..e13f1e4 100644
--- a/tests/test_crypto.py
+++ b/tests/test_crypto.py
@@ -21,7 +21,11 @@ from six import u, b, binary_type
from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, Error, PKey, PKeyType
from OpenSSL.crypto import X509, X509Type, X509Name, X509NameType
from OpenSSL.crypto import (
- X509Store, X509StoreType, X509StoreContext, X509StoreContextError
+ X509Store,
+ X509StoreFlags,
+ X509StoreType,
+ X509StoreContext,
+ X509StoreContextError
)
from OpenSSL.crypto import X509Req, X509ReqType
from OpenSSL.crypto import X509Extension, X509ExtensionType
@@ -3127,6 +3131,15 @@ class CRLTests(TestCase):
cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM)
pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)
+ root_cert = load_certificate(FILETYPE_PEM, root_cert_pem)
+ root_key = load_privatekey(FILETYPE_PEM, root_key_pem)
+ intermediate_cert = load_certificate(FILETYPE_PEM, intermediate_cert_pem)
+ intermediate_key = load_privatekey(FILETYPE_PEM, intermediate_key_pem)
+ intermediate_server_cert = load_certificate(
+ FILETYPE_PEM, intermediate_server_cert_pem)
+ intermediate_server_key = load_privatekey(
+ FILETYPE_PEM, intermediate_server_key_pem)
+
def test_construction(self):
"""
Confirm we can create :py:obj:`OpenSSL.crypto.CRL`. Check
@@ -3407,6 +3420,15 @@ class CRLTests(TestCase):
"""
self.assertRaises(Error, load_crl, FILETYPE_PEM, b"hello, world")
+ def test_get_issuer(self):
+ """
+ Load a known CRL and assert its issuer's common name is
+ what we expect from the encoded crlData string.
+ """
+ crl = load_crl(FILETYPE_PEM, crlData)
+ self.assertTrue(isinstance(crl.get_issuer(), X509Name))
+ self.assertEqual(crl.get_issuer().CN, 'Testing Root CA')
+
def test_dump_crl(self):
"""
The dumped CRL matches the original input.
@@ -3415,6 +3437,71 @@ class CRLTests(TestCase):
buf = dump_crl(FILETYPE_PEM, crl)
assert buf == crlData
+ def _make_test_crl(self, issuer_cert, issuer_key, certs=()):
+ """
+ Create a CRL.
+
+ :param list[X509] certs: A list of certificates to revoke.
+ :rtype: CRL
+ """
+ crl = CRL()
+ for cert in certs:
+ revoked = Revoked()
+ # FIXME: This string splicing is an unfortunate implementation
+ # detail that has been reported in
+ # https://github.com/pyca/pyopenssl/issues/258
+ serial = hex(cert.get_serial_number())[2:].encode('utf-8')
+ revoked.set_serial(serial)
+ revoked.set_reason(b'unspecified')
+ revoked.set_rev_date(b'20140601000000Z')
+ crl.add_revoked(revoked)
+ crl.set_version(1)
+ crl.set_lastUpdate(b'20140601000000Z')
+ crl.set_nextUpdate(b'20180601000000Z')
+ crl.sign(issuer_cert, issuer_key, digest=b'sha512')
+ return crl
+
+ def test_verify_with_revoked(self):
+ """
+ :func:`verify_certificate` raises error when an intermediate
+ certificate is revoked.
+ """
+ store = X509Store()
+ store.add_cert(self.root_cert)
+ store.add_cert(self.intermediate_cert)
+ root_crl = self._make_test_crl(
+ self.root_cert, self.root_key, certs=[self.intermediate_cert])
+ intermediate_crl = self._make_test_crl(
+ self.intermediate_cert, self.intermediate_key, certs=[])
+ store.add_crl(root_crl)
+ store.add_crl(intermediate_crl)
+ store.set_flags(
+ X509StoreFlags.CRL_CHECK | X509StoreFlags.CRL_CHECK_ALL)
+ store_ctx = X509StoreContext(store, self.intermediate_server_cert)
+ e = self.assertRaises(
+ X509StoreContextError, store_ctx.verify_certificate)
+ self.assertEqual(e.args[0][2], 'certificate revoked')
+
+ def test_verify_with_missing_crl(self):
+ """
+ :func:`verify_certificate` raises error when an intermediate
+ certificate's CRL is missing.
+ """
+ store = X509Store()
+ store.add_cert(self.root_cert)
+ store.add_cert(self.intermediate_cert)
+ root_crl = self._make_test_crl(
+ self.root_cert, self.root_key, certs=[self.intermediate_cert])
+ store.add_crl(root_crl)
+ store.set_flags(
+ X509StoreFlags.CRL_CHECK | X509StoreFlags.CRL_CHECK_ALL)
+ store_ctx = X509StoreContext(store, self.intermediate_server_cert)
+ e = self.assertRaises(
+ X509StoreContextError, store_ctx.verify_certificate)
+ self.assertEqual(e.args[0][2], 'unable to get certificate CRL')
+ self.assertEqual(
+ e.certificate.get_subject().CN, 'intermediate-service')
+
class X509StoreContextTests(TestCase):
"""