import datetime import os import random from typing import Optional from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey, RSAPrivateKey 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, emerging_list: list[Elector]): # First model: list of people authorized to vote on this machine. # Public (que name et procuration pas empreinte) ? self._private_key: RSAPrivateKey = rsa.generate_private_key( public_exponent=65537, key_size=2048, ) self.public_key: RSAPublicKey = self._private_key.public_key() # List of electors who can vote on this machine. self.emerging_list: list[Elector] = emerging_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] = [] self.end_of_election: bool = False def search_elector(self, pubkey: RSAPublicKey) -> Optional[Elector]: """ 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. """ try: return next(filter( lambda elector: elector.public_key_elector == pubkey, self.emerging_list )) except StopIteration: return None def search_proxy_vote(self, pubkey: RSAPublicKey) -> Optional[Elector]: """ 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. """ try: return next(filter( lambda elector: elector.public_key_mandataire == pubkey, self.emerging_list )) except StopIteration: return None def search_proof(self, pubkey: RSAPublicKey) -> 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: str) -> bool: """ Check if the card is valid. :param card: :param pin: :return: """ # Public key transmitted by the card. public_key = card.public_key # Challenge to verify public key. challenge = os.urandom(128) signature = card.reply_challenge(challenge) try: public_key.verify( signature, challenge, padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH ), hashes.SHA256() ) except InvalidSignature: print("Carte invalide : échec du challenge.") return False # Pin verification. digest = hashes.Hash(hashes.SHA256()) digest.update(bytes(pin, 'utf-8')) 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: str, 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(f"L’électeur {elector.name} est sur la liste électorale et vote par procuration.") else: print(f"L’électeur {elector.name} est sur la liste électorale.") # Is the fingerprint matching? if not self.check_fingerprint(elector, fingerprint): print(f"Erreur de reconnaissance de l’empreinte digitale de {elector.name}.") return False # L’électeur est authentifié. print(f"L’électeur {elector.name} est authentifié") return True def vote(self, card: Card, vote: str) -> bool: """ 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 # Check if elector can vote and has not vot. # Vérification si l’électeur peut voter pour lui. elector = self.search_elector(card.public_key) if elector is None: # 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 else: # Vérification si le mandataire a déjà voté pour l’électeur. proof = self.search_proof(elector.public_key_elector) if proof is not None: print(f"L’électeur {elector.name} a déjà voté.") return False # C’est ok print(f"L’électeur {elector.name} vote par procuration.") else: # Vérification si l’électeur a déjà voté pour lui. proof = self.search_proof(elector.public_key_elector) if proof is not None: print(f"L’électeur {elector.name} a déjà voté.") return False # C’est ok print(f"L’électeur {elector.name} peut voter.") # Create proof of voting: date + pubkey elector + pubkey mandataire. date = datetime.datetime.now() pem_votant: bytes = elector.public_key_elector.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.PKCS1, ) pem_mandataire: bytes = elector.public_key_mandataire.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.PKCS1, ) concatenation: bytes = bytes(str(date), 'utf-8') + pem_votant + pem_mandataire # Create proof signature. proof_signature = self._private_key.sign( concatenation, 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) print(f"Preuve de vote de {elector.name} ajoutée.") # Append proof and vote to the databases. self.proof_list.append(proof) self._vote_list.append(vote) random.shuffle(self._vote_list) print(f"Vote de {elector.name} comptabilisé(e)!") return True def cloture_du_vote(self) -> tuple[list[str], bytes]: """ End of the election, publishes the voting results. :return: The list of votes and its signature by the voting machine. """ # Election is closed self.end_of_election = True data = bytes("".join(self._vote_list), 'utf-8') 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: ... def print_emerging_list(self) -> None: ... # def to_bytes(public_key_votant, public): # pem_votant: bytes = self.public_key_votant.public_bytes( # encoding=serialization.Encoding.PEM, # format=serialization.PublicFormat.PKCS1, # ) # pem_mandataire: bytes = self.public_key_mandataire.public_bytes( # encoding=serialization.Encoding.PEM, # format=serialization.PublicFormat.PKCS1, # ) # return bytes(str(self.date), 'utf-8') + pem_votant + pem_mandataire