summaryrefslogtreecommitdiff
path: root/tools/check-sth.py
diff options
context:
space:
mode:
authorMagnus Ahltorp <map@kth.se>2015-05-20 17:12:23 +0200
committerMagnus Ahltorp <map@kth.se>2015-05-20 17:12:23 +0200
commit896e4712c6fc40f7efd09764d8a1ab04291a819a (patch)
treedaa86126682b4c4cb4fe665ec4c1bd8821cbf68d /tools/check-sth.py
parente7404b41b7f8e9737f22c15f562cd9d338a8d91e (diff)
Add consistency proof and signature checking
Diffstat (limited to 'tools/check-sth.py')
-rwxr-xr-xtools/check-sth.py98
1 files 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__':