/*
 * 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.cryptolib.scytl.keystore.service;

import static junitparams.JUnitParamsRunner.$;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyStore.PasswordProtection;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;

import javax.crypto.SecretKey;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.AfterClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.elgamal.bean.ElGamalPrivateKey;
import com.scytl.cryptolib.primitives.securerandom.constants.SecureRandomConstants;
import com.scytl.cryptolib.scytl.keystore.constants.ScytlKeyStoreConstants;
import com.scytl.cryptolib.scytl.keystore.utils.SksTestDataGenerator;
import com.scytl.cryptolib.test.tools.bean.TestCertificate;
import com.scytl.cryptolib.test.tools.bean.TestPrivateKey;
import com.scytl.cryptolib.test.tools.bean.TestPublicKey;
import com.scytl.cryptolib.test.tools.bean.TestSecretKey;
import com.scytl.cryptolib.test.tools.utils.CommonTestDataGenerator;

import junitparams.JUnitParamsRunner;
import junitparams.Parameters;

/**
 * Tests of the Scytl key store service API input validation.
 */
@RunWith(JUnitParamsRunner.class)
public class ScytlKeyStoreServiceValidationTest {

    private static String _alias;

    private static char[] _password;

    private static PasswordProtection _passwordProtection;

    private static PrivateKey _privateKey;

    private static Certificate _certificate;

    private static Certificate[] _certificateChain;

    private static SecretKey _secretKey;

    private static ElGamalPrivateKey _elGamalPrivateKey;

    private static byte[] _emptyByteArray;

    private static char[] _emptyCharArray;

    private static String _whiteSpaceAlias;

    private static String _aliasWithIllegalCharacter;

    private static String _belowMinLengthAlias;

    private static String _aboveMaxLengthAlias;

    private static char[] _whiteSpacePassword;

    private static char[] _belowMinLengthPassword;

    private static char[] _aboveMaxLengthPassword;

    private static PasswordProtection _nullPasswordProtection;

    private static PasswordProtection _emptyPasswordProtection;

    private static PasswordProtection _whiteSpacePasswordProtection;

    static {
        try {
            setUp();
        } catch (GeneralCryptoLibException | GeneralSecurityException
                | IOException e) {
            throw new CryptoLibException(
                "Setup failed for class ScytlKeyStoreServiceValidationTest");
        }
    }

    public static void setUp()
            throws GeneralCryptoLibException, GeneralSecurityException,
            IOException {

        Security.addProvider(new BouncyCastleProvider());

        _alias = SksTestDataGenerator.getAlias();
        _password = SksTestDataGenerator.getPassword();
        _passwordProtection = new PasswordProtection(_password);

        _privateKey = SksTestDataGenerator.getPrivateKey();
        _certificate = SksTestDataGenerator.getX509Certificate();
        _certificateChain = new Certificate[1];
        _certificateChain[0] = _certificate;
        _secretKey = SksTestDataGenerator.getSecretKey();
        _elGamalPrivateKey = SksTestDataGenerator.getElGamalPrivateKey();

        _emptyByteArray = new byte[0];
        _emptyCharArray = new char[0];

        _whiteSpaceAlias = SksTestDataGenerator.getWhiteSpaceAlias();
        _aliasWithIllegalCharacter =
            SksTestDataGenerator.getAliasWithIllegalCharacter();
        _belowMinLengthAlias =
            SksTestDataGenerator.getBelowMinLengthAlias();
        _aboveMaxLengthAlias =
            SksTestDataGenerator.getAboveMaxLengthAlias();

        _whiteSpacePassword = SksTestDataGenerator.getWhiteSpacePassword();
        _belowMinLengthPassword =
            SksTestDataGenerator.getBelowMinLengthPassword();
        _aboveMaxLengthPassword =
            SksTestDataGenerator.getAboveMaxLengthPassword();

        _nullPasswordProtection = new PasswordProtection(null);
        _emptyPasswordProtection = new PasswordProtection(_emptyCharArray);
        _whiteSpacePasswordProtection =
            new PasswordProtection(_whiteSpacePassword);
    }

    @AfterClass
    public static void tearDown() {

        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    @Parameters(method = "createScytlKeyStoreService")
    public void testScytlKeyStoreServiceCreationValidation(String path,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ScytlKeyStoreService(path);
    }

    public static Object[] createScytlKeyStoreService()
            throws GeneralCryptoLibException {

        String whiteSpacePath =
            CommonTestDataGenerator
                .getWhiteSpaceString(CommonTestDataGenerator.getInt(1,
                    SecureRandomConstants.MAXIMUM_GENERATED_STRING_LENGTH));

        return $($(null, "Properties file path is null."),
            $("", "Properties file path is blank."),
            $(whiteSpacePath, "Properties file path is blank."));
    }

    @Test
    @Parameters(method = "loadScytlKeyStore")
    public void testScytlKeyStoreLoadValidation(InputStream in,
            PasswordProtection password, String errorMsg)
            throws GeneralCryptoLibException, IOException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ScytlKeyStoreService().loadKeyStore(in, password);
    }

    public static Object[] loadScytlKeyStore() {

        InputStream keyStoreInputStream = new ByteArrayInputStream(
            "test".getBytes(StandardCharsets.UTF_8));

        return $(
            $(null, _passwordProtection, "Key store input stream is null."),
            $(keyStoreInputStream, null,
                "Key store password protection is null."),
            $(keyStoreInputStream, _nullPasswordProtection,
                "Key store password is null."),
            $(keyStoreInputStream, _emptyPasswordProtection,
                "Key store password is blank."),
            $(keyStoreInputStream, _whiteSpacePasswordProtection,
                "Key store password is blank."));
    }

    @Test
    @Parameters(method = "loadScytlKeyStoreFromJSON")
    public void testScytlKeyStoreLoadFromJSONValidation(InputStream in,
            PasswordProtection password, String errorMsg)
            throws GeneralCryptoLibException, IOException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ScytlKeyStoreService().loadKeyStoreFromJSON(in, password);
    }

    public static Object[] loadScytlKeyStoreFromJSON() {

        InputStream keyStoreInStream =
            SksTestDataGenerator.getKeyStoreInputStream();

        return $(
            $(null, _passwordProtection,
                "Key store JSON input stream is null."),
            $(keyStoreInStream, null,
                "Key store password protection is null."),
            $(keyStoreInStream, _nullPasswordProtection,
                "Key store password is null."),
            $(keyStoreInStream, _emptyPasswordProtection,
                "Key store password is blank."),
            $(keyStoreInStream, _whiteSpacePasswordProtection,
                "Key store password is blank."));
    }

    @Test
    @Parameters(method = "formatScytlKeyStoreToJSON")
    public void testFormatScytlKeyStoreToJSONValidation(InputStream in,
            String errorMsg) throws GeneralCryptoLibException, IOException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ScytlKeyStoreService().formatKeyStoreToJSON(in);
    }

    public static Object[] formatScytlKeyStoreToJSON() {

        return $($(null, "Key store input stream is null."));
    }

    @Test
    @Parameters(method = "getPrivateKeyFromScytlKeyStore")
    public void testGetPrivateKeyFromScytlKeyStoreValidation(String alias,
            char[] password, String errorMsg)
            throws GeneralCryptoLibException, IOException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ScytlKeyStoreService().createKeyStore().getPrivateKeyEntry(
            alias, password);
    }

    public static Object[] getPrivateKeyFromScytlKeyStore() {

        return $(
            $(null, _password, "Private key alias is null."),
            $("", _password, "Private key alias is blank."),
            $(_whiteSpaceAlias, _password, "Private key alias is blank."),
            $(_alias, null, "Private key password is null."),
            $(_alias, _emptyCharArray, "Private key password is blank."),
            $(_alias, _whiteSpacePassword,
                "Private key password is blank."));
    }

    @Test
    @Parameters(method = "getCertificateChainFromScytlKeyStore")
    public void testGetCertificateChainFromScytlKeyStoreValidation(
            String alias, String errorMsg)
            throws GeneralCryptoLibException, IOException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ScytlKeyStoreService().createKeyStore().getCertificateChain(
            alias);
    }

    public static Object[] getCertificateChainFromScytlKeyStore() {

        return $($(null, "Certificate chain alias is null."),
            $("", "Certificate chain alias is blank."),
            $(_whiteSpaceAlias, "Certificate chain alias is blank."));
    }

    @Test
    @Parameters(method = "getSecretKeyFromScytlKeyStore")
    public void testGetSecretKeyFromScytlKeyStoreValidation(String alias,
            char[] password, String errorMsg)
            throws GeneralCryptoLibException, IOException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ScytlKeyStoreService().createKeyStore().getSecretKeyEntry(
            alias, password);
    }

    public static Object[] getSecretKeyFromScytlKeyStore() {

        return $(
            $(null, _password, "Secret key alias is null."),
            $("", _password, "Secret key alias is blank."),
            $(_whiteSpaceAlias, _password, "Secret key alias is blank."),
            $(_alias, null, "Secret key password is null."),
            $(_alias, _emptyCharArray, "Secret key password is blank."),
            $(_alias, _whiteSpacePassword, "Secret key password is blank."));
    }

    @Test
    @Parameters(method = "getElGamalPrivateKeyFromScytlKeyStore")
    public void testGetElGamalPrivateKeyFromScytlKeyStoreValidation(
            String alias, char[] password, String errorMsg)
            throws GeneralCryptoLibException, IOException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ScytlKeyStoreService().createKeyStore()
            .getElGamalPrivateKeyEntry(alias, password);
    }

    public static Object[] getElGamalPrivateKeyFromScytlKeyStore() {

        return $(
            $(null, _password, "ElGamal private key alias is null."),
            $("", _password, "ElGamal private key alias is blank."),
            $(_whiteSpaceAlias, _password,
                "ElGamal private key alias is blank."),
            $(_alias, null, "ElGamal private key password is null."),
            $(_alias, _emptyCharArray,
                "ElGamal private key password is blank."),
            $(_alias, _whiteSpacePassword,
                "ElGamal private key password is blank."));
    }

    @Test
    @Parameters(method = "setPrivateKeyEntryInScytlKeyStore")
    public void testSetPrivateKeyEntryInScytlKeyStoreValidation(
            String alias, PrivateKey privateKey, char[] password,
            Certificate[] chain, String errorMsg)
            throws GeneralCryptoLibException, IOException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ScytlKeyStoreService().createKeyStore().setPrivateKeyEntry(
            alias, privateKey, password, chain);
    }

    public static Object[] setPrivateKeyEntryInScytlKeyStore() {

        Certificate[] chainContainingNullCert = new Certificate[2];
        chainContainingNullCert[0] = _certificate;
        chainContainingNullCert[1] = null;

        Certificate[] chainContainingNullContentCert =
            chainContainingNullCert.clone();
        chainContainingNullContentCert[1] =
            new TestCertificate("test", new TestPublicKey(null));

        Certificate[] chainContainingEmptyContentCert =
            chainContainingNullCert.clone();
        chainContainingEmptyContentCert[1] =
            new TestCertificate("test", new TestPublicKey(_emptyByteArray));

        return $(
            $(null, _privateKey, _password, _certificateChain,
                "Private key alias is null."),
            $("", _privateKey, _password, _certificateChain,
                "Private key alias is blank."),
            $(_whiteSpaceAlias, _privateKey, _password, _certificateChain,
                "Private key alias is blank."),
            $(_belowMinLengthAlias, _privateKey, _password,
                _certificateChain, "Private key alias is blank."),
            $(_aboveMaxLengthAlias,
                _privateKey,
                _password,
                _certificateChain,
                "Private key alias length must be less than or equal to : "
                    + ScytlKeyStoreConstants.MAXIMUM_SKS_ENTRY_ALIAS_LENGTH
                    + "; Found " + _aboveMaxLengthAlias.length()),
            $(_aliasWithIllegalCharacter,
                _privateKey,
                _password,
                _certificateChain,
                "Private key alias contains characters outside of allowed set "
                    + ScytlKeyStoreConstants.ALLOWED_SKS_ENTRY_ALIAS_CHARACTERS),
            $(_alias, null, _password, _certificateChain,
                "Private key is null."),
            $(_alias, new TestPrivateKey(null), _password,
                _certificateChain, "Private key content is null."),
            $(_alias, new TestPrivateKey(_emptyByteArray), _password,
                _certificateChain, "Private key content is empty."),
            $(_alias, _privateKey, null, _certificateChain,
                "Private key password is null."),
            $(_alias, _privateKey, _emptyCharArray, _certificateChain,
                "Private key password is blank."),
            $(_alias, _privateKey, _whiteSpacePassword, _certificateChain,
                "Private key password is blank."),
            $(_alias, _privateKey, _belowMinLengthPassword,
                _certificateChain,
                "Private key password length must be greater than or equal to : "
                    + ScytlKeyStoreConstants.MINIMUM_SKS_PASSWORD_LENGTH
                    + "; Found " + _belowMinLengthPassword.length),
            $(_alias, _privateKey, _aboveMaxLengthPassword,
                _certificateChain,
                "Private key password length must be less than or equal to : "
                    + ScytlKeyStoreConstants.MAXIMUM_SKS_PASSWORD_LENGTH
                    + "; Found " + _aboveMaxLengthPassword.length),
            $(_alias, _privateKey, _password, null,
                "Certificate chain is null."),
            $(_alias, _privateKey, _password, new Certificate[0],
                "Certificate chain is empty."),
            $(_alias, _privateKey, _password, chainContainingNullCert,
                "Certificate in chain is null."),
            $(_alias, _privateKey, _password,
                chainContainingNullContentCert,
                "Content of certificate in chain is null."),
            $(_alias, _privateKey, _password,
                chainContainingEmptyContentCert,
                "Content of certificate in chain is empty."));
    }

    @Test
    @Parameters(method = "setSecretKeyEntryInScytlKeyStore")
    public void testSetSecretKeyEntryInScytlKeyStoreValidation(
            String alias, SecretKey secretKey, char[] password,
            String errorMsg) throws GeneralCryptoLibException, IOException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ScytlKeyStoreService().createKeyStore().setSecretKeyEntry(
            alias, secretKey, password);
    }

    public static Object[] setSecretKeyEntryInScytlKeyStore() {

        return $(
            $(null, _secretKey, _password, "Secret key alias is null."),
            $("", _secretKey, _password, "Secret key alias is blank."),
            $(_whiteSpaceAlias, _secretKey, _password,
                "Secret key alias is blank."),
            $(_belowMinLengthAlias, _secretKey, _password,
                "Secret key alias is blank."),
            $(_aboveMaxLengthAlias,
                _secretKey,
                _password,
                "Secret key alias length must be less than or equal to : "
                    + ScytlKeyStoreConstants.MAXIMUM_SKS_ENTRY_ALIAS_LENGTH
                    + "; Found " + _aboveMaxLengthAlias.length()),
            $(_aliasWithIllegalCharacter,
                _secretKey,
                _password,
                "Secret key alias contains characters outside of allowed set "
                    + ScytlKeyStoreConstants.ALLOWED_SKS_ENTRY_ALIAS_CHARACTERS),
            $(_alias, null, _password, "Secret key is null."),
            $(_alias, new TestSecretKey(null), _password,
                "Secret key content is null."),
            $(_alias, new TestSecretKey(_emptyByteArray), _password,
                "Secret key content is empty."),
            $(_alias, _secretKey, null, "Secret key password is null."),
            $(_alias, _secretKey, _emptyCharArray,
                "Secret key password is blank."),
            $(_alias, _secretKey, _whiteSpacePassword,
                "Secret key password is blank."),
            $(_alias, _secretKey, _belowMinLengthPassword,
                "Secret key password length must be greater than or equal to : "
                    + ScytlKeyStoreConstants.MINIMUM_SKS_PASSWORD_LENGTH
                    + "; Found " + _belowMinLengthPassword.length),
            $(_alias, _secretKey, _aboveMaxLengthPassword,
                "Secret key password length must be less than or equal to : "
                    + ScytlKeyStoreConstants.MAXIMUM_SKS_PASSWORD_LENGTH
                    + "; Found " + _aboveMaxLengthPassword.length));
    }

    @Test
    @Parameters(method = "setElGamalPrivateKeyEntryInScytlKeyStore")
    public void testSetElGamalPrivateKeyEntryInScytlKeyStoreValidation(
            String alias, ElGamalPrivateKey privateKey, char[] password,
            String errorMsg) throws GeneralCryptoLibException, IOException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ScytlKeyStoreService().createKeyStore()
            .setElGamalPrivateKeyEntry(alias, privateKey, password);
    }

    public static Object[] setElGamalPrivateKeyEntryInScytlKeyStore() {

        return $(
            $(null, _elGamalPrivateKey, _password,
                "ElGamal private key alias is null."),
            $("", _elGamalPrivateKey, _password,
                "ElGamal private key alias is blank."),
            $(_whiteSpaceAlias, _elGamalPrivateKey, _password,
                "ElGamal private key alias is blank."),
            $(_belowMinLengthAlias, _elGamalPrivateKey, _password,
                "ElGamal private key alias is blank."),
            $(_aboveMaxLengthAlias,
                _elGamalPrivateKey,
                _password,
                "ElGamal private key alias length must be less than or equal to : "
                    + ScytlKeyStoreConstants.MAXIMUM_SKS_ENTRY_ALIAS_LENGTH
                    + "; Found " + _aboveMaxLengthAlias.length()),
            $(_aliasWithIllegalCharacter,
                _elGamalPrivateKey,
                _password,
                "ElGamal private key alias contains characters outside of allowed set "
                    + ScytlKeyStoreConstants.ALLOWED_SKS_ENTRY_ALIAS_CHARACTERS),
            $(_alias, null, _password, "ElGamal private key is null."),
            $(_alias, _elGamalPrivateKey, null,
                "ElGamal private key password is null."),
            $(_alias, _elGamalPrivateKey, _emptyCharArray,
                "ElGamal private key password is blank."),
            $(_alias, _elGamalPrivateKey, _whiteSpacePassword,
                "ElGamal private key password is blank."),
            $(_alias, _elGamalPrivateKey, _belowMinLengthPassword,
                "ElGamal private key password length must be greater than or equal to : "
                    + ScytlKeyStoreConstants.MINIMUM_SKS_PASSWORD_LENGTH
                    + "; Found " + _belowMinLengthPassword.length),
            $(_alias, _elGamalPrivateKey, _aboveMaxLengthPassword,
                "ElGamal private key password length must be less than or equal to : "
                    + ScytlKeyStoreConstants.MAXIMUM_SKS_PASSWORD_LENGTH
                    + "; Found " + _aboveMaxLengthPassword.length));
    }

    @Test
    @Parameters(method = "storeScytlKeyStore")
    public void testScytlKeyStoreStorageValidation(OutputStream out,
            char[] password, String errorMsg)
            throws GeneralCryptoLibException, IOException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        new ScytlKeyStoreService().createKeyStore().store(out, password);
    }

    public static Object[] storeScytlKeyStore() {

        OutputStream keyStoreOutStream = new ByteArrayOutputStream();

        return $(
            $(null, _password, "Key store output stream is null."),
            $(keyStoreOutStream, null, "Key store password is null."),
            $(keyStoreOutStream, _emptyCharArray,
                "Key store password is blank."),
            $(keyStoreOutStream, _whiteSpacePassword,
                "Key store password is blank."),
            $(keyStoreOutStream, _belowMinLengthPassword,
                "Key store password length must be greater than or equal to : "
                    + ScytlKeyStoreConstants.MINIMUM_SKS_PASSWORD_LENGTH
                    + "; Found " + _belowMinLengthPassword.length),
            $(keyStoreOutStream, _aboveMaxLengthPassword,
                "Key store password length must be less than or equal to : "
                    + ScytlKeyStoreConstants.MAXIMUM_SKS_PASSWORD_LENGTH
                    + "; Found " + _aboveMaxLengthPassword.length));
    }
}
