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

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: Fix comment 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 # Use case-insensitive matching for counting duplicates, since some
82 g_cur_path_id[name] = path_id + 1 85 # filesystems are case insensitive, but case preserving.
86 lowercase_name = name.lower()
87 path_id = g_cur_path_id.get(lowercase_name, 0)
88 g_cur_path_id[lowercase_name] = path_id + 1
83 89
84 # Use a short and clean name for the first use of this name. 90 # Use a short and clean name for the first use of this name.
85 if path_id == 0: 91 if path_id == 0:
86 return name 92 return name
87 93
88 # Otherwise append the count to make it unique. 94 # Otherwise append the count to make it unique.
89 return '%s_%d' % (name, path_id) 95 return '%s_%d' % (name, path_id)
90 96
91 97
92 def get_path_in_output_dir(name, suffix): 98 def get_path_in_output_dir(name, suffix):
93 return os.path.join(g_out_dir, '%s%s' % (name, suffix)) 99 return os.path.join(g_out_dir, '%s%s' % (name, suffix))
94 100
95 101
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): 102 class Key(object):
101 """Describes a public + private key pair. It is a dumb wrapper around an 103 """Describes a public + private key pair. It is a dumb wrapper around an
102 on-disk key.""" 104 on-disk key."""
103 105
104 def __init__(self, path): 106 def __init__(self, path):
105 self.path = path 107 self.path = path
106 108
107 109
108 def get_path(self): 110 def get_path(self):
109 """Returns the path to a file that contains the key contents.""" 111 """Returns the path to a file that contains the key contents."""
110 return self.path 112 return self.path
111 113
112 114
113 def generate_rsa_key(size_bits, path=None): 115 def get_or_generate_key(generation_arguments, path):
114 """Generates an RSA private key and returns it as a Key object. If |path| is 116 """Helper function to either retrieve a key from an existing file |path|, or
115 specified the resulting key will be saved at that location.""" 117 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 118
119 # Ensure the path doesn't already exists (otherwise will be overwriting 119 generation_arguments_str = ' '.join(generation_arguments)
120 # something).
121 assert not os.path.isfile(path)
122 120
123 subprocess.check_call( 121 # If the file doesn't already exist, generate a new key using the generation
124 ['openssl', 'genrsa', '-out', path, str(size_bits)]) 122 # parameters.
123 if not os.path.isfile(path):
124 key_contents = subprocess.check_output(generation_arguments)
125
126 # Prepend the generation parameters to the key file.
127 write_string_to_file(generation_arguments_str + '\n' + key_contents,
128 path)
129 else:
130 # If the path already exists, confirm that it is for the expected key type.
131 first_line = read_file_to_string(path).splitlines()[0]
132 if first_line != generation_arguments_str:
133 sys.stderr.write(('\nERROR: The existing key file:\n %s\nis not '
134 'compatible with the requested parameters:\n "%s" vs "%s".\n'
135 'Delete the file if you want to re-generate it with the new '
136 'parameters, otherwise pick a new filename\n') % (
137 path, first_line, generation_arguments_str))
138 sys.exit(1)
125 139
126 return Key(path) 140 return Key(path)
127 141
128 142
129 def generate_ec_key(named_curve, path=None): 143 def get_or_generate_rsa_key(size_bits, path):
130 """Generates an EC private key for the certificate and returns it as a Key 144 """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 145 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.""" 146 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 147
136 # Ensure the path doesn't already exists (otherwise will be overwriting
137 # something).
138 assert not os.path.isfile(path)
139 148
140 subprocess.check_call( 149 def get_or_generate_ec_key(named_curve, path):
141 ['openssl', 'ecparam', '-out', path, 150 """Retrieves an existing key from a file if the path exists. Otherwise
142 '-name', named_curve, '-genkey']) 151 generates an EC key with the specified named curve and saves it to the
152 path."""
153 return get_or_generate_key(['openssl', 'ecparam', '-name', named_curve,
154 '-genkey'], path)
143 155
144 return Key(path) 156
157 def create_key_path(base_name):
158 """Generates a name that contains |base_name| in it, and is relative to the
159 "keys/" directory. If create_key_path(xxx) is called more than once during
160 the script run, a suffix will be added."""
161
162 # Save keys to CWD/keys/<generate-script-name>/*.key
163 # Hack: if the script name was generate-certs.py, then just save to
164 # 'keys/*.key' (used by external consumers of common.py)
165 keys_dir = 'keys'
166 if g_script_name != 'certs':
167 keys_dir = os.path.join(keys_dir, g_script_name)
168
169 # Create the keys directory if it doesn't exist
170 if not os.path.exists(keys_dir):
171 os.makedirs(keys_dir)
172
173 return get_unique_path_id(os.path.join(keys_dir, base_name)) + '.key'
145 174
146 175
147 class Certificate(object): 176 class Certificate(object):
148 """Helper for building an X.509 certificate.""" 177 """Helper for building an X.509 certificate."""
149 178
150 def __init__(self, name, cert_type, issuer): 179 def __init__(self, name, cert_type, issuer):
151 # The name will be used for the subject's CN, and also as a component of 180 # The name will be used for the subject's CN, and also as a component of
152 # the temporary filenames to help with debugging. 181 # the temporary filenames to help with debugging.
153 self.name = name 182 self.name = name
154 self.path_id = get_unique_path_id(name) 183 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): 277 def set_key_internal(self, key):
249 self.key = key 278 self.key = key
250 279
251 # Associate the private key with the certificate. 280 # Associate the private key with the certificate.
252 section = self.config.get_section('root_ca') 281 section = self.config.get_section('root_ca')
253 section.set_property('private_key', self.key.get_path()) 282 section.set_property('private_key', self.key.get_path())
254 283
255 284
256 def get_key(self): 285 def get_key(self):
257 if self.key is None: 286 if self.key is None:
258 self.set_key_internal(generate_rsa_key(2048, path=self.get_path(".key"))) 287 self.set_key_internal(
288 get_or_generate_rsa_key(2048, create_key_path(self.name)))
259 return self.key 289 return self.key
260 290
261 291
262 def get_cert_path(self): 292 def get_cert_path(self):
263 return self.get_path('.pem') 293 return self.get_path('.pem')
264 294
265 295
266 def get_serial_path(self): 296 def get_serial_path(self):
267 return self.get_name_path('.serial') 297 return self.get_name_path('.serial')
268 298
269 299
270 def get_csr_path(self): 300 def get_csr_path(self):
271 return self.get_path('.csr') 301 return self.get_path('.csr')
272 302
273 303
274 def get_database_path(self): 304 def get_database_path(self):
275 return self.get_name_path('.db') 305 return self.get_name_path('.db')
276 306
277 307
278 def get_config_path(self): 308 def get_config_path(self):
279 return self.get_path('.cnf') 309 return self.get_path('.cnf')
280 310
281 311
282 def get_cert_pem(self): 312 def get_cert_pem(self):
283 # Finish generating a .pem file for the certificate. 313 # Finish generating a .pem file for the certificate.
284 self.finalize() 314 self.finalize()
285 315
286 # Read the certificate data. 316 # Read the certificate data.
287 with open(self.get_cert_path(), 'r') as f: 317 return read_file_to_string(self.get_cert_path())
288 return f.read()
289 318
290 319
291 def finalize(self): 320 def finalize(self):
292 """Finishes the certificate creation process. This generates any needed 321 """Finishes the certificate creation process. This generates any needed
293 key, creates and signs the CSR. On completion the resulting PEM file can be 322 key, creates and signs the CSR. On completion the resulting PEM file can be
294 found at self.get_cert_path()""" 323 found at self.get_cert_path()"""
295 324
296 if self.finalized: 325 if self.finalized:
297 return # Already finalized, no work needed. 326 return # Already finalized, no work needed.
298 327
(...skipping 174 matching lines...) Expand 10 before | Expand all | Expand 10 after
473 test_data += '\n' + text_data_to_pem('TIME', utc_time) 502 test_data += '\n' + text_data_to_pem('TIME', utc_time)
474 503
475 verify_result_string = 'SUCCESS' if verify_result else 'FAIL' 504 verify_result_string = 'SUCCESS' if verify_result else 'FAIL'
476 test_data += '\n' + text_data_to_pem('VERIFY_RESULT', verify_result_string) 505 test_data += '\n' + text_data_to_pem('VERIFY_RESULT', verify_result_string)
477 506
478 test_data += '\n' + text_data_to_pem('KEY_PURPOSE', key_purpose) 507 test_data += '\n' + text_data_to_pem('KEY_PURPOSE', key_purpose)
479 508
480 if errors is not None: 509 if errors is not None:
481 test_data += '\n' + text_data_to_pem('ERRORS', errors) 510 test_data += '\n' + text_data_to_pem('ERRORS', errors)
482 511
483 write_string_to_file(test_data, out_pem if out_pem else g_out_pem) 512 if not out_pem:
513 out_pem = g_script_name + '.pem'
514 write_string_to_file(test_data, out_pem)
484 515
485 516
486 def write_string_to_file(data, path): 517 def write_string_to_file(data, path):
487 with open(path, 'w') as f: 518 with open(path, 'w') as f:
488 f.write(data) 519 f.write(data)
489 520
490 521
522 def read_file_to_string(path):
523 with open(path, 'r') as f:
524 return f.read()
525
526
491 def init(invoking_script_path): 527 def init(invoking_script_path):
492 """Creates an output directory to contain all the temporary files that may be 528 """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 529 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. 530 are all based off of the name of the calling script.
495 """ 531 """
496 532
497 global g_out_dir 533 global g_out_dir
498 global g_out_pem 534 global g_script_name
535
536 # The scripts assume to be run from within their containing directory (paths
537 # to things like "keys/" are written relative).
538 expected_cwd = os.path.realpath(os.path.dirname(invoking_script_path))
539 actual_cwd = os.path.realpath(os.getcwd())
540 if actual_cwd != expected_cwd:
541 sys.stderr.write(
542 ('Your current working directory must be that containing the python '
543 'scripts:\n%s\nas the script may reference paths relative to this\n')
544 % (expected_cwd))
545 sys.exit(1)
499 546
500 # Base the output name off of the invoking script's name. 547 # Base the output name off of the invoking script's name.
501 out_name = os.path.splitext(os.path.basename(invoking_script_path))[0] 548 out_name = os.path.splitext(os.path.basename(invoking_script_path))[0]
502 549
503 # Strip the leading 'generate-' 550 # Strip the leading 'generate-'
504 if out_name.startswith('generate-'): 551 if out_name.startswith('generate-'):
505 out_name = out_name[9:] 552 out_name = out_name[9:]
506 553
507 # Use an output directory with the same name as the invoking script. 554 # Use an output directory with the same name as the invoking script.
508 g_out_dir = os.path.join('out', out_name) 555 g_out_dir = os.path.join('out', out_name)
509 556
510 # Ensure the output directory exists and is empty. 557 # Ensure the output directory exists and is empty.
511 sys.stdout.write('Creating output directory: %s\n' % (g_out_dir)) 558 sys.stdout.write('Creating output directory: %s\n' % (g_out_dir))
512 shutil.rmtree(g_out_dir, True) 559 shutil.rmtree(g_out_dir, True)
513 os.makedirs(g_out_dir) 560 os.makedirs(g_out_dir)
514 561
515 g_out_pem = os.path.join('%s.pem' % (out_name)) 562 g_script_name = out_name
516 563
517 564
518 def create_self_signed_root_certificate(name): 565 def create_self_signed_root_certificate(name):
519 return Certificate(name, TYPE_CA, None) 566 return Certificate(name, TYPE_CA, None)
520 567
521 568
522 def create_intermediate_certificate(name, issuer): 569 def create_intermediate_certificate(name, issuer):
523 return Certificate(name, TYPE_CA, issuer) 570 return Certificate(name, TYPE_CA, issuer)
524 571
525 572
526 def create_end_entity_certificate(name, issuer): 573 def create_end_entity_certificate(name, issuer):
527 return Certificate(name, TYPE_END_ENTITY, issuer) 574 return Certificate(name, TYPE_END_ENTITY, issuer)
528 575
529 init(sys.argv[0]) 576 init(sys.argv[0])
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698