From 896e4712c6fc40f7efd09764d8a1ab04291a819a Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Wed, 20 May 2015 17:12:23 +0200 Subject: Add consistency proof and signature checking --- tools/check-sth.py | 98 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 38 deletions(-) diff --git a/tools/check-sth.py b/tools/check-sth.py index 507c447..f94e894 100755 --- a/tools/check-sth.py +++ b/tools/check-sth.py @@ -11,7 +11,8 @@ import errno import shutil import base64 from datetime import datetime, timedelta, tzinfo -from certtools import get_sth +import time +from certtools import get_sth, create_ssl_context, check_sth_signature, get_public_key_from_file, get_consistency_proof, verify_consistency_proof NAGIOS_OK = 0 NAGIOS_WARN = 1 @@ -19,16 +20,15 @@ NAGIOS_CRIT = 2 NAGIOS_UNKNOWN = 3 DEFAULT_CUR_FILE = 'cur-sth.json' -DEFAULT_PREV_FILE = 'prev-sth.json' parser = argparse.ArgumentParser(description="") parser.add_argument('--cur-sth', default=DEFAULT_CUR_FILE, help="File containing current STH (default=%s)" % DEFAULT_CUR_FILE) -parser.add_argument('--prev-sth', - default=DEFAULT_PREV_FILE, - help="File containing previous STH (default=%s" % DEFAULT_PREV_FILE) parser.add_argument('baseurl', help="Base URL for CT server") +parser.add_argument('--publickey', default=None, metavar="file", help='Public key for the CT log') +parser.add_argument('--cafile', default=None, metavar="file", help='File containing the CA cert') +parser.add_argument('--allow-lag', action='store_true', help='Allow node to lag behind previous STH') def print_sth(sth): if sth is None: @@ -50,15 +50,19 @@ def get_new_sth(baseurl): def read_sth(fn): try: f = open(fn) - except IOError, errno.ENOENT: - return None + except IOError, e: + if e.errno == errno.ENOENT: + return None + raise e return json.loads(f.read()) def mv_file(fromfn, tofn): shutil.move(fromfn, tofn) def write_file(fn, sth): - open(fn, 'w').write(json.dumps(sth)) + tempname = fn + ".new" + open(tempname, 'w').write(json.dumps(sth)) + mv_file(tempname, fn) class UTC(tzinfo): def utcoffset(self, dt): @@ -67,46 +71,64 @@ class UTC(tzinfo): return timedelta(0) def check_age(sth): - now = datetime.now(UTC()) - sth_time = datetime.fromtimestamp(sth['timestamp'] / 1000, UTC()) - roothash = base64.b16encode(base64.decodestring(sth['sha256_root_hash'])) - if now > sth_time + timedelta(0, 6 * 3600): + age = time.time() - sth["timestamp"]/1000 + sth_time = datetime.fromtimestamp(sth['timestamp'] / 1000, UTC()).strftime("%Y-%m-%d %H:%M:%S") + roothash = b64_to_b16(sth['sha256_root_hash']) + if age > 6 * 3600: print "CRITICAL: %s is older than 6h: %s UTC" % (roothash, sth_time) sys.exit(NAGIOS_CRIT) - if now > sth_time + timedelta(0, 2 * 3600): + if age > 2 * 3600: print "WARNING: %s is older than 2h: %s UTC" % (roothash, sth_time) sys.exit(NAGIOS_WARN) + return "%s UTC, %d minutes ago" % (sth_time, age/60) -def check_treesize(cur, prev): - if prev is not None: - if cur['tree_size'] < prev['tree_size']: - print "CRITICAL: new tree smaller than previous tree (%d < %d)" % \ - (cur['tree_size'], prev['tree_size']) - sys.exit(NAGIOS_CRIT) +def check_consistency(newsth, oldsth, baseurl): + consistency_proof = [base64.decodestring(entry) for entry in get_consistency_proof(baseurl, oldsth["tree_size"], newsth["tree_size"])] + (old_treehead, new_treehead) = verify_consistency_proof(consistency_proof, oldsth["tree_size"], newsth["tree_size"], base64.b64decode(oldsth["sha256_root_hash"])) + assert old_treehead == base64.b64decode(oldsth["sha256_root_hash"]) + assert new_treehead == base64.b64decode(newsth["sha256_root_hash"]) + +def b64_to_b16(s): + return base64.b16encode(base64.decodestring(s)) def main(args): if args.cur_sth is None: args.cur_sth = "cur-sth.json" - if args.prev_sth is None: - args.prev_sth = "prev-sth.json" - - new = get_new_sth(args.baseurl) - cur = read_sth(args.cur_sth) - if cur is None or new['sha256_root_hash'] != cur['sha256_root_hash']: - if cur is not None: - mv_file(args.cur_sth, args.prev_sth) - write_file(args.cur_sth, new) - cur = new - prev = read_sth(args.prev_sth) - - #print_sth(cur) - #print_sth(prev) - - check_age(cur) - check_treesize(cur, prev) - # TODO: verify signature - # TODO: get and verify consistency proof + create_ssl_context(cafile=args.cafile) + + logpublickey = get_public_key_from_file(args.publickey) if args.publickey else None + + newsth = get_new_sth(args.baseurl) + check_sth_signature(args.baseurl, newsth, publickey=logpublickey) + + oldsth = read_sth(args.cur_sth) + + #print_sth(newsth) + #print_sth(oldsth) + + if oldsth: + if newsth["tree_size"] == oldsth["tree_size"]: + if oldsth["sha256_root_hash"] != newsth["sha256_root_hash"]: + print "CRITICAL: root hash is different even though tree size is the same.", + print "tree size:", newsth["tree_size"], + print "old hash:", b64_to_b16(oldsth["sha256_root_hash"]) + print "new hash:", b64_to_b16(newsth["sha256_root_hash"]) + sys.exit(NAGIOS_CRIT) + elif newsth["tree_size"] < oldsth["tree_size"]: + if not args.allow_lag: + print "CRITICAL: new tree smaller than previous tree (%d < %d)" % \ + (newsth["tree_size"], oldsth["tree_size"]) + sys.exit(NAGIOS_CRIT) + + if oldsth and oldsth["tree_size"] > 0 and oldsth["tree_size"] != newsth["tree_size"]: + check_consistency(newsth, oldsth, args.baseurl) + + age = check_age(newsth) + + write_file(args.cur_sth, newsth) + + print "OK: size: %d hash: %s, %s" % (newsth["tree_size"], b64_to_b16(newsth["sha256_root_hash"])[:8], age) sys.exit(NAGIOS_OK) if __name__ == '__main__': -- cgit v1.1