/*
 * 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.symmetric.key.factory;

import static org.junit.Assert.assertEquals;

import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

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

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.commons.utils.system.OperatingSystem;
import com.scytl.cryptolib.primitives.securerandom.configuration.ConfigSecureRandomAlgorithmAndProvider;
import com.scytl.cryptolib.symmetric.key.configuration.ConfigHmacSecretKeyAlgorithmAndSpec;
import com.scytl.cryptolib.symmetric.key.configuration.ConfigSecretKeyAlgorithmAndSpec;
import com.scytl.cryptolib.symmetric.key.configuration.SymmetricKeyPolicy;

import mockit.Deencapsulation;
import mockit.Expectations;
import mockit.Injectable;
import mockit.Mock;
import mockit.MockUp;

public class CryptoSecretKeyGeneratorTest {

    public static SymmetricKeyPolicy _policy;

    public static CryptoSecretKeyGeneratorForEncryption _cryptoSecretKeyGeneratorForEncryption;

    public static CryptoSecretKeyGeneratorForHmac _cryptoSecretKeyGeneratorForHmac;

    public static SecretKeyGeneratorFactory _secretKeyGeneratorFactory;

    private static OperatingSystem _os;

    @BeforeClass
    public static void setUp()
            throws NoSuchAlgorithmException, NoSuchProviderException {
        _os = OperatingSystem.current();

        Security.addProvider(new BouncyCastleProvider());

        _policy = getPolicy();

        _secretKeyGeneratorFactory =
            new SecretKeyGeneratorFactory(_policy);

        _cryptoSecretKeyGeneratorForEncryption =
            _secretKeyGeneratorFactory.createGeneratorForEncryption();

        _cryptoSecretKeyGeneratorForHmac =
            _secretKeyGeneratorFactory.createGeneratorForHmac();
    }

    @AfterClass
    public static void tearDown() {
        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

    @After
    public void revertSystemHelper() {
        Deencapsulation.setField(OperatingSystem.class, "current", _os);
    }

    @Test
    public void givenFactoryWhenCreateGeneratorAndSecretKeyThenExpectedKeyAttributes() {

        SecretKey secretKey =
            _cryptoSecretKeyGeneratorForEncryption.genSecretKey();

        String errorMsg =
            "The created secret key does not have the expected algorithm";
        assertEquals(errorMsg, "AES", secretKey.getAlgorithm());

        int expectedNumBytes = 128 / Byte.SIZE;
        errorMsg =
            "The created secret key does not have the expected length";
        assertEquals(errorMsg, expectedNumBytes,
            secretKey.getEncoded().length);
    }

    @Test
    public void givenFactoryWhenCreateGeneratorAndHmacSecretKeyThenExpectedKeyAttributes() {

        SecretKey secretKey =
            _cryptoSecretKeyGeneratorForHmac.genSecretKey();

        String errorMsg =
            "The created secret key does not have the expected algorithm";
        assertEquals(errorMsg, "HmacSHA256", secretKey.getAlgorithm());

        int expectedNumBytes = 256 / Byte.SIZE;
        errorMsg =
            "The created secret key does not have the expected length";
        assertEquals(errorMsg, expectedNumBytes,
            secretKey.getEncoded().length);
    }

    @Test(expected = CryptoLibException.class)
    public void givenUnsupportedAlgorithmWhenCreateSecretKeyGeneratorThenException(
            @Injectable final SymmetricKeyPolicy policy,
            @Injectable final KeyGenerator keyGenerator)
            throws NoSuchAlgorithmException, NoSuchProviderException {

        new Expectations() {
            {
                policy.getSecretKeyAlgorithmAndSpec();
                result = ConfigSecretKeyAlgorithmAndSpec.AES_128_BC;

                KeyGenerator.getInstance(
                    ConfigSecretKeyAlgorithmAndSpec.AES_128_BC
                        .getAlgorithm(),
                    BouncyCastleProvider.PROVIDER_NAME);
                result = new NoSuchAlgorithmException();
            }
        };
        new SecretKeyGeneratorFactory(policy)
            .createGeneratorForEncryption();
    }

    @Test(expected = CryptoLibException.class)
    public void givenBadKeyLengthWhenCreateSecretKeyGeneratorThenException() {

        new MockUp<ConfigSecretKeyAlgorithmAndSpec>() {
            @Mock
            private int getKeyLength() {
                return -1;
            }
        };

        SymmetricKeyPolicy policy = getPolicy();

        CryptoSecretKeyGeneratorForEncryption generator =
            new SecretKeyGeneratorFactory(policy)
                .createGeneratorForEncryption();

        generator.genSecretKey();
    }

    @Test(expected = CryptoLibException.class)
    public void givenBadOS() {

        Deencapsulation.setField(OperatingSystem.class, "current",
            OperatingSystem.OTHER);

        SymmetricKeyPolicy policy = getPolicy();

        CryptoSecretKeyGeneratorForEncryption generator =
            new SecretKeyGeneratorFactory(policy)
                .createGeneratorForEncryption();

        generator.genSecretKey();
    }

    private static SymmetricKeyPolicy getPolicy() {

        return new SymmetricKeyPolicy() {

            @Override
            public ConfigSecretKeyAlgorithmAndSpec getSecretKeyAlgorithmAndSpec() {
                return ConfigSecretKeyAlgorithmAndSpec.AES_128_SUNJCE;
            }

            @Override
            public ConfigSecureRandomAlgorithmAndProvider getSecureRandomAlgorithmAndProvider() {
                return getOsDependentSecureRandomAlgorithmAndProvider();
            }

            @Override
            public ConfigHmacSecretKeyAlgorithmAndSpec getHmacSecretKeyAlgorithmAndSpec() {
                return ConfigHmacSecretKeyAlgorithmAndSpec.HMACwithSHA256_256;
            }
        };
    }

    private static ConfigSecureRandomAlgorithmAndProvider getOsDependentSecureRandomAlgorithmAndProvider() {

        switch (OperatingSystem.current()) {

        case WINDOWS:
            return ConfigSecureRandomAlgorithmAndProvider.PRNG_SUN_MSCAPI;
        case LINUX:
            return ConfigSecureRandomAlgorithmAndProvider.NATIVE_PRNG_SUN;
        default:
            throw new CryptoLibException("OS not supported");
        }
    }
}
