File: verify_cert.py

package info (click to toggle)
python-nss 0.15.0-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 1,452 kB
  • ctags: 1,755
  • sloc: ansic: 27,607; python: 2,688; makefile: 2
file content (283 lines) | stat: -rwxr-xr-x 11,575 bytes parent folder | download
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
#!/usr/bin/python

import argparse
import sys

import nss.nss as nss
import nss.error as nss_error

'''
This example illustrates how one can use NSS to verify (validate) a
certificate. Certificate validation starts with an intended usage for
the certificate and returns a set of flags for which the certificate
is actually valid for. When a cert fails validation it can be
useful to obtain diagnostic information as to why. One of the
verification methods includes returning the diagnostic information in
what is called a log. A cert can also be checked to see if it
qualifies as a CA cert.

The actual code to verify the cert is simple and straight forward. The
complexity in this example derives mainly from handling all the
options necessary to make the example flexible.

* The certificate may either be read from a file or loaded by nickname
  from a NSS database.

* You can optionally print the details the cert.

* You can specify a set of intened cert usages (each -u option adds an
  other usage to the set).

* You can enable/disable checking the cert signature.

* You can enable/disable using the log variant.

* You can enable/disable verifying the cert's CA status.

* The results are pretty printed.

'''

#-------------------------------------------------------------------------------
cert_usage_map = {
    'CheckAllUsages'        : nss.certificateUsageCheckAllUsages,
    'SSLClient'             : nss.certificateUsageSSLClient,
    'SSLServer'             : nss.certificateUsageSSLServer,
    'SSLServerWithStepUp'   : nss.certificateUsageSSLServerWithStepUp,
    'SSLCA'                 : nss.certificateUsageSSLCA,
    'EmailSigner'           : nss.certificateUsageEmailSigner,
    'EmailRecipient'        : nss.certificateUsageEmailRecipient,
    'ObjectSigner'          : nss.certificateUsageObjectSigner,
    'UserCertImport'        : nss.certificateUsageUserCertImport,
    'VerifyCA'              : nss.certificateUsageVerifyCA,
    'ProtectedObjectSigner' : nss.certificateUsageProtectedObjectSigner,
    'StatusResponder'       : nss.certificateUsageStatusResponder,
    'AnyCA'                 : nss.certificateUsageAnyCA,
}

#-------------------------------------------------------------------------------

def password_callback(slot, retry, password):
    return options.db_passwd

def indented_output(msg, l, level=0):
    msg = '%s:' % msg
    lines = []
    if not l:
        l = ['--']
    lines.extend(nss.make_line_fmt_tuples(level, msg))
    lines.extend(nss.make_line_fmt_tuples(level+1, l))
    return nss.indented_format(lines)

def indented_obj(msg, obj, level=0):
    msg = '%s:' % msg
    lines = []
    lines.extend(nss.make_line_fmt_tuples(level, msg))
    lines.extend(obj.format_lines(level+1))
    return nss.indented_format(lines)


#-------------------------------------------------------------------------------

def main():
    global options

    parser = argparse.ArgumentParser(description='certificate validation example')

    # === NSS Database Group ===
    group = parser.add_argument_group('NSS Database',
                                      'Specify & control the NSS Database')
    group.add_argument('-d', '--db-name',
                       help='NSS database name (e.g. "sql:pki")')

    group.add_argument('-P', '--db-passwd',
                       help='NSS database password')

    # === Certificate Group ===
    group = parser.add_argument_group('Certificate',
                                      'Specify how the certificate is loaded')

    group.add_argument('-f', '--file', dest='cert_filename',
                       help='read cert from file')

    group.add_argument('-F', '--input-format', choices=['pem', 'der'],
                       help='format of input cert')

    group.add_argument('-n', '--nickname', dest='cert_nickname',
                       help='load cert from NSS database by looking it up under this nickname')

    # === Validation Group ===
    group = parser.add_argument_group('Validation',
                                      'Control the validation')

    group.add_argument('-u', '--usage', dest='cert_usage', action='append', choices=cert_usage_map.keys(),
                           help='certificate usage flags, may be specified multiple times')
    group.add_argument('-c', '--check-sig', action='store_true', dest='check_sig',
                           help='check signature')
    group.add_argument('-C', '--no-check-sig', action='store_false', dest='check_sig',
                           help='do not check signature')
    group.add_argument('-l', '--log', action='store_true', dest='with_log',
                           help='use verify log')
    group.add_argument('-L', '--no-log', action='store_false', dest='with_log',
                           help='do not use verify log')
    group.add_argument('-a', '--check-ca', action='store_true', dest='check_ca',
                           help='check if cert is CA')
    group.add_argument('-A', '--no-check-ca', action='store_false', dest='check_ca',
                           help='do not check if cert is CA')

    # === Miscellaneous Group ===
    group = parser.add_argument_group('Miscellaneous',
                                      'Miscellaneous options')

    group.add_argument('-p', '--print-cert', action='store_true', dest='print_cert',
                       help='print the certificate in a friendly fashion')


    parser.set_defaults(db_name = 'sql:pki',
                        db_passwd = 'db_passwd',
                        input_format = 'pem',
                        check_sig = True,
                        with_log = True,
                        check_ca = True,
                        print_cert = False,
                        )

    options = parser.parse_args()

    # Process the command line arguments

    # Get usage bitmask
    if options.cert_usage:
        intended_usage = 0
        for usage in options.cert_usage:
            try:
                flag = cert_usage_map[usage]
            except KeyError:
                print "Unknown usage '%s', valid values: %s" % (usage, ', '.join(sorted(cert_usage_map.keys())))
                return 1
            else:
                intended_usage |= flag
    else:
        # We can't use nss.certificateUsageCheckAllUsages here because
        # it's a special value of zero instead of being the bitwise OR
        # of all the certificateUsage* flags (go figure!)
        intended_usage = 0
        for usage in cert_usage_map.values():
            intended_usage |= usage

    if options.cert_filename and options.cert_nickname:
        print >>sys.stderr, "You may not specify both a cert filename and a nickname, only one or the other"
        return 1

    if not options.cert_filename and not options.cert_nickname:
        print >>sys.stderr, "You must specify either a cert filename or a nickname to load"
        return 1

    # Initialize NSS.
    print indented_output('NSS Database', options.db_name)
    print
    nss.nss_init(options.db_name)
    certdb = nss.get_default_certdb()
    nss.set_password_callback(password_callback)

    # Load the cert
    if options.cert_filename:
        # Read the certificate as DER encoded data then initialize a Certificate from the DER data
        filename = options.cert_filename
        si = nss.read_der_from_file(filename, options.input_format.lower() == 'pem')
        # Parse the DER encoded data returning a Certificate object
        cert = nss.Certificate(si)
    else:
        try:
            cert = nss.find_cert_from_nickname(options.cert_nickname)
        except Exception, e:
            print e
            print >>sys.stderr, 'Unable to load cert nickname "%s" from database "%s"' % \
                (options.cert_nickname, options.db_name)
            return 1

    # Dump the cert if the user wants to see it
    if options.print_cert:
        print cert
    else:
        print indented_output('cert subject', cert.subject)
    print

    # Dump the usages attached to the cert
    print indented_output('cert has these usages', nss.cert_type_flags(cert.cert_type))

    # Should we check if the cert is a CA cert?
    if options.check_ca:
        # CA Cert?
        is_ca, cert_type = cert.is_ca_cert(True)
        print
        print indented_output('is CA cert boolean', is_ca)
        print indented_output('is CA cert returned usages', nss.cert_type_flags(cert_type))

    print
    print indented_output('verifying usages for', nss.cert_usage_flags(intended_usage))
    print

    # Use the log or non-log variant to verify the cert
    #
    # Note: Anytime a NSPR or NSS function returns an error in python-nss it
    # raises a NSPRError exception. When an exception is raised the normal
    # return values are discarded because the flow of control continues at
    # the first except block prepared to catch the exception. Normally this
    # is what is desired because the return values would be invalid due to
    # the error. However the certificate verification functions are an
    # exception (no pun intended). An error might be returned indicating the
    # cert failed verification but you may still need access to the returned
    # usage bitmask and the log (if using the log variant). To handle this a
    # special error exception `CertVerifyError` (derived from `NSPRError`)
    # is defined which in addition to the normal NSPRError fields will also
    # contain the returned usages and optionally the CertVerifyLog
    # object. If no exception is raised these are returned as normal return
    # values.

    approved_usage = 0
    if options.with_log:
        try:
            approved_usage, log = cert.verify_with_log(certdb, options.check_sig, intended_usage, None)
        except nss_error.CertVerifyError, e:
            # approved_usage and log available in CertVerifyError exception on failure.
            print e
            print
            print indented_obj('log', e.log)
            print
            print indented_output('approved usages from exception', nss.cert_usage_flags(e.usages))
            approved_usage = e.usages # Get the returned usage bitmask from the exception
        except Exception, e:
            print e
        else:
            print indented_output('approved usages', nss.cert_usage_flags(approved_usage))
            if log.count:
                print
                print indented_obj('log', log)
    else:
        try:
            approved_usage = cert.verify(certdb, options.check_sig, intended_usage, None)
        except nss_error.CertVerifyError, e:
            # approved_usage available in CertVerifyError exception on failure.
            print e
            print indented_output('approved usages from exception', nss.cert_usage_flags(e.usages))
            approved_usage = e.usages # Get the returned usage bitmask from the exception
        except Exception, e:
            print e
        else:
            print indented_output('approved usages', nss.cert_usage_flags(approved_usage))

    # The cert is valid if all the intended usages are in the approved usages
    valid = (intended_usage & approved_usage) == intended_usage

    print
    if valid:
        print indented_output('SUCCESS: cert is approved for', nss.cert_usage_flags(intended_usage))
        return 0
    else:
        print indented_output('FAIL: cert not approved for', nss.cert_usage_flags(intended_usage ^ approved_usage))
        return 1

#-------------------------------------------------------------------------------
if __name__ == "__main__":
    sys.exit(main())