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

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

Issue 2797303006: Save the private keys used by generated verify_certificate_chain tests. (Closed)
Patch Set: Created 3 years, 8 months 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
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # Copyright (c) 2015 The Chromium Authors. All rights reserved. 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 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Set of helpers to generate signed X.509v3 certificates. 6 """Set of helpers to generate signed X.509v3 certificates.
7 7
8 This works by shelling out calls to the 'openssl req' and 'openssl ca' 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 9 commands, and passing the appropriate command line flags and configuration file
10 (.cnf). 10 (.cnf).
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
45 KEY_PURPOSE_ANY = 'anyExtendedKeyUsage' 45 KEY_PURPOSE_ANY = 'anyExtendedKeyUsage'
46 KEY_PURPOSE_SERVER_AUTH = 'serverAuth' 46 KEY_PURPOSE_SERVER_AUTH = 'serverAuth'
47 KEY_PURPOSE_CLIENT_AUTH = 'clientAuth' 47 KEY_PURPOSE_CLIENT_AUTH = 'clientAuth'
48 48
49 DEFAULT_KEY_PURPOSE = KEY_PURPOSE_SERVER_AUTH 49 DEFAULT_KEY_PURPOSE = KEY_PURPOSE_SERVER_AUTH
50 50
51 # Counters used to generate unique (but readable) path names. 51 # Counters used to generate unique (but readable) path names.
52 g_cur_path_id = {} 52 g_cur_path_id = {}
53 53
54 # Output paths used: 54 # Output paths used:
55 # - g_out_dir: where any temporary files (keys, cert req, signing db etc) are 55 # - g_out_dir: where any temporary files (cert req, signing db etc) are
56 # saved to. 56 # saved to.
57 # - g_out_pem: the path to the final output (which is a .pem file) 57 # - g_script_name: the name of the invoking script. For instance if this is
58 # being run by generate-foo.py then g_script_name will be
59 # 'foo'
58 # 60 #
59 # See init() for how these are assigned, based on the name of the calling 61 # See init() for how these are assigned, based on the name of the calling
60 # script. 62 # script.
61 g_out_dir = None 63 g_out_dir = None
62 g_out_pem = None 64 g_script_name = None
63 65
64 # The default validity range of generated certificates. Can be modified with 66 # The default validity range of generated certificates. Can be modified with
65 # set_default_validity_range(). 67 # set_default_validity_range().
66 g_default_start_date = JANUARY_1_2015_UTC 68 g_default_start_date = JANUARY_1_2015_UTC
67 g_default_end_date = JANUARY_1_2016_UTC 69 g_default_end_date = JANUARY_1_2016_UTC
68 70
69 71
70 def set_default_validity_range(start_date, end_date): 72 def set_default_validity_range(start_date, end_date):
71 """Sets the validity range that will be used for certificates created with 73 """Sets the validity range that will be used for certificates created with
72 Certificate""" 74 Certificate"""
73 global g_default_start_date 75 global g_default_start_date
74 global g_default_end_date 76 global g_default_end_date
75 g_default_start_date = start_date 77 g_default_start_date = start_date
76 g_default_end_date = end_date 78 g_default_end_date = end_date
77 79
80
78 def get_unique_path_id(name): 81 def get_unique_path_id(name):
79 """Returns a base filename that contains 'name', but is unique to the output 82 """Returns a base filename that contains 'name', but is unique to the output
80 directory""" 83 directory"""
81 path_id = g_cur_path_id.get(name, 0) 84 path_id = g_cur_path_id.get(name, 0)
82 g_cur_path_id[name] = path_id + 1 85 g_cur_path_id[name] = path_id + 1
83 86
84 # Use a short and clean name for the first use of this name. 87 # Use a short and clean name for the first use of this name.
85 if path_id == 0: 88 if path_id == 0:
86 return name 89 return name
87 90
88 # Otherwise append the count to make it unique. 91 # Otherwise append the count to make it unique.
89 return '%s_%d' % (name, path_id) 92 return '%s_%d' % (name, path_id)
90 93
91 94
92 def get_path_in_output_dir(name, suffix): 95 def get_path_in_output_dir(name, suffix):
93 return os.path.join(g_out_dir, '%s%s' % (name, suffix)) 96 return os.path.join(g_out_dir, '%s%s' % (name, suffix))
94 97
95 98
96 def get_unique_path_in_output_dir(name, suffix):
97 return get_path_in_output_dir(get_unique_path_id(name), suffix)
98
99
100 class Key(object): 99 class Key(object):
101 """Describes a public + private key pair. It is a dumb wrapper around an 100 """Describes a public + private key pair. It is a dumb wrapper around an
102 on-disk key.""" 101 on-disk key."""
103 102
104 def __init__(self, path): 103 def __init__(self, path):
105 self.path = path 104 self.path = path
106 105
107 106
108 def get_path(self): 107 def get_path(self):
109 """Returns the path to a file that contains the key contents.""" 108 """Returns the path to a file that contains the key contents."""
110 return self.path 109 return self.path
111 110
112 111
113 def generate_rsa_key(size_bits, path=None): 112 def get_or_generate_key(generation_arguments, path):
114 """Generates an RSA private key and returns it as a Key object. If |path| is 113 """Helper function to either retrieve a key from an existing file |path|, or
115 specified the resulting key will be saved at that location.""" 114 generate a new one using the command line |generation_arguments|."""
116 if path is None:
117 path = get_unique_path_in_output_dir('RsaKey', 'key')
118 115
119 # Ensure the path doesn't already exists (otherwise will be overwriting 116 generation_arguments_str = ' '.join(generation_arguments)
120 # something).
121 assert not os.path.isfile(path)
122 117
123 subprocess.check_call( 118 # If the file doesn't already exist, generate a new key using the generation
124 ['openssl', 'genrsa', '-out', path, str(size_bits)]) 119 # parameters.
120 if not os.path.isfile(path):
121 key_contents = subprocess.check_output(generation_arguments)
122
123 # Prepend the generation parameters to the key file.
124 write_string_to_file(generation_arguments_str + '\n' + key_contents,
125 path)
126 else:
127 # If the path already exists, confirm that it is for the expected key type.
128 file_contents = read_file_to_string(path)
129 if not file_contents.startswith(generation_arguments_str):
mattm 2017/04/07 22:01:27 This could just match part of the generation args
eroman 2017/04/07 23:36:33 Good point! Done.
130 sys.stderr.write(
131 ('The existing key file %s is not compatible with the '
132 'requested parameters:\n%s.\n') % (path, generation_arguments_str))
mattm 2017/04/07 22:01:27 might be helpful to mention you can delete the key
eroman 2017/04/07 23:36:33 Done. I also print out the contradiction explicitl
133 sys.exit(1)
125 134
126 return Key(path) 135 return Key(path)
127 136
128 137
129 def generate_ec_key(named_curve, path=None): 138 def get_or_generate_rsa_key(size_bits, path):
130 """Generates an EC private key for the certificate and returns it as a Key 139 """Retrieves an existing key from a file if the path exists. Otherwise
131 object. |named_curve| can be something like secp384r1. If |path| is specified 140 generates an RSA key with the specified bit size and saves it to the path."""
132 the resulting key will be saved at that location.""" 141 return get_or_generate_key(['openssl', 'genrsa', str(size_bits)], path)
133 if path is None:
134 path = get_unique_path_in_output_dir('EcKey', 'key')
135 142
136 # Ensure the path doesn't already exists (otherwise will be overwriting
137 # something).
138 assert not os.path.isfile(path)
139 143
140 subprocess.check_call( 144 def get_or_generate_ec_key(named_curve, path):
141 ['openssl', 'ecparam', '-out', path, 145 """Retrieves an existing key from a file if the path exists. Otherwise
142 '-name', named_curve, '-genkey']) 146 generates an EC key with the specified named curve and saves it to the
147 path."""
148 return get_or_generate_key(['openssl', 'ecparam', '-name', named_curve,
149 '-genkey'], path)
143 150
144 return Key(path) 151
152 def create_key_path(base_name):
153 """Generates a name that contains |base_name| in it, and is relative to the
154 "keys/" directory"""
155 return get_unique_path_id('keys/' + g_script_name + '_' + base_name) + '.key'
mattm 2017/04/07 22:01:27 This may work a bit less well for other scripts th
eroman 2017/04/07 23:36:33 I made a few changes which hopefully address those
145 156
146 157
147 class Certificate(object): 158 class Certificate(object):
148 """Helper for building an X.509 certificate.""" 159 """Helper for building an X.509 certificate."""
149 160
150 def __init__(self, name, cert_type, issuer): 161 def __init__(self, name, cert_type, issuer):
151 # The name will be used for the subject's CN, and also as a component of 162 # The name will be used for the subject's CN, and also as a component of
152 # the temporary filenames to help with debugging. 163 # the temporary filenames to help with debugging.
153 self.name = name 164 self.name = name
154 self.path_id = get_unique_path_id(name) 165 self.path_id = get_unique_path_id(name)
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after
248 def set_key_internal(self, key): 259 def set_key_internal(self, key):
249 self.key = key 260 self.key = key
250 261
251 # Associate the private key with the certificate. 262 # Associate the private key with the certificate.
252 section = self.config.get_section('root_ca') 263 section = self.config.get_section('root_ca')
253 section.set_property('private_key', self.key.get_path()) 264 section.set_property('private_key', self.key.get_path())
254 265
255 266
256 def get_key(self): 267 def get_key(self):
257 if self.key is None: 268 if self.key is None:
258 self.set_key_internal(generate_rsa_key(2048, path=self.get_path(".key"))) 269 self.set_key_internal(
270 get_or_generate_rsa_key(2048, create_key_path(self.name)))
259 return self.key 271 return self.key
260 272
261 273
262 def get_cert_path(self): 274 def get_cert_path(self):
263 return self.get_path('.pem') 275 return self.get_path('.pem')
264 276
265 277
266 def get_serial_path(self): 278 def get_serial_path(self):
267 return self.get_name_path('.serial') 279 return self.get_name_path('.serial')
268 280
269 281
270 def get_csr_path(self): 282 def get_csr_path(self):
271 return self.get_path('.csr') 283 return self.get_path('.csr')
272 284
273 285
274 def get_database_path(self): 286 def get_database_path(self):
275 return self.get_name_path('.db') 287 return self.get_name_path('.db')
276 288
277 289
278 def get_config_path(self): 290 def get_config_path(self):
279 return self.get_path('.cnf') 291 return self.get_path('.cnf')
280 292
281 293
282 def get_cert_pem(self): 294 def get_cert_pem(self):
283 # Finish generating a .pem file for the certificate. 295 # Finish generating a .pem file for the certificate.
284 self.finalize() 296 self.finalize()
285 297
286 # Read the certificate data. 298 # Read the certificate data.
287 with open(self.get_cert_path(), 'r') as f: 299 return read_file_to_string(self.get_cert_path())
288 return f.read()
289 300
290 301
291 def finalize(self): 302 def finalize(self):
292 """Finishes the certificate creation process. This generates any needed 303 """Finishes the certificate creation process. This generates any needed
293 key, creates and signs the CSR. On completion the resulting PEM file can be 304 key, creates and signs the CSR. On completion the resulting PEM file can be
294 found at self.get_cert_path()""" 305 found at self.get_cert_path()"""
295 306
296 if self.finalized: 307 if self.finalized:
297 return # Already finalized, no work needed. 308 return # Already finalized, no work needed.
298 309
(...skipping 174 matching lines...) Expand 10 before | Expand all | Expand 10 after
473 test_data += '\n' + text_data_to_pem('TIME', utc_time) 484 test_data += '\n' + text_data_to_pem('TIME', utc_time)
474 485
475 verify_result_string = 'SUCCESS' if verify_result else 'FAIL' 486 verify_result_string = 'SUCCESS' if verify_result else 'FAIL'
476 test_data += '\n' + text_data_to_pem('VERIFY_RESULT', verify_result_string) 487 test_data += '\n' + text_data_to_pem('VERIFY_RESULT', verify_result_string)
477 488
478 test_data += '\n' + text_data_to_pem('KEY_PURPOSE', key_purpose) 489 test_data += '\n' + text_data_to_pem('KEY_PURPOSE', key_purpose)
479 490
480 if errors is not None: 491 if errors is not None:
481 test_data += '\n' + text_data_to_pem('ERRORS', errors) 492 test_data += '\n' + text_data_to_pem('ERRORS', errors)
482 493
483 write_string_to_file(test_data, out_pem if out_pem else g_out_pem) 494 if not out_pem:
495 out_pem = g_script_name + '.pem'
496 write_string_to_file(test_data, out_pem)
484 497
485 498
486 def write_string_to_file(data, path): 499 def write_string_to_file(data, path):
487 with open(path, 'w') as f: 500 with open(path, 'w') as f:
488 f.write(data) 501 f.write(data)
489 502
490 503
504 def read_file_to_string(path):
505 with open(path, 'r') as f:
506 return f.read()
507
508
491 def init(invoking_script_path): 509 def init(invoking_script_path):
492 """Creates an output directory to contain all the temporary files that may be 510 """Creates an output directory to contain all the temporary files that may be
493 created, as well as determining the path for the final output. These paths 511 created, as well as determining the path for the final output. These paths
494 are all based off of the name of the calling script. 512 are all based off of the name of the calling script.
495 """ 513 """
496 514
497 global g_out_dir 515 global g_out_dir
498 global g_out_pem 516 global g_script_name
499 517
500 # Base the output name off of the invoking script's name. 518 # Base the output name off of the invoking script's name.
501 out_name = os.path.splitext(os.path.basename(invoking_script_path))[0] 519 out_name = os.path.splitext(os.path.basename(invoking_script_path))[0]
502 520
503 # Strip the leading 'generate-' 521 # Strip the leading 'generate-'
504 if out_name.startswith('generate-'): 522 if out_name.startswith('generate-'):
505 out_name = out_name[9:] 523 out_name = out_name[9:]
506 524
507 # Use an output directory with the same name as the invoking script. 525 # Use an output directory with the same name as the invoking script.
508 g_out_dir = os.path.join('out', out_name) 526 g_out_dir = os.path.join('out', out_name)
509 527
510 # Ensure the output directory exists and is empty. 528 # Ensure the output directory exists and is empty.
511 sys.stdout.write('Creating output directory: %s\n' % (g_out_dir)) 529 sys.stdout.write('Creating output directory: %s\n' % (g_out_dir))
512 shutil.rmtree(g_out_dir, True) 530 shutil.rmtree(g_out_dir, True)
513 os.makedirs(g_out_dir) 531 os.makedirs(g_out_dir)
514 532
515 g_out_pem = os.path.join('%s.pem' % (out_name)) 533 g_script_name = out_name
516 534
517 535
518 def create_self_signed_root_certificate(name): 536 def create_self_signed_root_certificate(name):
519 return Certificate(name, TYPE_CA, None) 537 return Certificate(name, TYPE_CA, None)
520 538
521 539
522 def create_intermediate_certificate(name, issuer): 540 def create_intermediate_certificate(name, issuer):
523 return Certificate(name, TYPE_CA, issuer) 541 return Certificate(name, TYPE_CA, issuer)
524 542
525 543
526 def create_end_entity_certificate(name, issuer): 544 def create_end_entity_certificate(name, issuer):
527 return Certificate(name, TYPE_END_ENTITY, issuer) 545 return Certificate(name, TYPE_END_ENTITY, issuer)
528 546
529 init(sys.argv[0]) 547 init(sys.argv[0])
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698