From 3629f925b06d9d789e6942876aaf2c0ef875353f Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Wed, 16 Jun 2021 10:17:42 +0200 Subject: validate tree heads harder Disallow time travel, tree shrinkage, new tree hash for same sized tree, new tree size with same tree hash. Consider an STH seen iff all three attributes -- timestamp, size, hash -- are identical. --- siglog-witness.py | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/siglog-witness.py b/siglog-witness.py index 2c9862e..c1152ef 100755 --- a/siglog-witness.py +++ b/siglog-witness.py @@ -416,22 +416,52 @@ def main(args): if not cur_tree_head.signature_valid(log_verification_key): return ERR_TREEHEAD_SIGNATURE_INVALID, "ERROR: signature of current tree head invalid" + # TODO: move to TreeHead.validate_history() ts_sec = new_tree_head.timestamp() ts_asc = time.ctime(ts_sec) if ts_sec < now - 12 * 3600: return (ERR_OK, - "WARNING: timestamp too old: {} ({})".format(ts_sec, ts_asc)) + "WARNING: Tree head timestamp too old: {} ({})".format(ts_sec, ts_asc)) if ts_sec > now + 12 * 3600: return (ERR_OK, - "WARNING: timestamp too new: {} ({})".format(ts_sec, ts_asc)) + "WARNING: Tree head timestamp too new: {} ({})".format(ts_sec, ts_asc)) - # TODO: Needs more thought: size, hash, timestamp -- what may change and what may not? - if new_tree_head.tree_size() <= cur_tree_head.tree_size(): - return 0, "INFO: Fetched head of tree of size {} already seen".format(cur_tree_head.tree_size()) + if new_tree_head.tree_size() < cur_tree_head.tree_size(): + return (ERR_TREEHEAD_INVALID, + "ERROR: Log is shrinking: {} < {} ".format(new_tree_head.tree_size(), + cur_tree_head.tree_size())) + + if new_tree_head.timestamp() < cur_tree_head.timestamp(): + return (ERR_TREEHEAD_INVALID, + "ERROR: Log is time traveling: {} < {} ".format(time.ctime(new_tree_head.timestamp()), + time.ctime(cur_tree_head.timestamp()))) + + if new_tree_head.timestamp() == cur_tree_head.timestamp() and \ + new_tree_head.root_hash() == cur_tree_head.root_hash() and \ + new_tree_head.tree_size() == cur_tree_head.tree_size(): + return (ERR_OK, + "INFO: Fetched head of tree of size {} already seen".format(cur_tree_head.tree_size())) + + if new_tree_head.root_hash() == cur_tree_head.root_hash() and \ + new_tree_head.tree_size() != cur_tree_head.tree_size(): + return (ERR_TREEHEAD_INVALID, + "ERROR: Tree size has changed but hash has not: " + "{}: {} != {}".format(new_tree_head.root_hash(), + new_tree_head.tree_size(), + cur_tree_head.tree_size())) + + if new_tree_head.root_hash() != cur_tree_head.root_hash() and \ + new_tree_head.tree_size() == cur_tree_head.tree_size(): + return (ERR_TREEHEAD_INVALID, + "ERROR: Hash has changed but tree size has not: " + "{}: {} != {}".format(new_tree_head.tree_size(), + new_tree_head.root_hash(), + cur_tree_head.root_hash())) + + # Same hash and size, new timestamp is ok. proof, err = fetch_consistency_proof(cur_tree_head.tree_size(), new_tree_head.tree_size()) if err: return err - if not consistency_proof_valid(cur_tree_head, new_tree_head, proof): errmsg = "ERROR: failing consistency proof check for {}->{}\n".format(cur_tree_head.tree_size(), new_tree_head.tree_size()) @@ -441,6 +471,7 @@ def main(args): new_tree_head.root_hash(), proof.path()) return ERR_CONSISTENCYPROOF_INVALID, errmsg + # TODO: end move err = sign_send_store_tree_head(signing_key, new_tree_head) if err: return err -- cgit v1.2.3