/*
 * 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 org.junit.Assert.assertTrue;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

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.commons.configuration.Provider;
import com.scytl.cryptolib.primitives.primes.utils.PrimitivesTestDataGenerator;
import com.scytl.cryptolib.primitives.securerandom.constants.SecureRandomConstants;
import com.scytl.cryptolib.test.tools.utils.CommonTestDataGenerator;

/**
 * Tests of the stores service API.
 */
public class StoresServiceTest {

    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 PrivateKey _rootPrivateKey;

    private static PublicKey _rootPublicKey;

    private static PrivateKey _privateKey;

    private static CryptoAPIX509Certificate _rootCertificate;

    private static CryptoAPIX509Certificate _certificate;

    private static char[] _rootPrivateKeyPassword;

    private static char[] _privateKeyPassword;

    private static String _rootPrivateKeyAlias;

    private static String _privateKeyAlias;

    private static char[] _keyStorePassword;

    private static KeyStore _pkcs12KeyStore;

    private static KeyStore _pkcs12KeyStoreLoaded;

    @BeforeClass
    public static void setUp()
            throws GeneralCryptoLibException, KeyStoreException,
            NoSuchFieldException, SecurityException,
            IllegalArgumentException, IllegalAccessException,
            NoSuchAlgorithmException, CertificateException, IOException {

        Security.addProvider(new BouncyCastleProvider());

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

        _storesServiceForDefaultPolicy = new StoresService();
        Field field = StoresService.class.getDeclaredField("_provider");
        field.setAccessible(true);
        field.set(_storesServiceForDefaultPolicy, Provider.SUN);

        _pkcs12KeyStore = _storesServiceForDefaultPolicy
            .createKeyStore(KeyStoreType.PKCS12);

        KeyPair rootKeyPair =
            AsymmetricTestDataGenerator.getKeyPairForSigning();
        _rootPublicKey = rootKeyPair.getPublic();
        _rootPrivateKey = rootKeyPair.getPrivate();
        KeyPair keyPair =
            AsymmetricTestDataGenerator.getKeyPairForSigning();
        _privateKey = keyPair.getPrivate();

        _rootCertificate = X509CertificateTestDataGenerator
            .getRootAuthorityX509Certificate(rootKeyPair);
        _certificate = X509CertificateTestDataGenerator
            .getSignX509Certificate(keyPair, rootKeyPair);

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

        _rootPrivateKeyPassword =
            PrimitivesTestDataGenerator.getCharArray64(numChars);
        _privateKeyPassword =
            PrimitivesTestDataGenerator.getCharArray64(numChars);
        _keyStorePassword =
            PrimitivesTestDataGenerator.getCharArray64(numChars);

        _rootPrivateKeyAlias =
            PrimitivesTestDataGenerator.getString32(numChars);
        _privateKeyAlias =
            PrimitivesTestDataGenerator.getString32(numChars);

        X509Certificate[] rootCertificateChain = new X509Certificate[1];
        rootCertificateChain[0] = _rootCertificate.getCertificate();
        _pkcs12KeyStore.setKeyEntry(_rootPrivateKeyAlias, _rootPrivateKey,
            _rootPrivateKeyPassword, rootCertificateChain);

        X509Certificate[] certificateChain = new X509Certificate[2];
        certificateChain[0] = _certificate.getCertificate();
        certificateChain[1] = _rootCertificate.getCertificate();
        _pkcs12KeyStore.setKeyEntry(_privateKeyAlias, _privateKey,
            _privateKeyPassword, certificateChain);

        FileOutputStream outStream =
            new FileOutputStream(new File(PKCS12_KEY_STORE_PATH));
        _pkcs12KeyStore.store(outStream, _keyStorePassword);

        FileInputStream inStream =
            new FileInputStream(new File(PKCS12_KEY_STORE_PATH));
        _pkcs12KeyStoreLoaded =
            _storesServiceForDefaultPolicy.loadKeyStore(
                KeyStoreType.PKCS12, inStream, _keyStorePassword);
    }

    @AfterClass
    public static void tearDown() {

        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);

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

    @Test
    public final void whenCreatePkcs12CanRetrieveRootPrivateKey()
            throws UnrecoverableKeyException, KeyStoreException,
            NoSuchAlgorithmException {

        Key rootPrivateKey = _pkcs12KeyStore.getKey(_rootPrivateKeyAlias,
            _rootPrivateKeyPassword);

        assertTrue(Arrays.equals(rootPrivateKey.getEncoded(),
            _rootPrivateKey.getEncoded()));
    }

    @Test
    public final void whenCreatePkcs12CanRetrieveRootCertificateChain()
            throws KeyStoreException, InvalidKeyException,
            CertificateException, NoSuchAlgorithmException,
            NoSuchProviderException, SignatureException {

        Certificate[] rootCertificateChain =
            _pkcs12KeyStore.getCertificateChain(_rootPrivateKeyAlias);

        Certificate rootCertificate = rootCertificateChain[0];

        rootCertificate.verify(_rootPublicKey);

        assertTrue(Arrays.equals(rootCertificate.getEncoded(),
            _rootCertificate.getEncoded()));
    }

    @Test
    public final void whenCreatePkcs12CanRetrievePrivateKey()
            throws UnrecoverableKeyException, KeyStoreException,
            NoSuchAlgorithmException {

        Key privateKey =
            _pkcs12KeyStore.getKey(_privateKeyAlias, _privateKeyPassword);

        assertTrue(Arrays.equals(privateKey.getEncoded(),
            _privateKey.getEncoded()));
    }

    @Test
    public final void whenCreatePkcs12CanRetrieveCertificateChain()
            throws KeyStoreException, InvalidKeyException,
            CertificateException, NoSuchAlgorithmException,
            NoSuchProviderException, SignatureException {

        Certificate[] certificateChain =
            _pkcs12KeyStore.getCertificateChain(_privateKeyAlias);

        Certificate certificate = certificateChain[0];
        Certificate rootCertificate = certificateChain[1];

        certificate.verify(_rootPublicKey);
        rootCertificate.verify(_rootPublicKey);

        assertTrue(Arrays.equals(certificate.getEncoded(),
            _certificate.getEncoded()));
        assertTrue(Arrays.equals(rootCertificate.getEncoded(),
            _rootCertificate.getEncoded()));
    }

    @Test
    public final void whenCreatePkcs12WithBcProviderThenCreationIsSuccessful()
            throws NoSuchFieldException, SecurityException,
            IllegalArgumentException, IllegalAccessException,
            GeneralCryptoLibException, KeyStoreException,
            UnrecoverableKeyException, NoSuchAlgorithmException,
            InvalidKeyException, CertificateException,
            NoSuchProviderException, SignatureException {

        Field field = StoresService.class.getDeclaredField("_provider");
        field.setAccessible(true);
        field.set(_storesServiceForDefaultPolicy, Provider.BOUNCY_CASTLE);
        KeyStore pkcs12KeyStore = _storesServiceForDefaultPolicy
            .createKeyStore(KeyStoreType.PKCS12);

        X509Certificate[] rootCertificateChain = new X509Certificate[1];
        rootCertificateChain[0] = _rootCertificate.getCertificate();
        pkcs12KeyStore.setKeyEntry(_rootPrivateKeyAlias, _rootPrivateKey,
            _rootPrivateKeyPassword, rootCertificateChain);

        Key privateKey = pkcs12KeyStore.getKey(_rootPrivateKeyAlias,
            _rootPrivateKeyPassword);
        assertTrue(Arrays.equals(privateKey.getEncoded(),
            _rootPrivateKey.getEncoded()));

        Certificate[] certificateChain =
            _pkcs12KeyStore.getCertificateChain(_rootPrivateKeyAlias);
        Certificate rootCertificate = certificateChain[0];
        rootCertificate.verify(_rootPublicKey);
        assertTrue(Arrays.equals(rootCertificate.getEncoded(),
            _rootCertificate.getEncoded()));
    }

    @Test
    public final void whenLoadPkcs12CanRetrieveRootPrivateKey()
            throws UnrecoverableKeyException, KeyStoreException,
            NoSuchAlgorithmException {

        Key privateKey = _pkcs12KeyStoreLoaded.getKey(_rootPrivateKeyAlias,
            _rootPrivateKeyPassword);

        assertTrue(Arrays.equals(privateKey.getEncoded(),
            _rootPrivateKey.getEncoded()));
    }

    @Test
    public final void whenLoadPkcs12CanRetrieveRootCertificateChain()
            throws KeyStoreException, InvalidKeyException,
            CertificateException, NoSuchAlgorithmException,
            NoSuchProviderException, SignatureException {

        Certificate[] certificateChain = _pkcs12KeyStoreLoaded
            .getCertificateChain(_rootPrivateKeyAlias);

        Certificate rootCertificate = certificateChain[0];

        rootCertificate.verify(_rootPublicKey);

        assertTrue(Arrays.equals(rootCertificate.getEncoded(),
            _rootCertificate.getEncoded()));
    }

    @Test
    public final void whenLoadPkcs12CanRetrievePrivateKey()
            throws UnrecoverableKeyException, KeyStoreException,
            NoSuchAlgorithmException {

        Key privateKey = _pkcs12KeyStoreLoaded.getKey(_privateKeyAlias,
            _privateKeyPassword);

        assertTrue(Arrays.equals(privateKey.getEncoded(),
            _privateKey.getEncoded()));
    }

    @Test
    public final void whenLoadPkcs12CanRetrieveCertificateChain()
            throws KeyStoreException, InvalidKeyException,
            CertificateException, NoSuchAlgorithmException,
            NoSuchProviderException, SignatureException {

        Certificate[] certificateChain =
            _pkcs12KeyStoreLoaded.getCertificateChain(_privateKeyAlias);

        Certificate certificate = certificateChain[0];
        Certificate rootCertificate = certificateChain[1];

        certificate.verify(_rootPublicKey);
        rootCertificate.verify(_rootPublicKey);

        assertTrue(Arrays.equals(certificate.getEncoded(),
            _certificate.getEncoded()));
        assertTrue(Arrays.equals(rootCertificate.getEncoded(),
            _rootCertificate.getEncoded()));
    }

    @Test
    public final void whenLoadPkcs12WithBcProviderThenLoadIsSuccessful()
            throws NoSuchFieldException, SecurityException,
            IllegalArgumentException, IllegalAccessException,
            GeneralCryptoLibException, KeyStoreException,
            NoSuchAlgorithmException, CertificateException, IOException,
            UnrecoverableKeyException, InvalidKeyException,
            NoSuchProviderException, SignatureException {

        Field field = StoresService.class.getDeclaredField("_provider");
        field.setAccessible(true);
        field.set(_storesServiceForDefaultPolicy, Provider.BOUNCY_CASTLE);
        KeyStore pkcs12KeyStore = _storesServiceForDefaultPolicy
            .createKeyStore(KeyStoreType.PKCS12);

        X509Certificate[] rootCertificateChain = new X509Certificate[1];
        rootCertificateChain[0] = _rootCertificate.getCertificate();
        pkcs12KeyStore.setKeyEntry(_rootPrivateKeyAlias, _rootPrivateKey,
            _rootPrivateKeyPassword, rootCertificateChain);

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

        FileInputStream inStream =
            new FileInputStream(new File(PKCS12_KEY_STORE_PATH));
        pkcs12KeyStore = _storesServiceForDefaultPolicy.loadKeyStore(
            KeyStoreType.PKCS12, inStream, _keyStorePassword);

        Key privateKey = pkcs12KeyStore.getKey(_rootPrivateKeyAlias,
            _rootPrivateKeyPassword);
        assertTrue(Arrays.equals(privateKey.getEncoded(),
            _rootPrivateKey.getEncoded()));

        Certificate[] certificateChain =
            pkcs12KeyStore.getCertificateChain(_rootPrivateKeyAlias);
        Certificate rootCertificate = certificateChain[0];
        rootCertificate.verify(_rootPublicKey);
        assertTrue(Arrays.equals(rootCertificate.getEncoded(),
            _rootCertificate.getEncoded()));
    }
}
