/*
 * 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.products.ov.channel.cc.commons.keymanagement;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;

import java.security.KeyManagementException;
import java.security.KeyPair;
import java.security.KeyStore.PasswordProtection;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Date;

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

import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.certificates.CertificatesServiceAPI;
import com.scytl.cryptolib.api.elgamal.ElGamalServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.primitives.PrimitivesServiceAPI;
import com.scytl.cryptolib.api.stores.StoresServiceAPI;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.cryptolib.certificates.bean.RootCertificateData;
import com.scytl.cryptolib.certificates.bean.ValidityDates;
import com.scytl.cryptolib.certificates.bean.X509DistinguishedName;
import com.scytl.cryptolib.certificates.service.CertificatesService;
import com.scytl.cryptolib.elgamal.bean.ElGamalEncryptionParameters;
import com.scytl.cryptolib.elgamal.bean.ElGamalKeyPair;
import com.scytl.cryptolib.elgamal.bean.ElGamalPrivateKey;
import com.scytl.cryptolib.elgamal.bean.ElGamalPublicKey;
import com.scytl.cryptolib.elgamal.service.ElGamalService;
import com.scytl.cryptolib.primitives.service.PrimitivesService;
import com.scytl.cryptolib.stores.service.StoresService;

/**
 * Tests of {@link CodecImpl}.
 */
public class CodecImplTest {
    private static final String ENCRYPTION_PARAMETERS_JSON =
        "{\"encryptionParams\":{\"p\":\"AMoJqsCm6UG4B+FGO1Vh6qJcCJcMAVyZFIbZnTIZyD3XAQSTLdxVZxrXm5ZNQMajKAmjUfb3/BV84OgDZaNMXQziV2Uw6hQ70jUcswZ6ZfHAQBy+kwSozDM1LA6ndqp0JIGmPD7LqFIhv9ly7427ksgX1LjNR1tUInVaCS26dfP5wkZyCxwa9/3TapFpFugek+WSJqozaDSd89WAuqXrbBu0FMJWvBVvJapsjfJLEOXTHzdeWef9X5M4FY4Uk+/0WaDVZXy5JhcdoxuNU6Oy0/9buDrPfzlg5a26gpV5ei7XHfykYxdBIPUQoAmraSyUp/byhSDcQu6GJxkfknfAORU=\",\"q\":\"AJT/piLUjl6zN9D/Pc4GYsAG0KAS57H7xJ/mqb+ycbIl\",\"g\":\"ALNkOUZfTDbTMsCYUAKBAAzPdl7422rYGhca6GUAwEPQMlexuIZfIoft+ww54NDqGrUb4TFyw8Yy+y+9Ryb7AXNockro4XYfUjN6eiwOrPxfWG3r9BxqReWZRp74uQ2Ec1toVBrpHaXQNETOJpPBThMKKqrgIuPeAdMRM9U9viku8X0IDmHcqoNaI2o2fvFLFaxlzt5h4kzeJ0A5Twn8wwp5Uln0KcTBJf3H904Icq1u9WTvCTa1jQkoSzkNDQ1VABNFWBa/hW/RGAG7YqTurYHQB+9sMRpOiN10X+6ihdF5VgGpCgXfquKCSqa7I44vq2Vr9+Bm34Gjy+uDOJWrilg=\"}}";

    private PasswordProtection pwd = new PasswordProtection("pwdpwd".toCharArray());

    private static StoresServiceAPI storesService;

    private static AsymmetricServiceAPI asymmetricService;

    private static CertificatesServiceAPI certificatesService;

    private static ElGamalServiceAPI elGamalService;

    private static PrimitivesServiceAPI primitivesService;

    private static PrivateKey caPrivateKey;

    private static X509Certificate[] caCertificateChain;

    private static Generator generator;

    private static CodecImpl codec;

    private static NodeKeys nodeKeys;

    private static ElGamalEncryptionParameters parameters;

    @BeforeClass
    public static void beforeClass() throws GeneralCryptoLibException, KeyManagementException {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }

        storesService = new StoresService();
        asymmetricService = new AsymmetricService();
        certificatesService = new CertificatesService();
        elGamalService = new ElGamalService();
        primitivesService = new PrimitivesService();

        KeyPair pair = asymmetricService.getKeyPairForSigning();

        caPrivateKey = pair.getPrivate();

        RootCertificateData certificateData = new RootCertificateData();
        certificateData.setSubjectDn(new X509DistinguishedName.Builder("CA", "ES").build());
        certificateData.setSubjectPublicKey(pair.getPublic());
        certificateData.setValidityDates(new ValidityDates(new Date(), new Date(System.currentTimeMillis() + 1000)));
        X509Certificate certificate =
            certificatesService.createRootAuthorityX509Certificate(certificateData, caPrivateKey).getCertificate();

        caCertificateChain = new X509Certificate[] {certificate };

        codec = new CodecImpl(storesService, asymmetricService);

        generator = new GeneratorImpl(asymmetricService, certificatesService, elGamalService, primitivesService, codec,
            "nodeId");
        nodeKeys = generator.generateNodeKeys(caPrivateKey, caCertificateChain);

        parameters = ElGamalEncryptionParameters.fromJson(ENCRYPTION_PARAMETERS_JSON);
    }

    @Before
    public void initiatePassword() {
        pwd = new PasswordProtection("password".toCharArray());
    }

    @Test
    public void testDecodeElectionSigningKeys() throws KeyManagementException {
        ElectionSigningKeys keys = generator.generateElectionSigningKeys("electionEventId", new Date(),
            new Date(System.currentTimeMillis() + 1000), nodeKeys);

        byte[] bytes = codec.encodeElectionSigningKeys(keys, pwd);
        ElectionSigningKeys keys2 = codec.decodeElectionSigningKeys(bytes, pwd);

        assertEquals(keys.privateKey(), keys2.privateKey());
        assertArrayEquals(keys.certificateChain(), keys2.certificateChain());
    }

    @Test(expected = KeyManagementException.class)
    public void testDecodeElectionSigningKeysInvalidBytes() throws KeyManagementException {
        byte[] bytes = {1, 2, 3 };
        codec.decodeElectionSigningKeys(bytes, pwd);
    }

    @Test(expected = InvalidPasswordException.class)
    public void testDecodeElectionSigningKeysInvalidPassword() throws KeyManagementException {
        ElectionSigningKeys keys = generator.generateElectionSigningKeys("electionEventId", new Date(),
            new Date(System.currentTimeMillis() + 1000), nodeKeys);

        byte[] bytes = codec.encodeElectionSigningKeys(keys, pwd);
        codec.decodeElectionSigningKeys(bytes, new PasswordProtection("pwdpwd2".toCharArray()));
    }

    @Test
    public void testDecodeElGamalPrivateKey() throws GeneralCryptoLibException, KeyManagementException {
        ElGamalKeyPair pair = elGamalService.getElGamalKeyPairGenerator().generateKeys(parameters, 3);
        ElGamalPrivateKey key = pair.getPrivateKeys();
        byte[] bytes = codec.encodeElGamalPrivateKey(key, nodeKeys.encryptionPublicKey());
        ElGamalPrivateKey key2 = codec.decodeElGamalPrivateKey(bytes, nodeKeys.encryptionPrivateKey());
        assertEquals(key, key2);
    }

    @Test(expected = KeyManagementException.class)
    public void testDecodeElGamalPrivateKeyInvalidBytes() throws GeneralCryptoLibException, KeyManagementException {
        codec.decodeElGamalPrivateKey(new byte[257], nodeKeys.encryptionPrivateKey());
    }

    @Test
    public void testDecodeElGamalPublicKey() throws GeneralCryptoLibException, KeyManagementException {
        ElGamalKeyPair pair = elGamalService.getElGamalKeyPairGenerator().generateKeys(parameters, 3);
        ElGamalPublicKey key = pair.getPublicKeys();
        byte[] bytes = codec.encodeElGamalPublicKey(key);
        ElGamalPublicKey key2 = codec.decodeElGamalPublicKey(bytes);
        assertEquals(key, key2);
    }

    @Test(expected = KeyManagementException.class)
    public void testDecodeElGamalPublicKeyInvalidBytes() throws GeneralCryptoLibException, KeyManagementException {
        byte[] bytes = {1, 2, 3, 4 };
        codec.decodeElGamalPublicKey(bytes);
    }

    @Test
    public void testDecodeNodeKeys() throws KeyManagementException {
        byte[] bytes = codec.encodeNodeKeys(nodeKeys, pwd);
        NodeKeys nodeKeys2 = codec.decodeNodeKeys(bytes, pwd);
        assertEquals(nodeKeys.caPrivateKey(), nodeKeys2.caPrivateKey());
        assertArrayEquals(nodeKeys.caCertificateChain(), nodeKeys2.caCertificateChain());
        assertEquals(nodeKeys.encryptionPrivateKey(), nodeKeys2.encryptionPrivateKey());
        assertArrayEquals(nodeKeys.encryptionCertificateChain(), nodeKeys2.encryptionCertificateChain());
        assertEquals(nodeKeys.logEncryptionPrivateKey(), nodeKeys2.logEncryptionPrivateKey());
        assertArrayEquals(nodeKeys.logEncryptionCertificateChain(), nodeKeys2.logEncryptionCertificateChain());
        assertEquals(nodeKeys.logSigningPrivateKey(), nodeKeys2.logSigningPrivateKey());
        assertArrayEquals(nodeKeys.logSigningCertificateChain(), nodeKeys2.logSigningCertificateChain());
    }

    @Test(expected = KeyManagementException.class)
    public void testDecodeNodeKeysInvalidBytes() throws KeyManagementException {
        byte[] bytes = {1, 2, 3 };
        codec.decodeNodeKeys(bytes, pwd);
    }

    @Test(expected = InvalidPasswordException.class)
    public void testDecodeNodeKeysInvalidPassword() throws KeyManagementException {
        byte[] bytes = codec.encodeNodeKeys(nodeKeys, pwd);
        codec.decodeNodeKeys(bytes, new PasswordProtection("pwdpwd2".toCharArray()));
    }

    @Test
    public void testDecodePassword() throws KeyManagementException {
        char[] password = pwd.getPassword();
        char[] guardedPassword = new char[password.length];
        System.arraycopy(password, 0, guardedPassword, 0, password.length);
        byte[] bytes = codec.encodePassword(pwd, nodeKeys.encryptionPublicKey());
        PasswordProtection password2 = codec.decodePassword(bytes, nodeKeys.encryptionPrivateKey());
        assertArrayEquals(guardedPassword, password2.getPassword());
    }

    @Test(expected = KeyManagementException.class)
    public void testDecodePasswordInvalidBytes() throws KeyManagementException {
        byte[] bytes = new byte[257];
        codec.decodePassword(bytes, nodeKeys.encryptionPrivateKey());
    }
}
