1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2015, NORDUnet A/S.
# See LICENSE for licensing information.
import sys
import json
import urllib2
import time
from base64 import b64encode
from mergetools import parse_args, get_nfetched, hexencode, hexdecode, \
get_logorder, get_sth
from certtools import create_ssl_context, get_public_key_from_file, \
timing_point, create_sth_signature, write_file, check_sth_signature, \
build_merkle_tree
def merge_sth(args, config, localconfig):
paths = localconfig["paths"]
own_key = (localconfig["nodename"],
"%s/%s-private.pem" % (paths["privatekeys"],
localconfig["nodename"]))
ctbaseurl = config["baseurl"]
signingnodes = config["signingnodes"]
mergenodes = config.get("mergenodes", [])
mergedb = paths["mergedb"]
sthfile = mergedb + "/sth"
logorderfile = mergedb + "/logorder"
currentsizefile = mergedb + "/fetched"
logpublickey = get_public_key_from_file(paths["logpublickey"])
backupquorum = config.get("backup-quorum-size", 0)
assert backupquorum <= len(mergenodes) - 1
create_ssl_context(cafile=paths["https_cacertfile"])
timing = timing_point()
trees = [{'tree_size': get_nfetched(currentsizefile, logorderfile),
'sha256_root_hash': ''}]
for mergenode in mergenodes:
if mergenode["name"] == config["primarymergenode"]:
continue
verifiedfile = mergedb + "/verified." + mergenode["name"]
try:
tree = json.loads(open(verifiedfile, "r").read())
except (IOError, ValueError):
tree = {'tree_size': 0, "sha256_root_hash": ''}
trees.append(tree)
trees.sort(key=lambda e: e['tree_size'], reverse=True)
#print >>sys.stderr, "DEBUG: trees:", trees
if backupquorum > len(trees) - 1:
print >>sys.stderr, "backup quorum > number of secondaries:", \
backupquorum, ">", len(trees) - 1
return
tree_size = trees[backupquorum]['tree_size']
root_hash = hexdecode(trees[backupquorum]['sha256_root_hash'])
#print >>sys.stderr, "DEBUG: tree size candidate at backupquorum", backupquorum, ":", tree_size
cur_sth = get_sth(sthfile)
if tree_size < cur_sth['tree_size']:
print >>sys.stderr, "candidate tree < current tree:", \
tree_size, "<", cur_sth['tree_size']
return
assert tree_size >= 0 # Don't read logorder without limit.
logorder = get_logorder(logorderfile, tree_size)
timing_point(timing, "get logorder")
if tree_size == -1:
tree_size = len(logorder)
print >>sys.stderr, "new tree size will be", tree_size
root_hash_calc = build_merkle_tree(logorder)[-1][0]
assert root_hash == '' or root_hash == root_hash_calc
root_hash = root_hash_calc
timestamp = int(time.time() * 1000)
tree_head_signature = None
for signingnode in signingnodes:
try:
tree_head_signature = \
create_sth_signature(tree_size, timestamp,
root_hash,
"https://%s/" % signingnode["address"],
key=own_key)
break
except urllib2.URLError, err:
print >>sys.stderr, err
sys.stderr.flush()
if tree_head_signature == None:
print >>sys.stderr, "Could not contact any signing nodes"
sys.exit(1)
sth = {"tree_size": tree_size, "timestamp": timestamp,
"sha256_root_hash": b64encode(root_hash),
"tree_head_signature": b64encode(tree_head_signature)}
check_sth_signature(ctbaseurl, sth, publickey=logpublickey)
timing_point(timing, "build sth")
print hexencode(root_hash), timestamp, tree_size
sys.stdout.flush()
write_file(sthfile, sth)
if args.timing:
print >>sys.stderr, "timing: merge_sth:", timing["deltatimes"]
sys.stderr.flush()
def main():
"""
Read file 'sth' to get current tree size, assuming zero if file not
found.
Read tree sizes from the backup.<secondary> files, put them in a
list and sort it. Let new tree size equal list[backup-quorum]. Barf
on a new tree size smaller than the currently published tree size.
Decide on a timestamp, build an STH and write it to file 'sth'.
"""
args, config, localconfig = parse_args()
while True:
merge_sth(args, config, localconfig)
if args.interval is None:
break
print >>sys.stderr, "sleeping", args.interval, "seconds"
time.sleep(args.interval)
if __name__ == '__main__':
sys.exit(main())
|