regen_certs.py (3341B)
1 # mypy: allow-untyped-defs 2 3 import argparse 4 import base64 5 import logging 6 import subprocess 7 import sys 8 9 10 logger = logging.getLogger(__name__) 11 12 13 # TODO(Issue #24180): Regenerate SXG fingerprint too. 14 CHROME_SPKI_CERTS_CONTENT = """\ 15 # This file is automatically generated by 'wpt regen-certs' 16 # DO NOT EDIT MANUALLY. 17 18 # tools/certs/web-platform.test.pem 19 WPT_FINGERPRINT = '{wpt_fingerprint}' 20 21 # signed-exchange/resources/127.0.0.1.sxg.pem 22 SXG_WPT_FINGERPRINT = '0Rt4mT6SJXojEMHTnKnlJ/hBKMBcI4kteBlhR1eTTdk=' 23 24 IGNORE_CERTIFICATE_ERRORS_SPKI_LIST = [ 25 WPT_FINGERPRINT, 26 SXG_WPT_FINGERPRINT 27 ] 28 """ 29 30 31 def get_parser(): 32 parser = argparse.ArgumentParser() 33 parser.add_argument("--checkend-seconds", type=int, default=5184000, 34 help="The number of seconds the certificates must be valid for") 35 parser.add_argument("--force", action="store_true", 36 help="Regenerate certificates even if not reaching expiry") 37 return parser 38 39 40 def check_cert(certificate, checkend_seconds): 41 """Checks whether an x509 certificate will expire within a set period. 42 43 Returns 0 if the certificate will not expire, non-zero otherwise.""" 44 cmd = [ 45 "openssl", "x509", 46 "-checkend", str(checkend_seconds), 47 "-noout", 48 "-in", certificate 49 ] 50 logger.info("Running '%s'" % " ".join(cmd)) 51 return subprocess.call(cmd) 52 53 54 def regen_certs(): 55 """Regenerate the wpt openssl certificates, by delegating to wptserve.""" 56 cmd = [ 57 sys.executable, "wpt", "serve", 58 "--config", "tools/certs/config.json", 59 "--exit-after-start", 60 ] 61 logger.info("Running '%s'" % " ".join(cmd)) 62 subprocess.check_call(cmd) 63 64 65 def regen_chrome_spki(): 66 """Regenerate the SPKI fingerprints for Chrome's ignore-cert list. 67 68 Chrome requires us to explicitly list which certificates are ignored by its 69 security-checking, by listing a base64 hash of the public key. This will 70 change every time we replace our certificates, so we store the hashes in a 71 file and regenerate it here. 72 """ 73 wpt_spki = calculate_spki("tools/certs/web-platform.test.pem") 74 with open("tools/wptrunner/wptrunner/browsers/chrome_spki_certs.py", "w") as f: 75 f.write(CHROME_SPKI_CERTS_CONTENT.format(wpt_fingerprint=wpt_spki)) 76 77 78 def calculate_spki(cert_path): 79 """Calculate the SPKI fingerprint for a given x509 certificate.""" 80 # We use shell=True as we control the input |cert_path|, and piping 81 # correctly across processes is non-trivial in Python. 82 cmd = (f"openssl x509 -noout -pubkey -in {cert_path} | " + 83 "openssl pkey -pubin -outform der | " + 84 "openssl dgst -sha256 -binary") 85 dgst_output = subprocess.check_output(cmd, shell=True) 86 87 return base64.b64encode(dgst_output).decode('utf-8') 88 89 90 def run(**kwargs): 91 logging.basicConfig() 92 93 if kwargs["force"]: 94 logger.info("Force regenerating WPT certificates") 95 checkend_seconds = kwargs["checkend_seconds"] 96 if (kwargs["force"] or 97 check_cert("tools/certs/cacert.pem", checkend_seconds) or 98 check_cert("tools/certs/web-platform.test.pem", checkend_seconds)): 99 regen_certs() 100 regen_chrome_spki() 101 else: 102 logger.info("Certificates are still valid for at least %s seconds, skipping regeneration" % checkend_seconds)