/*
 * 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.asymmetric.keypair.openssl;

import static org.junit.Assert.assertEquals;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidParameterException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.DSAParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAKeyGenParameterSpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.HashMap;
import java.util.Map;

import org.junit.Before;
import org.junit.Test;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;

import mockit.Expectations;
import mockit.Injectable;

/**
 * Tests of {@link RSAKeyPairGeneratorSpi}.
 */
public class RSAKeyPairGeneratorSpiTest {
    private static final String PEM = "-----BEGIN PRIVATE KEY-----\n"
        + "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAK76mIp/uSiPZQNP"
        + "5rxXKpWU7rNzy+qof73vZIFLMl2IaOwxjJswMon26rr7zzbLOBOkApgEBF5P5z/j"
        + "+jfpAYQel3KnjNsPcG7hiW1pCVdOg042D8lyJPKGC6a/LMwFcwTj8XaMiu6gTFJv"
        + "Iu5a5JJljZ21+tLLKDU9VXwXm+wJAgMBAAECgYEAj4WWtwtaWU18wkG4dUVd9YFK"
        + "ZUEWwmvhE26XVoHSVuu7e/iMZwIaghVYpXRDm6MGZP3C7uNkneMWP86D/3YMOfFB"
        + "eidi13iy9V3OiybqXainxvqH4tLInpQJeVGK1DK3flKp9r84Vlqx71hHyK5k3Zo5"
        + "kbed/xPkXDwfZkgeX7ECQQDizQ5R8hsrUgnKq5Fv6F1lH62zk94Qi8C1/Ir+xEkk"
        + "0VRggY1eX0hr6yKChJWgHRP8FT5PCAmEKmZwQeEY6o09AkEAxYGUFRRXrv9ZTIKZ"
        + "Pa9jAJbZCFSaxntcjAJ0AeFbZ49V0YsFXPDtP8i5p7YmJKJWharQ03qYdrokzokd"
        + "TL6evQJAEIAaqpuSnnQ5giU5T4r4QIMtgAMiBNImExdshVyCdctE+EB0qIkGG9Pf"
        + "jt9Wg2+aJJznC5jqGFN9YG1qkwV9gQJBAMVD29SGAeHudpF9a3L55uciw1iXsBMI"
        + "CXJpzNZH8JRpzCxDLzMaC8yGPjAtNMGoRsSwzGw86WTEHJkQx+vsA/0CQAPQ5Tnz"
        + "CmXUfUSCAeDaunjF252fl2h2WO1psS/ucfTiEbN4B6+77bvFDeMrfDfDTJWbXZK8"
        + "HB935zyvEMIiBcE=" + "-----END PRIVATE KEY-----\n";

    @Injectable
    private OpenSSL openSSL;

    @Injectable
    private RSAPrivateCrtKey privateKey;

    @Injectable
    private RSAPublicKey publicKey;

    @Injectable
    private KeyFactory factory;

    private RSAKeyPairGeneratorSpi spi;

    @Before
    public void setUp() {
        spi = new RSAKeyPairGeneratorSpi(openSSL, factory);
    }

    @Test
    public void testGenerateKeyPair()
            throws InvalidAlgorithmParameterException, OpenSSLException,
            InvalidKeySpecException {
        new Expectations() {
            {
                Map<String, String> options = new HashMap<>();
                options.put("rsa_keygen_bits", Integer.toString(1024));
                options.put("rsa_keygen_pubexp",
                    RSAKeyGenParameterSpec.F4.toString());
                openSSL.genpkey("RSA", options);
                result = PEM.getBytes(StandardCharsets.UTF_8);

                factory.generatePrivate(
                    withInstanceOf(PEMEncodedKeySpec.class));
                result = privateKey;

                privateKey.getModulus();
                result = BigInteger.TEN;
                privateKey.getPublicExponent();
                result = RSAKeyGenParameterSpec.F4;
                factory.generatePublic(
                    withInstanceOf(RSAPublicKeySpec.class));
                result = publicKey;
            }
        };

        KeyPair pair = spi.generateKeyPair();
        assertEquals(privateKey, pair.getPrivate());
        assertEquals(publicKey, pair.getPublic());
    }

    @Test(expected = CryptoLibException.class)
    public void testGenerateKeyPairKeyFactoryError1()
            throws InvalidKeySpecException, OpenSSLException {
        new Expectations() {
            {
                Map<String, String> options = new HashMap<>();
                options.put("rsa_keygen_bits", Integer.toString(1024));
                options.put("rsa_keygen_pubexp",
                    RSAKeyGenParameterSpec.F4.toString());
                openSSL.genpkey("RSA", options);
                result = PEM.getBytes(StandardCharsets.UTF_8);

                factory.generatePrivate(
                    withInstanceOf(PEMEncodedKeySpec.class));
                result = privateKey;

                privateKey.getModulus();
                result = BigInteger.TEN;
                privateKey.getPublicExponent();
                result = RSAKeyGenParameterSpec.F0;
                factory.generatePublic(
                    withInstanceOf(RSAPublicKeySpec.class));
                result = new InvalidKeySpecException("test");
            }
        };
        spi.generateKeyPair();
    }

    @Test(expected = CryptoLibException.class)
    public void testGenerateKeyPairKeyFactoryError2()
            throws InvalidKeySpecException, OpenSSLException {
        new Expectations() {
            {
                Map<String, String> options = new HashMap<>();
                options.put("rsa_keygen_bits", Integer.toString(1024));
                options.put("rsa_keygen_pubexp",
                    RSAKeyGenParameterSpec.F4.toString());
                openSSL.genpkey("RSA", options);
                result = PEM.getBytes(StandardCharsets.UTF_8);

                factory.generatePrivate(
                    withInstanceOf(PEMEncodedKeySpec.class));
                result = new InvalidKeySpecException("test");
            }
        };
        spi.generateKeyPair();
    }

    @Test(expected = CryptoLibException.class)
    public void testGenerateKeyPairOpenSSLError()
            throws InvalidKeySpecException, OpenSSLException {
        new Expectations() {
            {
                Map<String, String> options = new HashMap<>();
                options.put("rsa_keygen_bits", Integer.toString(1024));
                options.put("rsa_keygen_pubexp",
                    RSAKeyGenParameterSpec.F4.toString());
                openSSL.genpkey("RSA", options);
                result = new OpenSSLException("test");
            }
        };
        spi.generateKeyPair();
    }

    @Test
    public void testInitializeAlgorithmParameterSpecSecureRandom()
            throws InvalidAlgorithmParameterException,
            InvalidKeySpecException, OpenSSLException {
        final RSAKeyGenParameterSpec spec =
            new RSAKeyGenParameterSpec(1, RSAKeyGenParameterSpec.F0);
        spi.initialize(spec, new SecureRandom());
        new Expectations() {
            {
                Map<String, String> options = new HashMap<>();
                options.put("rsa_keygen_bits",
                    Integer.toString(spec.getKeysize()));
                options.put("rsa_keygen_pubexp",
                    spec.getPublicExponent().toString());
                openSSL.genpkey("RSA", options);
                result = PEM.getBytes(StandardCharsets.UTF_8);

                factory.generatePrivate(
                    withInstanceOf(PEMEncodedKeySpec.class));
                result = privateKey;

                privateKey.getModulus();
                result = BigInteger.TEN;
                privateKey.getPublicExponent();
                result = RSAKeyGenParameterSpec.F0;
                factory.generatePublic(
                    withInstanceOf(RSAPublicKeySpec.class));
                result = publicKey;
            }
        };

        KeyPair pair = spi.generateKeyPair();
        assertEquals(privateKey, pair.getPrivate());
        assertEquals(publicKey, pair.getPublic());
    }

    @Test(expected = InvalidAlgorithmParameterException.class)
    public void testInitializeAlgorithmParameterSpecSecureRandomInvalidKeysize()
            throws InvalidAlgorithmParameterException {
        RSAKeyGenParameterSpec spec =
            new RSAKeyGenParameterSpec(0, RSAKeyGenParameterSpec.F0);
        spi.initialize(spec, new SecureRandom());
    }

    @Test(expected = InvalidAlgorithmParameterException.class)
    public void testInitializeAlgorithmParameterSpecSecureRandomInvalidPublicExponent()
            throws InvalidAlgorithmParameterException {
        RSAKeyGenParameterSpec spec =
            new RSAKeyGenParameterSpec(1, BigInteger.ZERO);
        spi.initialize(spec, new SecureRandom());
    }

    @Test(expected = InvalidAlgorithmParameterException.class)
    public void testInitializeAlgorithmParameterSpecSecureRandomInvalidType()
            throws InvalidAlgorithmParameterException {
        AlgorithmParameterSpec spec =
            new DSAParameterSpec(null, null, null);
        spi.initialize(spec, new SecureRandom());
    }

    @Test
    public void testInitializeIntSecureRandom()
            throws InvalidKeySpecException, OpenSSLException {
        spi.initialize(10, new SecureRandom());
        new Expectations() {
            {
                Map<String, String> options = new HashMap<>();
                options.put("rsa_keygen_bits", Integer.toString(10));
                options.put("rsa_keygen_pubexp",
                    RSAKeyGenParameterSpec.F4.toString());
                openSSL.genpkey("RSA", options);
                result = PEM.getBytes(StandardCharsets.UTF_8);

                factory.generatePrivate(
                    withInstanceOf(PEMEncodedKeySpec.class));
                result = privateKey;

                privateKey.getModulus();
                result = BigInteger.TEN;
                privateKey.getPublicExponent();
                result = RSAKeyGenParameterSpec.F0;
                factory.generatePublic(
                    withInstanceOf(RSAPublicKeySpec.class));
                result = publicKey;
            }
        };

        KeyPair pair = spi.generateKeyPair();
        assertEquals(privateKey, pair.getPrivate());
        assertEquals(publicKey, pair.getPublic());
    }

    @Test(expected = InvalidParameterException.class)
    public void testInitializeIntSecureRandomInvalid() {
        spi.initialize(0, new SecureRandom());
    }
}
