195 lines
6.3 KiB
Python
195 lines
6.3 KiB
Python
#!/usr/bin/env python2.7
|
|
import atexit
|
|
import base64
|
|
import os
|
|
import re
|
|
import shutil
|
|
import socket
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import urllib
|
|
import time
|
|
import urllib2
|
|
|
|
import startservers
|
|
|
|
|
|
class ExitStatus:
|
|
OK, PythonFailure, NodeFailure, Error, OCSPFailure, CTFailure = range(6)
|
|
|
|
|
|
class ProcInfo:
|
|
"""
|
|
Args:
|
|
cmd (str): The command that was run
|
|
proc(subprocess.Popen): The Popen of the command run
|
|
"""
|
|
|
|
def __init__(self, cmd, proc):
|
|
self.cmd = cmd
|
|
self.proc = proc
|
|
|
|
|
|
def die(status):
|
|
global exit_status
|
|
# Set exit_status so cleanup handler knows what to report.
|
|
exit_status = status
|
|
sys.exit(exit_status)
|
|
|
|
# Fetch an OCSP response, parse it with OpenSSL, and return the output.
|
|
def get_ocsp(cert_file, url):
|
|
ocsp_req_file = os.path.join(tempdir, "ocsp.req")
|
|
ocsp_resp_file = os.path.join(tempdir, "ocsp.resp")
|
|
# First generate the OCSP request in DER form
|
|
openssl_ocsp = "openssl ocsp -issuer ../test-ca.pem -cert %s.pem" % cert_file
|
|
openssl_ocsp_cmd = ("""
|
|
openssl x509 -in %s -out %s.pem -inform der -outform pem;
|
|
%s -no_nonce -reqout %s
|
|
""" % (cert_file, cert_file, openssl_ocsp, ocsp_req_file))
|
|
print openssl_ocsp_cmd
|
|
subprocess.check_output(openssl_ocsp_cmd, shell=True)
|
|
with open(ocsp_req_file) as f:
|
|
ocsp_req = f.read()
|
|
ocsp_req_b64 = base64.b64encode(ocsp_req)
|
|
|
|
# Make the OCSP request three different ways: by POST, by GET, and by GET with
|
|
# URL-encoded parameters. All three should have an identical response.
|
|
get_response = urllib2.urlopen("%s/%s" % (url, ocsp_req_b64)).read()
|
|
get_encoded_response = urllib2.urlopen("%s/%s" % (url, urllib.quote(ocsp_req_b64, safe = ""))).read()
|
|
post_response = urllib2.urlopen("%s/" % (url), ocsp_req).read()
|
|
|
|
if get_response != get_encoded_response:
|
|
print "OCSP responses for GET and URL-encoded GET differed."
|
|
die(ExitStatus.OCSPFailure)
|
|
elif get_response != post_response:
|
|
print "OCSP responses for GET and POST differed."
|
|
die(ExitStatus.OCSPFailure)
|
|
|
|
with open(ocsp_resp_file, "w") as f:
|
|
f.write(get_response)
|
|
|
|
ocsp_verify_cmd = "%s -CAfile ../test-ca.pem -respin %s" % (openssl_ocsp, ocsp_resp_file)
|
|
print ocsp_verify_cmd
|
|
try:
|
|
output = subprocess.check_output(ocsp_verify_cmd, shell=True)
|
|
except subprocess.CalledProcessError as e:
|
|
output = e.output
|
|
print output
|
|
print "subprocess returned non-zero: %s" % e
|
|
die(ExitStatus.OCSPFailure)
|
|
|
|
print output
|
|
return output
|
|
|
|
def verify_ocsp_good(certFile, url):
|
|
output = get_ocsp(certFile, url)
|
|
if not re.search(": good", output):
|
|
if not re.search(" unauthorized \(6\)", output):
|
|
print "Expected OCSP response 'unauthorized', got something else."
|
|
die(ExitStatus.OCSPFailure)
|
|
return False
|
|
return True
|
|
|
|
def verify_ocsp_revoked(certFile, url):
|
|
output = get_ocsp(certFile, url)
|
|
if not re.search(": revoked", output):
|
|
print "Expected OCSP response 'revoked', got something else."
|
|
die(ExitStatus.OCSPFailure)
|
|
pass
|
|
|
|
# loop_check expects the function passed as action will return True/False to indicate
|
|
# success/failure
|
|
def loop_check(failureStatus, action, *args):
|
|
timeout = time.time() + 5
|
|
while True:
|
|
if action(*args):
|
|
break
|
|
if time.time() > timeout:
|
|
die(failureStatus)
|
|
time.sleep(0.25)
|
|
|
|
def verify_ct_submission(expectedSubmissions, url):
|
|
resp = urllib2.urlopen(url)
|
|
submissionStr = resp.read()
|
|
if int(submissionStr) != expectedSubmissions:
|
|
print "Expected %d submissions, found %d" % (expectedSubmissions, int(submissionStr))
|
|
die(ExitStatus.CTFailure)
|
|
|
|
def run_node_test():
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
try:
|
|
s.connect(('localhost', 4000))
|
|
except socket.error, e:
|
|
print("Cannot connect to WFE")
|
|
die(ExitStatus.Error)
|
|
|
|
os.chdir('test/js')
|
|
|
|
if subprocess.Popen('npm install', shell=True).wait() != 0:
|
|
print("\n Installing NPM modules failed")
|
|
die(ExitStatus.Error)
|
|
certFile = os.path.join(tempdir, "cert.der")
|
|
keyFile = os.path.join(tempdir, "key.pem")
|
|
if subprocess.Popen('''
|
|
node test.js --email foo@letsencrypt.org --agree true \
|
|
--domains foo.com --new-reg http://localhost:4000/acme/new-reg \
|
|
--certKey %s --cert %s
|
|
''' % (keyFile, certFile), shell=True).wait() != 0:
|
|
print("\nIssuing failed")
|
|
die(ExitStatus.NodeFailure)
|
|
|
|
ee_ocsp_url = "http://localhost:4002"
|
|
issuer_ocsp_url = "http://localhost:4003"
|
|
|
|
# Also verify that the static OCSP responder, which answers with a
|
|
# pre-signed, long-lived response for the CA cert, also works.
|
|
verify_ocsp_good("../test-ca.der", issuer_ocsp_url)
|
|
|
|
# As OCSP-Updater is generating responses indepedantly of the CA we sit in a loop
|
|
# checking OCSP until we either see a good response or we timeout (5s).
|
|
loop_check(ExitStatus.OCSPFailure, verify_ocsp_good, certFile, ee_ocsp_url)
|
|
|
|
verify_ct_submission(1, "http://localhost:4500/submissions")
|
|
|
|
if subprocess.Popen('''
|
|
node revoke.js %s %s http://localhost:4000/acme/revoke-cert
|
|
''' % (certFile, keyFile), shell=True).wait() != 0:
|
|
print("\nRevoking failed")
|
|
die(ExitStatus.NodeFailure)
|
|
|
|
verify_ocsp_revoked(certFile, ee_ocsp_url)
|
|
|
|
return 0
|
|
|
|
|
|
def run_client_tests():
|
|
root = os.environ.get("LETSENCRYPT_PATH")
|
|
assert root is not None, (
|
|
"Please set LETSENCRYPT_PATH env variable to point at "
|
|
"initialized (virtualenv) client repo root")
|
|
test_script_path = os.path.join(root, 'tests', 'boulder-integration.sh')
|
|
cmd = "source %s/venv/bin/activate && %s" % (root, test_script_path)
|
|
if subprocess.Popen(cmd, shell=True, cwd=root, executable='/bin/bash').wait() != 0:
|
|
die(ExitStatus.PythonFailure)
|
|
|
|
|
|
@atexit.register
|
|
def cleanup():
|
|
import shutil
|
|
shutil.rmtree(tempdir)
|
|
if exit_status == ExitStatus.OK:
|
|
print("\n\nSUCCESS")
|
|
else:
|
|
print("\n\nFAILURE %d" % exit_status)
|
|
|
|
|
|
exit_status = ExitStatus.OK
|
|
tempdir = tempfile.mkdtemp()
|
|
if not startservers.start(race_detection=True):
|
|
die(ExitStatus.Error)
|
|
run_node_test()
|
|
run_client_tests()
|
|
if not startservers.check():
|
|
die(ExitStatus.Error)
|