test_https.py (4217B)
1 #!/usr/bin/env python3 2 3 import http.server 4 import os 5 from pathlib import Path 6 import shutil 7 import ssl 8 import subprocess 9 import sys 10 import tempfile 11 import threading 12 13 14 class TestHandler(http.server.BaseHTTPRequestHandler): 15 def do_GET(self): 16 body = b"ok" 17 self.send_response(200) 18 self.send_header("Content-Length", str(len(body))) 19 self.end_headers() 20 self.wfile.write(body) 21 22 def log_message(self, format, *args): 23 pass 24 25 26 def run_checked(argv): 27 subprocess.run( 28 argv, 29 check=True, 30 stdout=subprocess.DEVNULL, 31 stderr=subprocess.PIPE, 32 text=True, 33 ) 34 35 36 def make_certificates(openssl, directory): 37 ca_key = directory / "ca.key" 38 ca_cert = directory / "ca.crt" 39 server_key = directory / "server.key" 40 server_csr = directory / "server.csr" 41 server_cert = directory / "server.crt" 42 server_extensions = directory / "server.ext" 43 44 run_checked([ 45 openssl, "req", "-x509", "-newkey", "rsa:2048", "-sha256", 46 "-nodes", "-days", "1", "-subj", "/CN=quickjs-tart test CA", 47 "-addext", "basicConstraints=critical,CA:TRUE", 48 "-addext", "keyUsage=critical,keyCertSign,cRLSign", 49 "-keyout", str(ca_key), "-out", str(ca_cert), 50 ]) 51 run_checked([ 52 openssl, "req", "-new", "-newkey", "rsa:2048", "-sha256", 53 "-nodes", "-subj", "/CN=127.0.0.1", "-keyout", str(server_key), 54 "-out", str(server_csr), 55 ]) 56 server_extensions.write_text( 57 "basicConstraints=critical,CA:FALSE\n" 58 "keyUsage=critical,digitalSignature,keyEncipherment\n" 59 "extendedKeyUsage=serverAuth\n" 60 "subjectAltName=IP:127.0.0.1\n", 61 encoding="ascii", 62 ) 63 run_checked([ 64 openssl, "x509", "-req", "-in", str(server_csr), "-CA", 65 str(ca_cert), "-CAkey", str(ca_key), "-CAcreateserial", "-days", 66 "1", "-sha256", "-extfile", str(server_extensions), "-out", 67 str(server_cert), 68 ]) 69 return ca_cert, server_cert, server_key 70 71 72 def run_https_probe(qtart, ca_cert, port, ca_environment_variable): 73 marker = f"https-ok-{ca_environment_variable}" 74 expression = ( 75 f'os.fetchHttp("https://127.0.0.1:{port}/", {{}}).promise.then(' 76 f'(r) => print(r.status === 200 ? "{marker}" : "bad-status"), ' 77 '(e) => print("https-error", e));' 78 ) 79 environment = os.environ.copy() 80 environment.pop("CURL_CA_BUNDLE", None) 81 environment.pop("SSL_CERT_DIR", None) 82 environment.pop("SSL_CERT_FILE", None) 83 environment[ca_environment_variable] = str(ca_cert) 84 result = subprocess.run( 85 [qtart, "--std", "-e", expression], 86 env=environment, 87 capture_output=True, 88 text=True, 89 timeout=20, 90 ) 91 if result.returncode != 0 or marker not in result.stdout: 92 sys.stderr.write(result.stdout) 93 sys.stderr.write(result.stderr) 94 return False 95 return True 96 97 98 def main(): 99 if len(sys.argv) != 2: 100 print("usage: test_https.py QTART", file=sys.stderr) 101 return 2 102 103 qtart = sys.argv[1] 104 openssl = shutil.which("openssl") 105 if openssl is None: 106 print("openssl is required", file=sys.stderr) 107 return 2 108 with tempfile.TemporaryDirectory(prefix="quickjs-tart-https-") as tmp: 109 ca_cert, server_cert, server_key = make_certificates( 110 openssl, 111 Path(tmp), 112 ) 113 server = http.server.ThreadingHTTPServer(("127.0.0.1", 0), TestHandler) 114 tls_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) 115 tls_context.load_cert_chain(server_cert, server_key) 116 server.socket = tls_context.wrap_socket(server.socket, server_side=True) 117 server_thread = threading.Thread(target=server.serve_forever) 118 server_thread.start() 119 try: 120 port = server.server_address[1] 121 if not run_https_probe(qtart, ca_cert, port, "SSL_CERT_FILE"): 122 return 1 123 if not run_https_probe(qtart, ca_cert, port, "CURL_CA_BUNDLE"): 124 return 1 125 finally: 126 server.shutdown() 127 server.server_close() 128 server_thread.join() 129 return 0 130 131 132 if __name__ == "__main__": 133 raise SystemExit(main())