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