def test_log_methods(self):
        # Arrange
        message = "test log message"

        # Act
        log_debug(message)
        log_error(message)
        log_info(message)
        log_warning(message)
Example #2
0
 def _get_hash_required(self, filename: str) -> Optional[FileInfo]:
     """
     Gets the hash for the requested filename (fully composed path, such as we might get from
     `utils.compose_filename`). If absent, logs an error and returns None.
     """
     hash = self.hashes[filename]
     if hash is None:
         log_error(f"No hash available for file: {filename}")
         return None
     return hash
Example #3
0
    def test_log_methods(self):
        # Arrange
        message = "test log message"

        # Act
        log_debug(message)
        log_error(message)
        log_info(message)
        log_warning(message)

        # Assert
        self.assertIsNotNone(message)
Example #4
0
    def get_encrypted_ballot(
            self, ballot_id: str) -> Optional[CiphertextAcceptedBallot]:
        """
        Given the ballot identifier name, returns the corresponding `CiphertextAcceptedBallot`
        if it exists, otherwise `None`. If the ballots are on disk, this will cause only
        that one ballot to be loaded and cached.
        """
        if ballot_id not in self.encrypted_ballot_memos:
            log_error(f"ballot_id {ballot_id} missing")
            return None

        return self.encrypted_ballot_memos[ballot_id].contents
Example #5
0
    def is_valid_proof(self,
                       public_key: ElementModP,
                       hash_header: Optional[ElementModQ] = None) -> bool:
        """Returns true if the plaintext, ciphertext, and proof are valid and consistent, false if not."""
        valid_proof: bool = self.proof.is_valid(self.decrypted_tally,
                                                self.encrypted_tally,
                                                public_key, hash_header)

        if not valid_proof:  # pragma: no cover
            log_error(
                f"Chaum-Pedersen proof validation failed: valid_proof: {valid_proof}, selection_info: {str(self)}"
            )

        return valid_proof
Example #6
0
    def all_hashes_unique(self) -> bool:
        """
        Checks that every hash value is unique. If a file hash repeated, then there's
        a chance that something went really wrong, like an identical ballot being repeated.
        """
        expected_num_hashes = len(self.hashes.keys())
        actual_num_hashes = len({v.hash for v in self.hashes.values()})

        if expected_num_hashes != actual_num_hashes:
            log_error(
                f"Expected to find {expected_num_hashes} unique ballot hashes, only found {actual_num_hashes}."
            )
            return False
        else:
            return True
Example #7
0
def tallies_match(provided_tally: TALLY_TYPE,
                  recomputed_tally: TALLY_TYPE) -> bool:
    """
    Helper function for comparing tallies. Logs useful errors if something doesn't match.
    """
    tally_success = True

    for selection in recomputed_tally.keys():
        recomputed_ciphertext: ElGamalCiphertext = recomputed_tally[selection]
        provided_ciphertext: ElGamalCiphertext = provided_tally[selection]
        if recomputed_ciphertext != provided_ciphertext:
            log_error(
                f"Mismatching ciphertext tallies found for selection ({selection}). Recomputed sum: ({recomputed_ciphertext}), provided sum: ({provided_ciphertext})"
            )
            tally_success = False

    return tally_success
Example #8
0
 def merge_from(self, other: "Manifest") -> None:
     """
     Given a second manifest, reads all its contents and merges them into this manifest
     (i.e., "self" mutates, but "other" doesn't change). This would be useful when multiple
     remote workers are writing files, and you want to merge the results into a single
     manifest object. Note: both manifests must share the same root directory, and any
     overlapping files in the manifests are considered an error unless they're identical.
     """
     assert (other.root_dir == self.root_dir
             ), "manifests must share the same root directory"
     self_keys = set(self.hashes.keys())
     other_keys = set(other.hashes.keys())
     shared_keys = self_keys.intersection(other_keys)
     for k in shared_keys:
         if self.hashes[k] != other.hashes[k]:
             msg = f"cannot merge manifests: disagreeing contents for {k}: {self.hashes[k]} vs. {other.hashes[k]}"
             log_error(msg)
             raise RuntimeError(msg)
     for k in other_keys:
         self.hashes[k] = other.hashes[k]
     self.bytes_written += other.bytes_written
Example #9
0
def verify_proven_ballot_proofs(
    extended_base_hash: ElementModQ,
    public_key: ElementModP,
    ciphertext_ballot: CiphertextAcceptedBallot,
    pballot: ProvenPlaintextBallot,
) -> bool:
    """
    Returns True if the proofs are consistent with the ciphertext.
    """
    # We're going to check the proof for validity here, even though it takes real time to compute,
    # because we don't expect to be decrypting very many ballots at once, and it's really valuable
    # to do the extra checking for correctness.

    selections: Dict[str, PlaintextBallotSelection] = plaintext_ballot_to_dict(
        pballot.ballot)
    ciphertexts: Dict[str, ElGamalCiphertext] = ciphertext_ballot_to_dict(
        ciphertext_ballot)
    proofs: Dict[str, ChaumPedersenDecryptionProof] = pballot.proofs
    for id in selections.keys():
        if id not in proofs:  # pragma: no cover
            log_error(f"No proof found for selection id {id}")
            return False
        if id not in ciphertexts:  # pragma: no cover
            log_error(f"No ciphertext found for selection id {id}")
            return False

        if not proofs[id].is_valid(selections[id].to_int(), ciphertexts[id],
                                   public_key, extended_base_hash):
            log_error(f"Invalid proof for selection id {id}")
            return False
    return True
Example #10
0
    def validate_contents(self, manifest_file_name: str,
                          file_contents: str) -> bool:
        """
        Checks the manifest for the given file name. Returns True if the name is
        included in the manifest *and* the file_contents match the manifest. If anything
        is not properly validated, a suitable error will be written to the ElectionGuard log.
        """
        if manifest_file_name not in self.hashes:
            log_error(f"File {manifest_file_name} was not in the manifest")
            return False

        file_info: FileInfo = self.hashes[manifest_file_name]
        file_len = len(file_contents.encode("utf-8"))

        if file_len != file_info.num_bytes:
            log_error(
                f"File {manifest_file_name} did not have the expected length (expected: {file_info.num_bytes} bytes, actual: {file_len} bytes)"
            )
            return False

        data_hash = sha256_hash(file_contents)
        if data_hash != file_info.hash:
            log_error(
                f"File {manifest_file_name} did not have the expected hash (expected: {file_info.hash}, actual: {data_hash})"
            )
            return False

        return True
Example #11
0
def decode_json_file_contents(
        json_str: str, class_handle: Type[Serializable[T]]) -> Optional[T]:
    """
    Wrapper around JSON deserialization. Given a string of JSON text and a handle to an
    ElectionGuard `Serializable` class, tries to decode the JSON into an instance of that
    class. If anything fails, the result will be `None`. No exceptions will be raised outside
    of this method, but all such failures will be logged to the ElectionGuard log.

    :param json_str: any JSON string
    :param class_handle: the class, itself, that we're trying to deserialize to
    :returns: the contents of the file, or `None` if there was an error
    """
    try:
        result = class_handle.from_json(json_str)
    except DecodeError as err:  # pragma: no cover
        log_error(f"Failed to decode an instance of {class_handle}: {err}")
        return None
    except UnfulfilledArgumentError as err:  # pragma: no cover
        log_error(f"Decoding failure for {class_handle}: {err}")
        return None

    return result
Example #12
0
    def is_valid(self) -> bool:
        if self.constants != ElectionConstants():
            log_error("Mismatching election constants!")
            return False

        # super-cheesy unit test to make sure keypair works

        m1 = randbelow(5)
        m2 = randbelow(5)
        nonce1 = rand_q()
        nonce2 = rand_q()

        c1 = get_optional(elgamal_encrypt(m1, nonce1, self.keypair.public_key))
        c2 = get_optional(elgamal_encrypt(m2, nonce2, self.keypair.public_key))
        csum = elgamal_add(c1, c2)

        psum = csum.decrypt(self.keypair.secret_key)

        if psum != m1 + m2:
            log_error("The given keypair didn't work for basic ElGamal math")
            return False

        return True
Example #13
0
def load_file_helper(
    root_dir: str,
    file_name: Union[str, PurePath],
    subdirectories: List[str] = None,
) -> Optional[str]:
    """
    Reads the requested file, by name, returning its contents as a Python string.

    Note: if the file_name is a path-like object, the root_dir and subdirectories
    are ignored and the file is directly loaded.

    :param root_dir: top-level directory where we'll be reading files
    :param file_name: name of the file, including any suffix
    :param subdirectories: path elements to be introduced between `root_dir` and the file; empty-list means no subdirectory
    :returns: the contents of the file, or `None` if there was an error
    """

    if isinstance(file_name, PurePath):
        full_name = file_name
    else:
        full_name = compose_filename(root_dir, file_name, subdirectories)

    try:
        s = stat(full_name)
        if s.st_size == 0:  # pragma: no cover
            log_error(f"The file ({full_name}) is empty")
            return None

        with open(full_name, "r") as f:
            data = f.read()

            return data
    except FileNotFoundError as e:
        log_error(f"Error reading file ({full_name}): {e}")
        return None
    except OSError as e:
        log_error(f"Error reading file ({full_name}): {e}")
        return None