Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 # This file requires openssl 1.0.0 at least
7 import os
8 import random
9 import pexpect
10 import time
11 import sys
13 def init_dsa(db_dir):
14 """
15 Initialize dsa parameters
17 Sets up a set of params to be reused for DSA key generation
19 Arguments:
20 db_dir -- location of the temporary params for the certificate
21 """
22 dsa_key_params = db_dir + "/dsa_param.pem"
23 os.system("openssl dsaparam -out " + dsa_key_params + " 2048")
26 def generate_cert_generic(db_dir, dest_dir, serial_num, key_type, name,
27 ext_text, signer_key_filename = "",
28 signer_cert_filename = "",
29 subject_string = ""):
30 """
31 Generate an x509 certificate with a sha256 signature
33 Preconditions:
34 if dsa keys are to be generated init_dsa must have been called before.
37 Arguments:
38 db_dir -- location of the temporary params for the certificate
39 dest_dir -- location of the x509 cert
40 serial_num -- serial number for the cert (must be unique for each signer
41 key)
42 key_type -- the type of key generated: potential values: 'rsa', 'dsa',
43 or any of the curves found by 'openssl ecparam -list_curves'
44 name -- the common name for the cert, will match the prefix of the
45 output cert
46 ext_text -- the text for the x509 extensions to be added to the
47 certificate
48 signer_key_filename -- the filename of the key from which the cert will
49 be signed if null the cert will be self signed (think CA
50 roots).
51 signer_cert_filename -- the certificate that will sign the certificate
52 (used to extract signer info) it must be in DER format.
54 output:
55 key_name -- the filename of the key file (PEM format)
56 cert_name -- the filename of the output certificate (DER format)
57 """
58 key_name = db_dir + "/"+ name + ".key"
59 if key_type == 'rsa':
60 os.system ("openssl genpkey -algorithm RSA -out " + key_name +
61 " -pkeyopt rsa_keygen_bits:2048")
62 elif key_type == 'dsa':
63 dsa_key_params = db_dir + "/dsa_param.pem"
64 os.system("openssl gendsa -out " + key_name + " " + dsa_key_params)
65 else:
66 #assume is ec
67 os.system("openssl ecparam -out " + key_name + " -name "+ key_type +
68 " -genkey");
69 csr_name = db_dir + "/"+ name + ".csr"
70 if not subject_string:
71 subject_string = '/CN=' + name
72 os.system ("openssl req -new -key " + key_name + " -days 3650" +
73 " -extensions v3_ca -batch -out " + csr_name +
74 " -utf8 -subj '" + subject_string + "'")
76 extensions_filename = db_dir + "/openssl-exts"
77 f = open(extensions_filename,'w')
78 f.write(ext_text)
79 f.close()
81 cert_name = dest_dir + "/"+ name + ".der"
82 if not signer_key_filename:
83 signer_key_filename = key_name;
84 os.system ("openssl x509 -req -sha256 -days 3650 -in " + csr_name +
85 " -signkey " + signer_key_filename +
86 " -set_serial " + str(serial_num) +
87 " -extfile " + extensions_filename +
88 " -outform DER -out "+ cert_name)
89 else:
90 os.system ("openssl x509 -req -sha256 -days 3650 -in " + csr_name +
91 " -CAkey " + signer_key_filename +
92 " -CA " + signer_cert_filename + " -CAform DER " +
93 " -set_serial " + str(serial_num) + " -out " + cert_name +
94 " -outform DER -extfile " + extensions_filename)
95 return key_name, cert_name
99 def generate_int_and_ee(db_dir, dest_dir, ca_key, ca_cert, name, int_ext_text,
100 ee_ext_text, key_type = 'rsa'):
101 """
102 Generate an intermediate and ee signed by the generated intermediate. The
103 name of the intermediate files will be the name '.der' or '.key'. The name
104 of the end entity files with be "ee-"+ name plus the appropiate prefixes.
105 The serial number will be generated radomly so it is potentially possible
106 to have problem (but very unlikely).
108 Arguments:
109 db_dir -- location of the temporary params for the certificate
110 dest_dir -- location of the x509 cert
111 ca_key -- The filename of the key that will be used to sign the
112 intermediate (PEM FORMAT)
113 ca_cert -- The filename of the cert that will be used to sign the
114 intermediate, it MUST be the private key for the ca_key.
115 The file must be in DER format.
116 name -- the common name for the intermediate, will match the prefix
117 of the output intermediate. The ee will have the name
118 prefixed with "ee-"
119 int_ext_text -- the text for the x509 extensions to be added to the
120 intermediate certificate
121 ee_ext_text -- the text for the x509 extensions to be added to the
122 end entity certificate
123 key_type -- the type of key generated: potential values: 'rsa', 'dsa',
124 or any of the curves found by 'openssl ecparam -list_curves'
126 output:
127 int_key -- the filename of the intermeidate key file (PEM format)
128 int_cert -- the filename of the intermediate certificate (DER format)
129 ee_key -- the filename of the end entity key file (PEM format)
130 ee_cert -- the filename of the end entity certficate (DER format)
132 """
133 [int_key, int_cert] = generate_cert_generic(db_dir, dest_dir,
134 random.randint(100,40000000),
135 key_type, "int-" + name,
136 int_ext_text,
137 ca_key, ca_cert)
138 [ee_key, ee_cert] = generate_cert_generic(db_dir, dest_dir,
139 random.randint(100,40000000),
140 key_type, name,
141 ee_ext_text, int_key, int_cert)
143 return int_key, int_cert, ee_key, ee_cert
145 def generate_pkcs12(db_dir, dest_dir, der_cert_filename, key_pem_filename,
146 prefix):
147 """
148 Generate a pkcs12 file for a given certificate name (in der format) and
149 a key filename (key in pem format). The output file will have an empty
150 password.
152 Arguments:
153 input:
154 db_dir -- location of the temporary params for the certificate
155 dest_dir -- location of the x509 cert
156 der_cert_filename -- the filename of the certificate to be included in the
157 output file (DER format)
158 key_pem_filename -- the filename of the private key of the certificate to
159 (PEM format)
160 prefix -- the name to be prepended to the output pkcs12 file.
161 output:
162 pk12_filename -- the filename of the outgoing pkcs12 output file
163 """
164 #make pem cert file from der filename
165 pem_cert_filename = db_dir + "/" + prefix + ".pem"
166 pk12_filename = dest_dir + "/" + prefix + ".p12"
167 os.system("openssl x509 -inform der -in " + der_cert_filename + " -out " +
168 pem_cert_filename )
169 #now make pkcs12 file
170 child = pexpect.spawn("openssl pkcs12 -export -in " + pem_cert_filename +
171 " -inkey " + key_pem_filename + " -out " +
172 pk12_filename)
173 child.expect('Enter Export Password:')
174 child.sendline('')
175 child.expect('Verifying - Enter Export Password:')
176 child.sendline('')
177 child.expect(pexpect.EOF)
178 return pk12_filename
180 def init_nss_db(db_dir):
181 """
182 Remove the current nss database in the specified directory and create a new
183 nss database with the cert8 format.
184 Arguments
185 db_dir -- the desired location of the new database
186 output
187 noise_file -- the path to a noise file suitable to generate TEST
188 certificates. This does not have enough entropy for a real
189 secret
190 pwd_file -- the patch to the secret file used for the database.
191 this file should be empty.
192 """
193 nss_db_files = ["cert8.db", "key3.db", "secmod.db", "noise", "pwdfile"]
194 for file in nss_db_files:
195 if os.path.isfile(file):
196 os.remove(file)
197 # create noise file
198 noise_file = db_dir + "/noise"
199 nf = open(noise_file, 'w')
200 nf.write(str(time.time()))
201 nf.close()
202 # create pwd file
203 pwd_file = db_dir + "/pwfile"
204 pf = open(pwd_file, 'w')
205 pf.write("\n")
206 pf.close()
207 # create nss db
208 os.system("certutil -d " + db_dir + " -N -f " + pwd_file);
209 return [noise_file, pwd_file]
211 def generate_ca_cert(db_dir, dest_dir, noise_file, name, version, do_bc):
212 """
213 Creates a new CA certificate in an sql NSS database and as a der file
214 Arguments:
215 db_dir -- the location of the nss database (in sql format)
216 dest_dir -- the location of for the output file
217 noise_file -- the location of a noise file.
218 name -- the nickname of the new certificate in the database and the
219 common name of the certificate
220 version -- the version number of the certificate (valid certs must use
221 3)
222 do_bc -- if the certificate should include the basic constraints
223 (valid ca's should be true)
224 output:
225 outname -- the location of the der file.
226 """
227 out_name = dest_dir + "/" + name + ".der"
228 base_exec_string = ("certutil -S -z " + noise_file + " -g 2048 -d " +
229 db_dir + "/ -n " + name + " -v 120 -s 'CN=" + name +
230 ",O=PSM Testing,L=Mountain View,ST=California,C=US'" +
231 " -t C,C,C -x --certVersion=" + str(int(version)))
232 if (do_bc):
233 child = pexpect.spawn(base_exec_string + " -2")
234 child.logfile = sys.stdout
235 child.expect('Is this a CA certificate \[y/N\]?')
236 child.sendline('y')
237 child.expect('Enter the path length constraint, enter to skip \[<0 for unlimited path\]: >')
238 child.sendline('')
239 child.expect('Is this a critical extension \[y/N\]?')
240 child.sendline('')
241 child.expect(pexpect.EOF)
242 else:
243 os.system(base_exec_string)
244 os.system("certutil -d " + db_dir + "/ -L -n " + name + " -r > " +
245 out_name)
246 return out_name
248 def generate_child_cert(db_dir, dest_dir, noise_file, name, ca_nick, version,
249 do_bc, is_ee, ocsp_url):
250 """
251 Creates a new child certificate in an sql NSS database and as a der file
252 Arguments:
253 db_dir -- the location of the nss database (in sql format)
254 dest_dir -- the location of for the output file
255 noise_file -- the location of a noise file.
256 name -- the nickname of the new certificate in the database and the
257 common name of the certificate
258 ca_nick -- the nickname of the isser of the new certificate
259 version -- the version number of the certificate (valid certs must use
260 3)
261 do_bc -- if the certificate should include the basic constraints
262 (valid ca's should be true)
263 is_ee -- is this and End Entity cert? false means intermediate
264 ocsp_url -- optional location of the ocsp responder for this certificate
265 this is included only if do_bc is set to True
266 output:
267 outname -- the location of the der file.
268 """
270 out_name = dest_dir + "/" + name + ".der"
271 base_exec_string = ("certutil -S -z " + noise_file + " -g 2048 -d " +
272 db_dir + "/ -n " + name + " -v 120 -m " +
273 str(random.randint(100, 40000000)) + " -s 'CN=" + name +
274 ",O=PSM Testing,L=Mountain View,ST=California,C=US'" +
275 " -t C,C,C -c " + ca_nick + " --certVersion=" +
276 str(int(version)))
277 if (do_bc):
278 extra_arguments = " -2"
279 if (ocsp_url):
280 extra_arguments += " --extAIA"
281 child = pexpect.spawn(base_exec_string + extra_arguments)
282 child.logfile = sys.stdout
283 child.expect('Is this a CA certificate \[y/N\]?')
284 if (is_ee):
285 child.sendline('N')
286 else:
287 child.sendline('y')
288 child.expect('Enter the path length constraint, enter to skip \[<0 for unlimited path\]: >')
289 child.sendline('')
290 child.expect('Is this a critical extension \[y/N\]?')
291 child.sendline('')
292 if (ocsp_url):
293 child.expect('\s+Choice >')
294 child.sendline('2')
295 child.expect('\s+Choice: >')
296 child.sendline('7')
297 child.expect('Enter data:')
298 child.sendline(ocsp_url)
299 child.expect('\s+Choice: >')
300 child.sendline('0')
301 child.expect('Add another location to the Authority Information Access extension \[y/N\]')
302 child.sendline('')
303 child.expect('Is this a critical extension \[y/N\]?')
304 child.sendline('')
305 child.expect(pexpect.EOF)
306 else:
307 os.system(base_exec_string)
308 os.system("certutil -d " + db_dir + "/ -L -n " + name + " -r > " +
309 out_name)
310 return out_name