Merge pull request 'Fully implement the vote' (#8) from base into main

Reviewed-on: #8
This commit is contained in:
faraphel 2024-07-05 09:09:11 +00:00
commit 9404c4e964
41 changed files with 1001 additions and 6 deletions

2
.gitignore vendored
View file

@ -1,3 +1,5 @@
# IDE # IDE
.idea/ .idea/
!.idea/runConfigurations/ !.idea/runConfigurations/
subject.pdf

0
cli/__init__.py Normal file
View file

31
cli/__main__.py Normal file
View file

@ -0,0 +1,31 @@
import argparse
from cli import role
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="role")
role.admin.load_parse(subparsers)
role.client.load_parse(subparsers)
role.machine.load_parse(subparsers)
arguments = parser.parse_args()
if not arguments:
parser.print_help()
exit(1)
match arguments.role:
case "admin":
role.admin.run(parser, arguments)
case "machine":
role.machine.run(parser, arguments)
case "client":
role.client.run(parser, arguments)
# if the role is unknown, show the usage
case _:
parser.print_usage()

3
cli/role/__init__.py Normal file
View file

@ -0,0 +1,3 @@
from . import admin
from . import client
from . import machine

View file

@ -0,0 +1,2 @@
from .argparse import load_parse
from .run import run

View file

@ -0,0 +1,2 @@
from . import create
from . import init

View file

@ -0,0 +1,2 @@
from .argparse import load_parse
from .run import run

View file

@ -0,0 +1,11 @@
import argparse
from . import create_role
def load_parse(subparsers):
subparser: argparse.ArgumentParser = subparsers.add_parser("create")
subsubparsers = subparser.add_subparsers(dest="create_role")
create_role.client.load_parse(subsubparsers)
create_role.machine.load_parse(subsubparsers)

View file

@ -0,0 +1,2 @@
from . import machine
from . import client

View file

@ -0,0 +1,2 @@
from .argparse import load_parse
from .run import run

View file

@ -0,0 +1,8 @@
import argparse
def load_parse(subparsers):
subparser: argparse.ArgumentParser = subparsers.add_parser("client")
subparser.add_argument("-u", "--username", dest="username", type=str, required=True)
subparser.add_argument("-p", "--password", dest="password", type=str, required=True)

View file

@ -0,0 +1,39 @@
import argparse
from pathlib import Path
import bcrypt
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
def run(parser: argparse.ArgumentParser, arguments: argparse.Namespace):
# TODO(Faraphel): should NOT be named "client"
print("creating new client...")
directory_client = Path(f"./assets/client/{arguments.username}/")
if directory_client.exists():
raise ValueError("This client already exists !")
directory_client.mkdir(parents=True)
# Generate a private key
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend(),
)
(directory_client / "private.key").write_bytes(
private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.OpenSSH,
encryption_algorithm=serialization.BestAvailableEncryption(arguments.password.encode()),
)
)
(directory_client / "public.key").write_bytes(
private_key.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.OpenSSH,
)
)

View file

@ -0,0 +1,2 @@
from .argparse import load_parse
from .run import run

View file

@ -0,0 +1,7 @@
import argparse
def load_parse(subparsers):
subparser: argparse.ArgumentParser = subparsers.add_parser("machine")
subparser.add_argument("-n", "--name", dest="name", type=str, required=True)

View file

@ -0,0 +1,67 @@
import argparse
from datetime import datetime, timedelta
from pathlib import Path
from cryptography import x509
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import rsa
def run(parser: argparse.ArgumentParser, arguments: argparse.Namespace):
print("creating a new machine...")
directory_admin = Path("./assets/admin/")
if not directory_admin.exists():
raise ValueError("Can't find the admin. Have you initialized it ?")
directory_machine = Path(f"./assets/machine/{arguments.name}/")
if directory_machine.exists():
raise ValueError("This machine already exists !")
directory_machine.mkdir(parents=True)
# Generate a private key
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
(directory_machine / "private.key").write_bytes(
private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
)
certificate_admin = x509.load_pem_x509_certificate(
(directory_admin / "certificate.pem").read_bytes()
)
private_key_admin = serialization.load_pem_private_key(
(directory_admin / "private.key").read_bytes(),
password=None
)
# Generate a self-signed root certificate
subject = x509.Name([
x509.NameAttribute(x509.NameOID.COUNTRY_NAME, "FR"),
x509.NameAttribute(x509.NameOID.STATE_OR_PROVINCE_NAME, "Province"),
x509.NameAttribute(x509.NameOID.LOCALITY_NAME, "Locality"),
x509.NameAttribute(x509.NameOID.ORGANIZATION_NAME, "Organization"),
x509.NameAttribute(x509.NameOID.COMMON_NAME, "domain.com"),
])
certificate = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(certificate_admin.subject)
.public_key(private_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.now() - timedelta(days=1)) # TODO(Faraphel): settings
.not_valid_after(datetime.now() + timedelta(days=365)) # TODO(Faraphel): settings
.sign(private_key_admin, hashes.SHA256())
)
# Save the root certificate to a file
(directory_machine / "certificate.pem").write_bytes(certificate.public_bytes(serialization.Encoding.PEM))
print("machine created !")

View file

@ -0,0 +1,15 @@
import argparse
from . import create_role
def run(parser: argparse.ArgumentParser, arguments: argparse.Namespace):
match arguments.create_role:
case "client":
create_role.client.run(parser, arguments)
case "machine":
create_role.machine.run(parser, arguments)
# if the subcommand is not known, show the help
case _:
parser.print_usage()

View file

@ -0,0 +1,2 @@
from .argparse import load_parse
from .run import run

View file

@ -0,0 +1,5 @@
import argparse
def load_parse(subparsers):
subparser: argparse.ArgumentParser = subparsers.add_parser("init")

View file

@ -0,0 +1,53 @@
import argparse
from datetime import datetime, timedelta
from pathlib import Path
from cryptography import x509
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import rsa
def run(parser: argparse.ArgumentParser, arguments: argparse.Namespace):
print("initializing the admin...")
directory = Path(f"./assets/admin/")
if directory.exists():
raise ValueError("The admin already exists !")
directory.mkdir(parents=True)
# Generate a private key
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=4096,
)
(directory / "private.key").write_bytes(
private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
)
# Generate a self-signed root certificate
subject = x509.Name([
x509.NameAttribute(x509.NameOID.COUNTRY_NAME, "FR"),
x509.NameAttribute(x509.NameOID.STATE_OR_PROVINCE_NAME, "Province"),
x509.NameAttribute(x509.NameOID.LOCALITY_NAME, "Locality"),
x509.NameAttribute(x509.NameOID.ORGANIZATION_NAME, "Organization"),
x509.NameAttribute(x509.NameOID.COMMON_NAME, "domain.com"),
])
certificate = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(subject)
.public_key(private_key.public_key())
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.now() - timedelta(days=1)) # TODO(Faraphel): settings
.not_valid_after(datetime.now() + timedelta(days=365)) # TODO(Faraphel): settings
.sign(private_key, hashes.SHA256())
)
# Save the root certificate to a file
(directory / "certificate.pem").write_bytes(certificate.public_bytes(serialization.Encoding.PEM))

View file

@ -0,0 +1,11 @@
import argparse
from . import action
def load_parse(subparsers):
subparser: argparse.ArgumentParser = subparsers.add_parser("admin")
subsubparsers = subparser.add_subparsers(dest="action")
action.create.load_parse(subsubparsers)
action.init.load_parse(subsubparsers)

15
cli/role/admin/run.py Normal file
View file

@ -0,0 +1,15 @@
import argparse
from . import action
def run(parser: argparse.ArgumentParser, arguments: argparse.Namespace):
match arguments.action:
case "create":
action.create.run(parser, arguments)
case "init":
action.init.run(parser, arguments)
# if the subcommand is not known, show the help
case _:
parser.print_usage()

View file

@ -0,0 +1,2 @@
from .argparse import load_parse
from .run import run

View file

@ -0,0 +1,2 @@
from . import vote
from .argparse import load_parse

View file

@ -0,0 +1,9 @@
import argparse
def load_parse(subparsers):
subparser: argparse.ArgumentParser = subparsers.add_parser("vote")
subparser.add_argument("-u", "--username", dest="username", type=str, required=True)
subparser.add_argument("-p", "--password", dest="password", type=str, required=True)
subparser.add_argument("-v", "--vote", dest="vote", type=str, required=True)

View file

@ -0,0 +1,23 @@
import argparse
import json
import ssl
import socket
def run(parser: argparse.ArgumentParser, arguments: argparse.Namespace):
context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT)
context.load_verify_locations(arguments.certificate)
context.check_hostname = False
with (
socket.create_connection((arguments.hostname, arguments.port)) as sock_client,
context.wrap_socket(sock_client, server_hostname=arguments.hostname) as ssl_sock_client
):
data = {
"username": arguments.username,
"password": arguments.password,
"vote": arguments.vote,
}
ssl_sock_client.send(json.dumps(data).encode())
print("message sent.")

View file

@ -0,0 +1,15 @@
import argparse
from pathlib import Path
from . import action
def load_parse(subparsers):
subparser: argparse.ArgumentParser = subparsers.add_parser("client")
subparser.add_argument("-H", "--host", dest="hostname", type=str, default="127.0.0.1")
subparser.add_argument("-P", "--port", dest="port", type=int, default=57823)
subparser.add_argument("-cc", "--certificate", dest="certificate", type=Path, required=False)
subsubparsers = subparser.add_subparsers(dest="action")
action.load_parse(subsubparsers)

13
cli/role/client/run.py Normal file
View file

@ -0,0 +1,13 @@
import argparse
from . import action
def run(parser: argparse.ArgumentParser, arguments: argparse.Namespace):
match arguments.action:
case "vote":
action.vote.run(parser, arguments)
# if the subcommand is not known, show the help
case _:
parser.print_usage()

View file

@ -0,0 +1,2 @@
from .argparse import load_parse
from .run import run

View file

@ -0,0 +1,11 @@
import argparse
from pathlib import Path
def load_parse(subparsers):
subparser: argparse.ArgumentParser = subparsers.add_parser("machine")
subparser.add_argument("-H", "--host", dest="hostname", type=str, default="0.0.0.0")
subparser.add_argument("-p", "--port", dest="port", type=int, default=57823)
subparser.add_argument("-cc", "--certificate", dest="certificate", type=Path, required=True)
subparser.add_argument("-ck", "--private-key", dest="private_key", type=Path, required=True)

23
cli/role/machine/run.py Normal file
View file

@ -0,0 +1,23 @@
import argparse
import json
def run(parser: argparse.ArgumentParser, arguments: argparse.Namespace):
import ssl
import socket
context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(arguments.certificate, arguments.private_key)
with (
socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock_server,
context.wrap_socket(sock_server, server_side=True) as ssl_sock_server
):
ssl_sock_server.bind((arguments.hostname, arguments.port))
ssl_sock_server.listen(5)
while True:
print("waiting for a connection...")
connect, address = ssl_sock_server.accept()
data = json.loads(connect.recv())
print(data)

View file

@ -1 +1 @@
cryptography cryptography

48
source/Card.py Normal file
View file

@ -0,0 +1,48 @@
import hashlib
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
from cryptography.hazmat.primitives.asymmetric import padding
class Card:
"""
Represent the card of an elector.
"""
def __init__(self, pin: str):
self._private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
self.public_key: RSAPublicKey = self._private_key.public_key()
digest = hashes.Hash(hashes.SHA256())
digest.update(bytes(pin, 'utf-8'))
self.hashed_pin = digest.finalize()
# self.activee = False
def check_pin(self, hashed_pin: bytes) -> bool:
"""
Check if the card is valid by comparing hashed PIN.
:param hashed_pin: 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

56
source/Certificate.py Normal file
View file

@ -0,0 +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")

297
source/Machine.py Normal file
View file

@ -0,0 +1,297 @@
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

View file

@ -4,23 +4,54 @@ from cryptography import x509
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
# ETAT
# generate a private key for the certificate # 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 # 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 # create a builder for the certificate
builder = x509.CertificateBuilder( builder = x509.CertificateBuilder(
issuer_name=x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, "vote.gouv.fr")]), 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")]), subject_name=x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, "vote.gouv.fr")]),
serial_number=x509.random_serial_number(), serial_number=x509.random_serial_number(),
public_key=public_key, public_key=admin_public_key,
not_valid_before=datetime.now(), not_valid_before=datetime.now(),
not_valid_after=datetime.now() + timedelta(weeks=1), not_valid_after=datetime.now() + timedelta(weeks=1),
) )
# create the certificate by signing it # 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)

47
source/models/Elector.py Normal file
View file

@ -0,0 +1,47 @@
from typing import Optional
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
class Elector:
# [
# {
# "name": "Bob",
# "votant": (pub_key_bob, Hash(empreinte_bob)),
# "mandataire": (b"", b"")
# },
# {
# "name": "Alice",
# "votant": (pub_key_alice, Hash(empreinte_alice)),
# "mandataire": (pub_key_Eve, Hash(empreinte_eve)), # Eve peut voter pour Alice.
# }
# ]
def __init__(self,
name: str,
public_key_elector: RSAPublicKey,
fingerprint_elector: str,
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):
self.public_key_mandataire = public_key
self.hashed_fingerprint_mandataire = hashed_fingerprint

24
source/models/Proof.py Normal file
View file

@ -0,0 +1,24 @@
from dataclasses import dataclass
from datetime import datetime
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
@dataclass
class Proof:
date: datetime
public_key_elector: RSAPublicKey
public_key_mandataire: RSAPublicKey
proof_signature: bytes
# 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

View file

82
tests/exemple1.py Normal file
View file

@ -0,0 +1,82 @@
from source.Card import Card
from source.models.Elector import Elector
from source.Machine import Machine
# Vote électronique
# 3 personnes
alice = {
"name": "Alice",
"password": "6060",
"empreinte": "empreinteA",
"vote": "Riri",
}
bob = {
"name": "Bob",
"password": "0100",
"empreinte": "empreinteB",
"vote": "Toto",
}
eve = {
"name": "Eve",
"password": "008",
"empreinte": "empreinteE",
"vote": "Jack",
}
personnes = [alice, bob, eve]
# Création des cartes
alice["card"] = Card(alice["password"])
bob["card"] = Card(bob["password"])
eve["card"] = Card(eve["password"])
print(f"Liste de cartes : {[personne['card'] for personne in personnes]}")
print("Cartes délections créées.")
print()
# Création de la liste électorale
# Eve peut voter pour Alice
alice_elector = Elector(name=alice["name"],
public_key_elector=alice["card"].public_key, fingerprint_elector=alice["empreinte"],
public_key_mandataire=eve["card"].public_key, fingerprint_mandataire=eve["empreinte"])
bob_elector = Elector(name=bob["name"],
public_key_elector=bob["card"].public_key, fingerprint_elector=bob["empreinte"])
emerging_list = [alice_elector, bob_elector]
print(f"Liste démargement: {emerging_list}")
print("Liste électorale créée.")
print()
# Création de la machine
machine = Machine(emerging_list)
print(f"Machine pubkey : {machine.public_key}")
print("Machine pour voter créée")
print()
# Votes des personnes
alice["password"] = "fleur"
for personne in personnes:
# Authentification
print()
print(f"La personne {personne["name"]} sauthentifie avec le mdp {personne["password"]} et lempreinte {personne["empreinte"]}.")
if not machine.authenticate(personne["card"], personne["password"], personne["empreinte"]):
continue
# Vote
print()
print(f"La personne {personne["name"]} va voter pour {personne["vote"]}")
if not machine.vote(personne["card"], personne["vote"]):
continue
# Publication des résultats
votes, sig_votes = machine.cloture_du_vote()
print()
print(votes)

2
tests/exemple2.py Normal file
View file

@ -0,0 +1,2 @@
# Vote à distance

27
tests/schema.md Normal file
View file

@ -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