Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | #!/usr/bin/python |
michael@0 | 2 | |
michael@0 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 6 | |
michael@0 | 7 | # requires openssl >= 1.0.0 |
michael@0 | 8 | |
michael@0 | 9 | import tempfile, os, sys, random |
michael@0 | 10 | libpath = os.path.abspath('../psm_common_py') |
michael@0 | 11 | |
michael@0 | 12 | sys.path.append(libpath) |
michael@0 | 13 | |
michael@0 | 14 | import CertUtils |
michael@0 | 15 | |
michael@0 | 16 | srcdir = os.getcwd() |
michael@0 | 17 | db = tempfile.mkdtemp() |
michael@0 | 18 | |
michael@0 | 19 | CA_basic_constraints = "basicConstraints = critical, CA:TRUE\n" |
michael@0 | 20 | EE_basic_constraints = "basicConstraints = CA:FALSE\n" |
michael@0 | 21 | |
michael@0 | 22 | CA_full_ku = "keyUsage = keyCertSign, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, cRLSign\n" |
michael@0 | 23 | EE_full_ku = "keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement\n" |
michael@0 | 24 | |
michael@0 | 25 | key_type = 'rsa' |
michael@0 | 26 | |
michael@0 | 27 | # codesigning differs significantly between mozilla::pkix and |
michael@0 | 28 | # classic NSS that actual testing on it is not very useful |
michael@0 | 29 | eku_values = { 'SA': "serverAuth", |
michael@0 | 30 | 'CA': "clientAuth", |
michael@0 | 31 | #'CS': "codeSigning", |
michael@0 | 32 | 'EP': "emailProtection", |
michael@0 | 33 | 'TS': "timeStamping", |
michael@0 | 34 | 'NS': "nsSGC", # Netscape Server Gated Crypto. |
michael@0 | 35 | 'OS': "1.3.6.1.5.5.7.3.9" |
michael@0 | 36 | } |
michael@0 | 37 | |
michael@0 | 38 | cert_usages = [ "certificateUsageSSLClient", |
michael@0 | 39 | "certificateUsageSSLServer", |
michael@0 | 40 | "certificateUsageSSLCA", |
michael@0 | 41 | "certificateUsageEmailSigner", |
michael@0 | 42 | "certificateUsageEmailRecipient", |
michael@0 | 43 | #"certificateUsageObjectSigner", |
michael@0 | 44 | "certificateUsageStatusResponder" |
michael@0 | 45 | ] |
michael@0 | 46 | |
michael@0 | 47 | js_file_header = """//// AUTOGENERATED FILE, DO NOT EDIT |
michael@0 | 48 | // -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
michael@0 | 49 | // This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 50 | // License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 51 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 52 | |
michael@0 | 53 | "use strict"; |
michael@0 | 54 | |
michael@0 | 55 | do_get_profile(); // must be called before getting nsIX509CertDB |
michael@0 | 56 | const certdb = Cc["@mozilla.org/security/x509certdb;1"] |
michael@0 | 57 | .getService(Ci.nsIX509CertDB); |
michael@0 | 58 | |
michael@0 | 59 | function cert_from_file(filename) { |
michael@0 | 60 | let der = readFile(do_get_file("test_cert_eku/" + filename, false)); |
michael@0 | 61 | return certdb.constructX509(der, der.length); |
michael@0 | 62 | } |
michael@0 | 63 | |
michael@0 | 64 | function load_cert(cert_name, trust_string) { |
michael@0 | 65 | var cert_filename = cert_name + ".der"; |
michael@0 | 66 | addCertFromFile(certdb, "test_cert_eku/" + cert_filename, trust_string); |
michael@0 | 67 | return cert_from_file(cert_filename); |
michael@0 | 68 | } |
michael@0 | 69 | |
michael@0 | 70 | function run_test() { |
michael@0 | 71 | load_cert("ca", "CT,CT,CT"); |
michael@0 | 72 | Services.prefs.setBoolPref("security.use_mozillapkix_verification", true); |
michael@0 | 73 | """ |
michael@0 | 74 | |
michael@0 | 75 | js_file_footer = """} |
michael@0 | 76 | """ |
michael@0 | 77 | |
michael@0 | 78 | def gen_int_js_output(int_string): |
michael@0 | 79 | expectedResult = "SEC_ERROR_INADEQUATE_CERT_TYPE" |
michael@0 | 80 | # For a certificate to verify successfully as a SSL CA, it must either |
michael@0 | 81 | # have no EKU or have the Server Auth or Netscape Server Gated Crypto |
michael@0 | 82 | # usage (the second of which is deprecated but currently supported for |
michael@0 | 83 | # compatibility purposes). |
michael@0 | 84 | if ("NONE" in int_string or "SA" in int_string or "NS" in int_string): |
michael@0 | 85 | expectedResult = "0" |
michael@0 | 86 | return (" checkCertErrorGeneric(certdb, load_cert('" + int_string + |
michael@0 | 87 | "', ',,'), " + expectedResult + ", certificateUsageSSLCA);\n") |
michael@0 | 88 | |
michael@0 | 89 | def single_test_output(ee_name, cert_usage, error): |
michael@0 | 90 | return (" checkCertErrorGeneric(certdb, cert_from_file('" + ee_name + |
michael@0 | 91 | ".der'), " + error + ", " + cert_usage + ");\n") |
michael@0 | 92 | |
michael@0 | 93 | def usage_to_abbreviation(usage): |
michael@0 | 94 | if usage is "certificateUsageStatusResponder": |
michael@0 | 95 | return "OS" |
michael@0 | 96 | if usage is "certificateUsageSSLServer": |
michael@0 | 97 | return "SA" |
michael@0 | 98 | if usage is "certificateUsageSSLClient": |
michael@0 | 99 | return "CA" |
michael@0 | 100 | if (usage is "certificateUsageEmailSigner" or |
michael@0 | 101 | usage is "certificateUsageEmailRecipient"): |
michael@0 | 102 | return "EP" |
michael@0 | 103 | raise Exception("unsupported usage: " + usage) |
michael@0 | 104 | |
michael@0 | 105 | # In general, for a certificate to be compatible with a usage, it must either |
michael@0 | 106 | # have no EKU at all or that usage must be present in its EKU extension. |
michael@0 | 107 | def has_compatible_eku(name_string, usage_abbreviation): |
michael@0 | 108 | return ("NONE" in name_string or usage_abbreviation in name_string) |
michael@0 | 109 | |
michael@0 | 110 | def gen_ee_js_output(int_string, ee_string, cert_usage, ee_name): |
michael@0 | 111 | if cert_usage is "certificateUsageSSLCA": |
michael@0 | 112 | # since none of these are CA certs (BC) these should all fail |
michael@0 | 113 | return single_test_output(ee_name, cert_usage, |
michael@0 | 114 | "SEC_ERROR_INADEQUATE_KEY_USAGE") |
michael@0 | 115 | |
michael@0 | 116 | usage_abbreviation = usage_to_abbreviation(cert_usage) |
michael@0 | 117 | if cert_usage is "certificateUsageStatusResponder": |
michael@0 | 118 | # For the Status Responder usage, the OSCP Signing usage must be |
michael@0 | 119 | # present in the end-entity's EKU extension (i.e. if the extension |
michael@0 | 120 | # is not present, the cert is not compatible with this usage). |
michael@0 | 121 | if "OS" not in ee_string: |
michael@0 | 122 | return single_test_output(ee_name, cert_usage, |
michael@0 | 123 | "SEC_ERROR_INADEQUATE_CERT_TYPE") |
michael@0 | 124 | if not has_compatible_eku(int_string, usage_abbreviation): |
michael@0 | 125 | return single_test_output(ee_name, cert_usage, |
michael@0 | 126 | "SEC_ERROR_INADEQUATE_CERT_TYPE") |
michael@0 | 127 | return single_test_output(ee_name, cert_usage, "0") |
michael@0 | 128 | |
michael@0 | 129 | # If the usage isn't Status Responder, if the end-entity certificate has |
michael@0 | 130 | # the OCSP Signing usage in its EKU, it is not valid for any other usage. |
michael@0 | 131 | if "OS" in ee_string: |
michael@0 | 132 | return single_test_output(ee_name, cert_usage, |
michael@0 | 133 | "SEC_ERROR_INADEQUATE_CERT_TYPE") |
michael@0 | 134 | |
michael@0 | 135 | if cert_usage is "certificateUsageSSLServer": |
michael@0 | 136 | if not has_compatible_eku(ee_string, usage_abbreviation): |
michael@0 | 137 | return single_test_output(ee_name, cert_usage, |
michael@0 | 138 | "SEC_ERROR_INADEQUATE_CERT_TYPE") |
michael@0 | 139 | # If the usage is SSL Server, the intermediate certificate must either |
michael@0 | 140 | # have no EKU extension or it must have the Server Auth or Netscape |
michael@0 | 141 | # Server Gated Crypto (deprecated but allowed for compatibility). |
michael@0 | 142 | if ("SA" not in int_string and "NONE" not in int_string and |
michael@0 | 143 | "NS" not in int_string): |
michael@0 | 144 | return single_test_output(ee_name, cert_usage, |
michael@0 | 145 | "SEC_ERROR_INADEQUATE_CERT_TYPE") |
michael@0 | 146 | return single_test_output(ee_name, cert_usage, "0") |
michael@0 | 147 | |
michael@0 | 148 | if not has_compatible_eku(ee_string, usage_abbreviation): |
michael@0 | 149 | return single_test_output(ee_name, cert_usage, |
michael@0 | 150 | "SEC_ERROR_INADEQUATE_CERT_TYPE") |
michael@0 | 151 | if not has_compatible_eku(int_string, usage_abbreviation): |
michael@0 | 152 | return single_test_output(ee_name, cert_usage, |
michael@0 | 153 | "SEC_ERROR_INADEQUATE_CERT_TYPE") |
michael@0 | 154 | |
michael@0 | 155 | return single_test_output(ee_name, cert_usage, "0") |
michael@0 | 156 | |
michael@0 | 157 | def generate_test_eku(): |
michael@0 | 158 | outmap = { "NONE" : ""} |
michael@0 | 159 | # add each one by itself |
michael@0 | 160 | for eku_name in (eku_values.keys()): |
michael@0 | 161 | outmap[eku_name] = eku_values[eku_name] |
michael@0 | 162 | # now combo of duples |
michael@0 | 163 | eku_names = sorted(eku_values.keys()) |
michael@0 | 164 | for i in range(len(eku_names)): |
michael@0 | 165 | for j in range(i + 1, len(eku_names)): |
michael@0 | 166 | name = eku_names[i] + "_" + eku_names[j] |
michael@0 | 167 | outmap[name] = (eku_values[eku_names[i]] + "," + |
michael@0 | 168 | eku_values[eku_names[j]]) |
michael@0 | 169 | all_names = eku_names[0] |
michael@0 | 170 | all_values = eku_values[eku_names[0]] |
michael@0 | 171 | for i in range (1, len(eku_names)): |
michael@0 | 172 | all_names = all_names + "_" + eku_names[i] |
michael@0 | 173 | all_values = all_values + ", " + eku_values[eku_names[i]] |
michael@0 | 174 | outmap[all_names] = all_values |
michael@0 | 175 | return outmap |
michael@0 | 176 | |
michael@0 | 177 | def generate_certs(do_cert_generation): |
michael@0 | 178 | js_outfile = open("../test_cert_eku.js", 'w') |
michael@0 | 179 | ca_name = "ca" |
michael@0 | 180 | if do_cert_generation: |
michael@0 | 181 | [ca_key, ca_cert] = CertUtils.generate_cert_generic( |
michael@0 | 182 | db, srcdir, 1, key_type, ca_name, |
michael@0 | 183 | CA_basic_constraints) |
michael@0 | 184 | ee_ext_text = EE_basic_constraints + EE_full_ku |
michael@0 | 185 | |
michael@0 | 186 | js_outfile.write(js_file_header) |
michael@0 | 187 | |
michael@0 | 188 | # now we do it again for valid basic constraints but strange eku/ku at the |
michael@0 | 189 | # intermediate layer |
michael@0 | 190 | eku_dict = generate_test_eku() |
michael@0 | 191 | print eku_dict |
michael@0 | 192 | for eku_name in (sorted(eku_dict.keys())): |
michael@0 | 193 | # generate int |
michael@0 | 194 | int_name = "int-EKU-" + eku_name |
michael@0 | 195 | int_serial = random.randint(100, 40000000) |
michael@0 | 196 | eku_text = "extendedKeyUsage = " + eku_dict[eku_name] |
michael@0 | 197 | if (eku_name == "NONE"): |
michael@0 | 198 | eku_text = "" |
michael@0 | 199 | int_ext_text = CA_basic_constraints + CA_full_ku + eku_text |
michael@0 | 200 | if do_cert_generation: |
michael@0 | 201 | [int_key, int_cert] = CertUtils.generate_cert_generic( |
michael@0 | 202 | db, srcdir, int_serial, key_type, int_name, |
michael@0 | 203 | int_ext_text, ca_key, ca_cert) |
michael@0 | 204 | js_outfile.write("\n") |
michael@0 | 205 | js_outfile.write(gen_int_js_output(int_name)) |
michael@0 | 206 | |
michael@0 | 207 | for ee_eku_name in (sorted(eku_dict.keys())): |
michael@0 | 208 | ee_base_name = "ee-EKU-" + ee_eku_name |
michael@0 | 209 | ee_name = ee_base_name + "-" + int_name |
michael@0 | 210 | ee_serial = random.randint(100, 40000000) |
michael@0 | 211 | ee_eku = "extendedKeyUsage = critical," + eku_dict[ee_eku_name] |
michael@0 | 212 | if (ee_eku_name == "NONE"): |
michael@0 | 213 | ee_eku = "" |
michael@0 | 214 | ee_ext_text = EE_basic_constraints + EE_full_ku + ee_eku |
michael@0 | 215 | if do_cert_generation: |
michael@0 | 216 | [ee_key, ee_cert] = CertUtils.generate_cert_generic( |
michael@0 | 217 | db, srcdir, ee_serial, key_type, ee_name, |
michael@0 | 218 | ee_ext_text, int_key, int_cert) |
michael@0 | 219 | for cert_usage in (cert_usages): |
michael@0 | 220 | js_outfile.write(gen_ee_js_output(int_name, ee_base_name, |
michael@0 | 221 | cert_usage, ee_name)) |
michael@0 | 222 | |
michael@0 | 223 | js_outfile.write(js_file_footer) |
michael@0 | 224 | js_outfile.close() |
michael@0 | 225 | |
michael@0 | 226 | # By default, re-generate the certificates. Anything can be passed as a |
michael@0 | 227 | # command-line option to prevent this. |
michael@0 | 228 | do_cert_generation = len(sys.argv) < 2 |
michael@0 | 229 | generate_certs(do_cert_generation) |