コード例 #1
0
    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)
コード例 #2
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'))
コード例 #3
0
 def output_filter2(p, o, output):
     '''
     detect common errors and kill process in that case
     '''
     if "Failed to parse info files!" in o:
         p.kill(signal.SIGKILL)
         raise TaskError(dict(reason='error executing vfork'))
コード例 #4
0
 def output_filter(p, o, output):
     '''
     detect common errors and kill process in that case
     '''
     if "Unable to download signature!" in o or\
             "ERROR: Invalid socket address!" in o:
         p.kill(signal.SIGKILL)
         raise TaskError(dict(reason='error executing vfork'))
コード例 #5
0
def generate_private_info_vfork(task):
    '''
    After the task has been approved, execute vfork to generate the
    private info
    '''
    # first of all, check that parent task is approved, but we only check that
    # when autoaccept is configured to False. if that's not the case,
    # then cancel everything
    autoaccept = app.config.get('AUTOACCEPT_REQUESTS', '')
    if not autoaccept and\
            task.get_prev().get_data()['output_data'] != dict(status="accepted"):
        task.set_output_data("task not accepted")
        raise TaskError(dict(reason="task not accepted"))

    input_data = task.get_parent().get_data()['input_data']
    election_id = input_data['id']
    sessions = input_data['sessions']
    election = db.session.query(Election)\
        .filter(Election.id == election_id).first()

    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']

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

    # this are an "indicative" url, because port can vary later on
    server_url = get_server_url()
    hint_server_url = get_hint_server_url()

    # generate localProtInfo.xml
    protinfos = []
    for session in sessions:
        session_privpath = os.path.join(election_privpath, session['id'])
        protinfo_path = os.path.join(session_privpath, 'localProtInfo.xml')
        stub_path = os.path.join(session_privpath, 'stub.xml')

        #l = ["vmni", "-party", "-arrays", "file", "-name", auth_name, "-http",
        #    server_url, "-hint", hint_server_url]
        #subprocess.check_call(l, cwd=session_privpath)
        v_gen_private_info(auth_name, server_url, hint_server_url,
                           session_privpath)

        # 5. read local protinfo file to be sent back to the orchestra director
        protinfo_file = codecs.open(protinfo_path, 'r', encoding='utf-8')
        protinfos.append(protinfo_file.read())
        protinfo_file.close()

    # set the output data of parent task, and update sender
    task.get_parent().set_output_data(protinfos)
コード例 #6
0
 def check_ballots_uncounted(session, ballot_hashes):
     '''
     check that no ballot has been used before, otherwise raise error as no
     ballot is allowed to be counted twice
     '''
     query = session.ballots.filter(Ballot.ballot_hash.in_(ballot_hashes))
     if query.count() > 0:
         hashes = json.dumps([ballot.ballot_hash for ballot in query])
         raise TaskError(
             dict(reason=(
                 "error, some ballots already tallied election_id = %s" +
                 ", session_id = %s, duplicated_ballot_hashes = %s") %
                  (str(election_id), session.id, hashes)))
コード例 #7
0
def reset_tally(election_id):
    # check election exists
    election = db.session.query(Election)\
        .filter(Election.id == election_id).first()
    if not election:
        raise TaskError(dict(reason="election not created"))

    # each session is a question
    sessions = election.sessions.all()
    for session in sessions:
        ballots = session.ballots
        for ballot in ballots:
            db.session.delete(ballot)
    db.session.commit()
コード例 #8
0
def check_tally_approval(task):
    '''
    Check if the tally was a approved. If it was, mark the tally as approved.
    If it was not, raise an exception so that the director gets the bad notice.
    '''
    if task.get_prev().get_data()['output_data'] != dict(status="accepted"):
        task.set_output_data("task not accepted")
        raise TaskError(dict(reason="task not accepted"))

    input_data = task.get_data()['input_data']
    election_id = input_data['election_id']
    privdata_path = app.config.get('PRIVATE_DATA_PATH', '')
    approve_path = os.path.join(privdata_path, str(election_id),
                                'tally_approved')

    # create the tally_approved flag file
    open(approve_path, 'a').close()
コード例 #9
0
def create(election_id):
    '''
    create the tarball
    '''
    if not re.match("^[a-zA-Z0-9_-]+$", election_id):
        raise TaskError(dict(reason="invalid characters in election_id"))

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

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

    pubdata_path = app.config.get('PUBLIC_DATA_PATH', '')
    election_pubpath = os.path.join(pubdata_path, 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 = %s" % 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, 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)

    # 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)

    result_privpath = os.path.join(election_privpath, 'result_json')

    # 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, election_id, 'pubkeys_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=(',', ': ')))

    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')

        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()
    print("tally = %s" % tally_path)
コード例 #10
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()
コード例 #11
0
def generate_public_key(task):
    '''
    Generates the local private info for a new election
    '''
    input_data = task.get_data()['input_data']
    session_id = input_data['session_id']
    election_id = input_data['election_id']

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

    # some sanity checks, as this is not a local task
    if not os.path.exists(session_privpath):
        raise TaskError(
            dict(reason="invalid session_id / election_id: " +
                 session_privpath))
    if os.path.exists(os.path.join(session_privpath, 'publicKey_raw')) or\
            os.path.exists(os.path.join(session_privpath, 'publicKey_json')):
        raise TaskError(dict(reason="pubkey already created"))

    # if it's not local, we have to create the merged protInfo.xml
    protinfo_path = os.path.join(session_privpath, 'protInfo.xml')
    if not os.path.exists(protinfo_path):
        protinfo_file = codecs.open(protinfo_path, 'w', encoding='utf-8')
        protinfo_file.write(input_data['protInfo_content'])
        protinfo_file.close()

    # generate raw public key
    def output_filter(p, o, output):
        '''
        detect common errors and kill process in that case
        '''
        if "Unable to download signature!" in o or\
                "ERROR: Invalid socket address!" in o:
            p.kill(signal.SIGKILL)
            raise TaskError(dict(reason='error executing vfork'))

    #call_cmd(["vmn", "-keygen", "publicKey_raw"], cwd=session_privpath,
    #         timeout=10*60, check_ret=0, output_filter=output_filter)
    v_gen_public_key(session_privpath, output_filter)

    def output_filter2(p, o, output):
        '''
        detect common errors and kill process in that case
        '''
        if "Failed to parse info files!" in o:
            p.kill(signal.SIGKILL)
            raise TaskError(dict(reason='error executing vfork'))

    # transform it into json format
    #call_cmd(["vmnc", "-pkey", "-outi", "json", "publicKey_raw",
    #          "publicKey_json"], cwd=session_privpath,
    #          timeout=20, check_ret=0)
    v_convert_pkey_json(session_privpath, output_filter)

    # publish protInfo.xml and publicKey_json
    pubdata_path = app.config.get('PUBLIC_DATA_PATH', '')
    session_pubpath = os.path.join(pubdata_path, str(election_id), session_id)
    if not os.path.exists(session_pubpath):
        mkdir_recursive(session_pubpath)

    pubkey_privpath = os.path.join(session_privpath, 'publicKey_json')
    pubkey_pubpath = os.path.join(session_pubpath, 'publicKey_json')
    shutil.copyfile(pubkey_privpath, pubkey_pubpath)

    protinfo_privpath = os.path.join(session_privpath, 'protInfo.xml')
    protinfo_pubpath = os.path.join(session_pubpath, 'protInfo.xml')
    shutil.copyfile(protinfo_privpath, protinfo_pubpath)
コード例 #12
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)
コード例 #13
0
ファイル: server.py プロジェクト: agoravoting/frestq
def hello_propagated_failure(task):
    print("this task will fail and doesn't handle failures")
    raise TaskError(dict(some_data="here goes some error"))
コード例 #14
0
ファイル: server.py プロジェクト: agoravoting/frestq
 def execute(self):
     raise TaskError(dict(some_data="here goes some error"))
コード例 #15
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')

    # 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"))
コード例 #16
0
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)
コード例 #17
0
def check_election_data(data, check_extra):
    '''
    check election input data. Used both in public_api.py:post_election and
    generate_private_info.
    '''
    requirements = [
        {
            'name': u'id',
            'isinstance': int
        },
        {
            'name': u'title',
            'isinstance': str
        },
        {
            'name': u'description',
            'isinstance': str
        },
        {
            'name': u'authorities',
            'isinstance': list
        },
    ]

    if check_extra:
        requirements += [
            {
                'name': 'callback_url',
                'isinstance': str
            },
            {
                'name': u'questions',
                'isinstance': list
            },
        ]
        questions = data.get('questions', None)
    else:
        try:
            questions = json.loads(data.get('questions', None))
        except:
            raise TaskError(dict(reason='questions is not in json'))

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

    if 'start_date' not in data or (
            data['start_date'] is not None
            and not isinstance(data['start_date'], datetime)):
        raise TaskError(dict(reason="invalid start_date parameter"))

    if 'end_date' not in data or (data['end_date'] is not None and
                                  not isinstance(data['end_date'], datetime)):
        raise TaskError(dict(reason="invalid end_date parameter"))

    if data['id'] < 1:
        raise TaskError(dict(reason="id must be positive"))

    if len(data['authorities']) == 0:
        raise TaskError(dict(reason='no authorities'))

    if not isinstance(questions, list) or len(questions) < 1 or\
            len(questions) > app.config.get('MAX_NUM_QUESTIONS_PER_ELECTION', 15):
        raise TaskError(
            dict(reason='Unsupported number of questions in the election'))


    if check_extra and\
            Election.query.filter_by(id=data['id']).count() > 0:
        raise TaskError(
            dict(reason='an election with id %s already '
                 'exists' % data['id']))

    auth_reqs = [
        {
            'name': 'name',
            'isinstance': str
        },
        {
            'name': 'orchestra_url',
            'isinstance': str
        },
        {
            'name': 'ssl_cert',
            'isinstance': str
        },
    ]

    for adata in data['authorities']:
        for req in auth_reqs:
            if req['name'] not in adata or not isinstance(
                    adata[req['name']], req['isinstance']):
                raise TaskError(
                    dict(reason="invalid %s parameter" % req['name']))

    def unique_by_keys(l, keys):
        for k in keys:
            if len(l) != len(set([i[k] for i in l])):
                return False
        return True

    if not unique_by_keys(data['authorities'], ['ssl_cert', 'orchestra_url']):
        raise TaskError(dict(reason="invalid authorities parameters"))

    q_reqs = [
        {
            'name': 'text',
            'isinstance': str
        },
        {
            'name': 'id',
            'isinstance': int
        },
    ]

    task_error = TaskError(dict(reason="invalid question/answers"))
    questions = data['questions']
    if isinstance(questions, str):
        questions = json.loads(questions)

    for question in questions:
        answers = question['answers']

        non_write_ins_answers = [
            answer for answer in question['answers'] if len([
                url for url in answer['urls']
                if url['url'] == 'true' and url['title'] == 'isWriteIn'
            ]) == 0
        ]

        if (not unique_by_keys(non_write_ins_answers, ['text'])
                or not unique_by_keys(answers, ['id'])):
            raise task_error

        if not check_pipe(q_reqs, answers):
            raise task_error

        l_ids = pluck(answers, 'id')
        if set(l_ids) != set(range(0, len(l_ids))):
            raise task_error