/*
 * 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.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
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.security.auth.x500.X500Principal;

import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
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.asymmetric.service.AsymmetricService;
import com.scytl.cryptolib.certificates.bean.X509DistinguishedName;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.cryptolib.certificates.factory.CryptoX509Certificate;
import com.scytl.cryptolib.elgamal.service.ElGamalService;
import com.scytl.cryptolib.scytl.keystore.cryptoapi.CryptoAPIScytlKeyStore;
import com.scytl.products.ov.commons.beans.exceptions.ApplicationException;
import com.scytl.products.ov.commons.beans.exceptions.LambdaException;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.path.PathResolver;
import com.scytl.products.ov.commons.util.JsonUtils;
import com.scytl.products.ov.config.exceptions.ConfigurationEngineException;
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.rsa.RSAKeyPairGenerator;
import com.scytl.products.ov.config.shares.keys.rsa.RSAPrivateKeySerializer;
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.csr.CSRGenerator;
import com.scytl.products.ov.csr.CSRSigningInputProperties;
import com.scytl.products.ov.csr.CertificateRequestSigner;
import com.scytl.products.ov.datapacks.beans.CertificateParameters;
import com.scytl.products.ov.sdm.application.config.SmartCardConfig;
import com.scytl.products.ov.sdm.domain.model.administrationauthority.ActivateOutputData;
import com.scytl.products.ov.sdm.domain.model.administrationauthority.AdministrationAuthorityRepository;
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.infrastructure.JsonConstants;
import com.scytl.products.ov.sdm.infrastructure.service.ConfigurationEntityStatusService;
import com.scytl.products.ov.utils.CertificatePersister;
import com.scytl.products.ov.utils.KeyStoreLoader;
import com.scytl.products.ov.utils.PemUtils;

/**
 * Service for handling admin boards.
 */
@Service
public class AdminBoardService {

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

    /** The Constant PRIVATE_KEY_ALIAS. */
    private static final String PRIVATE_KEY_ALIAS = "privatekey";

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

    /** The _asymmetric service api. */
    private AsymmetricServiceAPI asymmetricServiceAPI;

    private ElGamalServiceAPI elGamalServiceAPI;

    /** The _rsa key pair generator. */
    private RSAKeyPairGenerator rsaKeyPairGenerator;

    /** The _private key shares service rsa. */
    private final PrivateKeySharesService privateKeySharesServiceRSA =
        new PrivateKeySharesService(new RSAPrivateKeySerializer());

    /** The _create shares operation context rsa. */
    private final CreateSharesOperationContext createSharesOperationContextRSA =
        new CreateSharesOperationContext(SharesType.ADMIN_BOARD);

    /** The _csr generator. */
    private final CSRGenerator csrGenerator = new CSRGenerator();

    /** The administration authority repository. */
    @Autowired
    private AdministrationAuthorityRepository administrationAuthorityRepository;

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

    /** The path resolver. */
    @Autowired
    private PathResolver pathResolver;

    @Autowired
    private RootCAService platformRootCAService;

    @Autowired
    private RootCAService tenantCAService;

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

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

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

    /** The _tenant keys and certificates. */
    private PrivateKeyEntry tenantKeys;

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

    @Autowired
    SmartCardConfig smartCardConfig;

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

        asymmetricServiceAPI = new AsymmetricService();

        elGamalServiceAPI = new ElGamalService();

        rsaKeyPairGenerator = new RSAKeyPairGenerator(asymmetricServiceAPI);

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

        PrivateKeySerializer rsaPrivateKeySerializer = new RSAPrivateKeySerializer();

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

    }

    /**
     * Gets the admin boards.
     *
     * @return the admin boards
     */
    public String getAdminBoards() {
        return administrationAuthorityRepository.list();
    }

    /**
     * Constitute.
     *
     * @param adminBoardId
     *            the admin board id
     * @param in
     *            the keystore data
     * @param password
     *            the kesytore password
     * @throws ResourceNotFoundException
     *             the resource not found exception
     * @throws SharesException
     *             the shares exception
     */
    public void constitute(final String adminBoardId, final InputStream in, char[] password)
            throws ResourceNotFoundException, SharesException {

        LOGGER.info("Constituting adminBoard...");

        tenantKeys = extractTenantKeys(in, password);

        LOGGER.info("Extracted contents from tenant keystore.");

        CreateSharesHandler createSharesHandlerRSA = new CreateSharesHandler(createSharesOperationContextRSA,
            rsaKeyPairGenerator, privateKeySharesServiceRSA, smartcardService);

        createSharesHandlerRSAMap.put(adminBoardId, createSharesHandlerRSA);

        JsonObject administrationAuthority = getAdminBoardJsonObject(adminBoardId);
        Integer minimumThreshold =
            Integer.parseInt(administrationAuthority.getString(ConfigConstants.MINIMUM_THRESHOLD));
        JsonArray administrationBoardMembers =
            administrationAuthority.getJsonArray(ConfigConstants.ADMINISTRATION_BOARD_LABEL);
        int numberOfMembers = administrationBoardMembers.size();

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

        createSharesHandlerRSA.generateAndSplit(numberOfMembers, minimumThreshold);

        LOGGER.info("AdminBoard constituted.");
    }

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

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

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

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

        return status;
    }

    /**
     * Write share.
     *
     * @param adminBoardId
     *            the admin board id
     * @param shareNumber
     *            the share number
     * @param pin
     *            the pin
     * @throws ResourceNotFoundException
     *             the resource not found exception
     * @throws SharesException
     *             the shares exception
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     * @throws ApplicationException
     *             the application exception
     */
    public void writeShare(final String adminBoardId, final Integer shareNumber, final String pin)
            throws ResourceNotFoundException, SharesException, IOException, ApplicationException {

        CreateSharesHandler createSharesHandlerRSA = createSharesHandlerRSAMap.get(adminBoardId);
        if (createSharesHandlerRSA == null) {
            throw new ResourceNotFoundException(
                "CreateSharesHandler for this adminBoardID '" + adminBoardId + "' not found");
        }

        JsonObject administrationAuthority = getAdminBoardJsonObject(adminBoardId);

        Runnable createAdminBoardCertificate = () -> {
            try {

                createAdminBoardCertificate(adminBoardId, createSharesHandlerRSA, administrationAuthority);
                createSharesHandlerRSAMap.remove(adminBoardId);
            } catch (IOException | SharesException | ApplicationException | CertificateManagementException e) {
                throw new LambdaException(e);
            }
        };

        String member = getMemberFromRepository(administrationAuthority, shareNumber);
        LOGGER.info("Writing share for share number " + 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 {
            createSharesHandlerRSA.writeShareAndSelfSign(shareNumber, label, puk, pin, createAdminBoardCertificate);
        } catch (LambdaException e) {
            Exception cause = e.getCause();
            if (cause instanceof IOException) {
                throw (IOException) cause;
            } else if (cause instanceof SharesException) {
                throw (SharesException) cause;
            } else if (cause instanceof ApplicationException) {
                throw (ApplicationException) cause;
            } else {
                throw new AdminBoardServiceException("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.");
    }

    /**
     * Creates the admin board certificate.
     *
     * @param adminBoardId
     *            the admin board id
     * @param createSharesHandlerRSA
     *            the create shares handler rsa
     * @param administrationAuthority
     *            the administration authority
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     * @throws SharesException
     *             the shares exception
     * @throws ApplicationException
     *             the application exception
     * @throws CertificateManagementException
     */
    public void createAdminBoardCertificate(final String adminBoardId, final CreateSharesHandler createSharesHandlerRSA,
            final JsonObject administrationAuthority)
            throws IOException, SharesException, ApplicationException, CertificateManagementException {
        String status = administrationAuthority.getString("status");
        if (Status.LOCKED.name().equals(status)) {
            serializeDataToFile(adminBoardId, createSharesHandlerRSA);
            statusService.updateWithSynchronizedStatus(Status.CONSTITUTED.name(), adminBoardId,
                administrationAuthorityRepository, SynchronizeStatus.PENDING);
        } else {
            throw new ApplicationException("Invalid board status");
        }
    }

    /**
     * Extracts the tenant keys from a given keystore.
     *
     * @param in
     *            the keystore input stream
     * @param password
     *            the keystore password
     * @return the keys.
     */
    private PrivateKeyEntry extractTenantKeys(final InputStream in, char[] password) {
        CryptoAPIScytlKeyStore store = KeyStoreLoader.loadKeyStoreFromStream(in, password);
        PrivateKey key;
        Certificate[] chain;
        try {
            key = store.getPrivateKeyEntry(PRIVATE_KEY_ALIAS, password);
            chain = store.getCertificateChain(PRIVATE_KEY_ALIAS);
        } catch (GeneralCryptoLibException e) {
            throw new IllegalStateException("Failed to extract the tenant keys.", e);
        }
        return new PrivateKeyEntry(key, chain);
    }

    /**
     * Gets an administration board's certificate, and its corresponding tenant
     * certificate.
     * 
     * @param adminBoardId
     *            the identifier of the administration board
     * @return the admin board certificate chain, wit the admin board
     *         certificate as the first certificate
     * @throws CertificateManagementException
     */
    public X509Certificate[] getCertificateChain(String adminBoardId) throws CertificateManagementException {
        X509Certificate adminBoardCertificate = getAdminBoardCertificate(adminBoardId);
        X509Certificate tenantCACertificate = tenantCAService.load();

        return new X509Certificate[] {adminBoardCertificate, tenantCACertificate };
    }

    /**
     * Serialize data to file.
     *
     * @param adminBoardId
     *            the admin board id
     * @param createSharesHandlerRSA
     *            the create shares handler rsa
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     * @throws SharesException
     *             the shares exception
     * @throws CertificateManagementException
     */
    private void serializeDataToFile(final String adminBoardId, final CreateSharesHandler createSharesHandlerRSA)
            throws IOException, SharesException, CertificateManagementException {

        LOGGER.info("Serializing data to file");

        X500Principal subjectX500Principal = getSubjectX500Principal(adminBoardId);

        PKCS10CertificationRequest csr = createSharesHandlerRSA.generateCSR(subjectX500Principal, csrGenerator);

        LOGGER.info("Created CSR");

        Path outputFilePath = pathResolver.resolve(ConfigConstants.CONFIG_FILES_BASE_DIR, ConfigConstants.CSR_FOLDER);

        LOGGER.info("Path: " + outputFilePath.toAbsolutePath().toString());

        Files.createDirectories(outputFilePath);

        saveCsrToFile(adminBoardId, csr, outputFilePath);
        LOGGER.info("Saved CSR");

        saveSignedCertificateToFile(adminBoardId, csr, outputFilePath);
        LOGGER.info("Saved signed certificate");

        Certificate[] certificateChain = tenantKeys.getCertificateChain();
        if (certificateChain.length < 2) {
            throw new IllegalStateException("Platform CA certificate may be missing");
        }

        saveTenantCACertificate(certificateChain);
        LOGGER.info("Saved tenant CA certificate");

        savePlatformRootCACertificate(certificateChain);
        LOGGER.info("Saved PlatformRoot CA certificate");
    }

    private void saveTenantCACertificate(Certificate[] certificateChain) throws CertificateManagementException {
        if (certificateChain.length < 1) {
            throw new IllegalArgumentException("No certificates in this chain");
        }
        X509Certificate tenantCACertificate = (X509Certificate) certificateChain[0];
        tenantCAService.save(tenantCACertificate);
    }

    private void savePlatformRootCACertificate(Certificate[] certificateChain) throws CertificateManagementException {
        if (certificateChain.length < 2) {
            throw new IllegalArgumentException("The platform root CA certificate may be missing");
        }
        X509Certificate platformCACertificate = (X509Certificate) certificateChain[1];
        platformRootCAService.save(platformCACertificate);
    }

    /**
     * Gets the subject x500 principal.
     *
     * @param adminBoardId
     *            the admin board id
     * @return the subject x500 principal
     */
    private X500Principal getSubjectX500Principal(final String adminBoardId) {

        X509DistinguishedName x509DistinguishedName =
            getX509DistinguishedName((X509Certificate) tenantKeys.getCertificate());

        String subjectCommonName = "AdministrationBoard " + adminBoardId;
        String subjectOrganizationalUnit = x509DistinguishedName.getOrganizationalUnit();
        String subjectOrganization = x509DistinguishedName.getOrganization();
        String subjectCountry = x509DistinguishedName.getCountry();

		return new X500Principal("C=" + subjectCountry + ", O=" + subjectOrganization + ", OU="
				+ subjectOrganizationalUnit + ", CN=" + subjectCommonName);
    }

    /**
     * Gets the x509 distinguished name.
     *
     * @param x509Certificate
     *            the x509 certificate
     * @return the x509 distinguished name
     */
    private X509DistinguishedName getX509DistinguishedName(final X509Certificate x509Certificate) {

		CryptoAPIX509Certificate cryptoAPIX509Certificate = getCryptoAPIX509CertificateFromX509Certificate(
				x509Certificate);

        return cryptoAPIX509Certificate.getSubjectDn();
    }

    /**
     * Save csr to file.
     *
     * @param adminBoardId
     *            the admin board id
     * @param csr
     *            the csr
     * @param outputFilePath
     *            the output file path
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     * @throws SharesException
     *             the shares exception
     */
	private void saveCsrToFile(final String adminBoardId, final PKCS10CertificationRequest csr,
			final Path outputFilePath) throws IOException, SharesException {

        String data = PemUtils.csrToPem(csr);

        if (!outputFilePath.toFile().exists()) {
            throw new AdminBoardServiceException("Error while creating the CSR folder structure");
        }
        Path csrPath = pathResolver.resolve(outputFilePath.toString(), adminBoardId + "_CSR.pem");
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(csrPath.toFile()))) {
            writer.write(data);
            writer.close();
        }
    }

    /**
     * Save signed certificate to file.
     *
     * @param adminBoardId
     *            the admin board id
     * @param csr
     *            the csr
     * @param outputFilePath
     *            the output file path
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     * @throws SharesException
     *             the shares exception
     */
	private void saveSignedCertificateToFile(final String adminBoardId, final PKCS10CertificationRequest csr,
			final Path outputFilePath) throws IOException, SharesException {

        X509Certificate x509CertificateFromKeystore = (X509Certificate) tenantKeys.getCertificate();
        CryptoAPIX509Certificate cryptoAPIX509Certificate =
            getCryptoAPIX509CertificateFromX509Certificate(x509CertificateFromKeystore);

        CSRSigningInputProperties csrSingingInputProperties =
            constructCsrSigningInputProperties(cryptoAPIX509Certificate);

        JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest = new JcaPKCS10CertificationRequest(csr);

        CertificateRequestSigner certificateRequestSigner = new CertificateRequestSigner();
        CryptoAPIX509Certificate signedCertificate = certificateRequestSigner.signCSR(x509CertificateFromKeystore,
            tenantKeys.getPrivateKey(), jcaPKCS10CertificationRequest, csrSingingInputProperties);

        LOGGER.info("Created signed certificate");

        Path signedCertificatePath = pathResolver.resolve(outputFilePath.toString(), adminBoardId + ".pem");

        CertificatePersister.saveCertificate(signedCertificate, signedCertificatePath);

        LOGGER.info("Stored signed certificate to file");
    }

    /**
     * Construct csr signing input properties.
     *
     * @param cryptoAPIX509Certificate
     *            the crypto api x509 certificate
     * @return the CSR signing input properties
     */
    private CSRSigningInputProperties constructCsrSigningInputProperties(
            final CryptoAPIX509Certificate cryptoAPIX509Certificate) {

        ZonedDateTime notBefore = ZonedDateTime.now(ZoneOffset.UTC);
        ZonedDateTime notAfter =
            cryptoAPIX509Certificate.getNotAfter().toInstant().atZone(ZoneOffset.UTC).minusSeconds(1);
        return  new CSRSigningInputProperties(notBefore, notAfter, CertificateParameters.Type.SIGN);
    }

    /**
     * Gets the crypto api x509 certificate from x509 certificate.
     *
     * @param x509Certificate
     *            the x509 certificate
     * @return the crypto api x509 certificate from x509 certificate
     */
    private CryptoAPIX509Certificate getCryptoAPIX509CertificateFromX509Certificate(
            final X509Certificate x509Certificate) {

        CryptoAPIX509Certificate cryptoAPIX509Certificate = null;
        try {
            cryptoAPIX509Certificate = new CryptoX509Certificate(x509Certificate);
        } catch (GeneralCryptoLibException e) {
            throw new ConfigurationEngineException(
                "Exceptional while trying to creating CryptoAPIX509Certificate from X509Certificate", e);
        }
        return cryptoAPIX509Certificate;
    }

    /**
     * Gets the admin board json object.
     *
     * @param adminBoardId
     *            the admin board id
     * @return the admin board json object
     * @throws ResourceNotFoundException
     *             the resource not found exception
     */
    private JsonObject getAdminBoardJsonObject(final String adminBoardId) throws ResourceNotFoundException {

        // get number of members, threshold
        String administrationAuthorityJSON = administrationAuthorityRepository.find(adminBoardId);

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

        return JsonUtils.getJsonObject(administrationAuthorityJSON);
    }

    /**
     * Activate.
     *
     * @param adminBoardId
     *            the admin board id
     * @return the activate output data
     * @throws GeneralCryptoLibException
     *             the general crypto lib exception
     */
    public ActivateOutputData activate(final String adminBoardId) throws GeneralCryptoLibException {
        LOGGER.info("Loading the admin board " + adminBoardId + " public key...");
        PublicKey publicKey = loadAdminBoardPublicKey(adminBoardId);
        LOGGER.info("Admin board " + adminBoardId + " public key successfully loaded");
        String publicKeyPEM = com.scytl.cryptolib.primitives.primes.utils.PemUtils.publicKeyToPem(publicKey);
        ActivateOutputData output = new ActivateOutputData();
        output.setIssuerPublicKeyPEM(publicKeyPEM);
        output.setSerializedSubjectPublicKey(publicKeyPEM);
        return output;
    }

    /**
     * Read share.
     *
     * @param adminBoardId
     *            the admin board id
     * @param shareNumber
     *            the share number
     * @param pin
     *            the pin
     * @param publicKeyPEM
     *            the 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 adminBoardId, final Integer shareNumber, final String pin,
            final String publicKeyPEM)
            throws ResourceNotFoundException, SharesException, IllegalArgumentException {

        String member = getMemberFromRepository(adminBoardId, shareNumber);
        LOGGER.info("Reading share of admin board " + adminBoardId + " 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 AdminBoardServiceException(
                "The smartcard introduced does not correspond to the selected member: " + member);
        }

        PublicKey adminBoardPublicKey;
        try {
            adminBoardPublicKey = com.scytl.cryptolib.primitives.primes.utils.PemUtils.publicKeyFromPem(publicKeyPEM);
        } catch (GeneralCryptoLibException e) {
            throw new IllegalArgumentException("Public key is not in valid PEM format", e);
        }

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

        return shareSerialized;
    }

    /**
     * Reconstruct.
     *
     * @param adminBoardId
     *            the admin board id
     * @param serializedShares
     *            the serialized shares
     * @param publicKeyPEM
     *            the public key pem
     * @return the string
     * @throws SharesException
     *             the shares exception
     * @throws GeneralCryptoLibException
     *             the general crypto lib exception
     */
    public String reconstruct(final String adminBoardId, final List<String> serializedShares, final String publicKeyPEM)
            throws SharesException, GeneralCryptoLibException {

        PublicKey adminBoardPublicKey =
            com.scytl.cryptolib.primitives.primes.utils.PemUtils.publicKeyFromPem(publicKeyPEM);

        LOGGER.info("Reconstructing private key of admin board " + adminBoardId + "...");
        PrivateKey adminBoardPrivateKey = statelessReadSharesHandler
            .getPrivateKeyWithSerializedShares(new HashSet<>(serializedShares), adminBoardPublicKey);

        LOGGER.info("Private key successfully reconstructed");

        return com.scytl.cryptolib.primitives.primes.utils.PemUtils.privateKeyToPem(adminBoardPrivateKey);
    }

    /**
     * Gets the member from repository.
     *
     * @param adminBoardId
     *            the admin board id
     * @param shareNumber
     *            the share number
     * @return the member from repository
     * @throws ResourceNotFoundException
     *             the resource not found exception
     */
    private String getMemberFromRepository(final String adminBoardId, final Integer shareNumber)
            throws ResourceNotFoundException {
        JsonObject administrationAuthority = getAdminBoardJsonObject(adminBoardId);
        return getMemberFromRepository(administrationAuthority, shareNumber);
    }

    /**
     * Gets the member from repository.
     *
     * @param administrationAuthority
     *            the administration authority
     * @param shareNumber
     *            the share number
     * @return the member from repository
     * @throws ResourceNotFoundException
     *             the resource not found exception
     */
    private String getMemberFromRepository(final JsonObject administrationAuthority, final Integer shareNumber)
            throws ResourceNotFoundException {
        JsonArray administrationBoardMembers = administrationAuthority.getJsonArray("administrationBoard");

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

        return member;
    }

    /**
     * Load admin board public key.
     *
     * @param adminBoardId
     *            the admin board id
     * @return the public key
     */
    private PublicKey loadAdminBoardPublicKey(final String adminBoardId) {
        return getAdminBoardCertificate(adminBoardId).getPublicKey();
    }

    private X509Certificate getAdminBoardCertificate(String adminBoardId) {
        Path adminBoardCertPath = pathResolver.resolve(ConfigConstants.CONFIG_FILES_BASE_DIR,
            ConfigConstants.CSR_FOLDER, adminBoardId + Constants.PEM);

        try {
            X509Certificate adminBoardCert = (X509Certificate) com.scytl.cryptolib.primitives.primes.utils.PemUtils
                .certificateFromPem(new String(Files.readAllBytes(adminBoardCertPath), StandardCharsets.UTF_8));

            CryptoAPIX509Certificate cryptoCert = new CryptoX509Certificate(adminBoardCert);
            if (!cryptoCert.getSubjectDn().getCommonName().contains(adminBoardId)) {
                throw new AdminBoardServiceException(
                    "The loaded administration board certificate does not correspond to the board with id: "
                        + adminBoardId);
            }

            return cryptoCert.getCertificate();
        } catch (GeneralCryptoLibException | IOException e) {
            throw new AdminBoardServiceException("An error occurred while loading the administration board 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 AdminBoardServiceException("Cryptolib exception when getting hash for member: " + member, e);
        }

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