Commit 7afd44e6 authored by Stephane Glondu's avatar Stephane Glondu
Browse files

Add credentials (client-side)

Backported from Helios-C branch.

 * The Schnorr signature scheme is used for signing ballots.
 * The private key is derived from a (relatively) short password
   (advertised as "token" to the voter) that includes a checksum that
   is supposed to be sent to the voter. We estimate that a 15-letter
   password gives 82 bits of entropy, and this implementation rejects
   shorter ones.
 * We use the public credential as identity in NIZK proofs.
parent ca8da05b
......@@ -540,6 +540,20 @@ ElGamal.disjunctive_challenge_generator = function(id) { return function(commitm
return new BigInt(hex_sha1("prove|" + id + "|" + strings_to_hash.join(",")), 16);
}};
// same structure as above, adapted for (alpha, beta) pairs of
// encrypted choices instead of (A, B) of commitments
ElGamal.stringify_choices = function(choices) {
var strings_to_hash = [];
_(choices).each(function(choice) {
// toJSONObject instead of toString because of IE weirdness.
strings_to_hash[strings_to_hash.length] = choice.alpha.toJSONObject();
strings_to_hash[strings_to_hash.length] = choice.beta.toJSONObject();
});
return strings_to_hash.join(",");
};
// a challenge generator for Fiat-Shamir
ElGamal.fiatshamir_challenge_generator = function(commitment) {
return ElGamal.disjunctive_challenge_generator([commitment]);
......
......@@ -467,11 +467,13 @@ HELIOS.EncryptedVote = Class.extend({
return ea.toJSONObject(include_plaintext);
});
return {
var res = {
answers : answers,
election_hash : this.election_hash,
election_uuid : this.election_uuid
}
if (this.signature) res.signature = this.signature.toJSONObject();
return res;
},
get_hash: function() {
......
/*
Copyright © 2012-2013 Inria. All rights reserved.
Author: Stéphane Glondu <Stephane.Glondu@inria.fr>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
// Significant Helios-C-specific code is inside the heliosc.* namespace
var heliosc = {};
heliosc.booth = {};
heliosc.booth.derive_key = function (election) {
var n58 = BigInt.fromInt(58);
var n53 = BigInt.fromInt(53); // previous_prime(58)
var digits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
// FIXME: for demonstration purposes, this is a constant. In
// practice, it should be different (at least) for each election
// (e.g. UUID? fingerprint?).
var salt = sjcl.codec.hex.toBits("ed3540246bb511e286793cd92b7981b8");
// Check that a secret credential is well formed
function check(s) {
var n = s.length-1, res = BigInt.ZERO;
if (n < 14) return false; // < 82 bits of security
var i, j;
// main
for (i = 0; i < n; i++) {
j = digits.indexOf(s[i]);
if (j < 0) return false; // bad digit
res = res.multiply(n58).add(BigInt.fromInt(j));
}
// checksum
j = digits.indexOf(s[i]);
if (j < 0) return false; // bad digit
var checksum = res.add(BigInt.fromInt(j));
if (!checksum.mod(n53).equals(BigInt.ZERO)) return false; // bad checksum
return true;
}
// Derive a Schnorr keypair from the secret credential
function derive_key(secret) {
if (check(secret)) {
var hexkey = sjcl.codec.hex.fromBits(sjcl.misc.pbkdf2(secret, salt, 1000, 256));
var pk = election.public_key;
var x = (new BigInt(hexkey, 16)).mod(pk.q);
var y = pk.g.modPow(x, pk.p);
return { x: x, y: y };
} else {
throw "Bad token";
}
}
return derive_key;
}
HELIOS.EncryptedVote.prototype.doSignature = function(cred) {
// Schnorr signature
var pk = this.election.public_key;
var w = Random.getRandomInteger(pk.q);
var commitment = pk.g.modPow(w, pk.p);
var challenge = (new BigInt(hex_sha1(_(this.encrypted_answers).map(function(ea) {
return ElGamal.stringify_choices(ea.choices);
}).join(";") + ":" + commitment.toJSONObject()), 16)).mod(pk.q);
// we do (q-x*challenge)+w instead of directly w-x*challenge,
// in case mod doesn't support negative numbers as expected
var response = pk.q.subtract(cred.x.multiply(challenge).mod(pk.q));
response = response.add(w).mod(pk.q);
// hugly hijack of the DLogProof datatype... note: here, we
// give public credential instead of commitment, which can be computed
// from public credential, challenge and response
this.signature = new ElGamal.DLogProof(cred.y, challenge, response);
}
......@@ -6,6 +6,8 @@
<p>
<ol>
<li> <b>Enter</b> your secret token.</li><br />
<li> <b>Select</b> your options.<br />
<span style="font-size: 14pt;">Answer the questions, and review your choices.</span></li>
<br />
......@@ -25,6 +27,6 @@ Proceed to log in and cast your encrypted ballot for tallying.
</li>
</ol>
<div align="center"><button onclick="BOOTH.show_question(0);">Start</button></div>
<div align="center"><button onclick="BOOTH.ask_token();">Start</button></div>
</div>
......@@ -30,6 +30,7 @@
<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>
<script language="javascript" src="js/jscrypto/heliosc-booth.js?d=20130201"></script>
</head>
<body>
<div id="wrapper">
......@@ -167,12 +168,12 @@ BOOTH.setup_election = function(raw_json) {
// appears to be safer.
BOOTH.election = HELIOS.Election.fromJSONString(raw_json);
BOOTH.election.cast_url = "/election/cast?uuid=" + BOOTH.election.uuid
BOOTH.voter_id = "ID"; // placeholder for later
// 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
BOOTH.derive_key = heliosc.booth.derive_key(BOOTH.election);
// async?
BOOTH.setup_workers(raw_json);
......@@ -458,6 +459,7 @@ BOOTH.wait_for_ciphertexts = function() {
}
BOOTH.encrypted_ballot = HELIOS.EncryptedVote.fromEncryptedAnswers(BOOTH.election, BOOTH.encrypted_answers);
BOOTH.encrypted_ballot.doSignature(BOOTH.credential);
BOOTH._after_ballot_encryption();
};
......@@ -467,6 +469,7 @@ BOOTH.seal_ballot_raw = function() {
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, BOOTH.voter_id);
BOOTH.encrypted_ballot.doSignature(BOOTH.credential);
clearInterval(progress_interval);
BOOTH._after_ballot_encryption();
} else {
......@@ -553,6 +556,17 @@ BOOTH.do_done = function() {
BOOTH.started_p = false;
};
BOOTH.ask_token = function () {
try {
var token = window.prompt("Please enter your secret token:");
BOOTH.credential = BOOTH.derive_key(token);
BOOTH.voter_id = BOOTH.credential.y.toString();
BOOTH.show_question(0);
} catch (e) {
window.alert(e);
}
};
</script>
<div id="page">
<div id="progress_div" style="display:none; width: 500px; margin:auto;">
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment