/*
 * 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.commons.beans.keystore;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore.PasswordProtection;
import java.security.PrivateKey;
import java.security.Security;
import java.util.List;

import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;

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.scytl.keystore.cryptoapi.CryptoAPIScytlKeyStore;
import com.scytl.cryptolib.scytl.keystore.service.ScytlKeyStoreService;
import com.scytl.products.ov.commons.beans.utils.KeyStoreLoader;
import com.scytl.products.ov.commons.beans.utils.PasswordEncrypter;

/**
 * This IT tests that keystores and encrypted keystores passwords that are generated during configuration generation
 * phase can be recovered using the appropriate private key, that is held by appropriate services.
 * <P>
 * During the generation of the configuration, several keystores are generated. These keystores are used to protect
 * private keys. To open a keystore a password is required. The passwords for four of these keystores are encrypted
 * using public keys (at the moment that the keystores are created, and the password are never stored in cleartext). The
 * services where those keystores are sent will possess the corresponding private key (need to decrypt the password).
 * Therefore, when the services need to access the private key (contained in one of the keystores) then they must first
 * decrypt the password of the keystore, and then extract the private key. This IT performs these steps.
 */
public class ElectionKeystoresITest {

    private static AsymmetricService _asymmetricService;

    private static ScytlKeyStoreServiceAPI _scytlKeyStoreService;

    private static PasswordEncrypter _passwordEncrypter;

    private static String ALIAS = "encryptionkey";

    ///////////////////////////////////////////////////////////
    //
    // Ballot box signer
    //
    ///////////////////////////////////////////////////////////

    String ballotBoxSignerKeystore =
        "[encoded data, 9072 characters]";

    String ballotBoxSignerKeystorePasswordEncrypted =
        "XuL3LKUWsaCPqUgMhtNkuEEDqxxL1gHbQgadBW5G1OyDMyIE5rINEn4Xz45XnStMDRNrPe0L41PjUZ1SWLbavuD41ogYsKMSxMvPWiPIzj+oZQQ2pjlmm+Vb/xeUrIuKVi4PG1eCMKff46JFZMEMqWOlr2WOpMccHjhCOMGXQIEGcTUkPnlJfOflJHPMEoLuscDyNgJmG0zNS6sKkKDrLM7JAKMtIGXjLBN5lsD3FFRpQTPsvDl5Q4SU7Fb810y1WNKVTLP+dooCzvC7FIf87ryGQUMEXxaYVsX0uLI38n4KgT8RknFY9XRyz6eMR2PWrZHKLwt88ZldkAy+v+Qws3FwoIsUtiQv8ODm9VB0oiILiY3kVMUxmPZCLwsQB72Pe3WLWPDL733At+pyeBMGt3GQspbZzg==";

    String systemTenantElectionInformationKeystore = "/keystore/100_EI.sks";

    String systemTenantElectionInformationKeystorePassword = "VJTFGCIEYGS2MR4IOAXPB2PMZ7";

    ///////////////////////////////////////////////////////////
    //
    // Authentication token signer
    //
    ///////////////////////////////////////////////////////////

    String authenticationTokenSignerKeystore =
        "[encoded data, 9088 characters]";

    String authenticationTokenSignerKeystorePasswordEncrypted =
        "EsXpdgRtOGkxYAGBUU/GTFbQ6HoW1ySXKphO8zSe3O/Nw03ImQpFdcvVbEgVEyu/AwzwamaOuUNc87y6u7L8OBZ1k6u8+c9OUoQVczYprsHc9kvhdR1ASpDPofsD2taRdR04rQsgthSvja7efBDXrxHfVsocLoZKtXJe5Mmqylv7dUUPeEQOG3ENXAOKV8SFl2WQ00pyrjextjesmBmuscfvtaW6SXtrPGu0zv9vKqhJSOEN0ORFdqwWOXbvyIJtStvexFUnY8ZM2p/u7RnsKG2DgGAkHuWSltYyH/q8rC5rC6M/hOb2LUKshlnosf7Mp+koYKw4qFmB1DXdmw/9y9Utx+9sEy2enz5ALWKyP8U4JKO7PvimIiqTUgEXlqVwDNh9t6Wrv3kOXk6KIIhwgw64EIK5IQ==";

    String systemTenantAuthenticationKeystore = "/keystore/100_AU.sks";

    String systemTenantAuthenticationKeystorePassword = "RBW6WD4IPTA73VT4KASLILIIFJ";

    ///////////////////////////////////////////////////////////
    //
    // Codes Secret Key
    //
    ///////////////////////////////////////////////////////////

    String codesSecretKeyKeyStore = "eyJzYWx0IjoidDY2VXFkR1Q4djI3MEtNMXF1WWJSVjRCRlI0ZE05eS9tWFQ4M0N4VEtHMD0iLCJzZWNyZXRzIjp7ImNvZGVzc2siOiI1RUJNZ3BwbmxlOFZaZ1NWZ2tZeGVzWWdMMFhGckc2K0toVHV4VDNEbWRWc2pyVGtEdDlHWlRuVnJmVVg1NjJYdkczWCJ9LCJzdG9yZSI6Ik1GVUNBUU13RVFZSktvWklodmNOQVFjQm9BUUVBakFBTUQwd0lUQUpCZ1VyRGdNQ0dnVUFCQlMwWmgrckc1a2UrSEpzalgyMDFoRXMrRUtEQ2dRVWttRTN2eGNYYS9ZdnUvVXFLWVEyMVNkS0YvRUNBZ1FBIn0=";

    String codesSecretKeyKeystorePasswordEncrypted =
        "Ttuz2HQmeCTe2spYgtv/6K6lJ4lbQbIODEi/zV5PAFSqyFPd+MKD2HEDb7poOM4267S0e/r84VQsE+qdmAdJU5ZGW50hkp4n/4OehsUFi3ro4zNqlBBWLHkkQV7nIxwvKoHQL8zxYN1AeEsjVdZZ84RrhweDXCsGG7kNLV+a8DcKDgHJXGrRyVVTyix4+DiUjGpCbjZHesh/HLAozp+cfxaiE8tk8O4mMj0bK0+HUNcW84RHCcrEoM5hZaZ66ctNLBLo+MADdAluWxQVqfFajY3vHMy/gB3jq90eWtQ/HrhCsIHVGp3kt9GRx7DgQgDmbD9sYukQ8cFzp47eZHTSc+y2J/RJTYgMkqS2ArVqE7QhO1+3+WvGS+hIYkBmDWeBOWMBPHd3m60w/I1ngYbxQZl+cTM+6w==";

    String systemTenantVoteVerificationKeystore = "/keystore/100_VV.sks";

    String systemTenantVoteVerificationKeystorePassword = "6GXVCXTSB5KBBC6M2DTAOSU372";

    ///////////////////////////////////////////////////////////
    //
    // Choices Codes Encryption
    //
    ///////////////////////////////////////////////////////////

    // This keystore uses SHA-1-based PBKDF. Its secret keys cannot
    // be read because of apparent tampering, so it cannot be converted to
    // the newer SHA-256-based format.
    String choicesCodesEncryptionKeyStore =
        "[encoded data, 29292 characters]";

    String choicesCodesEncryptionKeystorePasswordEncrypted =
        "HWOqeyy+Z/Z/SnbEXxDyUmEQyBTbTQiOlotIIpUzIEGohWCdhv63gQvQErdOAQhDU5A8wG6TniVRXjlwoUdMwAciBbo8751E89XkeAAIe3eVpZ/ZGx5+lhA5WFxn+It+uxdBcRzvDWjLg8+dp9u1oQNSSNgttjMF5DUGlMHnG7KkudraW4m/rsFL/crj0tuvA59xd8aFE3iyQI+LOdMHQZQfEmqnM932Bi5PZuf2j7INjci8ix0rujRZSNUPEB5Rb+lFN8KREMeEBekxHUTXpUqLrR2I6Ul89oro+0e7zrIo+ZNeAYnXdbeDtYssIH3VVTUqcWS/br6rLK5VFTMBuDeTVfn/GxEiOe6KcBcuEI+uo9xnJLuKjZ95EPsE0AZKdyqH3Su0EgkYY/cdu5zjQxO5TT6TRw==";

    @BeforeClass
    public static void setUp() throws GeneralCryptoLibException {
        Security.addProvider(new BouncyCastleProvider());
        _asymmetricService = new AsymmetricService();

        _scytlKeyStoreService = new ScytlKeyStoreService();

        _passwordEncrypter = new PasswordEncrypter(_asymmetricService);
    }

    @Test
    public void ballotBoxSignerIT() throws IOException, GeneralCryptoLibException {

        CryptoAPIScytlKeyStore store = loadElectionEventKeystore(systemTenantElectionInformationKeystore,
            systemTenantElectionInformationKeystorePassword, ballotBoxSignerKeystorePasswordEncrypted,
            ballotBoxSignerKeystore);

        List<String> aliases = store.getPrivateKeyAliases();
        assertTrue(aliases.size() == 1);
        assertEquals("privatekey", aliases.get(0));
    }

    @Test
    public void authTokenSignerIT() throws IOException, GeneralCryptoLibException {

        CryptoAPIScytlKeyStore store =
            loadElectionEventKeystore(systemTenantAuthenticationKeystore, systemTenantAuthenticationKeystorePassword,
                authenticationTokenSignerKeystorePasswordEncrypted, authenticationTokenSignerKeystore);

        List<String> aliases = store.getPrivateKeyAliases();
        assertTrue(aliases.size() == 1);
        assertEquals("privatekey", aliases.get(0));
    }

    @Test
    public void codesSecretKeyIT() throws IOException, GeneralCryptoLibException {

        CryptoAPIScytlKeyStore store = loadElectionEventKeystore(systemTenantVoteVerificationKeystore,
            systemTenantVoteVerificationKeystorePassword, codesSecretKeyKeystorePasswordEncrypted,
                codesSecretKeyKeyStore);

        List<String> aliases = store.getSecretKeyAliases();
        assertTrue(aliases.size() == 1);
        assertEquals("codessk", aliases.get(0));
    }

    @Test
    @Ignore
    public void choicesCodesEncryptionIT() throws IOException, GeneralCryptoLibException {

        CryptoAPIScytlKeyStore store = loadElectionEventKeystore(systemTenantVoteVerificationKeystore,
            systemTenantVoteVerificationKeystorePassword, choicesCodesEncryptionKeystorePasswordEncrypted,
                choicesCodesEncryptionKeyStore);

        List<String> aliases = store.getSecretKeyAliases();
        assertTrue(aliases.size() == 50);
    }

    private CryptoAPIScytlKeyStore loadElectionEventKeystore(final String pathSystemTanantKeystore,
            final String systemTanantKeystorePassword, final String keystorePasswordEncrypted, final String keystore)
            throws IOException, GeneralCryptoLibException {

        CryptoAPIScytlKeyStore tenantKeystore = KeyStoreLoader.loadKeyStoreFromStream(
            readKeystoreAsStream(pathSystemTanantKeystore), systemTanantKeystorePassword.toCharArray());

        char[] passwordAsCharArray = systemTanantKeystorePassword.toCharArray();
        PrivateKey systemTenantPrivatekey = tenantKeystore.getPrivateKeyEntry(ALIAS, passwordAsCharArray);

        String keystorePasswordPlaintext =
            _passwordEncrypter.decryptPassword(keystorePasswordEncrypted, systemTenantPrivatekey);

        byte[] keystoreAsBytes = Base64.decodeBase64(keystore.getBytes(StandardCharsets.UTF_8));

        InputStream inputStream = new ByteArrayInputStream(keystoreAsBytes);
        passwordAsCharArray = keystorePasswordPlaintext.toCharArray();
        PasswordProtection passwordProtection = new PasswordProtection(passwordAsCharArray);
        CryptoAPIScytlKeyStore loadedKeystore =
            _scytlKeyStoreService.loadKeyStoreFromJSON(inputStream, passwordProtection);

        return loadedKeystore;
    }

    private InputStream readKeystoreAsStream(final String path) throws IOException {

        return getClass().getResourceAsStream(path);
    }
}
