#!/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 from mozbuild.base import MozbuildObject from mozfile import NamedTemporaryFile 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): 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) proc.communicate(inputdata) return proc.returncode def createRandomFile(randomFile): for count in xrange(0, 2048): randomFile.write(chr(random.randint(0, 255))) def createCertificateAuthority(build, srcDir): certutil = build.get_binary_path(what="certutil") pk12util = build.get_binary_path(what="pk12util") #TODO: mozfile.TemporaryDirectory tempDbDir = tempfile.mkdtemp() with NamedTemporaryFile() as pwfile, NamedTemporaryFile() as rndfile: pgoCAModulePathSrc = os.path.join(srcDir, "pgoca.p12") pgoCAPathSrc = os.path.join(srcDir, "pgoca.ca") pwfile.write("\n") # Create temporary certification database for CA generation status = runUtil(certutil, ["-N", "-d", tempDbDir, "-f", pwfile.name]) if status: return status createRandomFile(rndfile) status = runUtil(certutil, ["-S", "-d", tempDbDir, "-s", "CN=Temporary Certificate Authority, O=Mozilla Testing, OU=Profile Guided Optimization", "-t", "C,,", "-x", "-m", "1", "-v", "120", "-n", "pgo temporary ca", "-2", "-f", pwfile.name, "-z", rndfile.name], "Y\n0\nN\n") if status: return status status = runUtil(certutil, ["-L", "-d", tempDbDir, "-n", "pgo temporary ca", "-a", "-o", pgoCAPathSrc, "-f", pwfile.name]) if status: return status status = runUtil(pk12util, ["-o", pgoCAModulePathSrc, "-n", "pgo temporary ca", "-d", tempDbDir, "-w", pwfile.name, "-k", pwfile.name]) if status: return status shutil.rmtree(tempDbDir) return 0 def createSSLServerCertificate(build, srcDir): certutil = build.get_binary_path(what="certutil") pk12util = build.get_binary_path(what="pk12util") with NamedTemporaryFile() as pwfile, NamedTemporaryFile() as rndfile: pgoCAPath = os.path.join(srcDir, "pgoca.p12") pwfile.write("\n") if not dbFilesExist(srcDir): # Make sure all DB files from src are really deleted unlinkDbFiles(srcDir) # Create certification database for ssltunnel status = runUtil(certutil, ["-N", "-d", srcDir, "-f", pwfile.name]) if status: return status status = runUtil(pk12util, ["-i", pgoCAPath, "-w", pwfile.name, "-d", srcDir, "-k", pwfile.name]) if status: return status # Generate automatic certificate locations = ServerLocations(os.path.join(build.topsrcdir, "build", "pgo", "server-locations.txt")) iterator = iter(locations) # Skips the first entry, I don't know why: bug 879740 iterator.next() locationsParam = "" firstLocation = "" for loc in iterator: if loc.scheme == "https" and "nocert" not in loc.options: customCertOption = False customCertRE = re.compile("^cert=(?:\w+)") for option in loc.options: match = customCertRE.match(option) if match: customCertOption = True break if not customCertOption: if len(locationsParam) > 0: locationsParam += "," locationsParam += loc.host if firstLocation == "": firstLocation = loc.host if not firstLocation: print "Nothing to generate, no automatic secure hosts specified" else: createRandomFile(rndfile) runUtil(certutil, ["-D", "-n", "pgo server certificate", "-d", srcDir, "-z", rndfile.name, "-f", pwfile.name]) # Ignore the result, the certificate may not be present when new database is being built status = runUtil(certutil, ["-S", "-s", "CN=%s" % firstLocation, "-t", "Pu,,", "-c", "pgo temporary ca", "-m", "2", "-8", locationsParam, "-v", "120", "-n", "pgo server certificate", "-d", srcDir, "-z", rndfile.name, "-f", pwfile.name]) if status: return status return 0 if len(sys.argv) == 1: print "Specify --gen-server or --gen-ca" sys.exit(1) build = MozbuildObject.from_environment() certdir = os.path.join(build.topsrcdir, "build", "pgo", "certs") if sys.argv[1] == "--gen-server": certificateStatus = createSSLServerCertificate(build, certdir) if certificateStatus: print "TEST-UNEXPECTED-FAIL | SSL Server Certificate generation" sys.exit(certificateStatus) if sys.argv[1] == "--gen-ca": certificateStatus = createCertificateAuthority(build, certdir) if certificateStatus: print "TEST-UNEXPECTED-FAIL | Certificate Authority generation" else: print "\n\n" print "===================================================" print " IMPORTANT:" print " To use this new certificate authority in tests" print " run 'make' at testing/mochitest" print "===================================================" sys.exit(certificateStatus) print "Invalid option specified" sys.exit(1)