add ca.verify tests
[stack/cam.git] / cam / main.py
index 538697e..4158e42 100755 (executable)
@@ -2,21 +2,20 @@
 
 import logging
 import optparse
-import os
 import sys
 import time
 from cam import config
 
 
 USAGE = '''cam [<OPTIONS>] <COMMAND> [<ARG>...]
-CAM v2.0 - (c)2012 by <ale@incal.net> 
-A Certification Authority manager for complex situations.
+CAM v%(version)s - (c)2012-2014 by <ale@incal.net>
+Minimal X509 Certification Authority management tool.
 
 Known commands:
 
-  init [<RSA_CRT> [<DSA_CRT>]]
+  init [<RSA_CRT>]
     Initialize the environment and create a new CA certificate
-    (you can also import your own existing certificates)
+    (you can also import an existing certificate)
 
   gen <TAG>...
     Create (or re-create) the certificates corresponding
@@ -28,22 +27,28 @@ Known commands:
   list
     List all known certificates
 
+  verify FILES...
+    Verify the certificates found in FILES against the CA
+
   fp [<TAG>...]
     Print SHA1/MD5 fingerprints of certificates
 
-  files <TAG>
+  files <TAG>...
     Dump all the certificate-related files of this TAG
 
-  check 
+  check
     Should be run weekly from a cron job to warn you if some
     certificates are about to expire (controlled by the 'warning_days'
     parameter in the 'global' section of the configuration)
 
-The configuration file consists of a ini-style file, with one 'ca'
+The configuration file consists of a ini-style file, with a 'ca'
 section that specifies global CA parameters, and more sections for
-each tag with certificate-specific information. See the examples for
-more details on how to write your own configuration.
-'''
+each tag with certificate-specific information. See the documentation
+for more details on how to write your own configuration.
+
+Run `cam --help' to get a list of available command-line options.
+
+''' % {'version': '2.1'}
 
 
 def find_cert(certs, name):
@@ -53,67 +58,133 @@ def find_cert(certs, name):
     raise Exception('Certificate "%s" not found' % name)
 
 
+def cmd_init(global_config, ca, certs, args):
+    ca.create()
+
+
+def cmd_gen(global_config, ca, certs, args):
+    if len(args) < 1:
+        print 'Nothing to do.'
+    for tag in args:
+        ca.generate(find_cert(certs, tag))
+
+
+def cmd_gencrl(global_config, ca, certs, args):
+    ca.gencrl()
+
+
+def cmd_files(global_config, ca, certs, args):
+    if len(args) < 1:
+        print 'Nothing to do.'
+    for tag in args:
+        c = find_cert(certs, tag)
+        print c.public_key_file
+        print c.private_key_file
+
+
+def cmd_list(global_config, ca, certs, args):
+    now = time.time()
+    for cert in sorted(certs, key=lambda x: x.name):
+        expiry = cert.get_expiration_date()
+        state = 'OK'
+        expiry_str = ''
+        if not expiry:
+            state = 'MISSING'
+        else:
+            if expiry < now:
+                state = 'EXPIRED'
+            expiry_str = time.strftime('%Y/%m/%d', time.gmtime(expiry))
+        print cert.name, cert.cn, state, expiry_str
+
+
+def cmd_verify(global_config, ca, certs, args):
+    if len(args) < 1:
+        print 'Nothing to do.'
+    failed = False
+    for path in args:
+        if not ca.verify(path):
+            print '%s: FAIL' % path
+            failed = True
+        else:
+            print '%s: OK' % path
+    return failed
+
+
+def cmd_fingerprint(global_config, ca, certs, args):
+    if len(args) > 0:
+        certs = [find_cert(certs, x) for x in args]
+    for cert in certs:
+        print cert.name, cert.cn
+        print '  SHA1:', cert.get_fingerprint('sha1')
+        print '  MD5:', cert.get_fingerprint('md5')
+
+
+def cmd_check(global_config, ca, certs, args):
+    now = time.time()
+    warning_time = 86400 * int(global_config.get('warning_days', 15))
+    retval = 0
+    for cert in certs:
+        exp = cert.get_expiration_date()
+        if exp and (exp - now) < warning_time:
+            print '%s (%s) is about to expire.' % (cert.name, cert.cn)
+            retval = 1
+    return retval
+
+
+cmd_table = {
+    'init': cmd_init,
+    'gen': cmd_gen,
+    'gencrl': cmd_gencrl,
+    'files': cmd_files,
+    'list': cmd_list,
+    'verify': cmd_verify,
+    'fp': cmd_fingerprint,
+    'fingerprint': cmd_fingerprint,
+    'check': cmd_check,
+}
+
+
 def main():
     parser = optparse.OptionParser(usage=USAGE)
     parser.add_option('-d', '--debug', dest='debug', help='Be verbose',
                       action='store_true')
     parser.add_option('-c', '--config', dest='config', help='Config file')
     opts, args = parser.parse_args()
+
+    if len(args) > 0 and args[0] == 'help':
+        parser.print_help()
+        return 0
     if not opts.config:
         parser.error('Must specify --config')
     if len(args) < 1:
         parser.error('Must specify a command')
 
-    logging.basicConfig()
-    logging.getLogger().setLevel(opts.debug and logging.DEBUG or logging.INFO)
-
-    global_config, ca, certs = config.read_config(opts.config)
-
-    cmd, args = args[0], args[1:]
+    logging.basicConfig(
+        format='cam: %(levelname)s: %(message)s',
+        level=logging.DEBUG if opts.debug else logging.INFO)
 
     try:
-        if cmd == 'init':
-            ca.create()
-        elif cmd == 'gen':
-            if len(args) != 1:
-                parser.error('Wrong number of arguments')
-            ca.generate(find_cert(certs, args[0]))
-        elif cmd == 'gencrl':
-            ca.gencrl()
-        elif cmd == 'files':
-            if len(args) != 1:
-                parser.error('Wrong number of arguments')
-            c = find_cert(certs, args[0])
-            print c.public_key_file
-            print c.private_key_file
-        elif cmd == 'list':
-            for cert in sorted(certs, key=lambda x: x.name):
-                print cert.name, cert.cn, cert.get_expiration_date()
-        elif cmd == 'fp' or cmd == 'fingerprint':
-            if len(args) > 0:
-                certs = [find_cert(certs, x) for x in args]
-            for cert in certs:
-                print cert.name, cert.cn
-                print '  SHA1:', cert.get_fingerprint('sha1')
-                print '  MD5:', cert.get_fingerprint('md5')
-        elif cmd == 'check':
-            now = time.time()
-            warning_time = 86400 * int(global_config.get('warning_days', 15))
-            for cert in certs:
-                exp = cert.get_expiration_date()
-                if exp and (exp - now) < warning_time:
-                    print '%s (%s) is about to expire.' % (cert.name, cert.cn)
+        global_config, ca, certs = config.read_config(opts.config)
+        try:
+            cmd, args = args[0], args[1:]
+            if cmd not in cmd_table:
+                parser.error('unknown command "%s"' % cmd)
+            cmdfn = cmd_table[cmd]
+            return cmdfn(global_config, ca, certs, args)
+        finally:
+            ca.close()
+    except Exception as e:
+        if opts.debug:
+            logging.exception(e)
         else:
-            parser.error('unknown command "%s"' % cmd)
-    finally:
-        ca.close()
+            logging.error(e)
+        return 1
 
 
 def main_wrapper():
     try:
-        main()
-        return 0
-    except Exception, e:
+        return main()
+    except Exception:
         logging.exception('uncaught exception')
         return 1