OLD | NEW |
---|---|
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 Loading... | |
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 (cert req, signing db etc) are | 55 # - g_tmp_dir: where any temporary files (cert req, signing db etc) are |
56 # saved to. | 56 # saved to. |
57 # - g_script_name: the name of the invoking script. For instance if this is | 57 |
58 # being run by generate-foo.py then g_script_name will be | 58 # See init() for how these are assigned. |
59 # 'foo' | 59 g_tmp_dir = None |
60 # | |
61 # See init() for how these are assigned, based on the name of the calling | |
62 # script. | |
63 g_out_dir = None | |
64 g_script_name = None | |
65 | 60 |
66 # The default validity range of generated certificates. Can be modified with | 61 # The default validity range of generated certificates. Can be modified with |
67 # set_default_validity_range(). | 62 # set_default_validity_range(). |
68 g_default_start_date = JANUARY_1_2015_UTC | 63 g_default_start_date = JANUARY_1_2015_UTC |
69 g_default_end_date = JANUARY_1_2016_UTC | 64 g_default_end_date = JANUARY_1_2016_UTC |
70 | 65 |
71 | 66 |
72 def set_default_validity_range(start_date, end_date): | 67 def set_default_validity_range(start_date, end_date): |
73 """Sets the validity range that will be used for certificates created with | 68 """Sets the validity range that will be used for certificates created with |
74 Certificate""" | 69 Certificate""" |
(...skipping 13 matching lines...) Expand all Loading... | |
88 g_cur_path_id[lowercase_name] = path_id + 1 | 83 g_cur_path_id[lowercase_name] = path_id + 1 |
89 | 84 |
90 # Use a short and clean name for the first use of this name. | 85 # Use a short and clean name for the first use of this name. |
91 if path_id == 0: | 86 if path_id == 0: |
92 return name | 87 return name |
93 | 88 |
94 # Otherwise append the count to make it unique. | 89 # Otherwise append the count to make it unique. |
95 return '%s_%d' % (name, path_id) | 90 return '%s_%d' % (name, path_id) |
96 | 91 |
97 | 92 |
98 def get_path_in_output_dir(name, suffix): | 93 def get_path_in_tmp_dir(name, suffix): |
99 return os.path.join(g_out_dir, '%s%s' % (name, suffix)) | 94 return os.path.join(g_tmp_dir, '%s%s' % (name, suffix)) |
100 | 95 |
101 | 96 |
102 class Key(object): | 97 class Key(object): |
103 """Describes a public + private key pair. It is a dumb wrapper around an | 98 """Describes a public + private key pair. It is a dumb wrapper around an |
104 on-disk key.""" | 99 on-disk key.""" |
105 | 100 |
106 def __init__(self, path): | 101 def __init__(self, path): |
107 self.path = path | 102 self.path = path |
108 | 103 |
109 | 104 |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
152 path.""" | 147 path.""" |
153 return get_or_generate_key(['openssl', 'ecparam', '-name', named_curve, | 148 return get_or_generate_key(['openssl', 'ecparam', '-name', named_curve, |
154 '-genkey'], path) | 149 '-genkey'], path) |
155 | 150 |
156 | 151 |
157 def create_key_path(base_name): | 152 def create_key_path(base_name): |
158 """Generates a name that contains |base_name| in it, and is relative to the | 153 """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 | 154 "keys/" directory. If create_key_path(xxx) is called more than once during |
160 the script run, a suffix will be added.""" | 155 the script run, a suffix will be added.""" |
161 | 156 |
162 # Save keys to CWD/keys/<generate-script-name>/*.key | 157 #Save keys to CWD / keys / < generate - script - name >/*.key |
mattm
2017/05/02 06:43:46
indentation, space after #
are the internal space
eroman
2017/05/02 19:20:23
Done.
(I think these lines got messed up by some
| |
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' | 158 keys_dir = 'keys' |
166 if g_script_name != 'certs': | |
167 keys_dir = os.path.join(keys_dir, g_script_name) | |
168 | 159 |
169 # Create the keys directory if it doesn't exist | 160 # Create the keys directory if it doesn't exist |
170 if not os.path.exists(keys_dir): | 161 if not os.path.exists(keys_dir): |
171 os.makedirs(keys_dir) | 162 os.makedirs(keys_dir) |
172 | 163 |
173 return get_unique_path_id(os.path.join(keys_dir, base_name)) + '.key' | 164 return get_unique_path_id(os.path.join(keys_dir, base_name)) + '.key' |
174 | 165 |
175 | 166 |
176 class Certificate(object): | 167 class Certificate(object): |
177 """Helper for building an X.509 certificate.""" | 168 """Helper for building an X.509 certificate.""" |
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
252 self.md_flags = ['-md', md] | 243 self.md_flags = ['-md', md] |
253 | 244 |
254 | 245 |
255 def get_extensions(self): | 246 def get_extensions(self): |
256 return self.config.get_section('req_ext') | 247 return self.config.get_section('req_ext') |
257 | 248 |
258 | 249 |
259 def get_path(self, suffix): | 250 def get_path(self, suffix): |
260 """Forms a path to an output file for this certificate, containing the | 251 """Forms a path to an output file for this certificate, containing the |
261 indicated suffix. The certificate's name will be used as its basis.""" | 252 indicated suffix. The certificate's name will be used as its basis.""" |
262 return os.path.join(g_out_dir, '%s%s' % (self.path_id, suffix)) | 253 return os.path.join(g_tmp_dir, '%s%s' % (self.path_id, suffix)) |
263 | 254 |
264 | 255 |
265 def get_name_path(self, suffix): | 256 def get_name_path(self, suffix): |
266 """Forms a path to an output file for this CA, containing the indicated | 257 """Forms a path to an output file for this CA, containing the indicated |
267 suffix. If multiple certificates have the same name, they will use the same | 258 suffix. If multiple certificates have the same name, they will use the same |
268 path.""" | 259 path.""" |
269 return get_path_in_output_dir(self.name, suffix) | 260 return get_path_in_tmp_dir(self.name, suffix) |
270 | 261 |
271 | 262 |
272 def set_key(self, key): | 263 def set_key(self, key): |
273 assert self.finalized is False | 264 assert self.finalized is False |
274 self.set_key_internal(key) | 265 self.set_key_internal(key) |
275 | 266 |
276 | 267 |
277 def set_key_internal(self, key): | 268 def set_key_internal(self, key): |
278 self.key = key | 269 self.key = key |
279 | 270 |
(...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
407 | 398 |
408 # -------------------------------------- | 399 # -------------------------------------- |
409 # 'ca' section | 400 # 'ca' section |
410 # -------------------------------------- | 401 # -------------------------------------- |
411 | 402 |
412 section = self.config.get_section('ca') | 403 section = self.config.get_section('ca') |
413 section.set_property('default_ca', 'root_ca') | 404 section.set_property('default_ca', 'root_ca') |
414 | 405 |
415 section = self.config.get_section('root_ca') | 406 section = self.config.get_section('root_ca') |
416 section.set_property('certificate', self.get_cert_path()) | 407 section.set_property('certificate', self.get_cert_path()) |
417 section.set_property('new_certs_dir', g_out_dir) | 408 section.set_property('new_certs_dir', g_tmp_dir) |
418 section.set_property('serial', self.get_serial_path()) | 409 section.set_property('serial', self.get_serial_path()) |
419 section.set_property('database', self.get_database_path()) | 410 section.set_property('database', self.get_database_path()) |
420 section.set_property('unique_subject', 'no') | 411 section.set_property('unique_subject', 'no') |
421 | 412 |
422 # These will get overridden via command line flags. | 413 # These will get overridden via command line flags. |
423 section.set_property('default_days', '365') | 414 section.set_property('default_days', '365') |
424 section.set_property('default_md', 'sha256') | 415 section.set_property('default_md', 'sha256') |
425 | 416 |
426 section.set_property('policy', 'policy_anything') | 417 section.set_property('policy', 'policy_anything') |
427 section.set_property('email_in_dn', 'no') | 418 section.set_property('email_in_dn', 'no') |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
459 section = self.config.get_section('crl_ext') | 450 section = self.config.get_section('crl_ext') |
460 section.set_property('authorityKeyIdentifier', 'keyid:always') | 451 section.set_property('authorityKeyIdentifier', 'keyid:always') |
461 section.set_property('authorityInfoAccess', '@issuer_info') | 452 section.set_property('authorityInfoAccess', '@issuer_info') |
462 | 453 |
463 | 454 |
464 def text_data_to_pem(block_header, text_data): | 455 def text_data_to_pem(block_header, text_data): |
465 return '%s\n-----BEGIN %s-----\n%s\n-----END %s-----\n' % (text_data, | 456 return '%s\n-----BEGIN %s-----\n%s\n-----END %s-----\n' % (text_data, |
466 block_header, base64.b64encode(text_data), block_header) | 457 block_header, base64.b64encode(text_data), block_header) |
467 | 458 |
468 | 459 |
469 class TrustAnchor(object): | 460 def write_chain(description, chain, out_pem): |
470 """Structure that represents a trust anchor.""" | 461 """Writes the chain to a .pem file as a series of CERTIFICATE blocks""" |
471 | |
472 def __init__(self, cert, constrained=False): | |
473 self.cert = cert | |
474 self.constrained = constrained | |
475 | |
476 | |
477 def get_pem(self): | |
478 """Returns a PEM block string describing this trust anchor.""" | |
479 | |
480 cert_data = self.cert.get_cert_pem() | |
481 block_name = 'TRUST_ANCHOR_UNCONSTRAINED' | |
482 if self.constrained: | |
483 block_name = 'TRUST_ANCHOR_CONSTRAINED' | |
484 | |
485 # Use a different block name in the .pem file, depending on the anchor type. | |
486 return cert_data.replace('CERTIFICATE', block_name) | |
487 | |
488 | |
489 def write_test_file(description, chain, trust_anchor, utc_time, key_purpose, | |
490 verify_result, errors, out_pem=None): | |
491 """Writes a test file that contains all the inputs necessary to run a | |
492 verification on a certificate chain.""" | |
493 | 462 |
494 # Prepend the script name that generated the file to the description. | 463 # Prepend the script name that generated the file to the description. |
495 test_data = '[Created by: %s]\n\n%s\n' % (sys.argv[0], description) | 464 test_data = '[Created by: %s]\n\n%s\n' % (sys.argv[0], description) |
496 | 465 |
497 # Write the certificate chain to the output file. | 466 # Write the certificate chain to the output file. |
498 for cert in chain: | 467 for cert in chain: |
499 test_data += '\n' + cert.get_cert_pem() | 468 test_data += '\n' + cert.get_cert_pem() |
500 | 469 |
501 test_data += '\n' + trust_anchor.get_pem() | |
502 test_data += '\n' + text_data_to_pem('TIME', utc_time) | |
503 | |
504 verify_result_string = 'SUCCESS' if verify_result else 'FAIL' | |
505 test_data += '\n' + text_data_to_pem('VERIFY_RESULT', verify_result_string) | |
506 | |
507 test_data += '\n' + text_data_to_pem('KEY_PURPOSE', key_purpose) | |
508 | |
509 if errors is not None: | |
510 test_data += '\n' + text_data_to_pem('ERRORS', errors) | |
511 | |
512 if not out_pem: | |
513 out_pem = g_script_name + '.pem' | |
514 write_string_to_file(test_data, out_pem) | 470 write_string_to_file(test_data, out_pem) |
515 | 471 |
516 | 472 |
517 def write_string_to_file(data, path): | 473 def write_string_to_file(data, path): |
518 with open(path, 'w') as f: | 474 with open(path, 'w') as f: |
519 f.write(data) | 475 f.write(data) |
520 | 476 |
521 | 477 |
522 def read_file_to_string(path): | 478 def read_file_to_string(path): |
523 with open(path, 'r') as f: | 479 with open(path, 'r') as f: |
524 return f.read() | 480 return f.read() |
525 | 481 |
526 | 482 |
527 def init(invoking_script_path): | 483 def init(invoking_script_path): |
528 """Creates an output directory to contain all the temporary files that may be | 484 """Creates an output directory to contain all the temporary files that may be |
529 created, as well as determining the path for the final output. These paths | 485 created, as well as determining the path for the final output. These paths |
530 are all based off of the name of the calling script. | 486 are all based off of the name of the calling script. |
531 """ | 487 """ |
532 | 488 |
533 global g_out_dir | 489 global g_tmp_dir |
534 global g_script_name | |
535 | 490 |
536 # The scripts assume to be run from within their containing directory (paths | 491 # The scripts assume to be run from within their containing directory (paths |
537 # to things like "keys/" are written relative). | 492 # to things like "keys/" are written relative). |
538 expected_cwd = os.path.realpath(os.path.dirname(invoking_script_path)) | 493 expected_cwd = os.path.realpath(os.path.dirname(invoking_script_path)) |
539 actual_cwd = os.path.realpath(os.getcwd()) | 494 actual_cwd = os.path.realpath(os.getcwd()) |
540 if actual_cwd != expected_cwd: | 495 if actual_cwd != expected_cwd: |
541 sys.stderr.write( | 496 sys.stderr.write( |
542 ('Your current working directory must be that containing the python ' | 497 ('Your current working directory must be that containing the python ' |
543 'scripts:\n%s\nas the script may reference paths relative to this\n') | 498 'scripts:\n%s\nas the script may reference paths relative to this\n') |
544 % (expected_cwd)) | 499 % (expected_cwd)) |
545 sys.exit(1) | 500 sys.exit(1) |
546 | 501 |
547 # Base the output name off of the invoking script's name. | |
548 out_name = os.path.splitext(os.path.basename(invoking_script_path))[0] | |
549 | |
550 # Strip the leading 'generate-' | |
551 if out_name.startswith('generate-'): | |
552 out_name = out_name[9:] | |
553 | |
554 # Use an output directory with the same name as the invoking script. | 502 # Use an output directory with the same name as the invoking script. |
555 g_out_dir = os.path.join('out', out_name) | 503 g_tmp_dir = 'out' |
556 | 504 |
557 # Ensure the output directory exists and is empty. | 505 # Ensure the output directory exists and is empty. |
558 sys.stdout.write('Creating output directory: %s\n' % (g_out_dir)) | 506 sys.stdout.write('Creating output directory: %s\n' % (g_tmp_dir)) |
559 shutil.rmtree(g_out_dir, True) | 507 shutil.rmtree(g_tmp_dir, True) |
560 os.makedirs(g_out_dir) | 508 os.makedirs(g_tmp_dir) |
561 | |
562 g_script_name = out_name | |
563 | 509 |
564 | 510 |
565 def create_self_signed_root_certificate(name): | 511 def create_self_signed_root_certificate(name): |
566 return Certificate(name, TYPE_CA, None) | 512 return Certificate(name, TYPE_CA, None) |
567 | 513 |
568 | 514 |
569 def create_intermediate_certificate(name, issuer): | 515 def create_intermediate_certificate(name, issuer): |
570 return Certificate(name, TYPE_CA, issuer) | 516 return Certificate(name, TYPE_CA, issuer) |
571 | 517 |
572 | 518 |
573 def create_end_entity_certificate(name, issuer): | 519 def create_end_entity_certificate(name, issuer): |
574 return Certificate(name, TYPE_END_ENTITY, issuer) | 520 return Certificate(name, TYPE_END_ENTITY, issuer) |
575 | 521 |
576 init(sys.argv[0]) | 522 init(sys.argv[0]) |
OLD | NEW |