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)