def execute(self):
        '''
        Performs the tally in a synchronized way with the other authorities
        '''
        input_data = self.task.get_data()['input_data']
        sender_ssl_cert = self.task.get_data()['sender_ssl_cert']
        election_id = input_data['election_id']
        session_id = input_data['session_id']

        if election_id <= 0:
            raise TaskError(
                dict(reason="invalid election_id, must be positive"))
        if not re.match("^[a-zA-Z0-9_-]+$", session_id):
            raise TaskError(dict(reason="invalid characters in session_id"))

        election = db.session.query(Election)\
            .filter(Election.id == election_id).first()
        if not election:
            raise TaskError(dict(reason="election not found"))

        # check sender is legitimate
        found_director = False
        for auth in election.authorities.all():
            if not certs_differ(auth.ssl_cert, sender_ssl_cert):
                found_director = True
        if not found_director:
            raise TaskError(
                dict(reason="perform tally task sent by an invalid authority"))

        privdata_path = app.config.get('PRIVATE_DATA_PATH', '')
        election_privpath = os.path.join(privdata_path, str(election_id))
        session_privpath = os.path.join(election_privpath, session_id)
        tally_approved_path = os.path.join(election_privpath, 'tally_approved')

        # check that we have approved the tally
        autoaccept = app.config.get('AUTOACCEPT_REQUESTS', False)
        if not autoaccept:
            if not os.path.exists(tally_approved_path):
                raise TaskError(dict(reason="task not accepted"))
            os.unlink(tally_approved_path)

        #call_cmd(["vmn", "-mix", "privInfo.xml", "protInfo.xml",
        #    "ciphertexts_raw", "plaintexts_raw"], cwd=session_privpath,
        #    timeout=5*3600, check_ret=0)

        def output_filter(p, o, output):
            '''
            detect common errors and kill process in that case
            '''
            if 'Exception in thread "main"' in o:
                p.kill(signal.SIGKILL)
                raise TaskError(dict(reason='error executing vfork'))

        v_mix(session_privpath, output_filter)
    def execute(self):
        '''
        Performs the tally in a synchronized way with the other authorities
        '''
        input_data = self.task.get_data()['input_data']
        sender_ssl_cert = self.task.get_data()['sender_ssl_cert']
        election_id = input_data['election_id']
        session_id = input_data['session_id']

        if election_id <= 0:
            raise TaskError(dict(reason="invalid election_id, must be positive"))
        if not re.match("^[a-zA-Z0-9_-]+$", session_id):
            raise TaskError(dict(reason="invalid characters in session_id"))

        election = db.session.query(Election)\
            .filter(Election.id == election_id).first()
        if not election:
            raise TaskError(dict(reason="election not found"))

        # check sender is legitimate
        found_director = False
        for auth in election.authorities.all():
            if not certs_differ(auth.ssl_cert, sender_ssl_cert):
                found_director = True
        if not found_director:
            raise TaskError(dict(
                reason="perform tally task sent by an invalid authority"))

        privdata_path = app.config.get('PRIVATE_DATA_PATH', '')
        election_privpath = os.path.join(privdata_path, str(election_id))
        session_privpath = os.path.join(election_privpath, session_id)
        tally_approved_path = os.path.join(election_privpath, 'tally_approved')

        # check that we have approved the tally
        autoaccept = app.config.get('AUTOACCEPT_REQUESTS', False)
        if not autoaccept:
            if not os.path.exists(tally_approved_path):
                raise TaskError(dict(reason="task not accepted"))
            os.unlink(tally_approved_path)

        #call_cmd(["vmn", "-mix", "privInfo.xml", "protInfo.xml",
        #    "ciphertexts_raw", "plaintexts_raw"], cwd=session_privpath,
        #    timeout=5*3600, check_ret=0)

        def output_filter(p, o, output):
            '''
            detect common errors and kill process in that case
            '''
            if 'Exception in thread "main"' in o:
                p.kill(signal.SIGKILL)
                raise TaskError(dict(reason='error executing vfork'))

        v_mix(session_privpath, output_filter)
def review_tally(task):
    '''
    Generates the local private info for a new election
    '''
    sender_ssl_cert = task.get_data()['sender_ssl_cert']
    data = task.get_data()['input_data']

    # check input data
    requirements = [
        {'name': u'election_id', 'isinstance': int},
        {'name': u'callback_url', 'isinstance': basestring},
        {'name': u'votes_url', 'isinstance': basestring},
        {'name': u'votes_hash', 'isinstance': basestring},
    ]

    for req in requirements:
        if req['name'] not in data or not isinstance(data[req['name']],
                req['isinstance']):
            print req['name'], data.get(req['name'], None), type(data[req['name']])
            raise TaskError(dict(reason="invalid %s parameter" % req['name']))


    if data['election_id'] <= 0:
        raise TaskError(dict(reason="election_id must be a positive int"))

    if not data['votes_hash'].startswith("ni:///sha-256;"):
        raise TaskError(dict(reason="invalid votes_hash, must be sha256"))

    # check election has been created successfully
    election_id = data['election_id']

    election = db.session.query(Election)\
        .filter(Election.id == election_id).first()
    if not election:
        raise TaskError(dict(reason="election not created"))

    # check sender is legitimate
    found_director = False
    for auth in election.authorities.all():
        if not certs_differ(auth.ssl_cert, sender_ssl_cert):
            found_director = True
    if not found_director:
        raise TaskError(dict(reason="review tally sent by an invalid authority"))

    private_data_path = app.config.get('PRIVATE_DATA_PATH', '')
    election_privpath = os.path.join(private_data_path, str(election_id))

    pubdata_path = app.config.get('PUBLIC_DATA_PATH', '')
    election_pubpath = os.path.join(pubdata_path, str(election_id))
    tally_path = os.path.join(election_pubpath, 'tally.tar.gz')

    if os.path.exists(tally_path):
        raise TaskError(dict(reason="election already tallied"))

    pubkeys = []
    for session in election.sessions.all():
        session_privpath = os.path.join(election_privpath, session.id)
        protinfo_path = os.path.join(session_privpath, 'protInfo.xml')
        pubkey_path = os.path.join(session_privpath, 'publicKey_raw')
        if not os.path.exists(protinfo_path) or not os.path.exists(pubkey_path):
            raise TaskError(dict(reason="election not created"))

        # once we have checked that we have permissions to start doing the tally,
        # we can remove the "temporal" files of any previous tally
        ciphertexts_path = os.path.join(session_privpath, 'ciphertexts_json')
        cipherraw_path = os.path.join(session_privpath, 'ciphertexts_raw')
        if os.path.exists(ciphertexts_path):
            os.unlink(ciphertexts_path)
        if os.path.exists(cipherraw_path):
            os.unlink(cipherraw_path)

        pubkey_json_path = os.path.join(session_privpath, 'publicKey_json')
        with open(pubkey_json_path, 'r') as pubkey_file:
            pubkeys.append(json.loads(pubkey_file.read()))

        # reset securely
        #subprocess.check_call(["vmn", "-reset", "privInfo.xml", "protInfo.xml",
        #    "-f"], cwd=session_privpath)
        v_reset(session_privpath)

    # if there were previous tallies, remove the tally approved flag file
    approve_path = os.path.join(private_data_path, str(election_id), 'tally_approved')
    if os.path.exists(approve_path):
        os.unlink(approve_path)

    # retrieve votes/ciphertexts
    callback_url = data['votes_url']
    r = requests.get(data['votes_url'], verify=False, stream=True)
    if r.status_code != 200:
        raise TaskError(dict(reason="error downloading the votes"))

    # write ciphertexts to disk
    ciphertexts_path = os.path.join(election_privpath, 'ciphertexts_json')
    ciphertexts_file = open(ciphertexts_path, 'w')
    for chunk in r.iter_content(10*1024):
        ciphertexts_file.write(chunk)
    ciphertexts_file.close()

    # check votes hash
    input_hash = data['votes_hash'].replace('ni:///sha-256;', '')
    if not constant_time_compare(input_hash, hash_file(ciphertexts_path)):
        raise TaskError(dict(reason="invalid votes_hash"))

    # transform input votes into something readable by vfork. Basically
    # we read each line of the votes file, which corresponds with a ballot,
    # and split each choice to each session
    # So basically each input line looks like:
    # {"choices": [vote_for_session1, vote_for_session2, [...]], "proofs": []}
    #
    # And we generate N ciphertexts_json files, each of which, for each of
    # those lines input lines, will contain a line with vote_for_session<i>.
    # NOTE: This is the inverse of what the demociphs.py script does
    invotes_file = None
    outvotes_files = []

    # pubkeys needed to verify votes. we also save it to a file
    pubkeys_path = os.path.join(election_privpath, 'pubkeys_json')
    pubkeys_s = json.dumps(pubkeys,
        ensure_ascii=False, sort_keys=True, indent=4, separators=(',', ': '))
    with open(pubkeys_path, mode='w') as pubkeys_f:
        pubkeys_f.write(pubkeys_s)

    num_questions = len(election.sessions.all())
    invalid_votes = 0
    for qnum in range(num_questions):
        pubkeys[qnum]['g'] = int(pubkeys[qnum]['g'])
        pubkeys[qnum]['p'] = int(pubkeys[qnum]['p'])

    try:
        invotes_file = open(ciphertexts_path, 'r')
        for session in election.sessions.all():
            outvotes_path = os.path.join(election_privpath, session.id,
                'ciphertexts_json')
            outvotes_files.append(open(outvotes_path, 'w'))
        print("\n------ Reading and verifying POK of plaintext for the votes..\n")
        lnum = 0
        for line in invotes_file:
            lnum += 1
            line_data = json.loads(line)
            assert len(line_data['choices']) == len(outvotes_files)

            i = 0
            for choice in line_data['choices']:
                # NOTE: we use specific separators with no spaces, because
                # otherwise vfork won't read it well
                outvotes_files[i].write(json.dumps(choice,
                    ensure_ascii=False, sort_keys=True, separators=(',', ':')))
                outvotes_files[i].write("\n")
                i += 1
    finally:
        print("\n------ Verified %d votes in total (%d invalid)\n" % (lnum, invalid_votes))

        # save invalid votes
        invalid_votes_path = os.path.join(election_privpath, 'invalid_votes')
        with open(invalid_votes_path, 'w') as f:
          f.write("%d" % invalid_votes)

        if invotes_file is not None:
            invotes_file.close()
        for f in outvotes_files:
            f.close()

    # Convert each ciphertexts_json of each session into ciphertexts_raw
    for session in election.sessions.all():
        session_privpath = os.path.join(election_privpath, session.id)
        #subprocess.check_call(["vmnc", "-ciphs", "-ini", "json",
        #    "ciphertexts_json", "ciphertexts_raw"], cwd=session_privpath)
        v_convert_ctexts_json(session_privpath)

    autoaccept = app.config.get('AUTOACCEPT_REQUESTS', False)
    if not autoaccept:
        def str_date(date):
            if date:
                return date.isoformat()
            else:
                return ""

        # request user to decide
        label = "approve_election_tally"
        info_text = {
          'Title': election.title,
          'Description': election.description,
          'Voting period': "%s - %s" % (str_date(election.start_date), str_date(election.end_date)),
          'Question data': loads(election.questions),
          'Authorities': [auth.to_dict() for auth in election.authorities]
        }
        approve_task = ExternalTask(label=label,
            data=info_text)
        task.add(approve_task)

        check_approval_task = SimpleTask(
            receiver_url=app.config.get('ROOT_URL', ''),
            action="check_tally_approval",
            queue="orchestra_performer",
            data=dict(election_id=election_id))
        task.add(check_approval_task)
def verify_and_publish_tally(task):
    '''
    Once a tally has been performed, verify the result and if it's ok publish it
    '''
    sender_ssl_cert = task.get_data()['sender_ssl_cert']
    input_data = task.get_data()['input_data']
    election_id = input_data['election_id']
    if election_id <= 0:
        raise TaskError(dict(reason="election_id must be positive"))

    election = db.session.query(Election)\
        .filter(Election.id == election_id).first()
    if not election:
        raise TaskError(dict(reason="election not found"))

    # check sender is legitimate
    found_director = False
    for auth in election.authorities.all():
        if not certs_differ(auth.ssl_cert, sender_ssl_cert):
            found_director = True
    if not found_director:
        raise TaskError(dict(
            reason="perform tally task sent by an invalid authority"))

    privdata_path = app.config.get('PRIVATE_DATA_PATH', '')
    election_privpath = os.path.join(privdata_path, str(election_id))

    pubdata_path = app.config.get('PUBLIC_DATA_PATH', '')
    election_pubpath = os.path.join(pubdata_path, str(election_id))
    tally_path = os.path.join(election_pubpath, 'tally.tar.gz')
    tally_hash_path = os.path.join(election_pubpath, 'tally.tar.gz.sha256')

    # check election_pubpath already exists - it should contain pubkey etc
    if not os.path.exists(election_pubpath):
        raise TaskError(dict(reason="election public path doesn't exist"))

    # check no tally exists yet
    if os.path.exists(tally_path):
        raise TaskError(dict(reason="tally already exists, "
                             "election_id = %d" % election_id))

    pubkeys = []
    for session in election.sessions.all():
        session_privpath = os.path.join(election_privpath, session.id)
        plaintexts_raw_path = os.path.join(session_privpath, 'plaintexts_raw')
        plaintexts_json_path = os.path.join(session_privpath, 'plaintexts_json')
        proofs_path = os.path.join(session_privpath, 'dir', 'roProof')
        protinfo_path = os.path.join(session_privpath, 'protInfo.xml')

        pubkey_path = os.path.join(privdata_path, str(election_id), session.id, 'publicKey_json')
        with open(pubkey_path, 'r') as pubkey_file:
            pubkeys.append(json.loads(pubkey_file.read()))
            pubkey_file.close()

        # check that we have a tally
        if not os.path.exists(proofs_path) or not os.path.exists(plaintexts_raw_path):
            raise TaskError(dict(reason="proofs or plaintexts couldn't be verified"))

        # remove any previous plaintexts_json
        if os.path.exists(plaintexts_json_path):
            os.unlink(plaintexts_json_path)

        # transform plaintexts into json format
        #call_cmd(["vmnc", "-plain", "-outi", "json", "plaintexts_raw",
        #          "plaintexts_json"], cwd=session_privpath, check_ret=0,
        #          timeout=3600)
        v_convert_plaintexts_json(session_privpath)

        # verify the proofs. sometimes vfork raises an exception at the end
        # so we dismiss it if the verification is successful. TODO: fix that in
        # vfork
        try:
            # output = subprocess.check_output(["vmnv", protinfo_path, proofs_path, "-v"])
            output = v_verify(protinfo_path, proofs_path)
        except subprocess.CalledProcessError, e:
            output = e.output
        if "Verification completed SUCCESSFULLY after" not in output:
            raise TaskError(dict(reason="invalid tally proofs"))
def review_tally(task):
    '''
    Generates the local private info for a new election
    '''
    sender_ssl_cert = task.get_data()['sender_ssl_cert']
    data = task.get_data()['input_data']

    # check input data
    requirements = [
        {
            'name': u'election_id',
            'isinstance': int
        },
        {
            'name': u'callback_url',
            'isinstance': basestring
        },
        {
            'name': u'votes_url',
            'isinstance': basestring
        },
        {
            'name': u'votes_hash',
            'isinstance': basestring
        },
    ]

    for req in requirements:
        if req['name'] not in data or not isinstance(data[req['name']],
                                                     req['isinstance']):
            print req['name'], data.get(req['name'],
                                        None), type(data[req['name']])
            raise TaskError(dict(reason="invalid %s parameter" % req['name']))

    if data['election_id'] <= 0:
        raise TaskError(dict(reason="election_id must be a positive int"))

    if not data['votes_hash'].startswith("ni:///sha-256;"):
        raise TaskError(dict(reason="invalid votes_hash, must be sha256"))

    # check election has been created successfully
    election_id = data['election_id']

    election = db.session.query(Election)\
        .filter(Election.id == election_id).first()
    if not election:
        raise TaskError(dict(reason="election not created"))

    # check sender is legitimate
    found_director = False
    for auth in election.authorities.all():
        if not certs_differ(auth.ssl_cert, sender_ssl_cert):
            found_director = True
    if not found_director:
        raise TaskError(
            dict(reason="review tally sent by an invalid authority"))

    private_data_path = app.config.get('PRIVATE_DATA_PATH', '')
    election_privpath = os.path.join(private_data_path, str(election_id))

    pubdata_path = app.config.get('PUBLIC_DATA_PATH', '')
    election_pubpath = os.path.join(pubdata_path, str(election_id))
    tally_path = os.path.join(election_pubpath, 'tally.tar.gz')

    if os.path.exists(tally_path):
        raise TaskError(dict(reason="election already tallied"))

    pubkeys = []
    for session in election.sessions.all():
        session_privpath = os.path.join(election_privpath, session.id)
        protinfo_path = os.path.join(session_privpath, 'protInfo.xml')
        pubkey_path = os.path.join(session_privpath, 'publicKey_raw')
        if not os.path.exists(protinfo_path) or not os.path.exists(
                pubkey_path):
            raise TaskError(dict(reason="election not created"))

        # once we have checked that we have permissions to start doing the tally,
        # we can remove the "temporal" files of any previous tally
        ciphertexts_path = os.path.join(session_privpath, 'ciphertexts_json')
        cipherraw_path = os.path.join(session_privpath, 'ciphertexts_raw')
        if os.path.exists(ciphertexts_path):
            os.unlink(ciphertexts_path)
        if os.path.exists(cipherraw_path):
            os.unlink(cipherraw_path)

        pubkey_json_path = os.path.join(session_privpath, 'publicKey_json')
        with open(pubkey_json_path, 'r') as pubkey_file:
            pubkeys.append(json.loads(pubkey_file.read()))

        # reset securely
        #subprocess.check_call(["vmn", "-reset", "privInfo.xml", "protInfo.xml",
        #    "-f"], cwd=session_privpath)
        v_reset(session_privpath)

    # if there were previous tallies, remove the tally approved flag file
    approve_path = os.path.join(private_data_path, str(election_id),
                                'tally_approved')
    if os.path.exists(approve_path):
        os.unlink(approve_path)

    # retrieve votes/ciphertexts
    callback_url = data['votes_url']
    r = requests.get(data['votes_url'], verify=False, stream=True)
    if r.status_code != 200:
        raise TaskError(dict(reason="error downloading the votes"))

    # write ciphertexts to disk
    ciphertexts_path = os.path.join(election_privpath, 'ciphertexts_json')
    ciphertexts_file = open(ciphertexts_path, 'w')
    for chunk in r.iter_content(10 * 1024):
        ciphertexts_file.write(chunk)
    ciphertexts_file.close()

    # check votes hash
    input_hash = data['votes_hash'].replace('ni:///sha-256;', '')
    if not constant_time_compare(input_hash, hash_file(ciphertexts_path)):
        raise TaskError(dict(reason="invalid votes_hash"))

    # transform input votes into something readable by vfork. Basically
    # we read each line of the votes file, which corresponds with a ballot,
    # and split each choice to each session
    # So basically each input line looks like:
    # {"choices": [vote_for_session1, vote_for_session2, [...]], "proofs": []}
    #
    # And we generate N ciphertexts_json files, each of which, for each of
    # those lines input lines, will contain a line with vote_for_session<i>.
    # NOTE: This is the inverse of what the demociphs.py script does
    invotes_file = None
    outvotes_files = []

    # pubkeys needed to verify votes. we also save it to a file
    pubkeys_path = os.path.join(election_privpath, 'pubkeys_json')
    pubkeys_s = json.dumps(pubkeys,
                           ensure_ascii=False,
                           sort_keys=True,
                           indent=4,
                           separators=(',', ': '))
    with open(pubkeys_path, mode='w') as pubkeys_f:
        pubkeys_f.write(pubkeys_s)

    num_questions = len(election.sessions.all())
    invalid_votes = 0
    for qnum in range(num_questions):
        pubkeys[qnum]['g'] = int(pubkeys[qnum]['g'])
        pubkeys[qnum]['p'] = int(pubkeys[qnum]['p'])

    try:
        invotes_file = open(ciphertexts_path, 'r')
        for session in election.sessions.all():
            outvotes_path = os.path.join(election_privpath, session.id,
                                         'ciphertexts_json')
            outvotes_files.append(open(outvotes_path, 'w'))
        print(
            "\n------ Reading and verifying POK of plaintext for the votes..\n"
        )
        lnum = 0
        for line in invotes_file:
            lnum += 1
            line_data = json.loads(line)
            assert len(line_data['choices']) == len(outvotes_files)

            i = 0
            for choice in line_data['choices']:
                # NOTE: we use specific separators with no spaces, because
                # otherwise vfork won't read it well
                outvotes_files[i].write(
                    json.dumps(choice,
                               ensure_ascii=False,
                               sort_keys=True,
                               separators=(',', ':')))
                outvotes_files[i].write("\n")
                i += 1
    finally:
        print("\n------ Verified %d votes in total (%d invalid)\n" %
              (lnum, invalid_votes))

        # save invalid votes
        invalid_votes_path = os.path.join(election_privpath, 'invalid_votes')
        with open(invalid_votes_path, 'w') as f:
            f.write("%d" % invalid_votes)

        if invotes_file is not None:
            invotes_file.close()
        for f in outvotes_files:
            f.close()

    # Convert each ciphertexts_json of each session into ciphertexts_raw
    for session in election.sessions.all():
        session_privpath = os.path.join(election_privpath, session.id)
        #subprocess.check_call(["vmnc", "-ciphs", "-ini", "json",
        #    "ciphertexts_json", "ciphertexts_raw"], cwd=session_privpath)
        v_convert_ctexts_json(session_privpath)

    autoaccept = app.config.get('AUTOACCEPT_REQUESTS', False)
    if not autoaccept:

        def str_date(date):
            if date:
                return date.isoformat()
            else:
                return ""

        # request user to decide
        label = "approve_election_tally"
        info_text = {
            'Title':
            election.title,
            'Description':
            election.description,
            'Voting period':
            "%s - %s" %
            (str_date(election.start_date), str_date(election.end_date)),
            'Question data':
            loads(election.questions),
            'Authorities': [auth.to_dict() for auth in election.authorities]
        }
        approve_task = ExternalTask(label=label, data=info_text)
        task.add(approve_task)

        check_approval_task = SimpleTask(receiver_url=app.config.get(
            'ROOT_URL', ''),
                                         action="check_tally_approval",
                                         queue="orchestra_performer",
                                         data=dict(election_id=election_id))
        task.add(check_approval_task)
def verify_and_publish_tally(task):
    '''
    Once a tally has been performed, verify the result and if it's ok publish it
    '''
    sender_ssl_cert = task.get_data()['sender_ssl_cert']
    input_data = task.get_data()['input_data']
    election_id = input_data['election_id']
    if election_id <= 0:
        raise TaskError(dict(reason="election_id must be positive"))

    election = db.session.query(Election)\
        .filter(Election.id == election_id).first()
    if not election:
        raise TaskError(dict(reason="election not found"))

    # check sender is legitimate
    found_director = False
    for auth in election.authorities.all():
        if not certs_differ(auth.ssl_cert, sender_ssl_cert):
            found_director = True
    if not found_director:
        raise TaskError(
            dict(reason="perform tally task sent by an invalid authority"))

    privdata_path = app.config.get('PRIVATE_DATA_PATH', '')
    election_privpath = os.path.join(privdata_path, str(election_id))

    pubdata_path = app.config.get('PUBLIC_DATA_PATH', '')
    election_pubpath = os.path.join(pubdata_path, str(election_id))
    tally_path = os.path.join(election_pubpath, 'tally.tar.gz')
    tally_hash_path = os.path.join(election_pubpath, 'tally.tar.gz.sha256')

    # check election_pubpath already exists - it should contain pubkey etc
    if not os.path.exists(election_pubpath):
        raise TaskError(dict(reason="election public path doesn't exist"))

    # check no tally exists yet
    if os.path.exists(tally_path):
        raise TaskError(
            dict(reason="tally already exists, "
                 "election_id = %d" % election_id))

    pubkeys = []
    for session in election.sessions.all():
        session_privpath = os.path.join(election_privpath, session.id)
        plaintexts_raw_path = os.path.join(session_privpath, 'plaintexts_raw')
        plaintexts_json_path = os.path.join(session_privpath,
                                            'plaintexts_json')
        proofs_path = os.path.join(session_privpath, 'dir', 'roProof')
        protinfo_path = os.path.join(session_privpath, 'protInfo.xml')

        pubkey_path = os.path.join(privdata_path, str(election_id), session.id,
                                   'publicKey_json')
        with open(pubkey_path, 'r') as pubkey_file:
            pubkeys.append(json.loads(pubkey_file.read()))
            pubkey_file.close()

        # check that we have a tally
        if not os.path.exists(proofs_path) or not os.path.exists(
                plaintexts_raw_path):
            raise TaskError(
                dict(reason="proofs or plaintexts couldn't be verified"))

        # remove any previous plaintexts_json
        if os.path.exists(plaintexts_json_path):
            os.unlink(plaintexts_json_path)

        # transform plaintexts into json format
        #call_cmd(["vmnc", "-plain", "-outi", "json", "plaintexts_raw",
        #          "plaintexts_json"], cwd=session_privpath, check_ret=0,
        #          timeout=3600)
        v_convert_plaintexts_json(session_privpath)

        # verify the proofs. sometimes vfork raises an exception at the end
        # so we dismiss it if the verification is successful. TODO: fix that in
        # vfork
        try:
            # output = subprocess.check_output(["vmnv", protinfo_path, proofs_path, "-v"])
            output = v_verify(protinfo_path, proofs_path)
        except subprocess.CalledProcessError, e:
            output = e.output
        if "Verification completed SUCCESSFULLY after" not in output:
            raise TaskError(dict(reason="invalid tally proofs"))
def generate_private_info(task):
    '''
    Generates the local private info for a new election
    '''
    input_data = task.get_data()['input_data']
    election_id = input_data['id']

    # 1. check this is a new election and check input data
    private_data_path = app.config.get('PRIVATE_DATA_PATH', '')
    election_privpath = os.path.join(private_data_path, str(election_id))

    # check generic input data, similar to the data for public_api
    check_election_data(input_data, False)

    # check the sessions data
    if not isinstance(input_data.get('sessions', None), list) or\
            not len(input_data['sessions']):
        raise TaskError(dict(reason="No sessions provided"))
    for session in input_data['sessions']:
        if not isinstance(session, dict) or 'id' not in session or\
                'stub' not in session or\
                not isinstance(session['stub'], basestring) or\
                not re.match("^[a-zA-Z0-9_-]+$", session['id']):
            raise TaskError(dict(reason="Invalid session data provided"))


    # check that we are indeed one of the listed authorities
    auth_name = None
    for auth_data in input_data['authorities']:
        if auth_data['orchestra_url'] == app.config.get('ROOT_URL', ''):
            auth_name = auth_data['name']
    if not auth_name:
        raise TaskError(dict(reason="trying to process what SEEMS to be an external election"))

    # localProtInfo.xml should not exist for any of the sessions, as our task is
    # precisely to create it. note that we only check that localProtInfo.xml
    # files don't exist, because if we are the director, then the stub and
    # parent directory will already exist
    for session in input_data['sessions']:
        session_privpath = os.path.join(election_privpath, session['id'])
        protinfo_path = os.path.join(session_privpath, 'localProtInfo.xml')
        if os.path.exists(protinfo_path):
            raise TaskError(dict(reason="session_id %s already created" % session['id']))

    # 2. create base local data from received input in case it's needed:
    # create election models, dirs and stubs if we are not the director
    if certs_differ(task.get_data()['sender_ssl_cert'], app.config.get('SSL_CERT_STRING', '')):
        if os.path.exists(election_privpath):
            raise TaskError(dict(reason="Already existing election id %d" % input_data['id']))
        election = Election(
            id = input_data['id'],
            title = input_data['title'],
            description = input_data['description'],
            questions = input_data['questions'],
            start_date = input_data['start_date'],
            end_date = input_data['end_date'],
            num_parties = input_data['num_parties'],
            threshold_parties = input_data['threshold_parties'],
        )
        db.session.add(election)

        for auth_data in input_data['authorities']:
            authority = Authority(
                name = auth_data['name'],
                ssl_cert = auth_data['ssl_cert'],
                orchestra_url = auth_data['orchestra_url'],
                election_id = input_data['id']
            )
            db.session.add(authority)

        # create dirs and stubs, and session model
        i = 0
        for session in input_data['sessions']:
            session_model = Session(
                id=session['id'],
                election_id=election_id,
                status='default',
                public_key='',
                question_number=i
            )
            db.session.add(session_model)

            session_privpath = os.path.join(election_privpath, session['id'])
            mkdir_recursive(session_privpath)
            stub_path = os.path.join(session_privpath, 'stub.xml')
            stub_file = codecs.open(stub_path, 'w', encoding='utf-8')
            stub_content = stub_file.write(session['stub'])
            stub_file.close()
            i += 1
        db.session.commit()
    else:
        # if we are the director, models, dirs and stubs have been created
        # already, so we just get the election from the database
        election = db.session.query(Election)\
            .filter(Election.id == election_id).first()

    # only create external task if we have configured autoaccept to false in
    # settings:
    autoaccept = app.config.get('AUTOACCEPT_REQUESTS', '')
    if not autoaccept:
        def str_date(date):
            if date:
                return date.isoformat()
            else:
                return ""

        label = "approve_election"
        info_text = {
'Title': election.title,
'Description': election.description,
'Voting period': "%s - %s" % (str_date(election.start_date), str_date(election.end_date)),
'Question data': loads(election.questions),
'Authorities': [auth.to_dict() for auth in election.authorities]
	} 
        approve_task = ExternalTask(label=label,
            data=info_text)
        task.add(approve_task)

    vfork_task = SimpleTask(
        receiver_url=app.config.get('ROOT_URL', ''),
        action="generate_private_info_vfork",
        queue="orchestra_performer",
        data=dict())
    task.add(vfork_task)
Esempio n. 8
0
def generate_private_info(task):
    '''
    Generates the local private info for a new election
    '''
    input_data = task.get_data()['input_data']
    election_id = input_data['id']

    # 1. check this is a new election and check input data
    private_data_path = app.config.get('PRIVATE_DATA_PATH', '')
    election_privpath = os.path.join(private_data_path, str(election_id))

    # check generic input data, similar to the data for public_api
    check_election_data(input_data, False)

    # check the sessions data
    if not isinstance(input_data.get('sessions', None), list) or\
            not len(input_data['sessions']):
        raise TaskError(dict(reason="No sessions provided"))
    for session in input_data['sessions']:
        if not isinstance(session, dict) or 'id' not in session or\
                'stub' not in session or\
                not isinstance(session['stub'], str) or\
                not re.match("^[a-zA-Z0-9_-]+$", session['id']):
            raise TaskError(dict(reason="Invalid session data provided"))

    # check that we are indeed one of the listed authorities
    auth_name = None
    for auth_data in input_data['authorities']:
        if auth_data['orchestra_url'] == app.config.get('ROOT_URL', ''):
            auth_name = auth_data['name']
    if not auth_name:
        raise TaskError(
            dict(
                reason="trying to process what SEEMS to be an external election"
            ))

    # localProtInfo.xml should not exist for any of the sessions, as our task is
    # precisely to create it. note that we only check that localProtInfo.xml
    # files don't exist, because if we are the director, then the stub and
    # parent directory will already exist
    for session in input_data['sessions']:
        session_privpath = os.path.join(election_privpath, session['id'])
        protinfo_path = os.path.join(session_privpath, 'localProtInfo.xml')
        if os.path.exists(protinfo_path):
            raise TaskError(
                dict(reason="session_id %s already created" % session['id']))

    # 2. create base local data from received input in case it's needed:
    # create election models, dirs and stubs if we are not the director
    if certs_differ(task.get_data()['sender_ssl_cert'],
                    app.config.get('SSL_CERT_STRING', '')):
        if os.path.exists(election_privpath):
            raise TaskError(
                dict(reason="Already existing election id %d" %
                     input_data['id']))
        election = Election(
            id=input_data['id'],
            title=input_data['title'],
            description=input_data['description'],
            questions=input_data['questions'],
            start_date=input_data['start_date'],
            end_date=input_data['end_date'],
            num_parties=input_data['num_parties'],
            threshold_parties=input_data['threshold_parties'],
        )
        db.session.add(election)

        for auth_data in input_data['authorities']:
            authority = Authority(name=auth_data['name'],
                                  ssl_cert=auth_data['ssl_cert'],
                                  orchestra_url=auth_data['orchestra_url'],
                                  election_id=input_data['id'])
            db.session.add(authority)

        # create dirs and stubs, and session model
        i = 0
        for session in input_data['sessions']:
            session_model = Session(id=session['id'],
                                    election_id=election_id,
                                    status='default',
                                    public_key='',
                                    question_number=i)
            db.session.add(session_model)

            session_privpath = os.path.join(election_privpath, session['id'])
            mkdir_recursive(session_privpath)
            stub_path = os.path.join(session_privpath, 'stub.xml')
            stub_file = codecs.open(stub_path, 'w', encoding='utf-8')
            stub_content = stub_file.write(session['stub'])
            stub_file.close()
            i += 1
        db.session.commit()
    else:
        # if we are the director, models, dirs and stubs have been created
        # already, so we just get the election from the database
        election = db.session.query(Election)\
            .filter(Election.id == election_id).first()

    # only create external task if we have configured autoaccept to false in
    # settings:
    autoaccept = app.config.get('AUTOACCEPT_REQUESTS', '')
    if not autoaccept:

        def str_date(date):
            if date:
                return date.isoformat()
            else:
                return ""

        label = "approve_election"
        info_text = {
            'Title':
            election.title,
            'Description':
            election.description,
            'Voting period':
            "%s - %s" %
            (str_date(election.start_date), str_date(election.end_date)),
            'Question data':
            loads(election.questions),
            'Authorities': [auth.to_dict() for auth in election.authorities]
        }
        approve_task = ExternalTask(label=label, data=info_text)
        task.add(approve_task)

    vfork_task = SimpleTask(receiver_url=app.config.get('ROOT_URL', ''),
                            action="generate_private_info_vfork",
                            queue="orchestra_performer",
                            data=dict())
    task.add(vfork_task)
Esempio n. 9
0
def verify_and_publish_tally(task):
    '''
    Once a tally has been performed, verify the result and if it's ok publish it
    '''
    sender_ssl_cert = task.get_data()['sender_ssl_cert']
    input_data = task.get_data()['input_data']
    election_id = input_data['election_id']
    if election_id <= 0:
        raise TaskError(dict(reason="election_id must be positive"))

    election = db.session.query(Election)\
        .filter(Election.id == election_id).first()
    if not election:
        raise TaskError(dict(reason="election not found"))

    # check sender is legitimate
    found_director = False
    for auth in election.authorities.all():
        if not certs_differ(auth.ssl_cert, sender_ssl_cert):
            found_director = True
    if not found_director:
        raise TaskError(
            dict(reason="perform tally task sent by an invalid authority"))

    privdata_path = app.config.get('PRIVATE_DATA_PATH', '')
    election_privpath = os.path.join(privdata_path, str(election_id))

    pubdata_path = app.config.get('PUBLIC_DATA_PATH', '')
    election_pubpath = os.path.join(pubdata_path, str(election_id))
    tally_path = os.path.join(election_pubpath, 'tally.tar.gz')
    tally_hash_path = os.path.join(election_pubpath, 'tally.tar.gz.sha256')
    allow_disjoint_multiple_tallies = os.path.join(
        election_privpath, 'allow_disjoint_multiple_tallies')

    # check election_pubpath already exists - it should contain pubkey etc
    if not os.path.exists(election_pubpath):
        raise TaskError(dict(reason="election public path doesn't exist"))

    # check no tally exists yet
    if not os.path.exists(allow_disjoint_multiple_tallies):
        if os.path.exists(tally_path):
            raise TaskError(
                dict(
                    reason=
                    "election already tallied and multiple tallies not allowed"
                ))

    # if we allow multiple tallies and there's any previous tally, save it for backup
    elif os.path.exists(tally_path):
        datestr = datetime.fromtimestamp(
            os.path.getctime(tally_path)).isoformat()
        new_tally_path = os.path.join(election_pubpath,
                                      'tally_%s.tar.gz' % datestr)
        new_tally_hash_path = os.path.join(election_pubpath,
                                           'tally_%s.tar.gz.sha256' % datestr)
        os.rename(tally_path, new_tally_path)

        if os.path.exists(tally_hash_path):
            os.rename(tally_hash_path, new_tally_hash_path)

    pubkeys = []
    for session in election.sessions.all():
        session_privpath = os.path.join(election_privpath, session.id)
        plaintexts_raw_path = os.path.join(session_privpath, 'plaintexts_raw')
        plaintexts_json_path = os.path.join(session_privpath,
                                            'plaintexts_json')
        proofs_path = os.path.join(session_privpath, 'dir', 'roProof')
        protinfo_path = os.path.join(session_privpath, 'protInfo.xml')

        pubkey_path = os.path.join(privdata_path, str(election_id), session.id,
                                   'publicKey_json')
        with open(pubkey_path, 'r') as pubkey_file:
            pubkeys.append(json.loads(pubkey_file.read()))
            pubkey_file.close()

        # check that we have a tally
        if not os.path.exists(proofs_path) or not os.path.exists(
                plaintexts_raw_path):
            raise TaskError(
                dict(reason="proofs or plaintexts couldn't be verified"))

        # remove any previous plaintexts_json
        if os.path.exists(plaintexts_json_path):
            os.unlink(plaintexts_json_path)

        # transform plaintexts into json format
        #call_cmd(["vmnc", "-plain", "-outi", "json", "plaintexts_raw",
        #          "plaintexts_json"], cwd=session_privpath, check_ret=0,
        #          timeout=3600)
        v_convert_plaintexts_json(session_privpath)

        # verify the proofs. sometimes vfork raises an exception at the end
        # so we dismiss it if the verification is successful. TODO: fix that in
        # vfork
        try:
            # output = subprocess.check_output(["vmnv", protinfo_path, proofs_path, "-v"])
            output = v_verify(protinfo_path, proofs_path)
        except subprocess.CalledProcessError as e:
            output = e.output
        if "Verification completed SUCCESSFULLY after" not in output.decode(
                'utf-8'):
            raise TaskError(dict(reason="invalid tally proofs"))

    # get number of invalid votes that were detected before decryption
    invalid_votes_path = os.path.join(election_privpath, 'invalid_votes')
    invalid_votes = int(open(invalid_votes_path, 'r').read(), 10)

    # once the proofs have been verified, create and publish a tarball
    # containing plaintexts, protInfo and proofs
    # NOTE: we try our best to do a deterministic tally, i.e. one that can be
    # generated exactly the same bit by bit by all authorities

    # For example, here we open tarfile setting cwd so that the header of the
    # tarfile doesn't contain the full path, which would make the tally.tar.gz
    # not deterministic as it might vary from authority to authority
    cwd = os.getcwd()
    try:
        os.chdir(os.path.dirname(tally_path))
        import time
        old_time = time.time
        time.time = lambda: MAGIC_TIMESTAMP
        tar = tarfile.open(os.path.basename(tally_path), 'w|gz')
    finally:
        time.time = old_time
        os.chdir(cwd)
    timestamp = MAGIC_TIMESTAMP

    ciphertexts_path = os.path.join(election_privpath, 'ciphertexts_json')
    pubkeys_path = os.path.join(privdata_path, str(election_id),
                                'pubkeys_json')
    questions_path = os.path.join(privdata_path, str(election_id),
                                  'questions_json')

    with open(pubkeys_path, mode='w') as pubkeys_f:
        pubkeys_f.write(
            json.dumps(pubkeys,
                       ensure_ascii=False,
                       sort_keys=True,
                       indent=4,
                       separators=(',', ': ')))

    with codecs.open(questions_path, encoding='utf-8', mode='w') as res_f:
        res_f.write(
            json.dumps(json.loads(election.questions),
                       ensure_ascii=False,
                       sort_keys=True,
                       indent=4,
                       separators=(',', ': ')))

    deterministic_tar_add(tar, questions_path, 'questions_json', timestamp)
    deterministic_tar_add(tar, ciphertexts_path, 'ciphertexts_json', timestamp)
    deterministic_tar_add(tar, pubkeys_path, 'pubkeys_json', timestamp)

    for session in election.sessions.all():
        session_privpath = os.path.join(election_privpath, session.id)
        plaintexts_json_path = os.path.join(session_privpath,
                                            'plaintexts_json')
        proofs_path = os.path.join(session_privpath, 'dir', 'roProof')
        protinfo_path = os.path.join(session_privpath, 'protInfo.xml')

        outvotes_path = os.path.join(election_privpath, session.id,
                                     'ciphertexts_json')
        with open(outvotes_path, 'r') as outvotes:
            for line in outvotes:
                b = Ballot(session_id=session.id,
                           ballot_hash=hash_data(line.strip()))
                db.session.add(b)
            db.session.commit()

        deterministic_tar_add(tar, plaintexts_json_path,
                              os.path.join(session.id, 'plaintexts_json'),
                              timestamp)
        deterministic_tar_add(tar, proofs_path,
                              os.path.join(session.id, "proofs"), timestamp)
        deterministic_tar_add(tar, protinfo_path,
                              os.path.join(session.id, "protInfo.xml"),
                              timestamp)
    tar.close()

    # and publish also the sha256 of the tarball
    tally_hash_file = open(tally_hash_path, 'w')
    tally_hash_file.write(hash_file(tally_path))
    tally_hash_file.close()