M1-PCA-Project/source/Machine.py

297 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 nest 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 lempreinte 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 lutilisateur est authentifiée.
print("Lélecteur nest 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
# Cest 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
# Cest 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