michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: # This file requires openssl 1.0.0 at least michael@0: michael@0: import os michael@0: import random michael@0: import pexpect michael@0: import time michael@0: import sys michael@0: michael@0: def init_dsa(db_dir): michael@0: """ michael@0: Initialize dsa parameters michael@0: michael@0: Sets up a set of params to be reused for DSA key generation michael@0: michael@0: Arguments: michael@0: db_dir -- location of the temporary params for the certificate michael@0: """ michael@0: dsa_key_params = db_dir + "/dsa_param.pem" michael@0: os.system("openssl dsaparam -out " + dsa_key_params + " 2048") michael@0: michael@0: michael@0: def generate_cert_generic(db_dir, dest_dir, serial_num, key_type, name, michael@0: ext_text, signer_key_filename = "", michael@0: signer_cert_filename = "", michael@0: subject_string = ""): michael@0: """ michael@0: Generate an x509 certificate with a sha256 signature michael@0: michael@0: Preconditions: michael@0: if dsa keys are to be generated init_dsa must have been called before. michael@0: michael@0: michael@0: Arguments: michael@0: db_dir -- location of the temporary params for the certificate michael@0: dest_dir -- location of the x509 cert michael@0: serial_num -- serial number for the cert (must be unique for each signer michael@0: key) michael@0: key_type -- the type of key generated: potential values: 'rsa', 'dsa', michael@0: or any of the curves found by 'openssl ecparam -list_curves' michael@0: name -- the common name for the cert, will match the prefix of the michael@0: output cert michael@0: ext_text -- the text for the x509 extensions to be added to the michael@0: certificate michael@0: signer_key_filename -- the filename of the key from which the cert will michael@0: be signed if null the cert will be self signed (think CA michael@0: roots). michael@0: signer_cert_filename -- the certificate that will sign the certificate michael@0: (used to extract signer info) it must be in DER format. michael@0: michael@0: output: michael@0: key_name -- the filename of the key file (PEM format) michael@0: cert_name -- the filename of the output certificate (DER format) michael@0: """ michael@0: key_name = db_dir + "/"+ name + ".key" michael@0: if key_type == 'rsa': michael@0: os.system ("openssl genpkey -algorithm RSA -out " + key_name + michael@0: " -pkeyopt rsa_keygen_bits:2048") michael@0: elif key_type == 'dsa': michael@0: dsa_key_params = db_dir + "/dsa_param.pem" michael@0: os.system("openssl gendsa -out " + key_name + " " + dsa_key_params) michael@0: else: michael@0: #assume is ec michael@0: os.system("openssl ecparam -out " + key_name + " -name "+ key_type + michael@0: " -genkey"); michael@0: csr_name = db_dir + "/"+ name + ".csr" michael@0: if not subject_string: michael@0: subject_string = '/CN=' + name michael@0: os.system ("openssl req -new -key " + key_name + " -days 3650" + michael@0: " -extensions v3_ca -batch -out " + csr_name + michael@0: " -utf8 -subj '" + subject_string + "'") michael@0: michael@0: extensions_filename = db_dir + "/openssl-exts" michael@0: f = open(extensions_filename,'w') michael@0: f.write(ext_text) michael@0: f.close() michael@0: michael@0: cert_name = dest_dir + "/"+ name + ".der" michael@0: if not signer_key_filename: michael@0: signer_key_filename = key_name; michael@0: os.system ("openssl x509 -req -sha256 -days 3650 -in " + csr_name + michael@0: " -signkey " + signer_key_filename + michael@0: " -set_serial " + str(serial_num) + michael@0: " -extfile " + extensions_filename + michael@0: " -outform DER -out "+ cert_name) michael@0: else: michael@0: os.system ("openssl x509 -req -sha256 -days 3650 -in " + csr_name + michael@0: " -CAkey " + signer_key_filename + michael@0: " -CA " + signer_cert_filename + " -CAform DER " + michael@0: " -set_serial " + str(serial_num) + " -out " + cert_name + michael@0: " -outform DER -extfile " + extensions_filename) michael@0: return key_name, cert_name michael@0: michael@0: michael@0: michael@0: def generate_int_and_ee(db_dir, dest_dir, ca_key, ca_cert, name, int_ext_text, michael@0: ee_ext_text, key_type = 'rsa'): michael@0: """ michael@0: Generate an intermediate and ee signed by the generated intermediate. The michael@0: name of the intermediate files will be the name '.der' or '.key'. The name michael@0: of the end entity files with be "ee-"+ name plus the appropiate prefixes. michael@0: The serial number will be generated radomly so it is potentially possible michael@0: to have problem (but very unlikely). michael@0: michael@0: Arguments: michael@0: db_dir -- location of the temporary params for the certificate michael@0: dest_dir -- location of the x509 cert michael@0: ca_key -- The filename of the key that will be used to sign the michael@0: intermediate (PEM FORMAT) michael@0: ca_cert -- The filename of the cert that will be used to sign the michael@0: intermediate, it MUST be the private key for the ca_key. michael@0: The file must be in DER format. michael@0: name -- the common name for the intermediate, will match the prefix michael@0: of the output intermediate. The ee will have the name michael@0: prefixed with "ee-" michael@0: int_ext_text -- the text for the x509 extensions to be added to the michael@0: intermediate certificate michael@0: ee_ext_text -- the text for the x509 extensions to be added to the michael@0: end entity certificate michael@0: key_type -- the type of key generated: potential values: 'rsa', 'dsa', michael@0: or any of the curves found by 'openssl ecparam -list_curves' michael@0: michael@0: output: michael@0: int_key -- the filename of the intermeidate key file (PEM format) michael@0: int_cert -- the filename of the intermediate certificate (DER format) michael@0: ee_key -- the filename of the end entity key file (PEM format) michael@0: ee_cert -- the filename of the end entity certficate (DER format) michael@0: michael@0: """ michael@0: [int_key, int_cert] = generate_cert_generic(db_dir, dest_dir, michael@0: random.randint(100,40000000), michael@0: key_type, "int-" + name, michael@0: int_ext_text, michael@0: ca_key, ca_cert) michael@0: [ee_key, ee_cert] = generate_cert_generic(db_dir, dest_dir, michael@0: random.randint(100,40000000), michael@0: key_type, name, michael@0: ee_ext_text, int_key, int_cert) michael@0: michael@0: return int_key, int_cert, ee_key, ee_cert michael@0: michael@0: def generate_pkcs12(db_dir, dest_dir, der_cert_filename, key_pem_filename, michael@0: prefix): michael@0: """ michael@0: Generate a pkcs12 file for a given certificate name (in der format) and michael@0: a key filename (key in pem format). The output file will have an empty michael@0: password. michael@0: michael@0: Arguments: michael@0: input: michael@0: db_dir -- location of the temporary params for the certificate michael@0: dest_dir -- location of the x509 cert michael@0: der_cert_filename -- the filename of the certificate to be included in the michael@0: output file (DER format) michael@0: key_pem_filename -- the filename of the private key of the certificate to michael@0: (PEM format) michael@0: prefix -- the name to be prepended to the output pkcs12 file. michael@0: output: michael@0: pk12_filename -- the filename of the outgoing pkcs12 output file michael@0: """ michael@0: #make pem cert file from der filename michael@0: pem_cert_filename = db_dir + "/" + prefix + ".pem" michael@0: pk12_filename = dest_dir + "/" + prefix + ".p12" michael@0: os.system("openssl x509 -inform der -in " + der_cert_filename + " -out " + michael@0: pem_cert_filename ) michael@0: #now make pkcs12 file michael@0: child = pexpect.spawn("openssl pkcs12 -export -in " + pem_cert_filename + michael@0: " -inkey " + key_pem_filename + " -out " + michael@0: pk12_filename) michael@0: child.expect('Enter Export Password:') michael@0: child.sendline('') michael@0: child.expect('Verifying - Enter Export Password:') michael@0: child.sendline('') michael@0: child.expect(pexpect.EOF) michael@0: return pk12_filename michael@0: michael@0: def init_nss_db(db_dir): michael@0: """ michael@0: Remove the current nss database in the specified directory and create a new michael@0: nss database with the cert8 format. michael@0: Arguments michael@0: db_dir -- the desired location of the new database michael@0: output michael@0: noise_file -- the path to a noise file suitable to generate TEST michael@0: certificates. This does not have enough entropy for a real michael@0: secret michael@0: pwd_file -- the patch to the secret file used for the database. michael@0: this file should be empty. michael@0: """ michael@0: nss_db_files = ["cert8.db", "key3.db", "secmod.db", "noise", "pwdfile"] michael@0: for file in nss_db_files: michael@0: if os.path.isfile(file): michael@0: os.remove(file) michael@0: # create noise file michael@0: noise_file = db_dir + "/noise" michael@0: nf = open(noise_file, 'w') michael@0: nf.write(str(time.time())) michael@0: nf.close() michael@0: # create pwd file michael@0: pwd_file = db_dir + "/pwfile" michael@0: pf = open(pwd_file, 'w') michael@0: pf.write("\n") michael@0: pf.close() michael@0: # create nss db michael@0: os.system("certutil -d " + db_dir + " -N -f " + pwd_file); michael@0: return [noise_file, pwd_file] michael@0: michael@0: def generate_ca_cert(db_dir, dest_dir, noise_file, name, version, do_bc): michael@0: """ michael@0: Creates a new CA certificate in an sql NSS database and as a der file michael@0: Arguments: michael@0: db_dir -- the location of the nss database (in sql format) michael@0: dest_dir -- the location of for the output file michael@0: noise_file -- the location of a noise file. michael@0: name -- the nickname of the new certificate in the database and the michael@0: common name of the certificate michael@0: version -- the version number of the certificate (valid certs must use michael@0: 3) michael@0: do_bc -- if the certificate should include the basic constraints michael@0: (valid ca's should be true) michael@0: output: michael@0: outname -- the location of the der file. michael@0: """ michael@0: out_name = dest_dir + "/" + name + ".der" michael@0: base_exec_string = ("certutil -S -z " + noise_file + " -g 2048 -d " + michael@0: db_dir + "/ -n " + name + " -v 120 -s 'CN=" + name + michael@0: ",O=PSM Testing,L=Mountain View,ST=California,C=US'" + michael@0: " -t C,C,C -x --certVersion=" + str(int(version))) michael@0: if (do_bc): michael@0: child = pexpect.spawn(base_exec_string + " -2") michael@0: child.logfile = sys.stdout michael@0: child.expect('Is this a CA certificate \[y/N\]?') michael@0: child.sendline('y') michael@0: child.expect('Enter the path length constraint, enter to skip \[<0 for unlimited path\]: >') michael@0: child.sendline('') michael@0: child.expect('Is this a critical extension \[y/N\]?') michael@0: child.sendline('') michael@0: child.expect(pexpect.EOF) michael@0: else: michael@0: os.system(base_exec_string) michael@0: os.system("certutil -d " + db_dir + "/ -L -n " + name + " -r > " + michael@0: out_name) michael@0: return out_name michael@0: michael@0: def generate_child_cert(db_dir, dest_dir, noise_file, name, ca_nick, version, michael@0: do_bc, is_ee, ocsp_url): michael@0: """ michael@0: Creates a new child certificate in an sql NSS database and as a der file michael@0: Arguments: michael@0: db_dir -- the location of the nss database (in sql format) michael@0: dest_dir -- the location of for the output file michael@0: noise_file -- the location of a noise file. michael@0: name -- the nickname of the new certificate in the database and the michael@0: common name of the certificate michael@0: ca_nick -- the nickname of the isser of the new certificate michael@0: version -- the version number of the certificate (valid certs must use michael@0: 3) michael@0: do_bc -- if the certificate should include the basic constraints michael@0: (valid ca's should be true) michael@0: is_ee -- is this and End Entity cert? false means intermediate michael@0: ocsp_url -- optional location of the ocsp responder for this certificate michael@0: this is included only if do_bc is set to True michael@0: output: michael@0: outname -- the location of the der file. michael@0: """ michael@0: michael@0: out_name = dest_dir + "/" + name + ".der" michael@0: base_exec_string = ("certutil -S -z " + noise_file + " -g 2048 -d " + michael@0: db_dir + "/ -n " + name + " -v 120 -m " + michael@0: str(random.randint(100, 40000000)) + " -s 'CN=" + name + michael@0: ",O=PSM Testing,L=Mountain View,ST=California,C=US'" + michael@0: " -t C,C,C -c " + ca_nick + " --certVersion=" + michael@0: str(int(version))) michael@0: if (do_bc): michael@0: extra_arguments = " -2" michael@0: if (ocsp_url): michael@0: extra_arguments += " --extAIA" michael@0: child = pexpect.spawn(base_exec_string + extra_arguments) michael@0: child.logfile = sys.stdout michael@0: child.expect('Is this a CA certificate \[y/N\]?') michael@0: if (is_ee): michael@0: child.sendline('N') michael@0: else: michael@0: child.sendline('y') michael@0: child.expect('Enter the path length constraint, enter to skip \[<0 for unlimited path\]: >') michael@0: child.sendline('') michael@0: child.expect('Is this a critical extension \[y/N\]?') michael@0: child.sendline('') michael@0: if (ocsp_url): michael@0: child.expect('\s+Choice >') michael@0: child.sendline('2') michael@0: child.expect('\s+Choice: >') michael@0: child.sendline('7') michael@0: child.expect('Enter data:') michael@0: child.sendline(ocsp_url) michael@0: child.expect('\s+Choice: >') michael@0: child.sendline('0') michael@0: child.expect('Add another location to the Authority Information Access extension \[y/N\]') michael@0: child.sendline('') michael@0: child.expect('Is this a critical extension \[y/N\]?') michael@0: child.sendline('') michael@0: child.expect(pexpect.EOF) michael@0: else: michael@0: os.system(base_exec_string) michael@0: os.system("certutil -d " + db_dir + "/ -L -n " + name + " -r > " + michael@0: out_name) michael@0: return out_name michael@0: