Going home

This commit is contained in:
Rumperuu 2018-08-16 22:17:16 +01:00
parent 77508479b4
commit 89533a54b1
9 changed files with 256 additions and 151 deletions

View file

@ -208,6 +208,11 @@ class Ballot(models.Model):
cast = models.BooleanField(default=False) cast = models.BooleanField(default=False)
class EncBallot(models.Model):
handle = models.CharField(primary_key=True, default=uuid.uuid4, editable=False, max_length=255)
ballot = models.CharField(max_length=4096)
# Implements the new binary encoding scheme # Implements the new binary encoding scheme
class EncryptedVote(models.Model): class EncryptedVote(models.Model):
ballot = models.ForeignKey(Ballot, on_delete=models.CASCADE, related_name="encrypted_vote") ballot = models.ForeignKey(Ballot, on_delete=models.CASCADE, related_name="encrypted_vote")

View file

@ -20,5 +20,6 @@ urlpatterns = [
url(r'^(?P<event_id>[0-9a-f-]+)/prepare/$', views.event_trustee_setup, name='prepare-event'), url(r'^(?P<event_id>[0-9a-f-]+)/prepare/$', views.event_trustee_setup, name='prepare-event'),
url(r'^(?P<event_id>[0-9a-f-]+)/poll/(?P<poll_id>[0-9a-f-]+)/vote/$', views.event_vote, name='event-vote'), url(r'^(?P<event_id>[0-9a-f-]+)/poll/(?P<poll_id>[0-9a-f-]+)/vote/$', views.event_vote, name='event-vote'),
url(r'^(?P<event_id>[0-9a-f-]+)/create/poll/$', login_required(views.manage_questions), name='create-poll'), url(r'^(?P<event_id>[0-9a-f-]+)/create/poll/$', login_required(views.manage_questions), name='create-poll'),
url(r'^(?P<event_id>[0-9a-f-]+)/poll/(?P<poll_id>[0-9a-f-]+)/edit$', login_required(views.edit_poll), name='edit-poll') url(r'^(?P<event_id>[0-9a-f-]+)/poll/(?P<poll_id>[0-9a-f-]+)/edit$', login_required(views.edit_poll), name='edit-poll'),
url(r'^audit/$', views.vote_audit, name='vote_audit')
] ]

View file

@ -1,6 +1,8 @@
import urllib import urllib
import urllib2 import urllib2
import json import json
import logging
import base64
from django.contrib import messages from django.contrib import messages
from django.http import HttpResponseRedirect, HttpResponse, Http404 from django.http import HttpResponseRedirect, HttpResponse, Http404
@ -11,7 +13,7 @@ from django.views import generic
from django.conf import settings from django.conf import settings
from .forms import PollForm, OptionFormset, VoteForm, EventSetupForm, EventEditForm from .forms import PollForm, OptionFormset, VoteForm, EventSetupForm, EventEditForm
from .models import Event, Poll, Ballot, EncryptedVote, TrusteeKey, PartialBallotDecryption, CombinedBallot, VoteFragment from .models import Event, Poll, Ballot, EncBallot, EncryptedVote, TrusteeKey, PartialBallotDecryption, CombinedBallot, VoteFragment
from allauthdemo.auth.models import DemoUser from allauthdemo.auth.models import DemoUser
from .tasks import email_trustees_prep, update_EID, generate_combpk, event_ended, create_ballots from .tasks import email_trustees_prep, update_EID, generate_combpk, event_ended, create_ballots
@ -110,6 +112,15 @@ def edit_poll(request, event_id, poll_id):
return HttpResponseRedirect(reverse('polls:event-polls', args=[poll.event_id])) return HttpResponseRedirect(reverse('polls:event-polls', args=[poll.event_id]))
def vote_audit(request):
encryptedBallot = get_object_or_404(EncBallot, handle=''+urllib.quote_plus(request.GET.get('handle', None)))
return render(request, "polls/vote_audit.html",
{
"ballot": encryptedBallot.ballot
})
def event_vote(request, event_id, poll_id): def event_vote(request, event_id, poll_id):
event = get_object_or_404(Event, pk=event_id) event = get_object_or_404(Event, pk=event_id)
@ -172,10 +183,21 @@ def event_vote(request, event_id, poll_id):
cant_vote_reason = "The event either isn't ready for voting or it has expired and therefore you cannot vote." cant_vote_reason = "The event either isn't ready for voting or it has expired and therefore you cannot vote."
if request.method == "POST": if request.method == "POST":
data = json.loads(request.POST.lists()[0][0]) ballot_json = json.loads(request.POST.get('ballot'))
ballot_json = data['ballot']
encrypted_votes_json = ballot_json['encryptedVotes'] encrypted_votes_json = ballot_json['encryptedVotes']
enc_ballot_json = request.POST.get('encBallot')
handle_json = request.POST.get('handle')
# Adds or replaces the encrypted un-submitted ballot to the database for the auditor app to pick up later
if EncBallot.objects.filter(handle=handle_json).exists():
b = EncBallot.objects.get(handle=handle_json)
b.ballot = ballot_json
b.save()
else:
b = EncBallot(handle=handle_json, ballot=enc_ballot_json)
b.save()
# Before storing the encrypted votes, we need the voter's ballot # Before storing the encrypted votes, we need the voter's ballot
ballot, created = Ballot.objects.get_or_create(voter=email_key[0].user, poll=poll) ballot, created = Ballot.objects.get_or_create(voter=email_key[0].user, poll=poll)
EncryptedVote.objects.filter(ballot=ballot).delete() EncryptedVote.objects.filter(ballot=ballot).delete()

View file

@ -11,16 +11,16 @@
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script> <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.37/js/bootstrap-datetimepicker.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.37/js/bootstrap-datetimepicker.min.js"></script>
<script src="https://bitwiseshiftleft.github.io/sjcl/sjcl.js"></script>
<script src='https://www.google.com/recaptcha/api.js'></script> <script src='https://www.google.com/recaptcha/api.js'></script>
<script <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js" integrity="sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU=" crossorigin="anonymous"></script>
src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"
integrity="sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU="
crossorigin="anonymous"></script>
<script src="{% static 'js/papaparse.min.js' %}" type="text/javascript"></script> <script src="{% static 'js/papaparse.min.js' %}" type="text/javascript"></script>
<script src="{% static 'js/qrcode.min.js' %}" type="text/javascript"></script> <script src="{% static 'js/qrcode.min.js' %}" type="text/javascript"></script>
<script src="{% static 'js/create-event-poll.js' %}" type="text/javascript"></script> <script src="{% static 'js/create-event-poll.js' %}" type="text/javascript"></script>
<script src="{% static 'js/decrypt_event.js' %}" type="text/javascript"></script> <script src="{% static 'js/decrypt_event.js' %}" type="text/javascript"></script>
<script src="{% static 'js/event_vote.js' %}" type="text/javascript"></script> <script src="{% static 'js/event_vote.js' %}" type="text/javascript"></script>
<script src="{% static 'js/vote_audit.js' %}" type="text/javascript"></script>
<script src="{% static 'js/encrypt.js' %}" type="text/javascript"></script> <script src="{% static 'js/encrypt.js' %}" type="text/javascript"></script>
<script type="text/javascript" src="{% static 'js/core/rand.js' %}"></script> <script type="text/javascript" src="{% static 'js/core/rand.js' %}"></script>

View file

@ -53,6 +53,7 @@
</span> </span>
<!-- Poll Voting Section --> <!-- Poll Voting Section -->
<p id="poll-num" hidden>{{ poll_num}}</p>
<h3>Poll {{ poll_num }} of {{ poll_count }}: {{object.question_text}}</h3> <h3>Poll {{ poll_num }} of {{ poll_count }}: {{object.question_text}}</h3>
<hr/> <hr/>
@ -124,7 +125,7 @@
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button> <button id="close-button" type="button" class="btn btn-danger" data-dismiss="modal">Close without submitting vote</button>
</div> </div>
</div> </div>

View file

@ -0,0 +1,18 @@
{% extends "bases/bootstrap-with-nav.html" %}
{% load staticfiles %}
{% load bootstrap3 %}
{% block content %}
<input id="SK" value="temporary" type="text"/>
<button id="begin-test">Verify Ballot</button>
<br>
<br>
<label for="ballot">AES-encrypted ballot from server</label>
<pre id="ballot">{{ ballot }}</pre>
<label for="ballot-content">Decrypted ballot</label>
<pre id="ballot-content"></pre>
<label for="ballot">Ballot encoding</label>
<pre id="ballot-result"></pre>
{% endblock %}

View file

@ -118,7 +118,8 @@ encrypt=function(params,PK, m){
return{ return{
C1:C1, C1:C1,
C2:C2 C2:C2,
r:r
} }
} }

View file

@ -80,23 +80,22 @@ function isVotingInputValid() {
return valid; return valid;
} }
// Generates a blank vote as a string using the binary encoding scheme
function genBlankVote() {
var vote = "";
for(var i = 0; i < OPTION_COUNT; i++) {
vote += "0";
if (i !== (OPTION_COUNT - 1)) {
vote += ",";
}
}
return vote;
}
var progressBar = document.getElementById("progress-bar"); var progressBar = document.getElementById("progress-bar");
$('#gen-ballots-btn').click(function() {
// Ensure that the user selections are valid
if(isVotingInputValid()) {
// Hide the button
$(this).toggleClass('hidden');
// Inject the description progress bar which can then be updated by the encrypt btn
$('#progress-bar-description').toggleClass('hidden');
$('#progress-bar-container').toggleClass('hidden');
setTimeout(generateBallots, 25);
}
});
// Based on the user's vote in the current poll, this generates a ballot which // Based on the user's vote in the current poll, this generates a ballot which
// does not leak information about how many options the user has selected // does not leak information about how many options the user has selected
function generateBallot() { function generateBallot() {
@ -156,16 +155,20 @@ function generateBallot() {
unencryptedVote.split(',').forEach(function(fragment) { unencryptedVote.split(',').forEach(function(fragment) {
var cipher = encrypt(params, pk, parseInt(fragment)); var cipher = encrypt(params, pk, parseInt(fragment));
// Store C1 and C2 from the cipher in the fragment // Store C1, C2 and r from the cipher in the fragment
var c1Bytes = []; var c1Bytes = [];
cipher.C1.toBytes(c1Bytes); cipher.C1.toBytes(c1Bytes);
var c2Bytes = []; var c2Bytes = [];
cipher.C2.toBytes(c2Bytes); cipher.C2.toBytes(c2Bytes);
var rBytes = [];
cipher.r.toBytes(rBytes);
encFragments.push({ encFragments.push({
C1 : c1Bytes.toString(), C1 : c1Bytes.toString(),
C2 : c2Bytes.toString() C2 : c2Bytes.toString(),
r : rBytes.toString()
}); });
}); });
@ -180,29 +183,19 @@ function generateBallot() {
}; };
} }
$('#gen-ballots-btn').click(function() { // Generates a blank vote as a string using the binary encoding scheme
// Ensure that the user selections are valid function genBlankVote() {
if(isVotingInputValid()) { var vote = "";
// Hide the button
$(this).toggleClass('hidden');
// Inject the description progress bar which can then be updated by the encrypt btn for(var i = 0; i < OPTION_COUNT; i++) {
$('#progress-bar-description').toggleClass('hidden'); vote += "0";
$('#progress-bar-container').toggleClass('hidden');
setTimeout(generateBallotsAndShowUsr, 25); if (i !== (OPTION_COUNT - 1)) {
vote += ",";
} }
});
function voteSuccessfullyReceived() {
let titleTxt = 'Vote Successfully Received';
let bodyText = "Thank you for voting!";
if(POLL_NUM !== POLL_COUNT) {
bodyText += " You can vote on the next poll by closing down this dialog and clicking 'Next Poll'.";
} }
showDialogWithText(titleTxt, bodyText); return vote;
} }
var CSRF = $( "input[name='csrfmiddlewaretoken']" ).val(); var CSRF = $( "input[name='csrfmiddlewaretoken']" ).val();
@ -211,25 +204,6 @@ function csrfSafeMethod(method) {
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
} }
function sendBallotToServer(selection, altHash) {
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", CSRF);
}
}
});
$.ajax({
type : "POST",
url : window.location,
data : JSON.stringify({ ballot: selection}),
success : function(){
onAfterBallotSend(altHash);
}
});
}
var bytestostring = function(b) { var bytestostring = function(b) {
var s = ""; var s = "";
var len = b.length; var len = b.length;
@ -272,63 +246,24 @@ function SHA256Hash(bytes, toStr) {
} }
} }
// Called once the ballot has been sent to the back-end and dialog has closed function generateBallots() {
function onAfterBallotSend(altHash) { // Generate Ballot A and Ballot B to be displayed to the user
let titleText = 'Vote Successfully Received'; // This fn starts the process
let bodyText = "Thank you for voting! This is your copy of your ballot - make sure to save it onto your phone before closing this window."; var ballotA = generateBallot();
if(POLL_NUM !== POLL_COUNT) { // Update the progress bar once the generation has completed
bodyText += " You can vote on the next poll by closing down this dialog and clicking 'Next Poll'."; progressBar.setAttribute("style", "width: 50%;");
}
// With one ballot selected, we can display a QR code of the voter's copy // This delay allows the execution thread to update the above CSS on the progress bar
var modalDialog = $('#modalDialog'); setTimeout(function () {
var title = modalDialog.find('.modal-title'); var ballotB = generateBallot();
var body = modalDialog.find('.modal-body'); progressBar.setAttribute("style", "width: 100%;");
title.text(titleText);
body.empty();
var p = document.createElement("p"); showFirstQRCode(ballotA, ballotB);
p.innerHTML = bodyText; }, 150);
body.append(p);
// Generate the body of the dialog which displays the unselected ballot QR code and hash
var choiceGroupDiv = document.createElement('div');
choiceGroupDiv.setAttribute('class', 'choice-group');
var QRCodeImg = document.createElement('img');
QRCodeImg.setAttribute('class', 'QR-code');
new QRCode(QRCodeImg, altHash);
choiceGroupDiv.append(QRCodeImg);
// ----------------------------------------------
var hashGroupDiv = document.createElement('div');
var br = document.createElement('br');
hashGroupDiv.append( br );
var hash = document.createElement("span");
hash.innerHTML = "Hash: " + altHash;
hashGroupDiv.append( hash );
// -----------------------------------------------
body.append(choiceGroupDiv);
body.append(hashGroupDiv);
modalDialog.modal('show');
} }
function processBallotSelection(selection, selectionHash, alt, altHash) { function showFirstQRCode(ballotA, ballotB) {
// Dispatch the ballot to the server
sendBallotToServer(selection, altHash);
// Call the successfn currently with the selection hash but this may not be needed
//successFn(alt, altHash);
}
function showBallotChoiceDialog(ballotA, ballotB) {
var ballots = new Array(ballotA, ballotB); var ballots = new Array(ballotA, ballotB);
var ballotHashes = new Array(2); var ballotHashes = new Array(2);
@ -336,28 +271,18 @@ function showBallotChoiceDialog(ballotA, ballotB) {
for (let i = 0; i <= 1; i++) for (let i = 0; i <= 1; i++)
ballotHashes[i] = SHA256Hash(stringtobytes(JSON.stringify(ballots[i])), true); ballotHashes[i] = SHA256Hash(stringtobytes(JSON.stringify(ballots[i])), true);
// With the ballots and their hashes generated, we can display the ballot choice dialog // With the ballots and their hashes generated, we can display the QR code of both hashes
var modalDialog = $('#modalDialog'); var modalDialog = $('#modalDialog');
var title = modalDialog.find('.modal-title'); var title = modalDialog.find('.modal-title');
var body = modalDialog.find('.modal-body'); var body = modalDialog.find('.modal-body');
var footer = modalDialog.find('.modal-footer');
body.empty(); body.empty();
title.text('Please Select a Ballot'); title.text('Please Scan this QR Code');
// Generate the body of the dialog which consists of a button for A and for B as well as their hashes var QRCodeImg = document.createElement('img');
var choiceGroupDiv = document.createElement('div'); QRCodeImg.setAttribute('class', 'QR-code');
choiceGroupDiv.setAttribute('class', 'choice-group'); new QRCode(QRCodeImg, ballotHashes[0] + ';' + ballotHashes[1]);
var btnChoiceA = document.createElement('a');
btnChoiceA.setAttribute('id', 'choice-A');
btnChoiceA.setAttribute('class', 'btn btn-sq btn-primary');
btnChoiceA.innerHTML = 'A';
choiceGroupDiv.append(btnChoiceA);
var btnChoiceB = document.createElement('a');
btnChoiceB.setAttribute('id', 'choice-B');
btnChoiceB.setAttribute('class', 'btn btn-sq btn-warning choice');
btnChoiceB.innerHTML = 'B';
choiceGroupDiv.append(btnChoiceB);
// ---------------------------------------------- // ----------------------------------------------
@ -378,38 +303,153 @@ function showBallotChoiceDialog(ballotA, ballotB) {
// ----------------------------------------------- // -----------------------------------------------
body.append(choiceGroupDiv); body.append(QRCodeImg);
body.append(hashGroupDiv); body.append(hashGroupDiv);
var closeButton = $('close-button');
closeButton.removeClass('btn-success');
closeButton.addClass('btn-danger');
closeButton.text("Close without submitting vote");
var nextButton = document.createElement('button');
nextButton.setAttribute('type', 'button');
nextButton.setAttribute('id', 'next-button');
nextButton.setAttribute('class', 'btn btn-default');
nextButton.innerHTML = "Next";
footer.prepend(nextButton);
modalDialog.modal('show');
$('#next-button').click(function(e) {
showBallotChoiceDialog(ballots);
});
}
function showBallotChoiceDialog(ballots) {
// Display the ballot choice dialog
var modalDialog = $('#modalDialog');
var title = modalDialog.find('.modal-title');
var body = modalDialog.find('.modal-body');
body.empty();
title.text('Please Select a Ballot');
// Generate the body of the dialog which consists of a button for A and for B
var choiceGroupDiv = document.createElement('div');
choiceGroupDiv.setAttribute('class', 'choice-group');
var btnChoiceA = document.createElement('a');
btnChoiceA.setAttribute('id', 'choice-A');
btnChoiceA.setAttribute('class', 'btn btn-sq btn-primary');
btnChoiceA.innerHTML = 'A';
choiceGroupDiv.append(btnChoiceA);
var btnChoiceB = document.createElement('a');
btnChoiceB.setAttribute('id', 'choice-B');
btnChoiceB.setAttribute('class', 'btn btn-sq btn-warning choice');
btnChoiceB.innerHTML = 'B';
choiceGroupDiv.append(btnChoiceB);
body.append(choiceGroupDiv);
modalDialog.modal('show'); modalDialog.modal('show');
// Register callback functions for the selection of either A or B // Register callback functions for the selection of either A or B
$('#choice-A').click(function(e) { $('#choice-A').click(function(e) {
processBallotSelection(ballots[0], ballotHashes[0], ballots[1], ballotHashes[1]); sendBallotsToServer(ballots[0], ballots[1]);
}); });
$('#choice-B').click(function(e) { $('#choice-B').click(function(e) {
processBallotSelection(ballots[1], ballotHashes[1], ballots[0], ballotHashes[0]); sendBallotsToServer(ballots[1], ballots[0]);
}); });
} }
function generateBallotB(ballotA) { function sendBallotsToServer(selection, alt) {
var ballotB = generateBallot(); $.ajaxSetup({
progressBar.setAttribute("style", "width: 100%;"); beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", CSRF);
}
}
});
showBallotChoiceDialog(ballotA, ballotB); // Elliptic curve cryptography params used for encryption of encrypted vote
// fragments
var ctx = new CTX("BN254CX");
var n = new ctx.BIG();
var g1 = new ctx.ECP();
var g2 = new ctx.ECP2();
var parameter = $('#event-param').val();
var tempParams = JSON.parse(JSON.parse(parameter).crypto);
//copying the values
n.copy(tempParams.n);
g1.copy(tempParams.g1);
g2.copy(tempParams.g2);
var params = {
n:n,
g1:g1,
g2:g2
};
var tempPK = JSON.parse($('#comb_pk').val());
var pk = new ctx.ECP(0);
pk.copy(tempPK.PK);
var voterID = window.location.search.slice(1).split(/=(.+)/)[1];//.slice(0, -2);
var eventID = window.location.href.split('/')[4];
var pollNum = $('#poll-num').text();
var ballotID = encodeURIComponent(btoa(JSON.stringify({voterID: voterID, eventID: eventID, pollNum: pollNum})));
var SK = "temporary";
var encAlt = sjcl.encrypt(SK, JSON.stringify(alt));
selection = JSON.stringify(selection);
$.ajax({
type : "POST",
url : window.location,
data : { handle: ballotID, encBallot: encAlt, ballot: selection },
success : function(){
onAfterBallotSend(ballotID, SK);
}
});
} }
function generateBallotsAndShowUsr() { // Called once the ballot has been sent to the back-end and dialog has closed
// Generate Ballot A and Ballot B to be displayed to the user function onAfterBallotSend(ballotID, SK) {
// This fn starts the process let titleText = 'Vote Successfully Received';
var ballotA = generateBallot(); let bodyText = "Thank you for voting! Your secret key is '"+SK+"'. Make sure to scan this QR code with your phone before closing this window.";
// Update the progress bar once the generation has completed if(POLL_NUM !== POLL_COUNT) {
progressBar.setAttribute("style", "width: 50%;"); bodyText += " You can vote on the next poll by closing down this dialog and clicking 'Next Poll'.";
}
// This delay allows the execution thread to update the above CSS on the progress bar // With one ballot selected, we can display a QR code of the ballot ID
setTimeout(function () { var modalDialog = $('#modalDialog');
generateBallotB(ballotA); var title = modalDialog.find('.modal-title');
}, 150); var body = modalDialog.find('.modal-body');
title.text(titleText);
body.empty();
var p = document.createElement("p");
p.innerHTML = bodyText;
body.append(p);
// Generate the body of the dialog which displays the unselected ballot QR code
var QRCodeImg = document.createElement('img');
QRCodeImg.setAttribute('class', 'QR-code');
new QRCode(QRCodeImg, ballotID);
body.append(QRCodeImg);
var closeButton = $('#close-button');
closeButton.removeClass('btn-danger');
closeButton.addClass('btn-success');
closeButton.text("Close");
modalDialog.modal('show');
} }

17
static/js/vote_audit.js Normal file
View file

@ -0,0 +1,17 @@
$('#begin-test').click(function() {
var ballot = JSON.parse(sjcl.decrypt($('#SK').val(), $('#ballot').text()));
var votes = ballot['encryptedVotes'][0]['fragments'];
$('#ballot-content').text(JSON.stringify(votes));
var option = 0;
votes.forEach(function(cT) {
option++;
var encoding = "";
for (var i = 0; i < cT['C1'].length; i++) {
cipherText = "("+cT['C1']+","+cT['C2']+")";
var m = cT['C2'][i] / Math.pow(cT['C1'][i], cT['r'][i]);
encoding += (m) ? "1" : "0";
}
$('#ballot-result').text($('#ballot-result').text() + "\n\nOption "+option+": "+encoding);
})
});