Source code

Revision control

Other Tools

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