summaryrefslogtreecommitdiff
path: root/certdata2pem.py
blob: 5af0defc98e57d6fdd799cf947eebb5ba8fb06fa (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#!/usr/bin/python
# vim:set et sw=4:
#
# certdata2pem.py - splits certdata.txt into multiple files
#
# Copyright (C) 2009 Philipp Kern <pkern@debian.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,
# USA.

import base64
import datetime
import io
import os.path
import pathlib
import re
import sys
import textwrap

DATE_FMT = "%a %b %d %H:%M:%S %Y"
VERSION = pathlib.Path(__file__).parent / "VERSION"
VERSION = datetime.datetime.strptime(VERSION.read_text().strip(), "%Y%m%d")

objects = []

# Dirty file parser.
in_data, in_multiline, in_obj = False, False, False
field, type, value, obj = None, None, None, dict()

# Python 3 will not let us decode non-ascii characters if we
# have not specified an encoding, but Python 2's open does not
# have an option to set the encoding. Python 3's open is io.open
# and io.open has been backported to Python 2.6 and 2.7, so use io.open.
for line in io.open('certdata.txt', 'rt', encoding='utf8'):
    # Ignore the file header.
    if not in_data:
        if line.startswith('BEGINDATA'):
            in_data = True
        continue
    # Empty lines are significant if we are inside an object.
    if in_obj and len(line.strip()) == 0:
        objects.append(obj)
        obj = dict()
        in_obj = False
        continue
    if len(line.strip()) == 0:
        continue
    if line.startswith("# Not Valid Before: "):
        line = line.replace("# Not Valid Before: ", "", 1).strip()
        obj["before"] = datetime.datetime.strptime(line, DATE_FMT)
        continue
    if line.startswith("# Not Valid After : "):
        line = line.replace("# Not Valid After : ", "", 1).strip()
        obj["after"] = datetime.datetime.strptime(line, DATE_FMT)
        continue
    # Ignore comment lines.
    if line.startswith('#'):
        continue
    if in_multiline:
        if not line.startswith('END'):
            if type == 'MULTILINE_OCTAL':
                line = line.strip()
                for i in re.finditer(r'\\([0-3][0-7][0-7])', line):
                    value.append(int(i.group(1), 8))
            else:
                value += line
            continue
        obj[field] = value
        in_multiline = False
        continue
    if line.startswith('CKA_CLASS'):
        in_obj = True
    line_parts = line.strip().split(' ', 2)
    if len(line_parts) > 2:
        field, type = line_parts[0:2]
        value = ' '.join(line_parts[2:])
    elif len(line_parts) == 2:
        field, type = line_parts
        value = None
    else:
        raise NotImplementedError('line_parts < 2 not supported.')
    if type == 'MULTILINE_OCTAL':
        in_multiline = True
        value = bytearray()
        continue
    obj[field] = value
if len(obj) > 0:
    objects.append(obj)

# Read blacklist.
blacklist = []
if os.path.exists('blacklist.txt'):
    for line in open('blacklist.txt', 'r'):
        line = line.strip()
        if line.startswith('#') or len(line) == 0:
            continue
        item = line.split('#', 1)[0].strip()
        blacklist.append(item)

# Build up trust database.
trust = dict()
next_expiring = None
for obj in objects:
    if obj['CKA_CLASS'] != 'CKO_NSS_TRUST':
        continue
    if obj['CKA_LABEL'] in blacklist:
        print("Certificate %s blacklisted, ignoring." % obj['CKA_LABEL'])
    elif obj['CKA_TRUST_SERVER_AUTH'] == 'CKT_NSS_TRUSTED_DELEGATOR':
        if VERSION < obj["before"] or VERSION > obj["after"]:
            print('!'*74)
            print("EXPIRED BUT NOT BLACKLISTED CERTIFICATE FOUND: %s" % obj['CKA_LABEL'])
            print('!'*74)
        else:
            if not next_expiring:
                next_expiring = obj
            elif obj['after'] < next_expiring['after']:
                next_expiring = obj
            trust[obj['CKA_LABEL']] = True
    elif obj['CKA_TRUST_SERVER_AUTH'] == 'CKT_NSS_NOT_TRUSTED':
        print('!'*74)
        print("UNTRUSTED BUT NOT BLACKLISTED CERTIFICATE FOUND: %s" % obj['CKA_LABEL'])
        print('!'*74)
    else:
        print("Ignoring certificate %s.  SAUTH=%s, EPROT=%s" % \
              (obj['CKA_LABEL'], obj['CKA_TRUST_SERVER_AUTH'],
               obj['CKA_TRUST_EMAIL_PROTECTION']))

print('Next expiring certificate:', next_expiring['CKA_LABEL'], next_expiring['after'])

for obj in objects:
    if obj['CKA_CLASS'] == 'CKO_CERTIFICATE':
        if not obj['CKA_LABEL'] in trust or not trust[obj['CKA_LABEL']]:
            continue
        bname = obj['CKA_LABEL'][1:-1].replace('/', '_')\
                                      .replace(' ', '_')\
                                      .replace('(', '=')\
                                      .replace(')', '=')\
                                      .replace(',', '_')

        # this is the only way to decode the way NSS stores multi-byte UTF-8
        # and we need an escaped string for checking existence of things
        # otherwise we're dependant on the user's current locale.
        if bytes != str:
            # We're in python 3, convert the utf-8 string to a
            # sequence of bytes that represents this utf-8 string
            # then encode the byte-sequence as an escaped string that
            # can be passed to open() and os.path.exists()
            bname = bname.encode('utf-8').decode('unicode_escape').encode('latin-1')
        else:
            # Python 2
            # Convert the unicode string back to its original byte form
            # (contents of files returned by io.open are returned as
            #  unicode strings)
            # then to an escaped string that can be passed to open()
            # and os.path.exists()
            bname = bname.encode('utf-8').decode('string_escape')

        fname = bname + b'.crt'
        if os.path.exists(fname):
            print("Found duplicate certificate name %s, renaming." % bname)
            fname = bname + b'_2.crt'
        f = open(fname, 'w')
        f.write("-----BEGIN CERTIFICATE-----\n")
        encoded = base64.b64encode(obj['CKA_VALUE']).decode('utf-8')
        f.write("\n".join(textwrap.wrap(encoded, 64)))
        f.write("\n-----END CERTIFICATE-----\n")