security/manager/ssl/tests/unit/psm_common_py/CertUtils.py

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 # This Source Code Form is subject to the terms of the Mozilla Public
     2 # License, v. 2.0. If a copy of the MPL was not distributed with this
     3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
     5 # This file requires openssl 1.0.0 at least
     7 import os
     8 import random
     9 import pexpect
    10 import time
    11 import sys
    13 def init_dsa(db_dir):
    14     """
    15     Initialize dsa parameters
    17     Sets up a set of params to be reused for DSA key generation
    19     Arguments:
    20       db_dir     -- location of the temporary params for the certificate
    21     """
    22     dsa_key_params = db_dir + "/dsa_param.pem"
    23     os.system("openssl dsaparam -out " + dsa_key_params + " 2048")
    26 def generate_cert_generic(db_dir, dest_dir, serial_num,  key_type, name,
    27                           ext_text, signer_key_filename = "",
    28                           signer_cert_filename = "",
    29                           subject_string = ""):
    30     """
    31     Generate an x509 certificate with a sha256 signature
    33     Preconditions:
    34       if dsa keys are to be generated init_dsa must have been called before.
    37     Arguments:
    38       db_dir     -- location of the temporary params for the certificate
    39       dest_dir   -- location of the x509 cert
    40       serial_num -- serial number for the cert (must be unique for each signer
    41                     key)
    42       key_type   -- the type of key generated: potential values: 'rsa', 'dsa',
    43                     or any of the curves found by 'openssl ecparam -list_curves'
    44       name       -- the common name for the cert, will match the prefix of the
    45                     output cert
    46       ext_text   -- the text for the x509 extensions to be added to the
    47                     certificate
    48       signer_key_filename -- the filename of the key from which the cert will
    49                     be signed if null the cert will be self signed (think CA
    50                     roots).
    51       signer_cert_filename -- the certificate that will sign the certificate
    52                     (used to extract signer info) it must be in DER format.
    54     output:
    55       key_name   -- the filename of the key file (PEM format)
    56       cert_name  -- the filename of the output certificate (DER format)
    57     """
    58     key_name = db_dir + "/"+ name + ".key"
    59     if key_type == 'rsa':
    60       os.system ("openssl genpkey -algorithm RSA -out " + key_name +
    61                  " -pkeyopt rsa_keygen_bits:2048")
    62     elif key_type == 'dsa':
    63       dsa_key_params = db_dir + "/dsa_param.pem"
    64       os.system("openssl gendsa -out " + key_name + "  " + dsa_key_params)
    65     else:
    66       #assume is ec
    67       os.system("openssl ecparam -out " + key_name + " -name "+ key_type +
    68                 " -genkey");
    69     csr_name =  db_dir + "/"+ name + ".csr"
    70     if not subject_string:
    71       subject_string = '/CN=' + name
    72     os.system ("openssl req -new -key " + key_name + " -days 3650" +
    73                " -extensions v3_ca -batch -out " + csr_name +
    74                " -utf8 -subj '" + subject_string + "'")
    76     extensions_filename = db_dir + "/openssl-exts"
    77     f = open(extensions_filename,'w')
    78     f.write(ext_text)
    79     f.close()
    81     cert_name =  dest_dir + "/"+ name + ".der"
    82     if not signer_key_filename:
    83         signer_key_filename = key_name;
    84         os.system ("openssl x509 -req -sha256 -days 3650 -in " + csr_name +
    85                    " -signkey " + signer_key_filename +
    86                    " -set_serial " + str(serial_num) +
    87                    " -extfile " + extensions_filename +
    88                    " -outform DER -out "+ cert_name)
    89     else:
    90         os.system ("openssl x509 -req -sha256 -days 3650 -in " + csr_name +
    91                    " -CAkey " + signer_key_filename +
    92                    " -CA " + signer_cert_filename + " -CAform DER " +
    93                    " -set_serial " + str(serial_num) + " -out " + cert_name +
    94                    " -outform DER  -extfile " + extensions_filename)
    95     return key_name, cert_name
    99 def generate_int_and_ee(db_dir, dest_dir, ca_key, ca_cert, name, int_ext_text,
   100                         ee_ext_text, key_type = 'rsa'):
   101     """
   102     Generate an intermediate and ee signed by the generated intermediate. The
   103     name of the intermediate files will be the name '.der' or '.key'. The name
   104     of the end entity files with be "ee-"+ name plus the appropiate prefixes.
   105     The serial number will be generated radomly so it is potentially possible
   106     to have problem (but very unlikely).
   108     Arguments:
   109       db_dir     -- location of the temporary params for the certificate
   110       dest_dir   -- location of the x509 cert
   111       ca_key     -- The filename of the key that will be used to sign the
   112                     intermediate (PEM FORMAT)
   113       ca_cert    -- The filename of the cert that will be used to sign the
   114                     intermediate, it MUST be the private key for the ca_key.
   115                     The file must be in DER format.
   116       name       -- the common name for the intermediate, will match the prefix
   117                     of the output intermediate. The ee will have the name
   118                     prefixed with "ee-"
   119       int_ext_text  -- the text for the x509 extensions to be added to the
   120                     intermediate certificate
   121       ee_ext_text  -- the text for the x509 extensions to be added to the
   122                     end entity certificate
   123       key_type   -- the type of key generated: potential values: 'rsa', 'dsa',
   124                     or any of the curves found by 'openssl ecparam -list_curves'
   126     output:
   127       int_key   -- the filename of the intermeidate key file (PEM format)
   128       int_cert  -- the filename of the intermediate certificate (DER format)
   129       ee_key    -- the filename of the end entity key file (PEM format)
   130       ee_cert   -- the filename of the end entity certficate (DER format)
   132     """
   133     [int_key, int_cert] = generate_cert_generic(db_dir, dest_dir,
   134                                                 random.randint(100,40000000),
   135                                                 key_type, "int-" + name,
   136                                                 int_ext_text,
   137                                                 ca_key, ca_cert)
   138     [ee_key, ee_cert] = generate_cert_generic(db_dir, dest_dir,
   139                                               random.randint(100,40000000),
   140                                               key_type,  name,
   141                                               ee_ext_text, int_key, int_cert)
   143     return int_key, int_cert, ee_key, ee_cert
   145 def generate_pkcs12(db_dir, dest_dir, der_cert_filename, key_pem_filename,
   146                     prefix):
   147     """
   148     Generate a pkcs12 file for a given certificate  name (in der format) and
   149     a key filename (key in pem format). The output file will have an empty
   150     password.
   152     Arguments:
   153     input:
   154       db_dir     -- location of the temporary params for the certificate
   155       dest_dir   -- location of the x509 cert
   156       der_cert_filename -- the filename of the certificate to be included in the
   157                            output file (DER format)
   158       key_pem_filename  -- the filename of the private key of the certificate to
   159                            (PEM format)
   160       prefix     -- the name to be prepended to the output pkcs12 file.
   161     output:
   162       pk12_filename -- the filename of the outgoing pkcs12 output file
   163     """
   164     #make pem cert file  from der filename
   165     pem_cert_filename = db_dir + "/" + prefix + ".pem"
   166     pk12_filename = dest_dir + "/" + prefix + ".p12"
   167     os.system("openssl x509 -inform der -in " + der_cert_filename + " -out " +
   168                 pem_cert_filename )
   169     #now make pkcs12 file
   170     child = pexpect.spawn("openssl pkcs12 -export -in " + pem_cert_filename +
   171                           " -inkey " + key_pem_filename + " -out " +
   172                           pk12_filename)
   173     child.expect('Enter Export Password:')
   174     child.sendline('')
   175     child.expect('Verifying - Enter Export Password:')
   176     child.sendline('')
   177     child.expect(pexpect.EOF)
   178     return pk12_filename
   180 def init_nss_db(db_dir):
   181     """
   182     Remove the current nss database in the specified directory and create a new
   183     nss database with the cert8 format.
   184     Arguments
   185       db_dir -- the desired location of the new database
   186     output
   187      noise_file -- the path to a noise file suitable to generate TEST
   188                    certificates. This does not have enough entropy for a real
   189                    secret
   190      pwd_file   -- the patch to the secret file used for the database.
   191                    this file should be empty.
   192     """
   193     nss_db_files = ["cert8.db", "key3.db", "secmod.db", "noise", "pwdfile"]
   194     for file in nss_db_files:
   195         if os.path.isfile(file):
   196             os.remove(file)
   197     # create noise file
   198     noise_file = db_dir + "/noise"
   199     nf = open(noise_file, 'w')
   200     nf.write(str(time.time()))
   201     nf.close()
   202     # create pwd file
   203     pwd_file = db_dir + "/pwfile"
   204     pf = open(pwd_file, 'w')
   205     pf.write("\n")
   206     pf.close()
   207     # create nss db
   208     os.system("certutil -d " + db_dir + " -N -f " + pwd_file);
   209     return [noise_file, pwd_file]
   211 def generate_ca_cert(db_dir, dest_dir, noise_file, name, version, do_bc):
   212     """
   213     Creates a new CA certificate in an sql NSS database and as a der file
   214     Arguments:
   215       db_dir     -- the location of the nss database (in sql format)
   216       dest_dir   -- the location of for the output file
   217       noise_file -- the location of a noise file.
   218       name       -- the nickname of the new certificate in the database and the
   219                     common name of the certificate
   220       version    -- the version number of the certificate (valid certs must use
   221                     3)
   222       do_bc      -- if the certificate should include the basic constraints
   223                     (valid ca's should be true)
   224     output:
   225       outname    -- the location of the der file.
   226     """
   227     out_name = dest_dir + "/" + name + ".der"
   228     base_exec_string = ("certutil -S -z " + noise_file + " -g 2048 -d " +
   229                         db_dir + "/ -n " + name + " -v 120 -s 'CN=" + name +
   230                         ",O=PSM Testing,L=Mountain View,ST=California,C=US'" +
   231                         " -t C,C,C -x --certVersion=" + str(int(version)))
   232     if (do_bc):
   233         child = pexpect.spawn(base_exec_string + " -2")
   234         child.logfile = sys.stdout
   235         child.expect('Is this a CA certificate \[y/N\]?')
   236         child.sendline('y')
   237         child.expect('Enter the path length constraint, enter to skip \[<0 for unlimited path\]: >')
   238         child.sendline('')
   239         child.expect('Is this a critical extension \[y/N\]?')
   240         child.sendline('')
   241         child.expect(pexpect.EOF)
   242     else:
   243         os.system(base_exec_string)
   244     os.system("certutil -d " + db_dir + "/ -L -n " + name + " -r > " +
   245               out_name)
   246     return out_name
   248 def generate_child_cert(db_dir, dest_dir, noise_file, name, ca_nick, version,
   249                         do_bc, is_ee, ocsp_url):
   250     """
   251     Creates a new child certificate in an sql NSS database and as a der file
   252     Arguments:
   253       db_dir     -- the location of the nss database (in sql format)
   254       dest_dir   -- the location of for the output file
   255       noise_file -- the location of a noise file.
   256       name       -- the nickname of the new certificate in the database and the
   257                     common name of the certificate
   258       ca_nick    -- the nickname of the isser of the new certificate
   259       version    -- the version number of the certificate (valid certs must use
   260                     3)
   261       do_bc      -- if the certificate should include the basic constraints
   262                     (valid ca's should be true)
   263       is_ee      -- is this and End Entity cert? false means intermediate
   264       ocsp_url   -- optional location of the ocsp responder for this certificate
   265                     this is included only if do_bc is set to True
   266     output:
   267       outname    -- the location of the der file.
   268     """
   270     out_name = dest_dir + "/" + name + ".der"
   271     base_exec_string = ("certutil -S -z " + noise_file + " -g 2048 -d " +
   272                         db_dir + "/ -n " + name + " -v 120 -m " +
   273                         str(random.randint(100, 40000000)) + " -s 'CN=" + name +
   274                         ",O=PSM Testing,L=Mountain View,ST=California,C=US'" +
   275                         " -t C,C,C -c " + ca_nick + " --certVersion=" +
   276                         str(int(version)))
   277     if (do_bc):
   278         extra_arguments = " -2"
   279         if (ocsp_url):
   280             extra_arguments += " --extAIA"
   281         child = pexpect.spawn(base_exec_string + extra_arguments)
   282         child.logfile = sys.stdout
   283         child.expect('Is this a CA certificate \[y/N\]?')
   284         if (is_ee):
   285             child.sendline('N')
   286         else:
   287             child.sendline('y')
   288         child.expect('Enter the path length constraint, enter to skip \[<0 for unlimited path\]: >')
   289         child.sendline('')
   290         child.expect('Is this a critical extension \[y/N\]?')
   291         child.sendline('')
   292         if (ocsp_url):
   293             child.expect('\s+Choice >')
   294             child.sendline('2')
   295             child.expect('\s+Choice: >')
   296             child.sendline('7')
   297             child.expect('Enter data:')
   298             child.sendline(ocsp_url)
   299             child.expect('\s+Choice: >')
   300             child.sendline('0')
   301             child.expect('Add another location to the Authority Information Access extension \[y/N\]')
   302             child.sendline('')
   303             child.expect('Is this a critical extension \[y/N\]?')
   304             child.sendline('')
   305         child.expect(pexpect.EOF)
   306     else:
   307         os.system(base_exec_string)
   308     os.system("certutil -d " + db_dir + "/ -L -n " + name + " -r > " +
   309               out_name)
   310     return out_name

mercurial