# mypy: allow-untyped-defs import argparse import base64 import logging import subprocess import sys logger = logging.getLogger(__name__) # TODO(Issue #24180): Regenerate SXG fingerprint too. CHROME_SPKI_CERTS_CONTENT = """\ # This file is automatically generated by 'wpt regen-certs' # DO NOT EDIT MANUALLY. # tools/certs/web-platform.test.pem WPT_FINGERPRINT = '{wpt_fingerprint}' # signed-exchange/resources/127.0.0.1.sxg.pem SXG_WPT_FINGERPRINT = '0Rt4mT6SJXojEMHTnKnlJ/hBKMBcI4kteBlhR1eTTdk=' IGNORE_CERTIFICATE_ERRORS_SPKI_LIST = [ WPT_FINGERPRINT, SXG_WPT_FINGERPRINT ] """ def get_parser(): parser = argparse.ArgumentParser() parser.add_argument("--checkend-seconds", type=int, default=5184000, help="The number of seconds the certificates must be valid for") parser.add_argument("--force", action="store_true", help="Regenerate certificates even if not reaching expiry") return parser def check_cert(certificate, checkend_seconds): """Checks whether an x509 certificate will expire within a set period. Returns 0 if the certificate will not expire, non-zero otherwise.""" cmd = [ "openssl", "x509", "-checkend", str(checkend_seconds), "-noout", "-in", certificate ] logger.info("Running '%s'" % " ".join(cmd)) return subprocess.call(cmd) def regen_certs(): """Regenerate the wpt openssl certificates, by delegating to wptserve.""" cmd = [ sys.executable, "wpt", "serve", "--config", "tools/certs/config.json", "--exit-after-start", ] logger.info("Running '%s'" % " ".join(cmd)) subprocess.check_call(cmd) def regen_chrome_spki(): """Regenerate the SPKI fingerprints for Chrome's ignore-cert list. Chrome requires us to explicitly list which certificates are ignored by its security-checking, by listing a base64 hash of the public key. This will change every time we replace our certificates, so we store the hashes in a file and regenerate it here. """ wpt_spki = calculate_spki("tools/certs/web-platform.test.pem") with open("tools/wptrunner/wptrunner/browsers/chrome_spki_certs.py", "w") as f: f.write(CHROME_SPKI_CERTS_CONTENT.format(wpt_fingerprint=wpt_spki)) def calculate_spki(cert_path): """Calculate the SPKI fingerprint for a given x509 certificate.""" # We use shell=True as we control the input |cert_path|, and piping # correctly across processes is non-trivial in Python. cmd = (f"openssl x509 -noout -pubkey -in {cert_path} | " + "openssl pkey -pubin -outform der | " + "openssl dgst -sha256 -binary") dgst_output = subprocess.check_output(cmd, shell=True) return base64.b64encode(dgst_output).decode('utf-8') def run(**kwargs): logging.basicConfig() if kwargs["force"]: logger.info("Force regenerating WPT certificates") checkend_seconds = kwargs["checkend_seconds"] if (kwargs["force"] or check_cert("tools/certs/cacert.pem", checkend_seconds) or check_cert("tools/certs/web-platform.test.pem", checkend_seconds)): regen_certs() regen_chrome_spki() else: logger.info("Certificates are still valid for at least %s seconds, skipping regeneration" % checkend_seconds)