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

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: use __doc__ like a boss Created 5 years, 1 month 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;
mattm 2015/11/02 19:40:39 unnecessary semicolons
eroman 2015/11/02 20:51:43 Done.
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.InitConfig()
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.SetValidityRange(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.SetSignatureHash('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.GetExtensions().SetProperty('keyUsage',
108 'critical,digitalSignature,keyEncipherment')
109 self.GetExtensions().SetProperty('extendedKeyUsage',
110 'serverAuth,clientAuth')
111 else:
112 self.GetExtensions().SetProperty('keyUsage',
113 'critical,keyCertSign,cRLSign')
114 self.GetExtensions().SetProperty('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 WriteStringToFile('01\n', self.GetSerialPath())
124 WriteStringToFile('', self.GetDatabasePath())
125
126
127 def GenerateRsaKey(self, size_bits):
128 """Generates an RSA private key for the certificate."""
129 subprocess.check_call(
130 ['openssl', 'genrsa', '-out', self.GetKeyPath(), str(size_bits)])
131
132
133 def GenerateEcKey(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.GetKeyPath(), '-name', named_curve,
138 '-genkey'])
139
140
141 def SetValidityRange(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 SetSignatureHash(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 GetExtensions(self):
154 return self.config.GetSection('req_ext')
155
156
157 def GetPath(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 GetKeyPath(self):
164 return self.GetPath('.key')
165
166
167 def GetCertPath(self):
168 return self.GetPath('.pem')
169
170
171 def GetSerialPath(self):
172 return self.GetPath('.serial')
173
174
175 def GetCsrPath(self):
176 return self.GetPath('.csr')
177
178
179 def GetDatabasePath(self):
180 return self.GetPath('.db')
181
182
183 def GetConfigPath(self):
184 return self.GetPath('.cnf')
185
186
187 def GetCertPem(self):
188 # Finish generating a .pem file for the certificate.
189 self.Finalize()
190
191 # Read the certificate data.
192 with open(self.GetCertPath(), '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.GetCertPath()"""
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.GetKeyPath()):
214 self.GenerateRsaKey(2048)
215
216 # Serialize the config to a file.
217 self.config.WriteToFile(self.GetConfigPath())
218
219 # Create a CSR.
220 subprocess.check_call(
221 ['openssl', 'req', '-new',
222 '-key', self.GetKeyPath(),
223 '-out', self.GetCsrPath(),
224 '-config', self.GetConfigPath()])
225
226 cmd = ['openssl', 'ca', '-batch', '-in',
227 self.GetCsrPath(), '-out', self.GetCertPath(), '-config',
228 self.issuer.GetConfigPath()]
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 InitConfig(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.GetSection('req')
251
252 section.SetProperty('encrypt_key', 'no')
253 section.SetProperty('utf8', 'yes')
254 section.SetProperty('string_mask', 'utf8only')
255 section.SetProperty('prompt', 'no')
256 section.SetProperty('distinguished_name', 'req_dn')
257 section.SetProperty('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.GetSection('req_dn')
266 section.SetProperty('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.GetSection('req_ext')
275 section.SetProperty('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.GetSection('ca')
291 section.SetProperty('default_ca', 'root_ca')
292
293 section = self.config.GetSection('root_ca')
294 section.SetProperty('certificate', self.GetCertPath())
295 section.SetProperty('private_key', self.GetKeyPath())
296 section.SetProperty('new_certs_dir', g_out_dir)
297 section.SetProperty('serial', self.GetSerialPath())
298 section.SetProperty('database', self.GetDatabasePath())
299 section.SetProperty('unique_subject', 'no')
300
301 # These will get overridden via command line flags.
302 section.SetProperty('default_days', '365')
303 section.SetProperty('default_md', 'sha256')
304
305 section.SetProperty('policy', 'policy_anything')
306 section.SetProperty('email_in_dn', 'no')
307 section.SetProperty('preserve', 'yes')
308 section.SetProperty('name_opt', 'multiline,-esc_msb,utf8')
309 section.SetProperty('cert_opt', 'ca_default')
310 section.SetProperty('copy_extensions', 'copy')
311 section.SetProperty('x509_extensions', 'signing_ca_ext')
312 section.SetProperty('default_crl_days', '30')
313 section.SetProperty('crl_extensions', 'crl_ext')
314
315 section = self.config.GetSection('policy_anything')
316 section.SetProperty('domainComponent', 'optional')
317 section.SetProperty('countryName', 'optional')
318 section.SetProperty('stateOrProvinceName', 'optional')
319 section.SetProperty('localityName', 'optional')
320 section.SetProperty('organizationName', 'optional')
321 section.SetProperty('organizationalUnitName', 'optional')
322 section.SetProperty('commonName', 'optional')
323 section.SetProperty('emailAddress', 'optional')
324
325 section = self.config.GetSection('signing_ca_ext')
326 section.SetProperty('subjectKeyIdentifier', 'hash')
327 section.SetProperty('authorityKeyIdentifier', 'keyid:always')
328 section.SetProperty('authorityInfoAccess', '@issuer_info')
329 section.SetProperty('crlDistributionPoints', '@crl_info')
330
331 section = self.config.GetSection('issuer_info')
332 section.SetProperty('caIssuers;URI.0',
333 'http://url-for-aia/%s.cer' % (self.name))
334
335 section = self.config.GetSection('crl_info')
336 section.SetProperty('URI.0', 'http://url-for-crl/%s.crl' % (self.name))
337
338 section = self.config.GetSection('crl_ext')
339 section.SetProperty('authorityKeyIdentifier', 'keyid:always')
340 section.SetProperty('authorityInfoAccess', '@issuer_info')
341
342
343 def DataToPem(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 WriteTestFile(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.GetCertPem()
358
359 # Write the trust store.
360 for cert in trusted_certs:
361 cert_data = cert.GetCertPem()
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' + DataToPem('TIME', utc_time)
367
368 verify_result_string = 'SUCCESS' if verify_result else 'FAIL'
369 test_data += '\n' + DataToPem('VERIFY_RESULT', verify_result_string)
370
371 WriteStringToFile(test_data, g_out_pem)
372
373
374 def WriteStringToFile(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 CreateSelfSignedRootCertificate(name):
407 return Certificate(name, TYPE_CA, None)
408
409
410 def CreateIntermediaryCertificate(name, issuer):
411 return Certificate(name, TYPE_CA, issuer)
412
413
414 def CreateEndEntityCertificate(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