Chromium Code Reviews| 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 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 65 if path_id == 0: | 65 if path_id == 0: |
| 66 return name | 66 return name |
| 67 | 67 |
| 68 # Otherwise append the count to make it unique. | 68 # Otherwise append the count to make it unique. |
| 69 return '%s_%d' % (name, path_id) | 69 return '%s_%d' % (name, path_id) |
| 70 | 70 |
| 71 | 71 |
| 72 class Certificate(object): | 72 class Certificate(object): |
| 73 """Helper for building an X.509 certificate.""" | 73 """Helper for building an X.509 certificate.""" |
| 74 | 74 |
| 75 def __init__(self, name, cert_type, issuer): | 75 def __init__(self, name, cert_type, issuer, key_from=None): |
|
eroman
2016/06/14 00:22:22
Rather than making this an optional parameter to i
mattm
2016/06/14 21:37:30
done.
| |
| 76 # The name will be used for the subject's CN, and also as a component of | 76 # The name will be used for the subject's CN, and also as a component of |
| 77 # the temporary filenames to help with debugging. | 77 # the temporary filenames to help with debugging. |
| 78 self.name = name | 78 self.name = name |
| 79 self.path_id = GetUniquePathId(name) | 79 self.path_id = GetUniquePathId(name) |
| 80 | 80 |
| 81 # If specified, use they key from the given Certificate object instead of | |
|
eroman
2016/06/14 00:22:22
they --> the
mattm
2016/06/14 21:37:30
Done.
| |
| 82 # generating a new one. | |
| 83 self.key_from = key_from | |
| 84 | |
| 81 # The issuer is also a Certificate object. Passing |None| means it is a | 85 # The issuer is also a Certificate object. Passing |None| means it is a |
| 82 # self-signed certificate. | 86 # self-signed certificate. |
| 83 self.issuer = issuer | 87 self.issuer = issuer |
| 84 if issuer is None: | 88 if issuer is None: |
| 85 self.issuer = self | 89 self.issuer = self |
| 86 | 90 |
| 87 # The config contains all the OpenSSL options that will be passed via a | 91 # The config contains all the OpenSSL options that will be passed via a |
| 88 # .cnf file. Set up defaults. | 92 # .cnf file. Set up defaults. |
| 89 self.config = openssl_conf.Config() | 93 self.config = openssl_conf.Config() |
| 90 self.init_config() | 94 self.init_config() |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 126 # Initialize any files that will be needed if this certificate is used to | 130 # Initialize any files that will be needed if this certificate is used to |
| 127 # sign other certificates. Starts off serial numbers at 1, and will | 131 # sign other certificates. Starts off serial numbers at 1, and will |
| 128 # increment them for each signed certificate. | 132 # increment them for each signed certificate. |
| 129 write_string_to_file('01\n', self.get_serial_path()) | 133 write_string_to_file('01\n', self.get_serial_path()) |
| 130 write_string_to_file('', self.get_database_path()) | 134 write_string_to_file('', self.get_database_path()) |
| 131 | 135 |
| 132 | 136 |
| 133 def generate_rsa_key(self, size_bits): | 137 def generate_rsa_key(self, size_bits): |
| 134 """Generates an RSA private key for the certificate.""" | 138 """Generates an RSA private key for the certificate.""" |
| 135 subprocess.check_call( | 139 subprocess.check_call( |
| 136 ['openssl', 'genrsa', '-out', self.get_key_path(), str(size_bits)]) | 140 ['openssl', 'genrsa', '-out', self.get_key_path(), str(size_bits)]) |
|
eroman
2016/06/14 00:22:22
Can you add some safety-measures to prevent over-w
mattm
2016/06/14 21:37:30
Done.
| |
| 137 | 141 |
| 138 | 142 |
| 139 def generate_ec_key(self, named_curve): | 143 def generate_ec_key(self, named_curve): |
| 140 """Generates an EC private key for the certificate. |named_curve| can be | 144 """Generates an EC private key for the certificate. |named_curve| can be |
| 141 something like secp384r1""" | 145 something like secp384r1""" |
| 142 subprocess.check_call( | 146 subprocess.check_call( |
| 143 ['openssl', 'ecparam', '-out', self.get_key_path(), | 147 ['openssl', 'ecparam', '-out', self.get_key_path(), |
|
eroman
2016/06/14 00:22:22
same here.
mattm
2016/06/14 21:37:30
Done.
| |
| 144 '-name', named_curve, '-genkey']) | 148 '-name', named_curve, '-genkey']) |
| 145 | 149 |
| 146 | 150 |
| 147 def set_validity_range(self, start_date, end_date): | 151 def set_validity_range(self, start_date, end_date): |
| 148 """Sets the Validity notBefore and notAfter properties for the | 152 """Sets the Validity notBefore and notAfter properties for the |
| 149 certificate""" | 153 certificate""" |
| 150 self.validity_flags = ['-startdate', start_date, '-enddate', end_date] | 154 self.validity_flags = ['-startdate', start_date, '-enddate', end_date] |
| 151 | 155 |
| 152 | 156 |
| 153 def set_signature_hash(self, md): | 157 def set_signature_hash(self, md): |
| 154 """Sets the hash function that will be used when signing this certificate. | 158 """Sets the hash function that will be used when signing this certificate. |
| 155 Can be sha1, sha256, sha512, md5, etc.""" | 159 Can be sha1, sha256, sha512, md5, etc.""" |
| 156 self.md_flags = ['-md', md] | 160 self.md_flags = ['-md', md] |
| 157 | 161 |
| 158 | 162 |
| 159 def get_extensions(self): | 163 def get_extensions(self): |
| 160 return self.config.get_section('req_ext') | 164 return self.config.get_section('req_ext') |
| 161 | 165 |
| 162 | 166 |
| 163 def get_path(self, suffix): | 167 def get_path(self, suffix): |
| 164 """Forms a path to an output file for this certificate, containing the | 168 """Forms a path to an output file for this certificate, containing the |
| 165 indicated suffix. The certificate's name will be used as its basis.""" | 169 indicated suffix. The certificate's name will be used as its basis.""" |
| 166 return os.path.join(g_out_dir, '%s%s' % (self.path_id, suffix)) | 170 return os.path.join(g_out_dir, '%s%s' % (self.path_id, suffix)) |
| 167 | 171 |
| 168 | 172 |
| 169 def get_key_path(self): | 173 def get_key_path(self): |
| 174 if self.key_from is not None: | |
| 175 return self.key_from.get_key_path() | |
| 170 return self.get_path('.key') | 176 return self.get_path('.key') |
| 171 | 177 |
| 172 | 178 |
| 173 def get_cert_path(self): | 179 def get_cert_path(self): |
| 174 return self.get_path('.pem') | 180 return self.get_path('.pem') |
| 175 | 181 |
| 176 | 182 |
| 177 def get_serial_path(self): | 183 def get_serial_path(self): |
| 178 return self.get_path('.serial') | 184 return self.get_path('.serial') |
| 179 | 185 |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 209 | 215 |
| 210 self.finalized = True | 216 self.finalized = True |
| 211 | 217 |
| 212 # Ensure that the issuer has been "finalized", since its outputs need to be | 218 # Ensure that the issuer has been "finalized", since its outputs need to be |
| 213 # accessible. Note that self.issuer could be the same as self. | 219 # accessible. Note that self.issuer could be the same as self. |
| 214 self.issuer.finalize() | 220 self.issuer.finalize() |
| 215 | 221 |
| 216 # Ensure the certificate has a key. Callers have the option to generate a | 222 # Ensure the certificate has a key. Callers have the option to generate a |
| 217 # different type of key, but if that was not done default to a new 2048-bit | 223 # different type of key, but if that was not done default to a new 2048-bit |
| 218 # RSA key. | 224 # RSA key. |
| 219 if not os.path.isfile(self.get_key_path()): | 225 if not os.path.isfile(self.get_key_path()): |
|
eroman
2016/06/14 00:22:22
Can you similarly add a safety measure here?
(Or
mattm
2016/06/14 21:37:30
calls generate_rsa_key which has the assert in it,
| |
| 220 self.generate_rsa_key(2048) | 226 self.generate_rsa_key(2048) |
| 221 | 227 |
| 222 # Serialize the config to a file. | 228 # Serialize the config to a file. |
| 223 self.config.write_to_file(self.get_config_path()) | 229 self.config.write_to_file(self.get_config_path()) |
| 224 | 230 |
| 225 # Create a CSR. | 231 # Create a CSR. |
| 226 subprocess.check_call( | 232 subprocess.check_call( |
| 227 ['openssl', 'req', '-new', | 233 ['openssl', 'req', '-new', |
| 228 '-key', self.get_key_path(), | 234 '-key', self.get_key_path(), |
| 229 '-out', self.get_csr_path(), | 235 '-out', self.get_csr_path(), |
| (...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 344 section = self.config.get_section('crl_ext') | 350 section = self.config.get_section('crl_ext') |
| 345 section.set_property('authorityKeyIdentifier', 'keyid:always') | 351 section.set_property('authorityKeyIdentifier', 'keyid:always') |
| 346 section.set_property('authorityInfoAccess', '@issuer_info') | 352 section.set_property('authorityInfoAccess', '@issuer_info') |
| 347 | 353 |
| 348 | 354 |
| 349 def data_to_pem(block_header, block_data): | 355 def data_to_pem(block_header, block_data): |
| 350 return '-----BEGIN %s-----\n%s\n-----END %s-----\n' % (block_header, | 356 return '-----BEGIN %s-----\n%s\n-----END %s-----\n' % (block_header, |
| 351 base64.b64encode(block_data), block_header) | 357 base64.b64encode(block_data), block_header) |
| 352 | 358 |
| 353 | 359 |
| 354 def write_test_file(description, chain, trusted_certs, utc_time, verify_result): | 360 def write_test_file(description, chain, trusted_certs, utc_time, verify_result, |
| 361 out_pem=None): | |
| 355 """Writes a test file that contains all the inputs necessary to run a | 362 """Writes a test file that contains all the inputs necessary to run a |
| 356 verification on a certificate chain""" | 363 verification on a certificate chain""" |
| 357 | 364 |
| 358 # Prepend the script name that generated the file to the description. | 365 # Prepend the script name that generated the file to the description. |
| 359 test_data = '[Created by: %s]\n\n%s\n' % (sys.argv[0], description) | 366 test_data = '[Created by: %s]\n\n%s\n' % (sys.argv[0], description) |
| 360 | 367 |
| 361 # Write the certificate chain to the output file. | 368 # Write the certificate chain to the output file. |
| 362 for cert in chain: | 369 for cert in chain: |
| 363 test_data += '\n' + cert.get_cert_pem() | 370 test_data += '\n' + cert.get_cert_pem() |
| 364 | 371 |
| 365 # Write the trust store. | 372 # Write the trust store. |
| 366 for cert in trusted_certs: | 373 for cert in trusted_certs: |
| 367 cert_data = cert.get_cert_pem() | 374 cert_data = cert.get_cert_pem() |
| 368 # Use a different block type in the .pem file. | 375 # Use a different block type in the .pem file. |
| 369 cert_data = cert_data.replace('CERTIFICATE', 'TRUSTED_CERTIFICATE') | 376 cert_data = cert_data.replace('CERTIFICATE', 'TRUSTED_CERTIFICATE') |
| 370 test_data += '\n' + cert_data | 377 test_data += '\n' + cert_data |
| 371 | 378 |
| 372 test_data += '\n' + data_to_pem('TIME', utc_time) | 379 test_data += '\n' + data_to_pem('TIME', utc_time) |
| 373 | 380 |
| 374 verify_result_string = 'SUCCESS' if verify_result else 'FAIL' | 381 verify_result_string = 'SUCCESS' if verify_result else 'FAIL' |
| 375 test_data += '\n' + data_to_pem('VERIFY_RESULT', verify_result_string) | 382 test_data += '\n' + data_to_pem('VERIFY_RESULT', verify_result_string) |
| 376 | 383 |
| 377 write_string_to_file(test_data, g_out_pem) | 384 write_string_to_file(test_data, out_pem if out_pem else g_out_pem) |
| 378 | 385 |
| 379 | 386 |
| 380 def write_string_to_file(data, path): | 387 def write_string_to_file(data, path): |
| 381 with open(path, 'w') as f: | 388 with open(path, 'w') as f: |
| 382 f.write(data) | 389 f.write(data) |
| 383 | 390 |
| 384 | 391 |
| 385 def init(invoking_script_path): | 392 def init(invoking_script_path): |
| 386 """Creates an output directory to contain all the temporary files that may be | 393 """Creates an output directory to contain all the temporary files that may be |
| 387 created, as well as determining the path for the final output. These paths | 394 created, as well as determining the path for the final output. These paths |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 406 shutil.rmtree(g_out_dir, True) | 413 shutil.rmtree(g_out_dir, True) |
| 407 os.makedirs(g_out_dir) | 414 os.makedirs(g_out_dir) |
| 408 | 415 |
| 409 g_out_pem = os.path.join('%s.pem' % (out_name)) | 416 g_out_pem = os.path.join('%s.pem' % (out_name)) |
| 410 | 417 |
| 411 | 418 |
| 412 def create_self_signed_root_certificate(name): | 419 def create_self_signed_root_certificate(name): |
| 413 return Certificate(name, TYPE_CA, None) | 420 return Certificate(name, TYPE_CA, None) |
| 414 | 421 |
| 415 | 422 |
| 416 def create_intermediary_certificate(name, issuer): | 423 def create_intermediary_certificate(name, issuer, **kw_args): |
| 417 return Certificate(name, TYPE_CA, issuer) | 424 return Certificate(name, TYPE_CA, issuer, **kw_args) |
| 418 | 425 |
| 419 | 426 |
| 420 def create_end_entity_certificate(name, issuer): | 427 def create_end_entity_certificate(name, issuer, **kw_args): |
| 421 return Certificate(name, TYPE_END_ENTITY, issuer) | 428 return Certificate(name, TYPE_END_ENTITY, issuer, **kw_args) |
| 422 | 429 |
| 423 init(sys.argv[0]) | 430 init(sys.argv[0]) |
| OLD | NEW |