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
130
|
#!/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())
|