forked from mirrors/gecko-dev
(This also fixes Bug 879740 and Bug 1204543.) build/pgo/certs contains an NSS database set that has a bunch of hand-generated certificates, and many of these hand-generated certificates are specifically depended upon for a variety of unit tests. This patch changes all of these to use the "pycert.py" and "pykey.py" utilities that produce deterministic keys and certificates. The naming convention here is new, and defined in the README. It is based on the mochitest runtest.py naming convention that imports .ca and .client PEM-encoded certificates. Unfortunately, the updates to build/pgo/genpgocert.py to generate these files depends on OpenSSL in order to produce PKCS12 archives for pk11tool to import into NSS. This could be done with pure-NSS tooling, but it'd require some new command line functionality, which is out-of-scope for this change. Note that build/pgo/genpgocert.py no longer takes arguments when run. It's not run automatically anywhere that I can see, but could (reasonably) be, now. Differential Revision: https://phabricator.services.mozilla.com/D971 --HG-- extra : amend_source : bc389b9b0a807a4889feb14db439daa28635dfe9
183 lines
7.2 KiB
Python
183 lines
7.2 KiB
Python
#!/usr/bin/env python
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
# This script exists to generate the Certificate Authority and server
|
|
# certificates used for SSL testing in Mochitest. The already generated
|
|
# certs are located at $topsrcdir/build/pgo/certs/ .
|
|
|
|
import mozinfo
|
|
import os
|
|
import random
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import distutils
|
|
|
|
from mozbuild.base import MozbuildObject
|
|
from mozfile import NamedTemporaryFile, TemporaryDirectory
|
|
from mozprofile.permissions import ServerLocations
|
|
|
|
dbFiles = [
|
|
re.compile("^cert[0-9]+\.db$"),
|
|
re.compile("^key[0-9]+\.db$"),
|
|
re.compile("^secmod\.db$")
|
|
]
|
|
|
|
def unlinkDbFiles(path):
|
|
for root, dirs, files in os.walk(path):
|
|
for name in files:
|
|
for dbFile in dbFiles:
|
|
if dbFile.match(name) and os.path.exists(os.path.join(root, name)):
|
|
os.unlink(os.path.join(root, name))
|
|
|
|
def dbFilesExist(path):
|
|
for root, dirs, files in os.walk(path):
|
|
for name in files:
|
|
for dbFile in dbFiles:
|
|
if dbFile.match(name) and os.path.exists(os.path.join(root, name)):
|
|
return True
|
|
return False
|
|
|
|
def runUtil(util, args, inputdata = None, outputstream = None):
|
|
env = os.environ.copy()
|
|
if mozinfo.os == "linux":
|
|
pathvar = "LD_LIBRARY_PATH"
|
|
app_path = os.path.dirname(util)
|
|
if pathvar in env:
|
|
env[pathvar] = "%s%s%s" % (app_path, os.pathsep, env[pathvar])
|
|
else:
|
|
env[pathvar] = app_path
|
|
proc = subprocess.Popen([util] + args, env=env,
|
|
stdin=subprocess.PIPE if inputdata else None,
|
|
stdout=outputstream)
|
|
proc.communicate(inputdata)
|
|
return proc.returncode
|
|
|
|
def createRandomFile(randomFile):
|
|
for count in xrange(0, 2048):
|
|
randomFile.write(chr(random.randint(0, 255)))
|
|
|
|
def writeCertspecForServerLocations(fd):
|
|
locations = ServerLocations(os.path.join(build.topsrcdir,
|
|
"build", "pgo",
|
|
"server-locations.txt"))
|
|
SAN=[]
|
|
for loc in [i for i in iter(locations) if i.scheme == "https" and "nocert" not in i.options]:
|
|
customCertOption = False
|
|
customCertRE = re.compile("^cert=(?:\w+)")
|
|
for _ in [i for i in loc.options if customCertRE.match(i)]:
|
|
customCertOption = True
|
|
break
|
|
|
|
if not customCertOption:
|
|
SAN.append(loc.host)
|
|
|
|
fd.write("issuer:printableString/CN=Temporary Certificate Authority/O=Mozilla Testing/OU=Profile Guided Optimization\n")
|
|
fd.write("subject:{}\n".format(SAN[0]))
|
|
fd.write("extension:subjectAlternativeName:{}\n".format(",".join(SAN)))
|
|
|
|
def constructCertDatabase(build, srcDir):
|
|
certutil = build.get_binary_path(what="certutil")
|
|
pk12util = build.get_binary_path(what="pk12util")
|
|
openssl = distutils.spawn.find_executable("openssl")
|
|
pycert = os.path.join(build.topsrcdir, "security", "manager", "ssl", "tests",
|
|
"unit", "pycert.py")
|
|
pykey = os.path.join(build.topsrcdir, "security", "manager", "ssl", "tests",
|
|
"unit", "pykey.py")
|
|
|
|
|
|
with NamedTemporaryFile() as pwfile, NamedTemporaryFile() as rndfile, TemporaryDirectory() as pemfolder:
|
|
pgoCAPath = os.path.join(srcDir, "pgoca.p12")
|
|
|
|
pwfile.write("\n")
|
|
pwfile.flush()
|
|
|
|
if dbFilesExist(srcDir):
|
|
# Make sure all DB files from src are really deleted
|
|
unlinkDbFiles(srcDir)
|
|
|
|
# Copy all .certspec and .keyspec files to a temporary directory
|
|
for root, dirs, files in os.walk(srcDir):
|
|
for spec in [i for i in files if i.endswith(".certspec") or i.endswith(".keyspec")]:
|
|
shutil.copyfile(os.path.join(root, spec), os.path.join(pemfolder, spec))
|
|
|
|
# Write a certspec for the "server-locations.txt" file to that temporary directory
|
|
pgoserver_certspec = os.path.join(pemfolder, "pgoserver.certspec")
|
|
if os.path.exists(pgoserver_certspec):
|
|
raise Exception("{} already exists, which isn't allowed".format(pgoserver_certspec))
|
|
with open(pgoserver_certspec, "w") as fd:
|
|
writeCertspecForServerLocations(fd)
|
|
|
|
# Generate certs for all certspecs
|
|
for root, dirs, files in os.walk(pemfolder):
|
|
for certspec in [i for i in files if i.endswith(".certspec")]:
|
|
name = certspec.split(".certspec")[0]
|
|
pem = os.path.join(pemfolder, "{}.cert.pem".format(name))
|
|
|
|
print("Generating public certificate {} (pem={})".format(name, pem))
|
|
|
|
with open(os.path.join(root, certspec), "r") as certspec_file:
|
|
certspec_data = certspec_file.read()
|
|
with open(pem, "w") as pem_file:
|
|
status = runUtil(pycert, [], inputdata=certspec_data, outputstream=pem_file)
|
|
if status:
|
|
return status
|
|
|
|
status = runUtil(certutil, ["-A", "-n", name, "-t", "P,,", "-i", pem, "-d", srcDir, "-f", pwfile.name])
|
|
if status:
|
|
return status
|
|
|
|
|
|
for keyspec in [i for i in files if i.endswith(".keyspec")]:
|
|
parts = keyspec.split(".")
|
|
name = parts[0]
|
|
key_type = parts[1]
|
|
if key_type not in ["ca", "client", "server"]:
|
|
raise Exception("{}: keyspec filenames must be of the form XXX.client.keyspec or XXX.ca.keyspec (key_type={})".format(keyspec, key_type))
|
|
key_pem = os.path.join(pemfolder, "{}.key.pem".format(name))
|
|
|
|
print("Generating private key {} (pem={})".format(name, key_pem))
|
|
|
|
with open(os.path.join(root, keyspec), "r") as keyspec_file:
|
|
keyspec_data = keyspec_file.read()
|
|
with open(key_pem, "w") as pem_file:
|
|
status = runUtil(pykey, [], inputdata=keyspec_data, outputstream=pem_file)
|
|
if status:
|
|
return status
|
|
|
|
cert_pem = os.path.join(pemfolder, "{}.cert.pem".format(name))
|
|
if not os.path.exists(cert_pem):
|
|
raise Exception("There has to be a corresponding certificate named {} for the keyspec {}".format(cert_pem, keyspec))
|
|
|
|
p12 = os.path.join(pemfolder, "{}.key.p12".format(name))
|
|
print("Converting private key {} to PKCS12 (p12={})".format(key_pem, p12))
|
|
status = runUtil(openssl, ["pkcs12", "-export", "-inkey", key_pem, "-in", cert_pem, "-name", name, "-out", p12, "-passout", "file:"+pwfile.name])
|
|
if status:
|
|
return status
|
|
|
|
print("Importing private key {} to database".format(key_pem))
|
|
status = runUtil(pk12util, ["-i", p12, "-d", srcDir, "-w", pwfile.name, "-k", pwfile.name])
|
|
if status:
|
|
return status
|
|
|
|
if key_type == "ca":
|
|
shutil.copyfile(cert_pem, os.path.join(srcDir, "{}.ca".format(name)))
|
|
elif key_type == "client":
|
|
shutil.copyfile(p12, os.path.join(srcDir, "{}.client".format(name)))
|
|
elif key_type == "server":
|
|
pass # Nothing to do for server keys
|
|
else:
|
|
raise Exception("State error: Unknown keyspec key_type: {}".format(key_type))
|
|
|
|
return 0
|
|
|
|
build = MozbuildObject.from_environment()
|
|
certdir = os.path.join(build.topsrcdir, "build", "pgo", "certs")
|
|
certificateStatus = constructCertDatabase(build, certdir)
|
|
if certificateStatus:
|
|
print "TEST-UNEXPECTED-FAIL | SSL Server Certificate generation"
|
|
sys.exit(certificateStatus)
|