]>
Commit | Line | Data |
---|---|---|
f0aa99fb AF |
1 | #!/usr/bin/python |
2 | # vim:set et sw=4: | |
3 | # | |
4 | # certdata2pem.py - splits certdata.txt into multiple files | |
5 | # | |
6 | # Copyright (C) 2009 Philipp Kern <pkern@debian.org> | |
7 | # Copyright (C) 2013 Kai Engert <kaie@redhat.com> | |
8 | # | |
9 | # This program is free software; you can redistribute it and/or modify | |
10 | # it under the terms of the GNU General Public License as published by | |
11 | # the Free Software Foundation; either version 2 of the License, or | |
12 | # (at your option) any later version. | |
13 | # | |
14 | # This program is distributed in the hope that it will be useful, | |
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | # GNU General Public License for more details. | |
18 | # | |
19 | # You should have received a copy of the GNU General Public License | |
20 | # along with this program; if not, write to the Free Software | |
21 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, | |
22 | # USA. | |
23 | ||
24 | import base64 | |
25 | import os.path | |
26 | import re | |
27 | import sys | |
28 | import textwrap | |
043abb98 AB |
29 | import urllib.request, urllib.parse, urllib.error |
30 | import subprocess | |
f0aa99fb AF |
31 | |
32 | objects = [] | |
33 | ||
34 | def printable_serial(obj): | |
043abb98 | 35 | return ".".join([str(x) for x in obj['CKA_SERIAL_NUMBER']]) |
f0aa99fb AF |
36 | |
37 | # Dirty file parser. | |
38 | in_data, in_multiline, in_obj = False, False, False | |
043abb98 | 39 | field, ftype, value, binval, obj = None, None, None, bytearray(), dict() |
f0aa99fb AF |
40 | for line in open('certdata.txt', 'r'): |
41 | # Ignore the file header. | |
42 | if not in_data: | |
43 | if line.startswith('BEGINDATA'): | |
44 | in_data = True | |
45 | continue | |
46 | # Ignore comment lines. | |
47 | if line.startswith('#'): | |
48 | continue | |
49 | # Empty lines are significant if we are inside an object. | |
50 | if in_obj and len(line.strip()) == 0: | |
51 | objects.append(obj) | |
52 | obj = dict() | |
53 | in_obj = False | |
54 | continue | |
55 | if len(line.strip()) == 0: | |
56 | continue | |
57 | if in_multiline: | |
58 | if not line.startswith('END'): | |
043abb98 | 59 | if ftype == 'MULTILINE_OCTAL': |
f0aa99fb AF |
60 | line = line.strip() |
61 | for i in re.finditer(r'\\([0-3][0-7][0-7])', line): | |
043abb98 AB |
62 | integ = int(i.group(1), 8) |
63 | binval.extend((integ).to_bytes(1, sys.byteorder)) | |
64 | obj[field] = binval | |
f0aa99fb AF |
65 | else: |
66 | value += line | |
043abb98 | 67 | obj[field] = value |
f0aa99fb | 68 | continue |
f0aa99fb AF |
69 | in_multiline = False |
70 | continue | |
71 | if line.startswith('CKA_CLASS'): | |
72 | in_obj = True | |
73 | line_parts = line.strip().split(' ', 2) | |
74 | if len(line_parts) > 2: | |
043abb98 | 75 | field, ftype = line_parts[0:2] |
f0aa99fb AF |
76 | value = ' '.join(line_parts[2:]) |
77 | elif len(line_parts) == 2: | |
043abb98 | 78 | field, ftype = line_parts |
f0aa99fb AF |
79 | value = None |
80 | else: | |
043abb98 AB |
81 | raise NotImplementedError('line_parts < 2 not supported.\n' + line) |
82 | if ftype == 'MULTILINE_OCTAL': | |
f0aa99fb AF |
83 | in_multiline = True |
84 | value = "" | |
043abb98 | 85 | binval = bytearray() |
f0aa99fb AF |
86 | continue |
87 | obj[field] = value | |
043abb98 | 88 | if len(list(obj.items())) > 0: |
f0aa99fb AF |
89 | objects.append(obj) |
90 | ||
91 | # Build up trust database. | |
92 | trustmap = dict() | |
93 | for obj in objects: | |
94 | if obj['CKA_CLASS'] != 'CKO_NSS_TRUST': | |
95 | continue | |
96 | key = obj['CKA_LABEL'] + printable_serial(obj) | |
97 | trustmap[key] = obj | |
043abb98 | 98 | print(" added trust", key) |
f0aa99fb AF |
99 | |
100 | # Build up cert database. | |
101 | certmap = dict() | |
102 | for obj in objects: | |
103 | if obj['CKA_CLASS'] != 'CKO_CERTIFICATE': | |
104 | continue | |
105 | key = obj['CKA_LABEL'] + printable_serial(obj) | |
106 | certmap[key] = obj | |
043abb98 | 107 | print(" added cert", key) |
f0aa99fb AF |
108 | |
109 | def obj_to_filename(obj): | |
110 | label = obj['CKA_LABEL'][1:-1] | |
111 | label = label.replace('/', '_')\ | |
112 | .replace(' ', '_')\ | |
113 | .replace('(', '=')\ | |
114 | .replace(')', '=')\ | |
115 | .replace(',', '_') | |
043abb98 AB |
116 | labelbytes = bytearray() |
117 | i = 0 | |
118 | imax = len(label) | |
119 | while i < imax: | |
120 | if i < imax-3 and label[i] == '\\' and label[i+1] == 'x': | |
121 | labelbytes.extend(bytes.fromhex(label[i+2:i+4])) | |
122 | i += 4 | |
123 | continue | |
124 | labelbytes.extend(str.encode(label[i])) | |
125 | i = i+1 | |
126 | continue | |
127 | label = labelbytes.decode('utf-8') | |
f0aa99fb AF |
128 | serial = printable_serial(obj) |
129 | return label + ":" + serial | |
130 | ||
043abb98 AB |
131 | def write_cert_ext_to_file(f, oid, value, public_key): |
132 | f.write("[p11-kit-object-v1]\n") | |
133 | f.write("label: "); | |
134 | f.write(tobj['CKA_LABEL']) | |
135 | f.write("\n") | |
136 | f.write("class: x-certificate-extension\n"); | |
137 | f.write("object-id: " + oid + "\n") | |
138 | f.write("value: \"" + value + "\"\n") | |
139 | f.write("modifiable: false\n"); | |
140 | f.write(public_key) | |
141 | ||
f0aa99fb AF |
142 | trust_types = { |
143 | "CKA_TRUST_DIGITAL_SIGNATURE": "digital-signature", | |
144 | "CKA_TRUST_NON_REPUDIATION": "non-repudiation", | |
145 | "CKA_TRUST_KEY_ENCIPHERMENT": "key-encipherment", | |
146 | "CKA_TRUST_DATA_ENCIPHERMENT": "data-encipherment", | |
147 | "CKA_TRUST_KEY_AGREEMENT": "key-agreement", | |
148 | "CKA_TRUST_KEY_CERT_SIGN": "cert-sign", | |
149 | "CKA_TRUST_CRL_SIGN": "crl-sign", | |
150 | "CKA_TRUST_SERVER_AUTH": "server-auth", | |
151 | "CKA_TRUST_CLIENT_AUTH": "client-auth", | |
152 | "CKA_TRUST_CODE_SIGNING": "code-signing", | |
153 | "CKA_TRUST_EMAIL_PROTECTION": "email-protection", | |
154 | "CKA_TRUST_IPSEC_END_SYSTEM": "ipsec-end-system", | |
155 | "CKA_TRUST_IPSEC_TUNNEL": "ipsec-tunnel", | |
156 | "CKA_TRUST_IPSEC_USER": "ipsec-user", | |
157 | "CKA_TRUST_TIME_STAMPING": "time-stamping", | |
158 | "CKA_TRUST_STEP_UP_APPROVED": "step-up-approved", | |
159 | } | |
160 | ||
161 | legacy_trust_types = { | |
162 | "LEGACY_CKA_TRUST_SERVER_AUTH": "server-auth", | |
163 | "LEGACY_CKA_TRUST_CODE_SIGNING": "code-signing", | |
164 | "LEGACY_CKA_TRUST_EMAIL_PROTECTION": "email-protection", | |
165 | } | |
166 | ||
167 | legacy_to_real_trust_types = { | |
168 | "LEGACY_CKA_TRUST_SERVER_AUTH": "CKA_TRUST_SERVER_AUTH", | |
169 | "LEGACY_CKA_TRUST_CODE_SIGNING": "CKA_TRUST_CODE_SIGNING", | |
170 | "LEGACY_CKA_TRUST_EMAIL_PROTECTION": "CKA_TRUST_EMAIL_PROTECTION", | |
171 | } | |
172 | ||
173 | openssl_trust = { | |
174 | "CKA_TRUST_SERVER_AUTH": "serverAuth", | |
175 | "CKA_TRUST_CLIENT_AUTH": "clientAuth", | |
176 | "CKA_TRUST_CODE_SIGNING": "codeSigning", | |
177 | "CKA_TRUST_EMAIL_PROTECTION": "emailProtection", | |
178 | } | |
179 | ||
043abb98 AB |
180 | cert_distrust_types = { |
181 | "CKA_NSS_SERVER_DISTRUST_AFTER": "nss-server-distrust-after", | |
182 | "CKA_NSS_EMAIL_DISTRUST_AFTER": "nss-email-distrust-after", | |
183 | } | |
184 | ||
f0aa99fb AF |
185 | for tobj in objects: |
186 | if tobj['CKA_CLASS'] == 'CKO_NSS_TRUST': | |
187 | key = tobj['CKA_LABEL'] + printable_serial(tobj) | |
043abb98 | 188 | print("producing trust for " + key) |
f0aa99fb AF |
189 | trustbits = [] |
190 | distrustbits = [] | |
191 | openssl_trustflags = [] | |
192 | openssl_distrustflags = [] | |
193 | legacy_trustbits = [] | |
194 | legacy_openssl_trustflags = [] | |
043abb98 AB |
195 | for t in list(trust_types.keys()): |
196 | if t in tobj and tobj[t] == 'CKT_NSS_TRUSTED_DELEGATOR': | |
f0aa99fb AF |
197 | trustbits.append(t) |
198 | if t in openssl_trust: | |
199 | openssl_trustflags.append(openssl_trust[t]) | |
043abb98 | 200 | if t in tobj and tobj[t] == 'CKT_NSS_NOT_TRUSTED': |
f0aa99fb AF |
201 | distrustbits.append(t) |
202 | if t in openssl_trust: | |
203 | openssl_distrustflags.append(openssl_trust[t]) | |
204 | ||
043abb98 AB |
205 | for t in list(legacy_trust_types.keys()): |
206 | if t in tobj and tobj[t] == 'CKT_NSS_TRUSTED_DELEGATOR': | |
f0aa99fb AF |
207 | real_t = legacy_to_real_trust_types[t] |
208 | legacy_trustbits.append(real_t) | |
209 | if real_t in openssl_trust: | |
210 | legacy_openssl_trustflags.append(openssl_trust[real_t]) | |
043abb98 AB |
211 | if t in tobj and tobj[t] == 'CKT_NSS_NOT_TRUSTED': |
212 | raise NotImplementedError('legacy distrust not supported.\n' + line) | |
f0aa99fb AF |
213 | |
214 | fname = obj_to_filename(tobj) | |
215 | try: | |
216 | obj = certmap[key] | |
217 | except: | |
218 | obj = None | |
219 | ||
043abb98 AB |
220 | # optional debug code, that dumps the parsed input to files |
221 | #fulldump = "dump-" + fname | |
222 | #dumpf = open(fulldump, 'w') | |
223 | #dumpf.write(str(obj)); | |
224 | #dumpf.write(str(tobj)); | |
225 | #dumpf.close(); | |
f0aa99fb AF |
226 | |
227 | is_legacy = 0 | |
043abb98 | 228 | if 'LEGACY_CKA_TRUST_SERVER_AUTH' in tobj or 'LEGACY_CKA_TRUST_EMAIL_PROTECTION' in tobj or 'LEGACY_CKA_TRUST_CODE_SIGNING' in tobj: |
f0aa99fb AF |
229 | is_legacy = 1 |
230 | if obj == None: | |
043abb98 AB |
231 | raise NotImplementedError('found legacy trust without certificate.\n' + line) |
232 | ||
233 | legacy_fname = "legacy-default/" + fname + ".crt" | |
f0aa99fb AF |
234 | f = open(legacy_fname, 'w') |
235 | f.write("# alias=%s\n"%tobj['CKA_LABEL']) | |
236 | f.write("# trust=" + " ".join(legacy_trustbits) + "\n") | |
237 | if legacy_openssl_trustflags: | |
238 | f.write("# openssl-trust=" + " ".join(legacy_openssl_trustflags) + "\n") | |
239 | f.write("-----BEGIN CERTIFICATE-----\n") | |
043abb98 AB |
240 | temp_encoded_b64 = base64.b64encode(obj['CKA_VALUE']) |
241 | temp_wrapped = textwrap.wrap(temp_encoded_b64.decode(), 64) | |
242 | f.write("\n".join(temp_wrapped)) | |
f0aa99fb AF |
243 | f.write("\n-----END CERTIFICATE-----\n") |
244 | f.close() | |
f0aa99fb | 245 | |
043abb98 AB |
246 | if 'CKA_TRUST_SERVER_AUTH' in tobj or 'CKA_TRUST_EMAIL_PROTECTION' in tobj or 'CKA_TRUST_CODE_SIGNING' in tobj: |
247 | legacy_fname = "legacy-disable/" + fname + ".crt" | |
248 | f = open(legacy_fname, 'w') | |
249 | f.write("# alias=%s\n"%tobj['CKA_LABEL']) | |
250 | f.write("# trust=" + " ".join(trustbits) + "\n") | |
251 | if openssl_trustflags: | |
252 | f.write("# openssl-trust=" + " ".join(openssl_trustflags) + "\n") | |
253 | f.write("-----BEGIN CERTIFICATE-----\n") | |
254 | f.write("\n".join(textwrap.wrap(base64.b64encode(obj['CKA_VALUE']), 64))) | |
255 | f.write("\n-----END CERTIFICATE-----\n") | |
256 | f.close() | |
257 | ||
258 | # don't produce p11-kit output for legacy certificates | |
259 | continue | |
260 | ||
261 | pk = '' | |
262 | cert_comment = '' | |
263 | if obj != None: | |
264 | # must extract the public key from the cert, let's use openssl | |
265 | cert_fname = "cert-" + fname | |
266 | fc = open(cert_fname, 'w') | |
267 | fc.write("-----BEGIN CERTIFICATE-----\n") | |
268 | temp_encoded_b64 = base64.b64encode(obj['CKA_VALUE']) | |
269 | temp_wrapped = textwrap.wrap(temp_encoded_b64.decode(), 64) | |
270 | fc.write("\n".join(temp_wrapped)) | |
271 | fc.write("\n-----END CERTIFICATE-----\n") | |
272 | fc.close(); | |
273 | pk_fname = "pubkey-" + fname | |
274 | fpkout = open(pk_fname, "w") | |
275 | dump_pk_command = ["openssl", "x509", "-in", cert_fname, "-noout", "-pubkey"] | |
276 | subprocess.call(dump_pk_command, stdout=fpkout) | |
277 | fpkout.close() | |
278 | with open (pk_fname, "r") as myfile: | |
279 | pk=myfile.read() | |
280 | # obtain certificate information suitable as a comment | |
281 | comment_fname = "comment-" + fname | |
282 | fcout = open(comment_fname, "w") | |
283 | comment_command = ["openssl", "x509", "-in", cert_fname, "-noout", "-text"] | |
284 | subprocess.call(comment_command, stdout=fcout) | |
285 | fcout.close() | |
286 | sed_command = ["sed", "--in-place", "s/^/#/", comment_fname] | |
287 | subprocess.call(sed_command) | |
288 | with open (comment_fname, "r", errors = 'replace') as myfile: | |
289 | cert_comment=myfile.read() | |
290 | ||
291 | fname += ".tmp-p11-kit" | |
f0aa99fb | 292 | f = open(fname, 'w') |
043abb98 | 293 | |
f0aa99fb | 294 | if obj != None: |
043abb98 AB |
295 | is_distrusted = False |
296 | has_server_trust = False | |
297 | has_email_trust = False | |
298 | has_code_trust = False | |
299 | ||
300 | if 'CKA_TRUST_SERVER_AUTH' in tobj: | |
301 | if tobj['CKA_TRUST_SERVER_AUTH'] == 'CKT_NSS_NOT_TRUSTED': | |
302 | is_distrusted = True | |
303 | elif tobj['CKA_TRUST_SERVER_AUTH'] == 'CKT_NSS_TRUSTED_DELEGATOR': | |
304 | has_server_trust = True | |
305 | ||
306 | if 'CKA_TRUST_EMAIL_PROTECTION' in tobj: | |
307 | if tobj['CKA_TRUST_EMAIL_PROTECTION'] == 'CKT_NSS_NOT_TRUSTED': | |
308 | is_distrusted = True | |
309 | elif tobj['CKA_TRUST_EMAIL_PROTECTION'] == 'CKT_NSS_TRUSTED_DELEGATOR': | |
310 | has_email_trust = True | |
311 | ||
312 | if 'CKA_TRUST_CODE_SIGNING' in tobj: | |
313 | if tobj['CKA_TRUST_CODE_SIGNING'] == 'CKT_NSS_NOT_TRUSTED': | |
314 | is_distrusted = True | |
315 | elif tobj['CKA_TRUST_CODE_SIGNING'] == 'CKT_NSS_TRUSTED_DELEGATOR': | |
316 | has_code_trust = True | |
317 | ||
318 | if is_distrusted: | |
319 | trust_ext_oid = "1.3.6.1.4.1.3319.6.10.1" | |
320 | trust_ext_value = "0.%06%0a%2b%06%01%04%01%99w%06%0a%01%04 0%1e%06%08%2b%06%01%05%05%07%03%04%06%08%2b%06%01%05%05%07%03%01%06%08%2b%06%01%05%05%07%03%03" | |
321 | write_cert_ext_to_file(f, trust_ext_oid, trust_ext_value, pk) | |
322 | ||
323 | trust_ext_oid = "2.5.29.37" | |
324 | if has_server_trust: | |
325 | if has_email_trust: | |
326 | if has_code_trust: | |
327 | # server + email + code | |
328 | trust_ext_value = "0%2a%06%03U%1d%25%01%01%ff%04 0%1e%06%08%2b%06%01%05%05%07%03%04%06%08%2b%06%01%05%05%07%03%01%06%08%2b%06%01%05%05%07%03%03" | |
329 | else: | |
330 | # server + email | |
331 | trust_ext_value = "0 %06%03U%1d%25%01%01%ff%04%160%14%06%08%2b%06%01%05%05%07%03%04%06%08%2b%06%01%05%05%07%03%01" | |
332 | else: | |
333 | if has_code_trust: | |
334 | # server + code | |
335 | trust_ext_value = "0 %06%03U%1d%25%01%01%ff%04%160%14%06%08%2b%06%01%05%05%07%03%01%06%08%2b%06%01%05%05%07%03%03" | |
336 | else: | |
337 | # server | |
338 | trust_ext_value = "0%16%06%03U%1d%25%01%01%ff%04%0c0%0a%06%08%2b%06%01%05%05%07%03%01" | |
339 | else: | |
340 | if has_email_trust: | |
341 | if has_code_trust: | |
342 | # email + code | |
343 | trust_ext_value = "0 %06%03U%1d%25%01%01%ff%04%160%14%06%08%2b%06%01%05%05%07%03%04%06%08%2b%06%01%05%05%07%03%03" | |
344 | else: | |
345 | ||
346 | trust_ext_value = "0%16%06%03U%1d%25%01%01%ff%04%0c0%0a%06%08%2b%06%01%05%05%07%03%04" | |
347 | else: | |
348 | if has_code_trust: | |
349 | # code | |
350 | trust_ext_value = "0%16%06%03U%1d%25%01%01%ff%04%0c0%0a%06%08%2b%06%01%05%05%07%03%03" | |
351 | else: | |
352 | # none | |
353 | trust_ext_value = "0%18%06%03U%1d%25%01%01%ff%04%0e0%0c%06%0a%2b%06%01%04%01%99w%06%0a%10" | |
354 | ||
355 | # no 2.5.29.37 for neutral certificates | |
356 | if (is_distrusted or has_server_trust or has_email_trust or has_code_trust): | |
357 | write_cert_ext_to_file(f, trust_ext_oid, trust_ext_value, pk) | |
358 | ||
359 | pk = '' | |
360 | f.write("\n") | |
361 | ||
362 | f.write("[p11-kit-object-v1]\n") | |
363 | f.write("label: "); | |
364 | f.write(tobj['CKA_LABEL']) | |
365 | f.write("\n") | |
366 | if is_distrusted: | |
367 | f.write("x-distrusted: true\n") | |
368 | elif has_server_trust or has_email_trust or has_code_trust: | |
369 | f.write("trusted: true\n") | |
370 | else: | |
371 | f.write("trusted: false\n") | |
372 | ||
373 | # requires p11-kit >= 0.23.4 | |
374 | f.write("nss-mozilla-ca-policy: true\n") | |
375 | f.write("modifiable: false\n"); | |
376 | ||
377 | # requires p11-kit >= 0.23.19 | |
378 | for t in list(cert_distrust_types.keys()): | |
379 | if t in obj: | |
380 | value = obj[t] | |
381 | if value == 'CK_FALSE': | |
382 | value = bytearray(1) | |
383 | f.write(cert_distrust_types[t] + ": \"") | |
384 | f.write(urllib.parse.quote(value)); | |
385 | f.write("\"\n") | |
386 | ||
f0aa99fb | 387 | f.write("-----BEGIN CERTIFICATE-----\n") |
043abb98 AB |
388 | temp_encoded_b64 = base64.b64encode(obj['CKA_VALUE']) |
389 | temp_wrapped = textwrap.wrap(temp_encoded_b64.decode(), 64) | |
390 | f.write("\n".join(temp_wrapped)) | |
f0aa99fb | 391 | f.write("\n-----END CERTIFICATE-----\n") |
043abb98 AB |
392 | f.write(cert_comment) |
393 | f.write("\n") | |
394 | ||
f0aa99fb AF |
395 | else: |
396 | f.write("[p11-kit-object-v1]\n") | |
397 | f.write("label: "); | |
398 | f.write(tobj['CKA_LABEL']); | |
399 | f.write("\n") | |
400 | f.write("class: certificate\n") | |
401 | f.write("certificate-type: x-509\n") | |
043abb98 | 402 | f.write("modifiable: false\n"); |
f0aa99fb | 403 | f.write("issuer: \""); |
043abb98 | 404 | f.write(urllib.parse.quote(tobj['CKA_ISSUER'])); |
f0aa99fb AF |
405 | f.write("\"\n") |
406 | f.write("serial-number: \""); | |
043abb98 | 407 | f.write(urllib.parse.quote(tobj['CKA_SERIAL_NUMBER'])); |
f0aa99fb AF |
408 | f.write("\"\n") |
409 | if (tobj['CKA_TRUST_SERVER_AUTH'] == 'CKT_NSS_NOT_TRUSTED') or (tobj['CKA_TRUST_EMAIL_PROTECTION'] == 'CKT_NSS_NOT_TRUSTED') or (tobj['CKA_TRUST_CODE_SIGNING'] == 'CKT_NSS_NOT_TRUSTED'): | |
410 | f.write("x-distrusted: true\n") | |
411 | f.write("\n\n") | |
412 | f.close() | |
043abb98 | 413 | print(" -> written as '%s', trust = %s, openssl-trust = %s, distrust = %s, openssl-distrust = %s" % (fname, trustbits, openssl_trustflags, distrustbits, openssl_distrustflags)) |