|
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/. |
|
4 |
|
5 # This file requires openssl 1.0.0 at least |
|
6 |
|
7 import os |
|
8 import random |
|
9 import pexpect |
|
10 import time |
|
11 import sys |
|
12 |
|
13 def init_dsa(db_dir): |
|
14 """ |
|
15 Initialize dsa parameters |
|
16 |
|
17 Sets up a set of params to be reused for DSA key generation |
|
18 |
|
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") |
|
24 |
|
25 |
|
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 |
|
32 |
|
33 Preconditions: |
|
34 if dsa keys are to be generated init_dsa must have been called before. |
|
35 |
|
36 |
|
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. |
|
53 |
|
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 + "'") |
|
75 |
|
76 extensions_filename = db_dir + "/openssl-exts" |
|
77 f = open(extensions_filename,'w') |
|
78 f.write(ext_text) |
|
79 f.close() |
|
80 |
|
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 |
|
96 |
|
97 |
|
98 |
|
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). |
|
107 |
|
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' |
|
125 |
|
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) |
|
131 |
|
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) |
|
142 |
|
143 return int_key, int_cert, ee_key, ee_cert |
|
144 |
|
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. |
|
151 |
|
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 |
|
179 |
|
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] |
|
210 |
|
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 |
|
247 |
|
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 """ |
|
269 |
|
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 |
|
311 |