Implemented full end-to-end encryption and decryption of an event using trustee public and secret keys. This required modification of the NodeJS crypto server to receive post data from the crypto_rpc py methods and for combining SKs together. Additionally, the new binary voting encoding scheme has been implemented for encryption and decryption of the event. General UI improvements have been made and as well as some other bug fixes
This commit is contained in:
parent
0c354cd542
commit
e33b91f852
23 changed files with 809 additions and 446 deletions
|
@ -5,14 +5,11 @@ import json
|
|||
from os import urandom
|
||||
from celery import task
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import EmailValidator
|
||||
from django.core.mail import send_mail
|
||||
from django.conf import settings
|
||||
|
||||
from allauthdemo.polls.models import AccessKey
|
||||
from allauthdemo.polls.models import AccessKey, Ballot, Decryption, TrusteeSK, EventSK
|
||||
|
||||
from .crypto_rpc import param, combpk, addec, tally
|
||||
from .crypto_rpc import param, combpk, addec, tally, get_tally, combine_sks
|
||||
|
||||
'''
|
||||
Goal: This py file defines celery tasks that can be initiated
|
||||
|
@ -25,25 +22,43 @@ from .crypto_rpc import param, combpk, addec, tally
|
|||
# Will store the result of the initial cal to param() from .cpp_calls
|
||||
group_param = None
|
||||
|
||||
def is_valid_email(email):
|
||||
try:
|
||||
valid_email = EmailValidator(whitelist=None)
|
||||
valid_email(email)
|
||||
return True
|
||||
except ValidationError:
|
||||
return False
|
||||
|
||||
@task()
|
||||
def create_ballots(poll):
|
||||
for voter in poll.event.voters.all():
|
||||
ballot = poll.ballots.create(voter=voter, poll=poll)
|
||||
|
||||
'''
|
||||
Will generate a key for accessing either the event preparation page or the voting page
|
||||
Helper functions
|
||||
|
||||
gen_access_key - Will generate an a key for accessing either the event preparation page, voting page and decryption page
|
||||
'''
|
||||
def gen_access_key():
|
||||
return base64.urlsafe_b64encode(urandom(16)).decode('utf-8')
|
||||
|
||||
def email_trustees_dec(event):
|
||||
email_subject = "Event Ballot Decryption for '" + event.title + "'"
|
||||
|
||||
# Plain text email - this could be replaced for a HTML-based email in the future
|
||||
email_body = "Please visit the following URL to submit your trustee secret key to begin event decryption:\n\n"
|
||||
url_base = "http://" + settings.DOMAIN + "/event/" + str(event.pk) + "/decrypt/?key="
|
||||
email_body = email_body + url_base
|
||||
|
||||
for trustee in event.users_trustees.all():
|
||||
# Generate a key and create an AccessKey object
|
||||
key = gen_access_key()
|
||||
AccessKey.objects.create(user=trustee, event=event, key=key)
|
||||
|
||||
trustee.send_email(email_subject, email_body + key)
|
||||
|
||||
@task()
|
||||
def create_ballots(event):
|
||||
voters = event.voters.all()
|
||||
|
||||
for poll in event.polls.all():
|
||||
for voter in voters:
|
||||
ballot = poll.ballots.create(voter=voter, poll=poll)
|
||||
|
||||
@task()
|
||||
def create_ballots_for_poll(poll):
|
||||
for voter in poll.event.voters.all():
|
||||
ballot = poll.ballots.create(voter=voter, poll=poll)
|
||||
|
||||
|
||||
'''
|
||||
Emails an event preparation URL containing an access key for all of the trustees for an event
|
||||
'''
|
||||
|
@ -64,19 +79,31 @@ def email_trustees_prep(trustees, event):
|
|||
trustee.send_email(email_subject, email_body + key)
|
||||
|
||||
'''
|
||||
Emails the access keys for all of the voters for an event
|
||||
Emails a URL containing an access key for all of the voters for an event
|
||||
'''
|
||||
@task()
|
||||
def email_voters_a_key(voters, event):
|
||||
def email_voters_vote_url(voters, event):
|
||||
email_subject = "Voting Access for Event '" + event.title + "'"
|
||||
email_body = 'Key: '
|
||||
|
||||
# Plain text email - this could be replaced for a HTML-based email in the future
|
||||
email_body_base = "Please visit the following URL in order to vote on the event '" + event.title + "':\n\n"
|
||||
url_base = "http://" + settings.DOMAIN + "/event/" + str(event.pk) + "/poll/1/vote/?key="
|
||||
email_body_base = email_body_base + url_base
|
||||
|
||||
duration_info = "\n\nYou can vote between the following dates and times:\n"
|
||||
duration_info = duration_info + "Start: " + event.start_time_formatted_utc() + "\n"
|
||||
duration_info = duration_info + "End: " + event.end_time_formatted_utc()
|
||||
|
||||
for voter in voters:
|
||||
# Generate a key and create an AccessKey object
|
||||
key = gen_access_key()
|
||||
AccessKey.objects.create(user=voter, event=event, key=key)
|
||||
|
||||
voter.send_email(email_subject, email_body + key)
|
||||
# Update the email body to incl the access key as well as the duration information
|
||||
email_body = str(email_body_base + key)
|
||||
email_body = email_body + duration_info
|
||||
|
||||
voter.send_email(email_subject, email_body)
|
||||
|
||||
'''
|
||||
Updates the EID of an event to contain 2 event IDs: a human readable one (hr) and a crypto one (GP from param())
|
||||
|
@ -93,6 +120,83 @@ def update_EID(event):
|
|||
event.EID = json.dumps(EID)
|
||||
event.save()
|
||||
|
||||
@task()
|
||||
def event_ended(event):
|
||||
# Email all trustees to request their secret keys
|
||||
email_trustees_dec(event)
|
||||
|
||||
@task()
|
||||
def gen_event_sk_and_dec(event):
|
||||
trustee_sks = TrusteeSK.objects.filter(event=event)
|
||||
t_sks_count = len(trustee_sks)
|
||||
|
||||
# Combine SKs if there's more than one
|
||||
event_sk = None
|
||||
if t_sks_count == 1:
|
||||
event_sk = trustee_sks.get().key
|
||||
else:
|
||||
t_sks_str_list = list()
|
||||
|
||||
for t_sk in trustee_sks:
|
||||
t_sks_str_list.append(t_sk.key)
|
||||
|
||||
event_sk = combine_sks(t_sks_str_list)
|
||||
|
||||
EventSK.objects.create(event=event, key=event_sk)
|
||||
|
||||
# With the event sk created, we can decrypt the event
|
||||
decrypt_and_tally(event)
|
||||
|
||||
@task()
|
||||
def decrypt_and_tally(event):
|
||||
polls = event.polls.all()
|
||||
sk = EventSK.objects.filter(event=event).get().key
|
||||
|
||||
for i in range(len(polls)):
|
||||
poll = polls[i]
|
||||
result = str("")
|
||||
result += "{\"name\": \"" + poll.question_text + "\","
|
||||
|
||||
# get num of opts and ballots
|
||||
options = poll.options.all()
|
||||
opt_count = len(options)
|
||||
ballots = Ballot.objects.filter(poll=poll, cast=True)
|
||||
|
||||
result += "\"options\": ["
|
||||
for j in range(opt_count):
|
||||
# Collect all fragments for this opt
|
||||
frags_c1 = list()
|
||||
frags_c2 = list()
|
||||
|
||||
for ballot in ballots:
|
||||
enc_vote = ballot.encrypted_vote.get()
|
||||
|
||||
if enc_vote is not None:
|
||||
fragments = enc_vote.fragment.all()
|
||||
frags_c1.append(fragments[j].cipher_text_c1)
|
||||
frags_c2.append(fragments[j].cipher_text_c2)
|
||||
|
||||
ciphers = {
|
||||
'c1s': frags_c1,
|
||||
'c2s': frags_c2
|
||||
}
|
||||
|
||||
count = len(frags_c1)
|
||||
votes = get_tally(count, ciphers, sk, event.EID)
|
||||
|
||||
result += "{\"option\": \"" + str(options[j].choice_text) + "\", \"votes\": " + str(votes) + "}"
|
||||
|
||||
if j != (opt_count-1):
|
||||
result += ","
|
||||
|
||||
result += "]}"
|
||||
|
||||
if i != (len(polls) - 1):
|
||||
result += ","
|
||||
|
||||
poll.enc = result
|
||||
poll.save()
|
||||
|
||||
@task()
|
||||
def tally_results(event):
|
||||
for poll in event.polls.all():
|
||||
|
@ -101,39 +205,42 @@ def tally_results(event):
|
|||
decs.append(dec.text)
|
||||
amount = len(decs)
|
||||
result = tally(amount, event.EID, decs, poll.enc)
|
||||
send_mail(
|
||||
'Your Results:',
|
||||
poll.question_text + ": " + result,
|
||||
'from@example.com',
|
||||
["fake@fake.com"],
|
||||
fail_silently=False,
|
||||
)
|
||||
|
||||
# TODO: Email organisers using email_user method?
|
||||
|
||||
print(poll.question_text + ": " + result)
|
||||
|
||||
@task()
|
||||
def generate_combpk(event):
|
||||
pks = list()
|
||||
|
||||
for tkey in event.trustee_keys.all():
|
||||
pks.append(str(tkey.key))
|
||||
amount = len(pks)
|
||||
event.public_key = combpk(amount, pks)
|
||||
|
||||
event.public_key = combpk(pks)
|
||||
|
||||
event.prepared = True
|
||||
event.save()
|
||||
|
||||
@task
|
||||
def generate_enc(poll):
|
||||
c1s = list()#c1 components of ciphertexts
|
||||
c2s = list()#c1 components of ciphertexts
|
||||
# c1 and c2 components of ciphertexts
|
||||
c1s = list()
|
||||
c2s = list()
|
||||
|
||||
for ballot in poll.ballots.all():
|
||||
if (ballot.cast):
|
||||
if ballot.cast:
|
||||
c1s.append(str(ballot.cipher_text_c1))
|
||||
c2s.append(str(ballot.cipher_text_c2))
|
||||
|
||||
ciphers = {
|
||||
'c1s':c1s,
|
||||
'c2s':c2s
|
||||
'c1s': c1s,
|
||||
'c2s': c2s
|
||||
}
|
||||
amount = len(c1s)
|
||||
poll.enc = addec(amount, ciphers)
|
||||
|
||||
count = len(c1s)
|
||||
|
||||
poll.enc = addec(count, ciphers)
|
||||
poll.save()
|
||||
|
||||
|
||||
|
|
Reference in a new issue