Exemple #1
0
 def set_status_to_compilation_failed(self, error_message=None):
     utils.console(
         "Setting the status to compilation_failed for program with id %s" %
         str(self._id))
     if Program.Status.authorized_status_change(
             self.status, Program.Status.compilation_failed):
         self._status = Program.Status.compilation_failed.value
         if error_message is not None and type(error_message) == str:
             self._error_message = error_message
Exemple #2
0
 def set_status_to_broken(self, user, now):
     if now > app.config['FINAL_DEADLINE']:
         return
     if Program.Status.authorized_status_change(self.status,
                                                Program.Status.broken):
         self._status = Program.Status.broken.value
         if self._timestamp_first_break is None:
             self._timestamp_first_break = now
             self._timestamp_strawberries_next_update = now  # + app.config['NBR_SECONDS_PER_DAY']
         whitebox_break = WhiteboxBreak.create(user, self, now,
                                               self._strawberries_last)
         db.session.add(whitebox_break)
     else:
         utils.console("Could NOT set status to broken")
Exemple #3
0
 def set_status_to_unbroken(self):
     if Program.Status.authorized_status_change(self.status,
                                                Program.Status.unbroken):
         now = int(time.time())
         if now > app.config['FINAL_DEADLINE']:
             utils.console("Submission rejected after final deadline")
             self.set_status_to_compilation_failed(
                 "Submission rejected after final deadline")
             return
         self._status = Program.Status.unbroken.value
         if self._timestamp_published is None:
             self._timestamp_published = now
         self.update_strawberries(now)
         self._funny_name = get_funny_name(self._id)
Exemple #4
0
def clean_programs_timeout_to_compile_or_test():
    for _ in range(5):
        try:
            utils.console(
                'Try to clean programs timeout to compile or test...')
            Program.clean_programs_which_timeout_to_compile_or_test()
            db.session.commit()
            return True
        except:
            utils.console('Exception catched, trying again in 2sec')
            print_exc()
            time.sleep(2)

    utils.console('Could not clean programs which failed to compile or test')
    return False
Exemple #5
0
def compile_and_test():
    utils.console('Starting compile and test')

    if not clean_programs_timeout_to_compile_or_test():
        return ""

    client = docker.from_env()
    api_client = docker.APIClient(app.config['SOCK'])

    if utils.service_runs_already(
            client, app.config['NAME_OF_COMPILE_AND_TEST_SERVICE']):
        utils.console(
            'A program is currently being compiled or tested. Exiting.')
        return ""

    retry_count = 0
    while True:
        try:
            utils.console('Looking for a program to compile and test.')
            program_to_compile_and_test = Program.get_next_program_to_compile()
        except:
            retry_count += 1
            if retry_count < 5:
                utils.console('Exception catched, trying again in 2sec')
                time.sleep(2)
                continue
            else:
                utils.console(
                    'Could not look for a program to compile and test.')
                utils.console('Exception:')
                print_exc()
                return ""
        break

    if program_to_compile_and_test is None:
        utils.console('There is no program to compile and test. Exiting')
        return ""
    basename = os.path.splitext(program_to_compile_and_test.filename)[0]
    key_string = program_to_compile_and_test.key
    compiler = program_to_compile_and_test.compiler
    # Make sure the key can be converted in a 16-byte string
    try:
        key_bytes = bytes.fromhex(key_string)
        if len(key_bytes) != 16:
            raise
    except:
        utils.console("The key is invalid, setting the status to test failed.")
        program_to_compile_and_test.set_status_to_test_failed()
        db.session.commit()
        return ""

    utils.console('Preparing to compile and test a program (basename=%s)' %
                  basename)

    retry_count = 0
    while True:
        try:
            utils.console('Generating nonce')
            nonce = program_to_compile_and_test.generate_nonce()
            db.session.commit()
        except:
            retry_count += 1
            if retry_count < 5:
                utils.console('Exception catched, trying again in 2sec')
                time.sleep(2)
                continue
            else:
                utils.console('Could not generate nonce.')
                utils.console('Exception:')
                print_exc()
                return ""
        break

    # TODO: add more constraints on the service, use https instead of http, do not hardcode the urls
    restart_policy = docker.types.RestartPolicy(condition='on-failure',
                                                max_attempts=1)
    mem_limit = 2**20 * max(
        app.config['CHALLENGE_MAX_MEM_COMPILATION_IN_MB'],
        app.config['CHALLENGE_MAX_MEM_EXECUTION_IN_MB'])  # in Bytes
    resources = docker.types.Resources(mem_limit=mem_limit)
    networks = [app.config['COMPILE_AND_TEST_SERVICE_NETWORK']]
    env = [
        'UPLOAD_FOLDER=/uploads',
        'FILE_BASENAME=%s' % basename,
        'COMPILER=%s' % compiler,
        'URL_TO_PING_BACK=%s' %
        'http://launcher:5000/compile_and_test_result/%s/%s/' %
        (basename, nonce),
        'URL_FOR_FETCHING_PLAINTEXTS=%s' %
        'http://launcher:5000/get_plaintexts/%s/%s' % (basename, nonce),
        'CHALLENGE_MAX_MEM_COMPILATION_IN_MB=%d' %
        app.config['CHALLENGE_MAX_MEM_COMPILATION_IN_MB'],
        'CHALLENGE_MAX_TIME_COMPILATION_IN_SECS=%d' %
        app.config['CHALLENGE_MAX_TIME_COMPILATION_IN_SECS'],
        'CHALLENGE_MAX_BINARY_SIZE_IN_MB=%d' %
        app.config['CHALLENGE_MAX_BINARY_SIZE_IN_MB'],
        'CHALLENGE_MAX_MEM_EXECUTION_IN_MB=%d' %
        app.config['CHALLENGE_MAX_MEM_EXECUTION_IN_MB'],
        'CHALLENGE_MAX_TIME_EXECUTION_IN_SECS=%d' %
        app.config['CHALLENGE_MAX_TIME_EXECUTION_IN_SECS'],
        'CHALLENGE_NUMBER_OF_TEST_VECTORS=%d' %
        app.config['CHALLENGE_NUMBER_OF_TEST_VECTORS'],
    ]

    # We copy the source file from /uploads to a fresh directory in /compilations
    dir_for_compilation = basename
    path_for_compilations = os.path.join('/compilations', dir_for_compilation)
    if not os.path.exists(path_for_compilations):
        os.makedirs(path_for_compilations)
    source_name = basename + '.c'
    path_to_uploaded_source = os.path.join('/uploads', source_name)
    path_to_source_for_compilation = os.path.join(path_for_compilations,
                                                  source_name)
    if not os.path.exists(path_to_source_for_compilation):
        shutil.copy(path_to_uploaded_source, path_to_source_for_compilation)

    # We configure and launch the compile_and_test docker
    mounts = [
        '/whitebox_program_uploads/compilations/%s:/uploads:ro' %
        dir_for_compilation
    ]
    service = client.services.create(
        image='crx/compile_and_test',
        mounts=mounts,
        env=env,
        constraints=['node.labels.vm == node-sandbox'],
        name=app.config['NAME_OF_COMPILE_AND_TEST_SERVICE'],
        restart_policy=restart_policy,
        labels={'basename': str(basename)},
        networks=networks,
        resources=resources)

    while len(service.tasks()) == 0:
        time.sleep(0.1)
    task = service.tasks()[0]
    task_id = task['ID']
    retry_count = 0
    while True:
        try:
            utils.console('Setting the program\'s task id to %s' % task_id)
            program_to_compile_and_test.task_id = task_id
            db.session.commit()
        except:
            retry_count += 1
            if retry_count < 5:
                utils.console('Exception catched, trying again in 2sec')
                time.sleep(2)
                continue
            else:
                utils.console('Could not set the program task ide.')
                utils.console('Exception:')
                print_exc()
                return ""
        break

    utils.console('End of the compile_and_test procedure for the program:')
    utils.console(str(program_to_compile_and_test))

    return "youpi"
Exemple #6
0
def compile_and_test_result(basename, nonce, ret):
    utils.console(
        "Entering compile_and_test_result(basename=%s, nonce=%s, ret=%d)" %
        (basename, nonce, ret))
    if not utils.basename_and_nonce_are_valid(basename, nonce) or ret is None:
        utils.console("Exception takes place ... (0)")
        return ""

    program = Program.get(basename)
    if program.status != Program.Status.submitted:
        utils.console(
            "The program %d status is %s. No need to proceed for this program."
            % (program._id, program.status))
        utils.console("Exception takes place ... (1)")
        return ""

    # We (try to) remove the compilation directory
    dir_for_compilation = basename
    path_for_compilations = os.path.join('/compilations', dir_for_compilation)
    utils.console('Trying to remove %s' % str(path_for_compilations))
    try:
        shutil.rmtree(path_for_compilations)
    except:
        utils.console('Could NOT remove the directory %s' %
                      str(path_for_compilations))

    # We process the ret code
    if ret == ERR_CODE_CONTAININT_FORBIDDEN_STRING:
        postdata = request.get_json()
        program.set_status_to_preprocess_failed(postdata['error_message'])
    elif ret == ERR_CODE_COMPILATION_FAILED:
        program.set_status_to_compilation_failed(
            'Compilation failed for unknown reason (may be due to an excessive memory usage).'
        )
        utils.console('Compilation failed for file with basename %s' %
                      str(basename))
    elif ret == ERR_CODE_BIN_TOO_LARGE:
        program.set_status_to_compilation_failed(
            'Compiled binary file size exceeds the limit of %dMB.' %
            app.config['CHALLENGE_MAX_BINARY_SIZE_IN_MB'])
        utils.console('Compilation failed for file with basename %s' %
                      str(basename))
    elif ret == ERR_CODE_LINK_FAILED:
        program.set_status_to_link_failed()
        utils.console('Link failed for file with basename %s' % str(basename))
    elif ret == ERR_CODE_EXECUTION_EXCEED_RAM_LIMIT:
        postdata = request.get_json()
        program.set_status_to_execution_failed(
            "Execution reach memory limitation of %dMB. Memory consumption was %.2fMB."
            %
            (app.config['CHALLENGE_MAX_MEM_EXECUTION_IN_MB'], postdata['ram']))
        utils.console(
            'Code execution reach memory limit for file with basename %s' %
            str(basename))
    elif ret == ERR_CODE_EXECUTION_EXCEED_TIME_LIMIT:
        postdata = request.get_json()
        program.set_status_to_execution_failed(
            "Execution reach time limitation of %ds. Time used %.2fs" %
            (app.config['CHALLENGE_MAX_TIME_EXECUTION_IN_SECS'],
             postdata['cpu_time']))
        utils.console(
            'Code execution reach time limit for file with basename %s' %
            str(basename))
    elif ret == ERR_CODE_EXECUTION_FAILED:
        program.set_status_to_execution_failed()
        utils.console('Code execution failed for file with basename %s' %
                      str(basename))
    elif ret == CODE_SUCCESS:
        utils.console('Success for file with basename %s' % str(basename))
    else:
        utils.console(
            "We received an unexpected return code (%s) for file with basename %s"
            % (str(ret), str(basename)))
    db.session.commit()

    if ret != ERR_CODE_EXECUTION_FAILED:
        client = docker.from_env()
        utils.remove_compiler_service_for_basename(client, basename, app)

    if ret != CODE_SUCCESS:
        utils.console("Failed to compiling ... ")
        return ""

    # If we reach this point, the program was successfuly compiled,
    # we can test the ciphertexts
    response = request.get_json()
    size_factor = response['size_factor']
    ram_factor = response['ram_factor']
    time_factor = response['time_factor']
    program.set_performance_factor(size_factor, ram_factor, time_factor)

    ciphertexts = bytes.fromhex(response['ciphertexts'])
    number_of_test_vectors = app.config['CHALLENGE_NUMBER_OF_TEST_VECTORS']
    if len(ciphertexts) != 16 * number_of_test_vectors:
        utils.console(
            "The length of the ciphertexts is %d, we were expecting %d." %
            (len(ciphertexts), 16 * number_of_test_vectors))
        error_message = "The stream of ciphertexts does not have the appropriate length."
        utils.console(error_message)
        program.error_message = error_message
        program.set_status_to_test_failed()
        db.session.commit()

        utils.console("Exception take place... (3)")
        return ""

    # If we reach this point, the ciphertexts stream has the appropriate length
    utils.console("We received the appropriate number of ciphertexts.")
    utils.console(
        "Testing the plaintexts against the ciphertexts using the announced key..."
    )

    # Retrieve the plaintexts from the saved file
    path_to_plaintexts_file = os.path.join('/tmp', basename + '.plaintext.bin')
    plaintexts = b''
    with open(path_to_plaintexts_file, 'rb') as f:
        plaintexts = f.read()

    # Check the ciphertexts against the plaintext and key.
    # TODO the db should always return the key as 16 bytes
    key = bytes.fromhex(program.key)
    try:
        expected_ciphertexts = utils.compute_ciphertexts(
            plaintexts, key, number_of_test_vectors)
        if len(expected_ciphertexts) != 16 * number_of_test_vectors:
            raise
    except:
        error_message = "Could not compute the test vectors for the given key."
        program.set_status_to_test_failed(error_message)
        db.session.commit()
        return ""
    for i in range(number_of_test_vectors):
        ciphertext = ciphertexts[16 * i:16 * (i + 1)]
        expected_ciphertext = expected_ciphertexts[16 * i:16 * (i + 1)]
        if ciphertext != expected_ciphertext:
            plaintext = plaintexts[16 * i:16 * (i + 1)]
            utils.console(
                "One of the ciphertext failed the test (plaintext=%s, key=%s, ciphertext=%s)."
                % (plaintext, key, ciphertext))
            error_message = '''One of the tests failed:
- plaintext   %s
- key         %s
- ciphertext  %s
- expected    %s''' % (binascii.hexlify(plaintext).decode(),
                       binascii.hexlify(key).decode(),
                       binascii.hexlify(ciphertext).decode(),
                       binascii.hexlify(expected_ciphertext).decode())
            program.set_status_to_test_failed(error_message)
            db.session.commit()
            return ""

    # If we reach this point, all the tests were successful.
    # We save 10 test vectors for key validation in the database:
    plaintexts_for_breaking = plaintexts[0:10 * 16]
    ciphertexts_for_breaking = ciphertexts[0:10 * 16]
    program.plaintexts = plaintexts_for_breaking
    program.ciphertexts = ciphertexts_for_breaking
    # we save one pair for validating inversion
    plaintext_for_inverting = plaintexts[10 * 16:11 * 16]
    ciphertext_for_inverting = ciphertexts[10 * 16:11 * 16]
    program.plaintext_sha256_for_inverting = hashlib.sha256(
        plaintext_for_inverting).hexdigest()
    program.ciphertext_for_inverting = ciphertext_for_inverting

    program.set_status_to_unbroken()
    db.session.commit()
    utils.console("The program is unbroken!")

    # Cleanup
    try:
        os.remove(path_to_plaintexts_file)
        utils.console("We removed the file %s" % path_to_plaintexts_file)
    except:
        utils.console("Could NOT remove the file %s" % path_to_plaintexts_file)

    return ""
def compile_and_test_result(basename, nonce, ret):
    utils.console(f"Entering compile_and_test_result(basename={basename}, "
                  f"nonce={nonce}, ret={ret})")
    if not utils.basename_and_nonce_are_valid(basename, nonce) or ret is None:
        utils.console("Exception takes place ... (0)")
        return ""

    program = Program.get(basename)
    if program.status != Program.Status.submitted:
        utils.console(f"The program {program._id} status is {program.status}. "
                      "No need to proceed for this program.")
        utils.console("Exception takes place ... (1)")
        return ""

    # We (try to) remove the compilation directory
    dir_for_compilation = basename
    path_for_compilations = os.path.join('/compilations', dir_for_compilation)
    utils.console(f'Trying to remove {path_for_compilations}')
    try:
        shutil.rmtree(path_for_compilations)
    except:
        utils.console(f'Could NOT remove the dir {path_for_compilations}')

    # We process the ret code
    process_compile_and_test_ret(program, request, basename, ret)
    db.session.commit()

    if ret != ERR_CODE_EXECUTION_FAILED:
        client = docker.from_env()
        utils.remove_compiler_service_for_basename(client, basename, app)

    if ret != CODE_SUCCESS:
        utils.console("Failed to compiling ... ")
        return ""

    # If we reach this point, the program was successfully compiled,
    # we get performance factors
    postdata = request.get_json()
    size_factor = postdata['size_factor']
    ram_factor = postdata['ram_factor']
    time_factor = postdata['time_factor']
    program.set_performance_factor(size_factor, ram_factor, time_factor)

    # we can test the signatures
    signatures = bytes.fromhex(postdata['signatures'])
    number_of_test_vectors = app.config['CHALLENGE_NUMBER_OF_TEST_VECTORS']
    number_of_test_vectors += len(CHALLENGE_TEST_EDGE_CASES)
    if len(signatures) != 64 * number_of_test_vectors:
        utils.console(f"The length of the signatures is {len(signatures)}, "
                      f"we were expecting {32*number_of_test_vectors}.")
        error_message = "The stream of ciphertexts does not have the appropriate length."
        utils.console(error_message)
        program.error_message = error_message
        program.set_status_to_test_failed()
        db.session.commit()

        utils.console("Exception take place... (3)")
        return ""

    # If we reach this point, the ciphertexts stream has the appropriate length
    utils.console("We received the appropriate number of signatures.")
    utils.console("Verify signature for messages using the announced key...")

    # Retrieve the messages from the saved file
    path_to_messages_file = os.path.join('/tmp', basename + '.message.bin')
    with open(path_to_messages_file, 'rb') as f:
        messages = f.read()

    # Check the signature against the public key and messages.
    # TODO the db should always return the key as 128 hexdecimal digits
    pubkey = program.pubkey
    for i in range(number_of_test_vectors):
        message = messages[32*i:32*(i+1)].hex()
        signature = signatures[64*i:64*(i+1)].hex()
        if not ecdsa_verify_str(pubkey, message, signature):
            utils.console(f"The {i}-th signature cannot be verified "
                          f"(hash={message}, pubkey={pubkey}, "
                          f"signature={signature}).")

            error_message = f'''One of the tests failed:

- hash      {message}
- pubkey    {pubkey}
- signature {signature}'''
            program.set_status_to_test_failed(error_message)
            db.session.commit()
            return ""
    utils.console(f"All {number_of_test_vectors} signatures verified")

    # If we reach this point, all the tests were successful.
    # We save 10 test vectors for in the database
    messages_for_checking = messages[0:10*32]
    signatures_for_checking = signatures[0:10*64]
    program.hashes = messages_for_checking
    program.signatures = signatures_for_checking

    program.set_status_to_unbroken()
    db.session.commit()
    utils.console("The program is unbroken!")

    # Cleanup
    try:
        os.remove(path_to_messages_file)
        utils.console("We removed the file %s" % path_to_messages_file)
    except:
        utils.console("Could NOT remove the file %s" % path_to_messages_file)

    return ""
def process_compile_and_test_ret(program, request, basename, ret):
    if ret == ERR_CODE_CONTAININT_FORBIDDEN_STRING:
        postdata = request.get_json()
        program.set_status_to_preprocess_failed(postdata['error_message'])
    elif ret == ERR_CODE_COMPILATION_FAILED:
        postdata = request.get_json()
        if postdata:
            program.set_status_to_compilation_failed(postdata['error_message'])
        else:
            program.set_status_to_compilation_failed(
                'Compilation failed for unknown reason '
                '(may be due to an excessive memory usage).')
        utils.console(f'Compilation failed for file with basename {basename}')
    elif ret == ERR_CODE_BIN_TOO_LARGE:
        program.set_status_to_compilation_failed(
            "Compiled binary file size exceeds the limit of "
            f"{CHALLENGE_MAX_BINARY_SIZE_IN_MB:d}MB.")
        utils.console(f'Compilation failed for file with basename {basename}')
    elif ret == ERR_CODE_LINK_FAILED:
        program.set_status_to_link_failed()
        utils.console(f'Link failed for file with basename {basename}')
    elif ret == ERR_CODE_EXECUTION_EXCEED_RAM_LIMIT:
        postdata = request.get_json()
        program.set_status_to_execution_failed(
            "Execution reach memory limitation of "
            f"{CHALLENGE_MAX_MEM_EXECUTION_IN_MB:d}MB. "
            f"Memory consumption was {postdata['ram']:.2f}MB.")
        utils.console("Code execution reach memory limit for file with "
                      f"basename {basename}")
    elif ret == ERR_CODE_EXECUTION_EXCEED_TIME_LIMIT:
        postdata = request.get_json()
        program.set_status_to_execution_failed(
            "Execution reach time limitation of "
            f"{app.config['CHALLENGE_MAX_TIME_EXECUTION_IN_SECS']:d}s. "
            f"Time used {postdata['cpu_time']:.2f}s")
        utils.console("Code execution reach time limit for file with "
                      f"basename {basename}")
    elif ret == ERR_CODE_EXECUTION_FAILED:
        program.set_status_to_execution_failed()
        utils.console(
            f'Code execution failed for file with basename {basename}')
    elif ret == CODE_SUCCESS:
        utils.console(f'Success for file with basename {basename}')
    else:
        utils.console(f"We received an unexpected return code ({ret}) "
                      f"for file with basename {basename}")
Exemple #9
0
def compile_and_test_result(basename, nonce, ret):
    utils.console(
        "Entering compile_and_test_result(basename=%s, nonce=%s, ret=%d)" %
        (basename, nonce, ret))
    if not utils.basename_and_nonce_are_valid(basename, nonce) or ret is None:
        return ""
    program = Program.get(basename)

    # We (try to) remove the compilation directory
    dir_for_compilation = basename
    path_for_compilations = os.path.join('/compilations', dir_for_compilation)
    utils.console('Trying to remove %s' % str(path_for_compilations))
    try:
        shutil.rmtree(path_for_compilations)
    except:
        utils.console('Could NOT remove the directory %s' %
                      str(path_for_compilations))

    # We process the ret code
    if ret == ERR_CODE_COMPILATION_FAILED:
        program.set_status_to_compilation_failed(
            'Compilation failed for unknown reason (may be due to an excessive memory usage).'
        )
        utils.console('Compilation failed for file with basename %s' %
                      str(basename))
    elif ret == ERR_CODE_BIN_TOO_LARGE:
        program.set_status_to_compilation_failed(
            'Compiled binary file size exceeds the limit of %dMB.' %
            app.config['CHALLENGE_MAX_BINARY_SIZE_IN_MB'])
        utils.console('Compilation failed for file with basename %s' %
                      str(basename))
    elif ret == ERR_CODE_LINK_FAILED:
        program.set_status_to_link_failed()
        utils.console('Link failed for file with basename %s' % str(basename))
    elif ret == ERR_CODE_EXECUTION_FAILED:
        program.set_status_to_execution_failed()
        utils.console('Code execution failed for file with basename %s' %
                      str(basename))
    elif ret == CODE_SUCCESS:
        utils.console('Success for file with basename %s' % str(basename))
    else:
        utils.console(
            'We received an unexpected return code (%s) for file with basename %s'
            % (str(ret), str(basename)))
    db.session.commit()
    client = docker.from_env()
    utils.remove_compiler_service_for_basename(client, basename, app)
    if ret != CODE_SUCCESS:
        return ""

    # If we reach this point, the program was successfuly compiled, we can test the ciphertexts

    ciphertexts = request.get_data()
    number_of_test_vectors = app.config['CHALLENGE_NUMBER_OF_TEST_VECTORS']
    if len(ciphertexts) != 16 * number_of_test_vectors:
        utils.console(
            "The length of the ciphertexts is %d, we were expecting %d." %
            (len(ciphertexts), 16 * number_of_test_vectors))
        error_message = "The stream of ciphertexts does not have the appropriate length."
        utils.console(error_message)
        program.error_message = error_message
        program.set_status_to_test_failed()
        db.session.commit()
        return ""

    # If we reach this point, the ciphertexts stream has the appropriate length

    utils.console("We received the appropriate number of ciphertexts.")
    utils.console(
        "Testing the plaintexts against the ciphertexts using the announced key..."
    )

    # Retrieve the plaintexts from the saved file
    path_to_plaintexts_file = os.path.join('/tmp', basename + '.plaintext.bin')
    plaintexts = b''
    with open(path_to_plaintexts_file, 'rb') as f:
        plaintexts = f.read()

    # Check the ciphertexts against the plaintext and key.
    key = bytes.fromhex(
        program.key)  # TODO the db should always return the key as 16 bytes
    try:
        expected_ciphertexts = utils.compute_ciphertexts(
            plaintexts, key, number_of_test_vectors)
        if len(expected_ciphertexts) != 16 * number_of_test_vectors:
            raise
    except:
        error_message = "Could not compute the test vectors for the given key."
        program.set_status_to_test_failed(error_message)
        db.session.commit()
        return ""
    for i in range(number_of_test_vectors):
        ciphertext = ciphertexts[16 * i:16 * (i + 1)]
        expected_ciphertext = expected_ciphertexts[16 * i:16 * (i + 1)]
        if ciphertext != expected_ciphertext:
            plaintext = plaintexts[16 * i:16 * (i + 1)]
            utils.console(
                "One of the ciphertext failed the test (plaintext=%s, key=%s, ciphertext=%s)."
                % (plaintext, key, ciphertext))
            error_message = '''One of the tests failed:
- plaintext   %s
- key         %s
- ciphertext  %s
- expected    %s''' % (binascii.hexlify(plaintext).decode(),
                       binascii.hexlify(key).decode(),
                       binascii.hexlify(ciphertext).decode(),
                       binascii.hexlify(expected_ciphertext).decode())
            program.set_status_to_test_failed(error_message)
            db.session.commit()
            return ""

    # If we reach this point, all the tests were successful. We save 10 test vectors in the database so that we can test
    # key candidates in the future.

    plaintexts = plaintexts[0:10 * 16]
    ciphertexts = ciphertexts[0:10 * 16]
    program.plaintexts = plaintexts
    program.ciphertexts = ciphertexts
    program.set_status_to_unbroken()
    db.session.commit()
    utils.console("The program is unbroken!")

    # Cleanup
    try:
        os.remove(path_to_plaintexts_file)
        utils.console("We removed the file %s" % path_to_plaintexts_file)
    except:
        utils.console("Could NOT remove the file %s" % path_to_plaintexts_file)

    # Look for another program to compile and test
    compile_and_test()

    return ""