Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable use of CRL (and more) in verify context. #483

Merged
merged 18 commits into from
Jun 5, 2016
Next Next commit
Enable use of CRL (and more) in verify context.
  • Loading branch information
sholsapp authored and Dan Sully committed Jun 4, 2016
commit f38ea826687504262e03efd495d6381ccb4bd19e
212 changes: 172 additions & 40 deletions src/OpenSSL/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -1427,18 +1427,43 @@ def get_extension(self, index):

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
:py:class:`X509StoreContext`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no :py: prefixes in new code please

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you prefer this change? Just to "X509StoreContext" ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just

:class:`X509StoreContext`

unless i’m missing something?

"""

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
# FLAG_INHIBIT_ANY = _lib.X509_V_FLAG_FLAG_INHIBIT_ANY
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are these commented out lines about?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see the original diff, #281 - I can remove them if you'd like.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a comment from the #281 thread.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after talking to Paul: kill the commented out and also the comment above. if users ask for it we can still add them conditionally (i.e. getattr() style).

INHIBIT_MAP = _lib.X509_V_FLAG_INHIBIT_MAP
NOTIFY_POLICY = _lib.X509_V_FLAG_NOTIFY_POLICY
# USE_DELTAS = _lib.X509_V_FLAG_USE_DELTAS
CHECK_SS_SIGNATURE = _lib.X509_V_FLAG_CHECK_SS_SIGNATURE
CB_ISSUER_CHECK = _lib.X509_V_FLAG_CB_ISSUER_CHECK
# NO_ALT_CHAINS = _lib.X509_V_FLAG_NO_ALT_CHAINS

def __init__(self):
store = _lib.X509_STORE_new()
self._store = _ffi.gc(store, _lib.X509_STORE_free)

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`.
Expand All @@ -1452,6 +1477,44 @@ def add_cert(self, cert):
if not result:
_raise_current_error()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since you’re touching the code around here you could _openssl_assert-ize this as well :)


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:: 0.17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no such thing as pyOpenSSL 0.17. The next release will be 16.1.0.


:param CRL crl: The certificate revocation list to add to this store.
:return: :py:data:`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):
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this docstring needs to point to the new flags class in some sane manner. Maybe within :param int 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:: 0.17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no such thing as pyOpenSSL 0.17. The next release will be 16.1.0.


:param int flags: The verification flags to set on this store.
:return: :py:data:`None` if the verification flags were successfully set.
"""
_openssl_assert(_lib.X509_STORE_set_flags(self._store, flags) != 0)


X509StoreType = X509Store

Expand All @@ -1474,29 +1537,19 @@ 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
:py:class:`X509Store`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no :py: prefixes in new code please


: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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no empty line please

"""

def __init__(self, store, certificate):
Expand Down Expand Up @@ -1552,12 +1605,12 @@ def _exception_from_context(self):

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

Expand All @@ -1567,8 +1620,6 @@ def verify_certificate(self):

.. 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.
Expand Down Expand Up @@ -1902,9 +1953,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)

Expand Down Expand Up @@ -1937,9 +1985,7 @@ def add_revoked(self, revoked):
means it's okay to mutate it after adding: it won't affect
this CRL.

:param revoked: The new revocation.
:type revoked: :class:`Revoked`

:param Revoked revoked: The new revocation.
:return: :py:const:`None`
"""
copy = _lib.Cryptography_X509_REVOKED_dup(revoked._revoked)
Expand All @@ -1952,27 +1998,113 @@ def add_revoked(self, revoked):
# 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:: 0.17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no such thing as pyOpenSSL 0.17. The next release will be 16.1.0.


:param key: The key used to sign the CRL.
:type key: :py:class:`PKey`
:return: :py:class:`X509Name`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no :py: prefixes in new code please

"""
_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

:param type: The export format, either :py:data:`FILETYPE_PEM`,
:py:data:`FILETYPE_ASN1`, or :py:data:`FILETYPE_TEXT`.
def set_version(self, version):
"""
Set the CRL version.

:param int days: The number of days until the next update of this CRL.
.. versionadded:: 0.17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no such thing as pyOpenSSL 0.17. The next release will be 16.1.0.


:param int version: The version of the CRL.
:return: :py:const:`None`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just

``None``

is fine

"""
_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:: 0.17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no such thing as pyOpenSSL 0.17. The next release will be 16.1.0.


:param bytes when: A timestamp string.
:return: :py:const:`None`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just

``None``

is fine

"""
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:: 0.17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no such thing as pyOpenSSL 0.17. The next release will be 16.1.0.


:param bytes when: A timestamp string.
:return: :py:const:`None`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just

``None``

is fine

"""
return self._set_boundary_time(_lib.X509_CRL_get_nextUpdate, when)

def sign(self, issuer_cert, issuer_key, digest='sha1'):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a new API and there's no backwards compatibility concern I'd prefer this to not have a default arg for 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:: 0.17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no such thing as pyOpenSSL 0.17. The next release will be 16.1.0.


:param X509 issuer_cert: The issuer's certificate.
:param PKey issuer_key: The issuer's private key.
:param str digest: The digest method to sign the CRL with.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok sorry one last one: we don't do str in pyOpenSSL. This has to be bytes.

"""
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)
sign_result = _lib.X509_CRL_sign(self._crl, issuer_key._pkey, digest_obj)
_openssl_assert(sign_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 :py:data:`FILETYPE_PEM`,
:py:data:`FILETYPE_ASN1`, or :py: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`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please make this an

:rtype: bytes

and it’s fine to have an empty line between the params and the return docs.

"""

# TODO: fix this function to use functionality added in version 0.16.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i’m not sure what this means? also there’s no 0.16.

# Doing this without changing the public API is tricky. Checking if
# lastUpdate, nextUpdate, issuer or signing has happened is hard to do
# without generating a segmentation fault.

if not isinstance(cert, X509):
raise TypeError("cert must be an X509 instance")
if not isinstance(key, PKey):
Expand Down
Loading