We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Invalid OCSP response for /etc/letsencrypt/live/.../cert.pem: param thisUpdate is in the future..
If you're having trouble using Certbot and aren't sure you've found a bug or
request for a new feature, please first try asking for help at
https://community.letsencrypt.org/. There is a much larger community there of
people familiar with the project who will be able to more quickly answer your
questions.

## My operating system is (include version):
CentOS release 6.10 (Final)

## I installed Certbot with (certbot-auto, OS package manager, pip, etc):
certbot-auto

## I ran this command and it produced this output:
certbot-auto --no-self-upgrade

## Certbot's behavior differed from what I expected because:
Invalid OCSP response for /etc/letsencrypt/live/.../cert.pem: param thisUpdate is in the future..

## Here is a Certbot log showing the issue (if available):
###### Logs are stored in `/var/log/letsencrypt` by default. Feel free to redact domains, e-mail and IP addresses as you see fit.
```
2019-03-09 20:39:02,485:DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): ocsp.int-x3.letsencrypt.org:80
2019-03-09 20:39:02,581:DEBUG:urllib3.connectionpool:http://ocsp.int-x3.letsencrypt.org:80 "POST / HTTP/1.1" 200 527
2019-03-09 20:39:02,587:ERROR:certbot.ocsp:Invalid OCSP response for /etc/letsencrypt/live/.../cert.pem: param thisUpdate is in the future..
2019-03-09 20:39:02,594:DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): ocsp.int-x3.letsencrypt.org:80
2019-03-09 20:39:02,703:DEBUG:urllib3.connectionpool:http://ocsp.int-x3.letsencrypt.org:80 "POST / HTTP/1.1" 200 527
2019-03-09 20:39:02,705:ERROR:certbot.ocsp:Invalid OCSP response for /etc/letsencrypt/live/.../cert.pem: param thisUpdate is in the future..
/
```

This has happened on two computers in two very different infrastructure systems

## Here is the relevant nginx server block or Apache virtualhost for the domain I am configuring:


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `certbot/ocsp.py`
Content:
```
1 """Tools for checking certificate revocation."""
2 import logging
3 import re
4 from datetime import datetime, timedelta
5 from subprocess import Popen, PIPE
6 
7 try:
8     # Only cryptography>=2.5 has ocsp module
9     # and signature_hash_algorithm attribute in OCSPResponse class
10     from cryptography.x509 import ocsp  # pylint: disable=import-error
11     getattr(ocsp.OCSPResponse, 'signature_hash_algorithm')
12 except (ImportError, AttributeError):  # pragma: no cover
13     ocsp = None  # type: ignore
14 from cryptography import x509
15 from cryptography.hazmat.backends import default_backend
16 from cryptography.hazmat.primitives import serialization
17 from cryptography.hazmat.primitives import hashes  # type: ignore
18 from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature
19 import requests
20 
21 from acme.magic_typing import Optional, Tuple  # pylint: disable=unused-import, no-name-in-module
22 from certbot import crypto_util
23 from certbot import errors
24 from certbot import util
25 
26 logger = logging.getLogger(__name__)
27 
28 
29 class RevocationChecker(object):
30     """This class figures out OCSP checking on this system, and performs it."""
31 
32     def __init__(self, enforce_openssl_binary_usage=False):
33         self.broken = False
34         self.use_openssl_binary = enforce_openssl_binary_usage or not ocsp
35 
36         if self.use_openssl_binary:
37             if not util.exe_exists("openssl"):
38                 logger.info("openssl not installed, can't check revocation")
39                 self.broken = True
40                 return
41 
42             # New versions of openssl want -header var=val, old ones want -header var val
43             test_host_format = Popen(["openssl", "ocsp", "-header", "var", "val"],
44                                      stdout=PIPE, stderr=PIPE, universal_newlines=True)
45             _out, err = test_host_format.communicate()
46             if "Missing =" in err:
47                 self.host_args = lambda host: ["Host=" + host]
48             else:
49                 self.host_args = lambda host: ["Host", host]
50 
51     def ocsp_revoked(self, cert_path, chain_path):
52         # type: (str, str) -> bool
53         """Get revoked status for a particular cert version.
54 
55         .. todo:: Make this a non-blocking call
56 
57         :param str cert_path: Path to certificate
58         :param str chain_path: Path to intermediate cert
59         :returns: True if revoked; False if valid or the check failed
60         :rtype: bool
61 
62         """
63         if self.broken:
64             return False
65 
66         url, host = _determine_ocsp_server(cert_path)
67         if not host or not url:
68             return False
69 
70         if self.use_openssl_binary:
71             return self._check_ocsp_openssl_bin(cert_path, chain_path, host, url)
72         else:
73             return _check_ocsp_cryptography(cert_path, chain_path, url)
74 
75     def _check_ocsp_openssl_bin(self, cert_path, chain_path, host, url):
76         # type: (str, str, str, str) -> bool
77         # jdkasten thanks "Bulletproof SSL and TLS - Ivan Ristic" for documenting this!
78         cmd = ["openssl", "ocsp",
79                "-no_nonce",
80                "-issuer", chain_path,
81                "-cert", cert_path,
82                "-url", url,
83                "-CAfile", chain_path,
84                "-verify_other", chain_path,
85                "-trust_other",
86                "-header"] + self.host_args(host)
87         logger.debug("Querying OCSP for %s", cert_path)
88         logger.debug(" ".join(cmd))
89         try:
90             output, err = util.run_script(cmd, log=logger.debug)
91         except errors.SubprocessError:
92             logger.info("OCSP check failed for %s (are we offline?)", cert_path)
93             return False
94         return _translate_ocsp_query(cert_path, output, err)
95 
96 
97 def _determine_ocsp_server(cert_path):
98     # type: (str) -> Tuple[Optional[str], Optional[str]]
99     """Extract the OCSP server host from a certificate.
100 
101     :param str cert_path: Path to the cert we're checking OCSP for
102     :rtype tuple:
103     :returns: (OCSP server URL or None, OCSP server host or None)
104 
105     """
106     with open(cert_path, 'rb') as file_handler:
107         cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend())
108     try:
109         extension = cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess)
110         ocsp_oid = x509.AuthorityInformationAccessOID.OCSP
111         descriptions = [description for description in extension.value
112                         if description.access_method == ocsp_oid]
113 
114         url = descriptions[0].access_location.value
115     except (x509.ExtensionNotFound, IndexError):
116         logger.info("Cannot extract OCSP URI from %s", cert_path)
117         return None, None
118 
119     url = url.rstrip()
120     host = url.partition("://")[2].rstrip("/")
121 
122     if host:
123         return url, host
124     else:
125         logger.info("Cannot process OCSP host from URL (%s) in cert at %s", url, cert_path)
126         return None, None
127 
128 
129 def _check_ocsp_cryptography(cert_path, chain_path, url):
130     # type: (str, str, str) -> bool
131     # Retrieve OCSP response
132     with open(chain_path, 'rb') as file_handler:
133         issuer = x509.load_pem_x509_certificate(file_handler.read(), default_backend())
134     with open(cert_path, 'rb') as file_handler:
135         cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend())
136     builder = ocsp.OCSPRequestBuilder()
137     builder = builder.add_certificate(cert, issuer, hashes.SHA1())
138     request = builder.build()
139     request_binary = request.public_bytes(serialization.Encoding.DER)
140     try:
141         response = requests.post(url, data=request_binary,
142                                  headers={'Content-Type': 'application/ocsp-request'})
143     except requests.exceptions.RequestException:
144         logger.info("OCSP check failed for %s (are we offline?)", cert_path, exc_info=True)
145         return False
146     if response.status_code != 200:
147         logger.info("OCSP check failed for %s (HTTP status: %d)", cert_path, response.status_code)
148         return False
149 
150     response_ocsp = ocsp.load_der_ocsp_response(response.content)
151 
152     # Check OCSP response validity
153     if response_ocsp.response_status != ocsp.OCSPResponseStatus.SUCCESSFUL:
154         logger.error("Invalid OCSP response status for %s: %s",
155                      cert_path, response_ocsp.response_status)
156         return False
157 
158     # Check OCSP signature
159     try:
160         _check_ocsp_response(response_ocsp, request, issuer)
161     except UnsupportedAlgorithm as e:
162         logger.error(str(e))
163     except errors.Error as e:
164         logger.error(str(e))
165     except InvalidSignature:
166         logger.error('Invalid signature on OCSP response for %s', cert_path)
167     except AssertionError as error:
168         logger.error('Invalid OCSP response for %s: %s.', cert_path, str(error))
169     else:
170         # Check OCSP certificate status
171         logger.debug("OCSP certificate status for %s is: %s",
172                      cert_path, response_ocsp.certificate_status)
173         return response_ocsp.certificate_status == ocsp.OCSPCertStatus.REVOKED
174 
175     return False
176 
177 
178 def _check_ocsp_response(response_ocsp, request_ocsp, issuer_cert):
179     """Verify that the OCSP is valid for serveral criterias"""
180     # Assert OCSP response corresponds to the certificate we are talking about
181     if response_ocsp.serial_number != request_ocsp.serial_number:
182         raise AssertionError('the certificate in response does not correspond '
183                              'to the certificate in request')
184 
185     # Assert signature is valid
186     _check_ocsp_response_signature(response_ocsp, issuer_cert)
187 
188     # Assert issuer in response is the expected one
189     if (not isinstance(response_ocsp.hash_algorithm, type(request_ocsp.hash_algorithm))
190             or response_ocsp.issuer_key_hash != request_ocsp.issuer_key_hash
191             or response_ocsp.issuer_name_hash != request_ocsp.issuer_name_hash):
192         raise AssertionError('the issuer does not correspond to issuer of the certificate.')
193 
194     # In following checks, two situations can occur:
195     #   * nextUpdate is set, and requirement is thisUpdate < now < nextUpdate
196     #   * nextUpdate is not set, and requirement is thisUpdate < now
197     # NB1: We add a validity period tolerance to handle clock time inconsistencies,
198     #      value is 5 min like for OpenSSL.
199     # NB2: Another check is to verify that thisUpdate is not too old, it is optional
200     #      for OpenSSL, so we do not do it here.
201     # See OpenSSL implementation as a reference:
202     # https://github.com/openssl/openssl/blob/ef45aa14c5af024fcb8bef1c9007f3d1c115bd85/crypto/ocsp/ocsp_cl.c#L338-L391
203     now = datetime.now()
204     if not response_ocsp.this_update:
205         raise AssertionError('param thisUpdate is not set.')
206     if response_ocsp.this_update > now + timedelta(minutes=5):
207         raise AssertionError('param thisUpdate is in the future.')
208     if response_ocsp.next_update and response_ocsp.next_update < now - timedelta(minutes=5):
209         raise AssertionError('param nextUpdate is in the past.')
210 
211 
212 def _check_ocsp_response_signature(response_ocsp, issuer_cert):
213     """Verify an OCSP response signature against certificate issuer"""
214     # Following line may raise UnsupportedAlgorithm
215     chosen_hash = response_ocsp.signature_hash_algorithm
216     crypto_util.verify_signed_payload(issuer_cert.public_key(), response_ocsp.signature,
217                                       response_ocsp.tbs_response_bytes, chosen_hash)
218 
219 
220 def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors):
221     """Parse openssl's weird output to work out what it means."""
222 
223     states = ("good", "revoked", "unknown")
224     patterns = [r"{0}: (WARNING.*)?{1}".format(cert_path, s) for s in states]
225     good, revoked, unknown = (re.search(p, ocsp_output, flags=re.DOTALL) for p in patterns)
226 
227     warning = good.group(1) if good else None
228 
229     if ("Response verify OK" not in ocsp_errors) or (good and warning) or unknown:
230         logger.info("Revocation status for %s is unknown", cert_path)
231         logger.debug("Uncertain output:\n%s\nstderr:\n%s", ocsp_output, ocsp_errors)
232         return False
233     elif good and not warning:
234         return False
235     elif revoked:
236         warning = revoked.group(1)
237         if warning:
238             logger.info("OCSP revocation warning: %s", warning)
239         return True
240     else:
241         logger.warning("Unable to properly parse OCSP output: %s\nstderr:%s",
242                        ocsp_output, ocsp_errors)
243         return False
244
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/certbot/ocsp.py b/certbot/ocsp.py
--- a/certbot/ocsp.py
+++ b/certbot/ocsp.py
@@ -200,7 +200,7 @@
     #      for OpenSSL, so we do not do it here.
     # See OpenSSL implementation as a reference:
     # https://github.com/openssl/openssl/blob/ef45aa14c5af024fcb8bef1c9007f3d1c115bd85/crypto/ocsp/ocsp_cl.c#L338-L391
-    now = datetime.now()
+    now = datetime.utcnow()  # thisUpdate/nextUpdate are expressed in UTC/GMT time zone
     if not response_ocsp.this_update:
         raise AssertionError('param thisUpdate is not set.')
     if response_ocsp.this_update > now + timedelta(minutes=5):

{"golden_diff": "diff --git a/certbot/ocsp.py b/certbot/ocsp.py\n--- a/certbot/ocsp.py\n+++ b/certbot/ocsp.py\n@@ -200,7 +200,7 @@\n     #      for OpenSSL, so we do not do it here.\n     # See OpenSSL implementation as a reference:\n     # https://github.com/openssl/openssl/blob/ef45aa14c5af024fcb8bef1c9007f3d1c115bd85/crypto/ocsp/ocsp_cl.c#L338-L391\n-    now = datetime.now()\n+    now = datetime.utcnow()  # thisUpdate/nextUpdate are expressed in UTC/GMT time zone\n     if not response_ocsp.this_update:\n         raise AssertionError('param thisUpdate is not set.')\n     if response_ocsp.this_update > now + timedelta(minutes=5):\n", "issue": "Invalid OCSP response for /etc/letsencrypt/live/.../cert.pem: param thisUpdate is in the future..\nIf you're having trouble using Certbot and aren't sure you've found a bug or\r\nrequest for a new feature, please first try asking for help at\r\nhttps://community.letsencrypt.org/. There is a much larger community there of\r\npeople familiar with the project who will be able to more quickly answer your\r\nquestions.\r\n\r\n## My operating system is (include version):\r\nCentOS release 6.10 (Final)\r\n\r\n## I installed Certbot with (certbot-auto, OS package manager, pip, etc):\r\ncertbot-auto\r\n\r\n## I ran this command and it produced this output:\r\ncertbot-auto --no-self-upgrade\r\n\r\n## Certbot's behavior differed from what I expected because:\r\nInvalid OCSP response for /etc/letsencrypt/live/.../cert.pem: param thisUpdate is in the future..\r\n\r\n## Here is a Certbot log showing the issue (if available):\r\n###### Logs are stored in `/var/log/letsencrypt` by default. Feel free to redact domains, e-mail and IP addresses as you see fit.\r\n```\r\n2019-03-09 20:39:02,485:DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): ocsp.int-x3.letsencrypt.org:80\r\n2019-03-09 20:39:02,581:DEBUG:urllib3.connectionpool:http://ocsp.int-x3.letsencrypt.org:80 \"POST / HTTP/1.1\" 200 527\r\n2019-03-09 20:39:02,587:ERROR:certbot.ocsp:Invalid OCSP response for /etc/letsencrypt/live/.../cert.pem: param thisUpdate is in the future..\r\n2019-03-09 20:39:02,594:DEBUG:urllib3.connectionpool:Starting new HTTP connection (1): ocsp.int-x3.letsencrypt.org:80\r\n2019-03-09 20:39:02,703:DEBUG:urllib3.connectionpool:http://ocsp.int-x3.letsencrypt.org:80 \"POST / HTTP/1.1\" 200 527\r\n2019-03-09 20:39:02,705:ERROR:certbot.ocsp:Invalid OCSP response for /etc/letsencrypt/live/.../cert.pem: param thisUpdate is in the future..\r\n/\r\n```\r\n\r\nThis has happened on two computers in two very different infrastructure systems\r\n\r\n## Here is the relevant nginx server block or Apache virtualhost for the domain I am configuring:\r\n\n", "before_files": [{"content": "\"\"\"Tools for checking certificate revocation.\"\"\"\nimport logging\nimport re\nfrom datetime import datetime, timedelta\nfrom subprocess import Popen, PIPE\n\ntry:\n    # Only cryptography>=2.5 has ocsp module\n    # and signature_hash_algorithm attribute in OCSPResponse class\n    from cryptography.x509 import ocsp  # pylint: disable=import-error\n    getattr(ocsp.OCSPResponse, 'signature_hash_algorithm')\nexcept (ImportError, AttributeError):  # pragma: no cover\n    ocsp = None  # type: ignore\nfrom cryptography import x509\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import serialization\nfrom cryptography.hazmat.primitives import hashes  # type: ignore\nfrom cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature\nimport requests\n\nfrom acme.magic_typing import Optional, Tuple  # pylint: disable=unused-import, no-name-in-module\nfrom certbot import crypto_util\nfrom certbot import errors\nfrom certbot import util\n\nlogger = logging.getLogger(__name__)\n\n\nclass RevocationChecker(object):\n    \"\"\"This class figures out OCSP checking on this system, and performs it.\"\"\"\n\n    def __init__(self, enforce_openssl_binary_usage=False):\n        self.broken = False\n        self.use_openssl_binary = enforce_openssl_binary_usage or not ocsp\n\n        if self.use_openssl_binary:\n            if not util.exe_exists(\"openssl\"):\n                logger.info(\"openssl not installed, can't check revocation\")\n                self.broken = True\n                return\n\n            # New versions of openssl want -header var=val, old ones want -header var val\n            test_host_format = Popen([\"openssl\", \"ocsp\", \"-header\", \"var\", \"val\"],\n                                     stdout=PIPE, stderr=PIPE, universal_newlines=True)\n            _out, err = test_host_format.communicate()\n            if \"Missing =\" in err:\n                self.host_args = lambda host: [\"Host=\" + host]\n            else:\n                self.host_args = lambda host: [\"Host\", host]\n\n    def ocsp_revoked(self, cert_path, chain_path):\n        # type: (str, str) -> bool\n        \"\"\"Get revoked status for a particular cert version.\n\n        .. todo:: Make this a non-blocking call\n\n        :param str cert_path: Path to certificate\n        :param str chain_path: Path to intermediate cert\n        :returns: True if revoked; False if valid or the check failed\n        :rtype: bool\n\n        \"\"\"\n        if self.broken:\n            return False\n\n        url, host = _determine_ocsp_server(cert_path)\n        if not host or not url:\n            return False\n\n        if self.use_openssl_binary:\n            return self._check_ocsp_openssl_bin(cert_path, chain_path, host, url)\n        else:\n            return _check_ocsp_cryptography(cert_path, chain_path, url)\n\n    def _check_ocsp_openssl_bin(self, cert_path, chain_path, host, url):\n        # type: (str, str, str, str) -> bool\n        # jdkasten thanks \"Bulletproof SSL and TLS - Ivan Ristic\" for documenting this!\n        cmd = [\"openssl\", \"ocsp\",\n               \"-no_nonce\",\n               \"-issuer\", chain_path,\n               \"-cert\", cert_path,\n               \"-url\", url,\n               \"-CAfile\", chain_path,\n               \"-verify_other\", chain_path,\n               \"-trust_other\",\n               \"-header\"] + self.host_args(host)\n        logger.debug(\"Querying OCSP for %s\", cert_path)\n        logger.debug(\" \".join(cmd))\n        try:\n            output, err = util.run_script(cmd, log=logger.debug)\n        except errors.SubprocessError:\n            logger.info(\"OCSP check failed for %s (are we offline?)\", cert_path)\n            return False\n        return _translate_ocsp_query(cert_path, output, err)\n\n\ndef _determine_ocsp_server(cert_path):\n    # type: (str) -> Tuple[Optional[str], Optional[str]]\n    \"\"\"Extract the OCSP server host from a certificate.\n\n    :param str cert_path: Path to the cert we're checking OCSP for\n    :rtype tuple:\n    :returns: (OCSP server URL or None, OCSP server host or None)\n\n    \"\"\"\n    with open(cert_path, 'rb') as file_handler:\n        cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend())\n    try:\n        extension = cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess)\n        ocsp_oid = x509.AuthorityInformationAccessOID.OCSP\n        descriptions = [description for description in extension.value\n                        if description.access_method == ocsp_oid]\n\n        url = descriptions[0].access_location.value\n    except (x509.ExtensionNotFound, IndexError):\n        logger.info(\"Cannot extract OCSP URI from %s\", cert_path)\n        return None, None\n\n    url = url.rstrip()\n    host = url.partition(\"://\")[2].rstrip(\"/\")\n\n    if host:\n        return url, host\n    else:\n        logger.info(\"Cannot process OCSP host from URL (%s) in cert at %s\", url, cert_path)\n        return None, None\n\n\ndef _check_ocsp_cryptography(cert_path, chain_path, url):\n    # type: (str, str, str) -> bool\n    # Retrieve OCSP response\n    with open(chain_path, 'rb') as file_handler:\n        issuer = x509.load_pem_x509_certificate(file_handler.read(), default_backend())\n    with open(cert_path, 'rb') as file_handler:\n        cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend())\n    builder = ocsp.OCSPRequestBuilder()\n    builder = builder.add_certificate(cert, issuer, hashes.SHA1())\n    request = builder.build()\n    request_binary = request.public_bytes(serialization.Encoding.DER)\n    try:\n        response = requests.post(url, data=request_binary,\n                                 headers={'Content-Type': 'application/ocsp-request'})\n    except requests.exceptions.RequestException:\n        logger.info(\"OCSP check failed for %s (are we offline?)\", cert_path, exc_info=True)\n        return False\n    if response.status_code != 200:\n        logger.info(\"OCSP check failed for %s (HTTP status: %d)\", cert_path, response.status_code)\n        return False\n\n    response_ocsp = ocsp.load_der_ocsp_response(response.content)\n\n    # Check OCSP response validity\n    if response_ocsp.response_status != ocsp.OCSPResponseStatus.SUCCESSFUL:\n        logger.error(\"Invalid OCSP response status for %s: %s\",\n                     cert_path, response_ocsp.response_status)\n        return False\n\n    # Check OCSP signature\n    try:\n        _check_ocsp_response(response_ocsp, request, issuer)\n    except UnsupportedAlgorithm as e:\n        logger.error(str(e))\n    except errors.Error as e:\n        logger.error(str(e))\n    except InvalidSignature:\n        logger.error('Invalid signature on OCSP response for %s', cert_path)\n    except AssertionError as error:\n        logger.error('Invalid OCSP response for %s: %s.', cert_path, str(error))\n    else:\n        # Check OCSP certificate status\n        logger.debug(\"OCSP certificate status for %s is: %s\",\n                     cert_path, response_ocsp.certificate_status)\n        return response_ocsp.certificate_status == ocsp.OCSPCertStatus.REVOKED\n\n    return False\n\n\ndef _check_ocsp_response(response_ocsp, request_ocsp, issuer_cert):\n    \"\"\"Verify that the OCSP is valid for serveral criterias\"\"\"\n    # Assert OCSP response corresponds to the certificate we are talking about\n    if response_ocsp.serial_number != request_ocsp.serial_number:\n        raise AssertionError('the certificate in response does not correspond '\n                             'to the certificate in request')\n\n    # Assert signature is valid\n    _check_ocsp_response_signature(response_ocsp, issuer_cert)\n\n    # Assert issuer in response is the expected one\n    if (not isinstance(response_ocsp.hash_algorithm, type(request_ocsp.hash_algorithm))\n            or response_ocsp.issuer_key_hash != request_ocsp.issuer_key_hash\n            or response_ocsp.issuer_name_hash != request_ocsp.issuer_name_hash):\n        raise AssertionError('the issuer does not correspond to issuer of the certificate.')\n\n    # In following checks, two situations can occur:\n    #   * nextUpdate is set, and requirement is thisUpdate < now < nextUpdate\n    #   * nextUpdate is not set, and requirement is thisUpdate < now\n    # NB1: We add a validity period tolerance to handle clock time inconsistencies,\n    #      value is 5 min like for OpenSSL.\n    # NB2: Another check is to verify that thisUpdate is not too old, it is optional\n    #      for OpenSSL, so we do not do it here.\n    # See OpenSSL implementation as a reference:\n    # https://github.com/openssl/openssl/blob/ef45aa14c5af024fcb8bef1c9007f3d1c115bd85/crypto/ocsp/ocsp_cl.c#L338-L391\n    now = datetime.now()\n    if not response_ocsp.this_update:\n        raise AssertionError('param thisUpdate is not set.')\n    if response_ocsp.this_update > now + timedelta(minutes=5):\n        raise AssertionError('param thisUpdate is in the future.')\n    if response_ocsp.next_update and response_ocsp.next_update < now - timedelta(minutes=5):\n        raise AssertionError('param nextUpdate is in the past.')\n\n\ndef _check_ocsp_response_signature(response_ocsp, issuer_cert):\n    \"\"\"Verify an OCSP response signature against certificate issuer\"\"\"\n    # Following line may raise UnsupportedAlgorithm\n    chosen_hash = response_ocsp.signature_hash_algorithm\n    crypto_util.verify_signed_payload(issuer_cert.public_key(), response_ocsp.signature,\n                                      response_ocsp.tbs_response_bytes, chosen_hash)\n\n\ndef _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors):\n    \"\"\"Parse openssl's weird output to work out what it means.\"\"\"\n\n    states = (\"good\", \"revoked\", \"unknown\")\n    patterns = [r\"{0}: (WARNING.*)?{1}\".format(cert_path, s) for s in states]\n    good, revoked, unknown = (re.search(p, ocsp_output, flags=re.DOTALL) for p in patterns)\n\n    warning = good.group(1) if good else None\n\n    if (\"Response verify OK\" not in ocsp_errors) or (good and warning) or unknown:\n        logger.info(\"Revocation status for %s is unknown\", cert_path)\n        logger.debug(\"Uncertain output:\\n%s\\nstderr:\\n%s\", ocsp_output, ocsp_errors)\n        return False\n    elif good and not warning:\n        return False\n    elif revoked:\n        warning = revoked.group(1)\n        if warning:\n            logger.info(\"OCSP revocation warning: %s\", warning)\n        return True\n    else:\n        logger.warning(\"Unable to properly parse OCSP output: %s\\nstderr:%s\",\n                       ocsp_output, ocsp_errors)\n        return False\n", "path": "certbot/ocsp.py"}], "after_files": [{"content": "\"\"\"Tools for checking certificate revocation.\"\"\"\nimport logging\nimport re\nfrom datetime import datetime, timedelta\nfrom subprocess import Popen, PIPE\n\ntry:\n    # Only cryptography>=2.5 has ocsp module\n    # and signature_hash_algorithm attribute in OCSPResponse class\n    from cryptography.x509 import ocsp  # pylint: disable=import-error\n    getattr(ocsp.OCSPResponse, 'signature_hash_algorithm')\nexcept (ImportError, AttributeError):  # pragma: no cover\n    ocsp = None  # type: ignore\nfrom cryptography import x509\nfrom cryptography.hazmat.backends import default_backend\nfrom cryptography.hazmat.primitives import serialization\nfrom cryptography.hazmat.primitives import hashes  # type: ignore\nfrom cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature\nimport requests\n\nfrom acme.magic_typing import Optional, Tuple  # pylint: disable=unused-import, no-name-in-module\nfrom certbot import crypto_util\nfrom certbot import errors\nfrom certbot import util\n\nlogger = logging.getLogger(__name__)\n\n\nclass RevocationChecker(object):\n    \"\"\"This class figures out OCSP checking on this system, and performs it.\"\"\"\n\n    def __init__(self, enforce_openssl_binary_usage=False):\n        self.broken = False\n        self.use_openssl_binary = enforce_openssl_binary_usage or not ocsp\n\n        if self.use_openssl_binary:\n            if not util.exe_exists(\"openssl\"):\n                logger.info(\"openssl not installed, can't check revocation\")\n                self.broken = True\n                return\n\n            # New versions of openssl want -header var=val, old ones want -header var val\n            test_host_format = Popen([\"openssl\", \"ocsp\", \"-header\", \"var\", \"val\"],\n                                     stdout=PIPE, stderr=PIPE, universal_newlines=True)\n            _out, err = test_host_format.communicate()\n            if \"Missing =\" in err:\n                self.host_args = lambda host: [\"Host=\" + host]\n            else:\n                self.host_args = lambda host: [\"Host\", host]\n\n    def ocsp_revoked(self, cert_path, chain_path):\n        # type: (str, str) -> bool\n        \"\"\"Get revoked status for a particular cert version.\n\n        .. todo:: Make this a non-blocking call\n\n        :param str cert_path: Path to certificate\n        :param str chain_path: Path to intermediate cert\n        :returns: True if revoked; False if valid or the check failed\n        :rtype: bool\n\n        \"\"\"\n        if self.broken:\n            return False\n\n        url, host = _determine_ocsp_server(cert_path)\n        if not host or not url:\n            return False\n\n        if self.use_openssl_binary:\n            return self._check_ocsp_openssl_bin(cert_path, chain_path, host, url)\n        else:\n            return _check_ocsp_cryptography(cert_path, chain_path, url)\n\n    def _check_ocsp_openssl_bin(self, cert_path, chain_path, host, url):\n        # type: (str, str, str, str) -> bool\n        # jdkasten thanks \"Bulletproof SSL and TLS - Ivan Ristic\" for documenting this!\n        cmd = [\"openssl\", \"ocsp\",\n               \"-no_nonce\",\n               \"-issuer\", chain_path,\n               \"-cert\", cert_path,\n               \"-url\", url,\n               \"-CAfile\", chain_path,\n               \"-verify_other\", chain_path,\n               \"-trust_other\",\n               \"-header\"] + self.host_args(host)\n        logger.debug(\"Querying OCSP for %s\", cert_path)\n        logger.debug(\" \".join(cmd))\n        try:\n            output, err = util.run_script(cmd, log=logger.debug)\n        except errors.SubprocessError:\n            logger.info(\"OCSP check failed for %s (are we offline?)\", cert_path)\n            return False\n        return _translate_ocsp_query(cert_path, output, err)\n\n\ndef _determine_ocsp_server(cert_path):\n    # type: (str) -> Tuple[Optional[str], Optional[str]]\n    \"\"\"Extract the OCSP server host from a certificate.\n\n    :param str cert_path: Path to the cert we're checking OCSP for\n    :rtype tuple:\n    :returns: (OCSP server URL or None, OCSP server host or None)\n\n    \"\"\"\n    with open(cert_path, 'rb') as file_handler:\n        cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend())\n    try:\n        extension = cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess)\n        ocsp_oid = x509.AuthorityInformationAccessOID.OCSP\n        descriptions = [description for description in extension.value\n                        if description.access_method == ocsp_oid]\n\n        url = descriptions[0].access_location.value\n    except (x509.ExtensionNotFound, IndexError):\n        logger.info(\"Cannot extract OCSP URI from %s\", cert_path)\n        return None, None\n\n    url = url.rstrip()\n    host = url.partition(\"://\")[2].rstrip(\"/\")\n\n    if host:\n        return url, host\n    else:\n        logger.info(\"Cannot process OCSP host from URL (%s) in cert at %s\", url, cert_path)\n        return None, None\n\n\ndef _check_ocsp_cryptography(cert_path, chain_path, url):\n    # type: (str, str, str) -> bool\n    # Retrieve OCSP response\n    with open(chain_path, 'rb') as file_handler:\n        issuer = x509.load_pem_x509_certificate(file_handler.read(), default_backend())\n    with open(cert_path, 'rb') as file_handler:\n        cert = x509.load_pem_x509_certificate(file_handler.read(), default_backend())\n    builder = ocsp.OCSPRequestBuilder()\n    builder = builder.add_certificate(cert, issuer, hashes.SHA1())\n    request = builder.build()\n    request_binary = request.public_bytes(serialization.Encoding.DER)\n    try:\n        response = requests.post(url, data=request_binary,\n                                 headers={'Content-Type': 'application/ocsp-request'})\n    except requests.exceptions.RequestException:\n        logger.info(\"OCSP check failed for %s (are we offline?)\", cert_path, exc_info=True)\n        return False\n    if response.status_code != 200:\n        logger.info(\"OCSP check failed for %s (HTTP status: %d)\", cert_path, response.status_code)\n        return False\n\n    response_ocsp = ocsp.load_der_ocsp_response(response.content)\n\n    # Check OCSP response validity\n    if response_ocsp.response_status != ocsp.OCSPResponseStatus.SUCCESSFUL:\n        logger.error(\"Invalid OCSP response status for %s: %s\",\n                     cert_path, response_ocsp.response_status)\n        return False\n\n    # Check OCSP signature\n    try:\n        _check_ocsp_response(response_ocsp, request, issuer)\n    except UnsupportedAlgorithm as e:\n        logger.error(str(e))\n    except errors.Error as e:\n        logger.error(str(e))\n    except InvalidSignature:\n        logger.error('Invalid signature on OCSP response for %s', cert_path)\n    except AssertionError as error:\n        logger.error('Invalid OCSP response for %s: %s.', cert_path, str(error))\n    else:\n        # Check OCSP certificate status\n        logger.debug(\"OCSP certificate status for %s is: %s\",\n                     cert_path, response_ocsp.certificate_status)\n        return response_ocsp.certificate_status == ocsp.OCSPCertStatus.REVOKED\n\n    return False\n\n\ndef _check_ocsp_response(response_ocsp, request_ocsp, issuer_cert):\n    \"\"\"Verify that the OCSP is valid for serveral criterias\"\"\"\n    # Assert OCSP response corresponds to the certificate we are talking about\n    if response_ocsp.serial_number != request_ocsp.serial_number:\n        raise AssertionError('the certificate in response does not correspond '\n                             'to the certificate in request')\n\n    # Assert signature is valid\n    _check_ocsp_response_signature(response_ocsp, issuer_cert)\n\n    # Assert issuer in response is the expected one\n    if (not isinstance(response_ocsp.hash_algorithm, type(request_ocsp.hash_algorithm))\n            or response_ocsp.issuer_key_hash != request_ocsp.issuer_key_hash\n            or response_ocsp.issuer_name_hash != request_ocsp.issuer_name_hash):\n        raise AssertionError('the issuer does not correspond to issuer of the certificate.')\n\n    # In following checks, two situations can occur:\n    #   * nextUpdate is set, and requirement is thisUpdate < now < nextUpdate\n    #   * nextUpdate is not set, and requirement is thisUpdate < now\n    # NB1: We add a validity period tolerance to handle clock time inconsistencies,\n    #      value is 5 min like for OpenSSL.\n    # NB2: Another check is to verify that thisUpdate is not too old, it is optional\n    #      for OpenSSL, so we do not do it here.\n    # See OpenSSL implementation as a reference:\n    # https://github.com/openssl/openssl/blob/ef45aa14c5af024fcb8bef1c9007f3d1c115bd85/crypto/ocsp/ocsp_cl.c#L338-L391\n    now = datetime.utcnow()  # thisUpdate/nextUpdate are expressed in UTC/GMT time zone\n    if not response_ocsp.this_update:\n        raise AssertionError('param thisUpdate is not set.')\n    if response_ocsp.this_update > now + timedelta(minutes=5):\n        raise AssertionError('param thisUpdate is in the future.')\n    if response_ocsp.next_update and response_ocsp.next_update < now - timedelta(minutes=5):\n        raise AssertionError('param nextUpdate is in the past.')\n\n\ndef _check_ocsp_response_signature(response_ocsp, issuer_cert):\n    \"\"\"Verify an OCSP response signature against certificate issuer\"\"\"\n    # Following line may raise UnsupportedAlgorithm\n    chosen_hash = response_ocsp.signature_hash_algorithm\n    crypto_util.verify_signed_payload(issuer_cert.public_key(), response_ocsp.signature,\n                                      response_ocsp.tbs_response_bytes, chosen_hash)\n\n\ndef _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors):\n    \"\"\"Parse openssl's weird output to work out what it means.\"\"\"\n\n    states = (\"good\", \"revoked\", \"unknown\")\n    patterns = [r\"{0}: (WARNING.*)?{1}\".format(cert_path, s) for s in states]\n    good, revoked, unknown = (re.search(p, ocsp_output, flags=re.DOTALL) for p in patterns)\n\n    warning = good.group(1) if good else None\n\n    if (\"Response verify OK\" not in ocsp_errors) or (good and warning) or unknown:\n        logger.info(\"Revocation status for %s is unknown\", cert_path)\n        logger.debug(\"Uncertain output:\\n%s\\nstderr:\\n%s\", ocsp_output, ocsp_errors)\n        return False\n    elif good and not warning:\n        return False\n    elif revoked:\n        warning = revoked.group(1)\n        if warning:\n            logger.info(\"OCSP revocation warning: %s\", warning)\n        return True\n    else:\n        logger.warning(\"Unable to properly parse OCSP output: %s\\nstderr:%s\",\n                       ocsp_output, ocsp_errors)\n        return False\n", "path": "certbot/ocsp.py"}]}