tor-browser

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

genpgocert.py (8973B)


      1 #!/usr/bin/env python
      2 # This Source Code Form is subject to the terms of the Mozilla Public
      3 # License, v. 2.0. If a copy of the MPL was not distributed with this
      4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
      5 
      6 # This script exists to generate the Certificate Authority and server
      7 # certificates used for SSL testing in Mochitest. The already generated
      8 # certs are located at $topsrcdir/build/pgo/certs/ .
      9 
     10 import os
     11 import random
     12 import re
     13 import shutil
     14 import subprocess
     15 import sys
     16 
     17 import mozinfo
     18 from mozbuild.base import BinaryNotFoundException, MozbuildObject
     19 from mozfile import NamedTemporaryFile, TemporaryDirectory
     20 from mozprofile.permissions import ServerLocations
     21 
     22 dbFiles = [
     23    re.compile(r"^cert[0-9]+\.db$"),
     24    re.compile(r"^key[0-9]+\.db$"),
     25    re.compile(r"^secmod\.db$"),
     26 ]
     27 
     28 
     29 def unlinkDbFiles(path):
     30    for root, dirs, files in os.walk(path):
     31        for name in files:
     32            for dbFile in dbFiles:
     33                if dbFile.match(name) and os.path.exists(os.path.join(root, name)):
     34                    os.unlink(os.path.join(root, name))
     35 
     36 
     37 def dbFilesExist(path):
     38    for root, dirs, files in os.walk(path):
     39        for name in files:
     40            for dbFile in dbFiles:
     41                if dbFile.match(name) and os.path.exists(os.path.join(root, name)):
     42                    return True
     43    return False
     44 
     45 
     46 def runUtil(util, args, inputdata=None, outputstream=None):
     47    env = os.environ.copy()
     48    if mozinfo.os == "linux":
     49        pathvar = "LD_LIBRARY_PATH"
     50        app_path = os.path.dirname(util)
     51        if pathvar in env:
     52            env[pathvar] = "%s%s%s" % (app_path, os.pathsep, env[pathvar])
     53        else:
     54            env[pathvar] = app_path
     55    proc = subprocess.Popen(
     56        [util] + args,
     57        env=env,
     58        stdin=subprocess.PIPE if inputdata else None,
     59        stdout=outputstream,
     60        universal_newlines=True,
     61    )
     62    proc.communicate(inputdata)
     63    return proc.returncode
     64 
     65 
     66 def createRandomFile(randomFile):
     67    for count in xrange(0, 2048):
     68        randomFile.write(chr(random.randint(0, 255)))
     69 
     70 
     71 def writeCertspecForServerLocations(fd):
     72    locations = ServerLocations(
     73        os.path.join(build.topsrcdir, "build", "pgo", "server-locations.txt")
     74    )
     75    SAN = []
     76    for loc in [
     77        i for i in iter(locations) if i.scheme == "https" and "nocert" not in i.options
     78    ]:
     79        customCertOption = False
     80        customCertRE = re.compile(r"^cert=(?:\w+)")
     81        for _ in [i for i in loc.options if customCertRE.match(i)]:
     82            customCertOption = True
     83            break
     84 
     85        if "ipV4Address" in loc.options:
     86            loc.host = "ip4:" + loc.host
     87 
     88        if not customCertOption:
     89            SAN.append(loc.host)
     90 
     91    fd.write(
     92        "issuer:printableString/CN=Temporary Certificate Authority/O=Mozilla Testing/OU=Profile Guided Optimization\n"  # NOQA: E501
     93    )
     94    fd.write(f"subject:{SAN[0]}\n")
     95    fd.write("extension:subjectAlternativeName:{}\n".format(",".join(SAN)))
     96 
     97 
     98 def constructCertDatabase(build, srcDir):
     99    try:
    100        certutil = build.get_binary_path(what="certutil")
    101        pk12util = build.get_binary_path(what="pk12util")
    102    except BinaryNotFoundException as e:
    103        print(f"{e}\n\n{e.help()}\n")
    104        return 1
    105    openssl = shutil.which("openssl")
    106    pycert = os.path.join(build.topsrcdir, "security", "manager", "tools", "pycert.py")
    107    pykey = os.path.join(build.topsrcdir, "security", "manager", "tools", "pykey.py")
    108 
    109    with NamedTemporaryFile(mode="wt+") as pwfile, TemporaryDirectory() as pemfolder:
    110        pwfile.write("\n")
    111        pwfile.flush()
    112 
    113        if dbFilesExist(srcDir):
    114            # Make sure all DB files from src are really deleted
    115            unlinkDbFiles(srcDir)
    116 
    117        # Copy  all .certspec and .keyspec files to a temporary directory
    118        for root, dirs, files in os.walk(srcDir):
    119            for spec in [
    120                i for i in files if i.endswith(".certspec") or i.endswith(".keyspec")
    121            ]:
    122                shutil.copyfile(os.path.join(root, spec), os.path.join(pemfolder, spec))
    123 
    124        # Write a certspec for the "server-locations.txt" file to that temporary directory
    125        pgoserver_certspec = os.path.join(pemfolder, "pgoserver.certspec")
    126        if os.path.exists(pgoserver_certspec):
    127            raise Exception(f"{pgoserver_certspec} already exists, which isn't allowed")
    128        with open(pgoserver_certspec, "w") as fd:
    129            writeCertspecForServerLocations(fd)
    130 
    131        # Generate certs for all certspecs
    132        for root, dirs, files in os.walk(pemfolder):
    133            for certspec in [i for i in files if i.endswith(".certspec")]:
    134                name = certspec.split(".certspec")[0]
    135                pem = os.path.join(pemfolder, f"{name}.cert.pem")
    136 
    137                print(f"Generating public certificate {name} (pem={pem})")
    138 
    139                with open(os.path.join(root, certspec)) as certspec_file:
    140                    certspec_data = certspec_file.read()
    141                    with open(pem, "w") as pem_file:
    142                        status = runUtil(
    143                            pycert, [], inputdata=certspec_data, outputstream=pem_file
    144                        )
    145                        if status:
    146                            return status
    147 
    148                status = runUtil(
    149                    certutil,
    150                    [
    151                        "-A",
    152                        "-n",
    153                        name,
    154                        "-t",
    155                        "P,,",
    156                        "-i",
    157                        pem,
    158                        "-d",
    159                        srcDir,
    160                        "-f",
    161                        pwfile.name,
    162                    ],
    163                )
    164                if status:
    165                    return status
    166 
    167            for keyspec in [i for i in files if i.endswith(".keyspec")]:
    168                parts = keyspec.split(".")
    169                name = parts[0]
    170                key_type = parts[1]
    171                if key_type not in ["ca", "client", "server"]:
    172                    raise Exception(
    173                        f"{keyspec}: keyspec filenames must be of the form XXX.client.keyspec "
    174                        f"or XXX.ca.keyspec (key_type={key_type})"
    175                    )
    176                key_pem = os.path.join(pemfolder, f"{name}.key.pem")
    177 
    178                print(f"Generating private key {name} (pem={key_pem})")
    179 
    180                with open(os.path.join(root, keyspec)) as keyspec_file:
    181                    keyspec_data = keyspec_file.read()
    182                    with open(key_pem, "w") as pem_file:
    183                        status = runUtil(
    184                            pykey, [], inputdata=keyspec_data, outputstream=pem_file
    185                        )
    186                        if status:
    187                            return status
    188 
    189                cert_pem = os.path.join(pemfolder, f"{name}.cert.pem")
    190                if not os.path.exists(cert_pem):
    191                    raise Exception(
    192                        f"There has to be a corresponding certificate named {cert_pem} for "
    193                        f"the keyspec {keyspec}"
    194                    )
    195 
    196                p12 = os.path.join(pemfolder, f"{name}.key.p12")
    197                print(f"Converting private key {key_pem} to PKCS12 (p12={p12})")
    198                status = runUtil(
    199                    openssl,
    200                    [
    201                        "pkcs12",
    202                        "-export",
    203                        "-inkey",
    204                        key_pem,
    205                        "-in",
    206                        cert_pem,
    207                        "-name",
    208                        name,
    209                        "-out",
    210                        p12,
    211                        "-passout",
    212                        "file:" + pwfile.name,
    213                    ],
    214                )
    215                if status:
    216                    return status
    217 
    218                print(f"Importing private key {key_pem} to database")
    219                status = runUtil(
    220                    pk12util,
    221                    ["-i", p12, "-d", srcDir, "-w", pwfile.name, "-k", pwfile.name],
    222                )
    223                if status:
    224                    return status
    225 
    226                if key_type == "ca":
    227                    shutil.copyfile(cert_pem, os.path.join(srcDir, f"{name}.ca"))
    228                elif key_type == "client":
    229                    shutil.copyfile(p12, os.path.join(srcDir, f"{name}.client"))
    230                elif key_type == "server":
    231                    pass  # Nothing to do for server keys
    232                else:
    233                    raise Exception(
    234                        f"State error: Unknown keyspec key_type: {key_type}"
    235                    )
    236 
    237    return 0
    238 
    239 
    240 build = MozbuildObject.from_environment()
    241 certdir = os.path.join(build.topsrcdir, "build", "pgo", "certs")
    242 certificateStatus = constructCertDatabase(build, certdir)
    243 if certificateStatus:
    244    print("TEST-UNEXPECTED-FAIL | SSL Server Certificate generation")
    245 sys.exit(certificateStatus)