summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/tools/ci/regen_certs.py
blob: 8f3abdcad691bde3385ba496de07d979155296a9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# 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)