Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

563 lines (457 sloc) 19.427 kB
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8" />
<title>Helios Voting Booth</title>
<link rel="stylesheet" type="text/css" href="css/booth.css" />
<link rel="stylesheet" type="text/css" href="css/forms.css" />
<script language="javascript" src="js/jscrypto/jsbn.js"></script>
<script language="javascript" src="js/jscrypto/jsbn2.js"></script>
<script language="javascript" src="js/jscrypto/sjcl.js"></script>
<script language="javascript" src="js/underscore-min.js"></script>
<script language="javascript" src="js/jquery-1.2.2.min.js"></script>
<script language="javascript" src="js/jquery.query-2.1.5.js"></script>
<script language="javascript" src="js/jquery-jtemplates.js"></script>
<script language="javascript" src="js/jquery.json.min.js"></script>
<script language="javascript">
// required for jscrypto library
var JSCRYPTO_HOME = document.location.pathname.replace("vote.html", "js/jscrypto");
</script>
<script language="javascript" src="js/jscrypto/class.js"></script>
<script language="javascript" src="js/jscrypto/bigint.js?d=20111004"></script>
<!-- this script is a dummy wrapper for bigint when client-side crypto is not possible
<script language="javascript" src="js/jscrypto/bigint.dummy.js"></script>-->
<script language="javascript" src="js/jscrypto/random.js"></script>
<script language="javascript" src="js/jscrypto/elgamal.js?d=20111004"></script>
<script language="javascript" src="js/jscrypto/sha1.js"></script>
<script language="javascript" src="js/jscrypto/sha2.js"></script>
<script language="javascript" src="js/jscrypto/helios.js?d=20111004"></script>
</head>
<body>
<div id="wrapper">
<span style="float:right; padding: 7px 15px 5px 10px;">[<a href="javascript:BOOTH.exit();">exit</a>]</span>
<div id="banner">Helios Voting Booth</div>
<div id="content">
<div id="header">
</div>
<script language="javascript">
// utils
BOOTH = {};
BOOTH.setup_templates = function() {
if (BOOTH.templates_setup_p)
return;
var cache_bust = "?cb=" + new Date().getTime();
$('#header').setTemplateURL("templates/header.html" + cache_bust);
$('#election_div').setTemplateURL("templates/election.html" + cache_bust);
$('#question_div').setTemplateURL("templates/question.html" + cache_bust);
$('#confirm_div').setTemplateURL("templates/confirm.html" + cache_bust);
$('#seal_div').setTemplateURL("templates/seal.html" + cache_bust);
$('#audit_div').setTemplateURL("templates/audit.html" + cache_bust);
$('#footer').setTemplateURL("templates/footer.html" + cache_bust);
BOOTH.templates_setup_p = true;
};
// set up the message when navigating away
BOOTH.started_p = false;
window.onbeforeunload = function(evt) {
if (!BOOTH.started_p)
return;
if (typeof evt == 'undefined') {
evt = window.event;
}
var message = "If you leave this page with an in-progress ballot, your ballot will be lost.";
if (evt) {
evt.returnValue = message;
}
return message;
};
BOOTH.exit = function() {
if (confirm("Are you sure you want to exit the booth and lose all information about your current ballot?")) {
BOOTH.started_p = false;
window.location = BOOTH.election.cast_url;
}
};
BOOTH.setup_ballot = function(election) {
BOOTH.ballot = {};
// each question starts out with an empty array answer
BOOTH.ballot.answers = [];
$(BOOTH.election.questions).each(function(i,x){
BOOTH.ballot.answers[i] = [];
});
};
// all ciphertexts to null
BOOTH.reset_ciphertexts = function() {
_(BOOTH.encrypted_answers).each(function(enc_answer, ea_num) {
BOOTH.launch_async_encryption_answer(ea_num);
});
};
BOOTH.setup_workers = function(election_raw_json) {
// async?
if (!BOOTH.synchronous) {
// prepare spots for encrypted answers
// and one worker per question
BOOTH.encrypted_answers = [];
BOOTH.answer_timestamps = [];
BOOTH.workers = [];
$(BOOTH.election.questions).each(function(q_num, q) {
BOOTH.encrypted_answers[q_num] = null;
var new_worker = new window.Worker("boothworker.js");
new_worker.postMessage({
'type': 'setup',
'election' : election_raw_json,
'question_num' : q_num
});
new_worker.onmessage = function(event) {
// logging
if (event.data.type == 'log')
return console.log(event.data.msg);
// result of encryption
if (event.data.type == 'result') {
console.log("got encrypted answer " + q_num);
// this check ensures that race conditions
// don't screw up votes.
if (event.data.id == BOOTH.answer_timestamps[q_num]) {
BOOTH.encrypted_answers[q_num] = HELIOS.EncryptedAnswer.fromJSONObject(event.data.encrypted_answer, BOOTH.election);
} else {
console.log("no way jose");
}
}
};
BOOTH.workers[q_num] = new_worker;
});
}
};
function escape_html(content) {
return $('<div/>').text(content).html();
}
BOOTH.setup_election = function(raw_json) {
// IMPORTANT: we use the raw JSON for safer hash computation
// so that we are using the JSON serialization of the SERVER
// to compute the hash, not the JSON serialization in JavaScript.
// the main reason for this is unicode representation: the Python approach
// appears to be safer.
BOOTH.election = HELIOS.Election.fromJSONString(raw_json);
// FIXME: we shouldn't need to set both, but right now we are doing so
// because different code uses each one. Bah. Need fixing.
BOOTH.election.hash = b64_sha256(raw_json);
BOOTH.election.election_hash = BOOTH.election.hash
// dirty markers for encryption (mostly for async)
BOOTH.dirty = [];
// async?
BOOTH.setup_workers(raw_json);
document.title += ' - ' + BOOTH.election.name;
// escape election fields
$(['description', 'name']).each(function(i, field) {
BOOTH.election[field] = escape_html(BOOTH.election[field]);
});
$('#header').processTemplate({'election' : BOOTH.election});
$('#footer').processTemplate({'election' : BOOTH.election});
BOOTH.setup_ballot();
};
BOOTH.show = function(el) {
$('.panel').hide();
el.show();
return el;
};
BOOTH.show_election = function() {
BOOTH.show($('#election_div')).processTemplate({'election' : BOOTH.election});
};
BOOTH.launch_async_encryption_answer = function(question_num) {
BOOTH.answer_timestamps[question_num] = new Date().getTime();
BOOTH.encrypted_answers[question_num] = null;
BOOTH.dirty[question_num] = false;
BOOTH.workers[question_num].postMessage({
'type' : 'encrypt',
'answer' : BOOTH.ballot.answers[question_num],
'id' : BOOTH.answer_timestamps[question_num]
});
};
// check if the current question is ok
BOOTH.validate_question = function(question_num) {
// check if enough answers are checked
if (BOOTH.ballot.answers[question_num].length < BOOTH.election.questions[question_num].min) {
alert('You need to select at least ' + BOOTH.election.questions[question_num].min + ' answer(s).');
return false;
}
// if we need to launch the worker, let's do it
if (!BOOTH.synchronous) {
// we need a unique ID for this to ensure that old results
// don't mess things up. Using timestamp.
// check if dirty
if (BOOTH.dirty[question_num]) {
BOOTH.launch_async_encryption_answer(question_num);
}
}
return true;
};
BOOTH.validate_and_confirm = function(question_num) {
if (BOOTH.validate_question(question_num)) {
BOOTH.show_confirm();
}
};
BOOTH.next = function(question_num) {
if (BOOTH.validate_question(question_num)) {
BOOTH.show_question(question_num + 1);
}
};
BOOTH.previous = function(question_num) {
if (BOOTH.validate_question(question_num)) {
BOOTH.show_question(question_num - 1);
}
};
BOOTH.show_question = function(question_num) {
BOOTH.started_p = true;
// the first time we hit the last question, we enable the review all button
if (question_num == BOOTH.election.questions.length -1)
BOOTH.all_questions_seen = true;
BOOTH.show_progress('1');
BOOTH.show($('#question_div')).processTemplate({'question_num' : question_num,
'last_question_num' : BOOTH.election.questions.length - 1,
'question' : BOOTH.election.questions[question_num], 'show_reviewall' : BOOTH.all_questions_seen
});
// fake clicking through the answers, to trigger the disabling if need be
// first we remove the answers array
var answer_array = BOOTH.ballot.answers[question_num];
BOOTH.ballot.answers[question_num] = [];
// we should not set the dirty bit here, so we save it away
var old_dirty = BOOTH.dirty[question_num];
$(answer_array).each(function(i, ans) {
BOOTH.click_checkbox_script(question_num, ans, true);
});
BOOTH.dirty[question_num] = old_dirty;
};
BOOTH.click_checkbox_script = function(question_num, answer_num) {
document.forms['answer_form']['answer_'+question_num+'_'+answer_num].click();
};
BOOTH.click_checkbox = function(question_num, answer_num, checked_p) {
// keep track of dirty answers that need encrypting
BOOTH.dirty[question_num] = true;
if (checked_p) {
// multiple click events shouldn't screw this up
if ($(BOOTH.ballot.answers[question_num]).index(answer_num) == -1)
BOOTH.ballot.answers[question_num].push(answer_num);
$('#answer_label_' + question_num + "_" + answer_num).addClass('selected');
} else {
BOOTH.ballot.answers[question_num] = UTILS.array_remove_value(BOOTH.ballot.answers[question_num], answer_num);
$('#answer_label_' + question_num + "_" + answer_num).removeClass('selected');
}
if (BOOTH.election.questions[question_num].max != null && BOOTH.ballot.answers[question_num].length >= BOOTH.election.questions[question_num].max) {
// disable the other checkboxes
$('.ballot_answer').each(function(i, checkbox) {
if (!checkbox.checked)
checkbox.disabled = true;
});
$('#warning_box').html("Maximum number of options selected.<br />To change your selection, please de-select a current selection first.");
} else {
// enable the other checkboxes
$('.ballot_answer').each(function(i, checkbox) {
checkbox.disabled = false;
});
$('#warning_box').html("");
}
};
BOOTH.show_processing_before = function(str_to_execute) {
$('#processing_div').html("<h3 align='center'>Processing...</h3>");
BOOTH.show($('#processing_div'));
// add a timeout so browsers like Safari actually display the processing message
setTimeout(str_to_execute, 250);
};
BOOTH.show_encryption_message_before = function(func_to_execute) {
BOOTH.show($('#encrypting_div'));
func_to_execute();
};
BOOTH.load_and_setup_election = function(election_url) {
// the hash will be computed within the setup function call now
$.get(election_url, function(raw_json) {
BOOTH.setup_election(raw_json);
BOOTH.show_election();
BOOTH.election_url = election_url;
});
};
BOOTH.hide_progress = function() {
$('#progress_div').hide();
};
BOOTH.show_progress = function(step_num) {
$('#progress_div').show();
$(['1','2','3','4']).each(function(n, step) {
if (step == step_num)
$('#progress_' + step).attr('class', 'selected');
else
$('#progress_' + step).attr('class', 'unselected');
});
};
BOOTH.so_lets_go = function () {
BOOTH.hide_progress();
BOOTH.setup_templates();
// election URL
var election_url = $.query.get('election_url');
BOOTH.load_and_setup_election(election_url);
};
BOOTH.nojava = function() {
// in the case of Chrome, we get here when Java
// is disabled, instead of detecting the problem earlier.
// because navigator.javaEnabled() still returns true.
// $('#election_div').hide();
// $('#error_div').show();
/* var script = document.createElement('script');
script.type = 'text/javascript';
script.src = JSCRYPTO_HOME + '/bigint.dummy.js';
document.getElementsByTagName('head')[0].appendChild(script);
// FIXME: this is a bit unfortunate, can we have a trigger
// for loading this new script?
setTimeout("BigInt.setup(BOOTH.so_lets_go)", 2000); */
USE_SJCL = true;
sjcl.random.startCollectors();
BigInt.setup(BOOTH.so_lets_go);
};
BOOTH.ready_p = false;
$(document).ready(function() {
if (USE_SJCL)
sjcl.random.startCollectors();
// we're asynchronous if we have SJCL and Worker
BOOTH.synchronous = !(USE_SJCL && window.Worker);
BigInt.setup(BOOTH.so_lets_go, BOOTH.nojava);
});
BOOTH.show_confirm = function() {
// process the answers
var choices = BALLOT.pretty_choices(BOOTH.election, BOOTH.ballot);
BOOTH.show($('#confirm_div')).processTemplate({'questions' : BOOTH.election.questions, 'choices' : choices});
};
BOOTH.check_encryption_status = function() {
var progress = BOOTH.progress.progress();
if (progress == "" || progress == null)
progress = "0";
$('#percent_done').html(progress);
};
BOOTH._after_ballot_encryption = function() {
// removing the chrome weird fix because no more Java
var encrypted_vote_json = JSON.stringify(BOOTH.encrypted_ballot.toJSONObject());
BOOTH.encrypted_ballot_hash = BOOTH.encrypted_ballot.get_hash();
$('#seal_div').processTemplate({'cast_url': BOOTH.election.cast_url,
'encrypted_vote_json': encrypted_vote_json,
'encrypted_vote_hash' : BOOTH.encrypted_ballot_hash,
'election_uuid' : BOOTH.election.uuid,
'election_hash' : BOOTH.election_hash,
'election': BOOTH.election});
BOOTH.show($('#seal_div'));
BOOTH.show_progress('3');
};
// wait for all workers to be done
BOOTH.wait_for_ciphertexts = function() {
if (_.any(BOOTH.encrypted_answers, _.isNull)) {
setTimeout(BOOTH.wait_for_ciphertexts, 500);
return;
}
BOOTH.encrypted_ballot = HELIOS.EncryptedVote.fromEncryptedAnswers(BOOTH.election, BOOTH.encrypted_answers);
BOOTH._after_ballot_encryption();
};
BOOTH.seal_ballot_raw = function() {
if (BOOTH.synchronous) {
BOOTH.progress = new UTILS.PROGRESS();
var progress_interval = setInterval("BOOTH.check_encryption_status()", 500);
BOOTH.encrypted_ballot = new HELIOS.EncryptedVote(BOOTH.election, BOOTH.ballot.answers, BOOTH.progress);
clearInterval(progress_interval);
BOOTH._after_ballot_encryption();
} else {
BOOTH.wait_for_ciphertexts();
}
};
BOOTH.request_ballot_encryption = function() {
$.post(BOOTH.election_url + "/encrypt-ballot", {'answers_json': $.toJSON(BOOTH.ballot.answers)}, function(result) {
BOOTH.encrypted_ballot = HELIOS.EncryptedVote.fromJSONObject($.secureEvalJSON(result), BOOTH.election);
BOOTH._after_ballot_encryption();
});
};
BOOTH.seal_ballot = function() {
BOOTH.show_progress('2');
// if we don't have the ability to do crypto in the browser,
// we use the server
if (BigInt.is_dummy) {
BOOTH.show_encryption_message_before(BOOTH.request_ballot_encryption, true);
} else {
BOOTH.show_encryption_message_before(BOOTH.seal_ballot_raw, true);
$('#percent_done_container').show();
}
};
BOOTH.audit_ballot = function() {
BOOTH.audit_trail = BigInt.patchForChrome(function() {
return $.toJSON(BOOTH.encrypted_ballot.get_audit_trail());
});
BOOTH.show($('#audit_div')).processTemplate({'audit_trail' : BOOTH.audit_trail, 'election_url' : BOOTH.election_url});
};
BOOTH.post_audited_ballot = function() {
$.post(BOOTH.election_url + "/post-audited-ballot", {'audited_ballot': BOOTH.audit_trail}, function(result) {
alert('This audited ballot has been posted.\nRemember, this vote will only be used for auditing and will not be tallied.\nClick "back to voting" and cast a new ballot to make sure your vote counts.');
});
};
BOOTH.cast_ballot = function() {
// show progress spinner
$('#loading_div').html('<img src="loading.gif" id="proceed_loading_img" />');
$('#proceed_button').attr('disabled', 'disabled');
// at this point, we delete the plaintexts by resetting the ballot
BOOTH.setup_ballot(BOOTH.election);
// clear the plaintext from the encrypted
BOOTH.encrypted_ballot.clearPlaintexts();
// remove audit trail
BOOTH.audit_trail = null;
// we're ready to leave the site
BOOTH.started_p = false;
// submit the form
$('#send_ballot_form').submit();
};
BOOTH.show_receipt = function() {
UTILS.open_window_with_content("Your smart ballot tracker for " + BOOTH.election.name + ": " + BOOTH.encrypted_ballot_hash);
};
BOOTH.do_done = function() {
BOOTH.started_p = false;
};
</script>
<div id="page">
<div id="progress_div" style="display:none; width: 500px; margin:auto;">
<table width="100%">
<tr><td id="progress_1">(1) Select</td><td id="progress_2">(2) Encrypt</td><td id="progress_3">(3) Submit</td></tr>
</table>
</div>
<div id="election_div" class="panel">
<h3>Checking capabilities and loading election booth...</h3>
<div align="center"><img src="loading.gif" /><br />This may take up to 10 seconds</div>
</div>
<div id="error_div" class="panel" style="display: none;">
<h3>There's a problem</h3>
<p>
It appears that your browser does not have Java enabled. Helios needs Java to perform encryption within the browser.
</p>
<p>
You may be able to install Java by visiting <a target="_new" href="http://java.com">java.com</a>.
</p>
</div>
<div id="question_div" class="panel">
</div>
<div id="confirm_div" class="panel">
</div>
<div id="processing_div" class="panel" style="display:none;">
<h3 align="center">Processing....</h3>
</div>
<div id="encrypting_div" class="panel" style="display:none;">
<h3 align="center">Helios is now encrypting your ballot<br />
<img src="encrypting.gif" /> <span style="font-size:0.7em; display:none;" id="percent_done_container">(<span id="percent_done">0</span>%)</span></h3>
<p>
<b>If your browser complains about "long-running JavaScript"</b>, click Continue and let Helios finish encrypting your ballot.
</p>
<p>
<b>Why so long and complicated?</b>: Helios employs state-of-the-art cryptography to protect the secrecy of your ballot while giving you a smart tracker to ensure your ballot was counted.
No other web-based voting system provides this level of election integrity. Even the latest browsers don't support this technology natively, which means Helios is encrypting your data in JavaScript, and that takes a little bit more time than you may be accustomed to.
</p>
</div>
<div id="seal_div" class="panel">
</div>
<div id="audit_div" class="panel">
</div>
</div>
<br clear="both" />
</div>
<div id="footer">&nbsp;</div>
</div>
<div id="applet_div">
</div>
</body>
</html>
Jump to Line
Something went wrong with that request. Please try again.