2013-05-28 23:33:57 +04:00
|
|
|
#!/usr/bin/env python
|
2012-05-21 15:12:37 +04:00
|
|
|
# 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/.
|
2008-09-05 17:35:58 +04:00
|
|
|
|
2013-05-28 23:33:57 +04:00
|
|
|
# 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/ .
|
|
|
|
|
2008-09-05 17:35:58 +04:00
|
|
|
import os
|
2013-05-30 04:17:32 +04:00
|
|
|
import random
|
2008-09-05 17:35:58 +04:00
|
|
|
import re
|
2008-10-21 19:50:38 +04:00
|
|
|
import shutil
|
2013-05-28 23:33:57 +04:00
|
|
|
import subprocess
|
2008-09-05 17:35:58 +04:00
|
|
|
import sys
|
2022-11-24 20:23:47 +03:00
|
|
|
from distutils.spawn import find_executable
|
2008-09-05 17:35:58 +04:00
|
|
|
|
2022-11-24 20:23:47 +03:00
|
|
|
import mozinfo
|
|
|
|
from mozbuild.base import BinaryNotFoundException, MozbuildObject
|
2018-04-23 12:14:17 +03:00
|
|
|
from mozfile import NamedTemporaryFile, TemporaryDirectory
|
2013-05-28 23:33:57 +04:00
|
|
|
from mozprofile.permissions import ServerLocations
|
2010-01-15 20:22:54 +03:00
|
|
|
|
2008-09-05 17:35:58 +04:00
|
|
|
dbFiles = [
|
2018-05-22 00:56:34 +03:00
|
|
|
re.compile("^cert[0-9]+\.db$"),
|
|
|
|
re.compile("^key[0-9]+\.db$"),
|
|
|
|
re.compile("^secmod\.db$"),
|
2008-09-05 17:35:58 +04:00
|
|
|
]
|
|
|
|
|
2018-05-22 00:56:34 +03:00
|
|
|
|
2008-09-05 17:35:58 +04:00
|
|
|
def unlinkDbFiles(path):
|
2018-05-22 00:56:34 +03:00
|
|
|
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))
|
|
|
|
|
2008-10-21 19:50:38 +04:00
|
|
|
|
|
|
|
def dbFilesExist(path):
|
2018-05-22 00:56:34 +03:00
|
|
|
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,
|
2020-06-09 21:48:19 +03:00
|
|
|
stdout=outputstream,
|
|
|
|
universal_newlines=True,
|
|
|
|
)
|
2018-05-22 00:56:34 +03:00
|
|
|
proc.communicate(inputdata)
|
|
|
|
return proc.returncode
|
|
|
|
|
2008-09-05 17:35:58 +04:00
|
|
|
|
|
|
|
def createRandomFile(randomFile):
|
2018-05-22 00:56:34 +03:00
|
|
|
for count in xrange(0, 2048):
|
|
|
|
randomFile.write(chr(random.randint(0, 255)))
|
|
|
|
|
2008-09-05 17:35:58 +04:00
|
|
|
|
2018-04-23 12:14:17 +03:00
|
|
|
def writeCertspecForServerLocations(fd):
|
|
|
|
locations = ServerLocations(
|
|
|
|
os.path.join(build.topsrcdir, "build", "pgo", "server-locations.txt")
|
|
|
|
)
|
2018-05-22 00:56:34 +03:00
|
|
|
SAN = []
|
2018-04-23 12:14:17 +03:00
|
|
|
for loc in [
|
|
|
|
i for i in iter(locations) if i.scheme == "https" and "nocert" not in i.options
|
|
|
|
]:
|
2018-05-22 00:56:34 +03:00
|
|
|
customCertOption = False
|
|
|
|
customCertRE = re.compile("^cert=(?:\w+)")
|
|
|
|
for _ in [i for i in loc.options if customCertRE.match(i)]:
|
|
|
|
customCertOption = True
|
|
|
|
break
|
2018-04-23 12:14:17 +03:00
|
|
|
|
2020-02-27 02:15:02 +03:00
|
|
|
if "ipV4Address" in loc.options:
|
|
|
|
loc.host = "ip4:" + loc.host
|
|
|
|
|
2018-05-22 00:56:34 +03:00
|
|
|
if not customCertOption:
|
|
|
|
SAN.append(loc.host)
|
2018-04-23 12:14:17 +03:00
|
|
|
|
2018-05-22 00:58:19 +03:00
|
|
|
fd.write(
|
|
|
|
"issuer:printableString/CN=Temporary Certificate Authority/O=Mozilla Testing/OU=Profile Guided Optimization\n" # NOQA: E501
|
|
|
|
)
|
2018-04-23 12:14:17 +03:00
|
|
|
fd.write("subject:{}\n".format(SAN[0]))
|
|
|
|
fd.write("extension:subjectAlternativeName:{}\n".format(",".join(SAN)))
|
|
|
|
|
2018-05-22 00:56:34 +03:00
|
|
|
|
2018-04-23 12:14:17 +03:00
|
|
|
def constructCertDatabase(build, srcDir):
|
2020-04-21 14:58:04 +03:00
|
|
|
try:
|
|
|
|
certutil = build.get_binary_path(what="certutil")
|
|
|
|
pk12util = build.get_binary_path(what="pk12util")
|
|
|
|
except BinaryNotFoundException as e:
|
|
|
|
print("{}\n\n{}\n".format(e, e.help()))
|
|
|
|
return 1
|
2020-10-20 11:34:02 +03:00
|
|
|
openssl = find_executable("openssl")
|
2021-04-15 01:24:11 +03:00
|
|
|
pycert = os.path.join(build.topsrcdir, "security", "manager", "tools", "pycert.py")
|
|
|
|
pykey = os.path.join(build.topsrcdir, "security", "manager", "tools", "pykey.py")
|
2018-05-22 00:56:34 +03:00
|
|
|
|
2020-06-09 21:48:19 +03:00
|
|
|
with NamedTemporaryFile(mode="wt+") as pwfile, TemporaryDirectory() as pemfolder:
|
2018-05-22 00:56:34 +03:00
|
|
|
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)
|
2020-10-26 21:21:44 +03:00
|
|
|
)
|
2018-05-22 00:56:34 +03:00
|
|
|
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,
|
|
|
|
[
|
2018-05-22 00:58:19 +03:00
|
|
|
"-A",
|
|
|
|
"-n",
|
|
|
|
name,
|
|
|
|
"-t",
|
|
|
|
"P,,",
|
|
|
|
"-i",
|
|
|
|
pem,
|
|
|
|
"-d",
|
|
|
|
srcDir,
|
|
|
|
"-f",
|
|
|
|
pwfile.name,
|
|
|
|
],
|
|
|
|
)
|
2018-05-22 00:56:34 +03:00
|
|
|
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"]:
|
2018-05-22 00:58:19 +03:00
|
|
|
raise Exception(
|
|
|
|
"{}: keyspec filenames must be of the form XXX.client.keyspec "
|
|
|
|
"or XXX.ca.keyspec (key_type={})".format(keyspec, key_type)
|
|
|
|
)
|
2018-05-22 00:56:34 +03:00
|
|
|
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):
|
2018-05-22 00:58:19 +03:00
|
|
|
raise Exception(
|
|
|
|
"There has to be a corresponding certificate named {} for "
|
|
|
|
"the keyspec {}".format(cert_pem, keyspec)
|
|
|
|
)
|
2018-05-22 00:56:34 +03:00
|
|
|
|
|
|
|
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",
|
2018-05-22 00:58:19 +03:00
|
|
|
cert_pem,
|
|
|
|
"-name",
|
|
|
|
name,
|
|
|
|
"-out",
|
|
|
|
p12,
|
|
|
|
"-passout",
|
|
|
|
"file:" + pwfile.name,
|
2020-10-26 21:21:44 +03:00
|
|
|
],
|
2018-05-22 00:58:19 +03:00
|
|
|
)
|
2018-05-22 00:56:34 +03:00
|
|
|
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))
|
2020-10-26 21:21:44 +03:00
|
|
|
)
|
2018-05-22 00:56:34 +03:00
|
|
|
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)
|
2020-10-26 21:21:44 +03:00
|
|
|
)
|
2018-05-22 00:56:34 +03:00
|
|
|
|
|
|
|
return 0
|
2018-02-27 01:55:35 +03:00
|
|
|
|
2008-09-05 17:35:58 +04:00
|
|
|
|
2013-05-28 23:33:57 +04:00
|
|
|
build = MozbuildObject.from_environment()
|
|
|
|
certdir = os.path.join(build.topsrcdir, "build", "pgo", "certs")
|
2018-04-23 12:14:17 +03:00
|
|
|
certificateStatus = constructCertDatabase(build, certdir)
|
|
|
|
if certificateStatus:
|
2018-05-22 00:58:19 +03:00
|
|
|
print("TEST-UNEXPECTED-FAIL | SSL Server Certificate generation")
|
2018-04-23 12:14:17 +03:00
|
|
|
sys.exit(certificateStatus)
|