/*
 * 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.stores.service;

import static junitparams.JUnitParamsRunner.$;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

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.api.stores.bean.KeyStoreType;
import com.scytl.cryptolib.asymmetric.utils.AsymmetricTestDataGenerator;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.cryptolib.certificates.utils.X509CertificateTestDataGenerator;
import com.scytl.cryptolib.primitives.primes.utils.PrimitivesTestDataGenerator;
import com.scytl.cryptolib.primitives.securerandom.constants.SecureRandomConstants;
import com.scytl.cryptolib.test.tools.utils.CommonTestDataGenerator;

import junitparams.JUnitParamsRunner;
import junitparams.Parameters;

/**
 * Tests of the stores service API input validation.
 */
@RunWith(JUnitParamsRunner.class)
public class StoresServiceValidationTest {

    private static final String TEST_DATA_PATH =
        "target" + File.separator + "cryptolib-stores-test-data";

    private static final String PKCS12_KEY_STORE_PATH = TEST_DATA_PATH
        + File.separator + "CryptoLibStoresModuleTestPkcs12.p12";

    private static StoresService _storesServiceForDefaultPolicy;

    private static char[] _keyStorePassword;

    private static String _whiteSpaceString;

    static {
        try {
            setUp();
        } catch (GeneralCryptoLibException | KeyStoreException
                | NoSuchAlgorithmException | CertificateException
                | IOException e) {
            throw new CryptoLibException("Setup failed for class "
                + StoresServiceValidationTest.class.getSimpleName());
        }
    }

    public static void setUp()
            throws GeneralCryptoLibException, KeyStoreException,
            NoSuchAlgorithmException, CertificateException, IOException {

        Security.addProvider(new BouncyCastleProvider());

        _storesServiceForDefaultPolicy = new StoresService();

        int numChars = CommonTestDataGenerator.getInt(1,
            SecureRandomConstants.MAXIMUM_GENERATED_STRING_LENGTH);

        char[] privateKeyPassword =
            PrimitivesTestDataGenerator.getCharArray64(numChars);
        String privateKeyAlias =
            PrimitivesTestDataGenerator.getString32(numChars);

        _keyStorePassword =
            PrimitivesTestDataGenerator.getCharArray64(numChars);

        setUpKeyStore(privateKeyPassword, privateKeyAlias,
            _keyStorePassword);

        _whiteSpaceString =
            CommonTestDataGenerator.getWhiteSpaceString(numChars);
    }

    @AfterClass
    public static void tearDown() {

        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

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

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

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

    public static Object[] createStoresService() {

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

    @Test
    @Parameters(method = "createKeyStore")
    public void testKeyStoreCreationValidation(KeyStoreType type,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _storesServiceForDefaultPolicy.createKeyStore(type);
    }

    public static Object[] createKeyStore() {

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

    @Test
    @Parameters(method = "loadKeyStore")
    public void testKeyStoreLoadingValidation(KeyStoreType type,
            InputStream inStream, char[] password, String errorMsg)
            throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        _storesServiceForDefaultPolicy.loadKeyStore(type, inStream,
            password);
    }

    public static Object[] loadKeyStore()
            throws GeneralCryptoLibException, KeyStoreException,
            NoSuchAlgorithmException, CertificateException, IOException {

        FileInputStream keyStoreInStream =
            new FileInputStream(new File(PKCS12_KEY_STORE_PATH));

        return $(
            $(null, keyStoreInStream, _keyStorePassword,
                "Key store type is null."),
            $(KeyStoreType.PKCS12, null, _keyStorePassword,
                "Key store input stream is null."),
            $(KeyStoreType.PKCS12, keyStoreInStream, null,
                "Key store password is null."),
            $(KeyStoreType.PKCS12, keyStoreInStream, "".toCharArray(),
                "Key store password is blank."),
            $(KeyStoreType.PKCS12, keyStoreInStream,
                _whiteSpaceString.toCharArray(),
                "Key store password is blank."),
            $(KeyStoreType.PKCS12, keyStoreInStream,
                "wrong_password".toCharArray(),
                "Could not load key store."),
            $(KeyStoreType.JCEKS, keyStoreInStream, _keyStorePassword,
                "Could not load key store."));
    }

    private static void setUpKeyStore(final char[] privateKeyPassword,
            final String privateKeyAlias, final char[] keyStorePassword)
            throws GeneralCryptoLibException, KeyStoreException,
            NoSuchAlgorithmException, CertificateException, IOException {

        File outputDataDir = new File(TEST_DATA_PATH);
        if (!outputDataDir.exists()) {
            outputDataDir.mkdirs();
        }

        _storesServiceForDefaultPolicy = new StoresService();

        KeyStore pkcs12KeyStore = _storesServiceForDefaultPolicy
            .createKeyStore(KeyStoreType.PKCS12);

        KeyPair keyPair =
            AsymmetricTestDataGenerator.getKeyPairForSigning();
        PrivateKey privateKey = keyPair.getPrivate();

        CryptoAPIX509Certificate certificate =
            X509CertificateTestDataGenerator
                .getRootAuthorityX509Certificate(keyPair);

        X509Certificate[] certificateChain = new X509Certificate[1];
        certificateChain[0] = certificate.getCertificate();
        pkcs12KeyStore.setKeyEntry(privateKeyAlias, privateKey,
            privateKeyPassword, certificateChain);

        FileOutputStream outStream =
            new FileOutputStream(new File(PKCS12_KEY_STORE_PATH));
        pkcs12KeyStore.store(outStream, keyStorePassword);
    }
}
