In September '21, I came across this story "Swiss Post Offers up to €230,000 for Critical Vulnerabilities in e-Voting System" while catching up with the security news.
The headline certainly caught my attention as it looked like an outlier from the regular bug bounty programs or well-known exploit contests, not only for the announced rewards but mainly because of the target. So essentially Swiss Post, the national postal service of Switzerland, was opening to the general public a bug bounty program, using the YesWeHack platform, intended to uncover vulnerabilities in its future e-voting system.
The first part of this blog post series will detail the approach used to analyze the Swiss Post e-voting system, as well as the first round of vulnerabilities that I reported during September/October '21.
Index
Introduction
Although e-voting may not be suitable for every country, Switzerland seems to have a long tradition on referendums, and actually, they have been already using e-voting for many years. However, when the Swiss Post e-voting platform was published, back in 2019, it faced some public scrutiny, mostly from the academic community. As a result, some significant issues were uncovered, so eventually Swiss Post decided to suspend the deployment of the system. The first version had been developed by Scytl, a spanish company specialized in electronic voting systems. After that fiasco, Swiss Post changed their approach, acquiring the source code from Scytl and moving to a transparent, open-source focused, in-house development process, which is where they are at now.
Approach
Swiss Post e-voting platform is a quite complex system, comprised of different technologies, whose codebase is approximately 150,000 lines of code, most of them Java but also Typescript for the front-end applications. Please note that a significant part of the code is dedicated to implement custom cryptographic protocols and operations. Despite this, code and component interactions are surprisingly easy to follow as everything is highly documented: cryptographic protocols, architecture, operations...The entire system seems to have been designed, defined and implemented in such a way that a 3rd party may properly audit it. Actually Swiss Post paid special attention to this aspect by requesting an external auditability report.
It's important to note that I'm not a cryptographer. As many in infosec I don't even have a degree so for me the hardest part to cover has been the highly specialized cryptography implemented in this system. At the same time I was looking for vulnerabilities I had to spend some time wrapping my head around schemes such as Non-Interactive Zero Knowledge Proofs or Shamir's Secret Sharing. For the reader in a similar situation, it is worth mentioning that TrailOfBits recently published a really useful initiative https://www.zkdocs.com/ that breaks down all these concepts from a practical perspective, which facilitates the approach for non-academic security researchers.
Although Swiss Post provides the instructions to build a runnable version of the system I decided to follow a pure static analysis approach for various reasons:
- The system is not fully implemented yet. As a result, you need to bear in mind the available source code but also the specification documents to discover and assess potential vulnerabilities. As a result, testing things against the running version does not actually guarantee that you found a real issue.
- It's usually a pain to get everything working, so I'd rather spend 1 week looking at code than 1 week trying to build the system for no obvious advantage. Watch out, this does not mean the build instructions are either wrong or not accurate, I just didn't try.
- Besides the code, the most interesting parts of the Swiss Post e-voting system, from the offensive perspective, are not the applications themselves, but the whole deployment (including hardened boxes, firewall rules, human operations...) which cannot be fully replicated obviously.
Under my point of view, the following 4 documents should be used as a permanent reference to be able to properly analyze the source code. In such a complex system, with a very specific threat model and trust assumptions between components, you cannot just find something apparently bad and report it but you should be able to determine whether a potential issue is an actual vulnerability as well as its impact.
- Protocol of the Swiss Post Voting System
- System Specification
- E-voting Architecture
- Infrastructure white-paper
Attack Surface
The following diagram extracted from the documentation depicts a deployment overview of the system. On top of this I've added up to 5 points that represent, what I considered, the top priorities.
Before elaborating them, we should talk a little bit about 'Trust assumptions', which is a key concept to understand the whole design of the system, as well as the plausible vulnerabilities that can be rewarded according to their Bug Bounty rules.
In addition to the extensive documentation, Swiss Post elaborates a little bit more about the trust assumptions here
Do not get me wrong, I understand that the trustworthiness concept is required to model the cryptographic protocols that support the entire system but we cannot forget either that in the real-world this means absolutely nothing. Malicious actors are not going to refrain from attacking a component because it has been labeled as 'trustworthy' so this situation slightly reminds me of the spherical cow metaphor. Actually, one of the vulnerabilities herein described (ID#1) allows to fully compromise the SDM, a key trustworthy component.
Anyway, these are the rules so we have to play with them. The following image shows the trust assumptions for all the components and humans involved in the election process. A more elaborated definition can be found in the Protocol of the Swiss Post Voting System (2.1 'Parties and Channels').
Now we are in a position to dig deeper into the top 5 priorities for the attack surface (the order is not relevant) we just highlighted in the diagram above.
1. Air Gapped components connected using USB keys
The system architecture document provides the following self-explanatory diagram. Please also note that according to the threat model, the administrators are considered 'untrustworthy' but the SDM is a key trustworthy element in the e-voting system. Basically we are mixing potentially malicious actors and USB keys so I don't think it is really needed to elaborate more on why this should be considered a priority.2. SDM Online Instance
The Secure Data Manager Online instance implements a significant part of the core functionality the election event relies on. In addition to this, it is communicating with trustworthy components through untrustworthy ones. I came up with the following diagram to illustrate these interactions.
3. Voting Server
The Voting Server is the gateway to all the applications that need to perform voting process operations. It is comprised of different microservices, each of them is responsible for one part of the voting process, i.e., authentication, election information, vote verification, etc.The voting server also receives, processes, and stores the encrypted votes.
It is considered untrustworthy so it is assumed that can be compromised. Despite this, it is important to analyze the voting server, especially the Orchestrator, which handles interactions with trustworthy components so it plays an important role in the channel security implementation as I'm depicting in the following diagram.
4. Control Components
In the system architecture document (5.5 Control Components) we can find the following diagram
In addition to the SDM, the Control Components implement the most critical logic to guarantee the integrity of the election event. As a result, it is important to analyze both the interactions with the untrustworthy components (through the RabbitMQ cluster) and those parts of the underlying cryptographic protocol they sustain.
5. Voting Client
It is important to understand the implications of a potentially compromised voting client, although it is assumed, as well as the attack surface the voting server exposes to a malicious voting client.
Vulnerabilities
The following table summarizes the first round of vulnerabilities that I reported to Swiss Post via the YesWeHack Bug Bounty Platform during September and October. A second blog post will cover the remaining bugs that are still being analyzed. Once all the reported vulnerabilities have been addressed by Swiss Post I will also be in a better position to draw some final conclusions.#1 - SDM - Insecure USB file handling during 'importOperation'
File: e-voting-master/secure-data-manager/secure-data-manager-backend/web-services/src/main/java/ch/post/it/evoting/sdm/ws/application/OperationsController.java
242: @PostMapping(value = "/import")
243: @ApiOperation(value = "Import operation service")
244: @ApiResponses(value = { @ApiResponse(code = 404, message = "Not Found"), @ApiResponse(code = 403, message = "Forbidden"),
245: @ApiResponse(code = 500, message = "Internal Server Error") })
246: public ResponseEntity<OperationResult> importOperation(
247: @RequestBody
248: final OperationsData request) {
249:
250: if (StringUtils.isEmpty(request.getPath())) {
251: return handleIncompleteRequest();
252: }
253:
254: try {
255: exportImportService.importData(request.getPath());
256: exportImportService.verifySignaturesOnImport();
257: exportImportService.importDatabase();
258:
259: } catch (IOException e) {
260: OperationsOutputCode code = OperationsOutputCode.ERROR_IO_OPERATIONS;
261: return handleException(e, code.value());
262: } catch (InvalidParameterException e) {
263: OperationsOutputCode code = OperationsOutputCode.MISSING_PARAMETER;
264: return handleInvalidParamException(e, code.value());
265: } catch (CMSException | SignatureException e) {
266: OperationsOutputCode code = OperationsOutputCode.SIGNATURE_VERIFICATION_FAILED;
267: return handleException(e, code.value());
268: } catch (GeneralCryptoLibException e) {
269: OperationsOutputCode code = OperationsOutputCode.CHAIN_VALIDATION_FAILED;
270: return handleException(e, code.value());
271: } catch (CertificateException e) {
272: OperationsOutputCode code = OperationsOutputCode.ERROR_CERTIFICATE_PARSING;
273: return handleException(e, code.value());
274: } catch (ConsistencyCheckException e) {
275: OperationsOutputCode code = OperationsOutputCode.CONSISTENCY_ERROR;
276: return handleException(e, code.value());
277: } catch (Exception e) {
278: OperationsOutputCode code = OperationsOutputCode.GENERAL_ERROR;
279: return handleException(e, code.value());
280: }
281: return new ResponseEntity<>(HttpStatus.OK);
282: }
File: e-voting-master/secure-data-manager/secure-data-manager-backend/services/src/main/java/ch/post/it/evoting/sdm/application/service/ExportImportService.java
467: /**
468: * Import all files from selected exported election event to user/sdm
469: *
470: * @param usbElectionPath path to selected exported election event
471: * @throws IOException
472: */
473: public void importData(String usbElectionPath) throws IOException {
474: Filter<Path> filter = file -> true;
475: Path usbFolder = absolutePathResolver.resolve(usbElectionPath);
476: Path sdmFolder = pathResolver.resolve(Constants.SDM_DIR_NAME);
477:
478: copyFolder(usbFolder, sdmFolder, filter, false);
479: }
480:
481: private void copyFolder(Path source, Path dest, Filter<Path> filter, boolean isExportingElectionInformationFolders) throws IOException {
482: if (!Files.exists(source)) {
483: return;
484: }
485:
486: if (Files.isDirectory(source)) {
487: if (!Files.exists(dest)) {
488: Files.createDirectories(dest);
489: LOGGER.info("Directory created from {} to {}", source, dest);
490: }
491: Filter<Path> electionInformationFilter = filter;
492: try (DirectoryStream<Path> stream = Files.newDirectoryStream(source, electionInformationFilter)) {
493: for (Path file : stream) {
494: if (isExportingElectionInformationFolders) {
495: electionInformationFilter = getElectionInformationFilter(file);
496: }
497: copyFolder(file, dest.resolve(file.getFileName()), electionInformationFilter, isExportingElectionInformationFolders);
498: }
499: }
500: } else {
501: try {
502:
503: copy(source, dest, COPY_OPTIONS);
504:
505: LOGGER.info("File copied from {} to {}", source, dest);
506: } catch (IOException e) {
507: LOGGER.error("Error copying files from {} to {}", source, dest, e);
508: }
509: }
510: }
File: e-voting-master/secure-data-manager/secure-data-manager-backend/web-services/src/main/java/ch/post/it/evoting/sdm/ws/application/OperationsController.java
256: exportImportService.verifySignaturesOnImport();
257: exportImportService.importDatabase();
File: e-voting-master/secure-data-manager/secure-data-manager-backend/services/src/main/java/ch/post/it/evoting/sdm/application/service/ExportImportService.java
658: /**
659: * Verifies the signature of both db dump and elections config files.
660: *
661: * @throws IOException
662: * @throws CMSException
663: * @throws CertificateException
664: * @throws GeneralCryptoLibException
665: */
666: public void verifySignaturesOnImport() throws IOException, CMSException, CertificateException, GeneralCryptoLibException {
667: signaturesVerifierService.verifyPkcs7(getPathOfDumpDatabase(), getPathOfDumpDatabaseSignature());
668: signaturesVerifierService.verifyPkcs7(getPathOfElectionsConfig(), getPathOfElectionsConfigSignature());
669: }
File: e-voting-master/secure-data-manager/secure-data-manager-backend/services/src/main/java/ch/post/it/evoting/sdm/application/service/SignaturesVerifierServiceImpl.java
66: /**
67: * Verifies a P7 signature with the trusted chain of the SDM
68: *
69: * @param filePath
70: * @param signaturePath
71: * @return
72: * @throws IOException
73: * @throws CMSException
74: * @throws GeneralCryptoLibException
75: * @throws CertificateException
76: */
77: @Override
78: public Certificate[] verifyPkcs7(Path filePath, Path signaturePath)
79: throws IOException, CMSException, GeneralCryptoLibException, CertificateException {
80: Path trustedCAPath = pathResolver.resolve(Constants.CONFIG_FILES_BASE_DIR, Constants.CONFIG_FILE_NAME_TRUSTED_CA_PEM);
81:
82: return signatureVerifier.verifyPkcs7(filePath, signaturePath, trustedCAPath);
83:
84: }
File: e-voting-master/secure-data-manager/secure-data-manager-backend/web-services/src/main/java/ch/post/it/evoting/sdm/ws/application/OperationsController.java
284: protected List<String> getCommands(PhaseName phaseName) throws IOException, JAXBException, SAXException, XMLStreamException {
285:
286: Path pluginXmlPath = pathResolver.resolve(Constants.SDM_DIR_NAME).resolve(PLUGIN_FILE_NAME);
287: if (!pluginXmlPath.toFile().exists()) {
288: pluginXmlPath = pathResolver.resolve(Constants.SDM_DIR_NAME).resolve(Constants.SDM_CONFIG_DIR_NAME).resolve(PLUGIN_FILE_NAME);
289: if (!pluginXmlPath.toFile().exists()) {
290: LOGGER.error("The plugin.xml file is not found");
291: return new ArrayList<>();
292: }
293: }
294:
295: Plugins plugins = XmlObjectsLoader.unmarshal(pluginXmlPath);
296: PluginSequenceResolver pluginSequence = new PluginSequenceResolver(plugins);
297: return pluginSequence.getActionsForPhase(phaseName);
298: }
#2 - Insecure 'ReturnCodeGenerationInput' signature generation allows vote manipulation
Description
- We have that E1 = (c1,0,c1,1), the encrypted product of the selected voted options.
- We have that E1 is E1 exponentiated to the private key kid, E1=(ckid1,0, ckid1,1)
- We have an ElGamal Public Key Kid = gx
- We have VerifyExp(((p,q,g,E1),(Kid,E1)),πExp, aux U {vcdid})
Technical Analysis
File: e-voting-master/secure-data-manager/secure-data-manager-backend/services/src/main/java/ch/post/it/evoting/sdm/application/service/GenerateVerificationData.java
258: private ReturnCodeGenerationRequestPayload createRequestPayload(final VerificationCardSet verificationCardSet, final int chunkId,
259: final GenVerDatOutput genVerDatOutput) throws PayloadSignatureException {
260:
261: // Create payload.
262: final List<ReturnCodeGenerationInput> returnCodeGenerationInputList = new ArrayList<>();
263: for (int j = 0; j < genVerDatOutput.size(); j++) {
264: final String verificationCardId = genVerDatOutput.getVerificationCardIds().get(j);
265: final ElGamalMultiRecipientCiphertext confirmationKey = genVerDatOutput.getEncryptedHashedConfirmationKeys().get(j);
266: final ElGamalMultiRecipientCiphertext partialChoiceCode = genVerDatOutput.getEncryptedHashedPartialChoiceReturnCodes().get(j);
267: final ElGamalMultiRecipientPublicKey verificationCardPublicKey = genVerDatOutput.getVerificationCardKeyPairs().get(j).getPublicKey();
268:
269: returnCodeGenerationInputList
270: .add(new ReturnCodeGenerationInput(verificationCardId, confirmationKey, partialChoiceCode, verificationCardPublicKey));
271: }
272: final ReturnCodeGenerationRequestPayload payload = new ReturnCodeGenerationRequestPayload(tenantId, verificationCardSet.getElectionEventId(),
273: verificationCardSet.getVerificationCardSetId(), chunkId, genVerDatOutput.getGroup(), returnCodeGenerationInputList,
274: new CombinedCorrectnessInformation(getBallot(verificationCardSet.getBallotBoxId(), verificationCardSet.getElectionEventId())));
275:
276: // Sign the payload.
277: LOGGER.debug("Signing payload for verificationCardSet {} and chunkId {}...", verificationCardSet.getVerificationCardSetId(), chunkId);
278: try {
279: signPayload(payload);
280: } catch (CertificateManagementException | PayloadSignatureException e) {
281: throw new PayloadSignatureException(e);
282: }
283:
284: LOGGER.debug("Payload successfully created and signed for verificationCardSet {} and chunkId {}.",
285: verificationCardSet.getVerificationCardSetId(), chunkId);
286:
287: return payload;
288: }
289:
290: /**
291: * Signs a return code generation request payload.
292: *
293: * @param payload the payload to sign
294: * @throws PayloadSignatureException If an error occurs while getting the admin board's signing key.
295: * @throws CertificateManagementException If an error occurs while getting the admin board's certificate chain.
296: */
297: private void signPayload(final ReturnCodeGenerationRequestPayload payload) throws PayloadSignatureException, CertificateManagementException {
298: // Get the admin board's signing key.
299: final PrivateKey signingKey;
300: try {
301: signingKey = PemUtils.privateKeyFromPem(administrationBoardPrivateKeyPEM);
302: } catch (GeneralCryptoLibException e) {
303: throw new PayloadSignatureException(e);
304: }
305:
306: // Get the admin board's certificate chain.
307: final X509Certificate[] certificateChain = adminBoardService.getCertificateChain(adminBoardId);
308:
309: // Hash and sign the payload.
310: final byte[] payloadHash = hashService.recursiveHash(payload);
311: final CryptolibPayloadSignature signature = payloadSignatureService.sign(payloadHash, signingKey, certificateChain);
312: payload.setSignature(signature);
313: }
File: e-voting-master/crypto-primitives-master/src/main/java/ch/post/it/evoting/cryptoprimitives/hashing/HashService.java
082: public byte[] recursiveHash(final Hashable... values) {
083: checkNotNull(values);
084: checkArgument(Arrays.stream(values).allMatch(Objects::nonNull), "Values contain a null value which cannot be hashed.");
085: checkArgument(values.length != 0, "Cannot hash no values.");
086:
087: if (values.length > 1) {
088: final HashableList v = HashableList.from(ImmutableList.copyOf(values));
089: return recursiveHash(v);
090: } else {
091: final Hashable value = values[0];
092:
093: if (value instanceof HashableByteArray) {
094: final byte[] w = ((HashableByteArray) value).toHashableForm();
095: return this.hashFunction.apply(w);
096: } else if (value instanceof HashableString) {
097: final String w = ((HashableString) value).toHashableForm();
098: return this.hashFunction.apply(ConversionService.stringToByteArray(w));
099: } else if (value instanceof HashableBigInteger) {
100: final BigInteger w = ((HashableBigInteger) value).toHashableForm();
101: checkArgument(w.compareTo(BigInteger.ZERO) >= 0);
102: return this.hashFunction.apply(integerToByteArray(w));
103: } else if (value instanceof HashableList) {
104: final ImmutableList<? extends Hashable> w = ((HashableList) value).toHashableForm();
105:
106: checkArgument(!w.isEmpty(), "Cannot hash an empty list.");
107:
108: if (w.size() == 1) {
109: return recursiveHash(w.get(0));
110: }
111:
112: final byte[][] subHashes = w.stream()
113: .map(this::recursiveHash)
114: .toArray(byte[][]::new);
115: final byte[] concatenatedSubHashes = Bytes.concat(subHashes);
116:
117: return this.hashFunction.apply(concatenatedSubHashes);
118: } else {
119: throw new IllegalArgumentException(String.format("Object of type %s cannot be hashed.", value.getClass()));
120: }
121: }
122: }
File: e-voting-master/domain/src/main/java/ch/post/it/evoting/domain/returncodes/ReturnCodeGenerationInput.java
001: /*
002: * (c) Copyright 2021 Swiss Post Ltd.
003: */
004: package ch.post.it.evoting.domain.returncodes;
005:
006: import static com.google.common.base.Preconditions.checkNotNull;
007:
008: import java.util.Objects;
009:
010: import com.fasterxml.jackson.annotation.JsonCreator;
011: import com.fasterxml.jackson.annotation.JsonProperty;
012: import com.fasterxml.jackson.annotation.JsonPropertyOrder;
013: import com.google.common.collect.ImmutableList;
014:
015: import ch.post.it.evoting.cryptoprimitives.elgamal.ElGamalMultiRecipientCiphertext;
016: import ch.post.it.evoting.cryptoprimitives.elgamal.ElGamalMultiRecipientPublicKey;
017: import ch.post.it.evoting.cryptoprimitives.hashing.Hashable;
018: import ch.post.it.evoting.cryptoprimitives.hashing.HashableList;
019: import ch.post.it.evoting.cryptoprimitives.hashing.HashableString;
020:
021: @JsonPropertyOrder({ "verificationCardId", "encryptedHashedSquaredConfirmationKey", "encryptedHashedSquaredPartialChoiceReturnCodes" })
022: public class ReturnCodeGenerationInput implements HashableList {
023:
024: @JsonProperty
025: private final String verificationCardId;
026:
027: @JsonProperty
028: private final ElGamalMultiRecipientCiphertext encryptedHashedSquaredConfirmationKey;
029:
030: @JsonProperty
031: private final ElGamalMultiRecipientCiphertext encryptedHashedSquaredPartialChoiceReturnCodes;
032:
033: @JsonProperty
034: private final ElGamalMultiRecipientPublicKey verificationCardPublicKey;
035:
036: /**
037: * Creates an object used as the input for return code (choice return codes and vote cast return codes) generation requests.
038: *
039: * @param verificationCardId the verification card identifier.
040: * @param encryptedHashedSquaredConfirmationKey the encrypted hashed squared confirmation key.
041: * @param encryptedHashedSquaredPartialChoiceReturnCodes the encrypted hashed squared partial choice return codes.
042: * @param verificationCardPublicKey the verification card public key
043: */
044: @JsonCreator
045: public ReturnCodeGenerationInput(
046: @JsonProperty("verificationCardId")
047: final String verificationCardId,
048: @JsonProperty("encryptedHashedSquaredConfirmationKey")
049: final ElGamalMultiRecipientCiphertext encryptedHashedSquaredConfirmationKey,
050: @JsonProperty("encryptedHashedSquaredPartialChoiceReturnCodes")
051: final ElGamalMultiRecipientCiphertext encryptedHashedSquaredPartialChoiceReturnCodes,
052: @JsonProperty("verificationCardPublicKey")
053: final ElGamalMultiRecipientPublicKey verificationCardPublicKey) {
054:
055: checkNotNull(verificationCardId);
056: checkNotNull(encryptedHashedSquaredConfirmationKey);
057: checkNotNull(encryptedHashedSquaredPartialChoiceReturnCodes);
058: checkNotNull(verificationCardPublicKey);
059:
060: this.verificationCardId = verificationCardId;
061: this.encryptedHashedSquaredConfirmationKey = encryptedHashedSquaredConfirmationKey;
062: this.encryptedHashedSquaredPartialChoiceReturnCodes = encryptedHashedSquaredPartialChoiceReturnCodes;
063: this.verificationCardPublicKey = verificationCardPublicKey;
064: }
065:
066: public String getVerificationCardId() {
067: return verificationCardId;
068: }
069:
070: public ElGamalMultiRecipientCiphertext getEncryptedHashedSquaredConfirmationKey() {
071: return encryptedHashedSquaredConfirmationKey;
072: }
073:
074: public ElGamalMultiRecipientCiphertext getEncryptedHashedSquaredPartialChoiceReturnCodes() {
075: return encryptedHashedSquaredPartialChoiceReturnCodes;
076: }
077:
078: public ElGamalMultiRecipientPublicKey getVerificationCardPublicKey() {
079: return verificationCardPublicKey;
080: }
081:
082: @Override
083: public boolean equals(final Object o) {
084: if (this == o) {
085: return true;
086: }
087: if (o == null || getClass() != o.getClass()) {
088: return false;
089: }
090: final ReturnCodeGenerationInput that = (ReturnCodeGenerationInput) o;
091: return verificationCardId.equals(that.verificationCardId) && encryptedHashedSquaredConfirmationKey
092: .equals(that.encryptedHashedSquaredConfirmationKey) && encryptedHashedSquaredPartialChoiceReturnCodes
093: .equals(that.encryptedHashedSquaredPartialChoiceReturnCodes) && verificationCardPublicKey.equals(that.verificationCardPublicKey);
094: }
095:
096: @Override
097: public int hashCode() {
098: return Objects.hash(verificationCardId, encryptedHashedSquaredConfirmationKey, encryptedHashedSquaredPartialChoiceReturnCodes,
099: verificationCardPublicKey);
100: }
101:
102: @Override
103: public ImmutableList<Hashable> toHashableForm() {
104: return ImmutableList
105: .of(HashableString.from(verificationCardId), encryptedHashedSquaredConfirmationKey, encryptedHashedSquaredPartialChoiceReturnCodes);
106: }
107:
108: }
File: e-voting-master/control-components/return-codes-service/src/main/java/ch/post/it/evoting/controlcomponents/returncodes/service/ReturnCodesGenerationConsumer.java
131: @RabbitListener(queues = "${generation.computation.request.queue}", autoStartup = "false")
132: public void onMessage(final Message message) throws IOException {
133: final byte[] messageBody = message.getBody();
134: final byte[] dtoBytes = new byte[messageBody.length - 1];
135: System.arraycopy(messageBody, 1, dtoBytes, 0, messageBody.length - 1);
136:
137: final ChoiceCodeGenerationDTO<ReturnCodeGenerationRequestPayload> choiceCodeGenerationDTO = objectMapper
138: .readValue(dtoBytes, new TypeReference<ChoiceCodeGenerationDTO<ReturnCodeGenerationRequestPayload>>() {
139: });
140:
141: final ReturnCodeGenerationRequestPayload payload = choiceCodeGenerationDTO.getPayload();
142:
143: try {
144: validateSignature(payload);
...
253: private void validateSignature(final ReturnCodeGenerationRequestPayload payload)
254: throws MissingSignatureException, InvalidSignatureException, PayloadVerificationException {
255:
256: final String payloadId = String.format("[electionEventId:%s, verificationCardSetId:%s, chunkID:%s]", payload.getElectionEventId(),
257: payload.getVerificationCardSetId(), payload.getChunkId());
258:
259: LOGGER.info("Checking the signature of payload {}...", payloadId);
260:
261: if (payload.getSignature() == null) {
262: LOGGER.warn("REJECTED payload {} because it is not signed", payloadId);
263: throw new MissingSignatureException(payloadId);
264: }
265:
266: final byte[] payloadHash = hashService.recursiveHash(payload);
267: final boolean isPayloadSignatureValid = payloadSignatureService
268: .verify(payload.getSignature(), returnCodesKeyRepository.getPlatformCACertificate(), payloadHash);
File: e-voting-master/control-components/return-codes-service/src/main/java/ch/post/it/evoting/controlcomponents/returncodes/service/ReturnCodesGenerationConsumer.java
131: @RabbitListener(queues = "${generation.computation.request.queue}", autoStartup = "false")
132: public void onMessage(final Message message) throws IOException {
133: final byte[] messageBody = message.getBody();
134: final byte[] dtoBytes = new byte[messageBody.length - 1];
135: System.arraycopy(messageBody, 1, dtoBytes, 0, messageBody.length - 1);
136:
137: final ChoiceCodeGenerationDTO<ReturnCodeGenerationRequestPayload> choiceCodeGenerationDTO = objectMapper
138: .readValue(dtoBytes, new TypeReference<ChoiceCodeGenerationDTO<ReturnCodeGenerationRequestPayload>>() {
139: });
140:
141: final ReturnCodeGenerationRequestPayload payload = choiceCodeGenerationDTO.getPayload();
142:
143: try {
144: validateSignature(payload);
145:
146: final ElGamalPrivateKey ccrjReturnCodesGenerationSecretKey = getCcrjReturnCodesGenerationSecretKey(payload.getElectionEventId(),
147: payload.getVerificationCardSetId());
148: final ZpSubgroup group = ccrjReturnCodesGenerationSecretKey.getGroup();
149: gqGroup = new GqGroup(group.getP(), group.getQ(), group.getG());
150: electionEventId = payload.getElectionEventId();
151: verificationCardSetId = payload.getVerificationCardSetId();
152:
153: final List<ReturnCodeGenerationOutput> returnCodeGenerationOutputs = genEncLongCodeShares(ccrjReturnCodesGenerationSecretKey,
154: payload.getReturnCodeGenerationInputs());
155:
File: e-voting-master/control-components/return-codes-service/src/main/java/ch/post/it/evoting/controlcomponents/returncodes/service/ReturnCodesGenerationConsumer.java
191: @SuppressWarnings("java:S117")
192: private List<ReturnCodeGenerationOutput> genEncLongCodeShares(final ElGamalPrivateKey ccrjReturnCodesGenerationSecretKey,
193: final List<ReturnCodeGenerationInput> returnCodeGenerationInputs) throws GeneralCryptoLibException {
194:
195: checkNotNull(ccrjReturnCodesGenerationSecretKey);
196: checkNotNull(returnCodeGenerationInputs);
197: checkArgument(!returnCodeGenerationInputs.isEmpty(), "The list of inputs must not be empty.");
198:
199: final String ee = electionEventId;
200: final GqElement g = gqGroup.getGenerator();
201: final List<ReturnCodeGenerationOutput> returnCodeGenerationOutputs = new ArrayList<>();
202:
203: // Algorithm.
204: for (final ReturnCodeGenerationInput returnCodeGenerationInput : returnCodeGenerationInputs) {
205: final String vc_id = returnCodeGenerationInput.getVerificationCardId();
206:
207: final ZqElement k_j_id = deriveVoterChoiceReturnCodeGenerationSecretKey(returnCodeGenerationInput, ccrjReturnCodesGenerationSecretKey,
208: vc_id);
209: final GqElement K_j_id = g.exponentiate(k_j_id);
210: final ZqElement kc_j_id = deriveVoterVoteCastReturnCodeGenerationSecretKey(returnCodeGenerationInput, ccrjReturnCodesGenerationSecretKey,
211: vc_id);
212: final GqElement Kc_j_id = g.exponentiate(kc_j_id);
213:
214: // Compute c_expPCC_j_id.
215: final ElGamalMultiRecipientCiphertext c_pCC_id = returnCodeGenerationInput.getEncryptedHashedSquaredPartialChoiceReturnCodes();
216: final ElGamalMultiRecipientCiphertext c_expPCC_j_id = c_pCC_id.exponentiate(k_j_id);
217:
218: final List<String> i_aux = Arrays.asList(ee, vc_id, "GenEncLongCodeShares", String.valueOf(nodeID));
219:
220: // Compute pi_expPCC_j_id.
221: final GroupVector<GqElement, GqGroup> basesPCC = c_pCC_id.getPhi().prepend(c_pCC_id.getGamma()).prepend(g);
222: final GroupVector<GqElement, GqGroup> exponentiationsPCC = c_expPCC_j_id.getPhi().prepend(c_expPCC_j_id.getGamma()).prepend(K_j_id);
223: final ExponentiationProof pi_expPCC_j_id = zeroKnowledgeProofService.genExponentiationProof(basesPCC, k_j_id, exponentiationsPCC, i_aux);
224:
225: // Compute c_expCK_j_id.
226: final ElGamalMultiRecipientCiphertext c_ck_id = returnCodeGenerationInput.getEncryptedHashedSquaredConfirmationKey();
227: final ElGamalMultiRecipientCiphertext c_expCK_j_id = c_ck_id.exponentiate(kc_j_id);
228:
229: // Compute pi_expCK_j_id.
230: final GroupVector<GqElement, GqGroup> basesCK = c_ck_id.getPhi().prepend(c_ck_id.getGamma()).prepend(g);
231: final GroupVector<GqElement, GqGroup> exponentiationsCK = c_expCK_j_id.getPhi().prepend(c_expCK_j_id.getGamma()).prepend(Kc_j_id);
232: final ExponentiationProof pi_expCK_j_id = zeroKnowledgeProofService.genExponentiationProof(basesCK, kc_j_id, exponentiationsCK, i_aux);
233:
234: // Output.
235: final ReturnCodeGenerationOutput returnCodeGenerationOutput = new ReturnCodeGenerationOutput(vc_id,
236: new ElGamalMultiRecipientPublicKey(Collections.singletonList(K_j_id)),
237: new ElGamalMultiRecipientPublicKey(Collections.singletonList(Kc_j_id)), c_expPCC_j_id, pi_expPCC_j_id, c_expCK_j_id,
238: pi_expCK_j_id);
239: returnCodeGenerationOutputs.add(returnCodeGenerationOutput);
240:
241: // Secure log.
242: logEncryptedConfirmationKeySuccessfullyExponentiated(verificationCardSetId, vc_id);
243: logEncryptedPartialChoiceCodesSuccessfullyExponentiated(verificationCardSetId, vc_id);
244:
245: verificationCardPublicKeyExtendedRepository.save(new VerificationCardPublicKeyExtended(ee, vc_id, verificationCardSetId,
246: returnCodeGenerationInput.getVerificationCardPublicKey()));
247:
248: }
249:
250: return returnCodeGenerationOutputs;
251: }
File: e-voting-master/control-components/return-codes-service/src/main/java/ch/post/it/evoting/controlcomponents/returncodes/service/VotingClientProofsValidator.java
151: private ValidationError validateExponentiationProof(ValidationError result, ZpSubgroup mathematicalGroup, Vote vote) {
152: try {
153: result.setValidationErrorType(ValidationErrorType.FAILED);
154:
155: List<ZpGroupElement> groupElements = new ArrayList<>();
156: String[] exponentiatedCiphertext = getCiphertextElementsFromEncryptedOptions(vote.getEncryptedOptions());
157: for (String elementValue : exponentiatedCiphertext) {
158: ZpGroupElement groupElement = new ZpGroupElement(new BigInteger(elementValue), mathematicalGroup);
159: groupElements.add(groupElement);
160: }
161:
162: // create a list of exponentiated elements consisting of the verification card public key and the exponentiated ciphertexts.
163:
164: final String verificationCardId = vote.getVerificationCardId();
165: final Optional<VerificationCardPublicKeyExtended> optionalVerificationCardPublicKeyExtended = verificationCardPublicKeyExtendedRepository
166: .findById(verificationCardId);
167:
168: if (!optionalVerificationCardPublicKeyExtended.isPresent()) {
169: throw new MissingVerificationCardPublicKeyExtendedException(verificationCardId);
170: }
171:
172: final ElGamalPublicKey verificationCardPublicKey = CryptoAdapters
173: .convert(optionalVerificationCardPublicKeyExtended.get().getVerificationCardPublicKey());
174:
175: // There must be only one exponent in the verification card public key.
176: List<ZpGroupElement> verificationCardPublicKeyList = verificationCardPublicKey.getKeys();
177: if (verificationCardPublicKeyList.size() != 1) {
178: throw new IllegalArgumentException(
179: String.format("Unexpected number of keys: found %s but should be 1.", verificationCardPublicKeyList.size()));
180: }
181:
182: List<ZpGroupElement> exponentiatedElements = new ArrayList<>();
183: ZpGroupElement verificationCardPublicKeySingleElement = verificationCardPublicKeyList.get(0);
184: exponentiatedElements.add(verificationCardPublicKeySingleElement);
185: exponentiatedCiphertext = getCiphertextElementsFromEncryptedOptions(vote.getCipherTextExponentiations());
186:
187: for (String elementValue : exponentiatedCiphertext) {
188: ZpGroupElement exponentiatedElement = new ZpGroupElement(new BigInteger(elementValue), mathematicalGroup);
189: exponentiatedElements.add(exponentiatedElement);
190: }
191:
192: // create a list of base elements consisting of the encryption group's generator and the multiplied voting options ciphertext (E1).
193: List<ZpGroupElement> baseElements = new ArrayList<>();
194:
195: ZpGroupElement groupElementGenerator = new ZpGroupElement(mathematicalGroup.getG(), mathematicalGroup);
196: baseElements.add(groupElementGenerator);
197: baseElements.addAll(groupElements);
198:
199: if (proofsService.createProofVerifierAPI(mathematicalGroup)
200: .verifyExponentiationProof(exponentiatedElements, baseElements, Proof.fromJson(vote.getExponentiationProof()))) {
201: result.setValidationErrorType(ValidationErrorType.SUCCESS);
202: }
203: } catch (GeneralCryptoLibException | NumberFormatException e) {
204: LOGGER.error("The validation of the exponentiation proof failed.", e);
205: }
206: return result;
207:
208: }
209:
- This payload's signature verification logic lacks any kind of anti-replay protection, so the injection of the public keys can be performed by the 'voting server' also at specific times during the 'Voting Phase'.
- 'verificationCardPublicKeyExtendedRawRepository' is a Spring's CrudRepository, whose 'save' method when invoked repeatedly over the same id ('verificationCardId') just updates its previously stored contents.
File: e-voting-master/control-components/return-codes-service/src/main/java/ch/post/it/evoting/controlcomponents/returncodes/domain/VerificationCardPublicKeyExtendedRepository.java
39: public VerificationCardPublicKeyExtended save(final VerificationCardPublicKeyExtended verificationCardPublicKeyExtended) {
40: return toVerificationCardPublicKeyExtended(
41: verificationCardPublicKeyExtendedRawRepository.save(toVerificationCardPublicKeyExtendedRaw(verificationCardPublicKeyExtended)));
42: }
43:
File: e-voting-master/control-components/return-codes-service/src/main/java/ch/post/it/evoting/controlcomponents/returncodes/domain/VerificationCardPublicKeyExtendedRaw.java
13: @Entity
14: @Table(name = "CC_VERIFICATION_CARD_PUBLIC_KEY")
15: class VerificationCardPublicKeyExtendedRaw {
16:
17: private String electionEventId;
18: @Id
19: private String verificationCardId;
#5 - Lack of consistency check allows an adversary to forge the 'verificationCardId' in SecureLog entries
Description
Technical analysis
File: e-voting-master/control-components/return-codes-service/src/main/java/ch/post/it/evoting/controlcomponents/returncodes/service/PartialChoiceReturnCodesDecryptionConsumer.java
187: @RabbitListener(queues = "${verification.decryption.request.queue}", autoStartup = "false")
188: public void onMessage(final Message message) throws SafeStreamDeserializationException {
189:
190: final StreamSerializableObjectReader<ReturnCodeComputationDTO<ReturnCodesInput>> reader = new StreamSerializableObjectReaderImpl<>();
191: final ReturnCodeComputationDTO<ReturnCodesInput> data = reader.read(message.getBody(), 1, message.getBody().length);
192:
193: validateParameters(data);
194: if (isValid(data)) {
195: decryptMessageAndSendToOutputQueue(data);
196: }
197: }
File: e-voting-master/domain/src/main/java/ch/post/it/evoting/domain/returncodes/ReturnCodeComputationDTO.java
32: public ReturnCodeComputationDTO(UUID correlationId, String requestId, String electionEventId, String verificationCardSetId,
33: String verificationCardId, T payload) {
34: super(correlationId);
35: this.requestId = requestId;
36: this.payload = payload;
37: this.electionEventId = electionEventId;
38: this.verificationCardSetId = verificationCardSetId;
39: this.verificationCardId = verificationCardId;
40: }
...
106: @SuppressWarnings("unchecked")
107: @Override
108: public void deserialize(MessageUnpacker unpacker) throws SafeStreamDeserializationException {
109: try {
110: setCorrelationId(UUID.fromString(StreamSerializableUtil.retrieveStringValueWithNullCheck(unpacker)));
111: this.electionEventId = StreamSerializableUtil.retrieveStringValueWithNullCheck(unpacker);
112: this.verificationCardSetId = StreamSerializableUtil.retrieveStringValueWithNullCheck(unpacker);
113: this.verificationCardId = StreamSerializableUtil.retrieveStringValueWithNullCheck(unpacker);
File: e-voting-master/domain/src/main/java/ch/post/it/evoting/domain/returncodes/ReturnCodesInput.java
24: public class ReturnCodesInput implements StreamSerializable {
25:
26: private List<BigInteger> returnCodesInputElements;
27:
28: private ConfirmationKeyVerificationInput confirmationKeyVerificationInput;
29:
30: private PartialChoiceReturnCodesVerificationInput partialChoiceReturnCodesVerificationInput;
...
094: @Override
095: public void deserialize(MessageUnpacker unpacker) throws SafeStreamDeserializationException {
096: try {
097: if (unpacker.tryUnpackNil()) {
098: this.returnCodesInputElements = null;
099: } else {
100: int listSize = unpacker.unpackArrayHeader();
101: returnCodesInputElements = new ArrayList<>(listSize);
102: for (int i = 0; i < listSize; i++) {
103: returnCodesInputElements.add(StreamSerializableUtil.retrieveBigIntegerValueWithNullCheck(unpacker));
104: }
105: }
106:
107: if (MessageFormat.NIL.equals(unpacker.getNextFormat())) {
108: confirmationKeyVerificationInput = null;
109: unpacker.unpackNil();
110: } else {
111: confirmationKeyVerificationInput = new ConfirmationKeyVerificationInput();
112: confirmationKeyVerificationInput.setConfirmationMessage(StreamSerializableUtil.retrieveStringValueWithNullCheck(unpacker));
113: confirmationKeyVerificationInput.setVotingCardId(StreamSerializableUtil.retrieveStringValueWithNullCheck(unpacker));
114: }
115: if (MessageFormat.NIL.equals(unpacker.getNextFormat())) {
116: partialChoiceReturnCodesVerificationInput = null;
117: unpacker.unpackNil();
118: } else {
119: partialChoiceReturnCodesVerificationInput = new PartialChoiceReturnCodesVerificationInput();
120: partialChoiceReturnCodesVerificationInput
121: .setVerificationCardSetDataJwt(StreamSerializableUtil.retrieveStringValueWithNullCheck(unpacker));
122: partialChoiceReturnCodesVerificationInput.setElectionPublicKeyJwt(StreamSerializableUtil.retrieveStringValueWithNullCheck(unpacker));
123: partialChoiceReturnCodesVerificationInput.setVote(StreamSerializableUtil.retrieveStringValueWithNullCheck(unpacker));
124: }
125:
126: certificates = StreamSerializableUtil.retrieveStringValueWithNullCheck(unpacker);
127: } catch (IOException e) {
128: throw new SafeStreamDeserializationException(e);
129: }
130: }
File: e-voting-master/domain/src/main/java/ch/post/it/evoting/domain/election/model/vote/Vote.java
018: /**
019: * This class represents the vote in this context.
020: */
021: @JsonIgnoreProperties(ignoreUnknown = true)
022: public class Vote {
023:
024: // The identifier of the tenant.
025: @NotNull(groups = SyntaxErrorGroup.class)
026: @Pattern(regexp = Patterns.ID, groups = SemanticErrorGroup.class)
027: @Size(min = 1, max = Constants.COLUMN_LENGTH_100, groups = SemanticErrorGroup.class)
028: private String tenantId;
029:
...
092: /**
093: * The vote ciphertext that contains the exponentiated elements (C'0, C'1).
094: */
095: private String cipherTextExponentiations;
096:
097: /**
098: * The exponentiation proof.
099: */
100: private String exponentiationProof;
101:
102: /**
103: * The plaintext equality proof.
104: */
105: private String plaintextEqualityProof;
106:
107: /**
108: * The identifier of the verification card.
109: */
110: private String verificationCardId;
111:
- Consumed by the DTO
- Consumed when the deserialization of the encapsulated 'Vote' is invoked.
File: e-voting-master/control-components/return-codes-service/src/main/java/ch/post/it/evoting/controlcomponents/returncodes/service/PartialChoiceReturnCodesDecryptionConsumer.java
226: /**
227: * Checks if the input data is valid.
228: */
229: private boolean isValid(final ReturnCodeComputationDTO<ReturnCodesInput> data) {
230:
231: final String electionEventId = validateUUID(data.getElectionEventId());
232: final String verificationCardSetId = validateUUID(data.getVerificationCardSetId());
233: final String verificationCardId = validateUUID(data.getVerificationCardId());
234:
235: // Get the mathematicalGroup
236: final ZpSubgroup mathematicalGroup;
237: try {
238: mathematicalGroup = returnCodesKeyRepository.getMathematicalGroup(electionEventId, verificationCardSetId);
239:
240: if (mathematicalGroup == null) {
241: LOGGER.error("Unexpected scenario - encrypted partial Choice Return Codes' mathematicalGroup is null.");
242: return false;
243: }
244:
245: } catch (KeyManagementException e) {
246: LOGGER.error("Unexpected error getting the group for election event id {} and verification card set id {}.", electionEventId,
247: verificationCardSetId);
248: return false;
249: }
250:
251: if (!isPartialChoiceReturnCodesComputation(data)) {
252: LOGGER.error("Unexpected scenario - Necessary input to decrypt the encrypted partial Choice Return Codes is empty.");
253: return false;
254: }
255:
256: if (data.getPayload().getConfirmationKeyVerificationInput() != null) {
257: LOGGER.error("Unexpected scenario - Confirmation Key verification input must be empty.");
258: return false;
259: }
260:
261: // We abort the process if the control component already decrypted the partial Choice Return Codes for this verificationCardId
262: if (computedVerificationCardRepository.existsById(new ComputedVerificationCardPrimaryKey(electionEventId, verificationCardId))) {
263: LOGGER.error("Verification card has already been computed for electionEventId {} and verificationCardId {}", data.getElectionEventId(),
264: data.getVerificationCardId());
265: return false;
266: }
267:
268: LOGGER.info("Verification card is yet to be computed for electionEventId {} and verificationCardId {}", data.getElectionEventId(),
269: data.getVerificationCardId());
270:
271: if (!checkInputDataConsistency(data, mathematicalGroup)) {
272: return false;
273: }
274:
275: final PartialChoiceReturnCodesVerificationInput partialChoiceReturnCodesVerificationInput = data.getPayload()
276: .getPartialChoiceReturnCodesVerificationInput();
277:
278: // verify and obtain admin board public key
279: final PublicKey adminBoardPublicKey;
280: try {
281: adminBoardPublicKey = verifyChainAndGetAdminBoardPublicKey(data.getPayload().getCertificates());
282: } catch (GeneralCryptoLibException e) {
283: LOGGER.error("Could not read certificates from json.");
284: return false;
285: } catch (CertificateChainValidationException e) {
286: LOGGER.error("Invalid certificate chain: {}", String.join(" | ", certificateValidator.getErrors()));
287: return false;
288: }
289:
290: return votingClientProofsValidator.validateVoteAndProofs(mathematicalGroup, partialChoiceReturnCodesVerificationInput, adminBoardPublicKey);
291: }
File: e-voting-master/control-components/return-codes-service/src/main/java/ch/post/it/evoting/controlcomponents/returncodes/service/VotingClientProofsValidator.java
091: public boolean validateVoteAndProofs(ZpSubgroup mathematicalGroup,
092: PartialChoiceReturnCodesVerificationInput partialChoiceReturnCodesVerificationInput, PublicKey adminBoardPublicKey) {
093: try {
094: Vote voteObject = ObjectMappers.fromJson(partialChoiceReturnCodesVerificationInput.getVote(), Vote.class);
095:
096: ElGamalPublicKey electionPublicKey = verifyAndGetElectionPublicKey(partialChoiceReturnCodesVerificationInput.getElectionPublicKeyJwt(),
097: adminBoardPublicKey);
098:
099: if (electionPublicKey == null) {
100: LOGGER.error("The election public key is null.");
101: return false;
102: }
103:
104: VerificationCardSetData verificationCardSetData = verifyAndGetVerificationCardSetData(
105: partialChoiceReturnCodesVerificationInput.getVerificationCardSetDataJwt(), adminBoardPublicKey);
106:
107: if (verificationCardSetData == null) {
108: LOGGER.error("The verification card set data is null.");
109: return false;
110: }
111:
112: if (!verifyVerificationCardPublicKey(verificationCardSetData.getVerificationCardSetIssuerCert(), voteObject)) {
113: LOGGER.error("The Verification Card Public Key could not be verified.");
114: return false;
115: }
116:
117: ValidationError exponentiationProofValidation = validateExponentiationProof(new ValidationError(), mathematicalGroup, voteObject);
118: boolean exponentiationProofValidationResult = !ValidationErrorType.FAILED.equals(exponentiationProofValidation.getValidationErrorType());
119: if (!exponentiationProofValidationResult) {
120: String errorArgs = StringUtils.join(exponentiationProofValidation.getErrorArgs());
121: LOGGER.error("Exponentiation proof not valid. {}", errorArgs);
122: }
123:
124: ValidationError plaintextEqualityProofValidation = validatePlaintextEqualityProof(new ValidationError(), mathematicalGroup, voteObject,
125: electionPublicKey, verificationCardSetData.getChoicesCodesEncryptionPublicKey());
126: boolean plaintextEqualityProofValidationResult = !ValidationErrorType.FAILED
File: e-voting-master/control-components/return-codes-service/src/main/java/ch/post/it/evoting/controlcomponents/returncodes/service/VotingClientProofsValidator.java
146: * Validates an exponentiation zero-knowledge proof.
147: * <p>
148: * At this point, we already checked the group membership of all elements, via the class {@link ZpSubgroupProofVerifier} of CryptoLib.
149: * </p>
150: */
151: private ValidationError validateExponentiationProof(ValidationError result, ZpSubgroup mathematicalGroup, Vote vote) {
152: try {
153: result.setValidationErrorType(ValidationErrorType.FAILED);
154:
155: List<ZpGroupElement> groupElements = new ArrayList<>();
156: String[] exponentiatedCiphertext = getCiphertextElementsFromEncryptedOptions(vote.getEncryptedOptions());
157: for (String elementValue : exponentiatedCiphertext) {
158: ZpGroupElement groupElement = new ZpGroupElement(new BigInteger(elementValue), mathematicalGroup);
159: groupElements.add(groupElement);
160: }
161:
162: // create a list of exponentiated elements consisting of the verification card public key and the exponentiated ciphertexts.
163:
164: final String verificationCardId = vote.getVerificationCardId();
165: final Optional<VerificationCardPublicKeyExtended> optionalVerificationCardPublicKeyExtended = verificationCardPublicKeyExtendedRepository
166: .findById(verificationCardId);
167:
168: if (!optionalVerificationCardPublicKeyExtended.isPresent()) {
169: throw new MissingVerificationCardPublicKeyExtendedException(verificationCardId);
170: }
- The DTO's 'verificationCardId' value is used to update the list of partially decrypted PCC (line 488). This means the attacker can invoke the 'partialDecryptPCC' multiple times, with arbitrary valid votes, as long as the malicious voting server uses a different, but valid, vcdid (in the same election context) for the serialized DTO. As this vcdid will be added to the List of already decrypted pCCs, the voter associated to that vcdid will be unable to complete the 'SendVote' protocol (thus effectively preventing to cast a vote) even thou he/she never legitimately initiated that phase before.
- The DTO's 'verificationCardId' value is used at 'logPartialDecryptPccExponentiationProofSuccessfullyComputed' (line 475) where the secure log that will be consumed by the trusted auditors is generated. The vcdid used to generate the secure log entry is different from the vcdid used to perform the exponentiation.
File: e-voting-master/control-components/return-codes-service/src/main/java/ch/post/it/evoting/controlcomponents/returncodes/service/PartialChoiceReturnCodesDecryptionConsumer.java
414: private void decryptMessageAndSendToOutputQueue(final ReturnCodeComputationDTO<ReturnCodesInput> data) {
415: LOGGER.info("Decrypting the encrypted partial Choice Return Codes.");
416:
417: final String electionEventId = data.getElectionEventId();
418: final String verificationCardSetId = data.getVerificationCardSetId();
419: final String verificationCardId = data.getVerificationCardId();
420:
421: // This is the gamma element of the encrypted partial choice return codes.
422: final BigInteger gammaEncryptedPartialChoiceReturnCodesBigInteger = data.getPayload().getReturnCodesInputElements().get(0);
423:
424: final ElGamalPrivateKey ccrjChoiceReturnCodesEncryptionPrivateKey;
425: final ZpGroupElement gammaEncryptedPartialChoiceReturnCodes;
426: try {
427: ccrjChoiceReturnCodesEncryptionPrivateKey = returnCodesKeyRepository
428: .getCcrjChoiceReturnCodesEncryptionSecretKey(electionEventId, verificationCardSetId);
429:
430: gammaEncryptedPartialChoiceReturnCodes = new ZpGroupElement(gammaEncryptedPartialChoiceReturnCodesBigInteger,
431: ccrjChoiceReturnCodesEncryptionPrivateKey.getGroup());
432: } catch (GeneralCryptoLibException e) {
433: LOGGER.error(FAILED_TO_OBTAIN_THE_DECRYPTION_INPUT_PARAMETERS, e);
434: return;
435: } catch (KeyManagementException e) {
436: LOGGER.error(FAILED_FAILED_TO_GET_THE_CCR_J_CHOICE_RETURN_CODES_ENCRYPTION_PRIVATE_KEY_FROM_THE_KEY_REPOSITORY, e);
437: return;
438: }
439:
440: final List<Exponent> ccrjChoiceReturnCodesEncryptionPrivateKeyElements = ccrjChoiceReturnCodesEncryptionPrivateKey.getKeys();
441: if (ccrjChoiceReturnCodesEncryptionPrivateKeyElements.isEmpty()) {
442: LOGGER.error(CCR_J_CHOICE_RETURN_CODES_ENCRYPTION_PRIVATE_KEY_DID_NOT_CONTAIN_ANY_EXPONENT);
443: throw new IllegalStateException(CCR_J_CHOICE_RETURN_CODES_ENCRYPTION_PRIVATE_KEY_DID_NOT_CONTAIN_ANY_EXPONENT);
444: }
445:
446: // gamma element exponentiated to the CCR_j Choice Return Codes Encryption private key elements.
447: final List<ZpGroupElement> gammaExponentiatedToPrivateKeyElements = new ArrayList<>(ccrjChoiceReturnCodesEncryptionPrivateKeyElements.size());
448:
449: // gamma element exponentiated to the CCR_j Choice Return Codes Encryption private key elements as a JSON element.
450: final List<String> gammaExponentiatedToPrivateKeyElementsJson = new ArrayList<>(ccrjChoiceReturnCodesEncryptionPrivateKeyElements.size());
451:
452: try {
453: for (final Exponent exponent : ccrjChoiceReturnCodesEncryptionPrivateKeyElements) {
454: final ZpGroupElement exponentiated = gammaEncryptedPartialChoiceReturnCodes.exponentiate(exponent);
455: gammaExponentiatedToPrivateKeyElements.add(exponentiated);
456: gammaExponentiatedToPrivateKeyElementsJson.add(exponentiated.toJson());
457: }
458: } catch (GeneralCryptoLibException e) {
459: LOGGER.error(FAILED_TO_OBTAIN_THE_DECRYPTION_INPUT_PARAMETERS, e);
460: return;
461: }
462:
463: LOGGER.info(
464: "Partially decrypted the encrypted partial Choice Return Codes using the CCR_j Choice Return Codes encryption secret key for election event ID {}, verification card set ID {} and verification card ID{}",
465: electionEventId, verificationCardSetId, verificationCardId);
466:
467: final ElGamalPublicKey ccrjChoiceReturnCodesEncryptionPublicKey;
468: final Proof exponentiationProof;
469: try {
470: ccrjChoiceReturnCodesEncryptionPublicKey = returnCodesKeyRepository
471: .getCcrjChoiceReturnCodesEncryptionPublicKey(electionEventId, verificationCardSetId);
472:
473: exponentiationProof = calculateExponentiationProof(ccrjChoiceReturnCodesEncryptionPrivateKey, ccrjChoiceReturnCodesEncryptionPublicKey,
474: gammaEncryptedPartialChoiceReturnCodes, gammaExponentiatedToPrivateKeyElements);
475: logPartialDecryptPccExponentiationProofSuccessfullyComputed(data, gammaEncryptedPartialChoiceReturnCodes,
476: gammaExponentiatedToPrivateKeyElements, exponentiationProof);
477:
478: } catch (GeneralCryptoLibException e) {
479: LOGGER.error("Error while generating proof of knowledge of the CCR_j Choice Return Codes secret key.");
480: LOGGER.error(FAILED_TO_OBTAIN_THE_DECRYPTION_INPUT_PARAMETERS, e);
481: return;
482: } catch (KeyManagementException e) {
483: LOGGER.error(FAILED_FAILED_TO_GET_THE_CCR_J_CHOICE_RETURN_CODES_ENCRYPTION_PRIVATE_KEY_FROM_THE_KEY_REPOSITORY, e);
484: return;
485: }
486:
487: // Add the verification card id to the list of partial decrypted PCC
488: computedVerificationCardRepository.save(new ComputedVerificationCard(electionEventId, verificationCardId));
489:
490: final ChoiceCodesVerificationDecryptResPayload resultPayload = new ChoiceCodesVerificationDecryptResPayload();
491:
492: resultPayload.setDecryptContributionResult(gammaExponentiatedToPrivateKeyElementsJson);
493: try {
494: resultPayload.setExponentiationProofJson(exponentiationProof.toJson());
495: resultPayload.setPublicKeyJson(ccrjChoiceReturnCodesEncryptionPublicKey.toJson());
496: } catch (GeneralCryptoLibException e) {
497: LOGGER.error(FAILED_TO_OBTAIN_THE_DECRYPTION_INPUT_PARAMETERS, e);
498: return;
499: }
500:
501: try {
502: final PayloadSignature payloadSignature = payloadSigner.sign(resultPayload, keysManager.getElectionSigningPrivateKey(electionEventId),
503: keysManager.getElectionSigningCertificateChain(electionEventId));
504: resultPayload.setSignature(payloadSignature);
505: LOGGER.info("Partially decrypted partial Choice Return Codes and corresponding proofs correctly signed.");
506: } catch (KeyManagementException e) {
507: LOGGER.error(FAILED_FAILED_TO_GET_THE_CCR_J_CHOICE_RETURN_CODES_ENCRYPTION_PRIVATE_KEY_FROM_THE_KEY_REPOSITORY, e);
508: return;
509: } catch (PayloadSignatureException e) {
510: LOGGER.error(FAILED_TO_SIGN_PARTIAL_CHOICE_RETURN_CODES_DECRYPTION_RESULT, e);
511: return;
512: }
513:
514: final UUID correlationId = data.getCorrelationId();
515: final String requestId = data.getRequestId();
516:
517: final ReturnCodeComputationDTO<ChoiceCodesVerificationDecryptResPayload> returnCodeComputationDTO = new ReturnCodeComputationDTO<>(
518: correlationId, requestId, electionEventId, verificationCardSetId, verificationCardId, resultPayload);
519:
520: final Message amqpMessage = MessageSerialisation.getMessage(returnCodeComputationDTO);
521:
522: rabbitTemplate.send(decryptionOutputQueue, amqpMessage);
523: }
File: e-voting-master/control-components/return-codes-service/src/main/java/ch/post/it/evoting/controlcomponents/returncodes/service/PartialChoiceReturnCodesDecryptionConsumer.java
563: private void logPartialDecryptPccExponentiationProofSuccessfullyComputed(final ReturnCodeComputationDTO<ReturnCodesInput> data,
564: final ZpGroupElement gammaEncryptedPartialChoiceReturnCodes, final List<ZpGroupElement> gammaExponentiatedToPrivateKeyElements,
565: final Proof exponentiationProof) {
566:
567: ControlComponentContext context = new ControlComponentContext(data.getElectionEventId(), data.getVerificationCardSetId(), controlComponentId);
568:
569: PartialDecryptPccExponentiationProof partialDecryptPccExponentiationProof = new PartialDecryptPccExponentiationProof(
570: data.getVerificationCardId(), gammaEncryptedPartialChoiceReturnCodes.getValue(), gammaExponentiatedToPrivateKeyElements,
571: exponentiationProof);
572:
573: final ReturnCodesMessage message = returnCodesMessageFactory
574: .buildPartialDecryptPccExponentiationProofLogMessage(context, partialDecryptPccExponentiationProof);
575:
576: SECURE_LOGGER.info(message);
577: }
#7 - Improper parsing of the request body when validating signatures for secure requests
Description
Technical analysis
File: e-voting-master/voting-server/commons/commons-infrastructure/src/main/java/ch/post/it/evoting/votingserver/commons/infrastructure/remote/client/RestClientInterceptor.java
093: private String getRequestBodyToString(final RequestBody request) {
094: final RequestBody copy = request;
095: try (final Buffer buffer = new Buffer()) {
096: if (copy != null) {
097: copy.writeTo(buffer);
098: } else {
099: return "";
100: }
101: return buffer.readUtf8();
102: } catch (final IOException e) {
103: final String errorMsg = "Exception occurred during process body to String";
104: LOGGER.error(errorMsg, e);
105: return "";
106: }
107: }
File: e-voting-master/voting-server/commons/commons-infrastructure/src/main/java/ch/post/it/evoting/votingserver/commons/infrastructure/remote/filter/SignedRequestFilter.java
184: /**
185: * Reads the request body from the request and returns it as a String.
186: *
187: * @param multiReadHttpServletRequest HttpServletRequest that contains the request body
188: * @return request body as a String or null
189: */
190: private String getRequestBodyToString(final MultiReadHttpServletRequest multiReadHttpServletRequest) {
191: try {
192: // Read from request
193: StringBuilder buffer = new StringBuilder();
194: BufferedReader reader = multiReadHttpServletRequest.getReader();
195: String line;
196: while ((line = reader.readLine()) != null) {
197: buffer.append(line);
198: }
199: return buffer.toString();
200: } catch (Exception e) {
201: LOGGER.error("Failed to read the request body from the request.", e);
202: }
203: return null;
204: }