Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(203)

Side by Side Diff: net/data/verify_certificate_chain_unittest/common.py

Issue 1414393008: Add scripts to generate simple test data for certificate verification. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@key_usages
Patch Set: rebase Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #!/usr/bin/python
2 # Copyright (c) 2015 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """Set of helpers to generate signed X.509v3 certificates.
7
8 This works by shelling out calls to the 'openssl req' and 'openssl ca'
9 commands, and passing the appropriate command line flags and configuration file
10 (.cnf).
11 """
12
13 import base64
14 import os
15 import shutil
16 import subprocess
17 import sys
18
19 import openssl_conf
20
21 # Enum for the "type" of certificate that is to be created. This is used to
22 # select sane defaults for the .cnf file and command line flags, but they can
23 # all be overridden.
24 TYPE_CA = 2
25 TYPE_END_ENTITY = 3
26
27 # January 1st, 2015 midnight UTC
28 JANUARY_1_2015_UTC = '150101120000Z'
29
30 # January 1st, 2016 midnight UTC
31 JANUARY_1_2016_UTC = '160101120000Z'
32
33 # March 2nd, 2015 midnight UTC
34 DEFAULT_TIME = '150302120000Z'
35
36 # Counter used to generate unique (but readable) path names.
37 g_cur_path_id = 0
38
39 # Output paths used:
40 # - g_out_dir: where any temporary files (keys, cert req, signing db etc) are
41 # saved to.
42 # - g_out_pem: the path to the final output (which is a .pem file)
43 #
44 # See init() for how these are assigned, based on the name of the calling
45 # script.
46 g_out_dir = None
47 g_out_pem = None
48
49
50 def GetUniquePathId(name):
51 """Returns a base filename that contains 'name', but is unique to the output
52 directory"""
53 global g_cur_path_id
54
55 path_id = g_cur_path_id
56 g_cur_path_id += 1
57
58 # Use a short and clean name for the first use of this name.
59 if path_id == 0:
60 return name
61
62 # Otherwise append the count to make it unique.
63 return '%s_%d' % (name, path_id)
64
65
66 class Certificate(object):
67 """Helper for building an X.509 certificate."""
68
69 def __init__(self, name, cert_type, issuer):
70 # The name will be used for the subject's CN, and also as a component of
71 # the temporary filenames to help with debugging.
72 self.name = name
73 self.path_id = GetUniquePathId(name)
74
75 # The issuer is also a Certificate object. Passing |None| means it is a
76 # self-signed certificate.
77 self.issuer = issuer
78 if issuer is None:
79 self.issuer = self
80
81 # The config contains all the OpenSSL options that will be passed via a
82 # .cnf file. Set up defaults.
83 self.config = openssl_conf.Config()
84 self.init_config()
85
86 # Some settings need to be passed as flags rather than in the .cnf file.
87 # Technically these can be set though a .cnf, however doing so makes it
88 # sticky to the issuing certificate, rather than selecting it per
89 # subordinate certificate.
90 self.validity_flags = []
91 self.md_flags = []
92
93 # By default OpenSSL will use the current time for the start time. Instead
94 # default to using a fixed timestamp for more predictabl results each time
95 # the certificates are re-generated.
96 self.set_validity_range(JANUARY_1_2015_UTC, JANUARY_1_2016_UTC)
97
98 # Use SHA-256 when THIS certificate is signed (setting it in the
99 # configuration would instead set the hash to use when signing other
100 # certificates with this one).
101 self.set_signature_hash('sha256')
102
103 # Set appropriate key usages and basic constraints. For flexibility in
104 # testing (since want to generate some flawed certificates) these are set
105 # on a per-certificate basis rather than automatically when signing.
106 if cert_type == TYPE_END_ENTITY:
107 self.get_extensions().set_property('keyUsage',
108 'critical,digitalSignature,keyEncipherment')
109 self.get_extensions().set_property('extendedKeyUsage',
110 'serverAuth,clientAuth')
111 else:
112 self.get_extensions().set_property('keyUsage',
113 'critical,keyCertSign,cRLSign')
114 self.get_extensions().set_property('basicConstraints', 'critical,CA:true')
115
116 # Tracks whether the PEM file for this certificate has been written (since
117 # generation is done lazily).
118 self.finalized = False
119
120 # Initialize any files that will be needed if this certificate is used to
121 # sign other certificates. Starts off serial numbers at 1, and will
122 # increment them for each signed certificate.
123 write_string_to_file('01\n', self.get_serial_path())
124 write_string_to_file('', self.get_database_path())
125
126
127 def generate_rsa_key(self, size_bits):
128 """Generates an RSA private key for the certificate."""
129 subprocess.check_call(
130 ['openssl', 'genrsa', '-out', self.get_key_path(), str(size_bits)])
131
132
133 def generate_ec_key(self, named_curve):
134 """Generates an EC private key for the certificate. |named_curve| can be
135 something like secp384r1"""
136 subprocess.check_call(
137 ['openssl', 'ecparam', '-out', self.get_key_path(),
138 '-name', named_curve, '-genkey'])
139
140
141 def set_validity_range(self, start_date, end_date):
142 """Sets the Validity notBefore and notAfter properties for the
143 certificate"""
144 self.validity_flags = ['-startdate', start_date, '-enddate', end_date]
145
146
147 def set_signature_hash(self, md):
148 """Sets the hash function that will be used when signing this certificate.
149 Can be sha1, sha256, sha512, md5, etc."""
150 self.md_flags = ['-md', md]
151
152
153 def get_extensions(self):
154 return self.config.get_section('req_ext')
155
156
157 def get_path(self, suffix):
158 """Forms a path to an output file for this certificate, containing the
159 indicated suffix. The certificate's name will be used as its basis."""
160 return os.path.join(g_out_dir, '%s%s' % (self.path_id, suffix))
161
162
163 def get_key_path(self):
164 return self.get_path('.key')
165
166
167 def get_cert_path(self):
168 return self.get_path('.pem')
169
170
171 def get_serial_path(self):
172 return self.get_path('.serial')
173
174
175 def get_csr_path(self):
176 return self.get_path('.csr')
177
178
179 def get_database_path(self):
180 return self.get_path('.db')
181
182
183 def get_config_path(self):
184 return self.get_path('.cnf')
185
186
187 def get_cert_pem(self):
188 # Finish generating a .pem file for the certificate.
189 self.finalize()
190
191 # Read the certificate data.
192 with open(self.get_cert_path(), 'r') as f:
193 return f.read()
194
195
196 def finalize(self):
197 """Finishes the certificate creation process. This generates any needed
198 key, creates and signs the CSR. On completion the resulting PEM file can be
199 found at self.get_cert_path()"""
200
201 if self.finalized:
202 return # Already finalized, no work needed.
203
204 self.finalized = True
205
206 # Ensure that the issuer has been "finalized", since its outputs need to be
207 # accessible. Note that self.issuer could be the same as self.
208 self.issuer.finalize()
209
210 # Ensure the certificate has a key. Callers have the option to generate a
211 # different type of key, but if that was not done default to a new 2048-bit
212 # RSA key.
213 if not os.path.isfile(self.get_key_path()):
214 self.generate_rsa_key(2048)
215
216 # Serialize the config to a file.
217 self.config.write_to_file(self.get_config_path())
218
219 # Create a CSR.
220 subprocess.check_call(
221 ['openssl', 'req', '-new',
222 '-key', self.get_key_path(),
223 '-out', self.get_csr_path(),
224 '-config', self.get_config_path()])
225
226 cmd = ['openssl', 'ca', '-batch', '-in',
227 self.get_csr_path(), '-out', self.get_cert_path(), '-config',
228 self.issuer.get_config_path()]
229
230 if self.issuer == self:
231 cmd.append('-selfsign')
232
233 # Add in any extra flags.
234 cmd.extend(self.validity_flags)
235 cmd.extend(self.md_flags)
236
237 # Run the 'openssl ca' command.
238 subprocess.check_call(cmd)
239
240
241 def init_config(self):
242 """Initializes default properties in the certificate .cnf file that are
243 generic enough to work for all certificates (but can be overridden later).
244 """
245
246 # --------------------------------------
247 # 'req' section
248 # --------------------------------------
249
250 section = self.config.get_section('req')
251
252 section.set_property('encrypt_key', 'no')
253 section.set_property('utf8', 'yes')
254 section.set_property('string_mask', 'utf8only')
255 section.set_property('prompt', 'no')
256 section.set_property('distinguished_name', 'req_dn')
257 section.set_property('req_extensions', 'req_ext')
258
259 # --------------------------------------
260 # 'req_dn' section
261 # --------------------------------------
262
263 # This section describes the certificate subject's distinguished name.
264
265 section = self.config.get_section('req_dn')
266 section.set_property('commonName', '"%s"' % (self.name))
267
268 # --------------------------------------
269 # 'req_ext' section
270 # --------------------------------------
271
272 # This section describes the certificate's extensions.
273
274 section = self.config.get_section('req_ext')
275 section.set_property('subjectKeyIdentifier', 'hash')
276
277 # --------------------------------------
278 # SECTIONS FOR CAs
279 # --------------------------------------
280
281 # The following sections are used by the 'openssl ca' and relate to the
282 # signing operation. They are not needed for end-entity certificate
283 # configurations, but only if this certifiate will be used to sign other
284 # certificates.
285
286 # --------------------------------------
287 # 'ca' section
288 # --------------------------------------
289
290 section = self.config.get_section('ca')
291 section.set_property('default_ca', 'root_ca')
292
293 section = self.config.get_section('root_ca')
294 section.set_property('certificate', self.get_cert_path())
295 section.set_property('private_key', self.get_key_path())
296 section.set_property('new_certs_dir', g_out_dir)
297 section.set_property('serial', self.get_serial_path())
298 section.set_property('database', self.get_database_path())
299 section.set_property('unique_subject', 'no')
300
301 # These will get overridden via command line flags.
302 section.set_property('default_days', '365')
303 section.set_property('default_md', 'sha256')
304
305 section.set_property('policy', 'policy_anything')
306 section.set_property('email_in_dn', 'no')
307 section.set_property('preserve', 'yes')
308 section.set_property('name_opt', 'multiline,-esc_msb,utf8')
309 section.set_property('cert_opt', 'ca_default')
310 section.set_property('copy_extensions', 'copy')
311 section.set_property('x509_extensions', 'signing_ca_ext')
312 section.set_property('default_crl_days', '30')
313 section.set_property('crl_extensions', 'crl_ext')
314
315 section = self.config.get_section('policy_anything')
316 section.set_property('domainComponent', 'optional')
317 section.set_property('countryName', 'optional')
318 section.set_property('stateOrProvinceName', 'optional')
319 section.set_property('localityName', 'optional')
320 section.set_property('organizationName', 'optional')
321 section.set_property('organizationalUnitName', 'optional')
322 section.set_property('commonName', 'optional')
323 section.set_property('emailAddress', 'optional')
324
325 section = self.config.get_section('signing_ca_ext')
326 section.set_property('subjectKeyIdentifier', 'hash')
327 section.set_property('authorityKeyIdentifier', 'keyid:always')
328 section.set_property('authorityInfoAccess', '@issuer_info')
329 section.set_property('crlDistributionPoints', '@crl_info')
330
331 section = self.config.get_section('issuer_info')
332 section.set_property('caIssuers;URI.0',
333 'http://url-for-aia/%s.cer' % (self.name))
334
335 section = self.config.get_section('crl_info')
336 section.set_property('URI.0', 'http://url-for-crl/%s.crl' % (self.name))
337
338 section = self.config.get_section('crl_ext')
339 section.set_property('authorityKeyIdentifier', 'keyid:always')
340 section.set_property('authorityInfoAccess', '@issuer_info')
341
342
343 def data_to_pem(block_header, block_data):
344 return '-----BEGIN %s-----\n%s\n-----END %s-----\n' % (block_header,
345 base64.b64encode(block_data), block_header)
346
347
348 def write_test_file(description, chain, trusted_certs, utc_time, verify_result):
349 """Writes a test file that contains all the inputs necessary to run a
350 verification on a certificate chain"""
351
352 # Prepend the script name that generated the file to the description.
353 test_data = '[Created by: %s]\n\n%s\n' % (sys.argv[0], description)
354
355 # Write the certificate chain to the output file.
356 for cert in chain:
357 test_data += '\n' + cert.get_cert_pem()
358
359 # Write the trust store.
360 for cert in trusted_certs:
361 cert_data = cert.get_cert_pem()
362 # Use a different block type in the .pem file.
363 cert_data = cert_data.replace('CERTIFICATE', 'TRUSTED_CERTIFICATE')
364 test_data += '\n' + cert_data
365
366 test_data += '\n' + data_to_pem('TIME', utc_time)
367
368 verify_result_string = 'SUCCESS' if verify_result else 'FAIL'
369 test_data += '\n' + data_to_pem('VERIFY_RESULT', verify_result_string)
370
371 write_string_to_file(test_data, g_out_pem)
372
373
374 def write_string_to_file(data, path):
375 with open(path, 'w') as f:
376 f.write(data)
377
378
379 def init(invoking_script_path):
380 """Creates an output directory to contain all the temporary files that may be
381 created, as well as determining the path for the final output. These paths
382 are all based off of the name of the calling script.
383 """
384
385 global g_out_dir
386 global g_out_pem
387
388 # Base the output name off of the invoking script's name.
389 out_name = os.path.splitext(os.path.basename(invoking_script_path))[0]
390
391 # Strip the leading 'generate-'
392 if out_name.startswith('generate-'):
393 out_name = out_name[9:]
394
395 # Use an output directory with the same name as the invoking script.
396 g_out_dir = os.path.join('out', out_name)
397
398 # Ensure the output directory exists and is empty.
399 sys.stdout.write('Creating output directory: %s\n' % (g_out_dir))
400 shutil.rmtree(g_out_dir, True)
401 os.makedirs(g_out_dir)
402
403 g_out_pem = os.path.join('%s.pem' % (out_name))
404
405
406 def create_self_signed_root_certificate(name):
407 return Certificate(name, TYPE_CA, None)
408
409
410 def create_intermediary_certificate(name, issuer):
411 return Certificate(name, TYPE_CA, issuer)
412
413
414 def create_end_entity_certificate(name, issuer):
415 return Certificate(name, TYPE_END_ENTITY, issuer)
416
417 init(sys.argv[0])
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698