4 # certdata2pem.py - splits certdata.txt into multiple files
6 # Copyright (C) 2009 Philipp Kern <pkern@debian.org>
7 # Copyright (C) 2013 Kai Engert <kaie@redhat.com>
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.
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.
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,
29 import urllib
.request
, urllib
.parse
, urllib
.error
34 def printable_serial(obj
):
35 return ".".join([str(x
) for x
in obj
['CKA_SERIAL_NUMBER']])
38 in_data
, in_multiline
, in_obj
= False, False, False
39 field
, ftype
, value
, binval
, obj
= None, None, None, bytearray(), dict()
40 for line
in open('certdata.txt', 'r'):
41 # Ignore the file header.
43 if line
.startswith('BEGINDATA'):
46 # Ignore comment lines.
47 if line
.startswith('#'):
49 # Empty lines are significant if we are inside an object.
50 if in_obj
and len(line
.strip()) == 0:
55 if len(line
.strip()) == 0:
58 if not line
.startswith('END'):
59 if ftype
== 'MULTILINE_OCTAL':
61 for i
in re
.finditer(r
'\\([0-3][0-7][0-7])', line
):
62 integ
= int(i
.group(1), 8)
63 binval
.extend((integ
).to_bytes(1, sys
.byteorder
))
71 if line
.startswith('CKA_CLASS'):
73 line_parts
= line
.strip().split(' ', 2)
74 if len(line_parts
) > 2:
75 field
, ftype
= line_parts
[0:2]
76 value
= ' '.join(line_parts
[2:])
77 elif len(line_parts
) == 2:
78 field
, ftype
= line_parts
81 raise NotImplementedError('line_parts < 2 not supported.\n' + line
)
82 if ftype
== 'MULTILINE_OCTAL':
88 if len(list(obj
.items())) > 0:
91 # Build up trust database.
94 if obj
['CKA_CLASS'] != 'CKO_NSS_TRUST':
96 key
= obj
['CKA_LABEL'] + printable_serial(obj
)
98 print(" added trust", key
)
100 # Build up cert database.
103 if obj
['CKA_CLASS'] != 'CKO_CERTIFICATE':
105 key
= obj
['CKA_LABEL'] + printable_serial(obj
)
107 print(" added cert", key
)
109 def obj_to_filename(obj
):
110 label
= obj
['CKA_LABEL'][1:-1]
111 label
= label
.replace('/', '_')\
116 labelbytes
= bytearray()
120 if i
< imax
-3 and label
[i
] == '\\' and label
[i
+1] == 'x':
121 labelbytes
.extend(bytes
.fromhex(label
[i
+2:i
+4]))
124 labelbytes
.extend(str.encode(label
[i
]))
127 label
= labelbytes
.decode('utf-8')
128 serial
= printable_serial(obj
)
129 return label
+ ":" + serial
131 def write_cert_ext_to_file(f
, oid
, value
, public_key
):
132 f
.write("[p11-kit-object-v1]\n")
134 f
.write(tobj
['CKA_LABEL'])
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");
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",
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",
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",
174 "CKA_TRUST_SERVER_AUTH": "serverAuth",
175 "CKA_TRUST_CLIENT_AUTH": "clientAuth",
176 "CKA_TRUST_CODE_SIGNING": "codeSigning",
177 "CKA_TRUST_EMAIL_PROTECTION": "emailProtection",
180 cert_distrust_types
= {
181 "CKA_NSS_SERVER_DISTRUST_AFTER": "nss-server-distrust-after",
182 "CKA_NSS_EMAIL_DISTRUST_AFTER": "nss-email-distrust-after",
186 if tobj
['CKA_CLASS'] == 'CKO_NSS_TRUST':
187 key
= tobj
['CKA_LABEL'] + printable_serial(tobj
)
188 print("producing trust for " + key
)
191 openssl_trustflags
= []
192 openssl_distrustflags
= []
193 legacy_trustbits
= []
194 legacy_openssl_trustflags
= []
195 for t
in list(trust_types
.keys()):
196 if t
in tobj
and tobj
[t
] == 'CKT_NSS_TRUSTED_DELEGATOR':
198 if t
in openssl_trust
:
199 openssl_trustflags
.append(openssl_trust
[t
])
200 if t
in tobj
and tobj
[t
] == 'CKT_NSS_NOT_TRUSTED':
201 distrustbits
.append(t
)
202 if t
in openssl_trust
:
203 openssl_distrustflags
.append(openssl_trust
[t
])
205 for t
in list(legacy_trust_types
.keys()):
206 if t
in tobj
and tobj
[t
] == 'CKT_NSS_TRUSTED_DELEGATOR':
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
])
211 if t
in tobj
and tobj
[t
] == 'CKT_NSS_NOT_TRUSTED':
212 raise NotImplementedError('legacy distrust not supported.\n' + line
)
214 fname
= obj_to_filename(tobj
)
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));
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
:
231 raise NotImplementedError('found legacy trust without certificate.\n' + line
)
233 legacy_fname
= "legacy-default/" + fname
+ ".crt"
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")
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
))
243 f
.write("\n-----END CERTIFICATE-----\n")
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")
258 # don't produce p11-kit output for legacy certificates
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")
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
)
278 with
open (pk_fname
, "r") as myfile
:
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
)
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()
291 fname
+= ".tmp-p11-kit"
295 is_distrusted
= False
296 has_server_trust
= False
297 has_email_trust
= False
298 has_code_trust
= False
300 if 'CKA_TRUST_SERVER_AUTH' in tobj
:
301 if tobj
['CKA_TRUST_SERVER_AUTH'] == 'CKT_NSS_NOT_TRUSTED':
303 elif tobj
['CKA_TRUST_SERVER_AUTH'] == 'CKT_NSS_TRUSTED_DELEGATOR':
304 has_server_trust
= True
306 if 'CKA_TRUST_EMAIL_PROTECTION' in tobj
:
307 if tobj
['CKA_TRUST_EMAIL_PROTECTION'] == 'CKT_NSS_NOT_TRUSTED':
309 elif tobj
['CKA_TRUST_EMAIL_PROTECTION'] == 'CKT_NSS_TRUSTED_DELEGATOR':
310 has_email_trust
= True
312 if 'CKA_TRUST_CODE_SIGNING' in tobj
:
313 if tobj
['CKA_TRUST_CODE_SIGNING'] == 'CKT_NSS_NOT_TRUSTED':
315 elif tobj
['CKA_TRUST_CODE_SIGNING'] == 'CKT_NSS_TRUSTED_DELEGATOR':
316 has_code_trust
= True
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
)
323 trust_ext_oid
= "2.5.29.37"
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"
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"
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"
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"
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"
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"
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"
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"
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
)
362 f
.write("[p11-kit-object-v1]\n")
364 f
.write(tobj
['CKA_LABEL'])
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")
371 f
.write("trusted: false\n")
373 # requires p11-kit >= 0.23.4
374 f
.write("nss-mozilla-ca-policy: true\n")
375 f
.write("modifiable: false\n");
377 # requires p11-kit >= 0.23.19
378 for t
in list(cert_distrust_types
.keys()):
381 if value
== 'CK_FALSE':
383 f
.write(cert_distrust_types
[t
] + ": \"")
384 f
.write(urllib
.parse
.quote(value
));
387 f
.write("-----BEGIN CERTIFICATE-----\n")
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
))
391 f
.write("\n-----END CERTIFICATE-----\n")
392 f
.write(cert_comment
)
396 f
.write("[p11-kit-object-v1]\n")
398 f
.write(tobj
['CKA_LABEL']);
400 f
.write("class: certificate\n")
401 f
.write("certificate-type: x-509\n")
402 f
.write("modifiable: false\n");
403 f
.write("issuer: \"");
404 f
.write(urllib
.parse
.quote(tobj
['CKA_ISSUER']));
406 f
.write("serial-number: \"");
407 f
.write(urllib
.parse
.quote(tobj
['CKA_SERIAL_NUMBER']));
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")
413 print(" -> written as '%s', trust = %s, openssl-trust = %s, distrust = %s, openssl-distrust = %s" % (fname
, trustbits
, openssl_trustflags
, distrustbits
, openssl_distrustflags
))