| 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/*.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' | 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 |