287 lines
12 KiB
Python
Executable File
287 lines
12 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
import sys
|
|
import optparse
|
|
|
|
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():
|
|
# Command line argument processing
|
|
parser = optparse.OptionParser()
|
|
|
|
parser.set_defaults(dbdir = '/etc/pki/nssdb',
|
|
db_passwd = 'db_passwd',
|
|
input_format = 'pem',
|
|
check_sig = True,
|
|
print_cert = False,
|
|
with_log = True,
|
|
check_ca = True,
|
|
)
|
|
|
|
param_group = optparse.OptionGroup(parser, 'NSS Database',
|
|
'Specify & control the NSS Database')
|
|
|
|
param_group.add_option('-d', '--dbdir', dest='dbdir',
|
|
help='NSS database directory, default="%default"')
|
|
param_group.add_option('-P', '--db-passwd', dest='db_passwd',
|
|
help='NSS database password, default="%default"')
|
|
|
|
parser.add_option_group(param_group)
|
|
|
|
param_group = optparse.OptionGroup(parser, 'Certificate',
|
|
'Specify how the certificate is loaded')
|
|
|
|
param_group.add_option('-f', '--file', dest='cert_filename',
|
|
help='read cert from file')
|
|
param_group.add_option('--format', dest='input_format', choices=['pem', 'der'],
|
|
help='import format for certificate (der|pem) default="%default"')
|
|
param_group.add_option('-n', '--nickname', dest='cert_nickname',
|
|
help='load cert from NSS database by looking it up under this nickname')
|
|
|
|
|
|
parser.add_option_group(param_group)
|
|
|
|
param_group = optparse.OptionGroup(parser, 'Validation',
|
|
'Control the validation')
|
|
|
|
param_group.add_option('-u', '--usage', dest='cert_usage', action='append', choices=cert_usage_map.keys(),
|
|
help='may be specified multiple times, default="CheckAllUsages", may be one of: %s' % ', '.join(sorted(cert_usage_map.keys())))
|
|
param_group.add_option('-c', '--check-sig', action='store_true', dest='check_sig',
|
|
help='check signature default=%default')
|
|
param_group.add_option('-C', '--no-check-sig', action='store_false', dest='check_sig',
|
|
help='check signature')
|
|
param_group.add_option('-l', '--log', action='store_true', dest='with_log',
|
|
help='use verify log, default=%default')
|
|
param_group.add_option('-L', '--no-log', action='store_false', dest='with_log',
|
|
help='use verify log, default=%default')
|
|
param_group.add_option('-a', '--check-ca', action='store_true', dest='check_ca',
|
|
help='check if cert is CA, default=%default')
|
|
param_group.add_option('-A', '--no-check-ca', action='store_false', dest='check_ca',
|
|
help='check if cert is CA, default=%default')
|
|
|
|
parser.add_option_group(param_group)
|
|
|
|
param_group = optparse.OptionGroup(parser, 'Miscellaneous',
|
|
'Miscellaneous options')
|
|
|
|
param_group.add_option('-p', '--print-cert', action='store_true', dest='print_cert',
|
|
help='print the certificate in a friendly fashion, default=%default')
|
|
|
|
parser.add_option_group(param_group)
|
|
|
|
options, args = 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.dbdir)
|
|
print
|
|
nss.nss_init(options.dbdir)
|
|
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.dbdir)
|
|
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())
|
|
|
|
|