From 74c29a5c935e84af20343c8a68058c73fb1eeb28 Mon Sep 17 00:00:00 2001 From: Faraphel Date: Wed, 3 Jul 2024 23:41:29 +0200 Subject: [PATCH] rewrote the base for the program --- source/Card.py | 53 ++++++++++++- source/Certificate.py | 50 ++++++++++++ source/Machine.py | 168 +++++++++++++++++++++++++++++++++-------- source/Pki.py | 23 ++++++ source/__main__.py | 44 +++++++++-- source/wtf/Machine2.py | 23 ++++++ source/wtf/__init__.py | 0 tests/exemple1.py | 18 +++++ tests/schema.md | 27 +++++++ 9 files changed, 370 insertions(+), 36 deletions(-) create mode 100644 source/Pki.py create mode 100644 source/wtf/Machine2.py create mode 100644 source/wtf/__init__.py create mode 100644 tests/exemple1.py create mode 100644 tests/schema.md diff --git a/source/Card.py b/source/Card.py index 6b981bf..dd6b828 100644 --- a/source/Card.py +++ b/source/Card.py @@ -1,2 +1,53 @@ +import hashlib +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.asymmetric import padding + + class Card: - ... + """ + Represent the card of an elector. + """ + + def __init__(self, name: str, pin: int): + self.name = name + + self.private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=2048, + ) + self._public_key = self.private_key.public_key() + + digest = hashes.Hash(hashes.SHA256()) + digest.update(pin.to_bytes()) + self.hashed_pin = digest.finalize() + # self.activee = False + + @property + def public_key(self): + return self._public_key + + 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. + :return: True if the received hash PIN matches the stored hashed PIN, False otherwise. + """ + return self.hashed_pin == hashed_pin + + # NOTE(Faraphel): a PKI or a banlist / whitelist shall be checked with self.public_key + + def reply_challenge(self, data: bytes = "encrypted data") -> bytes: + """ + :param data: The challeng sent by the terminal to sign. + :return: The signed challenge. + """ + signature = self.private_key.sign( + data, + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA256() + ) + return signature diff --git a/source/Certificate.py b/source/Certificate.py index 703fac3..2e11446 100644 --- a/source/Certificate.py +++ b/source/Certificate.py @@ -1,6 +1,56 @@ +from datetime import datetime + +from cryptography import x509 +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import rsa + + class Certificate: """ A certificate. Wrapper around cryptography.x509.Certificate. """ + def __init__( + self, + issuer: str, + issuer_private_key: bytes = None, + subject: str, + valid_start: datetime, + valid_end: datetime + ): + # generate a private key for the certificate + self._private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + # get the public key from it + self.public_key = self._private_key.public_key() + + # if the issuer private key is not specified, consider it a self-signed certificate + if issuer_private_key is None: + issuer_private_key = self._private_key + + # create a builder for the certificate + builder = x509.CertificateBuilder( + issuer_name=x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, issuer)]), + subject_name=x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, subject)]), + serial_number=x509.random_serial_number(), + public_key=self.public_key, + not_valid_before=valid_start, + not_valid_after=valid_end, + ) + + # create the certificate by signing it + self._certificate = builder.sign(issuer_private_key, algorithm=hashes.SHA256()) + + def is_valid(self, date: datetime, issuer_public_key: bytes) -> bool: + return ( + # check if the date is valid + self._certificate.not_valid_before <= date <= self._certificate.not_valid_after and + # check the signature of the certificate + self._certificate.verify_directly_issued_by() + ) + + + +# Exemple d'utilisation +if __name__ == "__main__": + certificate = Certificate("Example Subject") diff --git a/source/Machine.py b/source/Machine.py index 7f87505..5ff0d9f 100644 --- a/source/Machine.py +++ b/source/Machine.py @@ -1,3 +1,13 @@ +import os +from typing import Optional + +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding + +from .Card import Card + + class Machine: """ @@ -6,48 +16,146 @@ class Machine: def __init__(self): # First DB: list of people authorized to vote on this machine. # Public (que name et procuration pas empreinte) ? - self.emerging_list = [ - { - "name": "Bob", - "personne": (pub_key_bob, Enc(empreinte_bob)), - "procuration": "vide", - }, - { - "name": "Alice", - "personne": (pub_key_alice, Enc(empreinte_alice)), - "procuration": (pub_key_Eve, Enc(empreinte_eve)), # Eve peut voter pour Alice. - } - ] + + # 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 + # } + # ] # 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")), - ] + self.signing_list = {} + # [ + # ("pub_key_Alice", "pub_key_Eve", "date", signature("message")), + # ("pub_key_Bob", "pub_key_Bob", "date", signature("message")), + # ] # Third DB: votes (shuffle when vote is inserted). # - private - self._vote_list = ["A", "B", "C"] + self._vote_list = [] + # ["A", "B", "C"] - def authenticate(self): - pass + # If details during the simulation. + #def send_challenge(self) -> bytes: + # pass + #def check_challenge(self, hash_rand: bytes) -> bool: + # pass - def vote(self, card: Card) -> bool: - if not authenticate(card): - print("la carte peut pas voter dans cette machine") - return False + def search_pubkey(self, pubkey: bytes) -> list[tuple[bytes, bytes]]: + """ + Search pubkey in emerging list. + :param pubkey: Public key to search in the emerging list. + :return: All entries. + """ + + next(filter( + lambda data: data["personne"][0] == and + self.emerging_list + )) - if card_in_siging_list(card): - print("déjà vôté") - return False + def check_fingerprint(self, empreinte: bytes) -> bool: + """ - vote = get_vote() - generate_signature(vote, card) - add_vote_to_urn(vote) - print("Vote comptabilisé!") + :return: + """ return True + + def check_card(self, card: Card, pin: int) -> bool: + """ + Check if the card is valid. + :param card: + :param pin: + :return: + """ + # Public key. + 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: + return False + + # Pin + # CHECK(biloute02,faraphel): c’est bon comme ça le digest ? + digest = hashes.Hash(hashes.SHA256()) + digest.update(pin.as_bytes()) + hashed_pin = digest.finalize() + if not card.check_pin(hashed_pin): + return False + + # Is in emerging list? + + 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("") + return False + + pubkey = card.public_key + # Check pubkey + entries = self.search_pubkey(pubkey) + if not entry: + return False + + # Check fingerprint + if + return True + + def vote(self, card: Card, vote: str) -> bool: + public_key = card.public_key + + # 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 + + # 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 + + print("La personne peut plus voter") + return False + + # vote = get_vote() + # generate_signature(vote, card) + # add_vote_to_urn(vote) + # print("Vote comptabilisé!") + # return True + def print_signing_list(self) -> None: ... diff --git a/source/Pki.py b/source/Pki.py new file mode 100644 index 0000000..4d2b586 --- /dev/null +++ b/source/Pki.py @@ -0,0 +1,23 @@ +class Pki: + """ + Represent a Public Key Infrastructure + """ + + def __init__(self): + pass + +class Certificate: + """ + Represent a Certificate + """ + + def __init__(self): + # create a builder for the certificate + builder = x509.CertificateBuilder( + issuer_name=x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, issuer)]), + subject_name=x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, subject)]), + serial_number=x509.random_serial_number(), + public_key=self.public_key, + not_valid_before=valid_start, + not_valid_after=valid_end, + ) diff --git a/source/__main__.py b/source/__main__.py index 7fa7189..f9663f5 100644 --- a/source/__main__.py +++ b/source/__main__.py @@ -4,23 +4,57 @@ from cryptography import x509 from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import rsa + +# ETAT + + # generate a private key for the certificate -private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) +admin_private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096) # get the public key from it -public_key = private_key.public_key() +admin_public_key = admin_private_key.public_key() # create a builder for the certificate builder = x509.CertificateBuilder( issuer_name=x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, "vote.gouv.fr")]), subject_name=x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, "vote.gouv.fr")]), serial_number=x509.random_serial_number(), - public_key=public_key, + public_key=admin_public_key, not_valid_before=datetime.now(), not_valid_after=datetime.now() + timedelta(weeks=1), ) # create the certificate by signing it -certificate = builder.sign(private_key, algorithm=hashes.SHA256()) +admin_certificate = builder.sign(admin_private_key, algorithm=hashes.SHA256()) -print(certificate) +print(admin_certificate) + + +# BUREAU + + +# generate a private key for the certificate +machine_private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) +# get the public key from it +machine_public_key = machine_private_key.public_key() + +# create a builder for the certificate +builder = x509.CertificateBuilder( + issuer_name=x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, "vote.gouv.fr")]), + subject_name=x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, "machine.vote.gouv.fr")]), + serial_number=x509.random_serial_number(), + public_key=machine_public_key, + not_valid_before=datetime.now(), + not_valid_after=datetime.now() + timedelta(weeks=1), +) + +# create the certificate by signing it +machine_certificate = builder.sign(admin_private_key, algorithm=hashes.SHA256()) + + +print(machine_certificate) +# check that the machine +machine_certificate.verify_directly_issued_by(admin_certificate) + + +# diff --git a/source/wtf/Machine2.py b/source/wtf/Machine2.py new file mode 100644 index 0000000..fc4c70f --- /dev/null +++ b/source/wtf/Machine2.py @@ -0,0 +1,23 @@ +class Machine2: + def __init__(self, certificat): + self.certificat = certificat + self.liste_electorale = {} + self.bdd_votes = [] + self.bdd_preuves = [] + + def charger_liste_electorale(self, liste_electorale): + self.liste_electorale = liste_electorale + + def authentifier_electeur(self, electeur): + return electeur.authentifier() and electeur.carte_election.cle_publique in self.liste_electorale + + def enregistrer_vote(self, preuve_vote): + self.bdd_votes.append(preuve_vote['vote']) + self.bdd_preuves.append(preuve_vote) + + def publier_resultats(self): + return self.bdd_votes + + def fin_de_vote(self): + # Bloquer les nouveaux votes + self.bloque = True diff --git a/source/wtf/__init__.py b/source/wtf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/exemple1.py b/tests/exemple1.py new file mode 100644 index 0000000..e285edb --- /dev/null +++ b/tests/exemple1.py @@ -0,0 +1,18 @@ +import hashlib +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.asymmetric import padding + +from source.Card import Card +from source.Certificate import Certificate +from source.Machine import Machine + +Alice_card = Card("Alice", 6060) + +Machine = Machine() + + +Machine.authenticate(Alice_card, 6060, azerty) + + +Machine.vote(Alice_card) \ No newline at end of file diff --git a/tests/schema.md b/tests/schema.md new file mode 100644 index 0000000..ff92dec --- /dev/null +++ b/tests/schema.md @@ -0,0 +1,27 @@ +# Initialisation + +Il à été distribué dans chaque bureau de votes une machine qui servira d'urne + +L'Administrateur à son propre certificat admin +L'administrateur crée un certificat pour chaque machine dans les bureaux de votes + +Tout le monde récupère une carte leur permettant de voter dans leur bureau de vote respectif avec +un code pin et leurs empreinte digitale + +# Voter + +L'électeur ou son représentant se présente au bureau de vote avec la carte + +Il s'authentifie auprès de la machine + +Il choisi son candidat +La machine enregistre son choix et le fait qu'il à voter de manière indépendante. + +# Publication + +A la fin du vote, les votes sont publiés et visible par tous. + +# Difference avec le vote electronique + +On etabli la connexion TLS au debut avant de realiser l'authentification +