| OLD | NEW | 
|---|
| (Empty) |  | 
|  | 1 #!/usr/bin/python | 
|  | 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 | 
|  | 4 # found in the LICENSE file. | 
|  | 5 | 
|  | 6 """Set of helpers to generate signed X.509 certificates. | 
|  | 7 | 
|  | 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 | 
|  | 10 (.cnf). | 
|  | 11 """ | 
|  | 12 | 
|  | 13 import base64 | 
|  | 14 import os | 
|  | 15 import shutil | 
|  | 16 import subprocess | 
|  | 17 import sys | 
|  | 18 | 
|  | 19 sys.path.insert(0, os.path.dirname(__file__)) | 
|  | 20 import openssl_conf | 
|  | 21 | 
|  | 22 # Enum for the "type" of certificate that is to be created. This is used to | 
|  | 23 # select sane defaults for the .cnf file and command line flags, but they can | 
|  | 24 # all be overridden. | 
|  | 25 TYPE_CA = 2 | 
|  | 26 TYPE_END_ENTITY = 3 | 
|  | 27 | 
|  | 28 # January 1st, 2015 midnight UTC | 
|  | 29 JANUARY_1_2015_UTC = '150101120000Z' | 
|  | 30 | 
|  | 31 # January 1st, 2016 midnight UTC | 
|  | 32 JANUARY_1_2016_UTC = '160101120000Z' | 
|  | 33 | 
|  | 34 # March 2nd, 2015 midnight UTC | 
|  | 35 DEFAULT_TIME = '160302120000Z' | 
|  | 36 | 
|  | 37 g_next_path_id = 1 | 
|  | 38 | 
|  | 39 # Base directory into which all temporary output files will be saved. | 
|  | 40 g_out_dir = None | 
|  | 41 | 
|  | 42 g_invoking_script_name = None | 
|  | 43 | 
|  | 44 | 
|  | 45 def GetUniquePathId(name): | 
|  | 46   """Returns a base filename that contains 'name', but is unique to the output | 
|  | 47   directory""" | 
|  | 48   global g_next_path_id | 
|  | 49   path_id = '%s_%d' % (name, g_next_path_id) | 
|  | 50   g_next_path_id += 1 | 
|  | 51   return path_id | 
|  | 52 | 
|  | 53 | 
|  | 54 class Certificate(object): | 
|  | 55   """Helper for building an X.509 certificate.""" | 
|  | 56 | 
|  | 57   def __init__(self, name, cert_type, issuer): | 
|  | 58     # The name will be used for the subject's CN, and also as a component of | 
|  | 59     # the temporary filenames to help with debugging. | 
|  | 60     self.name = name | 
|  | 61     self.path_id  = GetUniquePathId(name) | 
|  | 62 | 
|  | 63     # The issuer is also a Certificate object. Passing |None| means it is a | 
|  | 64     # self-signed certificate. | 
|  | 65     self.issuer = issuer | 
|  | 66     if issuer is None: | 
|  | 67       self.issuer = self | 
|  | 68 | 
|  | 69     # The config contains all the OpenSSL options that will be passed via a | 
|  | 70     # .cnf file. Set up defaults. | 
|  | 71     self.config = openssl_conf.Config() | 
|  | 72     self.InitConfig() | 
|  | 73 | 
|  | 74     # Some settings need to be passed as flags rather than in the .cnf file. | 
|  | 75     # Technically these can be set though a .cnf, however doing so makes it | 
|  | 76     # sticky to the issuing certificate, rather than selecting it per | 
|  | 77     # subordinate certificate. | 
|  | 78     self.validity_flags = [] | 
|  | 79     self.md_flags = [] | 
|  | 80 | 
|  | 81     # By default OpenSSL will use the current time for the start time. Instead | 
|  | 82     # default to using a fixed timestamp for more predictabl results each time | 
|  | 83     # the certificates are re-generated. | 
|  | 84     self.SetValidityRange(JANUARY_1_2015_UTC, JANUARY_1_2016_UTC) | 
|  | 85 | 
|  | 86     # Use SHA-256 when THIS certificate is signed (setting it in the | 
|  | 87     # configuration would instead set the hash to use when signing other | 
|  | 88     # certificates with this one). | 
|  | 89     self.SetSignatureHash('sha256') | 
|  | 90 | 
|  | 91     # Set appropriate key usages and basic constraints. For flexibility in | 
|  | 92     # testing (since want to generate some flawed certificates) these are set | 
|  | 93     # on a per-certificate basis rather than automatically when signing. | 
|  | 94     if cert_type == TYPE_END_ENTITY: | 
|  | 95       self.GetExtensions().SetProperty('keyUsage', | 
|  | 96               'critical,digitalSignature,keyEncipherment') | 
|  | 97       self.GetExtensions().SetProperty('extendedKeyUsage', | 
|  | 98               'serverAuth,clientAuth') | 
|  | 99     else: | 
|  | 100       self.GetExtensions().SetProperty('keyUsage', | 
|  | 101               'critical,keyCertSign,cRLSign') | 
|  | 102       self.GetExtensions().SetProperty('basicConstraints', 'critical,CA:true') | 
|  | 103 | 
|  | 104     # Tracks whether the PEM file for this certificate has been written (since | 
|  | 105     # generation is done lazily). | 
|  | 106     self.finalized = False | 
|  | 107 | 
|  | 108     # Initialize any files that will be needed if this certificate is used to | 
|  | 109     # sign other certificates. Starts off serial numbers at 1, and will | 
|  | 110     # increment them for each signed certificate. | 
|  | 111     WriteStringToFile('01\n', self.GetSerialPath()) | 
|  | 112     WriteStringToFile('', self.GetDatabasePath()) | 
|  | 113 | 
|  | 114 | 
|  | 115   def GenerateRsaKey(self, size_bits): | 
|  | 116     """Generates an RSA private key for the certificate.""" | 
|  | 117     subprocess.check_call( | 
|  | 118         ['openssl', 'genrsa', '-out', self.GetKeyPath(), str(size_bits)]) | 
|  | 119 | 
|  | 120 | 
|  | 121   def GenerateEcKey(self, named_curve): | 
|  | 122     """Generates an EC private key for the certificate. |named_curve| can be | 
|  | 123     something like secp384r1""" | 
|  | 124     subprocess.check_call( | 
|  | 125         ['openssl', 'ecparam', '-out', self.GetKeyPath(), '-name', named_curve, | 
|  | 126          '-genkey']) | 
|  | 127 | 
|  | 128 | 
|  | 129   def SetValidityRange(self, start_date, end_date): | 
|  | 130     """Sets the Validity notBefore and notAfter properties for the | 
|  | 131     certificate""" | 
|  | 132     self.validity_flags = ['-startdate', start_date, '-enddate', end_date] | 
|  | 133 | 
|  | 134 | 
|  | 135   def SetSignatureHash(self, md): | 
|  | 136     """Sets the hash function that will be used when signing this certificate. | 
|  | 137     Can be sha1, sha256, sha512, md5, etc.""" | 
|  | 138     self.md_flags = ['-md', md] | 
|  | 139 | 
|  | 140 | 
|  | 141   def GetExtensions(self): | 
|  | 142     return self.config.GetSection('req_ext') | 
|  | 143 | 
|  | 144 | 
|  | 145   def GetPath(self, suffix): | 
|  | 146     """Forms a path to an output file for this certificate, containing the | 
|  | 147     indicated suffix. The certificate's name will be used as its basis.""" | 
|  | 148     return os.path.join(g_out_dir, '%s%s' % (self.path_id, suffix)) | 
|  | 149 | 
|  | 150 | 
|  | 151   def GetKeyPath(self): | 
|  | 152     return self.GetPath('.key') | 
|  | 153 | 
|  | 154 | 
|  | 155   def GetCertPath(self): | 
|  | 156     return self.GetPath('.pem') | 
|  | 157 | 
|  | 158 | 
|  | 159   def GetSerialPath(self): | 
|  | 160     return self.GetPath('.serial') | 
|  | 161 | 
|  | 162 | 
|  | 163   def GetCsrPath(self): | 
|  | 164     return self.GetPath('.csr') | 
|  | 165 | 
|  | 166 | 
|  | 167   def GetDatabasePath(self): | 
|  | 168     return self.GetPath('.db') | 
|  | 169 | 
|  | 170 | 
|  | 171   def GetConfigPath(self): | 
|  | 172     return self.GetPath('.cnf') | 
|  | 173 | 
|  | 174 | 
|  | 175   def GetCertPem(self): | 
|  | 176     # Finish generating a .pem file for the certificate. | 
|  | 177     self.Finalize() | 
|  | 178 | 
|  | 179     # Read the certificate data. | 
|  | 180     with open(self.GetCertPath(), 'r') as f: | 
|  | 181       return f.read() | 
|  | 182 | 
|  | 183 | 
|  | 184   def Finalize(self): | 
|  | 185     """Finishes the certificate creation process. This generates any needed | 
|  | 186     key, creates and signs the CSR. On completion the resulting PEM file can be | 
|  | 187     found at self.GetCertPath()""" | 
|  | 188 | 
|  | 189     if self.finalized: | 
|  | 190       return # Already finalized, no work needed. | 
|  | 191 | 
|  | 192     self.finalized = True | 
|  | 193 | 
|  | 194     # Ensure that the issuer has been "finalized", since its outputs need to be | 
|  | 195     # accessible. Note that self.issuer could be the same as self. | 
|  | 196     self.issuer.Finalize() | 
|  | 197 | 
|  | 198     # Ensure the certificate has a key. Callers have the option to generate a | 
|  | 199     # different type of key, but if that was not done default to a new 2048-bit | 
|  | 200     # RSA key. | 
|  | 201     if not os.path.isfile(self.GetKeyPath()): | 
|  | 202       self.GenerateRsaKey(2048) | 
|  | 203 | 
|  | 204 | 
|  | 205     # Serialize the config to a file. | 
|  | 206     self.config.WriteToFile(self.GetConfigPath()) | 
|  | 207 | 
|  | 208     # Create a CSR. | 
|  | 209     subprocess.check_call( | 
|  | 210         ['openssl', 'req', '-new', | 
|  | 211          '-key', self.GetKeyPath(), | 
|  | 212          '-out', self.GetCsrPath(), | 
|  | 213          '-config', self.GetConfigPath()]) | 
|  | 214 | 
|  | 215     cmd = ['openssl', 'ca', '-batch', '-in', | 
|  | 216         self.GetCsrPath(), '-out', self.GetCertPath(), '-config', | 
|  | 217         self.issuer.GetConfigPath()] | 
|  | 218 | 
|  | 219     if self.issuer == self: | 
|  | 220       cmd.append('-selfsign') | 
|  | 221 | 
|  | 222     # Add in any extra flags. | 
|  | 223     cmd.extend(self.validity_flags) | 
|  | 224     cmd.extend(self.md_flags) | 
|  | 225 | 
|  | 226     # Run the 'openssl ca' command. | 
|  | 227     subprocess.check_call(cmd) | 
|  | 228 | 
|  | 229 | 
|  | 230   def InitConfig(self): | 
|  | 231     """Initializes default properties in the certificate .cnf file that are | 
|  | 232     generic enough to work for all certificates (but can be overridden later). | 
|  | 233     """ | 
|  | 234 | 
|  | 235     # -------------------------------------- | 
|  | 236     # 'req' section | 
|  | 237     # -------------------------------------- | 
|  | 238 | 
|  | 239     section = self.config.GetSection('req') | 
|  | 240 | 
|  | 241     section.SetProperty('encrypt_key', 'no') | 
|  | 242     section.SetProperty('utf8', 'yes') | 
|  | 243     section.SetProperty('string_mask', 'utf8only') | 
|  | 244     section.SetProperty('prompt', 'no') | 
|  | 245     section.SetProperty('distinguished_name', 'req_dn') | 
|  | 246     section.SetProperty('req_extensions', 'req_ext') | 
|  | 247 | 
|  | 248     # -------------------------------------- | 
|  | 249     # 'req_dn' section | 
|  | 250     # -------------------------------------- | 
|  | 251 | 
|  | 252     # This section describes the certificate subject's distinguished name. | 
|  | 253 | 
|  | 254     section = self.config.GetSection('req_dn') | 
|  | 255     section.SetProperty('commonName', '"%s"' % (self.name)) | 
|  | 256 | 
|  | 257     # -------------------------------------- | 
|  | 258     # 'req_ext' section | 
|  | 259     # -------------------------------------- | 
|  | 260 | 
|  | 261     # This section describes the certificate's extensions. | 
|  | 262 | 
|  | 263     section = self.config.GetSection('req_ext') | 
|  | 264     section.SetProperty('subjectKeyIdentifier', 'hash') | 
|  | 265 | 
|  | 266     # -------------------------------------- | 
|  | 267     # SECTIONS FOR CAs | 
|  | 268     # -------------------------------------- | 
|  | 269 | 
|  | 270     # The following sections are used by the 'openssl ca' and relate to the | 
|  | 271     # signing operation. They are not needed for end-entity certificate | 
|  | 272     # configurations, but only if this certifiate will be used to sign other | 
|  | 273     # certificates. | 
|  | 274 | 
|  | 275     # -------------------------------------- | 
|  | 276     # 'ca' section | 
|  | 277     # -------------------------------------- | 
|  | 278 | 
|  | 279     section = self.config.GetSection('ca') | 
|  | 280     section.SetProperty('default_ca', 'root_ca') | 
|  | 281 | 
|  | 282     section = self.config.GetSection('root_ca') | 
|  | 283     section.SetProperty('certificate', self.GetCertPath()) | 
|  | 284     section.SetProperty('private_key', self.GetKeyPath()) | 
|  | 285     section.SetProperty('new_certs_dir', g_out_dir) | 
|  | 286     section.SetProperty('serial', self.GetSerialPath()) | 
|  | 287     section.SetProperty('database', self.GetDatabasePath()) | 
|  | 288     section.SetProperty('unique_subject', 'no') | 
|  | 289 | 
|  | 290     # These will get overridden via command line flags. | 
|  | 291     section.SetProperty('default_days', '365') | 
|  | 292     section.SetProperty('default_md', 'sha256') | 
|  | 293 | 
|  | 294     section.SetProperty('policy', 'policy_anything') | 
|  | 295     section.SetProperty('email_in_dn', 'no') | 
|  | 296     section.SetProperty('preserve', 'yes') | 
|  | 297     section.SetProperty('name_opt', 'multiline,-esc_msb,utf8') | 
|  | 298     section.SetProperty('cert_opt', 'ca_default') | 
|  | 299     section.SetProperty('copy_extensions', 'copy') | 
|  | 300     section.SetProperty('x509_extensions', 'signing_ca_ext') | 
|  | 301     section.SetProperty('default_crl_days', '30') | 
|  | 302     section.SetProperty('crl_extensions', 'crl_ext') | 
|  | 303 | 
|  | 304     section = self.config.GetSection('policy_anything') | 
|  | 305     section.SetProperty('domainComponent', 'optional') | 
|  | 306     section.SetProperty('countryName', 'optional') | 
|  | 307     section.SetProperty('stateOrProvinceName', 'optional') | 
|  | 308     section.SetProperty('localityName', 'optional') | 
|  | 309     section.SetProperty('organizationName', 'optional') | 
|  | 310     section.SetProperty('organizationalUnitName', 'optional') | 
|  | 311     section.SetProperty('commonName', 'optional') | 
|  | 312     section.SetProperty('emailAddress', 'optional') | 
|  | 313 | 
|  | 314     section = self.config.GetSection('signing_ca_ext') | 
|  | 315     section.SetProperty('subjectKeyIdentifier', 'hash') | 
|  | 316     section.SetProperty('authorityKeyIdentifier', 'keyid:always') | 
|  | 317     section.SetProperty('authorityInfoAccess', '@issuer_info') | 
|  | 318     section.SetProperty('crlDistributionPoints', '@crl_info') | 
|  | 319 | 
|  | 320     section = self.config.GetSection('issuer_info') | 
|  | 321     section.SetProperty('caIssuers;URI.0', | 
|  | 322                         'http://url-for-aia/%s.cer' % (self.name)) | 
|  | 323 | 
|  | 324     section = self.config.GetSection('crl_info') | 
|  | 325     section.SetProperty('URI.0', 'http://url-for-crl/%s.crl' % (self.name)) | 
|  | 326 | 
|  | 327     section = self.config.GetSection('crl_ext') | 
|  | 328     section.SetProperty('authorityKeyIdentifier', 'keyid:always') | 
|  | 329     section.SetProperty('authorityInfoAccess', '@issuer_info') | 
|  | 330 | 
|  | 331 | 
|  | 332 def DataToPem(block_header, block_data): | 
|  | 333   return '-----BEGIN %s-----\n%s\n-----END %s-----\n' % (block_header, | 
|  | 334           base64.b64encode(block_data), block_header) | 
|  | 335 | 
|  | 336 | 
|  | 337 def WriteTestFile(description, chain, trusted_certs, utc_time, verify_result): | 
|  | 338   """Writes a test file that contains all the inputs necessary to run a | 
|  | 339   verification on a certificate chain""" | 
|  | 340 | 
|  | 341   # Prepend the script name that generated the file to the description. | 
|  | 342   test_data = '[Created by: %s.py]\n\n%s\n' % (g_invoking_script_name, | 
|  | 343           description) | 
|  | 344 | 
|  | 345   # Write the certificate chain to the output file. | 
|  | 346   for cert in chain: | 
|  | 347     test_data += '\n' + cert.GetCertPem() | 
|  | 348 | 
|  | 349   # Write the trust store. | 
|  | 350   for cert in trusted_certs: | 
|  | 351     cert_data = cert.GetCertPem() | 
|  | 352     # Use a different block type in the .pem file. | 
|  | 353     cert_data = cert_data.replace('CERTIFICATE', 'TRUSTED_CERTIFICATE') | 
|  | 354     test_data += '\n' + cert_data | 
|  | 355 | 
|  | 356   test_data += '\n' + DataToPem('TIME', utc_time) | 
|  | 357 | 
|  | 358   if verify_result: | 
|  | 359     verify_result = 'SUCCESS' | 
|  | 360   else: | 
|  | 361     verify_result = 'FAIL' | 
|  | 362 | 
|  | 363   test_data += '\n' + DataToPem('VERIFY_RESULT', verify_result) | 
|  | 364 | 
|  | 365   # Save the chain using the same name as the script that generated it (but | 
|  | 366   # with the 'generate-' prefix stripped). | 
|  | 367   stripped_script_name = g_invoking_script_name | 
|  | 368   parts = g_invoking_script_name.split('generate-', 1) | 
|  | 369   if len(parts) > 0: | 
|  | 370     stripped_script_name = parts[1] | 
|  | 371 | 
|  | 372   final_output_path = os.path.join('%s.pem' % (stripped_script_name)) | 
|  | 373 | 
|  | 374   WriteStringToFile(test_data, final_output_path) | 
|  | 375 | 
|  | 376 | 
|  | 377 def WriteStringToFile(data, path): | 
|  | 378   with open(path, 'w') as f: | 
|  | 379     f.write(data) | 
|  | 380 | 
|  | 381 | 
|  | 382 def Init(invoking_script_path): | 
|  | 383   """Creates an output directory for all the temporary files generated by the | 
|  | 384   calling script. | 
|  | 385   """ | 
|  | 386 | 
|  | 387   global g_out_dir | 
|  | 388   global g_invoking_script_name | 
|  | 389 | 
|  | 390   g_invoking_script_name = os.path.splitext( | 
|  | 391           os.path.basename(invoking_script_path))[0] | 
|  | 392 | 
|  | 393   # Ensure that out/ exists | 
|  | 394   if not os.path.exists('out'): | 
|  | 395     os.makedirs('out') | 
|  | 396 | 
|  | 397   # Use an output directory with the same name as the invoking script. | 
|  | 398   g_out_dir = os.path.join('out', g_invoking_script_name) | 
|  | 399   sys.stdout.write('Creating output directory: %s\n' % (g_out_dir)) | 
|  | 400 | 
|  | 401   # Ensure the output directory is empty. | 
|  | 402   shutil.rmtree(g_out_dir, True) | 
|  | 403   os.makedirs(g_out_dir) | 
|  | 404 | 
|  | 405 | 
|  | 406 def CreateSelfSignedRootCertificate(name): | 
|  | 407   return Certificate(name, TYPE_CA, None) | 
|  | 408 | 
|  | 409 | 
|  | 410 def CreateIntermediaryCertificate(name, issuer): | 
|  | 411   return Certificate(name, TYPE_CA, issuer) | 
|  | 412 | 
|  | 413 | 
|  | 414 def CreateEndEntityCertificate(name, issuer): | 
|  | 415   return Certificate(name, TYPE_END_ENTITY, issuer) | 
|  | 416 | 
| OLD | NEW | 
|---|