src/tests/certgenerator.py
changeset 3010 2741200f3d9e
child 3158 58c9c2c21e67
child 3230 30355eee0c43
equal deleted inserted replaced
3009:54f122d0f1cb 3010:2741200f3d9e
       
     1 #!/usr/bin/python2.6
       
     2 #
       
     3 # CDDL HEADER START
       
     4 #
       
     5 # The contents of this file are subject to the terms of the
       
     6 # Common Development and Distribution License (the "License").
       
     7 # You may not use this file except in compliance with the License.
       
     8 #
       
     9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
       
    10 # or http://www.opensolaris.org/os/licensing.
       
    11 # See the License for the specific language governing permissions
       
    12 # and limitations under the License.
       
    13 #
       
    14 # When distributing Covered Code, include this CDDL HEADER in each
       
    15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
       
    16 # If applicable, add the following below this CDDL HEADER, with the
       
    17 # fields enclosed by brackets "[]" replaced with your own identifying
       
    18 # information: Portions Copyright [yyyy] [name of copyright owner]
       
    19 #
       
    20 # CDDL HEADER END
       
    21 #
       
    22 
       
    23 #
       
    24 # Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
       
    25 #
       
    26 
       
    27 import os
       
    28 import subprocess
       
    29 
       
    30 class CertGenerator(object):
       
    31         """A class which creates certificates."""
       
    32 
       
    33         def __init__(self, base_dir="."):
       
    34                 # Allow relative path, but convert it to absolute path first.
       
    35                 self.base_dir = os.path.abspath(base_dir)
       
    36 
       
    37                 conf_dict = {"base_dir": self.base_dir}
       
    38                 self.cnf_file = os.path.join(self.base_dir, "openssl.cnf")
       
    39                 with open(self.cnf_file, "wb") as fh:
       
    40                         fh.write(self.openssl_conf % conf_dict)
       
    41 
       
    42                 # Set up the needed files.
       
    43                 fh = open(os.path.join(self.base_dir, "index"), "wb")
       
    44                 fh.close()
       
    45 
       
    46                 fh = open(os.path.join(self.base_dir, "serial"), "wb")
       
    47                 fh.write("01\n")
       
    48                 fh.close()
       
    49 
       
    50                 # Set up the names of the needed directories.
       
    51                 self.keys_loc = "keys"
       
    52                 self.cs_loc = "code_signing_certs"
       
    53                 self.chain_certs_loc = "chain_certs"
       
    54                 self.trust_anchors_loc = "trust_anchors"
       
    55                 self.crl_loc = "crl"
       
    56 
       
    57                 # Set up the paths to the certificates that will be needed.
       
    58                 self.keys_dir = os.path.join(self.base_dir, self.keys_loc)
       
    59                 self.cs_dir = os.path.join(self.base_dir, self.cs_loc)
       
    60                 self.chain_certs_dir = os.path.join(self.base_dir,
       
    61                     self.chain_certs_loc)
       
    62                 self.raw_trust_anchor_dir = os.path.join(self.base_dir,
       
    63                     self.trust_anchors_loc)
       
    64                 self.crl_dir = os.path.join(self.base_dir, self.crl_loc)
       
    65 
       
    66                 os.mkdir(self.keys_dir)
       
    67                 os.mkdir(self.cs_dir)
       
    68                 os.mkdir(self.chain_certs_dir)
       
    69                 os.mkdir(self.raw_trust_anchor_dir)
       
    70                 os.mkdir(self.crl_dir)
       
    71 
       
    72         def convert_pem_to_text(self, tmp_pth, out_pth, kind="x509"):
       
    73                 """Convert a pem file to a human friendly text file."""
       
    74 
       
    75                 assert not os.path.exists(out_pth)
       
    76 
       
    77                 cmd = ["openssl", kind, "-in", tmp_pth,
       
    78                     "-text"]
       
    79 
       
    80                 fh = open(out_pth, "wb")
       
    81                 p = subprocess.Popen(cmd, stdout=fh)
       
    82                 assert p.wait() == 0
       
    83                 fh.close()
       
    84 
       
    85         def make_ca_cert(self, new_name, parent_name, parent_loc=None,
       
    86             ext="v3_ca", ta_path=None, expired=False, future=False, https=False):
       
    87                 """Create a new CA cert."""
       
    88 
       
    89                 if not parent_loc:
       
    90                         parent_loc = self.trust_anchors_loc
       
    91                 if not ta_path:
       
    92                         ta_path = self.base_dir
       
    93                 subj_str_to_use = self.subj_str
       
    94                 if https:
       
    95                         subj_str_to_use = self.https_subj_str
       
    96                 cmd = ["openssl", "req", "-new", "-nodes",
       
    97                     "-keyout", "%s/%s_key.pem" % (self.keys_dir, new_name),
       
    98                     "-out", "%s/%s.csr" % (self.chain_certs_dir, new_name),
       
    99                     "-sha256", "-subj", subj_str_to_use % (new_name, new_name)]
       
   100                 p = subprocess.Popen(cmd)
       
   101                 assert p.wait() == 0
       
   102 
       
   103                 cmd = ["openssl", "ca", "-policy", "policy_anything",
       
   104                     "-extensions", ext,
       
   105                     "-out", "%s/%s_cert.pem" % (self.chain_certs_dir,
       
   106                         new_name),
       
   107                     "-in", "%s/%s.csr" % (self.chain_certs_dir, new_name),
       
   108                     "-cert", "%s/%s/%s_cert.pem" % (ta_path, parent_loc,
       
   109                         parent_name),
       
   110                     "-outdir", "%s" % self.chain_certs_dir,
       
   111                     "-keyfile", "%s/%s/%s_key.pem" % (ta_path, self.keys_loc,
       
   112                         parent_name),
       
   113                     "-config", self.cnf_file,
       
   114                     "-batch"]
       
   115                 if expired:
       
   116                         cmd.append("-startdate")
       
   117                         cmd.append("090101010101Z")
       
   118                         cmd.append("-enddate")
       
   119                         cmd.append("090102010101Z")
       
   120                 elif future:
       
   121                         cmd.append("-startdate")
       
   122                         cmd.append("350101010101Z")
       
   123                         cmd.append("-enddate")
       
   124                         cmd.append("350102010101Z")
       
   125                 else:
       
   126                         cmd.append("-days")
       
   127                         cmd.append("1000")
       
   128                 p = subprocess.Popen(cmd)
       
   129                 assert p.wait() == 0
       
   130 
       
   131         def make_cs_cert(self, new_name, parent_name, parent_loc=None,
       
   132                 ext="v3_req", ca_path=None, expiring=False, expired=False,
       
   133                     future=False, https=False, passphrase=None):
       
   134                 """Create a new code signing cert."""
       
   135 
       
   136                 if not parent_loc:
       
   137                         parent_loc = self.trust_anchors_loc
       
   138                 if not ca_path:
       
   139                         ca_path = self.base_dir
       
   140                 subj_str_to_use = self.subj_str
       
   141                 if https:
       
   142                         subj_str_to_use = self.https_subj_str
       
   143                 cmd = ["openssl", "genrsa", "-out", "%s/%s_key.pem" % \
       
   144                     (self.keys_dir, new_name), "1024"]
       
   145                 p = subprocess.Popen(cmd)
       
   146                 assert p.wait() == 0
       
   147 
       
   148                 cmd = ["openssl", "req", "-new", "-nodes",
       
   149                     "-key", "%s/%s_key.pem" % (self.keys_dir, new_name),
       
   150                     "-out", "%s/%s.csr" % (self.cs_dir, new_name),
       
   151                     "-sha256", "-subj", subj_str_to_use % (new_name, new_name)]
       
   152                 p = subprocess.Popen(cmd)
       
   153                 assert p.wait() == 0
       
   154 
       
   155                 if passphrase:
       
   156                         # Add a passphrase to the key just created using a new filename.
       
   157                         cmd = ["openssl", "rsa", "-des3",
       
   158                             "-in", "%s/%s_key.pem" % (self.keys_dir, new_name),
       
   159                             "-out", "%s/%s_reqpass_key.pem" % (self.keys_dir,
       
   160                                 new_name),
       
   161                             "-passout", "pass:%s" % passphrase]
       
   162                         p = subprocess.Popen(cmd)
       
   163                         assert p.wait() == 0
       
   164 
       
   165                 cmd = ["openssl", "ca", "-policy", "policy_anything",
       
   166                     "-extensions", ext,
       
   167                     "-out", "%s/%s_cert.pem" % (self.cs_dir, new_name),
       
   168                     "-in", "%s/%s.csr" % (self.cs_dir, new_name),
       
   169                     "-cert", "%s/%s/%s_cert.pem" % (ca_path, parent_loc,
       
   170                         parent_name),
       
   171                     "-outdir", "%s" % self.cs_dir,
       
   172                     "-keyfile", "%s/%s/%s_key.pem" % (ca_path, self.keys_loc,
       
   173                         parent_name),
       
   174                     "-config", self.cnf_file,
       
   175                     "-batch"]
       
   176                 if expired:
       
   177                         cmd.append("-startdate")
       
   178                         cmd.append("090101010101Z")
       
   179                         cmd.append("-enddate")
       
   180                         cmd.append("090102010101Z")
       
   181                 elif future:
       
   182                         cmd.append("-startdate")
       
   183                         cmd.append("350101010101Z")
       
   184                         cmd.append("-enddate")
       
   185                         cmd.append("350102010101Z")
       
   186                 elif expiring:
       
   187                         cmd.append("-days")
       
   188                         cmd.append("27")
       
   189                 else:
       
   190                         cmd.append("-days")
       
   191                         cmd.append("1000")
       
   192                 p = subprocess.Popen(cmd)
       
   193                 assert p.wait() == 0
       
   194 
       
   195         def make_trust_anchor(self, name, https=False):
       
   196                 """Make a new trust anchor."""
       
   197 
       
   198                 subj_str_to_use = self.subj_str
       
   199                 if https:
       
   200                         subj_str_to_use = self.https_subj_str
       
   201                 cmd = ["openssl", "req", "-new", "-x509", "-nodes",
       
   202                     "-keyout", "%s/%s_key.pem" % (self.keys_dir, name),
       
   203                     "-subj", subj_str_to_use % (name, name),
       
   204                     "-out", "%s/%s/%s_cert.tmp" % (self.base_dir, name, name),
       
   205                     "-days", "1000",
       
   206                     "-sha256"]
       
   207 
       
   208                 os.mkdir("%s/%s" % (self.base_dir, name))
       
   209 
       
   210                 p = subprocess.Popen(cmd)
       
   211                 assert p.wait() == 0
       
   212                 self.convert_pem_to_text("%s/%s/%s_cert.tmp" % (self.base_dir,
       
   213                     name, name), "%s/%s/%s_cert.pem" % (self.base_dir, name,
       
   214                         name))
       
   215 
       
   216                 try:
       
   217                         os.link("%s/%s/%s_cert.pem" % (self.base_dir, name, name),
       
   218                             "%s/%s_cert.pem" % (self.raw_trust_anchor_dir, name))
       
   219                 except:
       
   220                         shutil.copy("%s/%s/%s_cert.pem" % (self.base_dir, name,
       
   221                             name), "%s/%s_cert.pem" % (self.raw_trust_anchor_dir,
       
   222                                 name))
       
   223 
       
   224         def revoke_cert(self, ca, revoked_cert, ca_dir=None, cert_dir=None,
       
   225                 ca_path=None):
       
   226                 """Revoke a certificate using the CA given."""
       
   227 
       
   228                 if not ca_dir:
       
   229                         ca_dir = ca
       
   230                 if not cert_dir:
       
   231                         cert_dir = self.cs_loc
       
   232                 if not ca_path:
       
   233                         ca_path = self.base_dir
       
   234                 cmd = ["openssl", "ca", "-keyfile", "%s/%s/%s_key.pem" % \
       
   235                     (ca_path, self.keys_loc, ca),
       
   236                     "-cert", "%s/%s/%s_cert.pem" % (ca_path, ca_dir, ca),
       
   237                     "-config", self.cnf_file,
       
   238                     "-revoke", "%s/%s/%s_cert.pem" % (self.base_dir, cert_dir,
       
   239                     revoked_cert)]
       
   240                 p = subprocess.Popen(cmd)
       
   241                 assert p.wait() == 0
       
   242 
       
   243                 cmd = ["openssl", "ca", "-gencrl",
       
   244                     "-keyfile", "%s/%s/%s_key.pem" % (ca_path, self.keys_loc, ca),
       
   245                     "-cert", "%s/%s/%s_cert.pem" % (ca_path, ca_dir, ca),
       
   246                     "-config", self.cnf_file,
       
   247                     "-out", "%s/%s_crl.tmp" % (self.crl_dir, ca),
       
   248                     "-crldays", "1000"]
       
   249                 p = subprocess.Popen(cmd)
       
   250                 assert p.wait() == 0
       
   251                 self.convert_pem_to_text("%s/%s_crl.tmp" % (self.crl_dir, ca),
       
   252                     "%s/%s_crl.pem" % (self.crl_dir, ca), kind="crl")
       
   253 
       
   254         subj_str = "/C=US/ST=California/L=Santa Clara/O=pkg5/CN=%s/emailAddress=%s"
       
   255         https_subj_str = "/C=US/ST=California/L=Santa Clara/O=pkg5/OU=%s/" \
       
   256             "CN=localhost/emailAddress=%s"
       
   257 
       
   258         openssl_conf = """\
       
   259 HOME                    = .
       
   260 RANDFILE                = $ENV::HOME/.rnd
       
   261 
       
   262 [ ca ]
       
   263 default_ca      = CA_default
       
   264 
       
   265 [ CA_default ]
       
   266 dir             = %(base_dir)s
       
   267 crl_dir         = $dir/crl
       
   268 database        = $dir/index
       
   269 serial          = $dir/serial
       
   270 
       
   271 x509_extensions = usr_cert
       
   272 unique_subject  = no
       
   273 
       
   274 default_md      = sha256
       
   275 preserve        = no
       
   276 
       
   277 policy          = policy_match
       
   278 
       
   279 # For the 'anything' policy
       
   280 # At this point in time, you must list all acceptable 'object'
       
   281 # types.
       
   282 [ policy_anything ]
       
   283 countryName             = optional
       
   284 stateOrProvinceName     = optional
       
   285 localityName            = optional
       
   286 organizationName        = optional
       
   287 organizationalUnitName  = optional
       
   288 commonName              = supplied
       
   289 emailAddress            = optional
       
   290 
       
   291 ####################################################################
       
   292 [ req ]
       
   293 default_bits            = 2048
       
   294 default_keyfile         = ./private/ca-key.pem
       
   295 default_md              = sha256
       
   296 
       
   297 prompt                  = no
       
   298 distinguished_name      = root_ca_distinguished_name
       
   299 
       
   300 x509_extensions = v3_ca
       
   301 string_mask = nombstr
       
   302 
       
   303 [ root_ca_distinguished_name ]
       
   304 commonName = ta1
       
   305 countryName = US
       
   306 stateOrProvinceName = California
       
   307 localityName = Santa Clara
       
   308 0.organizationName = pkg5
       
   309 emailAddress = ta1@pkg5
       
   310 
       
   311 [ usr_cert ]
       
   312 
       
   313 # These extensions are added when 'ca' signs a request.
       
   314 
       
   315 subjectKeyIdentifier=hash
       
   316 authorityKeyIdentifier=keyid,issuer:always
       
   317 
       
   318 [ v3_req ]
       
   319 
       
   320 # Extensions to add to a certificate request.
       
   321 
       
   322 basicConstraints = critical,CA:FALSE
       
   323 keyUsage = critical, digitalSignature
       
   324 
       
   325 [ v3_confused_cs ]
       
   326 
       
   327 # Have CA be true, but don't have keyUsage allow certificate signing to created
       
   328 # a confused certificate.
       
   329 
       
   330 basicConstraints = critical,CA:true
       
   331 keyUsage = critical, digitalSignature
       
   332 
       
   333 [ v3_no_keyUsage ]
       
   334 
       
   335 # The extensions to use for a code signing certificate without a keyUsage
       
   336 # extension.
       
   337 
       
   338 basicConstraints = critical,CA:FALSE
       
   339 
       
   340 [ v3_ca ]
       
   341 
       
   342 # Extensions for a typical CA.
       
   343 
       
   344 # PKIX recommendation.
       
   345 subjectKeyIdentifier=hash
       
   346 authorityKeyIdentifier=keyid:always,issuer:always
       
   347 basicConstraints = critical,CA:true
       
   348 keyUsage = critical, keyCertSign, cRLSign
       
   349 
       
   350 [ v3_ca_lp4 ]
       
   351 
       
   352 # Extensions for a typical CA.
       
   353 
       
   354 # PKIX recommendation.
       
   355 subjectKeyIdentifier=hash
       
   356 authorityKeyIdentifier=keyid:always,issuer:always
       
   357 basicConstraints = critical,CA:true,pathlen:4
       
   358 keyUsage = critical, keyCertSign, cRLSign
       
   359 
       
   360 [ v3_ca_lp3 ]
       
   361 
       
   362 # Extensions for a typical CA
       
   363 
       
   364 # PKIX recommendation.
       
   365 subjectKeyIdentifier=hash
       
   366 authorityKeyIdentifier=keyid:always,issuer:always
       
   367 basicConstraints = critical,CA:true,pathlen:3
       
   368 keyUsage = critical, keyCertSign, cRLSign
       
   369 
       
   370 [ v3_ca_lp2 ]
       
   371 
       
   372 # Extensions for a typical CA.
       
   373 
       
   374 # PKIX recommendation.
       
   375 subjectKeyIdentifier=hash
       
   376 authorityKeyIdentifier=keyid:always,issuer:always
       
   377 basicConstraints = critical,CA:true,pathlen:2
       
   378 keyUsage = critical, keyCertSign, cRLSign
       
   379 
       
   380 [ v3_ca_lp1 ]
       
   381 
       
   382 # Extensions for a typical CA.
       
   383 
       
   384 # PKIX recommendation.
       
   385 subjectKeyIdentifier=hash
       
   386 authorityKeyIdentifier=keyid:always,issuer:always
       
   387 basicConstraints = critical,CA:true,pathlen:1
       
   388 keyUsage = critical, keyCertSign, cRLSign
       
   389 
       
   390 [ v3_ca_lp0 ]
       
   391 
       
   392 # Extensions for a typical CA.
       
   393 
       
   394 # PKIX recommendation.
       
   395 subjectKeyIdentifier=hash
       
   396 authorityKeyIdentifier=keyid:always,issuer:always
       
   397 basicConstraints = critical,CA:true,pathlen:0
       
   398 keyUsage = critical, keyCertSign, cRLSign
       
   399 
       
   400 [ v3_ca_no_crl ]
       
   401 
       
   402 # Extensions for a CA which cannot sign a CRL.
       
   403 
       
   404 # PKIX recommendation.
       
   405 subjectKeyIdentifier=hash
       
   406 authorityKeyIdentifier=keyid:always,issuer:always
       
   407 basicConstraints = critical,CA:true
       
   408 keyUsage = critical, keyCertSign
       
   409 
       
   410 [ v3_ca_no_keyUsage ]
       
   411 
       
   412 # Extensions for a CA without keyUsage information.
       
   413 
       
   414 # PKIX recommendation.
       
   415 subjectKeyIdentifier=hash
       
   416 authorityKeyIdentifier=keyid:always,issuer:always
       
   417 basicConstraints = critical,CA:true
       
   418 
       
   419 [ issuer_ext ]
       
   420 
       
   421 # Used for a code signing cert with an unsupported critical extension.
       
   422 
       
   423 basicConstraints = critical,CA:FALSE
       
   424 issuerAltName = critical,issuer:copy
       
   425 
       
   426 [ issuer_ext_ca ]
       
   427 
       
   428 # Used for a CA cert with an unsupported critical extension.
       
   429 
       
   430 basicConstraints = critical,CA:TRUE
       
   431 issuerAltName = critical,issuer:copy
       
   432 
       
   433 [ issuer_ext_non_critical ]
       
   434 
       
   435 # Used to test a recognized non-critical extension with an unrecognized value.
       
   436 
       
   437 basicConstraints = critical,CA:FALSE
       
   438 keyUsage = encipherOnly
       
   439 
       
   440 [ issuer_ext_bad_val ]
       
   441 
       
   442 # Used to test a recognized critical extension with an unrecognized value.
       
   443 
       
   444 basicConstraints = critical,CA:FALSE
       
   445 keyUsage = critical, encipherOnly
       
   446 
       
   447 [ crl_ext ]
       
   448 
       
   449 # Used for testing certificate revocation.
       
   450 
       
   451 basicConstraints = critical,CA:FALSE
       
   452 crlDistributionPoints = URI:http://localhost:12001/file/0/ch1_ta4_crl.pem
       
   453 
       
   454 [ ch5_ta1_crl ]
       
   455 
       
   456 # Used for testing certificate revocation.
       
   457 
       
   458 basicConstraints = critical,CA:FALSE
       
   459 crlDistributionPoints = URI:http://localhost:12001/file/0/ch5_ta1_crl.pem
       
   460 
       
   461 [ ch1.1_ta4_crl ]
       
   462 
       
   463 # Used for testing certificate revocation.
       
   464 
       
   465 basicConstraints = critical,CA:FALSE
       
   466 crlDistributionPoints = URI:http://localhost:12001/file/0/ch1.1_ta4_crl.pem
       
   467 
       
   468 [ ch1_ta1_crl ]
       
   469 
       
   470 # Used for testing certificate revocation at the level of a chain certificate.
       
   471 
       
   472 basicConstraints = critical,CA:FALSE
       
   473 crlDistributionPoints = URI:http://localhost:12001/file/0/ch1_pubCA1_crl.pem
       
   474 
       
   475 [ crl_ca ]
       
   476 
       
   477 # Used for testing CA certificate revocation by a trust anchor.
       
   478 
       
   479 # PKIX recommendation.
       
   480 subjectKeyIdentifier=hash
       
   481 authorityKeyIdentifier=keyid:always,issuer:always
       
   482 basicConstraints = critical,CA:true
       
   483 crlDistributionPoints = URI:http://localhost:12001/file/0/ta5_crl.pem
       
   484 keyUsage = critical, keyCertSign, cRLSign
       
   485 
       
   486 [ bad_crl ]
       
   487 
       
   488 # Used for testing a CRL with a bad file format.
       
   489 
       
   490 # PKIX recommendation.
       
   491 subjectKeyIdentifier=hash
       
   492 authorityKeyIdentifier=keyid:always,issuer:always
       
   493 
       
   494 basicConstraints = critical,CA:false
       
   495 
       
   496 crlDistributionPoints = URI:http://localhost:12001/file/0/example_file
       
   497 
       
   498 [ bad_crl_loc ]
       
   499 
       
   500 # PKIX recommendation.
       
   501 subjectKeyIdentifier=hash
       
   502 authorityKeyIdentifier=keyid:always,issuer:always
       
   503 
       
   504 basicConstraints = critical,CA:false
       
   505 
       
   506 crlDistributionPoints = URI:foo://bar/baz
       
   507 """
       
   508 
       
   509