Merge pull request #20 from Rumperuu/master

Here is my work so far on the auditing (as well as some assorted other changes, such as clearing up the README and a couple voting form tweaks)
This commit is contained in:
Vincent 2018-08-29 18:02:59 +01:00 committed by GitHub
commit fc0d0ea21c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 343 additions and 171 deletions

View file

@ -1,5 +1,5 @@
{ {
"name": "Node DEMOS2 Cryptography Server", "name": "node-demos2-cryptography-server",
"version": "1.0.0", "version": "1.0.0",
"description": "Exposes Cryptographic Functionality from Various Endpoints", "description": "Exposes Cryptographic Functionality from Various Endpoints",
"main": "index.js", "main": "index.js",

View file

@ -1,62 +1,81 @@
# DEMOS2 # DEMOS2
Prototype Django-based e-voting application, to demonstrate DEMOS2's client-side encryption e-voting. Prototype Django-based e-voting application, to demonstrate DEMOS2's client-side
encryption e-voting.
The previous repository for DEMOS2 by Carey Williams can be found at: https://github.com/CareyJWilliams/DEMOS2 The previous repository for DEMOS2 by Carey Williams can be found at:
https://github.com/CareyJWilliams/DEMOS2
## Setup
### Main Application Dependencies ### Main Application Dependencies
Python: Version 2.7 (Anything higher than this will not currently work) * Python
Python packages: Specified in 'requirements.txt' - PyCharm will detect these dependencies and offer installation * Version 2.7; anything higher than this will not currently work
MySQL Server: Community Ed will do - initialised with legacy password authentication * All Python packages specified in `requirements.txt`; PyCharm will detect these dependencies and offer installation
New MySQL DB User: Default username and password specified in 'aullauthdemo/settings.py' * MySQL Server: Community Edition
New MySQL DB: demos2 (also specified in 'aullauthdemo/settings.py') - make sure to set the charset to UTF8 * Initialise with 'legacy password authentication'
* Create a new MySQL DB user with the default username and password specified in `aullauthdemo/settings.py`
* Create a new MySQL DB called `demos2` (also specified in `aullauthdemo/settings.py`), making sure to set the charset to 'UTF8'
Finally, with all the above dependencies in place, you can simply issue the following command to initialise the DB: ### Database setup
python manage.py migrate After installing the above dependencies, issue the following command to initialise the DB:
'aullauthdemo/settings.py' specifies the Google reCAPTCHA site key and private key which will need changing when deployed ```
onto a new domain. There is also a DOMAIN setting within the file that needs updating during deployment as things python manage.py migrate
like email functionality depend on this setting for correct URL generation during event preparation etc. ```
The first run will produce a `django.db.utils.IntegrityError: (1215, 'Cannot add foreign key constraint')` error. Issue the command a second time and it will complete successfully.
### Settings
`allauthdemo/settings.py` specifies the Google reCAPTCHA site key and private key, which will need changing when deployed onto a new domain.
There is also a `DOMAIN` setting within the file that needs updating during deployment as things like email functionality depend on this setting for correct URL generation during event preparation etc.
Emails from the application are currently sent from the following email account which can be updated within the settings: Emails from the application are currently sent from the following email account which can be updated within the settings:
demos2.no.reply@gmail.com ```
demos2.no.reply@gmail.com
```
### NodeJS Dependencies ### NodeJS Dependencies
The Node.js crypto server depends on the milagro-crypto-js and express modules. A package.json file can be found in the The Node.js crypto server depends on the `milagro-crypto-js` and `express` modules. A `package.json` file can be found in the `Node/` directory with these dependency requirements and therefore from this folder you can run:
Node/ directory with these dependency requirements and therefore from this folder you can run:
npm install ```
npm install
```
Once the dependencies have been installed, you can then run the node server as per the below instructions. Once the dependencies have been installed, you can then run the node server as per the below instructions.
## Instructions
### Step 1: Running the Python app and creating a new user account ### Step 1: Running the Python app and creating a new user account
You can run the server with the following command: You can run the server with the following command:
python manage.py runserver ```
python manage.py runserver
```
The application will then be available at: The application will then be available at `127.0.0.1:8000`.
127.0.0.1:8000 You can then click on 'Join' to create a new user account.
You can then click on 'Join' to create a new user account. Currently, a server error is thrown when you create a new ### Step 2: Running the Node.js Server
email account saying something like 'Too Many Attempts'. Rest assured that the account will have been created. Navigate
back to the home page and you should be able to log in. This will hopefully be fixed in a future version.
### Step 2: Running Celery The Node.js server exposes a lot of cryptographic operations that the application depends on throughout. To run the server, issue the following command line request from the `Node/` folder:
Celery is used to run tasks asynchronously and the DEMOS2 application can't run without this application. A bash script ```
called 'start_celery_worker.sh' is provided to make starting a worker as easy as possible: node index.js
```
./start_celery_worker.sh ### Step 3: Running Celery
### Step 3: Running the NodeJS Server Celery is used to run tasks asynchronously and the DEMOS2 application can't run without this application. A bash script called `start_celery_worker.sh` is provided to make starting a worker as easy as possible:
The NodeJS server exposes a lot of cryptographic operations that the application depends on throughout. To run the ```
server, issue the following command line request from the Node/ folder: ./start_celery_worker.sh
```
node index.js

View file

@ -209,6 +209,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

@ -16,7 +16,7 @@ from .crypto_rpc import param, combpk, add_ciphers, get_tally
The following tasks were re-implemented by Thomas Smith: generate_event_param, tally_results, generate_combpk, generate_enc The following tasks were re-implemented by Thomas Smith: generate_event_param, tally_results, generate_combpk, generate_enc
This file was also updated by Vincent de Almeida This file was also updated by Vincent de Almeida and Ben Goldsworthy
''' '''
# Will store the result of the initial cal to param() from .cpp_calls # Will store the result of the initial cal to param() from .cpp_calls

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
@ -113,6 +115,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):
encrypted_ballot = get_object_or_404(EncBallot, handle=''+urllib.quote_plus(request.GET.get('handle', None)))
return render(request, "polls/vote_audit.html",
{
"ballot": encrypted_ballot.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)
@ -175,11 +186,22 @@ 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'))
selection = data['selection'] selection = request.POST.get('selection')
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 = enc_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,15 +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/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/>
@ -123,7 +124,6 @@
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
{% comment %}<button type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>{% endcomment %}
<button id="cancelVoteBtn" type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button> <button id="cancelVoteBtn" type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>
<button id="closeDialogBtn" type="button" class="btn btn-primary hidden" data-dismiss="modal">Close</button> <button id="closeDialogBtn" type="button" class="btn btn-primary hidden" data-dismiss="modal">Close</button>
</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

@ -92,23 +92,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() {
@ -168,16 +167,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()
}); });
}); });
@ -192,33 +195,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;
// Update the dialog's btns
$('#cancelVoteBtn').addClass("hidden");
$('#closeDialogBtn').removeClass("hidden");
} }
var CSRF = $( "input[name='csrfmiddlewaretoken']" ).val(); var CSRF = $( "input[name='csrfmiddlewaretoken']" ).val();
@ -227,29 +216,6 @@ function csrfSafeMethod(method) {
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
} }
function sendBallotToServer(selection, ballot, ballotSelectionDialog) {
// Use ajax to send the selected ballot to server
$.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({ selection: selection, ballot: ballot}),
success : function() {
voteSuccessfullyReceived();
}
});
// Update the client to inform the user that the vote is being processed
showDialogWithText("Please Wait", "Processing Vote. Please wait...");
}
var bytestostring = function(b) { var bytestostring = function(b) {
var s = ""; var s = "";
var len = b.length; var len = b.length;
@ -292,35 +258,96 @@ function SHA256Hash(bytes, toStr) {
} }
} }
// FAO Ben: Called once the ballot has been sent to the back-end and dialog has closed function generateBallots() {
function onAfterBallotSend() { // Generate Ballot A and Ballot B to be displayed to the user
// TODO: FAO Ben: Implement QR func here. // This fn starts the process
// TODO: Currently, there is a dialog already implemented in the event_vote.html page which is var ballotA = generateBallot();
// TODO: used for voting error information but could be used to display the QR code using JS in
// TODO: a similar way that showBallotChoiceDialog does. // Update the progress bar once the generation has completed
progressBar.setAttribute("style", "width: 50%;");
// This delay allows the execution thread to update the above CSS on the progress bar
setTimeout(function () {
var ballotB = generateBallot();
progressBar.setAttribute("style", "width: 100%;");
showFirstQRCode(ballotA, ballotB);
}, 150);
} }
function processBallotSelection(selection, selectedBallot, selectedBallotHash, dialog, successFn) { function showFirstQRCode(ballotA, ballotB) {
// Dispatch the ballot to the server var ballots = new Array(ballotA, ballotB);
sendBallotToServer(selection, selectedBallot, dialog); var ballotHashes = new Array(2);
// Call the successfn currently with the selection hash but this may not be needed // Hash both ballots and store
successFn(selectedBallotHash); for (let i = 0; i <= 1; i++)
} ballotHashes[i] = SHA256Hash(stringtobytes(JSON.stringify(ballots[i])), true);
function showBallotChoiceDialog(ballotA, ballotB) { // With the ballots and their hashes generated, we can display the QR code of both hashes
// Output hashes of the 2 ballots
const BALLOT_A_HASH = SHA256Hash(stringtobytes(JSON.stringify(ballotA)), true);
const BALLOT_B_HASH = SHA256Hash(stringtobytes(JSON.stringify(ballotB)), true);
// With the ballots and their hashes generated, we can display the ballot choice dialog
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();
title.text('Please Scan this QR Code');
var QRCodeImg = document.createElement('img');
QRCodeImg.setAttribute('class', 'QR-code');
new QRCode(QRCodeImg, ballotHashes[0] + ';' + ballotHashes[1]);
// ----------------------------------------------
var hashGroupDiv = document.createElement('div');
var br = document.createElement('br');
hashGroupDiv.append( br );
var hashA = document.createElement("span");
hashA.innerHTML = "Hash A: " + ballotHashes[0];
hashGroupDiv.append( hashA );
var br2 = document.createElement('br');
hashGroupDiv.append( br2 );
var hashB = document.createElement("span");
hashB.innerHTML = "Hash B: " + ballotHashes[1];
hashGroupDiv.append( hashB );
// -----------------------------------------------
body.append(QRCodeImg);
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(); body.empty();
title.text('Please Select a Ballot'); title.text('Please Select a Ballot');
// Generate the body of the dialog which consists of a button for A and for B as well as their hashes // Generate the body of the dialog which consists of a button for A and for B
var choiceGroupDiv = document.createElement('div'); var choiceGroupDiv = document.createElement('div');
choiceGroupDiv.setAttribute('class', 'choice-group'); choiceGroupDiv.setAttribute('class', 'choice-group');
@ -336,66 +363,109 @@ function showBallotChoiceDialog(ballotA, ballotB) {
btnChoiceB.innerHTML = 'B'; btnChoiceB.innerHTML = 'B';
choiceGroupDiv.append(btnChoiceB); choiceGroupDiv.append(btnChoiceB);
// ----------------------------------------------
var hashGroupDiv = document.createElement('div');
var br = document.createElement('br');
hashGroupDiv.append( br );
var hashA = document.createElement("span");
hashA.innerHTML = "Hash A: " + BALLOT_A_HASH;
hashGroupDiv.append( hashA );
var br2 = document.createElement('br');
hashGroupDiv.append( br2 );
var hashB = document.createElement("span");
hashB.innerHTML = "Hash B: " + BALLOT_B_HASH;
hashGroupDiv.append( hashB );
// -----------------------------------------------
body.append(choiceGroupDiv); body.append(choiceGroupDiv);
body.append(hashGroupDiv);
// Prepare the appropriate dialog buttons modalDialog.modal('show');
$('#cancelVoteBtn').removeClass("hidden");
$('#closeDialogBtn').removeClass("hidden").addClass("hidden");
if(!dialogOpen) {
modalDialog.modal('toggle');
dialogOpen = true;
}
// 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("A", ballotA, BALLOT_A_HASH, modalDialog, onAfterBallotSend); sendBallotsToServer(ballots[0], ballots[1]);
}); });
$('#choice-B').click(function(e) { $('#choice-B').click(function(e) {
processBallotSelection("B", ballotB, BALLOT_B_HASH, modalDialog, onAfterBallotSend); 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");
if(POLL_NUM == POLL_COUNT) {
$('#next-button').hide();
}
modalDialog.modal('show');
} }
$('#modalDialog').on('hide.bs.modal', function (e) { $('#modalDialog').on('hide.bs.modal', function (e) {

1
static/js/qrcode.min.js vendored Normal file

File diff suppressed because one or more lines are too long

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

@ -0,0 +1,34 @@
$('#begin-test').click(function() {
var ballot = JSON.parse(sjcl.decrypt($('#SK').val(), $('#ballot').text()));
var votes = ballot['encryptedVotes'];
$('#ballot-content').text(JSON.stringify(votes));
var voteNum = 0, optionNum = 0;
// For each encrypted vote within the ballot...
votes.forEach(function(vote) {
voteNum++;
$('#ballot-result').text($('#ballot-result').text() + "Vote " + voteNum + ": \n ");
// For each encrypted fragment within the vote (i.e. the presence of a vote for each option)...
vote['fragments'].forEach(function(fragment) {
optionNum++;
$('#ballot-result').text($('#ballot-result').text() + "Option " + optionNum + ": \n ");
var encoding = "";
console.log(fragment);
// For each pair of values in C1 and C2 and the randomness r, test whether C2/(C1)^r = g^0 or g^1, and
// record g's exponent.
for (var i = 0; i < fragment['C1'].length; i++) {
cipherText = "("+fragment['C1']+","+fragment['C2']+")";
var m = fragment['C2'][i] / Math.pow(fragment['C1'][i], fragment['r'][i]);
encoding += (m) ? "1" : "0";
}
// Somehow, this string of 1s and 0s here needs to become _one_ 1 or 0 to signify whether the option was
// voted for or not.
$('#ballot-result').text($('#ballot-result').text() + encoding + "\n ");
});
});
});