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

check crl/ocsp against *now* by default instead of context.moment #7

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 45 additions & 17 deletions certvalidator/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,14 @@ class ValidationContext():
# period of certificates
moment = None

# When using cached OCSP and CRL responses it is necessary to check their
# validity against the time which they were acquired. This time is assumed
# to be identical to the passed in moment. This value will only be set to
# moment if a moment is given and either a CRL or OCSP list is also given.
revocation_moment = None

# By default, any CRLs or OCSP responses that are passed to the constructor
# are chccked. If _allow_fetching is True, any CRLs or OCSP responses that
# are checked. If _allow_fetching is True, any CRLs or OCSP responses that
# can be downloaded will also be checked. The next two attributes change
# that behavior.

Expand Down Expand Up @@ -134,11 +140,13 @@ def __init__(self, trust_roots=None, extra_trust_roots=None, other_certs=None,
be ignored.

:param moment:
If certificate validation should be performed based on a date and
time other than right now. A datetime.datetime object with a tzinfo
value. If this parameter is specified, then the only way to check
OCSP and CRL responses is to pass them via the crls and ocsps
parameters. Can not be combined with allow_fetching=True.
A datetime.datetime object with a tzinfo value.
To be used when certificate validation should be performed based on
a date and time other than right now. This is common when validating
signatures that include timestamps. Works in combination with
either passed OCSP and/or CRL responses *OR* allow_fetching=True,
but *NOT* both. If allow_fetching=True the certificate revocation
will be checked against the current time and not the passed moment.

:param crls:
None or a list/tuple of asn1crypto.crl.CertificateList objects of
Expand Down Expand Up @@ -179,6 +187,7 @@ def __init__(self, trust_roots=None, extra_trust_roots=None, other_certs=None,
considered weak. Valid options include: "md2", "md5", "sha1"
"""

_revocation_moment = datetime.now(timezone.utc)
if crls is not None:
if not isinstance(crls, (list, tuple)):
raise TypeError(pretty_message(
Expand Down Expand Up @@ -227,15 +236,7 @@ def __init__(self, trust_roots=None, extra_trust_roots=None, other_certs=None,
new_ocsps.append(ocsp_)
ocsps = new_ocsps

if moment is not None:
if allow_fetching:
raise ValueError(pretty_message(
'''
allow_fetching must be False when moment is specified
'''
))

elif not allow_fetching and crls is None and ocsps is None and revocation_mode != "soft-fail":
if not allow_fetching and crls is None and ocsps is None and revocation_mode != "soft-fail":
raise ValueError(pretty_message(
'''
revocation_mode is "%s" and allow_fetching is False, however
Expand Down Expand Up @@ -278,6 +279,15 @@ def __init__(self, trust_roots=None, extra_trust_roots=None, other_certs=None,
attribute is not set to a valid timezone
'''
))
if moment is not None and (crls or ocsps):
if allow_fetching:
raise ValueError(pretty_message(
'''
allow_fetching must be False when moment and (OCSPs or CRLs)
are specified
'''
))
_revocation_moment = moment

if revocation_mode not in set(['soft-fail', 'hard-fail', 'require']):
raise ValueError(pretty_message(
Expand Down Expand Up @@ -336,6 +346,7 @@ def __init__(self, trust_roots=None, extra_trust_roots=None, other_certs=None,
)

self.moment = moment
self.revocation_moment = _revocation_moment

self._validate_map = {}
self._crl_issuer_map = {}
Expand Down Expand Up @@ -363,6 +374,13 @@ def __init__(self, trust_roots=None, extra_trust_roots=None, other_certs=None,
self._soft_fail_exceptions = []
self.weak_hash_algos = weak_hash_algos

@property
def allow_fetching(self):
"""
Check if allow fetching is allowed
"""
return self._allow_fetching

@property
def crls(self):
"""
Expand Down Expand Up @@ -468,7 +486,11 @@ def retrieve_crls(self, cert):
self._fetched_crls[cert.issuer_serial] = []
if self._revocation_mode == "soft-fail":
self._soft_fail_exceptions.append(e)
raise SoftFailError()
if hasattr(e, 'reason'):
if isinstance(e.reason, str):
raise SoftFailError(e.reason, [e.reason])
else:
raise SoftFailError(str(e.reason), [str(e.reason)])
else:
raise

Expand Down Expand Up @@ -507,7 +529,13 @@ def retrieve_ocsps(self, cert, issuer):
self._fetched_ocsps[cert.issuer_serial] = []
if self._revocation_mode == "soft-fail":
self._soft_fail_exceptions.append(e)
raise SoftFailError()
if hasattr(e, 'reason'):
if isinstance(e.reason, str):
raise SoftFailError(e.reason, [e.reason])
else:
raise SoftFailError(str(e.reason), [str(e.reason)])
else:
raise SoftFailError(e.strerror, [e.strerror])
else:
raise

Expand Down
4 changes: 3 additions & 1 deletion certvalidator/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ def failures(self):

class SoftFailError(Exception):

pass
@property
def failures(self):
return self.args[1]


class ValidationError(Exception):
Expand Down
4 changes: 4 additions & 0 deletions certvalidator/ocsp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ def fetch(cert, issuer, hash_algo='sha1', nonce=True, user_agent=None, timeout=1
response = urlopen(request, ocsp_request.dump(), timeout)
ocsp_response = ocsp.OCSPResponse.load(response.read())
request_nonce = ocsp_request.nonce_value
if ocsp_response['response_status'].native == 'unauthorized':
raise errors.OCSPNoMatchesError(
'Unable to verify OCSP response since the responder returned unauthorized'
)
response_nonce = ocsp_response.nonce_value
if request_nonce and response_nonce and request_nonce.native != response_nonce.native:
raise errors.OCSPValidationError(
Expand Down
Loading