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

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

mercurial