Fully implement the vote #8
5 changed files with 157 additions and 58 deletions
|
@ -1,6 +1,7 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
from cryptography.hazmat.primitives import hashes
|
from cryptography.hazmat.primitives import hashes
|
||||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
|
||||||
from cryptography.hazmat.primitives.asymmetric import padding
|
from cryptography.hazmat.primitives.asymmetric import padding
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,24 +10,20 @@ class Card:
|
||||||
Represent the card of an elector.
|
Represent the card of an elector.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name: str, pin: int):
|
def __init__(self, name: str, pin: str):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
self.private_key = rsa.generate_private_key(
|
self._private_key = rsa.generate_private_key(
|
||||||
public_exponent=65537,
|
public_exponent=65537,
|
||||||
key_size=2048,
|
key_size=2048,
|
||||||
)
|
)
|
||||||
self._public_key = self.private_key.public_key()
|
self.public_key: RSAPublicKey = self._private_key.public_key()
|
||||||
|
|
||||||
digest = hashes.Hash(hashes.SHA256())
|
digest = hashes.Hash(hashes.SHA256())
|
||||||
digest.update(pin.to_bytes())
|
digest.update(bytes(pin, 'utf-8'))
|
||||||
self.hashed_pin = digest.finalize()
|
self.hashed_pin = digest.finalize()
|
||||||
# self.activee = False
|
# self.activee = False
|
||||||
|
|
||||||
@property
|
|
||||||
def public_key(self):
|
|
||||||
return self._public_key
|
|
||||||
|
|
||||||
def check_pin(self, hashed_pin: bytes) -> bool:
|
def check_pin(self, hashed_pin: bytes) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if the card is valid by comparing hashed PIN.
|
Check if the card is valid by comparing hashed PIN.
|
||||||
|
@ -42,7 +39,7 @@ class Card:
|
||||||
:param data: The challeng sent by the terminal to sign.
|
:param data: The challeng sent by the terminal to sign.
|
||||||
:return: The signed challenge.
|
:return: The signed challenge.
|
||||||
"""
|
"""
|
||||||
signature = self.private_key.sign(
|
signature = self._private_key.sign(
|
||||||
data,
|
data,
|
||||||
padding.PSS(
|
padding.PSS(
|
||||||
mgf=padding.MGF1(hashes.SHA256()),
|
mgf=padding.MGF1(hashes.SHA256()),
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from cryptography.exceptions import InvalidSignature
|
from cryptography.exceptions import InvalidSignature
|
||||||
from cryptography.hazmat.primitives import hashes
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey, RSAPrivateKey
|
||||||
from cryptography.hazmat.primitives.asymmetric import padding
|
from cryptography.hazmat.primitives.asymmetric import padding
|
||||||
|
|
||||||
from .Card import Card
|
from .Card import Card
|
||||||
|
@ -17,15 +20,15 @@ class Machine:
|
||||||
Represent a machine used to vote
|
Represent a machine used to vote
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, emerging_list: list[Proof]):
|
def __init__(self, emerging_list: list[Elector]):
|
||||||
# First model: list of people authorized to vote on this machine.
|
# First model: list of people authorized to vote on this machine.
|
||||||
# Public (que name et procuration pas empreinte) ?
|
# Public (que name et procuration pas empreinte) ?
|
||||||
|
|
||||||
self.private_key = rsa.generate_private_key(
|
self._private_key: RSAPrivateKey = rsa.generate_private_key(
|
||||||
public_exponent=65537,
|
public_exponent=65537,
|
||||||
key_size=2048,
|
key_size=2048,
|
||||||
)
|
)
|
||||||
self._public_key = self.private_key.public_key()
|
self.public_key: RSAPublicKey = self._private_key.public_key()
|
||||||
|
|
||||||
# List of electors who can vote on this machine.
|
# List of electors who can vote on this machine.
|
||||||
self.emerging_list: list[Elector] = emerging_list
|
self.emerging_list: list[Elector] = emerging_list
|
||||||
|
@ -40,7 +43,7 @@ class Machine:
|
||||||
|
|
||||||
self.end_of_election: bool = False
|
self.end_of_election: bool = False
|
||||||
|
|
||||||
def search_elector(self, pubkey: bytes) -> Optional[Elector]:
|
def search_elector(self, pubkey: RSAPublicKey) -> Optional[Elector]:
|
||||||
"""
|
"""
|
||||||
Search public key of elector in the emerging list.
|
Search public key of elector in the emerging list.
|
||||||
:param pubkey: Public key of elector to search in the emerging list.
|
:param pubkey: Public key of elector to search in the emerging list.
|
||||||
|
@ -54,7 +57,7 @@ class Machine:
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def search_proxy_vote(self, pubkey: bytes) -> Optional[Elector]:
|
def search_proxy_vote(self, pubkey: RSAPublicKey) -> Optional[Elector]:
|
||||||
"""
|
"""
|
||||||
Search the elector with the public key registered as the proxy vote.
|
Search the elector with the public key registered as the proxy vote.
|
||||||
:param pubkey: Public key of the mandataire who can proxy vote.
|
:param pubkey: Public key of the mandataire who can proxy vote.
|
||||||
|
@ -68,7 +71,7 @@ class Machine:
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def search_proof(self, pubkey: bytes) -> Optional[Proof]:
|
def search_proof(self, pubkey: RSAPublicKey) -> Optional[Proof]:
|
||||||
"""
|
"""
|
||||||
Search pubkey in the signature list
|
Search pubkey in the signature list
|
||||||
:param pubkey: Public to search in the signature list.
|
:param pubkey: Public to search in the signature list.
|
||||||
|
@ -100,7 +103,7 @@ class Machine:
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_card(self, card: Card, pin: int) -> bool:
|
def check_card(self, card: Card, pin: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if the card is valid.
|
Check if the card is valid.
|
||||||
:param card:
|
:param card:
|
||||||
|
@ -108,7 +111,7 @@ class Machine:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
# Public key transmitted by the card.
|
# Public key transmitted by the card.
|
||||||
public_key = card.public_key()
|
public_key = card.public_key
|
||||||
|
|
||||||
# Challenge to verify public key.
|
# Challenge to verify public key.
|
||||||
challenge = os.urandom(128)
|
challenge = os.urandom(128)
|
||||||
|
@ -130,7 +133,7 @@ class Machine:
|
||||||
|
|
||||||
# Pin verification.
|
# Pin verification.
|
||||||
digest = hashes.Hash(hashes.SHA256())
|
digest = hashes.Hash(hashes.SHA256())
|
||||||
digest.update(pin.to_bytes())
|
digest.update(bytes(pin, 'utf-8'))
|
||||||
hashed_pin = digest.finalize()
|
hashed_pin = digest.finalize()
|
||||||
if not card.check_pin(hashed_pin):
|
if not card.check_pin(hashed_pin):
|
||||||
print("Mot de passe de la carte refusé.")
|
print("Mot de passe de la carte refusé.")
|
||||||
|
@ -139,7 +142,7 @@ class Machine:
|
||||||
# Card has been verified.
|
# Card has been verified.
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def authenticate(self, card: Card, pin: int, fingerprint: str) -> bool:
|
def authenticate(self, card: Card, pin: str, fingerprint: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Authenticate a person with its card and its fingerprint.
|
Authenticate a person with its card and its fingerprint.
|
||||||
:param card: Elector card.
|
:param card: Elector card.
|
||||||
|
@ -149,7 +152,7 @@ class Machine:
|
||||||
"""
|
"""
|
||||||
# Check if election is not ended.
|
# Check if election is not ended.
|
||||||
if self.end_of_election:
|
if self.end_of_election:
|
||||||
print("L’élection est terminée")
|
print("L’élection est terminée.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check if card is valid.
|
# Check if card is valid.
|
||||||
|
@ -164,14 +167,16 @@ class Machine:
|
||||||
if elector is None:
|
if elector is None:
|
||||||
print("L’électeur n’est pas inscrit dans la liste électorale.")
|
print("L’électeur n’est pas inscrit dans la liste électorale.")
|
||||||
return False
|
return False
|
||||||
print("L’électeur peut voter")
|
print(f"L’électeur {elector.name} vote par procuration.")
|
||||||
|
print(f"L’électeur {elector.name} peut voter.")
|
||||||
|
|
||||||
# Is the fingerprint matching?
|
# Is the fingerprint matching?
|
||||||
if not self.check_fingerprint(elector, fingerprint):
|
if not self.check_fingerprint(elector, fingerprint):
|
||||||
print("Erreur de reconnaissance de l’empreinte digitale.")
|
print(f"Erreur de reconnaissance de l’empreinte digitale de {elector.name}.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# L’électeur est authentifié.
|
# L’électeur est authentifié.
|
||||||
|
print(f"Électeur {elector.name} authentifié")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def vote(self, card: Card, vote: str) -> bool:
|
def vote(self, card: Card, vote: str) -> bool:
|
||||||
|
@ -183,9 +188,11 @@ class Machine:
|
||||||
"""
|
"""
|
||||||
# Check if election is not ended.
|
# Check if election is not ended.
|
||||||
if self.end_of_election:
|
if self.end_of_election:
|
||||||
print("L’élection est terminée")
|
print("L’élection est terminée.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Check if elector can vote and has not vot.
|
||||||
|
|
||||||
# Vérification si l’électeur peut voter pour lui.
|
# Vérification si l’électeur peut voter pour lui.
|
||||||
elector = self.search_elector(card.public_key)
|
elector = self.search_elector(card.public_key)
|
||||||
if elector is None:
|
if elector is None:
|
||||||
|
@ -199,23 +206,39 @@ class Machine:
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Vérification si le mandataire a déjà voté pour l’électeur.
|
# Vérification si le mandataire a déjà voté pour l’électeur.
|
||||||
proof = self.search_proof(elector.public_key)
|
proof = self.search_proof(elector.public_key_elector)
|
||||||
if proof is not None:
|
if proof is not None:
|
||||||
print("L’électeur a déjà voté par procuration pour ...")
|
print(f"L’électeur {elector.name} a déjà voté.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# C’est ok
|
||||||
|
print(f"L’électeur {elector.name} vote par procuration.")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Vérification si l’électeur a déjà voté pour lui.
|
# Vérification si l’électeur a déjà voté pour lui.
|
||||||
proof = self.search_proof(elector.public_key)
|
proof = self.search_proof(elector.public_key_elector)
|
||||||
if proof is not None:
|
if proof is not None:
|
||||||
print("L’électeur ... a déjà voté pour lui")
|
print(f"L’électeur {elector.name} a déjà voté.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Create proof of voting.
|
# C’est ok
|
||||||
|
print(f"L’électeur {elector.name} peut voter.")
|
||||||
|
|
||||||
|
# Create proof of voting: date + pubkey elector + pubkey mandataire.
|
||||||
date = datetime.datetime.now()
|
date = datetime.datetime.now()
|
||||||
data = bytes(str(date), 'utf-8') + elector.public_key_elector + elector.public_key_mandataire
|
pem_votant: bytes = elector.public_key_elector.public_bytes(
|
||||||
proof_signature = self.private_key.sign(
|
encoding=serialization.Encoding.PEM,
|
||||||
data,
|
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(
|
padding.PSS(
|
||||||
mgf=padding.MGF1(hashes.SHA256()),
|
mgf=padding.MGF1(hashes.SHA256()),
|
||||||
salt_length=padding.PSS.MAX_LENGTH
|
salt_length=padding.PSS.MAX_LENGTH
|
||||||
|
@ -226,25 +249,26 @@ class Machine:
|
||||||
elector.public_key_elector,
|
elector.public_key_elector,
|
||||||
elector.public_key_mandataire,
|
elector.public_key_mandataire,
|
||||||
proof_signature)
|
proof_signature)
|
||||||
|
print(f"Preuve de vote de {elector.name} ajoutée.")
|
||||||
|
|
||||||
# Append proof and vote to the databases.
|
# Append proof and vote to the databases.
|
||||||
self.proof_list.append(proof)
|
self.proof_list.append(proof)
|
||||||
# TODO(biloute02): shuffle when vote is inserted.
|
|
||||||
self._vote_list.append(vote)
|
self._vote_list.append(vote)
|
||||||
|
random.shuffle(self._vote_list)
|
||||||
|
|
||||||
print("Vote comptabilisé!")
|
print(f"Vote de {elector.name} comptabilisé!")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def cloture_du_vote(self) -> tuple[list[str], bytes]:
|
def cloture_du_vote(self) -> tuple[list[str], bytes]:
|
||||||
"""
|
"""
|
||||||
End of the elction, publishes the voting results.
|
End of the election, publishes the voting results.
|
||||||
:return: The list of votes and its signature by the voting machine.
|
:return: The list of votes and its signature by the voting machine.
|
||||||
"""
|
"""
|
||||||
|
# Election is closed
|
||||||
self.end_of_election = True
|
self.end_of_election = True
|
||||||
|
|
||||||
# TODO(biloute02): Binariser la base de vote pour la signature.
|
data = bytes("".join(self._vote_list), 'utf-8')
|
||||||
data = ...
|
vote_list_signature = self._private_key.sign(
|
||||||
vote_list_signature = self.private_key.sign(
|
|
||||||
data,
|
data,
|
||||||
padding.PSS(
|
padding.PSS(
|
||||||
mgf=padding.MGF1(hashes.SHA256()),
|
mgf=padding.MGF1(hashes.SHA256()),
|
||||||
|
@ -260,3 +284,13 @@ class Machine:
|
||||||
def print_emerging_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
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
from dataclasses import dataclass
|
from typing import Optional
|
||||||
|
|
||||||
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Elector:
|
class Elector:
|
||||||
# [
|
# [
|
||||||
# {
|
# {
|
||||||
# "name": "Bob",
|
# "name": "Bob",
|
||||||
# "votant": (pub_key_bob, Hash(empreinte_bob)),
|
# "votant": (pub_key_bob, Hash(empreinte_bob)),
|
||||||
# "mandataire": None,
|
# "mandataire": (b"", b"")
|
||||||
# },
|
# },
|
||||||
# {
|
# {
|
||||||
# "name": "Alice",
|
# "name": "Alice",
|
||||||
|
@ -16,11 +17,30 @@ class Elector:
|
||||||
# }
|
# }
|
||||||
# ]
|
# ]
|
||||||
|
|
||||||
name: str
|
def __init__(self,
|
||||||
public_key_elector: bytes
|
name: str,
|
||||||
hashed_fingerprint_elector: bytes
|
public_key_elector: RSAPublicKey,
|
||||||
public_key_mandataire: bytes = b""
|
fingerprint_elector: str,
|
||||||
hashed_fingerprint_mandataire: bytes = b""
|
public_key_mandataire: Optional[RSAPublicKey] = None,
|
||||||
|
fingerprint_mandataire: Optional[str] = None):
|
||||||
|
self.name = name
|
||||||
|
self.public_key_elector = public_key_elector
|
||||||
|
|
||||||
|
if public_key_mandataire is None:
|
||||||
|
self.public_key_mandataire = public_key_elector
|
||||||
|
else:
|
||||||
|
self.public_key_mandataire = public_key_mandataire
|
||||||
|
|
||||||
|
digest = hashes.Hash(hashes.SHA256())
|
||||||
|
digest.update(bytes(fingerprint_elector, 'utf-8'))
|
||||||
|
self.hashed_fingerprint_elector: bytes = digest.finalize()
|
||||||
|
|
||||||
|
if fingerprint_mandataire is None:
|
||||||
|
self.hashed_fingerprint_mandataire = self.hashed_fingerprint_elector
|
||||||
|
else:
|
||||||
|
digest = hashes.Hash(hashes.SHA256())
|
||||||
|
digest.update(bytes(fingerprint_mandataire, 'utf-8'))
|
||||||
|
self.hashed_fingerprint_mandataire: bytes = digest.finalize()
|
||||||
|
|
||||||
def set_mandataire(self, public_key: bytes, hashed_fingerprint: bytes):
|
def set_mandataire(self, public_key: bytes, hashed_fingerprint: bytes):
|
||||||
self.public_key_mandataire = public_key
|
self.public_key_mandataire = public_key
|
||||||
|
|
|
@ -1,19 +1,24 @@
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
|
||||||
|
|
||||||
import struct
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Proof:
|
class Proof:
|
||||||
|
|
||||||
date: datetime
|
date: datetime
|
||||||
public_key_votant: bytes
|
public_key_votant: RSAPublicKey
|
||||||
public_key_mandataire: bytes
|
public_key_mandataire: RSAPublicKey
|
||||||
proof_signature: bytes
|
proof_signature: bytes
|
||||||
|
|
||||||
def to_bytes(self):
|
# def to_bytes(public_key_votant, public):
|
||||||
struct.pack("<d...", self.date.timestamp(), self.public_key_votant, self.public_key_mandataire)
|
# pem_votant: bytes = self.public_key_votant.public_bytes(
|
||||||
|
# encoding=serialization.Encoding.PEM,
|
||||||
#bytes(str(ici), 'utf-8')
|
# format=serialization.PublicFormat.PKCS1,
|
||||||
bytes(self.date.timestamp())
|
# )
|
||||||
|
# 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
|
||||||
|
|
|
@ -4,15 +4,58 @@ from cryptography.hazmat.primitives.asymmetric import rsa
|
||||||
from cryptography.hazmat.primitives.asymmetric import padding
|
from cryptography.hazmat.primitives.asymmetric import padding
|
||||||
|
|
||||||
from source.Card import Card
|
from source.Card import Card
|
||||||
from source.Certificate import Certificate
|
from source.models.Elector import Elector
|
||||||
|
#from source.Certificate import Certificate
|
||||||
from source.Machine import Machine
|
from source.Machine import Machine
|
||||||
|
|
||||||
Alice_card = Card("Alice", 6060)
|
# Création des cartes
|
||||||
|
|
||||||
Machine = Machine()
|
alice_card = Card("Alice", "6060")
|
||||||
|
bob_card = Card("Bob", "0100")
|
||||||
|
eve_card = Card("Eve", "0008")
|
||||||
|
print(f"carte alice pubkey: {alice_card.public_key}")
|
||||||
|
print("Cartes d’élections créent")
|
||||||
|
|
||||||
|
input()
|
||||||
|
|
||||||
|
# Création de la liste électorale
|
||||||
|
|
||||||
|
# Eve peut voter pour Alice
|
||||||
|
alice_elector = Elector(name="Alice",
|
||||||
|
public_key_elector=alice_card.public_key,
|
||||||
|
fingerprint_elector="empreinteA",
|
||||||
|
public_key_mandataire=eve_card.public_key,
|
||||||
|
fingerprint_mandataire="empreinteE")
|
||||||
|
bob_elector = Elector("Bob", bob_card.public_key, "empreinteB")
|
||||||
|
eve_elector = Elector("Eve", eve_card.public_key, "empreinteE")
|
||||||
|
emerging_list = [alice_elector, bob_elector, eve_elector]
|
||||||
|
print(f"alice name: {alice_elector.name}")
|
||||||
|
print(f"alice pubkey: {alice_elector.public_key_elector}")
|
||||||
|
print(f"alice pubkey mandataire: {alice_elector.public_key_mandataire}")
|
||||||
|
print(f"alice empreinte: {alice_elector.hashed_fingerprint_elector}")
|
||||||
|
print(f"alice empreinte mandataire: {alice_elector.hashed_fingerprint_mandataire}")
|
||||||
|
print(f"liste d’émargement: {emerging_list}")
|
||||||
|
print("Liste électorale crée.")
|
||||||
|
|
||||||
|
input()
|
||||||
|
|
||||||
|
# Création de la machine
|
||||||
|
|
||||||
|
machine = Machine(emerging_list)
|
||||||
|
print(f"machine pubkey : {machine._public_key.public_bytes()}")
|
||||||
|
print("Machine pour voter crée")
|
||||||
|
|
||||||
|
input()
|
||||||
|
|
||||||
|
|
||||||
Machine.authenticate(Alice_card, 6060, "azerty")
|
|
||||||
|
# Authentification
|
||||||
|
|
||||||
|
# Vote
|
||||||
|
|
||||||
|
# Publication des résultats
|
||||||
|
|
||||||
|
#Machine.authenticate(Alice_card, 6060, "azerty")
|
||||||
|
|
||||||
|
|
||||||
Machine.vote(Alice_card)
|
#Machine.vote(Alice_card)
|
Loading…
Reference in a new issue