mirror of
https://github.com/certbot/certbot.git
synced 2026-08-02 19:31:51 +02:00
Simplify logic
This commit is contained in:
committed by
Adrien Ferrand
parent
c9da0b9120
commit
3c3928a690
@@ -1,15 +1,20 @@
|
|||||||
"""Module to handle the context of integration tests."""
|
"""Module to handle the context of integration tests."""
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from certbot_integration_tests.utils import certbot_call
|
import requests
|
||||||
|
|
||||||
|
from certbot_integration_tests.utils import certbot_call, misc, ocsp_server
|
||||||
|
from certbot_integration_tests.utils.constants import MOCK_OCSP_SERVER_PORT
|
||||||
|
|
||||||
|
|
||||||
class IntegrationTestsContext(object):
|
class IntegrationTestsContext(object):
|
||||||
"""General fixture describing a certbot integration tests context"""
|
"""General fixture describing a certbot integration tests context"""
|
||||||
def __init__(self, request):
|
def __init__(self, request):
|
||||||
|
self._subprocesses = []
|
||||||
self.request = request
|
self.request = request
|
||||||
|
|
||||||
if hasattr(request.config, 'slaveinput'): # Worker node
|
if hasattr(request.config, 'slaveinput'): # Worker node
|
||||||
@@ -51,6 +56,9 @@ class IntegrationTestsContext(object):
|
|||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
"""Cleanup the integration test context."""
|
"""Cleanup the integration test context."""
|
||||||
|
for process in self._subprocesses:
|
||||||
|
process.terminate()
|
||||||
|
process.wait()
|
||||||
shutil.rmtree(self.workspace)
|
shutil.rmtree(self.workspace)
|
||||||
|
|
||||||
def certbot(self, args, force_renew=True):
|
def certbot(self, args, force_renew=True):
|
||||||
@@ -77,3 +85,24 @@ class IntegrationTestsContext(object):
|
|||||||
:return: the well-formed domain suitable for redirection on
|
:return: the well-formed domain suitable for redirection on
|
||||||
"""
|
"""
|
||||||
return '{0}.{1}.wtf'.format(subdomain, self.worker_id)
|
return '{0}.{1}.wtf'.format(subdomain, self.worker_id)
|
||||||
|
|
||||||
|
def mock_ocsp_server(self, cert_path, ocsp_status):
|
||||||
|
"""
|
||||||
|
Start a mock OCSP server to check OSCP statuses with Pebble.
|
||||||
|
:param cert_path: the path to the cert whose OCSP status is checked
|
||||||
|
:param ocsp_status: the OCSP status to return
|
||||||
|
"""
|
||||||
|
root_url = self.directory_url.replace('/dir', '')
|
||||||
|
|
||||||
|
issuer_key_path = os.path.join(self.workspace, 'ocsp_key.pem')
|
||||||
|
issuer_cert_path = os.path.join(self.workspace, 'ocsp_cert.pem')
|
||||||
|
with open(issuer_key_path, 'w') as file_h:
|
||||||
|
misc.ignore_https_warnings()
|
||||||
|
file_h.write(requests.get(root_url + '/intermediate-key', verify=False).content)
|
||||||
|
with open(issuer_cert_path, 'w') as file_h:
|
||||||
|
misc.ignore_https_warnings()
|
||||||
|
file_h.write(requests.get(root_url + '/intermediate', verify=False).content)
|
||||||
|
|
||||||
|
process = subprocess.Popen([sys.executable, ocsp_server.__file__, cert_path,
|
||||||
|
issuer_cert_path, issuer_key_path, ocsp_status])
|
||||||
|
self._subprocesses.append(process)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from __future__ import print_function
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import requests
|
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
@@ -556,35 +555,26 @@ def test_ocsp_status_stale(context):
|
|||||||
|
|
||||||
def test_ocsp_status_live(context):
|
def test_ocsp_status_live(context):
|
||||||
"""Test retrieval of OCSP statuses for live config"""
|
"""Test retrieval of OCSP statuses for live config"""
|
||||||
mock_ocsp_server = None
|
|
||||||
if context.acme_server == 'pebble':
|
|
||||||
mock_ocsp_server = misc.mock_ocsp_server(context.directory_url, context.workspace)
|
|
||||||
|
|
||||||
cert = context.get_domain('ocsp-check')
|
cert = context.get_domain('ocsp-check')
|
||||||
cert_path = join(context.config_dir, 'live', cert, 'cert.pem')
|
cert_path = join(context.config_dir, 'live', cert, 'cert.pem')
|
||||||
|
|
||||||
try:
|
# OSCP 1: Check live certificate OCSP status (VALID)
|
||||||
# OSCP 1: Check live certificate OCSP status (VALID)
|
context.certbot(['--domains', cert])
|
||||||
context.certbot(['--domains', cert])
|
if context.acme_server == 'pebble':
|
||||||
if mock_ocsp_server:
|
context.mock_ocsp_server(cert_path, 'GOOD')
|
||||||
requests.put(mock_ocsp_server[1], json={'cert_path': cert_path, 'ocsp_status': 'GOOD'})
|
output = context.certbot(['certificates'])
|
||||||
output = context.certbot(['certificates'])
|
|
||||||
|
|
||||||
assert output.count('VALID') == 1, 'Expected {0} to be VALID'.format(cert)
|
assert output.count('VALID') == 1, 'Expected {0} to be VALID'.format(cert)
|
||||||
assert output.count('EXPIRED') == 0, 'Did not expect {0} to be EXPIRED'.format(cert)
|
assert output.count('EXPIRED') == 0, 'Did not expect {0} to be EXPIRED'.format(cert)
|
||||||
|
|
||||||
# OSCP 2: Check live certificate OCSP status (REVOKED)
|
# OSCP 2: Check live certificate OCSP status (REVOKED)
|
||||||
context.certbot(['revoke', '--cert-name', cert, '--no-delete-after-revoke'])
|
context.certbot(['revoke', '--cert-name', cert, '--no-delete-after-revoke'])
|
||||||
if mock_ocsp_server:
|
if context.acme_server == 'pebble':
|
||||||
requests.put(mock_ocsp_server[1], json={'cert_path': cert_path, 'ocsp_status': 'REVOKED'})
|
context.mock_ocsp_server(cert_path, 'REVOKED')
|
||||||
# Sometimes in oldest tests (using openssl binary and not cryptography), the OCSP status is
|
# Sometimes in oldest tests (using openssl binary and not cryptography), the OCSP status is
|
||||||
# not seen immediately by Certbot as invalid. Waiting few seconds solves this transient issue.
|
# not seen immediately by Certbot as invalid. Waiting few seconds solves this transient issue.
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
output = context.certbot(['certificates'])
|
output = context.certbot(['certificates'])
|
||||||
|
|
||||||
assert output.count('INVALID') == 1, 'Expected {0} to be INVALID'.format(cert)
|
assert output.count('INVALID') == 1, 'Expected {0} to be INVALID'.format(cert)
|
||||||
assert output.count('REVOKED') == 1, 'Expected {0} to be REVOKED'.format(cert)
|
assert output.count('REVOKED') == 1, 'Expected {0} to be REVOKED'.format(cert)
|
||||||
finally:
|
|
||||||
if mock_ocsp_server:
|
|
||||||
mock_ocsp_server[0].terminate()
|
|
||||||
mock_ocsp_server[0].wait()
|
|
||||||
|
|||||||
@@ -23,10 +23,6 @@ from cryptography.hazmat.primitives.asymmetric import ec
|
|||||||
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption
|
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption
|
||||||
from six.moves import socketserver, SimpleHTTPServer
|
from six.moves import socketserver, SimpleHTTPServer
|
||||||
|
|
||||||
|
|
||||||
from certbot_integration_tests.utils import ocsp_server
|
|
||||||
from certbot_integration_tests.utils.constants import MOCK_OCSP_SERVER_PORT
|
|
||||||
|
|
||||||
RSA_KEY_TYPE = 'rsa'
|
RSA_KEY_TYPE = 'rsa'
|
||||||
ECDSA_KEY_TYPE = 'ecdsa'
|
ECDSA_KEY_TYPE = 'ecdsa'
|
||||||
|
|
||||||
@@ -41,7 +37,7 @@ def check_until_timeout(url):
|
|||||||
for _ in range(0, 150):
|
for _ in range(0, 150):
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
try:
|
try:
|
||||||
_ignore_https_warnings()
|
ignore_https_warnings()
|
||||||
if requests.get(url, verify=False).status_code == 200:
|
if requests.get(url, verify=False).status_code == 200:
|
||||||
return
|
return
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
@@ -286,28 +282,8 @@ def load_sample_data_path(workspace):
|
|||||||
return copied
|
return copied
|
||||||
|
|
||||||
|
|
||||||
def mock_ocsp_server(directory_url, workspace):
|
def ignore_https_warnings():
|
||||||
root_url = directory_url.replace('/dir', '')
|
"""Invoke logic to ignore HTTPS warning while invoking request"""
|
||||||
|
|
||||||
key_path = os.path.join(workspace, 'ocsp_key.pem')
|
|
||||||
cert_path = os.path.join(workspace, 'ocsp_cert.pem')
|
|
||||||
with open(key_path, 'w') as file_h:
|
|
||||||
_ignore_https_warnings()
|
|
||||||
file_h.write(requests.get(root_url + '/intermediate-key', verify=False).content)
|
|
||||||
with open(cert_path, 'w') as file_h:
|
|
||||||
_ignore_https_warnings()
|
|
||||||
file_h.write(requests.get(root_url + '/intermediate', verify=False).content)
|
|
||||||
|
|
||||||
environ = os.environ.copy()
|
|
||||||
environ['ISSUER_KEY_PATH'] = key_path
|
|
||||||
environ['ISSUER_CERT_PATH'] = cert_path
|
|
||||||
|
|
||||||
process = subprocess.Popen([sys.executable, ocsp_server.__file__], env=environ)
|
|
||||||
|
|
||||||
return process, 'http://127.0.0.1:{0}'.format(MOCK_OCSP_SERVER_PORT)
|
|
||||||
|
|
||||||
|
|
||||||
def _ignore_https_warnings():
|
|
||||||
try:
|
try:
|
||||||
import urllib3
|
import urllib3
|
||||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|||||||
@@ -1,79 +1,53 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import json
|
import sys
|
||||||
import os
|
|
||||||
|
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
from cryptography.hazmat.primitives import serialization, hashes
|
from cryptography.hazmat.primitives import serialization, hashes
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
from cryptography.x509 import ocsp
|
from cryptography.x509 import ocsp
|
||||||
from flask import Flask, request
|
from six.moves import BaseHTTPServer
|
||||||
|
|
||||||
|
from certbot_integration_tests.utils.misc import GracefulTCPServer
|
||||||
from certbot_integration_tests.utils.constants import MOCK_OCSP_SERVER_PORT
|
from certbot_integration_tests.utils.constants import MOCK_OCSP_SERVER_PORT
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
app.debug = False
|
|
||||||
|
|
||||||
certificates_map = {}
|
def _create_ocsp_handler(cert_path, issuer_cert_path, issuer_key_path, ocsp_status_text):
|
||||||
|
class ProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||||
|
def do_POST(self):
|
||||||
|
with open(issuer_cert_path, 'rb') as file_h1:
|
||||||
|
issuer_cert = x509.load_pem_x509_certificate(file_h1.read(), default_backend())
|
||||||
|
with open(issuer_key_path, 'rb') as file_h2:
|
||||||
|
issuer_key = serialization.load_pem_private_key(file_h2.read(), None, default_backend())
|
||||||
|
with open(cert_path, 'rb') as file_h3:
|
||||||
|
cert = x509.load_pem_x509_certificate(file_h3.read(), default_backend())
|
||||||
|
|
||||||
|
ocsp_status = getattr(ocsp.OCSPCertStatus, ocsp_status_text)
|
||||||
|
|
||||||
@app.route('/', methods=['GET'])
|
now = datetime.datetime.utcnow()
|
||||||
def heartbeat():
|
revocation_time = now if ocsp_status == ocsp.OCSPCertStatus.REVOKED else None
|
||||||
return 'Done', 200
|
revocation_reason = x509.ReasonFlags.unspecified if ocsp_status == ocsp.OCSPCertStatus.REVOKED else None
|
||||||
|
|
||||||
|
builder = ocsp.OCSPResponseBuilder()
|
||||||
|
builder = builder.add_response(
|
||||||
|
cert=cert, issuer=issuer_cert, algorithm=hashes.SHA1(),
|
||||||
|
cert_status=ocsp_status,
|
||||||
|
this_update=now,
|
||||||
|
next_update=now + datetime.timedelta(hours=1),
|
||||||
|
revocation_time=revocation_time, revocation_reason=revocation_reason
|
||||||
|
).responder_id(ocsp.OCSPResponderEncoding.NAME, issuer_cert)
|
||||||
|
|
||||||
@app.route('/', methods=['PUT'])
|
response = builder.sign(issuer_key, hashes.SHA256())
|
||||||
def register_certificate():
|
|
||||||
config = json.loads(request.get_data())
|
|
||||||
cert_path = config['cert_path']
|
|
||||||
ocsp_status = config['ocsp_status']
|
|
||||||
|
|
||||||
with open(cert_path, 'rb') as file_h3:
|
self.send_response(200)
|
||||||
cert = x509.load_pem_x509_certificate(file_h3.read(), default_backend())
|
self.end_headers()
|
||||||
serial_number = cert.serial_number
|
self.wfile.write(response.public_bytes(serialization.Encoding.DER))
|
||||||
certificates_map[serial_number] = (cert_path, ocsp_status)
|
|
||||||
|
|
||||||
return 'Done', 200
|
return ProxyHandler
|
||||||
|
|
||||||
|
|
||||||
@app.route('/', methods=['POST'])
|
|
||||||
def status_certificate():
|
|
||||||
raw = request.get_data()
|
|
||||||
ocsp_request = ocsp.load_der_ocsp_request(raw)
|
|
||||||
|
|
||||||
serial_number = ocsp_request.serial_number
|
|
||||||
if serial_number not in certificates_map:
|
|
||||||
response = ocsp.OCSPResponseBuilder.build_unsuccessful(
|
|
||||||
ocsp.OCSPResponseStatus.UNAUTHORIZED)
|
|
||||||
else:
|
|
||||||
config = certificates_map[serial_number]
|
|
||||||
cert_path = config[0]
|
|
||||||
ocsp_status = getattr(ocsp.OCSPCertStatus, config[1])
|
|
||||||
|
|
||||||
with open(os.environ['ISSUER_CERT_PATH'], 'rb') as file_h1:
|
|
||||||
issuer_cert = x509.load_pem_x509_certificate(file_h1.read(), default_backend())
|
|
||||||
with open(os.environ['ISSUER_KEY_PATH'], 'rb') as file_h2:
|
|
||||||
issuer_key = serialization.load_pem_private_key(file_h2.read(), None, default_backend())
|
|
||||||
with open(cert_path, 'rb') as file_h3:
|
|
||||||
cert = x509.load_pem_x509_certificate(file_h3.read(), default_backend())
|
|
||||||
|
|
||||||
now = datetime.datetime.utcnow()
|
|
||||||
|
|
||||||
revocation_time = now if ocsp_status == ocsp.OCSPCertStatus.REVOKED else None
|
|
||||||
revocation_reason = x509.ReasonFlags.unspecified if ocsp_status == ocsp.OCSPCertStatus.REVOKED else None
|
|
||||||
|
|
||||||
builder = ocsp.OCSPResponseBuilder()
|
|
||||||
builder = builder.add_response(
|
|
||||||
cert=cert, issuer=issuer_cert, algorithm=hashes.SHA1(),
|
|
||||||
cert_status=ocsp_status,
|
|
||||||
this_update=now,
|
|
||||||
next_update=now + datetime.timedelta(hours=1),
|
|
||||||
revocation_time=revocation_time, revocation_reason=revocation_reason
|
|
||||||
).responder_id(ocsp.OCSPResponderEncoding.NAME, issuer_cert)
|
|
||||||
|
|
||||||
response = builder.sign(issuer_key, hashes.SHA256())
|
|
||||||
|
|
||||||
return response.public_bytes(serialization.Encoding.DER), 200
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(host='0.0.0.0', port=int(os.environ.get('OCSP_PORT', MOCK_OCSP_SERVER_PORT)))
|
httpd = GracefulTCPServer(('', MOCK_OCSP_SERVER_PORT), _create_ocsp_handler(*sys.argv[1:5]))
|
||||||
|
try:
|
||||||
|
httpd.handle_request()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
|||||||
Reference in New Issue
Block a user