From e3015a5e81d12f5fda1130a6f236fe453667171e Mon Sep 17 00:00:00 2001 From: biloute02 Date: Thu, 4 Jul 2024 12:36:14 +0200 Subject: [PATCH] Authentication and vote implemented along with the voting databases. --- source/Card.py | 2 +- source/Machine.py | 278 ++++++++++++++++++++++++++++++---------------- 2 files changed, 186 insertions(+), 94 deletions(-) diff --git a/source/Card.py b/source/Card.py index dd6b828..2687892 100644 --- a/source/Card.py +++ b/source/Card.py @@ -30,7 +30,7 @@ class Card: def check_pin(self, hashed_pin: bytes) -> bool: """ Check if the card is valid by comparing hashed PIN. - :param pin_hash: The PIN to check. + :param hashed_pin: The PIN to check. :return: True if the received hash PIN matches the stored hashed PIN, False otherwise. """ return self.hashed_pin == hashed_pin diff --git a/source/Machine.py b/source/Machine.py index 5ff0d9f..6841eb7 100644 --- a/source/Machine.py +++ b/source/Machine.py @@ -1,81 +1,104 @@ +import datetime import os from typing import Optional from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.asymmetric import padding from .Card import Card +from .models.Elector import Elector +from .models.Proof import Proof class Machine: """ - + Represent a machine used to vote """ - def __init__(self): - # First DB: list of people authorized to vote on this machine. + def __init__(self, emerging_list: list[Proof]): + # First model: list of people authorized to vote on this machine. # Public (que name et procuration pas empreinte) ? - # fonction insérer élément - # fonction check - self.emerging_list = {} - #[ - # { - # "name": "Bob", - # "personne": (pub_key_bob, Hash(empreinte_bob)), - # "procuration": None, - # }, - # { - # "name": "Alice", - # "personne": (pub_key_alice, Hash(empreinte_alice)), - # "procuration": (pub_key_Eve, Hash(empreinte_eve)), # Eve peut voter pour Alice. - # } - # { - # "name": "Eve", - # "personne": (pub_key_eve, Hash(empreinte_eve)), - # "procuration": None - # } - # ] + self.private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + ) + self._public_key = self.private_key.public_key() - # Second DB: list of people who voted. - # - public - self.signing_list = {} - # [ - # ("pub_key_Alice", "pub_key_Eve", "date", signature("message")), - # ("pub_key_Bob", "pub_key_Bob", "date", signature("message")), - # ] + # List of electors who can vote on this machine. + self.emerging_list: list[Elector] = emerging_list - # Third DB: votes (shuffle when vote is inserted). - # - private - self._vote_list = [] + # List of people who voted. + self.proof_list: list[Proof] = [] + + # Private votes (shuffle when a vote is inserted). # ["A", "B", "C"] + # NOTE(Faraphel): rename candidates ? + self._vote_list: list[str] = [] - # If details during the simulation. - #def send_challenge(self) -> bytes: - # pass - #def check_challenge(self, hash_rand: bytes) -> bool: - # pass + self.end_of_election: bool = False - def search_pubkey(self, pubkey: bytes) -> list[tuple[bytes, bytes]]: + def search_elector(self, pubkey: bytes) -> Optional[Elector]: """ - Search pubkey in emerging list. - :param pubkey: Public key to search in the emerging list. - :return: All entries. + Search public key of elector in the emerging list. + :param pubkey: Public key of elector to search in the emerging list. + :return: The first elector who matches or None. """ - - next(filter( - lambda data: data["personne"][0] == and - self.emerging_list - )) + try: + return next(filter( + lambda elector: elector.public_key_elector == pubkey, + self.emerging_list + )) + except StopIteration: + return None - def check_fingerprint(self, empreinte: bytes) -> bool: + def search_proxy_vote(self, pubkey: bytes) -> Optional[Elector]: """ - - :return: + Search the elector with the public key registered as the proxy vote. + :param pubkey: Public key of the mandataire who can proxy vote. + :return: The elector with the proxy vote pubkey registered. """ - return True + try: + return next(filter( + lambda elector: elector.public_key_mandataire == pubkey, + self.emerging_list + )) + except StopIteration: + return None + def search_proof(self, pubkey: bytes) -> Optional[Proof]: + """ + Search pubkey in the signature list + :param pubkey: Public to search in the signature list. + :return: The first elector with pubkey which voted or None. + """ + try: + return next(filter( + lambda proof: proof.public_key_elector == pubkey, + self.proof_list + )) + except StopIteration: + return None + + def check_fingerprint(self, elector: Elector, fingerprint: str) -> bool: + """ + Hash the fingerprint and check if it authenticates the elector. + :param elector: Elector to authenticate. + :param fingerprint: Fingerprint to check. + :return: True if the fingerprint authenticates the mandataire or the voter else False. + """ + # Is the fingerprint matches? + digest = hashes.Hash(hashes.SHA256()) + digest.update(bytes(fingerprint, 'utf-8')) + hashed_fingerprint = digest.finalize() + + if (hashed_fingerprint == elector.hashed_fingerprint_mandataire or + hashed_fingerprint == elector.hashed_fingerprint_elector): + return True + else: + return False def check_card(self, card: Card, pin: int) -> bool: """ @@ -84,7 +107,7 @@ class Machine: :param pin: :return: """ - # Public key. + # Public key transmitted by the card. public_key = card.public_key() # Challenge to verify public key. @@ -102,59 +125,134 @@ class Machine: hashes.SHA256() ) except InvalidSignature: + print("Carte invalide : échec du challenge.") return False - # Pin - # CHECK(biloute02,faraphel): c’est bon comme ça le digest ? + # Pin verification. digest = hashes.Hash(hashes.SHA256()) - digest.update(pin.as_bytes()) + digest.update(pin.to_bytes()) hashed_pin = digest.finalize() if not card.check_pin(hashed_pin): + print("Mot de passe de la carte refusé.") + return False + + # Card has been verified. + return True + + def authenticate(self, card: Card, pin: int, fingerprint: str) -> bool: + """ + Authenticate a person with its card and its fingerprint. + :param card: Elector card. + :param pin: Elector card pin. + :param fingerprint: Elector fingerprint associated with its card public key. + :return: True if the elector is registered in the emerging list, else False. + """ + # Check if election is not ended. + if self.end_of_election: + print("L’élection est terminée") + return False + + # Check if card is valid. + if not self.check_card(card, pin): + print("Carte de l’électeur invalide.") return False # Is in emerging list? + elector = self.search_elector(card.public_key) + if elector is None: + elector = self.search_proxy_vote(card.public_key) + if elector is None: + print("L’électeur n’est pas inscrit dans la liste électorale.") + return False + print("L’électeur peut voter") - return True - - def authenticate(self, card: Card, pin: int, empreinte: bytes) -> bool: - # Check if card is v - if not self.check_card(card, pin): - print("") + # Is the fingerprint matching? + if not self.check_fingerprint(elector, fingerprint): + print("Erreur de reconnaissance de l’empreinte digitale.") return False - pubkey = card.public_key - # Check pubkey - entries = self.search_pubkey(pubkey) - if not entry: - return False - - # Check fingerprint - if + # L’électeur est authentifié. return True def vote(self, card: Card, vote: str) -> bool: - public_key = card.public_key + """ + Vote with the card (user must have been authenticated previously). + :param card: Elector card + :param vote: Elector vote or proxy vote + :return: True if the vote has been inserted else False. + """ + # Check if election is not ended. + if self.end_of_election: + print("L’élection est terminée") + return False - # Chercher si public_key in signing_list for person. - personne = self.search_personne(public_key) - if personne is not None: - print("La personne peut voter pour elle-même") - return True + # Vérification si l’électeur peut voter pour lui. + elector = self.search_elector(card.public_key) + if elector is None: - # Chercher si public_key in signing_list for procuration. - mandataire = self.search_procuration(public_key) - if mandataire is not None: - print("La personne a une procuration") - return True + # Vérification si l’électeur est un mandataire. + elector = self.search_proxy_vote(card.public_key) + if elector is None: + # Ne devrait pas arriver si l’utilisateur est authentifiée. + print("L’électeur n’est pas inscrit dans la liste électorale.") + return False - print("La personne peut plus voter") - return False + else: + # Vérification si le mandataire a déjà voté pour l’électeur. + proof = self.search_proof(elector.public_key) + if proof is not None: + print("L’électeur a déjà voté par procuration pour ...") + return False - # vote = get_vote() - # generate_signature(vote, card) - # add_vote_to_urn(vote) - # print("Vote comptabilisé!") - # return True + else: + # Vérification si l’électeur a déjà voté pour lui. + proof = self.search_proof(elector.public_key) + if proof is not None: + print("L’électeur ... a déjà voté pour lui") + return False + + # Create proof of voting. + date = datetime.datetime.now() + data = bytes(str(date), 'utf-8') + elector.public_key_elector + elector.public_key_mandataire + proof_signature = self.private_key.sign( + data, + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA256() + ) + proof = Proof(date, + elector.public_key_elector, + elector.public_key_mandataire, + proof_signature) + + # Append proof and vote to the databases. + self.proof_list.append(proof) + # TODO(biloute02): shuffle when vote is inserted. + self._vote_list.append(vote) + + print("Vote comptabilisé!") + return True + + def cloture_du_vote(self) -> tuple[list[str], bytes]: + """ + End of the elction, publishes the voting results. + :return: The list of votes and its signature by the voting machine. + """ + self.end_of_election = True + + # TODO(biloute02): Binariser la base de vote pour la signature. + data = ... + vote_list_signature = self.private_key.sign( + data, + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA256() + ) + return self._vote_list, vote_list_signature def print_signing_list(self) -> None: ... @@ -162,9 +260,3 @@ class Machine: def print_emerging_list(self) -> None: ... - def cloture_du_vote(self) -> tuple[list[vote], signature]: - ... - - -machine = Machine() -machine.emerging_list \ No newline at end of file