From 6b62ebbf1de5b9e55b04e9cfafd0620f1374c2d4 Mon Sep 17 00:00:00 2001 From: Magnus Ahltorp Date: Tue, 31 Mar 2015 14:27:23 +0200 Subject: Cleanup tests and use urllib2.build_opener Remove unused files Generate test config files directly in release directory Move test database files to "tests" directory Generate log key when preparing tests Report error when STH not found in v1.erl Make merge, fetchallcerts, submitcert, verifysct, and testcase1 take log key as argument --- tools/certkeys.py | 8 -------- tools/certtools.py | 50 ++++++++++++++++++++++++++++++++------------------ tools/compileconfig.py | 2 +- tools/create-key.sh | 4 ++++ tools/fetchallcerts.py | 5 ++++- tools/merge.py | 16 ++++++++++++---- tools/submitcert.py | 5 ++++- tools/testcase1.py | 12 ++++++++---- tools/verifysct.py | 5 ++++- 9 files changed, 69 insertions(+), 38 deletions(-) create mode 100755 tools/create-key.sh (limited to 'tools') diff --git a/tools/certkeys.py b/tools/certkeys.py index 52d61be..43646ef 100644 --- a/tools/certkeys.py +++ b/tools/certkeys.py @@ -4,14 +4,6 @@ publickeys = { "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfahLEimAoz2t01p3uMziiLOl/fHTD" "M0YDOhBRuiBARsV4UvxG2LdNgoIGLrtCzWE0J5APC2em4JlvR8EEEFMoA==", - "https://127.0.0.1:8080/": - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4qWq6afhBUi0OdcWUYhyJLNXTkGqQ9" - "PMS5lqoCgkV2h1ZvpNjBH2u8UbgcOQwqDo66z6BWQJGolozZYmNHE2kQ==", - - "https://localhost:8080/": - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4qWq6afhBUi0OdcWUYhyJLNXTkGqQ9" - "PMS5lqoCgkV2h1ZvpNjBH2u8UbgcOQwqDo66z6BWQJGolozZYmNHE2kQ==", - "https://flimsy.ct.nordu.net/": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4qWq6afhBUi0OdcWUYhyJLNXTkGqQ9" "PMS5lqoCgkV2h1ZvpNjBH2u8UbgcOQwqDo66z6BWQJGolozZYmNHE2kQ==", diff --git a/tools/certtools.py b/tools/certtools.py index 2c97dfb..da5021a 100644 --- a/tools/certtools.py +++ b/tools/certtools.py @@ -88,8 +88,15 @@ def get_root_cert(issuer): return root_cert +def urlopen(url, data=None): + try: + opener = urllib2.build_opener(urllib2.HTTPSHandler(context=None)) + except TypeError: + opener = urllib2.build_opener(urllib2.HTTPSHandler()) + return opener.open(url, data) + def get_sth(baseurl): - result = urllib2.urlopen(baseurl + "ct/v1/get-sth").read() + result = urlopen(baseurl + "ct/v1/get-sth").read() return json.loads(result) def get_proof_by_hash(baseurl, hash, tree_size): @@ -97,7 +104,7 @@ def get_proof_by_hash(baseurl, hash, tree_size): params = urllib.urlencode({"hash":base64.b64encode(hash), "tree_size":tree_size}) result = \ - urllib2.urlopen(baseurl + "ct/v1/get-proof-by-hash?" + params).read() + urlopen(baseurl + "ct/v1/get-proof-by-hash?" + params).read() return json.loads(result) except urllib2.HTTPError, e: print "ERROR:", e.read() @@ -108,7 +115,7 @@ def get_consistency_proof(baseurl, tree_size1, tree_size2): params = urllib.urlencode({"first":tree_size1, "second":tree_size2}) result = \ - urllib2.urlopen(baseurl + "ct/v1/get-sth-consistency?" + params).read() + urlopen(baseurl + "ct/v1/get-sth-consistency?" + params).read() return json.loads(result)["consistency"] except urllib2.HTTPError, e: print "ERROR:", e.read() @@ -131,7 +138,7 @@ def unpack_tls_array(packed_data, length_len): def add_chain(baseurl, submission): try: - result = urllib2.urlopen(baseurl + "ct/v1/add-chain", json.dumps(submission)).read() + result = urlopen(baseurl + "ct/v1/add-chain", json.dumps(submission)).read() return json.loads(result) except urllib2.HTTPError, e: print "ERROR", e.code,":", e.read() @@ -148,7 +155,7 @@ def add_chain(baseurl, submission): def add_prechain(baseurl, submission): try: - result = urllib2.urlopen(baseurl + "ct/v1/add-pre-chain", + result = urlopen(baseurl + "ct/v1/add-pre-chain", json.dumps(submission)).read() return json.loads(result) except urllib2.HTTPError, e: @@ -167,7 +174,7 @@ def add_prechain(baseurl, submission): def get_entries(baseurl, start, end): try: params = urllib.urlencode({"start":start, "end":end}) - result = urllib2.urlopen(baseurl + "ct/v1/get-entries?" + params).read() + result = urlopen(baseurl + "ct/v1/get-entries?" + params).read() return json.loads(result) except urllib2.HTTPError, e: print "ERROR:", e.read() @@ -198,8 +205,9 @@ def encode_signature(hash_alg, signature_alg, unpacked_signature): signature += tls_array(unpacked_signature, 2) return signature -def check_signature(baseurl, signature, data): - publickey = base64.decodestring(publickeys[baseurl]) +def check_signature(baseurl, signature, data, publickey=None): + if publickey == None: + publickey = base64.decodestring(publickeys[baseurl]) (hash_alg, signature_alg, unpacked_signature) = decode_signature(signature) assert hash_alg == 4, \ "hash_alg is %d, expected 4" % (hash_alg,) # sha256 @@ -230,20 +238,25 @@ def check_auth_header(authheader, expected_key, publickeydir, data, path): return True def http_request(url, data=None, key=None, verifynode=None, publickeydir="."): - req = urllib2.Request(url, data) + try: + opener = urllib2.build_opener(urllib2.HTTPSHandler(context=None)) + except TypeError: + opener = urllib2.build_opener(urllib2.HTTPSHandler()) + (keyname, keyfile) = key privatekey = get_eckey_from_file(keyfile) sk = ecdsa.SigningKey.from_der(privatekey) parsed_url = urlparse.urlparse(url) if data == None: - data = parsed_url.query + data_to_sign = parsed_url.query method = "GET" else: + data_to_sign = data method = "POST" - signature = sk.sign("%s\0%s\0%s" % (method, parsed_url.path, data), hashfunc=hashlib.sha256, + signature = sk.sign("%s\0%s\0%s" % (method, parsed_url.path, data_to_sign), hashfunc=hashlib.sha256, sigencode=ecdsa.util.sigencode_der) - req.add_header('X-Catlfish-Auth', base64.b64encode(signature) + ";key=" + keyname) - result = urllib2.urlopen(req) + opener.addheaders = [('X-Catlfish-Auth', base64.b64encode(signature) + ";key=" + keyname)] + result = opener.open(url, data) authheader = result.info().get('X-Catlfish-Auth') data = result.read() check_auth_header(authheader, verifynode, publickeydir, data, parsed_url.path) @@ -263,7 +276,7 @@ def create_signature(baseurl, data, key=None): unpacked_signature = get_signature(baseurl, data, key) return encode_signature(4, 3, unpacked_signature) -def check_sth_signature(baseurl, sth): +def check_sth_signature(baseurl, sth, publickey=None): signature = base64.decodestring(sth["tree_head_signature"]) version = struct.pack(">b", 0) @@ -273,7 +286,7 @@ def check_sth_signature(baseurl, sth): hash = base64.decodestring(sth["sha256_root_hash"]) tree_head = version + signature_type + timestamp + tree_size + hash - check_signature(baseurl, signature, tree_head) + check_signature(baseurl, signature, tree_head, publickey=publickey) def create_sth_signature(tree_size, timestamp, root_hash, baseurl, key=None): version = struct.pack(">b", 0) @@ -284,8 +297,9 @@ def create_sth_signature(tree_size, timestamp, root_hash, baseurl, key=None): return create_signature(baseurl, tree_head, key=key) -def check_sct_signature(baseurl, signed_entry, sct, precert=False): - publickey = base64.decodestring(publickeys[baseurl]) +def check_sct_signature(baseurl, signed_entry, sct, precert=False, publickey=None): + if publickey == None: + publickey = base64.decodestring(publickeys[baseurl]) calculated_logid = hashlib.sha256(publickey).digest() received_logid = base64.decodestring(sct["id"]) assert calculated_logid == received_logid, \ @@ -306,7 +320,7 @@ def check_sct_signature(baseurl, signed_entry, sct, precert=False): entry_type + signed_entry + \ tls_array(base64.decodestring(sct["extensions"]), 2) - check_signature(baseurl, signature, signed_struct) + check_signature(baseurl, signature, signed_struct, publickey=publickey) def pack_mtl(timestamp, leafcert): entry_type = struct.pack(">H", 0) diff --git a/tools/compileconfig.py b/tools/compileconfig.py index 4996994..c239bd0 100755 --- a/tools/compileconfig.py +++ b/tools/compileconfig.py @@ -158,7 +158,7 @@ def gen_config(nodename, config, localconfig): bind_publichttpaddress = localconfig.get("publichttpaddresses", {}).get(nodename) options = localconfig.get("options", []) - configfile = open(paths["configdir"] + nodename + ".config", "w") + configfile = open(paths["configdir"] + "/" + nodename + ".config", "w") print >>configfile, "%% catlfish configuration file (-*- erlang -*-)" (nodetype, nodeconfig) = get_node_config(nodename, config) diff --git a/tools/create-key.sh b/tools/create-key.sh new file mode 100755 index 0000000..9d29c86 --- /dev/null +++ b/tools/create-key.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +openssl ecparam -name prime256v1 -genkey -noout -out $1-private.pem +openssl ec -in $1-private.pem -pubout -out $1.pem diff --git a/tools/fetchallcerts.py b/tools/fetchallcerts.py index e0ea92f..395fe69 100755 --- a/tools/fetchallcerts.py +++ b/tools/fetchallcerts.py @@ -22,6 +22,7 @@ parser = argparse.ArgumentParser(description='') parser.add_argument('baseurl', help="Base URL for CT server") parser.add_argument('--store', default=None, metavar="dir", help='Store certificates in directory dir') parser.add_argument('--write-sth', action='store_true', help='Write STH') +parser.add_argument('--publickey', default=None, metavar="file", help='Public key for the CT log') args = parser.parse_args() def get_entries_wrapper(baseurl, start, end): @@ -39,8 +40,10 @@ def print_layer(layer): for entry in layer: print base64.b16encode(entry) +logpublickey = get_public_key_from_file(args.publickey) if args.publickey else None + sth = get_sth(args.baseurl) -check_sth_signature(args.baseurl, sth) +check_sth_signature(args.baseurl, sth, publickey=logpublickey) tree_size = sth["tree_size"] root_hash = base64.decodestring(sth["sha256_root_hash"]) diff --git a/tools/merge.py b/tools/merge.py index e6fae24..f9c93d9 100755 --- a/tools/merge.py +++ b/tools/merge.py @@ -16,7 +16,9 @@ import hashlib import urlparse import os import yaml -from certtools import build_merkle_tree, create_sth_signature, check_sth_signature, get_eckey_from_file, timing_point, http_request +from certtools import build_merkle_tree, create_sth_signature, \ + check_sth_signature, get_eckey_from_file, timing_point, http_request, \ + get_public_key_from_file parser = argparse.ArgumentParser(description="") parser.add_argument('--config', help="System configuration", required=True) @@ -41,6 +43,8 @@ logorderfile = mergedb + "/logorder" own_key = (localconfig["nodename"], "%s/%s-private.pem" % (paths["privatekeys"], localconfig["nodename"])) +logpublickey = get_public_key_from_file(paths["logpublickey"]) + hashed_dir = True def parselogrow(row): @@ -238,19 +242,23 @@ tree_size = len(logorder) root_hash = tree[-1][0] 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: - pass + except urllib2.URLError, e: + print e +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": base64.b64encode(root_hash), "tree_head_signature": base64.b64encode(tree_head_signature)} -check_sth_signature(ctbaseurl, sth) +check_sth_signature(ctbaseurl, sth, publickey=logpublickey) timing_point(timing, "build sth") diff --git a/tools/submitcert.py b/tools/submitcert.py index 2e8cc33..ba4b337 100755 --- a/tools/submitcert.py +++ b/tools/submitcert.py @@ -30,6 +30,7 @@ parser.add_argument('--sct-file', default=None, metavar="file", help='Store SCT: parser.add_argument('--parallel', type=int, default=16, metavar="n", help="Number of parallel submits") parser.add_argument('--check-sct', action='store_true', help="Check SCT signature") parser.add_argument('--pre-warm', action='store_true', help="Wait 3 seconds after first submit") +parser.add_argument('--publickey', default=None, metavar="file", help='Public key for the CT log') args = parser.parse_args() from multiprocessing import Pool @@ -37,6 +38,8 @@ from multiprocessing import Pool baseurl = args.baseurl certfilepath = args.store +logpublickey = get_public_key_from_file(args.publickey) if args.publickey else None + lookup_in_log = False if certfilepath[-1] == "/": @@ -84,7 +87,7 @@ def submitcert((certfile, cert)): try: if args.check_sct: - check_sct_signature(baseurl, signed_entry, result, precert=precert) + check_sct_signature(baseurl, signed_entry, result, precert=precert, publickey=logpublickey) timing_point(timing, "checksig") except AssertionError, e: print "ERROR:", certfile, e diff --git a/tools/testcase1.py b/tools/testcase1.py index 4502b56..1d46230 100755 --- a/tools/testcase1.py +++ b/tools/testcase1.py @@ -14,7 +14,9 @@ import hashlib import itertools from certtools import * -baseurls = ["https://127.0.0.1:8080/"] +baseurls = [sys.argv[1]] +logpublickeyfile = sys.argv[2] + certfiles = ["../tools/testcerts/cert1.txt", "../tools/testcerts/cert2.txt", "../tools/testcerts/cert3.txt", "../tools/testcerts/cert4.txt", "../tools/testcerts/cert5.txt"] @@ -28,6 +30,8 @@ cc5 = get_certs_from_file(certfiles[4]) failures = 0 indentation = "" +logpublickey = get_public_key_from_file(logpublickeyfile) + def testgroup(name): global indentation print name + ":" @@ -55,7 +59,7 @@ def print_and_check_tree_size(expected, baseurl): global failures sth = get_sth(baseurl) try: - check_sth_signature(baseurl, sth) + check_sth_signature(baseurl, sth, publickey=logpublickey) except AssertionError, e: print_error("%s", e) except ecdsa.keys.BadSignatureError, e: @@ -71,13 +75,13 @@ def do_add_chain(chain, baseurl): print_error("%s", e) try: signed_entry = pack_cert(chain[0]) - check_sct_signature(baseurl, signed_entry, result) + check_sct_signature(baseurl, signed_entry, result, publickey=logpublickey) + print_success("signature check succeeded") except AssertionError, e: print_error("%s", e) except ecdsa.keys.BadSignatureError, e: print e print_error("bad SCT signature") - print_success("signature check succeeded") return result def get_and_validate_proof(timestamp, chain, leaf_index, nentries, baseurl): diff --git a/tools/verifysct.py b/tools/verifysct.py index 27ab4c9..4b8e38a 100755 --- a/tools/verifysct.py +++ b/tools/verifysct.py @@ -22,12 +22,15 @@ parser = argparse.ArgumentParser(description='') parser.add_argument('baseurl', help="Base URL for CT server") parser.add_argument('--sct-file', default=None, metavar="dir", help='SCT:s to verify') parser.add_argument('--parallel', type=int, default=16, metavar="n", help="Number of parallel verifications") +parser.add_argument('--publickey', default=None, metavar="file", help='Public key for the CT log') args = parser.parse_args() from multiprocessing import Pool baseurl = args.baseurl +logpublickey = get_public_key_from_file(args.publickey) if args.publickey else None + sth = get_sth(baseurl) def verifysct(sctentry): @@ -43,7 +46,7 @@ def verifysct(sctentry): signed_entry = pack_precert(leafcert, issuer_key_hash) else: signed_entry = pack_cert(leafcert) - check_sct_signature(baseurl, signed_entry, sctentry["sct"], precert=issuer_key_hash) + check_sct_signature(baseurl, signed_entry, sctentry["sct"], precert=issuer_key_hash, publickey=logpublickey) timing_point(timing, "checksig") except AssertionError, e: print "ERROR:", e -- cgit v1.1