Skip to content

Commit 62d1d03

Browse files
- Add checks for the context and manifest matching manifesthash values (#306)
- Add C++ and C# unit tests
1 parent 6ba0bca commit 62d1d03

File tree

4 files changed

+138
-62
lines changed

4 files changed

+138
-62
lines changed

bindings/netstandard/ElectionGuard/ElectionGuard.Encryption.Tests/TestEncrypt.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,5 +154,48 @@ public void Test_Constant_Serialization_Succeeds()
154154
Assert.That(constants.Contains(Constants.G.ToHex()));
155155
}
156156

157+
[Test]
158+
public void Test_EncryptMediator_Hashes_Match()
159+
{
160+
var keypair = ElGamalKeyPair.FromSecret(Constants.TWO_MOD_Q);
161+
var manifest = ManifestGenerator.GetManifestFromFile();
162+
var internalManifest = new InternalManifest(manifest);
163+
var context = new CiphertextElectionContext(
164+
1UL, 1UL, keypair.PublicKey, Constants.TWO_MOD_Q, internalManifest.ManifestHash); // make a context with the correct manifesthash
165+
var device = new EncryptionDevice(12345UL, 23456UL, 34567UL, "Location");
166+
try
167+
{
168+
var mediator = new EncryptionMediator(internalManifest, context, device);
169+
Assert.IsNotNull(mediator); // should not be null if it gets created
170+
}
171+
catch (Exception ex)
172+
{
173+
// if there is an exception then the manifest hash would not be equal
174+
Assert.AreNotEqual(context.ManifestHash.ToHex(), internalManifest.ManifestHash.ToHex());
175+
}
176+
}
177+
178+
[Test]
179+
public void Test_EncryptMediator_Hashes_Dont_Match()
180+
{
181+
var keypair = ElGamalKeyPair.FromSecret(Constants.TWO_MOD_Q);
182+
var manifest = ManifestGenerator.GetManifestFromFile();
183+
var internalManifest = new InternalManifest(manifest);
184+
var context = new CiphertextElectionContext(
185+
1UL, 1UL, keypair.PublicKey, Constants.TWO_MOD_Q, Constants.ONE_MOD_Q); // make a context with a different manifesthash
186+
var device = new EncryptionDevice(12345UL, 23456UL, 34567UL, "Location");
187+
try
188+
{
189+
var mediator = new EncryptionMediator(internalManifest, context, device);
190+
Assert.IsNull(mediator); // should not be created, so null at best
191+
}
192+
catch (Exception ex)
193+
{
194+
// if there is an exception then the manifest hash would not be equal
195+
Assert.AreNotEqual(context.ManifestHash.ToHex(), internalManifest.ManifestHash.ToHex());
196+
}
197+
}
198+
199+
157200
}
158201
}

bindings/netstandard/ElectionGuard/ElectionGuard.Encryption/Encrypt.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@ public unsafe EncryptionMediator(
110110
CiphertextElectionContext context,
111111
EncryptionDevice device)
112112
{
113+
if (manifest.ManifestHash.ToHex() != context.ManifestHash.ToHex())
114+
{
115+
Status.ELECTIONGUARD_STATUS_ERROR_INVALID_ARGUMENT.ThrowIfError();
116+
}
117+
113118
var status = NativeInterface.EncryptionMediator.New(
114119
manifest.Handle, context.Handle, device.Handle, out Handle);
115120
status.ThrowIfError();

src/electionguard/encrypt.cpp

Lines changed: 34 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55
#include "electionguard/ballot_code.hpp"
66
#include "electionguard/elgamal.hpp"
77
#include "electionguard/hash.hpp"
8+
#include "electionguard/precompute_buffers.hpp"
89
#include "log.hpp"
910
#include "nonces.hpp"
1011
#include "serialize.hpp"
1112
#include "utils.hpp"
12-
#include "electionguard/precompute_buffers.hpp"
13-
#include <nlohmann/json.hpp>
1413

1514
#include <algorithm>
1615
#include <future>
1716
#include <iostream>
17+
#include <nlohmann/json.hpp>
1818

1919
extern "C" {
2020
#include "../karamel/Hacl_Bignum4096.h"
@@ -90,18 +90,10 @@ namespace electionguard
9090
return DeviceSerializer::fromBson(move(data));
9191
}
9292

93-
uint64_t EncryptionDevice::getDeviceUuid() const {
94-
return pimpl->deviceUuid;
95-
}
96-
uint64_t EncryptionDevice::getSessionUuid() const {
97-
return pimpl->sessionUuid;
98-
}
99-
uint64_t EncryptionDevice::getLaunchCode() const {
100-
return pimpl->launchCode;
101-
}
102-
std::string EncryptionDevice::getLocation() const {
103-
return pimpl->location;
104-
}
93+
uint64_t EncryptionDevice::getDeviceUuid() const { return pimpl->deviceUuid; }
94+
uint64_t EncryptionDevice::getSessionUuid() const { return pimpl->sessionUuid; }
95+
uint64_t EncryptionDevice::getLaunchCode() const { return pimpl->launchCode; }
96+
std::string EncryptionDevice::getLocation() const { return pimpl->location; }
10597

10698
#pragma endregion
10799

@@ -127,6 +119,11 @@ namespace electionguard
127119
const EncryptionDevice &encryptionDevice)
128120
: pimpl(new Impl(internalManifest, context, encryptionDevice))
129121
{
122+
if (internalManifest.getManifestHash()->toHex() != context.getManifestHash()->toHex()) {
123+
throw invalid_argument("manifest and context do not match hashes manifest:" +
124+
internalManifest.getManifestHash()->toHex() +
125+
" context:" + context.getManifestHash()->toHex());
126+
}
130127
}
131128

132129
EncryptionMediator::~EncryptionMediator() = default;
@@ -296,8 +293,8 @@ namespace electionguard
296293
auto pubkey_to_exp = triple1->get_pubkey_to_exp();
297294

298295
// Generate the encryption using precomputed values
299-
ciphertext = elgamalEncrypt_with_precomputed(selection.getVote(),
300-
*g_to_exp, *pubkey_to_exp);
296+
ciphertext =
297+
elgamalEncrypt_with_precomputed(selection.getVote(), *g_to_exp, *pubkey_to_exp);
301298
if (ciphertext == nullptr) {
302299
throw runtime_error("encryptSelection:: Error generating ciphertext");
303300
}
@@ -309,12 +306,10 @@ namespace electionguard
309306
encrypted = CiphertextBallotSelection::make_with_precomputed(
310307
selection.getObjectId(), description.getSequenceOrder(), *descriptionHash,
311308
move(ciphertext), cryptoExtendedBaseHash, selection.getVote(),
312-
move(precomputedTwoTriplesAndAQuad),
313-
isPlaceholder, true);
309+
move(precomputedTwoTriplesAndAQuad), isPlaceholder, true);
314310
} else {
315311
// Generate the encryption
316-
ciphertext =
317-
elgamalEncrypt(selection.getVote(), *selectionNonce, elgamalPublicKey);
312+
ciphertext = elgamalEncrypt(selection.getVote(), *selectionNonce, elgamalPublicKey);
318313
if (ciphertext == nullptr) {
319314
throw runtime_error("encryptSelection:: Error generating ciphertext");
320315
}
@@ -336,17 +331,17 @@ namespace electionguard
336331

337332
// verify the selection.
338333
if (encrypted->isValidEncryption(*descriptionHash, elgamalPublicKey,
339-
cryptoExtendedBaseHash)) {
334+
cryptoExtendedBaseHash)) {
340335
return encrypted;
341336
}
342-
throw runtime_error("encryptSelection failed validity check");
337+
throw runtime_error("encryptSelection failed validity check");
343338
}
344339

345340
string getOvervoteAndWriteIns(const PlaintextBallotContest &contest,
346341
const InternalManifest &internalManifest,
347342
eg_valid_contest_return_type_t is_overvote)
348343
{
349-
json overvoteAndWriteIns;
344+
json overvoteAndWriteIns;
350345
auto selections = contest.getSelections();
351346

352347
// if an overvote is detected then put the selections into json
@@ -403,24 +398,23 @@ namespace electionguard
403398

404399
string overvoteAndWriteIns_string("");
405400
if (overvoteAndWriteIns.dump() != string("null")) {
406-
overvoteAndWriteIns_string = overvoteAndWriteIns.dump();
401+
overvoteAndWriteIns_string = overvoteAndWriteIns.dump();
407402
}
408403

409404
return overvoteAndWriteIns_string;
410405
}
411406

412407
unique_ptr<CiphertextBallotContest>
413-
encryptContest(const PlaintextBallotContest &contest,
414-
const InternalManifest &internalManifest,
408+
encryptContest(const PlaintextBallotContest &contest, const InternalManifest &internalManifest,
415409
const ContestDescriptionWithPlaceholders &description,
416410
const ElementModP &elgamalPublicKey, const ElementModQ &cryptoExtendedBaseHash,
417411
const ElementModQ &nonceSeed, bool shouldVerifyProofs /* = true */)
418412

419413
{
420414
// Validate Input
421415
bool supportOvervotes = true;
422-
eg_valid_contest_return_type_t is_valid_contest =
423-
contest.isValid(description.getObjectId(), description.getSelections().size(),
416+
eg_valid_contest_return_type_t is_valid_contest = contest.isValid(
417+
description.getObjectId(), description.getSelections().size(),
424418
description.getNumberElected(), description.getVotesAllowed(), supportOvervotes);
425419
if ((is_valid_contest != SUCCESS) && (is_valid_contest != OVERVOTE)) {
426420
throw invalid_argument("the plaintext contest was invalid");
@@ -471,16 +465,16 @@ namespace electionguard
471465

472466
// if the is an overvote then we need to make all the selection votes 0
473467
if (is_valid_contest == OVERVOTE) {
474-
duplicate_selection = make_unique<PlaintextBallotSelection>(
475-
selection_ptr->getObjectId(), 0, false);
468+
duplicate_selection =
469+
make_unique<PlaintextBallotSelection>(selection_ptr->getObjectId(), 0, false);
476470
selection_ptr = duplicate_selection.get();
477471
}
478472

479473
selectionCount += selection_ptr->getVote();
480474

481475
encryptedSelections.push_back(encryptSelection(
482476
*selection_ptr, selectionDescription.get(), *elgamalPublicKey_ptr,
483-
*cryptoExtendedBaseHash_ptr, *sharedNonce.get(), false, shouldVerifyProofs));
477+
*cryptoExtendedBaseHash_ptr, *sharedNonce.get(), false, shouldVerifyProofs));
484478
} else {
485479
// Should never happen since the contest is normalized by emplaceMissingValues
486480
throw runtime_error("encryptedContest:: Error constructing encrypted selection");
@@ -507,18 +501,17 @@ namespace electionguard
507501

508502
auto placeholderSelection = selectionFrom(placeholder, true, selectPlaceholder);
509503
encryptedSelections.push_back(encryptSelection(
510-
*placeholderSelection, placeholder, *elgamalPublicKey_ptr,
511-
*cryptoExtendedBaseHash_ptr, *sharedNonce.get(), false, shouldVerifyProofs));
504+
*placeholderSelection, placeholder, *elgamalPublicKey_ptr,
505+
*cryptoExtendedBaseHash_ptr, *sharedNonce.get(), false, shouldVerifyProofs));
512506
}
513507

514508
// Derive the extendedDataNonce from the selection nonce and a constant
515509
auto noncesForExtendedData =
516-
make_unique<Nonces>(*sharedNonce->clone(), "constant-extended-data");
510+
make_unique<Nonces>(*sharedNonce->clone(), "constant-extended-data");
517511
auto extendedDataNonce = noncesForExtendedData->get(0);
518512

519-
vector<uint8_t> extendedData_plaintext((uint8_t *)&extendedData.front(),
520-
(uint8_t *)&extendedData.front() +
521-
extendedData.size());
513+
vector<uint8_t> extendedData_plaintext(
514+
(uint8_t *)&extendedData.front(), (uint8_t *)&extendedData.front() + extendedData.size());
522515

523516
// Perform HashedElGamalCiphertext calculation
524517
unique_ptr<HashedElGamalCiphertext> hashedElGamal =
@@ -536,8 +529,8 @@ namespace electionguard
536529
auto encryptedContest = CiphertextBallotContest::make(
537530
contest.getObjectId(), description.getSequenceOrder(), *descriptionHash,
538531
move(encryptedSelections), elgamalPublicKey, cryptoExtendedBaseHash, *chaumPedersenNonce,
539-
description.getNumberElected(), sharedNonce->clone(), nullptr,
540-
nullptr, move(hashedElGamal));
532+
description.getNumberElected(), sharedNonce->clone(), nullptr, nullptr,
533+
move(hashedElGamal));
541534

542535
if (encryptedContest == nullptr || encryptedContest->getProof() == nullptr) {
543536
throw runtime_error("encryptedContest:: Error constructing encrypted constest");
@@ -574,8 +567,8 @@ namespace electionguard
574567
hasContest = true;
575568
auto encrypted = encryptContest(
576569
contest.get(), internalManifest, description.get(),
577-
*context.getElGamalPublicKey(),
578-
*context.getCryptoExtendedBaseHash(), nonceSeed, shouldVerifyProofs);
570+
*context.getElGamalPublicKey(), *context.getCryptoExtendedBaseHash(),
571+
nonceSeed, shouldVerifyProofs);
579572

580573
encryptedContests.push_back(move(encrypted));
581574
break;

test/electionguard/test_encrypt.cpp

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ TEST_CASE("Encrypt simple selection using precomputed values succeeds")
6060
auto hashContext = metadata->crypto_hash();
6161
auto plaintext = BallotGenerator::selectionFrom(*metadata);
6262

63-
// cause a two triples and a quad to be populated
63+
// cause a two triples and a quad to be populated
6464
PrecomputeBufferContext::init(1);
6565
PrecomputeBufferContext::populate(*keypair->getPublicKey());
6666
PrecomputeBufferContext::stop_populate();
@@ -132,7 +132,7 @@ TEST_CASE("Encrypt PlaintextBallot with EncryptionMediator against constructed "
132132
auto device = make_unique<EncryptionDevice>(12345UL, 23456UL, 34567UL, "Location");
133133

134134
auto mediator = make_unique<EncryptionMediator>(*internal, *context, *device);
135-
135+
136136
// Act
137137
auto plaintext = BallotGenerator::getFakeBallot(*internal);
138138
// Log::debug(plaintext->toJson());
@@ -201,7 +201,7 @@ TEST_CASE("Encrypt PlaintextBallot overvote")
201201
CHECK(heg != nullptr);
202202
CHECK(heg->getData().size() == (size_t)(BYTES_512 + sizeof(uint16_t)));
203203

204-
unique_ptr<ElementModP> new_pad = make_unique<ElementModP>(*heg->getPad());
204+
unique_ptr<ElementModP> new_pad = make_unique<ElementModP>(*heg->getPad());
205205
unique_ptr<HashedElGamalCiphertext> newHEG =
206206
make_unique<HashedElGamalCiphertext>(move(new_pad), heg->getData(), heg->getMac());
207207

@@ -210,8 +210,8 @@ TEST_CASE("Encrypt PlaintextBallot overvote")
210210
string new_plaintext_string((char *)&new_plaintext.front(), new_plaintext.size());
211211

212212
CHECK(new_plaintext_string ==
213-
string("{\"error\":\"overvote\",\"error_data\":[\"benjamin-franklin-selection\""
214-
",\"john-adams-selection\"]}"));
213+
string("{\"error\":\"overvote\",\"error_data\":[\"benjamin-franklin-selection\""
214+
",\"john-adams-selection\"]}"));
215215
}
216216

217217
TEST_CASE("Encrypt simple PlaintextBallot with EncryptionMediator succeeds")
@@ -260,28 +260,29 @@ TEST_CASE("Encrypt full PlaintextBallot with WriteIn and Overvote with Encryptio
260260
auto device = make_unique<EncryptionDevice>(12345UL, 23456UL, 34567UL, "Location");
261261

262262
auto mediator = make_unique<EncryptionMediator>(*internal, *context, *device);
263-
263+
264264
// Act
265-
string plaintextBallot_json = string("{\"object_id\": \"03a29d15-667c-4ac8-afd7-549f19b8e4eb\","
266-
"\"style_id\": \"jefferson-county-ballot-style\", \"contests\": [ {\"object_id\":"
267-
"\"justice-supreme-court\", \"sequence_order\": 0, \"ballot_selections\": [{"
268-
"\"object_id\": \"john-adams-selection\", \"sequence_order\": 0, \"vote\": 1,"
269-
"\"is_placeholder_selection\": false, \"extended_data\": null}, {\"object_id\""
270-
": \"benjamin-franklin-selection\", \"sequence_order\": 1, \"vote\": 1,"
271-
"\"is_placeholder_selection\": false, \"extended_data\": null}, {\"object_id\":"
272-
" \"write-in-selection\", \"sequence_order\": 3, \"vote\": 1, \"is_placeholder_selection\""
273-
": false, \"write_in\": \"Susan B. Anthony\"}], \"extended_data\": null}]}");
274-
// TODO - Once the relevant plaintext ballot file is in the environment then
265+
string plaintextBallot_json = string(
266+
"{\"object_id\": \"03a29d15-667c-4ac8-afd7-549f19b8e4eb\","
267+
"\"style_id\": \"jefferson-county-ballot-style\", \"contests\": [ {\"object_id\":"
268+
"\"justice-supreme-court\", \"sequence_order\": 0, \"ballot_selections\": [{"
269+
"\"object_id\": \"john-adams-selection\", \"sequence_order\": 0, \"vote\": 1,"
270+
"\"is_placeholder_selection\": false, \"extended_data\": null}, {\"object_id\""
271+
": \"benjamin-franklin-selection\", \"sequence_order\": 1, \"vote\": 1,"
272+
"\"is_placeholder_selection\": false, \"extended_data\": null}, {\"object_id\":"
273+
" \"write-in-selection\", \"sequence_order\": 3, \"vote\": 1, \"is_placeholder_selection\""
274+
": false, \"write_in\": \"Susan B. Anthony\"}], \"extended_data\": null}]}");
275+
// TODO - Once the relevant plaintext ballot file is in the environment then
275276
// uncomment the below and stop using the hard coded string.
276277
//auto plaintextBallot =
277278
// BallotGenerator::getSimpleBallotFromFile(TEST_BALLOT_WITH_WRITEIN_SELECTED);
278279
auto plaintextBallot = PlaintextBallot::fromJson(plaintextBallot_json);
279280
auto ciphertext = mediator->encrypt(*plaintextBallot);
280-
281+
281282
// Assert
282283
CHECK(ciphertext->isValidEncryption(*context->getManifestHash(), *keypair->getPublicKey(),
283284
*context->getCryptoExtendedBaseHash()) == true);
284-
285+
285286
// check to make sure we have a hashed elgamal ciphertext
286287
unique_ptr<HashedElGamalCiphertext> heg = nullptr;
287288
auto contests = ciphertext->getContests();
@@ -307,9 +308,9 @@ TEST_CASE("Encrypt full PlaintextBallot with WriteIn and Overvote with Encryptio
307308
Log::debug(new_plaintext_string);
308309

309310
CHECK(new_plaintext_string ==
310-
string("{\"error\":\"overvote\",\"error_data\":[\"john-adams-selection\","
311-
"\"benjamin-franklin-selection\",\"write-in-selection\"],\"write_ins\""
312-
":{\"write-in-selection\":\"Susan B. Anthony\"}}"));
311+
string("{\"error\":\"overvote\",\"error_data\":[\"john-adams-selection\","
312+
"\"benjamin-franklin-selection\",\"write-in-selection\"],\"write_ins\""
313+
":{\"write-in-selection\":\"Susan B. Anthony\"}}"));
313314
}
314315

315316
TEST_CASE("Encrypt simple CompactPlaintextBallot with EncryptionMediator succeeds")
@@ -438,3 +439,37 @@ TEST_CASE("Encrypt simple ballot from file succeeds with precomputed values")
438439
*context->getCryptoExtendedBaseHash()) == true);
439440
PrecomputeBufferContext::empty_queues();
440441
}
442+
443+
TEST_CASE("Create EncryptionMediator with same manifest hash")
444+
{
445+
auto secret = ElementModQ::fromHex(a_fixed_secret);
446+
auto keypair = ElGamalKeyPair::fromSecret(*secret);
447+
auto manifest = ManifestGenerator::getJeffersonCountyManifest_Minimal();
448+
auto internal = make_unique<InternalManifest>(*manifest);
449+
auto context = make_unique<CiphertextElectionContext>(
450+
1UL, 1UL, keypair->getPublicKey()->clone(), Q().clone(),
451+
internal.get()->getManifestHash()->clone(), Q().clone(), Q().clone());
452+
auto device = make_unique<EncryptionDevice>(12345UL, 23456UL, 34567UL, "Location");
453+
454+
auto mediator = make_unique<EncryptionMediator>(*internal, *context, *device);
455+
CHECK(mediator != nullptr);
456+
}
457+
458+
TEST_CASE("Create EncryptionMediator with different manifest hash")
459+
{
460+
auto secret = ElementModQ::fromHex(a_fixed_secret);
461+
auto keypair = ElGamalKeyPair::fromSecret(*secret);
462+
auto manifest = ManifestGenerator::getJeffersonCountyManifest_Minimal();
463+
auto internal = make_unique<InternalManifest>(*manifest);
464+
auto context =
465+
make_unique<CiphertextElectionContext>(1UL, 1UL, keypair->getPublicKey()->clone(),
466+
Q().clone(), Q().clone(), Q().clone(), Q().clone());
467+
auto device = make_unique<EncryptionDevice>(12345UL, 23456UL, 34567UL, "Location");
468+
469+
try {
470+
auto mediator = make_unique<EncryptionMediator>(*internal, *context, *device);
471+
CHECK(mediator == nullptr);
472+
} catch (const std::exception &e) {
473+
CHECK(internal->getManifestHash()->toHex() != context->getManifestHash()->toHex());
474+
}
475+
}

0 commit comments

Comments
 (0)