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

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.util.Arrays;
import java.util.Base64;

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

import com.scytl.cryptolib.api.asymmetric.utils.KeyPairConverterAPI;
import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.cryptolib.asymmetric.signer.configuration.DigitalSignerPolicyFromProperties;
import com.scytl.cryptolib.commons.constants.Constants;
import com.scytl.cryptolib.primitives.securerandom.configuration.SecureRandomPolicyFromProperties;
import com.scytl.cryptolib.primitives.securerandom.factory.CryptoRandomBytes;
import com.scytl.cryptolib.primitives.securerandom.factory.SecureRandomFactory;

public class DigitalSignerTest {

    /**
     * An int of value zero.
     */
    public static final int ZERO = 0;

    /**
     * An int of value one.
     */
    public static final int ONE = 1;

    /**
     * An int of value two.
     */
    public static final int TWO = 2;

    private static final int DATA_BYTE_LENGTH = 100;

    private static final int NUMBER_OF_DATA_PARTS = 10;

    private static final String MISSING_SIGNER_PRNG_PROPERTIES =
            "properties/no_signer_prng_settings-cryptolibPolicy.properties";

    private static CryptoRandomBytes _randomByteGenerator;

    private static byte[] _data;

    private static byte[][] _dataParts;

    private static KeyPair _keyPair;

    private static CryptoDigitalSigner _cryptoDigitalSigner_sha256_rsa;

    private static CryptoDigitalSigner _cryptoDigitalSigner_sha256_rsa_pss;

    private static AsymmetricService _asymmetricServiceFromDefaultConstructor_sha256_rsa_pss;

    private static PrivateKey _privateKeyUsedInJavaScript;

    private static PublicKey _publicKeyUsedInJavaScript;

    private static String _signedData = "Ox2fUJq1gAbX";

    private static byte[] _signedDataAsBytes;

    private static String _rsaSignatureGeneratedInJs;

    private static String _rsaPssSignatureGeneratedInJs1;

    private static String _rsaPssSignatureGeneratedInJs2;

    @BeforeClass
    public static void setUp() throws GeneralCryptoLibException {

        Security.addProvider(new BouncyCastleProvider());

        // create services
        String pathProperties_signer_SHA256_RSA =
            "properties/cryptolibPolicy_signer_SHA256withRSA.properties";

        // create services
        String pathProperties_signer_SHA256_RSA_PSS =
            "properties/cryptolibPolicy_signer_SHA256withRSA_PSS.properties";

        // the service is only used for generating a key pair
        _asymmetricServiceFromDefaultConstructor_sha256_rsa_pss =
            new AsymmetricService();

        // create key pairs for signing
        _keyPair =
            _asymmetricServiceFromDefaultConstructor_sha256_rsa_pss
                .getKeyPairForSigning();

        // create a signer using a properties file with SHA256withRSA set as the
        // signing algorithm
        _cryptoDigitalSigner_sha256_rsa =
            new DigitalSignerFactory(
                new DigitalSignerPolicyFromProperties(
                    pathProperties_signer_SHA256_RSA)).create();

        // create a signer using a properties file with SHA256withRSA/PSS set as
        // the signing algorithm
        _cryptoDigitalSigner_sha256_rsa_pss =
            new DigitalSignerFactory(
                new DigitalSignerPolicyFromProperties(
                    pathProperties_signer_SHA256_RSA_PSS)).create();

        _randomByteGenerator =
            new SecureRandomFactory(new SecureRandomPolicyFromProperties(
                Constants.CRYPTOLIB_POLICY_PROPERTIES_FILE_PATH))
                .createByteRandom();

        _data = _randomByteGenerator.nextRandom(DATA_BYTE_LENGTH);

        _dataParts = new byte[NUMBER_OF_DATA_PARTS][];
        for (int i = 0; i < NUMBER_OF_DATA_PARTS; i++) {
            _dataParts[i] =
                _randomByteGenerator.nextRandom(DATA_BYTE_LENGTH);
        }

        KeyPairConverterAPI keyPairConverter =
            _asymmetricServiceFromDefaultConstructor_sha256_rsa_pss
                .getKeyPairConverter();

        String privateKeyFromJsPemString =
            "-----BEGIN RSA PRIVATE KEY-----MIIEowIBAAKCAQEAglypZU45bnf2opnRI+Y51VqouBpvDT33xIB/OtbwKzwVpi+JrjBFtfk33tE9t/dSRs79CK94HRhCWcOiLa2qWPrjeZ9SBiEyScrhIvRZVBF41zBgwQNuRvJCsKmAqlZaFNJDZxEP4repmlBn1CfVFmfrXmOKqwP5F7l9ZtucveRzsfmF1yVPFkW8TMuB3YqMiyymyqHlS8ujCsu5I8tpgPbwuxdMOY94fNhSXrYkY8IuX1g1zdq/Z1jluOaR/UqK4UpnbuJaH/F0VgDNiWh6cTD0DFGEk0b70i5wU4Q3L/S6XZQRvSuADoCbhwBKuFL5pW5n865oLVb5S3wuVdWaGwIDAQABAoIBAC/tn34Wf3kE9BGeGc1oFLVDaqqdVVz5/oEpeR2J7q0GnzMFYUpAhzC7WvY52cYsUPyll1Q9Jx0TUTmteo/uvKWQQFfz4nVMeS+2PoXabolBDzuWlsv/1eiRo0FOYHa/3siu8YcQN9X0DpAkpbfTmT1uoZOHZ3EuucMmOFu7vGn38Grw8bSxpR0uvTtnb8ygC+aB51y38RMyhzQQanrM8FMeAfDAy6IB0Yo7b0c50Cxa6Ax4nqn9LXyGakr5WeAMkgTIOA/GId9SZD4e5eRpq+628pOeR4O9datFltgl6r1+A4ii2VrJsDqeatGtODlX6KRKqwFHoGIa2TjgSZLuorECgYEAxeSZDOOgFsI5mB7RkRzZaQ9znJ15sgdyZiAFZAOUah4hSGdAXNAnZTlrdacduXEu3EfkpuPToX7xZSv5FRYwfBwMwCLeytlGLPjQzWejZGbo4+KqgzWb9fECDYVtDPlJ/+yLih9nt67BHweJKxYydl18rVigdVyy22X86NijSykCgYEAqKPUrXZAo+TJvmTw4tgsibJgvXBYBhmsej8mGNQw+Nyp2gV28sgm61ifIeXKS8teq+MFwGA6cHQedbsCqhMHokdhESZmlbWxhSFLihQcewBxwvrBwbaxI23yXRzwMewznZFL032PpcbqrmwFmcSSEZ3nmbvTH6ShqLW+pzDNp6MCgYBQLzdgxJ7qedqSa/JohTMG4e7rh9d2rpPJE7J7ewPZF8pOpx+qO+Gqn2COdJ+Ts2vUcAETKn9nEaPIZc/wnmQY9dioxbhWo0FPGaaphBPtq9Ez/XUv4zoFppk5V1X/isdUPsmvttf00oeIBiqrXbwmv+yz5JRn2Z7TTXjz9Ev+OQKBgQCUuoCMRzl1EgcXIqEL/0kwW6BUEqufHa9u1Ri9Vw6lvL8T6DPipMEmWK9nzuid9gtVns/ovTVtDgv7GuabplLaPQePf4WDzY11c0rSyS/hDyBFrK+LL5uEOqhAlJAGB2HyOj1clWVF+GvrTpuV5LZKUS/79pmZU7G7QCaX/0Ow7wKBgC/kDH7cmWQnWvvJ5izrx/7PogQVPOLELeUIGLu/hjsSdDKiFCxCUZ948+9NuG+DnpXDWzw//r8mPBRRGGsqFws5Aipp7yjQ3kRDCCzGelPCVhHyfmKqA+8ewXPulKS3/wIyHIvaXmsuAtTfurHtpRyzjKmCBK1Y6WQ3trIXvo7s-----END RSA PRIVATE KEY-----";
        String publicKeyFromJsPemString =
            "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAglypZU45bnf2opnRI+Y51VqouBpvDT33xIB/OtbwKzwVpi+JrjBFtfk33tE9t/dSRs79CK94HRhCWcOiLa2qWPrjeZ9SBiEyScrhIvRZVBF41zBgwQNuRvJCsKmAqlZaFNJDZxEP4repmlBn1CfVFmfrXmOKqwP5F7l9ZtucveRzsfmF1yVPFkW8TMuB3YqMiyymyqHlS8ujCsu5I8tpgPbwuxdMOY94fNhSXrYkY8IuX1g1zdq/Z1jluOaR/UqK4UpnbuJaH/F0VgDNiWh6cTD0DFGEk0b70i5wU4Q3L/S6XZQRvSuADoCbhwBKuFL5pW5n865oLVb5S3wuVdWaGwIDAQAB-----END PUBLIC KEY-----";

        _privateKeyUsedInJavaScript =
            keyPairConverter
                .getPrivateKeyForSigningFromPem(privateKeyFromJsPemString);
        _publicKeyUsedInJavaScript =
            keyPairConverter
                .getPublicKeyForSigningFromPem(publicKeyFromJsPemString);

        _rsaSignatureGeneratedInJs =
            "UqEYX273eKEylP+nZDKHtCaPFQ1+mZvJhm0GIe8QOBjnglXBljfItkKKC1gMneIxT77m137+ikQsuECbnrZ3NOenGmx4J5yafDLbY1MeUiI35aYvmPk1RyNguX5SelTRAytGNV6aoZSuyBkbR0bdO7av5JzP35I+GURLMVeEI+veew9e6rG6oI7XZqRLV52LOYfoB/BWiORhmJDNh5Xj82G+D3OdU0DPd4dIR16tIxaYuhoQqR9dxFFN7UzzD6bbpzldcV9olfANHr135GDWxYmtwXxgv+pN0Cf5p5nJp9h+yLUnZurkzpWO69GmLYWqjHuK/vMK503WeBXco1QmsA==";

        _rsaPssSignatureGeneratedInJs1 =
            "ZMnNlXZWDO7nrYtF2WNiazpD2+rTNvWe6WtoBbBQV7E1VwsrJSADEmDaqdTqLPKuGipVhLY8RMVuggTXe966kOO8wBkD6r23Mbz5mRPSjplgXBXDqhsMZzHdOF5f6rHm+fVal9eLO+cl3hR8IlrYDgffLpGdPJjuehyDs2wVedQhxrEDCePF9VFGqhHJ7UqcA5fPjkczVrePI3k1kM9GnTyy7aXN3mlJ7TUpNbueF0xAmi13KCg4YvS/Sz1re6cg79JxpyzyEIBunJPwqFzImjf4CeqwBVBtlb8MqrysCOsBnbQ4CPozUvPqYx4IclkMB3x/V/+Ir7byimX/8ZbBuQ==";

        _rsaPssSignatureGeneratedInJs2 =
            "Jcf4sBBK0QDDEhrnLdf4uiYMi0homLhMON0T1xHBL3/408wC3SWcFUihHfjUIaCRKOxjjQcEfHXCQpgbbG/rsuhVMtrasExr3pV3mo5DHEsQNAyVQqscMEMGhx7xLZdkuEO5UpwtKEO3Zt4MFPOaiiQwNhKAZxmYhcjenZ19eyKXo60Tqa4JR/t7A6cHYTIdDBr1ixZvjnAeKxGXkFyzKUPFkP5teflVX4YwQFFEM5JryN6Yec2QupssouuXegy0Dd55wGXBKVkHiQ1eseW6WsxsFqg1jVaCdxClvMsrtZkna5r+Q0p9xJNa9GmgafJX0rQj9fDujcsVJLRelHohqQ==";

        _signedData = "Ox2fUJq1gAbX";

        _signedDataAsBytes = _signedData.getBytes(StandardCharsets.UTF_8);
    }

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

    @Test
    public void whenEncryptAndDecryptSingleDataByteArrayUsingRsaThenTrue() {

        byte[] signatureBytes =
            _cryptoDigitalSigner_sha256_rsa.sign(_keyPair.getPrivate(),
                _data);

        boolean signatureVerified =
            _cryptoDigitalSigner_sha256_rsa.verifySignature(
                signatureBytes, _keyPair.getPublic(), _data);

        Assert.assertTrue(signatureVerified);
    }

    @Test
    public void whenEncryptAndDecryptArrayOfDataByteArraysUsingRsaThenTrue() {

        byte[] signatureBytes =
            _cryptoDigitalSigner_sha256_rsa.sign(_keyPair.getPrivate(),
                _dataParts);

        boolean signatureVerified =
            _cryptoDigitalSigner_sha256_rsa.verifySignature(
                signatureBytes, _keyPair.getPublic(), _dataParts);

        Assert.assertTrue(signatureVerified);
    }

    @Test
    public void whenSingUsingStream() throws IOException {

        byte[] data = new byte[10000];
        byte b = 101;
        Arrays.fill(data, b);
        byte[] signatureBytes =
            _cryptoDigitalSigner_sha256_rsa.sign(_keyPair.getPrivate(),
                new ByteArrayInputStream(data));

        boolean signatureVerified =
            _cryptoDigitalSigner_sha256_rsa.verifySignature(
                signatureBytes, _keyPair.getPublic(),
                new ByteArrayInputStream(data));

        Assert.assertTrue(signatureVerified);
    }

    @Test
    public void whenEncryptAndDecryptCommaSeperatedListOfDataByteArraysUsingRsaThenTrue() {

        byte[] signatureBytes =
            _cryptoDigitalSigner_sha256_rsa.sign(_keyPair.getPrivate(),
                _dataParts[ZERO], _dataParts[ONE], _dataParts[TWO]);

        boolean signatureVerified =
            _cryptoDigitalSigner_sha256_rsa.verifySignature(
                signatureBytes, _keyPair.getPublic(), _dataParts[ZERO],
                _dataParts[ONE], _dataParts[TWO]);

        Assert.assertTrue(signatureVerified);
    }

    @Test
    public void whenEncryptAndDecryptSingleDataByteArrayUsingRsaPssThenTrue() {

        byte[] signatureBytes =
            _cryptoDigitalSigner_sha256_rsa_pss.sign(
                _keyPair.getPrivate(), _data);

        boolean signatureVerified =
            _cryptoDigitalSigner_sha256_rsa_pss.verifySignature(
                signatureBytes, _keyPair.getPublic(), _data);

        Assert.assertTrue(signatureVerified);
    }

    @Test
    public void whenEncryptAndDecryptArrayOfDataByteArraysUsingRsaPssThenTrue() {

        byte[] signatureBytes =
            _cryptoDigitalSigner_sha256_rsa_pss.sign(
                _keyPair.getPrivate(), _dataParts);

        boolean signatureVerified =
            _cryptoDigitalSigner_sha256_rsa_pss.verifySignature(
                signatureBytes, _keyPair.getPublic(), _dataParts);

        Assert.assertTrue(signatureVerified);
    }

    @Test
    public void whenEncryptAndDecryptCommaSeperatedListOfDataByteArraysUsingRsaPssThenTrue() {

        byte[] signatureBytes =
            _cryptoDigitalSigner_sha256_rsa_pss.sign(
                _keyPair.getPrivate(), _dataParts[ZERO], _dataParts[ONE],
                _dataParts[TWO]);

        boolean signatureVerified =
            _cryptoDigitalSigner_sha256_rsa_pss.verifySignature(
                signatureBytes, _keyPair.getPublic(), _dataParts[ZERO],
                _dataParts[ONE], _dataParts[TWO]);

        Assert.assertTrue(signatureVerified);
    }

    @Test
    public void whenRegenerateSignatureAsFromJsUsingRsaThenEquals() {

        byte[] signature =
            _cryptoDigitalSigner_sha256_rsa.sign(
                _privateKeyUsedInJavaScript, _signedDataAsBytes);

        String signatureBase64 = Base64.getEncoder().encodeToString(signature);

        String errorMsg =
            "The generated signature does not match the expected one";
        assertEquals(errorMsg, _rsaSignatureGeneratedInJs, signatureBase64);
    }

    @Test
    public void whenVerifyRealRsaSignatureFromJsThenOk() {

        byte[] signatureBytes =
            Base64.getDecoder().decode(_rsaSignatureGeneratedInJs);

        assertTrue(_cryptoDigitalSigner_sha256_rsa
            .verifySignature(signatureBytes, _publicKeyUsedInJavaScript,
                _signedDataAsBytes));
    }

    @Test
    public void whenVerifyBadRsaSignatureFromJsThenFalse() {

        byte[] signatureBytes = Base64.getDecoder().decode("XXXXXX");

        assertFalse(_cryptoDigitalSigner_sha256_rsa
            .verifySignature(signatureBytes, _publicKeyUsedInJavaScript,
                _signedDataAsBytes));
    }

    @Test
    public void whenVerifyRealRsaPssSignature1FromJsThenOk() {

        byte[] signatureBytes =
            Base64.getDecoder().decode(_rsaPssSignatureGeneratedInJs1);

        assertTrue(_cryptoDigitalSigner_sha256_rsa_pss
            .verifySignature(signatureBytes, _publicKeyUsedInJavaScript,
                _signedDataAsBytes));
    }

    @Test
    public void whenVerifyRealRsaPssSignature2FromJsThenOk() {

        byte[] signatureBytes =
            Base64.getDecoder().decode(_rsaPssSignatureGeneratedInJs2);

        assertTrue(_cryptoDigitalSigner_sha256_rsa_pss
            .verifySignature(signatureBytes, _publicKeyUsedInJavaScript,
                _signedDataAsBytes));
    }

    @Test
    public void whenVerifyBadRsaPssSignatureFromJsThenFalse() {

        byte[] signatureBytes = Base64.getDecoder().decode("XXXXXX");

        assertFalse(_cryptoDigitalSigner_sha256_rsa_pss
            .verifySignature(signatureBytes, _publicKeyUsedInJavaScript,
                _signedDataAsBytes));
    }

    @Test
    public void whenVerifyRsaSignatureFromJsAsRsaPssThenFalse() {

        byte[] signatureBytes =
            Base64.getDecoder().decode(_rsaSignatureGeneratedInJs);

        assertFalse(_cryptoDigitalSigner_sha256_rsa_pss
            .verifySignature(signatureBytes, _publicKeyUsedInJavaScript,
                _signedDataAsBytes));
    }

    @Test
    public void whenVerifyRsaPssSignatureFromJsAsRsaThenFalse() {

        byte[] signatureBytes =
            Base64.getDecoder().decode(_rsaPssSignatureGeneratedInJs1);

        assertFalse(_cryptoDigitalSigner_sha256_rsa
            .verifySignature(signatureBytes, _publicKeyUsedInJavaScript,
                _signedDataAsBytes));
    }

    @Test
    public void testEncryptAndDecryptCommaSeparatedListOfDataByteArrays() {

        byte[] signatureBytes =
            _cryptoDigitalSigner_sha256_rsa.sign(_keyPair.getPrivate(),
                _dataParts[ZERO], _dataParts[ONE], _dataParts[TWO]);

        boolean signatureVerified =
            _cryptoDigitalSigner_sha256_rsa.verifySignature(
                signatureBytes, _keyPair.getPublic(), _dataParts[ZERO],
                _dataParts[ONE], _dataParts[TWO]);

        Assert.assertTrue(signatureVerified);
    }

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

        byte[] message = "data".getBytes();

        _cryptoDigitalSigner_sha256_rsa.sign(null, message);

    }

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

        byte[] message = "data".getBytes();

        PrivateKey key = new PrivateKey() {

			private static final long serialVersionUID = 1L;

			@Override
            public String getFormat() {
                return null;
            }

            @Override
            public byte[] getEncoded() {
                return null;
            }

            @Override
            public String getAlgorithm() {
                return null;
            }
        };
        _cryptoDigitalSigner_sha256_rsa.sign(key, message);

    }

    @Test
    public void testSigngNoData() {

        _cryptoDigitalSigner_sha256_rsa.sign(_keyPair.getPrivate());

    }

    @Test
    public void testSigngEmptyData() {

        byte[] message = new byte[0];

        _cryptoDigitalSigner_sha256_rsa.sign(_keyPair.getPrivate(),
            message);

    }

    /**
     * The digital signer cannot be created without the signer's PRNG
     * configuration parameters.
     */
    @Test(expected = CryptoLibException.class)
    public void ensurePRNGIsSetUp() {
        new DigitalSignerFactory(new DigitalSignerPolicyFromProperties(
                MISSING_SIGNER_PRNG_PROPERTIES)).create();
    }
}
