/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.products.ov.sdm.application.service;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonString;
import javax.json.JsonValue;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.elgamal.ElGamalServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.scytl.keystore.ScytlKeyStoreServiceAPI;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.cryptolib.elgamal.bean.ElGamalEncryptionParameters;
import com.scytl.cryptolib.elgamal.bean.ElGamalPrivateKey;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.elgamal.service.ElGamalService;
import com.scytl.cryptolib.primitives.primes.utils.PemUtils;
import com.scytl.cryptolib.scytl.keystore.cryptoapi.CryptoAPIScytlKeyStore;
import com.scytl.cryptolib.scytl.keystore.service.ScytlKeyStoreService;
import com.scytl.products.oscore.logging.api.domain.Level;
import com.scytl.products.oscore.logging.api.domain.LogContent;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.oscore.logging.core.provider.TransactionInfoProvider;
import com.scytl.products.ov.commons.beans.AuthenticationContextData;
import com.scytl.products.ov.commons.beans.AuthenticationVoterData;
import com.scytl.products.ov.commons.beans.ElectionInformationContents;
import com.scytl.products.ov.commons.beans.SharesPublicKey;
import com.scytl.products.ov.commons.beans.exceptions.LambdaException;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.configuration.JSONSignatureConfiguration;
import com.scytl.products.ov.commons.path.PathResolver;
import com.scytl.products.ov.commons.sign.JSONSigner;
import com.scytl.products.ov.commons.util.JsonUtils;
import com.scytl.products.ov.config.shares.domain.CreateSharesOperationContext;
import com.scytl.products.ov.config.shares.domain.SharesType;
import com.scytl.products.ov.config.shares.exception.SharesException;
import com.scytl.products.ov.config.shares.handler.CreateSharesHandler;
import com.scytl.products.ov.config.shares.handler.StatelessReadSharesHandler;
import com.scytl.products.ov.config.shares.keys.PrivateKeySerializer;
import com.scytl.products.ov.config.shares.keys.elgamal.ElGamalKeyPairGenerator;
import com.scytl.products.ov.config.shares.keys.elgamal.ElGamalPrivateKeySerializer;
import com.scytl.products.ov.config.shares.keys.elgamal.ElGamalPublicKeyAdapter;
import com.scytl.products.ov.config.shares.service.PrivateKeySharesService;
import com.scytl.products.ov.config.shares.service.SmartCardService;
import com.scytl.products.ov.config.shares.service.SmartCardServiceFactory;
import com.scytl.products.ov.constants.Constants;
import com.scytl.products.ov.sdm.application.config.SmartCardConfig;
import com.scytl.products.ov.sdm.application.exception.ElectionAuthorityServiceException;
import com.scytl.products.ov.sdm.application.log.SdmSecureLogEvent;
import com.scytl.products.ov.sdm.application.patch.CreateEBKeysSerializer;
import com.scytl.products.ov.sdm.domain.common.SignedObject;
import com.scytl.products.ov.sdm.domain.model.administrationauthority.ActivateOutputData;
import com.scytl.products.ov.sdm.domain.model.ballot.BallotRepository;
import com.scytl.products.ov.sdm.domain.model.config.CreateElectoralBoardKeyPairInput;
import com.scytl.products.ov.sdm.domain.model.electionevent.ElectionEventRepository;
import com.scytl.products.ov.sdm.domain.model.electoralauthority.ElectoralAuthorityRepository;
import com.scytl.products.ov.sdm.domain.model.status.SmartCardStatus;
import com.scytl.products.ov.sdm.domain.model.status.Status;
import com.scytl.products.ov.sdm.domain.model.status.SynchronizeStatus;
import com.scytl.products.ov.sdm.domain.service.ConfigConstants;
import com.scytl.products.ov.sdm.domain.service.impl.CCPublicKeySignatureValidator;
import com.scytl.products.ov.sdm.domain.service.impl.ElectoralAuthorityDataGeneratorServiceImpl;
import com.scytl.products.ov.sdm.domain.service.utils.ElGamalPublicKeyCombinerWithCompression;
import com.scytl.products.ov.sdm.infrastructure.JsonConstants;
import com.scytl.products.ov.sdm.infrastructure.service.ConfigurationEntityStatusService;
import com.scytl.products.ov.utils.ConfigObjectMapper;

/**
 * The Class ElectoralAuthorityService.
 */
@Service
public class ElectoralAuthorityService {

	/** The Constant LOGGER. */
	private static final Logger LOGGER = LoggerFactory.getLogger(ElectoralAuthorityService.class);

	/** The _create shares handler el gamal map. */
	private final Map<String, CreateSharesHandler> createSharesHandlerElGamalMap = new HashMap<>();

	/** The _create shares operation context el gamal. */
	private final CreateSharesOperationContext createSharesOperationContextElGamal = new CreateSharesOperationContext(
			SharesType.ELECTORAL_BOARD);

	/** The _private key shares service el gamal. */
	private final PrivateKeySharesService privateKeySharesServiceElGamal = new PrivateKeySharesService(
			new ElGamalPrivateKeySerializer());

    /** The _electoral authority repository. */
    @Autowired
    private ElectoralAuthorityRepository electoralAuthorityRepository;

    /** The _election event repository. */
    @Autowired
    private ElectionEventRepository electionEventRepository;

    @Autowired
    private BallotRepository ballotRepository;

    /** The _electoral authority data generator service impl. */
    @Autowired
    private ElectoralAuthorityDataGeneratorServiceImpl electoralAuthorityDataGeneratorServiceImpl;

    /** The _status service. */
    @Autowired
    private ConfigurationEntityStatusService statusService;

    /** The _path resolver. */
    @Autowired
    private PathResolver pathResolver;
    
    @Autowired
    private PlatformRootCAService platformRootCAService;
    
    @Autowired
    private CCPublicKeySignatureValidator keySignatureValidator; 

    /** The _smartcard service. */
    private SmartCardService smartcardService;

    /** The _create eb keys serializer. */
    private CreateEBKeysSerializer createEBKeysSerializer;

    /** The _puk. */
    @Value("${smartcards.puk:222222}")
    private String puk;

    @Value("${tenantID}")
    private String tenantId;

    /** The _stores service. */
    private ScytlKeyStoreServiceAPI storesServiceAPI;

    private AsymmetricServiceAPI asymmetricServiceAPI;

    private ElGamalServiceAPI elGamalServiceAPI;

    /** The _stateless read shares handler. */
    private StatelessReadSharesHandler statelessReadSharesHandler;

    /** The _config object mapper. */
    private ConfigObjectMapper configObjectMapper;

    /** The _signer. */
    private JSONSigner signer;

    @Autowired
    SmartCardConfig smartCardConfig;

    @Autowired
    private TransactionInfoProvider transactionInfoProvider;

    @Autowired
    private SecureLoggingWriter secureLogger;

    /**
     * Inits the.
     *
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     * @throws GeneralCryptoLibException
     *             if there was a problem when trying to create a
     *             ScytlKeyStoreService.
     */
    @PostConstruct
    public void init() throws IOException, GeneralCryptoLibException {

        smartcardService = SmartCardServiceFactory
            .getSmartCardService(smartCardConfig.isSmartCardEnabled());

        storesServiceAPI = new ScytlKeyStoreService();

        asymmetricServiceAPI = new AsymmetricService();

        elGamalServiceAPI = new ElGamalService();

        configObjectMapper = new ConfigObjectMapper();
        createEBKeysSerializer =
            new CreateEBKeysSerializer(configObjectMapper);

        PrivateKeySerializer elGamalPrivateKeySerializer =
            new ElGamalPrivateKeySerializer();

        statelessReadSharesHandler = new StatelessReadSharesHandler(
            elGamalPrivateKeySerializer, smartcardService,
            asymmetricServiceAPI, elGamalServiceAPI);

        signer =
            new JSONSigner(JSONSignatureConfiguration.RSA_PSS_SHA256);
    }

    /**
     * Gets the smart card reader status.
     *
     * @return the smart card reader status
     */
    public SmartCardStatus getSmartCardReaderStatus() {

        LOGGER.info("Checking smartCardReader status...");

        SmartCardStatus status = SmartCardStatus.EMPTY;
        if (smartcardService.isSmartcardOk()) {
            status = SmartCardStatus.INSERTED;
        }

        LOGGER.info("SmartCardReader status is " + status);

        return status;
    }

    /**
     * Constitute.
     *
     * @param electionEventId
     *            the election event id
     * @param electoralAuthorityId
     *            the electoral authority id
     * @throws ResourceNotFoundException
     *             the resource not found exception
     * @throws SharesException
     *             the shares exception
     * @throws GeneralCryptoLibException
     */
    public void constitute(final String electionEventId,
            final String electoralAuthorityId)
            throws ResourceNotFoundException, SharesException,
            GeneralCryptoLibException {

        LOGGER.info("Constituting electoral authority...");

        transactionInfoProvider.generate(tenantId, "", "");

        secureLogger.log(Level.INFO, new LogContent.LogContentBuilder()
            .logEvent(SdmSecureLogEvent.CONSTITUTING_ELECTORAL_AUTHORITY)
            .objectId(electoralAuthorityId).user(electoralAuthorityId)
            .electionEvent(electionEventId).createLogInfo());

        ElGamalKeyPairGenerator elGamalKeyPairGenerator =
            createElGamalKeyPairGenerator(electionEventId,
                elGamalServiceAPI);

        CreateSharesHandler createSharesHandlerElGamal =
            new CreateSharesHandler(createSharesOperationContextElGamal,
                elGamalKeyPairGenerator, privateKeySharesServiceElGamal,
                smartcardService);

        try {
            createSharesHandlerElGamalMap.put(electoralAuthorityId,
                createSharesHandlerElGamal);

            // get threshold and number of members
            JsonObject electoralAuthority =
                getElectoralAuthorityJsonObject(electoralAuthorityId);

            Integer minimumThreshold = Integer.parseInt(electoralAuthority
                .getString(ConfigConstants.MINIMUM_THRESHOLD));

            JsonArray administrationBoardMembers = electoralAuthority
                .getJsonArray(ConfigConstants.ELECTORAL_BOARD_LABEL);
            int numberOfMembers = administrationBoardMembers.size();

            LOGGER.info("Threshold " + minimumThreshold
                + ", numberOfMembers " + numberOfMembers);

            createSharesHandlerElGamal.generateAndSplit(numberOfMembers,
                minimumThreshold);

        } catch (ResourceNotFoundException | SharesException e) {
            secureLogger.log(Level.ERROR,
                new LogContent.LogContentBuilder().logEvent(
                    SdmSecureLogEvent.ELECTORAL_AUTHORITY_CONSTITUTION_FAILED)
                    .objectId(electoralAuthorityId)
                    .user(electoralAuthorityId)
                    .electionEvent(electionEventId)
                    .additionalInfo("err_desc", e.getMessage())
                    .createLogInfo());
            throw e;
        }

        LOGGER.info("Electoral authority constituted.");
        secureLogger.log(Level.INFO,
            new LogContent.LogContentBuilder().logEvent(
                SdmSecureLogEvent.ELECTORAL_AUTHORITY_CONSTITUTION_COMPLETED_SUCCESSFULLY)
                .objectId(electoralAuthorityId).user(electoralAuthorityId)
                .electionEvent(electionEventId).createLogInfo());
    }

    /**
     * Change the state of the electoral authority from constituted to SIGNED
     * for a given election event and electoral authority id.
     *
     * @param electionEventId
     *            the election event id.
     * @param electoralAuthorityId
     *            the electoral authority unique id.
     * @param privateKeyPEM
     *            the private key pem
     * @return true if the status is successfully changed to signed. Otherwise,
     *         false.
     * @throws ResourceNotFoundException
     *             if the electoral authority is not found.
     * @throws GeneralCryptoLibException
     *             the general crypto lib exception
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    public boolean sign(final String electionEventId,
            final String electoralAuthorityId, final String privateKeyPEM)
            throws ResourceNotFoundException, GeneralCryptoLibException,
            IOException {

        boolean result = false;

        transactionInfoProvider.generate(tenantId, "", "");

        secureLogger.log(Level.INFO,
            new LogContent.LogContentBuilder()
                .logEvent(SdmSecureLogEvent.SIGNING_ELECTORAL_AUTHORITY)
                .objectId(electoralAuthorityId)
                .electionEvent(electionEventId).createLogInfo());

        JsonObject electoralAuthorityJson;
        try {
            electoralAuthorityJson =
                getElectoralAuthorityJsonObject(electoralAuthorityId);

            if (electoralAuthorityJson != null
                && electoralAuthorityJson.containsKey(
                    JsonConstants.JSON_ATTRIBUTE_NAME_STATUS)) {
                String status = electoralAuthorityJson
                    .getString(JsonConstants.JSON_ATTRIBUTE_NAME_STATUS);
                if (Status.READY.name().equals(status)) {

                    PrivateKey privateKey =
                        PemUtils.privateKeyFromPem(privateKeyPEM);

                    LOGGER.info("Signing authentication context data");
                    signAuthenticationContextData(electionEventId,
                        privateKey);
                    LOGGER.info("Signing election information contents");
                    signElectionInformationContents(electionEventId,
                        privateKey);
                    LOGGER.info("Signing electoral authority "
                        + electoralAuthorityId);
                    signElectoralAuthority(electionEventId,
                        electoralAuthorityId, privateKey);

                    LOGGER.info(
                        "Changing the status of the electoral authority");
                    statusService.updateWithSynchronizedStatus(
                        Status.SIGNED.name(), electoralAuthorityId,
                        electoralAuthorityRepository,
                        SynchronizeStatus.PENDING);
                    result = true;

                    LOGGER.info(
                        "The electoral authority was successfully signed");
                    secureLogger.log(Level.INFO,
                        new LogContent.LogContentBuilder().logEvent(
                            SdmSecureLogEvent.ELECTORAL_AUTHORITY_SIGNING_COMPLETED_SUCCESSFULLY)
                            .objectId(electoralAuthorityId)
                            .electionEvent(electionEventId)
                            .createLogInfo());

                } else {
                    secureLogger.log(Level.ERROR,
                        new LogContent.LogContentBuilder().logEvent(
                            SdmSecureLogEvent.ELECTORAL_AUTHORITY_SIGNING_FAILED)
                            .objectId(electoralAuthorityId)
                            .electionEvent(electionEventId)
                            .additionalInfo("err_desc",
                                "electoralAuthorityJson status is not ready")
                            .createLogInfo());
                }
            }
        } catch (IOException | GeneralCryptoLibException
                | ResourceNotFoundException e) {
            secureLogger.log(Level.ERROR,
                new LogContent.LogContentBuilder().logEvent(
                    SdmSecureLogEvent.ELECTORAL_AUTHORITY_SIGNING_FAILED)
                    .objectId(electoralAuthorityId)
                    .electionEvent(electionEventId)
                    .additionalInfo("err_desc", e.getMessage())
                    .createLogInfo());

            throw e;
        }

        return result;
    }

    /**
     * Write share.
     *
     * @param electionEventId
     *            the election event id
     * @param electoralAuthorityId
     *            the electoral authority id
     * @param shareNumber
     *            the share number
     * @param pin
     *            the pin
     * @throws ResourceNotFoundException
     *             the resource not found exception
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     * @throws GeneralCryptoLibException
     *             the general crypto lib exception
     * @throws SharesException
     *             the shares exception
     */
    public void writeShare(final String electionEventId,
            final String electoralAuthorityId, final Integer shareNumber,
            final String pin)
            throws ResourceNotFoundException, IOException,
            GeneralCryptoLibException, SharesException {

        CreateSharesHandler createSharesHandlerElGamal =
            getHandler(electoralAuthorityId);

        JsonObject electoralAuthority =
            getElectoralAuthorityJsonObject(electoralAuthorityId);
        JsonArray electoralAuthorityMembers = electoralAuthority
            .getJsonArray(ConfigConstants.ELECTORAL_BOARD_LABEL);

        Runnable serializePublicKey = () -> {

            try {
                serializePublicsKeyAndVerifyThatTheyWereWritten(
                    electionEventId, electoralAuthorityId,
                    createSharesHandlerElGamal);

                updateElectoralAuthorityStatus(electoralAuthorityId,
                    electoralAuthority);

                createSharesHandlerElGamalMap.remove(electionEventId);
            } catch (SharesException e) {
                throw new LambdaException(e);
            }
        };

        String member = electoralAuthorityMembers.getString(shareNumber);
        if (member == null) {
            throw new ResourceNotFoundException(
                "Electoral board member for share number " + shareNumber
                    + " is null.");
        }
        if (member.isEmpty()) {
            throw new ResourceNotFoundException(
                "Electoral board member for share number " + shareNumber
                    + " is empty.");
        }

        LOGGER.info("Writing share for shareNumber " + shareNumber
            + " and member '" + member + "'");
        String label = getHashValueForMember(member);
        if (label.length() > Constants.SMART_CARD_LABEL_MAX_LENGTH) {
            label =
                label.substring(0, Constants.SMART_CARD_LABEL_MAX_LENGTH);
        }

        try {
            createSharesHandlerElGamal.writeShare(shareNumber, label, puk,
                pin, getPrivateKeyToBeUsedForSigning(electionEventId),
                serializePublicKey);
        } catch (LambdaException e) {
        	LOGGER.error("Error trying to write share", e);
            Exception cause = e.getCause();
            if (cause instanceof SharesException) {
                throw (SharesException) cause;
            } else {
				throw new ElectionAuthorityServiceException("This lambda exception should have been properly handled",
						cause);
            }
        }
        String retrievedLabel =
            statelessReadSharesHandler.getSmartcardLabel();
        if (!retrievedLabel.equals(label)) {
            throw new IllegalStateException("Label for share number "
                + shareNumber + " and member '" + member
                + "' was not correctly written. " + "Expected: '" + label
                + "'; Found: '" + retrievedLabel + "'");
        }
        LOGGER.info("Share written.");
    }

    /**
     * Sign electoral authority.
     *
     * @param electionEventId
     *            the election event id
     * @param electoralAuthorityId
     *            the electoral authority id
     * @param privateKey
     *            the private key
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    private void signElectoralAuthority(final String electionEventId,
            final String electoralAuthorityId, final PrivateKey privateKey)
            throws IOException {

        Path electoralAuthorityPath =
            pathResolver.resolve(ConfigConstants.CONFIG_FILES_BASE_DIR,
                electionEventId, ConfigConstants.CONFIG_DIR_NAME_ONLINE,
                ConfigConstants.CONFIG_DIR_NAME_ELECTORAL_AUTHORITY,
                electoralAuthorityId);

        Path electoralAuthorityDataPath =
            pathResolver.resolve(electoralAuthorityPath.toString(),
                ConfigConstants.CONFIG_FILE_NAME_ELECTORAL_AUTHORITY_JSON);
        Path signedElectoralAuthorityDataPath = pathResolver.resolve(
            electoralAuthorityPath.toString(),
            ConfigConstants.CONFIG_FILE_NAME_SIGNED_ELECTORAL_AUTHORITY_JSON);

        com.scytl.products.ov.commons.beans.ElectoralAuthority electoralAuthority =
            configObjectMapper.fromJSONFileToJava(
                new File(electoralAuthorityDataPath.toString()),
                com.scytl.products.ov.commons.beans.ElectoralAuthority.class);

        Path decryptionKeyDataPath =
            pathResolver.resolve(electoralAuthorityPath.toString(),
                ConfigConstants.CONFIG_FILE_NAME_DECRYPTION_KEY_JSON);
        Path signedDecryptionKeyDataPath = pathResolver.resolve(
            electoralAuthorityPath.toString(),
            ConfigConstants.CONFIG_FILE_NAME_SIGNED_DECRYPTION_KEY_JSON);

        com.scytl.products.ov.commons.beans.SharesPublicKey decyptionKey =
            configObjectMapper.fromJSONFileToJava(
                new File(decryptionKeyDataPath.toString()),
                com.scytl.products.ov.commons.beans.SharesPublicKey.class);

        String signedElectoralAuthority =
            signer.sign(privateKey, electoralAuthority);
        SignedObject signedElectoralAuthorityObject = new SignedObject();
        signedElectoralAuthorityObject
            .setSignature(signedElectoralAuthority);
        configObjectMapper.fromJavaToJSONFile(
            signedElectoralAuthorityObject,
            new File(signedElectoralAuthorityDataPath.toString()));

        String signedDecryptionKey =
            signer.sign(privateKey, decyptionKey);
        SignedObject signedDecryptionKeyObject = new SignedObject();
        signedDecryptionKeyObject.setSignature(signedDecryptionKey);
        configObjectMapper.fromJavaToJSONFile(signedDecryptionKeyObject,
            new File(signedDecryptionKeyDataPath.toString()));
    }

    /**
     * Sign election information contents.
     *
     * @param electionEventId
     *            the election event id
     * @param privateKey
     *            the private key
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    private void signElectionInformationContents(
            final String electionEventId, final PrivateKey privateKey)
            throws IOException {

        Path electionInformationPth =
            pathResolver.resolve(ConfigConstants.CONFIG_FILES_BASE_DIR,
                electionEventId, ConfigConstants.CONFIG_DIR_NAME_ONLINE,
                ConfigConstants.CONFIG_DIR_NAME_ELECTIONINFORMATION);

        Path electionInformationsContextPath = pathResolver.resolve(
            electionInformationPth.toString(),
            ConfigConstants.CONFIG_FILE_NAME_ELECTION_INFORMATION_CONTESTS);
        Path signedElectionInformationsContextPath = pathResolver.resolve(
            electionInformationPth.toString(),
            ConfigConstants.CONFIG_FILE_NAME_SIGNED_ELECTION_INFORMATION_CONTENTS);

        if (doesNotExist(signedElectionInformationsContextPath)) {

            ElectionInformationContents electionInformationContents =
                configObjectMapper.fromJSONFileToJava(
                    new File(electionInformationsContextPath.toString()),
                    ElectionInformationContents.class);

            String signedElectionInformationContents =
                signer.sign(privateKey, electionInformationContents);
            SignedObject signedElectionInformationContentsObject =
                new SignedObject();
            signedElectionInformationContentsObject
                .setSignature(signedElectionInformationContents);
            configObjectMapper.fromJavaToJSONFile(
                signedElectionInformationContentsObject, new File(
                    signedElectionInformationsContextPath.toString()));
        }
    }

    /**
     * Sign authentication context data.
     *
     * @param electionEventId
     *            the election event id
     * @param privateKey
     *            the private key
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    private void signAuthenticationContextData(
            final String electionEventId, final PrivateKey privateKey)
            throws IOException {

        Path authenticationPath =
            pathResolver.resolve(ConfigConstants.CONFIG_FILES_BASE_DIR,
                electionEventId, ConfigConstants.CONFIG_DIR_NAME_ONLINE,
                ConfigConstants.CONFIG_DIR_NAME_AUTHENTICATION);

        signAuthenticationContextDataJson(privateKey, authenticationPath);
        signAuthenticationVoterDataJson(privateKey, authenticationPath);

    }

    /**
     * Sign authentication voter data json.
     *
     * @param privateKey
     *            the private key
     * @param authenticationPath
     *            the authentication path
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    private void signAuthenticationVoterDataJson(
            final PrivateKey privateKey, final Path authenticationPath)
            throws IOException {
        Path authenticationVoterDataPath =
            pathResolver.resolve(authenticationPath.toString(),
                ConfigConstants.CONFIG_FILE_NAME_AUTH_VOTER_DATA);
        Path signedAuthenticationVoterDataPath =
            pathResolver.resolve(authenticationPath.toString(),
                ConfigConstants.CONFIG_FILE_NAME_SIGNED_AUTH_VOTER_DATA);

        if (doesNotExist(signedAuthenticationVoterDataPath)) {
            AuthenticationVoterData authenticationVoterData =
                configObjectMapper.fromJSONFileToJava(
                    new File(authenticationVoterDataPath.toString()),
                    AuthenticationVoterData.class);

            String signedAuthVoterData =
                signer.sign(privateKey, authenticationVoterData);
            SignedObject signedAuthVoterDataObject = new SignedObject();
            signedAuthVoterDataObject.setSignature(signedAuthVoterData);
            configObjectMapper.fromJavaToJSONFile(
                signedAuthVoterDataObject,
                new File(signedAuthenticationVoterDataPath.toString()));
        }
    }

    /**
     * Does not exist.
     *
     * @param signedAuthenticationVoterDataPath
     *            the signed authentication voter data path
     * @return true, if successful
     */
    private boolean doesNotExist(
            final Path signedAuthenticationVoterDataPath) {
        return !Files.exists(signedAuthenticationVoterDataPath);
    }

    /**
     * Sign authentication context data json.
     *
     * @param privateKey
     *            the private key
     * @param authenticationPath
     *            the authentication path
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     */
    private void signAuthenticationContextDataJson(
            final PrivateKey privateKey, final Path authenticationPath)
            throws IOException {
        Path authenticationContexPath =
            pathResolver.resolve(authenticationPath.toString(),
                ConfigConstants.CONFIG_FILE_NAME_AUTH_CONTEXT_DATA);
        Path signedAuthenticationContexPath =
            pathResolver.resolve(authenticationPath.toString(),
                ConfigConstants.CONFIG_FILE_NAME_SIGNED_AUTH_CONTEXT_DATA);

        if (doesNotExist(signedAuthenticationContexPath)) {

            AuthenticationContextData authenticationContextData =
                configObjectMapper.fromJSONFileToJava(
                    new File(authenticationContexPath.toString()),
                    AuthenticationContextData.class);

            String signedAuthContextData =
                signer.sign(privateKey, authenticationContextData);
            SignedObject signedAuthContextDataObject = new SignedObject();
            signedAuthContextDataObject
                .setSignature(signedAuthContextData);
            configObjectMapper.fromJavaToJSONFile(
                signedAuthContextDataObject,
                new File(signedAuthenticationContexPath.toString()));
        }
    }

    /**
     * Gets the handler.
     *
     * @param electoralAuthorityId
     *            the electoral authority id
     * @return the handler
     * @throws ResourceNotFoundException
     *             the resource not found exception
     */
    private CreateSharesHandler getHandler(
            final String electoralAuthorityId)
            throws ResourceNotFoundException {

        CreateSharesHandler createSharesHandlerElGamal =
            createSharesHandlerElGamalMap.get(electoralAuthorityId);
        if (createSharesHandlerElGamal == null) {
            throw new ResourceNotFoundException(
                "CreateSharesHandler for this electoralAuthorityId '"
                    + electoralAuthorityId + "' not found");
        }
        return createSharesHandlerElGamal;
    }

    /**
     * Update electoral authority status.
     *
     * @param electoralAuthorityId
     *            the electoral authority id
     * @param electoralAuthority
     *            the electoral authority
     */
    private void updateElectoralAuthorityStatus(
            final String electoralAuthorityId,
            final JsonObject electoralAuthority) {

        String status = electoralAuthority.getString("status");
        if (Status.LOCKED.name().equals(status)) {
            statusService.update(Status.READY.name(),
                electoralAuthorityId, electoralAuthorityRepository);
        }
    }

    /**
     * Serialize the combined public keys and verify that something was written.
     *
     * @param electionEventId
     *            the election event id
     * @param electoralAuthorityId
     *            the electoral authority id
     * @param createSharesHandlerElGamal
     *            the create shares handler el gamal
     * @throws SharesException
     *             the shares exception
     */
    private void serializePublicsKeyAndVerifyThatTheyWereWritten(
            final String electionEventId,
            final String electoralAuthorityId,
            final CreateSharesHandler createSharesHandlerElGamal)
            throws SharesException {

        CreateElectoralBoardKeyPairInput createEbKeyPairInput =
            electoralAuthorityDataGeneratorServiceImpl
                .generate(electoralAuthorityId, electionEventId);
        Path outputFolder =
            pathResolver.resolve(createEbKeyPairInput.getOutputFolder());

        final ElGamalPublicKey decryptionPublicKey =
            getDecryptionElGamalPublicKey(createSharesHandlerElGamal);

        final List<ElGamalPublicKey> mixingPublicKeys =
            getMixingElGamalPublicKeys(electionEventId,
                electoralAuthorityId);
        
        ElGamalPublicKey electoralAuthorityPublicKey =
                combineUsingCompression(decryptionPublicKey, mixingPublicKeys);

        boolean areElectoralAuthorityKeysSerialized =
            createEBKeysSerializer.serializeElectoralAuthorityKeys(
                outputFolder, electoralAuthorityId,
                electoralAuthorityPublicKey, decryptionPublicKey);
        if (!areElectoralAuthorityKeysSerialized) {
            throw new IllegalStateException(
                "The serialization of the Electoral Authority public keys failed. They might not be written to file. Stopping the process.");
        }

    }
    
    public ElGamalPublicKey combineUsingCompression(ElGamalPublicKey decryptionPublicKey, List<ElGamalPublicKey> mixingPublicKeys) {

        ElGamalPublicKey combined;
        try {
            combined =  new ElGamalPublicKeyCombinerWithCompression().combine(decryptionPublicKey, mixingPublicKeys);
        } catch (GeneralCryptoLibException e) {
			throw new ElectionAuthorityServiceException(
					"Exception when trying to combine public keys: " + e.getMessage(), e);
        } 
        
        return combined;
    }

    private List<ElGamalPublicKey> getMixingElGamalPublicKeys(
            String electionEventId, String electoralAuthorityId) {
        List<ElGamalPublicKey> keys = new ArrayList<>();
        try {
            X509Certificate rootCACertificate = platformRootCAService.load();
            for (JsonObject object : getMixingKeyJsons(
                electoralAuthorityId)) {
                ElGamalPublicKey key =
                    ElGamalPublicKey.fromJson(object.getString("publicKey"));
                byte[] signature = Base64.getDecoder()
                    .decode(object.getString("signature"));
                X509Certificate signingCertificate =
                    (X509Certificate) PemUtils.certificateFromPem(
                        object.getString("signerCertificate"));
                X509Certificate nodeCACertificate =
                    (X509Certificate) PemUtils.certificateFromPem(
                        object.getString("nodeCACertificate"));
                X509Certificate[] chain = {signingCertificate,
                        nodeCACertificate, rootCACertificate };
                keySignatureValidator.checkMixingKeySignature(signature,
                    chain, key, electionEventId, electoralAuthorityId);
                keys.add(key);
            }
        } catch (SignatureException | GeneralCryptoLibException | ResourceNotFoundException
                | CertificateManagementException e) {
            throw new IllegalStateException("Failed to get mixing ElGamal public keys", e);
        }
        return keys;
    }

    private List<JsonObject> getMixingKeyJsons(String electoralAuthorityId)
            throws ResourceNotFoundException {
        JsonObject authority =
            getElectoralAuthorityJsonObject(electoralAuthorityId);
        String keys =
            authority.getString(ConfigConstants.MIX_DEC_KEY_LABEL);
        JsonArray array = JsonUtils.getJsonArray(keys);
        List<JsonObject> jsons = new ArrayList<>(array.size());
        for (JsonValue value : array) {
            jsons.add(
                JsonUtils.getJsonObject(((JsonString) value).getString()));
        }
        return jsons;
    }

    /**
     * Creates the encryption parameters generator.
     *
     * @param electionEventId
     *            the election event id
     * @return the el gamal key pair generator
     * @throws ResourceNotFoundException
     *             the resource not found exception
     * @throws GeneralCryptoLibException
     */
    private ElGamalKeyPairGenerator createElGamalKeyPairGenerator(
            final String electionEventId, ElGamalServiceAPI service)
            throws ResourceNotFoundException, GeneralCryptoLibException {

        ElGamalEncryptionParameters encryptionParameters;
        try {
            encryptionParameters = getEncryptionParameters(electionEventId);
        } catch (GeneralCryptoLibException e) {
			throw new ResourceNotFoundException("Failed to obtain encryption parameters", e);
        }
        
        int subkeysCount =
                obtainRequiredNumSubkeysForWriteins(electionEventId) + 1;

        LOGGER.info("Encryption parameters: " + encryptionParameters.toJson()
            + " with subkeycount: " + subkeysCount);

        return new ElGamalKeyPairGenerator(encryptionParameters,
            subkeysCount, service);
    }

    /**
     * Gets the el gamal public key.
     *
     * @param createSharesHandlerRSA
     *            the create shares handler rsa
     * @return the el gamal public key
     * @throws SharesException
     *             the shares exception
     */
    private ElGamalPublicKey getDecryptionElGamalPublicKey(
            final CreateSharesHandler createSharesHandlerRSA)
            throws SharesException {

        PublicKey publicKey = createSharesHandlerRSA.getPublicKey();
        if (!(publicKey instanceof ElGamalPublicKeyAdapter)) {
			throw new ElectionAuthorityServiceException(
					"Error while trying to obtain the decryption ElGamal public key", new IllegalStateException());
        }
        return ((ElGamalPublicKeyAdapter) publicKey).getPublicKey();
    }

    /**
     * Gets the private key to be used for signing.
     *
     * @param eeid
     *            the eeid
     * @return the private key to be used for signing
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     * @throws GeneralCryptoLibException
     *             the general crypto lib exception
     * @throws ResourceNotFoundException
     *             the resource not found exception
     */
    private PrivateKey getPrivateKeyToBeUsedForSigning(final String eeid)
            throws IOException, GeneralCryptoLibException,
            ResourceNotFoundException {

        Path path =
            pathResolver.resolve(ConfigConstants.CONFIG_FILES_BASE_DIR);
        String pwTag = "authoritiesca";
        String alias = "privatekey";

        return getAuthoritiesCAPrivateKey(path, eeid, pwTag, alias);
    }

    /**
     * Gets the authorities ca private key.
     *
     * @param absolutePath
     *            the absolute path
     * @param eeid
     *            the eeid
     * @param passwordTag
     *            the password tag
     * @param alias
     *            the alias
     * @return the authorities ca private key
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     * @throws GeneralCryptoLibException
     *             the general crypto lib exception
     * @throws ResourceNotFoundException
     *             the resource not found exception
     */
    private PrivateKey getAuthoritiesCAPrivateKey(final Path absolutePath,
            final String eeid, final String passwordTag,
            final String alias)
            throws IOException, GeneralCryptoLibException,
            ResourceNotFoundException {

        final Path authoritiesCaKeyStorePath =
            pathResolver.resolve(absolutePath.toString(), eeid,
                Constants.OFFLINE_DIRECTORY, "authoritiesca.sks");
        final CryptoAPIScytlKeyStore ks;
        final InputStream in =
            new FileInputStream(authoritiesCaKeyStorePath.toFile());
        final char[] password = getPassword(
            pathResolver.resolve(absolutePath.toString(), eeid,
                Constants.OFFLINE_DIRECTORY, Constants.PW_TXT),
            passwordTag);
        ks = storesServiceAPI.loadKeyStore(in,
            new KeyStore.PasswordProtection(password));
        return ks.getPrivateKeyEntry(alias, password);
    }

    /**
     * Gets the password.
     *
     * @param path
     *            the path
     * @param name
     *            the name
     * @return the password
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     * @throws ResourceNotFoundException
     *             the resource not found exception
     */
    private char[] getPassword(final Path path, final String name)
            throws IOException, ResourceNotFoundException {

        final List<String> lines = Files.readAllLines(path);
        String password = null;

        for (final String line : lines) {
            final String[] splittedLine = line.split(",");

            if (splittedLine[0].equals(name)) {
                password = splittedLine[1];
            }
        }

        if (password == null) {
            throw new ResourceNotFoundException(
                "The passwords file does not contain a password for "
                    + name);
        }

        return password.toCharArray();
    }

    /**
     * Gets the electoral authority json object.
     *
     * @param electoralAuthorityId
     *            the electoral authority id
     * @return the electoral authority json object
     * @throws ResourceNotFoundException
     *             the resource not found exception
     */
    private JsonObject getElectoralAuthorityJsonObject(
            final String electoralAuthorityId)
            throws ResourceNotFoundException {

        String electoralAuthorityJSON =
            electoralAuthorityRepository.find(electoralAuthorityId);

        if (StringUtils.isEmpty(electoralAuthorityJSON)
            || JsonConstants.JSON_EMPTY_OBJECT
                .equals(electoralAuthorityJSON)) {
            throw new ResourceNotFoundException(
                "Electoral Authority not found");
        }

        return JsonUtils.getJsonObject(electoralAuthorityJSON);
    }

    /**
     * Gets the encryption parameters.
     *
     * @param electionEventId
     *            the election event id
     * @return the encryption parameters
     * @throws ResourceNotFoundException
     *             the resource not found exception
     * @throws GeneralCryptoLibException
     *             the general crypto lib exception
     */
    private ElGamalEncryptionParameters getEncryptionParameters(
            final String electionEventId)
            throws ResourceNotFoundException, GeneralCryptoLibException {

        String electoralAuthorityJSON =
            electionEventRepository.find(electionEventId);

        if (StringUtils.isEmpty(electoralAuthorityJSON)
            || JsonConstants.JSON_EMPTY_OBJECT
                .equals(electoralAuthorityJSON)) {
            throw new ResourceNotFoundException(
                "Electoral Authority not found");
        }
        JsonObject electoralAuthorityObject =
            JsonUtils.getJsonObject(electoralAuthorityJSON);
        JsonObject encryptionParams =
            electoralAuthorityObject.getJsonObject("settings")
                .getJsonObject("encryptionParameters");

        BigInteger p = new BigInteger(encryptionParams.getString("p"));
        BigInteger q = new BigInteger(encryptionParams.getString("q"));
        BigInteger g = new BigInteger(encryptionParams.getString("g"));
        return new ElGamalEncryptionParameters(p, q, g);
    }

    private int obtainRequiredNumSubkeysForWriteins(
            String electionEventId) {

        String ballotJson =
            ballotRepository.listByElectionEvent(electionEventId);
        JsonArray ballotsArray = JsonUtils.getJsonObject(ballotJson)
            .getJsonArray(JsonConstants.JSON_ATTRIBUTE_NAME_RESULT);

        int largestMaxValueInAllContestsInAllBallots = 0;

        for (int i = 0; i < ballotsArray.size(); i++) {

            JsonObject ballot = ballotsArray.getJsonObject(i);
            JsonArray contestsArray = ballot.getJsonArray("contests");

            int largestMaxValueInContests = 0;

            for (int j = 0; j < contestsArray.size(); j++) {

                JsonObject contest = contestsArray.getJsonObject(j);
                largestMaxValueInContests +=
                    getSumOfAllMaxValuesInThisContest(contest);
            }

            largestMaxValueInAllContestsInAllBallots =
                Math.max(largestMaxValueInAllContestsInAllBallots,
                    largestMaxValueInContests);
        }
        return largestMaxValueInAllContestsInAllBallots;
    }

    private int getSumOfAllMaxValuesInThisContest(JsonObject contest) {

        JsonArray questionsArray = contest.getJsonArray("questions");
        Integer runningTotal = Integer.valueOf(0);

        for (int k = 0; k < questionsArray.size(); k++) {
            JsonObject question = questionsArray.getJsonObject(k);
            if (Boolean.valueOf(question.getString("writeIn"))) {
                Integer localMax =
                    Integer.valueOf(question.getString("max"));
                runningTotal += localMax;
            }
        }

        return runningTotal;
    }

    /**
     * Activate.
     *
     * @param electionEventId
     *            the election event id
     * @param electoralAuthorityId
     *            the electoral authority id
     * @return the activate output data
     * @throws GeneralCryptoLibException
     *             the general crypto lib exception
     */
    public ActivateOutputData activate(final String electionEventId,
            final String electoralAuthorityId)
            throws GeneralCryptoLibException {

        LOGGER.info("Loading the authorities CA public key...");
        PublicKey publicKey = loadAuthoritiesCAPublicKey(electionEventId);
        LOGGER.info("Authorities CA public key successfully loaded");
        String issuerPublicKeyPEM = PemUtils.publicKeyToPem(publicKey);

        LOGGER.info("Loading the decryption " + electoralAuthorityId
            + " public key...");
        ElGamalPublicKey elGamalPublicKey =
            loadDecryptionPublicKey(electionEventId, electoralAuthorityId);
        String elGamalPublicKeyB64 = new String(
            Base64.getEncoder()
                .encode(elGamalPublicKey.toJson()
                    .getBytes(StandardCharsets.UTF_8)),
            StandardCharsets.UTF_8);
        LOGGER.info("Authorities CA public key successfully loaded");

        ActivateOutputData output = new ActivateOutputData();
        output.setIssuerPublicKeyPEM(issuerPublicKeyPEM);
        output.setSerializedSubjectPublicKey(elGamalPublicKeyB64);
        return output;
    }

    /**
     * Read share.
     *
     * @param electionEventId
     *            the election event id
     * @param electoralAuthorityId
     *            the electoral authority id
     * @param shareNumber
     *            the share number
     * @param pin
     *            the pin
     * @param issuerPublicKeyPEM
     *            the issuer public key pem
     * @return the string
     * @throws ResourceNotFoundException
     *             the resource not found exception
     * @throws SharesException
     *             the shares exception
     * @throws IllegalArgumentException
     *             the illegal argument exception
     */
    public String readShare(final String electionEventId,
            final String electoralAuthorityId, final Integer shareNumber,
            final String pin, final String issuerPublicKeyPEM)
            throws ResourceNotFoundException, SharesException,
            IllegalArgumentException {

        String member =
            getElectoralAuthorityMember(electoralAuthorityId, shareNumber);

        LOGGER.info("Reading share of election event " + electionEventId
            + " for electoral authority " + electoralAuthorityId
            + " and member " + member + "...");

        LOGGER.info("Checking that the smartcard corresponds to member "
            + member + "...");
        String label = getHashValueForMember(member);
        if (label.length() > Constants.SMART_CARD_LABEL_MAX_LENGTH) {
            label =
                label.substring(0, Constants.SMART_CARD_LABEL_MAX_LENGTH);
        }
        if (!statelessReadSharesHandler.getSmartcardLabel()
            .equals(label)) {
            throw new ElectionAuthorityServiceException(
                "The smartcard introduced does not correspond to the selected member: "
                    + member);
        }

        PublicKey authoritiesCAPublicKey;
        try {
            authoritiesCAPublicKey =
                PemUtils.publicKeyFromPem(issuerPublicKeyPEM);
        } catch (GeneralCryptoLibException e) {
            throw new IllegalArgumentException(
                "Public key is not in valid PEM format", e);
        }

        LOGGER.info("Reading share from smartcard...");
        String shareSerialized = statelessReadSharesHandler
            .readShareAndStringifyElGamal(pin, authoritiesCAPublicKey);
        LOGGER.info("Share successfully read");

        return shareSerialized;
    }

    /**
     * Gets the electoral authority member.
     *
     * @param electoralAuthorityId
     *            the electoral authority id
     * @param shareNumber
     *            the share number
     * @return the electoral authority member
     * @throws ResourceNotFoundException
     *             the resource not found exception
     */
    private String getElectoralAuthorityMember(
            final String electoralAuthorityId, final Integer shareNumber)
            throws ResourceNotFoundException {
        JsonObject electoralAuthority =
            getElectoralAuthorityJsonObject(electoralAuthorityId);
        JsonArray electoralAuthorityMembers = electoralAuthority
            .getJsonArray(ConfigConstants.ELECTORAL_BOARD_LABEL);
        return electoralAuthorityMembers.getString(shareNumber);
    }

    /**
     * Reconstruct.
     * @param electoralAuthorityId
     *            the electoral authority id
     * @param serializedShares
     *            the serialized shares
     * @param serializedPublicKey
     *            the serialized public key
     *
     * @return the string
     * @throws SharesException
     *             the shares exception
     * @throws GeneralCryptoLibException
     *             the general crypto lib exception
     */
    public String reconstruct(final String electoralAuthorityId,
            final List<String> serializedShares,
            final String serializedPublicKey)
            throws SharesException, GeneralCryptoLibException {

        ElGamalPublicKey elGamalPublicKey = ElGamalPublicKey.fromJson(
            new String(Base64.getDecoder().decode(serializedPublicKey),
                StandardCharsets.UTF_8));

        LOGGER.info("Reconstructing private key of electoral authority "
            + electoralAuthorityId + "...");
        ElGamalPrivateKey elGamalPrivateKey = statelessReadSharesHandler
            .getPrivateKeyWithSerializedSharesElGamal(
                new HashSet<>(serializedShares), elGamalPublicKey);

        String elGamalPrivateKeyB64 = new String(
            Base64.getEncoder()
                .encode(elGamalPrivateKey.toJson()
                    .getBytes(StandardCharsets.UTF_8)),
            StandardCharsets.UTF_8);
        LOGGER.info("Private key successfully reconstructed");

        return elGamalPrivateKeyB64;
    }

    /**
     * Load decryption public key.
     *
     * @param eeid
     *            the eeid
     * @param electoralAuthorityId
     *            the electoral authority id
     * @return the el gamal public key
     */
    private ElGamalPublicKey loadDecryptionPublicKey(final String eeid,
            final String electoralAuthorityId) {

        Path electoralAuthorityPubKeyPath =
            pathResolver.resolve(ConfigConstants.CONFIG_FILES_BASE_DIR,
                eeid, Constants.ONLINE_DIRECTORY,
                Constants.ELECTORAL_AUTHORITIES_DIRECTORY,
                electoralAuthorityId, Constants.DECRYPTION_KEY_FILE_NAME);

        try {
            SharesPublicKey sharesPublicKey = configObjectMapper
                .fromJSONFileToJava(electoralAuthorityPubKeyPath.toFile(),
                    SharesPublicKey.class);

            String serializedPublicKey = sharesPublicKey.getPublicKey();

			return ElGamalPublicKey
					.fromJson(new String(Base64.getDecoder().decode(serializedPublicKey), StandardCharsets.UTF_8));
			
        } catch (IOException | GeneralCryptoLibException e) {
			throw new ElectionAuthorityServiceException(
					"An error occurred while loading the decryption public key " + electoralAuthorityId, e);
        }
    }

    /**
     * Load authorities ca public key.
     *
     * @param eeid
     *            the eeid
     * @return the public key
     */
    private PublicKey loadAuthoritiesCAPublicKey(final String eeid) {

        Path authoritiesCAPath = pathResolver.resolve(
            ConfigConstants.CONFIG_FILES_BASE_DIR, eeid,
            Constants.OFFLINE_DIRECTORY, "authoritiesca" + Constants.PEM);

        try {
            X509Certificate authoritiesCA =
                (X509Certificate) com.scytl.cryptolib.primitives.primes.utils.PemUtils
                    .certificateFromPem(
                        new String(Files.readAllBytes(authoritiesCAPath),
                            StandardCharsets.UTF_8));
            return authoritiesCA.getPublicKey();
        } catch (GeneralCryptoLibException | IOException e) {
			throw new ElectionAuthorityServiceException("An error occurred while loading the authorities CA public key",
					e);
        }

    }

    /**
     * Gets the hash value for member.
     *
     * @param member
     *            the member
     * @return the hash value for member
     */
    private String getHashValueForMember(final String member) {

        String base64Value = null;

        try {
            MessageDigest mdEnc = MessageDigest
                .getInstance(Constants.MESSAGE_DIGEST_ALGORITHM);
            byte[] memberByteArray =
                member.getBytes(StandardCharsets.UTF_8);
            mdEnc.update(memberByteArray, 0, memberByteArray.length);

            base64Value =
                Base64.getEncoder().encodeToString(mdEnc.digest());
        } catch (NoSuchAlgorithmException e) {
			throw new ElectionAuthorityServiceException("Cryptolib exception when getting hash for member: " + member,
					e);
        }

        LOGGER.info(
            "Base64 value is " + base64Value + " for member " + member);
        return base64Value;
    }
}
