#!/usr/bin/env python # Copyright (c) 2014-2016, NORDUnet A/S. # See LICENSE for licensing information. import argparse import urllib2 import urllib import json import base64 import sys import struct import hashlib import itertools from certtools import * from precerttools import * from datetime import datetime import os import signal import select import zipfile import itertools parser = argparse.ArgumentParser(description='') parser.add_argument('baseurl', help="Base URL for CT server") parser.add_argument('--store', default=None, metavar="dir", help='Get certificates from directory dir') parser.add_argument('--sct-file', default=None, metavar="file", help='Store SCT:s in file') parser.add_argument('--parallel', type=int, default=16, metavar="n", help="Number of parallel submits") parser.add_argument('--maxcerts', type=int, metavar="n", help="Maximum number of certificates to submit") 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') parser.add_argument('--cafile', default=None, metavar="file", help='File containing the CA cert') args = parser.parse_args() create_ssl_context(cafile=args.cafile) 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] == "/": certfiles = [certfilepath + filename for filename in sorted(os.listdir(certfilepath)) if os.path.isfile(certfilepath + filename)] else: certfiles = [certfilepath] sth = get_sth(baseurl) session = None def submitcert((certfile, cert)): timing = timing_point() certchain = get_certs_from_string(cert) precerts = get_precerts_from_string(cert) assert len(precerts) == 0 or len(precerts) == 1 precert = precerts[0] if precerts else None timing_point(timing, "readcerts") try: if precert: if ext_key_usage_precert_signing_cert in get_ext_key_usage(certchain[0]): issuer_key_hash = get_cert_key_hash(certchain[1]) issuer = certchain[1] else: issuer_key_hash = get_cert_key_hash(certchain[0]) issuer = None cleanedcert = cleanprecert(precert, issuer=issuer) signed_entry = pack_precert(cleanedcert, issuer_key_hash) leafcert = cleanedcert result = add_prechain(baseurl, {"chain":map(base64.b64encode, [precert] + certchain)}, session=session) else: signed_entry = pack_cert(certchain[0]) leafcert = certchain[0] issuer_key_hash = None result = add_chain(baseurl, {"chain":map(base64.b64encode, certchain)}, session=session) except SystemExit: print "EXIT:", certfile select.select([], [], [], 1.0) return (None, None) timing_point(timing, "addchain") if result == None: print "ERROR for certfile", certfile return (None, timing["deltatimes"]) try: if args.check_sct: check_sct_signature(baseurl, signed_entry, result, precert=precert, publickey=logpublickey) timing_point(timing, "checksig") except AssertionError, e: print "ERROR:", certfile, e return (None, None) except urllib2.HTTPError, e: print "ERROR:", certfile, e return (None, None) except ecdsa.keys.BadSignatureError, e: print "ERROR: bad signature", certfile return (None, None) if lookup_in_log: merkle_tree_leaf = pack_mtl(result["timestamp"], leafcert) leaf_hash = get_leaf_hash(merkle_tree_leaf) proof = get_proof_by_hash(baseurl, leaf_hash, sth["tree_size"]) leaf_index = proof["leaf_index"] entries = get_entries(baseurl, leaf_index, leaf_index) fetched_entry = entries["entries"][0] print "does the leaf_input of the fetched entry match what we calculated:", \ base64.decodestring(fetched_entry["leaf_input"]) == merkle_tree_leaf extra_data = fetched_entry["extra_data"] certchain = decode_certificate_chain(base64.decodestring(extra_data)) submittedcertchain = certchain[1:] for (submittedcert, fetchedcert, i) in zip(submittedcertchain, certchain, itertools.count(1)): print "cert", i, "in chain is the same:", submittedcert == fetchedcert if len(certchain) == len(submittedcertchain) + 1: last_issuer = get_cert_info(certchain[-1])["issuer"] root_subject = get_cert_info(certchain[-1])["subject"] print "issuer of last cert in submitted chain and " \ "subject of last cert in fetched chain is the same:", \ last_issuer == root_subject elif len(certchain) == len(submittedcertchain): print "cert chains are the same length" else: print "ERROR: fetched cert chain has length", len(certchain), print "and submitted chain has length", len(submittedcertchain) timing_point(timing, "lookup") return ((leafcert, issuer_key_hash, result), timing["deltatimes"]) def get_ncerts(certfiles): n = 0 for certfile in certfiles: if certfile.endswith(".zip"): zf = zipfile.ZipFile(certfile) n += len(zf.namelist()) zf.close() else: n += 1 return n def get_all_certificates(certfiles): for certfile in certfiles: if certfile.endswith(".zip"): zf = zipfile.ZipFile(certfile) for name in zf.namelist(): yield (name, zf.read(name)) zf.close() else: yield (certfile, open(certfile).read()) def save_sct(sct, sth, leafcert, issuer_key_hash): sctlog = open(args.sct_file, "a") sctentry = {"leafcert": base64.b64encode(leafcert), "sct": sct, "sth": sth} if issuer_key_hash: sctentry["issuer_key_hash"] = base64.b64encode(issuer_key_hash) json.dump(sctentry, sctlog) sctlog.write("\n") sctlog.close() def worker_init(): signal.signal(signal.SIGINT, signal.SIG_IGN) global session session = requests.sessions.Session() p = Pool(args.parallel, worker_init) nsubmitted = 0 lastprinted = 0 print "listing certs" ncerts = get_ncerts(certfiles) if args.maxcerts: ncerts = min(ncerts, args.maxcerts) print ncerts, "certs" certs = itertools.islice(get_all_certificates(certfiles), ncerts) errors = 0 (result, timing) = submitcert(certs.next()) if result != None: nsubmitted += 1 (leafcert, issuer_key_hash, sct) = result save_sct(sct, sth, leafcert, issuer_key_hash) else: errors += 1 if args.pre_warm: select.select([], [], [], 3.0) starttime = datetime.now() try: for result, timing in p.imap_unordered(submitcert, certs): if timing == None: print "error" print "submitted", nsubmitted p.terminate() p.join() sys.exit(1) if result != None: nsubmitted += 1 (leafcert, issuer_key_hash, sct) = result save_sct(sct, sth, leafcert, issuer_key_hash) else: errors += 1 deltatime = datetime.now() - starttime deltatime_f = deltatime.seconds + deltatime.microseconds / 1000000.0 rate = nsubmitted / deltatime_f if nsubmitted > lastprinted + ncerts / 10: print nsubmitted, "rate %.1f" % rate lastprinted = nsubmitted #print timing, "rate %.1f" % rate print "submitted", nsubmitted except KeyboardInterrupt: p.terminate() p.join() if errors: print >> sys.stderr, errors, "errors encountered, exiting with error code" sys.exit(1)