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:
commit
fc0d0ea21c
14 changed files with 343 additions and 171 deletions
|
@ -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",
|
||||||
|
|
79
README.md
79
README.md
|
@ -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
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
]
|
]
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
18
allauthdemo/templates/polls/vote_audit.html
Normal file
18
allauthdemo/templates/polls/vote_audit.html
Normal 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 %}
|
|
@ -238,7 +238,7 @@ function isEventTimingsValid() {
|
||||||
// Extract the string val from the vote start and end input controls
|
// Extract the string val from the vote start and end input controls
|
||||||
var start_date_time = $('#vote-start-input').val();
|
var start_date_time = $('#vote-start-input').val();
|
||||||
var end_date_time = $('#vote-end-input').val();
|
var end_date_time = $('#vote-end-input').val();
|
||||||
|
|
||||||
// Convert the string vals to Date objects
|
// Convert the string vals to Date objects
|
||||||
var start_dateObj = new Date(start_date_time);
|
var start_dateObj = new Date(start_date_time);
|
||||||
var end_dateObj = new Date(end_date_time);
|
var end_dateObj = new Date(end_date_time);
|
||||||
|
|
|
@ -118,7 +118,8 @@ encrypt=function(params,PK, m){
|
||||||
|
|
||||||
return{
|
return{
|
||||||
C1:C1,
|
C1:C1,
|
||||||
C2:C2
|
C2:C2,
|
||||||
|
r:r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 += ",";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
return 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);
|
|
||||||
|
|
||||||
// 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
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
34
static/js/vote_audit.js
Normal 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 ");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Reference in a new issue