297 lines
11 KiB
Python
297 lines
11 KiB
Python
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
|