michael@0: #!/usr/bin/python michael@0: 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: # requires openssl >= 1.0.0 michael@0: michael@0: import tempfile, os, sys, random michael@0: libpath = os.path.abspath('../psm_common_py') michael@0: michael@0: sys.path.append(libpath) michael@0: michael@0: import CertUtils michael@0: michael@0: srcdir = os.getcwd() michael@0: db = tempfile.mkdtemp() michael@0: michael@0: CA_basic_constraints = "basicConstraints = critical, CA:TRUE\n" michael@0: EE_basic_constraints = "basicConstraints = CA:FALSE\n" michael@0: michael@0: CA_full_ku = "keyUsage = keyCertSign, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, cRLSign\n" michael@0: EE_full_ku = "keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement\n" michael@0: michael@0: key_type = 'rsa' michael@0: michael@0: # codesigning differs significantly between mozilla::pkix and michael@0: # classic NSS that actual testing on it is not very useful michael@0: eku_values = { 'SA': "serverAuth", michael@0: 'CA': "clientAuth", michael@0: #'CS': "codeSigning", michael@0: 'EP': "emailProtection", michael@0: 'TS': "timeStamping", michael@0: 'NS': "nsSGC", # Netscape Server Gated Crypto. michael@0: 'OS': "1.3.6.1.5.5.7.3.9" michael@0: } michael@0: michael@0: cert_usages = [ "certificateUsageSSLClient", michael@0: "certificateUsageSSLServer", michael@0: "certificateUsageSSLCA", michael@0: "certificateUsageEmailSigner", michael@0: "certificateUsageEmailRecipient", michael@0: #"certificateUsageObjectSigner", michael@0: "certificateUsageStatusResponder" michael@0: ] michael@0: michael@0: js_file_header = """//// AUTOGENERATED FILE, DO NOT EDIT michael@0: // -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 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: "use strict"; michael@0: michael@0: do_get_profile(); // must be called before getting nsIX509CertDB michael@0: const certdb = Cc["@mozilla.org/security/x509certdb;1"] michael@0: .getService(Ci.nsIX509CertDB); michael@0: michael@0: function cert_from_file(filename) { michael@0: let der = readFile(do_get_file("test_cert_eku/" + filename, false)); michael@0: return certdb.constructX509(der, der.length); michael@0: } michael@0: michael@0: function load_cert(cert_name, trust_string) { michael@0: var cert_filename = cert_name + ".der"; michael@0: addCertFromFile(certdb, "test_cert_eku/" + cert_filename, trust_string); michael@0: return cert_from_file(cert_filename); michael@0: } michael@0: michael@0: function run_test() { michael@0: load_cert("ca", "CT,CT,CT"); michael@0: Services.prefs.setBoolPref("security.use_mozillapkix_verification", true); michael@0: """ michael@0: michael@0: js_file_footer = """} michael@0: """ michael@0: michael@0: def gen_int_js_output(int_string): michael@0: expectedResult = "SEC_ERROR_INADEQUATE_CERT_TYPE" michael@0: # For a certificate to verify successfully as a SSL CA, it must either michael@0: # have no EKU or have the Server Auth or Netscape Server Gated Crypto michael@0: # usage (the second of which is deprecated but currently supported for michael@0: # compatibility purposes). michael@0: if ("NONE" in int_string or "SA" in int_string or "NS" in int_string): michael@0: expectedResult = "0" michael@0: return (" checkCertErrorGeneric(certdb, load_cert('" + int_string + michael@0: "', ',,'), " + expectedResult + ", certificateUsageSSLCA);\n") michael@0: michael@0: def single_test_output(ee_name, cert_usage, error): michael@0: return (" checkCertErrorGeneric(certdb, cert_from_file('" + ee_name + michael@0: ".der'), " + error + ", " + cert_usage + ");\n") michael@0: michael@0: def usage_to_abbreviation(usage): michael@0: if usage is "certificateUsageStatusResponder": michael@0: return "OS" michael@0: if usage is "certificateUsageSSLServer": michael@0: return "SA" michael@0: if usage is "certificateUsageSSLClient": michael@0: return "CA" michael@0: if (usage is "certificateUsageEmailSigner" or michael@0: usage is "certificateUsageEmailRecipient"): michael@0: return "EP" michael@0: raise Exception("unsupported usage: " + usage) michael@0: michael@0: # In general, for a certificate to be compatible with a usage, it must either michael@0: # have no EKU at all or that usage must be present in its EKU extension. michael@0: def has_compatible_eku(name_string, usage_abbreviation): michael@0: return ("NONE" in name_string or usage_abbreviation in name_string) michael@0: michael@0: def gen_ee_js_output(int_string, ee_string, cert_usage, ee_name): michael@0: if cert_usage is "certificateUsageSSLCA": michael@0: # since none of these are CA certs (BC) these should all fail michael@0: return single_test_output(ee_name, cert_usage, michael@0: "SEC_ERROR_INADEQUATE_KEY_USAGE") michael@0: michael@0: usage_abbreviation = usage_to_abbreviation(cert_usage) michael@0: if cert_usage is "certificateUsageStatusResponder": michael@0: # For the Status Responder usage, the OSCP Signing usage must be michael@0: # present in the end-entity's EKU extension (i.e. if the extension michael@0: # is not present, the cert is not compatible with this usage). michael@0: if "OS" not in ee_string: michael@0: return single_test_output(ee_name, cert_usage, michael@0: "SEC_ERROR_INADEQUATE_CERT_TYPE") michael@0: if not has_compatible_eku(int_string, usage_abbreviation): michael@0: return single_test_output(ee_name, cert_usage, michael@0: "SEC_ERROR_INADEQUATE_CERT_TYPE") michael@0: return single_test_output(ee_name, cert_usage, "0") michael@0: michael@0: # If the usage isn't Status Responder, if the end-entity certificate has michael@0: # the OCSP Signing usage in its EKU, it is not valid for any other usage. michael@0: if "OS" in ee_string: michael@0: return single_test_output(ee_name, cert_usage, michael@0: "SEC_ERROR_INADEQUATE_CERT_TYPE") michael@0: michael@0: if cert_usage is "certificateUsageSSLServer": michael@0: if not has_compatible_eku(ee_string, usage_abbreviation): michael@0: return single_test_output(ee_name, cert_usage, michael@0: "SEC_ERROR_INADEQUATE_CERT_TYPE") michael@0: # If the usage is SSL Server, the intermediate certificate must either michael@0: # have no EKU extension or it must have the Server Auth or Netscape michael@0: # Server Gated Crypto (deprecated but allowed for compatibility). michael@0: if ("SA" not in int_string and "NONE" not in int_string and michael@0: "NS" not in int_string): michael@0: return single_test_output(ee_name, cert_usage, michael@0: "SEC_ERROR_INADEQUATE_CERT_TYPE") michael@0: return single_test_output(ee_name, cert_usage, "0") michael@0: michael@0: if not has_compatible_eku(ee_string, usage_abbreviation): michael@0: return single_test_output(ee_name, cert_usage, michael@0: "SEC_ERROR_INADEQUATE_CERT_TYPE") michael@0: if not has_compatible_eku(int_string, usage_abbreviation): michael@0: return single_test_output(ee_name, cert_usage, michael@0: "SEC_ERROR_INADEQUATE_CERT_TYPE") michael@0: michael@0: return single_test_output(ee_name, cert_usage, "0") michael@0: michael@0: def generate_test_eku(): michael@0: outmap = { "NONE" : ""} michael@0: # add each one by itself michael@0: for eku_name in (eku_values.keys()): michael@0: outmap[eku_name] = eku_values[eku_name] michael@0: # now combo of duples michael@0: eku_names = sorted(eku_values.keys()) michael@0: for i in range(len(eku_names)): michael@0: for j in range(i + 1, len(eku_names)): michael@0: name = eku_names[i] + "_" + eku_names[j] michael@0: outmap[name] = (eku_values[eku_names[i]] + "," + michael@0: eku_values[eku_names[j]]) michael@0: all_names = eku_names[0] michael@0: all_values = eku_values[eku_names[0]] michael@0: for i in range (1, len(eku_names)): michael@0: all_names = all_names + "_" + eku_names[i] michael@0: all_values = all_values + ", " + eku_values[eku_names[i]] michael@0: outmap[all_names] = all_values michael@0: return outmap michael@0: michael@0: def generate_certs(do_cert_generation): michael@0: js_outfile = open("../test_cert_eku.js", 'w') michael@0: ca_name = "ca" michael@0: if do_cert_generation: michael@0: [ca_key, ca_cert] = CertUtils.generate_cert_generic( michael@0: db, srcdir, 1, key_type, ca_name, michael@0: CA_basic_constraints) michael@0: ee_ext_text = EE_basic_constraints + EE_full_ku michael@0: michael@0: js_outfile.write(js_file_header) michael@0: michael@0: # now we do it again for valid basic constraints but strange eku/ku at the michael@0: # intermediate layer michael@0: eku_dict = generate_test_eku() michael@0: print eku_dict michael@0: for eku_name in (sorted(eku_dict.keys())): michael@0: # generate int michael@0: int_name = "int-EKU-" + eku_name michael@0: int_serial = random.randint(100, 40000000) michael@0: eku_text = "extendedKeyUsage = " + eku_dict[eku_name] michael@0: if (eku_name == "NONE"): michael@0: eku_text = "" michael@0: int_ext_text = CA_basic_constraints + CA_full_ku + eku_text michael@0: if do_cert_generation: michael@0: [int_key, int_cert] = CertUtils.generate_cert_generic( michael@0: db, srcdir, int_serial, key_type, int_name, michael@0: int_ext_text, ca_key, ca_cert) michael@0: js_outfile.write("\n") michael@0: js_outfile.write(gen_int_js_output(int_name)) michael@0: michael@0: for ee_eku_name in (sorted(eku_dict.keys())): michael@0: ee_base_name = "ee-EKU-" + ee_eku_name michael@0: ee_name = ee_base_name + "-" + int_name michael@0: ee_serial = random.randint(100, 40000000) michael@0: ee_eku = "extendedKeyUsage = critical," + eku_dict[ee_eku_name] michael@0: if (ee_eku_name == "NONE"): michael@0: ee_eku = "" michael@0: ee_ext_text = EE_basic_constraints + EE_full_ku + ee_eku michael@0: if do_cert_generation: michael@0: [ee_key, ee_cert] = CertUtils.generate_cert_generic( michael@0: db, srcdir, ee_serial, key_type, ee_name, michael@0: ee_ext_text, int_key, int_cert) michael@0: for cert_usage in (cert_usages): michael@0: js_outfile.write(gen_ee_js_output(int_name, ee_base_name, michael@0: cert_usage, ee_name)) michael@0: michael@0: js_outfile.write(js_file_footer) michael@0: js_outfile.close() michael@0: michael@0: # By default, re-generate the certificates. Anything can be passed as a michael@0: # command-line option to prevent this. michael@0: do_cert_generation = len(sys.argv) < 2 michael@0: generate_certs(do_cert_generation)