diff --git a/.gitignore b/.gitignore index 681947f7ff38ef3bb2c398d79c496ecc6172d9cb..a2dca2bee7e168df398b922f596e4e4a6e230a26 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ media/* venv celerybeat-* env.sh -.cache \ No newline at end of file +.cache +.idea/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index be482e4e44bdb9307de52e9541d65cef31c05445..6b93e8ab16e9d955e4a725263820f997c0ed30c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,3 +12,6 @@ addons: postgresql: "9.3" before_script: - psql -c 'create database helios;' -U postgres +before_install: + - export BOTO_CONFIG=/dev/null + diff --git a/helios/__init__.py b/helios/__init__.py index 06f05140bb83798916f63786610efa87d916e26a..3a516d7ce25e875c70086e5fa368e1af73eb6cd4 100644 --- a/helios/__init__.py +++ b/helios/__init__.py @@ -1,7 +1,5 @@ - from django.conf import settings from django.core.urlresolvers import reverse -from helios.views import election_shortcut TEMPLATE_BASE = settings.HELIOS_TEMPLATE_BASE or "helios/templates/base.html" diff --git a/helios/crypto/utils.py b/helios/crypto/utils.py index dd395a598fbbec1df4bce90f81e7d95a9228b32b..258f4130cb45bf509bc62710674363830ebe6a5d 100644 --- a/helios/crypto/utils.py +++ b/helios/crypto/utils.py @@ -1,7 +1,7 @@ """ Crypto Utils """ - +import hashlib import hmac, base64, json from hashlib import sha256 @@ -21,3 +21,11 @@ def to_json(d): def from_json(json_str): if not json_str: return None return json.loads(json_str) + + +def do_hmac(k,s): + """ + HMAC a value with a key, hex output + """ + mac = hmac.new(k, s, hashlib.sha1) + return mac.hexdigest() \ No newline at end of file diff --git a/helios/templates/election_keygenerator.html b/helios/templates/election_keygenerator.html index 60a2c55c703964884b93c44739c301ed47541561..4521fc9393185cf177f5e4cba6d3f69b2193ef46 100644 --- a/helios/templates/election_keygenerator.html +++ b/helios/templates/election_keygenerator.html @@ -76,11 +76,23 @@ function show_sk() { } function download_sk() { - UTILS.open_window_with_content(jQuery.toJSON(SECRET_KEY), "application/json"); + $('#pk_content').show(); + $('#sk_content').html(jQuery.toJSON(SECRET_KEY)); +} + +function download_sk_to_file(filename) { + var element = document.createElement('a'); + element.setAttribute('href','data:text/plain;charset=utf-8,'+ encodeURIComponent(jQuery.toJSON(SECRET_KEY))); + element.setAttribute('download', filename); + element.style.display = 'none'; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); } function show_pk() { $('#sk_download').hide(); + $('#pk_content').hide(); $('#pk_hash').show(); $('#pk_form').show(); } @@ -101,7 +113,7 @@ function show_pk() { <span id="buttons"><button onclick="generate_keypair(); return false;" id="generate_button">Generate Election Keys</button></span> -<br /><br /> +<br /> If you've already generated a keypair, you can <a href="javascript:show_key_reuse()">reuse it</a>. </p> @@ -126,11 +138,24 @@ Your key has been generated, but you may choose to<br /><a href="javascript:clea </span> <p> - <button style="font-size:16pt;" onclick="download_sk(); $('#pk_link').show();">Save your secret key</button> + <button style="font-size:16pt;" onclick="download_sk(); $('#pk_link').show();">Show my secret key</button> </p> +</div> -<p style="display: none;" id="pk_link"> - <a href="javascript:show_pk();">ok, I've saved the key, let's move on</a>. +<div style="display:none;" id="pk_content"> + <p>Bellow is your trustee secret key content. Please copy its content and save it securely. <br> + You can also click to dowload it to a file. + And please don't lose it! Otherwise it will not be possible to decrypt the election tally.<br> + </p> + <textarea id="sk_content" rows="5" wrap="soft" cols="50" style="height: 25em;"></textarea> +</div> + +<div style="display:none;" id="pk_link"> +<p> +<a id="download_to_file" href="javascript:download_sk_to_file('trustee_key_for_{{election.name}}.txt');">download private key to a file</a> +</p> +<p> + <a href="javascript:show_pk();">ok, I've saved the key, let's move on</a> </p> </div> diff --git a/helios/templates/trustee_check_sk.html b/helios/templates/trustee_check_sk.html index f6b615e43b9f1400b752a39df6dfa491e5ce8de9..81afe42d4945f3b9856dc31a1eab792d3647c6db 100644 --- a/helios/templates/trustee_check_sk.html +++ b/helios/templates/trustee_check_sk.html @@ -59,7 +59,7 @@ To verify that you have the right secret key, paste it here: <p> <form onsubmit="check_sk(this.secret_key.value); this.secret_key.value=''; return false;"> -<textarea name="secret_key" cols="60" rows ="5" wrap="soft"> +<textarea name="secret_key" cols="60" rows ="5" wrap="soft" style="height: 25em;"> </textarea> <br /> <input type="submit" value="check" /> diff --git a/helios/templates/trustee_decrypt_and_prove.html b/helios/templates/trustee_decrypt_and_prove.html index d4cbe438493aa0c6c9a00778acccaece94bc5956..05153b8a349b2ae8b91b5de283f2bc850ab89f63 100644 --- a/helios/templates/trustee_decrypt_and_prove.html +++ b/helios/templates/trustee_decrypt_and_prove.html @@ -157,7 +157,7 @@ function reset() { <form onsubmit="return false;"> <h3>FIRST STEP: enter your secret key</h3> - <textarea id="sk_textarea" cols="60" rows="5"></textarea> + <textarea id="sk_textarea" cols="60" rows="5" style="height: 25em;"></textarea> </form> <p id="tally_section"> <button onclick="do_tally();">Generate partial decryption</button> @@ -187,13 +187,11 @@ function reset() { When you're ready, you can submit this result to the server. </p> Your partial decryption:<br /> - <form action="javascript:submit_result();"> - <textarea id="result_textarea" cols="60" rows="5" wrap="soft"></textarea><br /><br /> - <input type="submit" value="Upload decryption factors to server" /> - </form> - <br /> - <a href="javascript:reset()">reset and restart decryption process</a> - <br /> + <p> + <textarea id="result_textarea" cols="60" rows="5" wrap="soft" style="height: 25em;"></textarea> + <button onclick="submit_result();">Upload decryption factors to server</button> + </p> + <p><a href="javascript:reset()">reset and restart decryption process</a></p> </div> <div id="done_div"> diff --git a/helios/templates/voters_list.html b/helios/templates/voters_list.html index 1409770868a887eafd24135e982a0d81d5c2155d..fffe57fdbc717c6c97cb39f4527938bccdacf811 100644 --- a/helios/templates/voters_list.html +++ b/helios/templates/voters_list.html @@ -112,6 +112,11 @@ Voters {{voters_page.start_index}} - {{voters_page.end_index}} (of {{total_voter <table class="pretty"> <tr> {% if admin_p or not election.use_voter_aliases %} +{% if admin_p %} +<th style="width: 80px;">Actions</th> +<th>Login</th> +<th>Email Address</th> +{% endif %} <th>Name</th> {% endif %} @@ -123,19 +128,22 @@ Voters {{voters_page.start_index}} - {{voters_page.end_index}} (of {{total_voter {% for voter in voters %} <tr> {% if admin_p or not election.use_voter_aliases %} -<td> {% if admin_p %} +<td style="white-space: nowrap;"> {% if election.frozen_at %} [<a href="{% url "helios.views.voters_email" election.uuid %}?voter_id={{voter.voter_login_id}}">email</a>] {% endif %} [<a onclick="return confirm('are you sure you want to remove {{voter.name}} ?');" href="{% url "helios.views.voter_delete" election.uuid voter.uuid %}">x</a>] +</td> +<td>{{voter.voter_login_id}}</td> +<td>{{voter.voter_email}}</td> {% endif %} -<img class="small-logo" src="/static/auth/login-icons/{{voter.voter_type}}.png" alt="{{voter.voter_type}}" /> {{voter.name}}</td> +<td><img class="small-logo" src="/static/auth/login-icons/{{voter.voter_type}}.png" alt="{{voter.voter_type}}" /> {{voter.name}}</td> {% endif %} {% if election.use_voter_aliases %} <td>{{voter.alias}}</td> {% endif %} -<td><tt style="font-size: 1.4em;;">{% if voter.vote_hash %}{{voter.vote_hash}} <span style="font-size:0.8em;">[<a href="{% url "helios.views.castvote_shortcut" vote_tinyhash=voter.vote_tinyhash %}">view</a>]</span>{% else %}—{% endif %}</tt></td> +<td><tt style="font-size: 1.4em;">{% if voter.vote_hash %}{{voter.vote_hash}} <span style="font-size:0.8em;">[<a href="{% url "helios.views.castvote_shortcut" vote_tinyhash=voter.vote_tinyhash %}">view</a>]</span>{% else %}—{% endif %}</tt></td> </tr> {% endfor %} </table> diff --git a/helios/urls.py b/helios/urls.py index 67c0077672cd476a705ca58d4bf9ab6ca4288e07..8effba1727a9bbd7623471c2c70f00ae43dff62e 100644 --- a/helios/urls.py +++ b/helios/urls.py @@ -1,12 +1,8 @@ # -*- coding: utf-8 -*- -from django.conf.urls import * - -from django.conf import settings +from django.conf.urls import patterns, include from views import * -urlpatterns = None - urlpatterns = patterns('', (r'^autologin$', admin_autologin), (r'^testcookie$', test_cookie), diff --git a/helios/utils.py b/helios/utils.py index d053dc11fec961d94b6edeee4879826107622d5c..03095a75074b55123571991a8e35cf0924248431 100644 --- a/helios/utils.py +++ b/helios/utils.py @@ -5,9 +5,7 @@ Ben Adida - ben@adida.net 2005-04-11 """ -import urllib, re, sys, datetime, urlparse, string - -import boto.ses +import urllib, re, datetime, string # utils from helios_auth, too from helios_auth.utils import * @@ -15,14 +13,6 @@ from helios_auth.utils import * from django.conf import settings import random, logging -import hashlib, hmac, base64 - -def do_hmac(k,s): - """ - HMAC a value with a key, hex output - """ - mac = hmac.new(k, s, hashlib.sha1) - return mac.hexdigest() def split_by_length(str, length, rejoin_with=None): @@ -167,7 +157,7 @@ def one_val_raw_sql(raw_sql, values=[]): """ for a simple aggregate """ - from django.db import connection, transaction + from django.db import connection cursor = connection.cursor() cursor.execute(raw_sql, values) diff --git a/helios/views.py b/helios/views.py index 2959463db664c989371b021338ace8a48abca19f..3337459fae6dd93048807f7982f194873a2cc552 100644 --- a/helios/views.py +++ b/helios/views.py @@ -6,41 +6,41 @@ Ben Adida (ben@adida.net) """ from django.core.urlresolvers import reverse -from django.core.mail import send_mail from django.core.paginator import Paginator from django.core.exceptions import PermissionDenied -from django.http import * +from django.http import HttpResponse, Http404, HttpResponseRedirect, HttpResponseForbidden from django.db import transaction, IntegrityError -from mimetypes import guess_type - from validate_email import validate_email -import csv, urllib, os, base64 +import urllib, os, base64 from crypto import algs, electionalgs, elgamal from crypto import utils as cryptoutils from workflows import homomorphic -from helios import utils as helios_utils -from view_utils import * +from helios import utils, VOTERS_EMAIL, VOTERS_UPLOAD +from view_utils import SUCCESS, FAILURE, return_json, render_template, render_template_raw -from helios_auth.security import * +from helios_auth.security import check_csrf, login_required, get_user, save_in_session_across_logouts from helios_auth.auth_systems import AUTH_SYSTEMS, can_list_categories from helios_auth.models import AuthenticationExpired -from helios import security from helios_auth import views as auth_views import tasks -from security import * -from helios_auth.security import get_user, save_in_session_across_logouts +from security import (election_view, election_admin, + trustee_check, set_logged_in_trustee, + can_create_election, user_can_see_election, get_voter, + user_can_admin_election, user_can_feature_election) import uuid, datetime +import logging -from models import * +from models import User, Election, CastVote, Voter, VoterFile, Trustee, AuditedBallot +import datatypes -import forms, signals +import forms # Parameters for everything ELGAMAL_PARAMS = elgamal.Cryptosystem() @@ -196,7 +196,7 @@ def election_new(request): election_params = dict(election_form.cleaned_data) # is the short name valid - if helios_utils.urlencode(election_params['short_name']) == election_params['short_name']: + if utils.urlencode(election_params['short_name']) == election_params['short_name']: election_params['uuid'] = str(uuid.uuid1()) election_params['cast_url'] = settings.SECURE_URL_HOST + reverse(one_election_cast, args=[election_params['uuid']]) @@ -293,8 +293,8 @@ def election_badge(request, election): @election_view() def one_election_view(request, election): user = get_user(request) - admin_p = security.user_can_admin_election(user, election) - can_feature_p = security.user_can_feature_election(user, election) + admin_p = user_can_admin_election(user, election) + can_feature_p = user_can_feature_election(user, election) notregistered = False eligible_p = True @@ -383,7 +383,7 @@ def list_trustees(request, election): def list_trustees_view(request, election): trustees = Trustee.get_by_election(election) user = get_user(request) - admin_p = security.user_can_admin_election(user, election) + admin_p = user_can_admin_election(user, election) return render_template(request, 'list_trustees', {'election': election, 'trustees': trustees, 'admin_p':admin_p}) @@ -424,14 +424,9 @@ def trustee_login(request, election_short_name, trustee_email, trustee_secret): if trustee.secret == trustee_secret: set_logged_in_trustee(request, trustee) return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(trustee_home, args=[election.uuid, trustee.uuid])) - else: - # bad secret, we'll let that redirect to the front page - pass - else: - # no such trustee - raise Http404 - - return HttpResponseRedirect(settings.SECURE_URL_HOST + "/") + # bad secret or no such trustee + raise Http404("Trustee not recognized.") + raise Http404("No election {} found.".format(election_short_name)) @election_admin() def trustee_send_url(request, election, trustee_uuid): @@ -451,7 +446,7 @@ Your trustee dashboard is at Helios """ % (election.name, url) - helios_utils.send_email(settings.SERVER_EMAIL, ["%s <%s>" % (trustee.name, trustee.email)], 'your trustee homepage for %s' % election.name, body) + utils.send_email(settings.SERVER_EMAIL, ["%s <%s>" % (trustee.name, trustee.email)], 'your trustee homepage for %s' % election.name, body) logging.info("URL %s " % url) return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(list_trustees_view, args = [election.uuid])) @@ -476,7 +471,7 @@ def trustee_upload_pk(request, election, trustee): if not trustee.public_key.verify_sk_proof(trustee.pok, algs.DLog_challenge_generator): raise Exception("bad pok for this public key") - trustee.public_key_hash = utils.hash_b64(utils.to_json(trustee.public_key.toJSONDict())) + trustee.public_key_hash = cryptoutils.hash_b64(utils.to_json(trustee.public_key.toJSONDict())) trustee.save() @@ -512,8 +507,7 @@ def encrypt_ballot(request, election): perform the ballot encryption given answers_json, a JSON'ified list of list of answers (list of list because each question could have a list of answers if more than one.) """ - # FIXME: maybe make this just request.POST at some point? - answers = utils.from_json(request.REQUEST['answers_json']) + answers = utils.from_json(request.POST['answers_json']) ev = homomorphic.EncryptedVote.fromElectionAndAnswers(election, answers) return ev.ld_object.includeRandomness().toJSONDict() @@ -552,7 +546,13 @@ def password_voter_login(request, election): """ # the URL to send the user to after they've logged in - return_url = request.REQUEST.get('return_url', reverse(one_election_cast_confirm, args=[election.uuid])) + if request.method == "GET" and 'return_url' in request.GET: + return_url = request.GET['return_url'] + elif request. method == "POST" and 'return_url' in request.POST: + return_url = request.POST['return_url'] + else: + return_url = reverse(one_election_cast_confirm, args=[election.uuid]) + bad_voter_login = (request.GET.get('bad_voter_login', "0") == "1") if request.method == "GET": @@ -568,7 +568,7 @@ def password_voter_login(request, election): 'password_login_form': password_login_form, 'bad_voter_login' : bad_voter_login}) - login_url = request.REQUEST.get('login_url', None) + login_url = request.GET.get('login_url', None) if not login_url: # login depending on whether this is a private election @@ -923,7 +923,7 @@ def one_election_set_featured(request, election): """ user = get_user(request) - if not security.user_can_feature_election(user, election): + if not user_can_feature_election(user, election): raise PermissionDenied() featured_p = bool(int(request.GET['featured_p'])) @@ -987,7 +987,7 @@ def one_election_copy(request, election): def one_election_questions(request, election): questions_json = utils.to_json(election.questions) user = get_user(request) - admin_p = security.user_can_admin_election(user, election) + admin_p = user_can_admin_election(user, election) return render_template(request, 'election_questions', {'election': election, 'questions_json' : questions_json, 'admin_p': admin_p}) @@ -1191,7 +1191,7 @@ def voters_list_pretty(request, election): order_by = 'alias' user = get_user(request) - admin_p = security.user_can_admin_election(user, election) + admin_p = user_can_admin_election(user, election) categories = None eligibility_category_id = None @@ -1204,7 +1204,7 @@ def voters_list_pretty(request, election): return user_reauth(request, user) # files being processed - voter_files = election.voterfile_set.all() + voter_files = election.voterfile_set.all().order_by('-uploaded_at') # load a bunch of voters # voters = Voter.get_by_election(election, order_by=order_by) @@ -1224,9 +1224,9 @@ def voters_list_pretty(request, election): return render_template(request, 'voters_list', {'election': election, 'voters_page': voters_page, 'voters': voters_page.object_list, 'admin_p': admin_p, - 'email_voters': helios.VOTERS_EMAIL, + 'email_voters': VOTERS_EMAIL, 'limit': limit, 'total_voters': total_voters, - 'upload_p': helios.VOTERS_UPLOAD, 'q' : q, + 'upload_p': VOTERS_UPLOAD, 'q' : q, 'voter_files': voter_files, 'categories': categories, 'eligibility_category_id' : eligibility_category_id}) @@ -1330,7 +1330,7 @@ def voters_upload_cancel(request, election): @election_admin(frozen=True) def voters_email(request, election): - if not helios.VOTERS_EMAIL: + if not VOTERS_EMAIL: return HttpResponseRedirect(settings.SECURE_URL_HOST + reverse(one_election_view, args=[election.uuid])) TEMPLATES = [ ('vote', 'Time to Vote'), @@ -1339,11 +1339,11 @@ def voters_email(request, election): ('result', 'Election Result') ] - template = request.REQUEST.get('template', 'vote') + template = request.GET.get('template', 'vote') if not template in [t[0] for t in TEMPLATES]: raise Exception("bad template") - voter_id = request.REQUEST.get('voter_id', None) + voter_id = request.GET.get('voter_id', None) if voter_id: voter = Voter.get_by_election_and_voter_id(election, voter_id) diff --git a/helios_auth/auth_systems/facebookclient/djangofb/default_app/urls.py b/helios_auth/auth_systems/facebookclient/djangofb/default_app/urls.py index 850184440c5fc153df12bf71e792cdccfe9baa57..5e793a6ae4a68e8662f45546b6475e5e7eb7a92c 100644 --- a/helios_auth/auth_systems/facebookclient/djangofb/default_app/urls.py +++ b/helios_auth/auth_systems/facebookclient/djangofb/default_app/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls.defaults import * +from django.conf.urls import * urlpatterns = patterns('{{ project }}.{{ app }}.views', (r'^$', 'canvas'), diff --git a/helios_auth/auth_systems/google.py b/helios_auth/auth_systems/google.py index b6eb57c4390ab54bd747bc03971bc42ba5fbca1f..7caa32fc2f03e810f2f675b587d37ea01d41cf40 100644 --- a/helios_auth/auth_systems/google.py +++ b/helios_auth/auth_systems/google.py @@ -50,11 +50,11 @@ def get_user_info_after_auth(request): # get the nice name http = httplib2.Http(".cache") http = credentials.authorize(http) - (resp_headers, content) = http.request("https://www.googleapis.com/plus/v1/people/me", "GET") + (resp_headers, content) = http.request("https://people.googleapis.com/v1/people/me?personFields=names", "GET") response = json.loads(content) - name = response['displayName'] + name = response['names'][0]['displayName'] # watch out, response also contains email addresses, but not sure whether thsoe are verified or not # so for email address we will only look at the id_token diff --git a/helios_auth/security/__init__.py b/helios_auth/security/__init__.py index b036067d9b837e4156df46fd9a6efdc78516b98f..facc0c32d73b225997acb529de57b501de2f8aad 100644 --- a/helios_auth/security/__init__.py +++ b/helios_auth/security/__init__.py @@ -10,6 +10,7 @@ from functools import update_wrapper from django.http import HttpResponse, Http404, HttpResponseRedirect from django.core.exceptions import * from django.conf import settings +from django.http import HttpResponseNotAllowed import oauth diff --git a/helios_auth/urls.py b/helios_auth/urls.py index e4dca398503d1e2f802fd4811330b66b9020a1a9..11d10139530db6f84becb2cf5f55326641fb2163 100644 --- a/helios_auth/urls.py +++ b/helios_auth/urls.py @@ -1,31 +1,33 @@ + """ Authentication URLs Ben Adida (ben@adida.net) """ -from django.conf.urls import * +from django.conf.urls import url -from views import * -from auth_systems.password import password_login_view, password_forgotten_view -from auth_systems.twitter import follow_view +import views +from settings import AUTH_ENABLED_AUTH_SYSTEMS -urlpatterns = patterns('', +urlpatterns = [ # basic static stuff - (r'^$', index), - (r'^logout$', logout), - (r'^start/(?P<system_name>.*)$', start), + url(r'^$', views.index), + url(r'^logout$', views.logout), + url(r'^start/(?P<system_name>.*)$', views.start), # weird facebook constraint for trailing slash - (r'^after/$', after), - (r'^why$', perms_why), - (r'^after_intervention$', after_intervention), - - ## should make the following modular + url(r'^after/$', views.after), + url(r'^why$', views.perms_why), + url(r'^after_intervention$', views.after_intervention), +] - # password auth - (r'^password/login', password_login_view), - (r'^password/forgot', password_forgotten_view), +# password auth +if 'password' in AUTH_ENABLED_AUTH_SYSTEMS: + from auth_systems.password import password_login_view, password_forgotten_view + urlpatterns.append(url(r'^password/login', password_login_view)) + urlpatterns.append(url(r'^password/forgot', password_forgotten_view)) - # twitter - (r'^twitter/follow', follow_view), -) +# twitter +if 'twitter' in AUTH_ENABLED_AUTH_SYSTEMS: + from auth_systems.twitter import follow_view + urlpatterns.append(url(r'^twitter/follow', follow_view)) diff --git a/heliosbooth/boothworker-single.js b/heliosbooth/boothworker-single.js index edb1debb5132006a7efcd8af99706ebd19b9d1bb..1c87986cad03eef9b61fd5320a0ff8659d6e8af3 100644 --- a/heliosbooth/boothworker-single.js +++ b/heliosbooth/boothworker-single.js @@ -49,12 +49,16 @@ function do_encrypt(message) { // receive either // a) an election and an integer position of the question // that this worker will be used to encrypt -// {'type': 'setup', 'question_num' : 2, 'election' : election_json} +// {'type': 'setup', 'election': election_json} // // b) an answer that needs encrypting -// {'type': 'encrypt', 'answer' : answer_json} +// {'type': 'encrypt', 'q_num': 2, 'id': id, 'answer': answer_json} // self.onmessage = function(event) { // dispatch to method - self['do_' + event.data.type](event.data); -} + if (event.data.type === "setup") { + do_setup(event.data); + } else if (event.data.type === "encrypt") { + do_encrypt(event.data); + } +}; diff --git a/heliosbooth/css/forms.css b/heliosbooth/css/forms.css index ee1a141a51660875e8ca2ee388e6d0baa1633b12..ae4cbb7fcbf9e2208ab08c6585b68c7a5144fbe2 100644 --- a/heliosbooth/css/forms.css +++ b/heliosbooth/css/forms.css @@ -7,7 +7,7 @@ form.prettyform label,input,textarea,select { line-height: 1.8; } -form.prettyform label { +form.prettyform label:not(.answer) { display: block; text-align: right; float: left; diff --git a/heliosbooth/js/jscrypto/random.js b/heliosbooth/js/jscrypto/random.js index dd69f9d4fcb14add53e641eef94072b377b93844..8e363a6d0d561d3bbfd21a515b472b717d7cefde 100644 --- a/heliosbooth/js/jscrypto/random.js +++ b/heliosbooth/js/jscrypto/random.js @@ -26,10 +26,9 @@ Random.getRandomInteger = function(max) { var bit_length = max.bitLength(); Random.setupGenerator(); var random; - random = sjcl.random.randomWords(Math.ceil(bit_length / 32)+2, 0); + random = sjcl.random.randomWords(Math.ceil(bit_length / 32) + 2, 6); // we get a bit array instead of a BigInteger in this case var rand_bi = new BigInt(sjcl.codec.hex.fromBits(random), 16); return rand_bi.mod(max); - return BigInt._from_java_object(random).mod(max); }; diff --git a/heliosbooth/templates/question.html b/heliosbooth/templates/question.html index 776eefdf5be00e3515be452728a743e9496731e6..8945e456ce7703cad8ab925c04c490435b375a7c 100644 --- a/heliosbooth/templates/question.html +++ b/heliosbooth/templates/question.html @@ -25,14 +25,19 @@ as many as you approve of </p> {#foreach $T.question.answers as answer} -<div id="answer_label_{$T.question_num}_{$T.answer_ordering[$T.answer$index]}"><input type="checkbox" class="ballot_answer" id="answer_{$T.question_num}_{$T.answer_ordering[$T.answer$index]}" name="answer_{$T.question_num}_{$T.answer_ordering[$T.answer$index]}" value="yes" onclick="BOOTH.click_checkbox({$T.question_num}, {$T.answer_ordering[$T.answer$index]}, this.checked);" /> {$T.question.answers[$T.answer_ordering[$T.answer$index]]} +<div id="answer_label_{$T.question_num}_{$T.answer_ordering[$T.answer$index]}"> + <input type="checkbox" class="ballot_answer" id="answer_{$T.question_num}_{$T.answer_ordering[$T.answer$index]}" name="answer_{$T.question_num}_{$T.answer_ordering[$T.answer$index]}" value="yes" onclick="BOOTH.click_checkbox({$T.question_num}, {$T.answer_ordering[$T.answer$index]}, this.checked);" /> -{#if $T.question.answer_urls && $T.question.answer_urls[$T.answer_ordering[$T.answer$index]] && $T.question.answer_urls[$T.answer_ordering[$T.answer$index]] != ""} - -<span style="font-size: 12pt;"> -[<a target="_blank" href="{$T.question.answer_urls[$T.answer_ordering[$T.answer$index]]}" rel="noopener noreferrer">more info</a>] -</span> -{#/if} + <label class="answer" for="answer_{$T.question_num}_{$T.answer_ordering[$T.answer$index]}"> + {$T.question.answers[$T.answer_ordering[$T.answer$index]]} + + {#if $T.question.answer_urls && $T.question.answer_urls[$T.answer_ordering[$T.answer$index]] && $T.question.answer_urls[$T.answer_ordering[$T.answer$index]] != ""} + + <span style="font-size: 12pt;"> + [<a target="_blank" href="{$T.question.answer_urls[$T.answer_ordering[$T.answer$index]]}" rel="noopener noreferrer">more info</a>] + </span> + {#/if} + </label> </div> {#/for} diff --git a/heliosverifier/css/forms.css b/heliosverifier/css/forms.css index ee1a141a51660875e8ca2ee388e6d0baa1633b12..ae4cbb7fcbf9e2208ab08c6585b68c7a5144fbe2 100644 --- a/heliosverifier/css/forms.css +++ b/heliosverifier/css/forms.css @@ -7,7 +7,7 @@ form.prettyform label,input,textarea,select { line-height: 1.8; } -form.prettyform label { +form.prettyform label:not(.answer) { display: block; text-align: right; float: left; diff --git a/heliosverifier/verify.html b/heliosverifier/verify.html index 344a277648a0791a8f6fa134f274f978ce0549b4..0e06b17c2d172f50e0e91290d1e4439806502033 100644 --- a/heliosverifier/verify.html +++ b/heliosverifier/verify.html @@ -97,151 +97,158 @@ function load_election_and_ballots(election_url) { // the hash will be computed within the setup function call now $.get(election_url, function(raw_json) { - election = HELIOS.Election.fromJSONString(raw_json); - result_append("loaded election: " + election.name); - result_append("election fingerprint: " + election.get_hash()); - - var tally = []; + try { + election = HELIOS.Election.fromJSONString(raw_json); + result_append("loaded election: " + election.name); + result_append("election fingerprint: " + election.get_hash()); + + var tally = []; - $(election.questions).each(function(qnum, q) { - if (q.tally_type != "homomorphic") { - result_append("PROBLEM: this election is not a straight-forward homomorphic-tally election. As a result, Helios cannot currently verify it."); - return; - } + $(election.questions).each(function(qnum, q) { + if (q.tally_type != "homomorphic") { + result_append("PROBLEM: this election is not a straight-forward homomorphic-tally election. As a result, Helios cannot currently verify it."); + return; + } - tally[qnum] = $(q.answers).map(function(anum, a) { - return 1; + tally[qnum] = $(q.answers).map(function(anum, a) { + return 1; + }); }); - }); - result_append("loading list of voters..."); - - // load voter list - load_ballot_list(election_url, [], null, function(ballot_list) { - result_append("loaded voter list, now loading ballots for each.."); + result_append("loading list of voters..."); - // load all ballots - load_ballots(election_url, ballot_list, [], function(ballots) { - result_append(""); - result_append("<h3>Ballots</h3>"); - // now load each ballot - $(ballots).each(function(i, cast_vote){ - - if (cast_vote.vote == null) - return; - - var vote = HELIOS.EncryptedVote.fromJSONObject(cast_vote.vote, election); - result_append("Voter #" + (i+1)); - result_append("-- UUID: " + cast_vote.voter_uuid); - result_append("-- Ballot Tracking Number: " + vote.get_hash()); - - vote.verifyProofs(election.public_key, function(answer_num, choice_num, result, choice) { - overall_result = overall_result && result; - if (choice_num != null) { - // keep track of tally - tally[answer_num][choice_num] = choice.multiply(tally[answer_num][choice_num]); - - result_append("Question #" + (answer_num+1) + ", Option #" + (choice_num+1) + " -- " + pretty_result(result)); - } else { - result_append("Question #" + (answer_num+1) + " OVERALL -- " + pretty_result(result)); - } - }); + // load voter list + load_ballot_list(election_url, [], null, function(ballot_list) { + result_append("loaded voter list, now loading ballots for each.."); + // load all ballots + load_ballots(election_url, ballot_list, [], function(ballots) { result_append(""); - }); - - // get the election result - $.get(election_url + "/result", function(result) { - var results = $.secureEvalJSON(result); - - // get the trustees and proofs - $.get(election_url + "/trustees/", function(trustees_json) { - trustees = $.secureEvalJSON(trustees_json); - - // create the Helios objects - trustees = $(trustees).map(function(i, trustee) {return HELIOS.Trustee.fromJSONObject(trustee)}); - - // the public key that we'll check - var combined_key = 1; - - result_append("<h3>Trustees</h3>"); - // verify the keys - $(trustees).each(function(i, trustee) { - result_append("Trustee #" + (i+1) + ": " + trustee.email); - if (trustee.public_key.verifyKnowledgeOfSecretKey(trustee.pok, ElGamal.fiatshamir_dlog_challenge_generator)) { - result_append("-- PK " + trustee.public_key_hash + " -- VERIFIED."); + result_append("<h3>Ballots</h3>"); + // now load each ballot + $(ballots).each(function(i, cast_vote){ + + if (cast_vote.vote == null) + return; + + var vote = HELIOS.EncryptedVote.fromJSONObject(cast_vote.vote, election); + result_append("Voter #" + (i+1)); + result_append("-- UUID: " + cast_vote.voter_uuid); + result_append("-- Ballot Tracking Number: " + vote.get_hash()); + + vote.verifyProofs(election.public_key, function(answer_num, choice_num, result, choice) { + overall_result = overall_result && result; + if (choice_num != null) { + // keep track of tally + tally[answer_num][choice_num] = choice.multiply(tally[answer_num][choice_num]); + + result_append("Question #" + (answer_num+1) + ", Option #" + (choice_num+1) + " -- " + pretty_result(result)); + } else { + result_append("Question #" + (answer_num+1) + " OVERALL -- " + pretty_result(result)); + } + }); + + result_append(""); + }); - // FIXME check the public key hash - } else { - result_append("==== ERROR for PK of trustee " + trustee.email); - overall_result = false; - } + // get the election result + $.get(election_url + "/result", function(result) { + var results = $.secureEvalJSON(result); - combined_key = trustee.public_key.multiply(combined_key); + // get the trustees and proofs + $.get(election_url + "/trustees/", function(trustees_json) { + trustees = $.secureEvalJSON(trustees_json); - result_append(""); - }); + // create the Helios objects + trustees = $(trustees).map(function(i, trustee) {return HELIOS.Trustee.fromJSONObject(trustee)}); - // verify the combination of the keys into the final public key - if (combined_key.equals(election.public_key)) { - result_append("election public key CORRECTLY FORMED"); - } else { - result_append("==== ERROR, election public key doesn't match"); - overall_result = false; - } + // the public key that we'll check + var combined_key = 1; - result_append("<h3>Tally</h3>"); + result_append("<h3>Trustees</h3>"); + // verify the keys + $(trustees).each(function(i, trustee) { + result_append("Trustee #" + (i+1) + ": " + trustee.email); + if (trustee.public_key.verifyKnowledgeOfSecretKey(trustee.pok, ElGamal.fiatshamir_dlog_challenge_generator)) { + result_append("-- PK " + trustee.public_key_hash + " -- VERIFIED."); - $(tally).each(function(q_num, q) { - result_append("Question #" + (q_num+1) + ": " + election.questions[q_num].short_name); - $(q).each(function(a_num, a) { - var plaintext = new ElGamal.Plaintext(election.public_key.g.modPow(BigInt.fromInt(results[q_num][a_num]), election.public_key.p), election.public_key); + // FIXME check the public key hash + } else { + result_append("==== ERROR for PK of trustee " + trustee.email); + overall_result = false; + } - var check = true; - result_append("Answer #" + (a_num + 1) + ": " + election.questions[q_num].answers[a_num] + " - COUNT = " + results[q_num][a_num]); + combined_key = trustee.public_key.multiply(combined_key); - var decryption_factors = []; + result_append(""); + }); - // go through the trustees' decryption factors and verify each one - $(trustees).each(function(t_num, trustee) { - if (trustee.public_key.verifyDecryptionFactor(a, trustee.decryption_factors[q_num][a_num], - trustee.decryption_proofs[q_num][a_num], ElGamal.fiatshamir_challenge_generator)) { - result_append("-- Trustee " + trustee.email + ": decryption factor verifies"); + // verify the combination of the keys into the final public key + if (combined_key.equals(election.public_key)) { + result_append("election public key CORRECTLY FORMED"); + } else { + result_append("==== ERROR, election public key doesn't match"); + overall_result = false; + } + + result_append("<h3>Tally</h3>"); + + $(tally).each(function(q_num, q) { + result_append("Question #" + (q_num+1) + ": " + election.questions[q_num].short_name); + $(q).each(function(a_num, a) { + var plaintext = new ElGamal.Plaintext(election.public_key.g.modPow(BigInt.fromInt(results[q_num][a_num]), election.public_key.p), election.public_key); + + var check = true; + result_append("Answer #" + (a_num + 1) + ": " + election.questions[q_num].answers[a_num] + " - COUNT = " + results[q_num][a_num]); + + var decryption_factors = []; + + // go through the trustees' decryption factors and verify each one + $(trustees).each(function(t_num, trustee) { + if (trustee.public_key.verifyDecryptionFactor(a, trustee.decryption_factors[q_num][a_num], + trustee.decryption_proofs[q_num][a_num], ElGamal.fiatshamir_challenge_generator)) { + result_append("-- Trustee " + trustee.email + ": decryption factor verifies"); + } else { + result_append("==== ERROR with Trustee " + trustee.email + ": decryption factor does not verify"); + check= false; + overall_result = false; + } + + decryption_factors.push(trustee.decryption_factors[q_num][a_num]); + }); + + // recheck decryption factors + var expected_value = election.public_key.g.modPow(BigInt.fromInt(results[q_num][a_num]), election.public_key.p); + var recomputed_value = a.decrypt(decryption_factors).getM(); + if (expected_value.equals(recomputed_value)) { } else { - result_append("==== ERROR with Trustee " + trustee.email + ": decryption factor does not verify"); - check= false; + check = false; overall_result = false; } - decryption_factors.push(trustee.decryption_factors[q_num][a_num]); + result_append("-" + pretty_result(check)); }); - // recheck decryption factors - var expected_value = election.public_key.g.modPow(BigInt.fromInt(results[q_num][a_num]), election.public_key.p); - var recomputed_value = a.decrypt(decryption_factors).getM(); - if (expected_value.equals(recomputed_value)) { - } else { - check = false; - overall_result = false; - } - - result_append("-" + pretty_result(check)); }); - }); + result_append("<h3>FINAL RESULT</h3>"); - result_append("<h3>FINAL RESULT</h3>"); - - if (overall_result) { - result_append("ELECTION FULLY VERIFIED -- SUCCESS!"); - } else { - result_append("VERIFICATION FAILED"); - } + if (overall_result) { + result_append("ELECTION FULLY VERIFIED -- SUCCESS!"); + } else { + result_append("VERIFICATION FAILED"); + } + }); }); - }); + }); + }); - - }); + } catch (error) { + result_append("<p>It appears that you are trying to verify a private election.</p>"); + result_append('<p>You can log in as a valid voter or log in as the election admin.</p>'); + result_append('<a class="btn" href="' + election_url + '">Log in as a valid voter </a>'); + result_append('<a class="btn" href="/auth/?return_url=/verifier/verify.html?election_url=' + election_url + '">Log in as the election admin</a>'); + } }); } diff --git a/requirements.txt b/requirements.txt index 77a8496dd0eb5685e812314d06a41878a656212e..237e75c98c2c3ecb7659d3d44a4c079030515fbc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Django==1.8.18 +Django==1.8.19 anyjson==0.3.3 celery==3.1.18 django-celery==3.1.16 @@ -10,8 +10,8 @@ pyparsing==1.5.7 python-dateutil>=1.5 python-openid==2.2.5 wsgiref==0.1.2 -gunicorn==19.3 -requests==2.7.0 +gunicorn==19.9 +requests==2.21.0 unicodecsv==0.9.0 dj_database_url==0.3.0 django-sslify==0.2.7 diff --git a/reset.sh b/reset.sh index f7ad853220fcc371459bc86beeff2efdbac7122d..52141e13601899518420a971142f26d30f772ab2 100755 --- a/reset.sh +++ b/reset.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -e # Exit immediately if a command exits with a non-zero status. dropdb helios createdb helios python manage.py syncdb diff --git a/runtime.txt b/runtime.txt index 2b55d15ca0c808349643460bb82abb17df496d02..f27f1cc5ca4d2e750411e5ee682f6eb6baa674c2 100644 --- a/runtime.txt +++ b/runtime.txt @@ -1 +1 @@ -python-2.7.10 +python-2.7.15 diff --git a/server_ui/glue.py b/server_ui/glue.py index bcb0995e16e53fe7d95facd436dca11cbcb7b6ee..9b3b5b37ba54c7de399c820d274e131374029184 100644 --- a/server_ui/glue.py +++ b/server_ui/glue.py @@ -5,6 +5,7 @@ Glue some events together from django.conf import settings from django.core.urlresolvers import reverse from django.conf import settings +from helios.view_utils import render_template_raw import helios.views, helios.signals import views @@ -12,30 +13,18 @@ import views def vote_cast_send_message(user, voter, election, cast_vote, **kwargs): ## FIXME: this doesn't work for voters that are not also users # prepare the message - subject = "%s - vote cast" % election.name + subject_template = 'email/cast_vote_subject.txt' + body_template = 'email/cast_vote_body.txt' - body = """ -You have successfully cast a vote in - - %s - -Your ballot is archived at: - - %s -""" % (election.name, helios.views.get_castvote_url(cast_vote)) - - if election.use_voter_aliases: - body += """ - -This election uses voter aliases to protect your privacy. -Your voter alias is : %s -""" % voter.alias - - body += """ - --- -%s -""" % settings.SITE_TITLE + extra_vars = { + 'election' : election, + 'voter': voter, + 'cast_vote': cast_vote, + 'cast_vote_url': helios.views.get_castvote_url(cast_vote), + 'custom_subject' : "%s - vote cast" % election.name + } + subject = render_template_raw(None, subject_template, extra_vars) + body = render_template_raw(None, body_template, extra_vars) # send it via the notification system associated with the auth system user.send_message(subject, body) diff --git a/server_ui/media/boothcss/forms.css b/server_ui/media/boothcss/forms.css index ee1a141a51660875e8ca2ee388e6d0baa1633b12..ae4cbb7fcbf9e2208ab08c6585b68c7a5144fbe2 100644 --- a/server_ui/media/boothcss/forms.css +++ b/server_ui/media/boothcss/forms.css @@ -7,7 +7,7 @@ form.prettyform label,input,textarea,select { line-height: 1.8; } -form.prettyform label { +form.prettyform label:not(.answer) { display: block; text-align: right; float: left; diff --git a/server_ui/templates/email/cast_vote_body.txt b/server_ui/templates/email/cast_vote_body.txt new file mode 100644 index 0000000000000000000000000000000000000000..e248c36c1e105f47610ba8ea6774865badce1d19 --- /dev/null +++ b/server_ui/templates/email/cast_vote_body.txt @@ -0,0 +1,14 @@ +Dear {{voter.name}}, + +You have successfully cast a vote in {{election.name}}. + +Your ballot is archived at: {{cast_vote_url}} + +{% if election.use_voter_aliases %} +This election uses voter aliases to protect your privacy. +Your voter alias is: {{voter.alias}}. +{% endif %} + +-- + +Helios diff --git a/server_ui/templates/email/cast_vote_subject.txt b/server_ui/templates/email/cast_vote_subject.txt new file mode 100644 index 0000000000000000000000000000000000000000..a7929f12b2c11c9525bb772d319c7dafb0cf98f8 --- /dev/null +++ b/server_ui/templates/email/cast_vote_subject.txt @@ -0,0 +1 @@ +{{custom_subject|safe}} diff --git a/server_ui/templates/index.html b/server_ui/templates/index.html index 2b0a0e2c44ebf541b8fbcdc1309b9d526148e852..e31ac19e9c3fa5bc425c623c5e5fe6d7b946cc38 100644 --- a/server_ui/templates/index.html +++ b/server_ui/templates/index.html @@ -24,7 +24,7 @@ Helios elections are: </ul> <p> -More than <b>100,000 votes</b> have been cast using Helios. +More than <b>2,000,000 votes</b> have been cast using Helios. </p> {% if create_p %} diff --git a/server_ui/urls.py b/server_ui/urls.py index d71e04dcac68008d2001ca99c7df7f055bd6404a..0711bd7c15047d880c8d3d18926aceea00e338b4 100644 --- a/server_ui/urls.py +++ b/server_ui/urls.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -from django.conf.urls import * +from django.conf.urls import patterns -from views import * +from views import home, about, docs, faq, privacy urlpatterns = patterns('', (r'^$', home),