tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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)