Blame certdata2pem.py

William Pitcock c17d73
#!/usr/bin/python
William Pitcock c17d73
# vim:set et sw=4:
William Pitcock c17d73
#
William Pitcock c17d73
# certdata2pem.py - splits certdata.txt into multiple files
William Pitcock c17d73
#
William Pitcock c17d73
# Copyright (C) 2009 Philipp Kern <pkern@debian.org>
William Pitcock c17d73
#
William Pitcock c17d73
# This program is free software; you can redistribute it and/or modify
William Pitcock c17d73
# it under the terms of the GNU General Public License as published by
William Pitcock c17d73
# the Free Software Foundation; either version 2 of the License, or
William Pitcock c17d73
# (at your option) any later version.
William Pitcock c17d73
#
William Pitcock c17d73
# This program is distributed in the hope that it will be useful,
William Pitcock c17d73
# but WITHOUT ANY WARRANTY; without even the implied warranty of
William Pitcock c17d73
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
William Pitcock c17d73
# GNU General Public License for more details.
William Pitcock c17d73
#
William Pitcock c17d73
# You should have received a copy of the GNU General Public License
William Pitcock c17d73
# along with this program; if not, write to the Free Software
William Pitcock c17d73
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,
William Pitcock c17d73
# USA.
William Pitcock c17d73
William Pitcock c17d73
import base64
William Pitcock c17d73
import os.path
William Pitcock c17d73
import re
William Pitcock c17d73
import sys
William Pitcock c17d73
import textwrap
William Pitcock c17d73
import io
William Pitcock c17d73
William Pitcock c17d73
objects = []
William Pitcock c17d73
William Pitcock c17d73
# Dirty file parser.
William Pitcock c17d73
in_data, in_multiline, in_obj = False, False, False
William Pitcock c17d73
field, type, value, obj = None, None, None, dict()
William Pitcock c17d73
William Pitcock c17d73
# Python 3 will not let us decode non-ascii characters if we
William Pitcock c17d73
# have not specified an encoding, but Python 2's open does not
William Pitcock c17d73
# have an option to set the encoding. Python 3's open is io.open
William Pitcock c17d73
# and io.open has been backported to Python 2.6 and 2.7, so use io.open.
William Pitcock c17d73
for line in io.open('certdata.txt', 'rt', encoding='utf8'):
William Pitcock c17d73
    # Ignore the file header.
William Pitcock c17d73
    if not in_data:
William Pitcock c17d73
        if line.startswith('BEGINDATA'):
William Pitcock c17d73
            in_data = True
William Pitcock c17d73
        continue
William Pitcock c17d73
    # Ignore comment lines.
William Pitcock c17d73
    if line.startswith('#'):
William Pitcock c17d73
        continue
William Pitcock c17d73
    # Empty lines are significant if we are inside an object.
William Pitcock c17d73
    if in_obj and len(line.strip()) == 0:
William Pitcock c17d73
        objects.append(obj)
William Pitcock c17d73
        obj = dict()
William Pitcock c17d73
        in_obj = False
William Pitcock c17d73
        continue
William Pitcock c17d73
    if len(line.strip()) == 0:
William Pitcock c17d73
        continue
William Pitcock c17d73
    if in_multiline:
William Pitcock c17d73
        if not line.startswith('END'):
William Pitcock c17d73
            if type == 'MULTILINE_OCTAL':
William Pitcock c17d73
                line = line.strip()
William Pitcock c17d73
                for i in re.finditer(r'\\([0-3][0-7][0-7])', line):
William Pitcock c17d73
                    value.append(int(i.group(1), 8))
William Pitcock c17d73
            else:
William Pitcock c17d73
                value += line
William Pitcock c17d73
            continue
William Pitcock c17d73
        obj[field] = value
William Pitcock c17d73
        in_multiline = False
William Pitcock c17d73
        continue
William Pitcock c17d73
    if line.startswith('CKA_CLASS'):
William Pitcock c17d73
        in_obj = True
William Pitcock c17d73
    line_parts = line.strip().split(' ', 2)
William Pitcock c17d73
    if len(line_parts) > 2:
William Pitcock c17d73
        field, type = line_parts[0:2]
William Pitcock c17d73
        value = ' '.join(line_parts[2:])
William Pitcock c17d73
    elif len(line_parts) == 2:
William Pitcock c17d73
        field, type = line_parts
William Pitcock c17d73
        value = None
William Pitcock c17d73
    else:
William Pitcock c17d73
        raise NotImplementedError('line_parts < 2 not supported.')
William Pitcock c17d73
    if type == 'MULTILINE_OCTAL':
William Pitcock c17d73
        in_multiline = True
William Pitcock c17d73
        value = bytearray()
William Pitcock c17d73
        continue
William Pitcock c17d73
    obj[field] = value
William Pitcock c17d73
if len(obj) > 0:
William Pitcock c17d73
    objects.append(obj)
William Pitcock c17d73
William Pitcock c17d73
# Read blacklist.
William Pitcock c17d73
blacklist = []
William Pitcock c17d73
if os.path.exists('blacklist.txt'):
William Pitcock c17d73
    for line in open('blacklist.txt', 'r'):
William Pitcock c17d73
        line = line.strip()
William Pitcock c17d73
        if line.startswith('#') or len(line) == 0:
William Pitcock c17d73
            continue
William Pitcock c17d73
        item = line.split('#', 1)[0].strip()
William Pitcock c17d73
        blacklist.append(item)
William Pitcock c17d73
William Pitcock c17d73
# Build up trust database.
William Pitcock c17d73
trust = dict()
William Pitcock c17d73
for obj in objects:
William Pitcock c17d73
    if obj['CKA_CLASS'] != 'CKO_NSS_TRUST':
William Pitcock c17d73
        continue
William Pitcock c17d73
    if obj['CKA_LABEL'] in blacklist:
William Pitcock c17d73
        print("Certificate %s blacklisted, ignoring." % obj['CKA_LABEL'])
William Pitcock c17d73
    elif obj['CKA_TRUST_SERVER_AUTH'] == 'CKT_NSS_TRUSTED_DELEGATOR':
William Pitcock c17d73
        trust[obj['CKA_LABEL']] = True
William Pitcock c17d73
    elif obj['CKA_TRUST_EMAIL_PROTECTION'] == 'CKT_NSS_TRUSTED_DELEGATOR':
William Pitcock c17d73
        trust[obj['CKA_LABEL']] = True
William Pitcock c17d73
    elif obj['CKA_TRUST_SERVER_AUTH'] == 'CKT_NSS_NOT_TRUSTED':
William Pitcock c17d73
        print('!'*74)
William Pitcock c17d73
        print("UNTRUSTED BUT NOT BLACKLISTED CERTIFICATE FOUND: %s" % obj['CKA_LABEL'])
William Pitcock c17d73
        print('!'*74)
William Pitcock c17d73
    else:
William Pitcock c17d73
        print("Ignoring certificate %s.  SAUTH=%s, EPROT=%s" % \
William Pitcock c17d73
              (obj['CKA_LABEL'], obj['CKA_TRUST_SERVER_AUTH'],
William Pitcock c17d73
               obj['CKA_TRUST_EMAIL_PROTECTION']))
William Pitcock c17d73
William Pitcock c17d73
for obj in objects:
William Pitcock c17d73
    if obj['CKA_CLASS'] == 'CKO_CERTIFICATE':
William Pitcock c17d73
        if not obj['CKA_LABEL'] in trust or not trust[obj['CKA_LABEL']]:
William Pitcock c17d73
            continue
William Pitcock c17d73
        bname = obj['CKA_LABEL'][1:-1].replace('/', '_')\
William Pitcock c17d73
                                      .replace(' ', '_')\
William Pitcock c17d73
                                      .replace('(', '=')\
William Pitcock c17d73
                                      .replace(')', '=')\
William Pitcock c17d73
                                      .replace(',', '_')
William Pitcock c17d73
William Pitcock c17d73
        # this is the only way to decode the way NSS stores multi-byte UTF-8
William Pitcock c17d73
        # and we need an escaped string for checking existence of things
William Pitcock c17d73
        # otherwise we're dependant on the user's current locale.
William Pitcock c17d73
        if bytes != str:
William Pitcock c17d73
            # We're in python 3, convert the utf-8 string to a
William Pitcock c17d73
            # sequence of bytes that represents this utf-8 string
William Pitcock c17d73
            # then encode the byte-sequence as an escaped string that
William Pitcock c17d73
            # can be passed to open() and os.path.exists()
William Pitcock c17d73
            bname = bname.encode('utf-8').decode('unicode_escape').encode('latin-1')
William Pitcock c17d73
        else:
William Pitcock c17d73
            # Python 2
William Pitcock c17d73
            # Convert the unicode string back to its original byte form
William Pitcock c17d73
            # (contents of files returned by io.open are returned as
William Pitcock c17d73
            #  unicode strings)
William Pitcock c17d73
            # then to an escaped string that can be passed to open()
William Pitcock c17d73
            # and os.path.exists()
William Pitcock c17d73
            bname = bname.encode('utf-8').decode('string_escape')
William Pitcock c17d73
William Pitcock c17d73
        fname = bname + b'.crt'
William Pitcock c17d73
        if os.path.exists(fname):
William Pitcock c17d73
            print("Found duplicate certificate name %s, renaming." % bname)
William Pitcock c17d73
            fname = bname + b'_2.crt'
William Pitcock c17d73
        f = open(fname, 'w')
William Pitcock c17d73
        f.write("-----BEGIN CERTIFICATE-----\n")
William Pitcock c17d73
        encoded = base64.b64encode(obj['CKA_VALUE']).decode('utf-8')
William Pitcock c17d73
        f.write("\n".join(textwrap.wrap(encoded, 64)))
William Pitcock c17d73
        f.write("\n-----END CERTIFICATE-----\n")
William Pitcock c17d73