/*
 * 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.certificates.utils;

import java.security.KeyPair;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.List;

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.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.certificates.CertificatesServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.cryptolib.certificates.bean.CertificateData;
import com.scytl.cryptolib.certificates.bean.RootCertificateData;
import com.scytl.cryptolib.certificates.bean.ValidityDates;
import com.scytl.cryptolib.certificates.bean.X509CertificateType;
import com.scytl.cryptolib.certificates.bean.X509DistinguishedName;
import com.scytl.cryptolib.certificates.cryptoapi.CryptoAPIX509Certificate;
import com.scytl.cryptolib.certificates.service.CertificatesService;

public class X509CertificateChainValidatorValidationTest {

    private static final long gap = 1000l;

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

    @BeforeClass
    public static void setUp() throws GeneralCryptoLibException {
        Security.addProvider(new BouncyCastleProvider());
        _certificateService = new CertificatesService();

        _asymmetricService = new AsymmetricService();

        setup();
    }

    private static AsymmetricServiceAPI _asymmetricService;

    private static CertificatesServiceAPI _certificateService;

    private static KeyPair _intermediate11Keys;

    private static CryptoAPIX509Certificate _intermediate11x509Certificate;

    private static CertificateData _intermediate12CertificateData;

    private static KeyPair _intermediate12Keys;

    private static CryptoAPIX509Certificate _intermediate12x509Certificate;

    private static CertificateData _intermediate1CertificateData;

    private static CertificateData _intermediate21CertificateData;

    private static KeyPair _intermediate21Keys;

    private static CryptoAPIX509Certificate _intermediate21x509Certificate;

    private static CryptoAPIX509Certificate _root1AuthorityX509Certificate;

    private static RootCertificateData _root1CertificateData;

    private static KeyPair _root1Keys;

    private static CryptoAPIX509Certificate _root2AuthorityX509Certificate;

    private static RootCertificateData _root2CertificateData;

    private static KeyPair _root2Keys;

    private static X509CertificateChainValidator _target;

    private static KeyPair _oneKeys;

    private static KeyPair _otherKeys;

    private static final int _okTimeReference = 5;

    /**
     * @throws GeneralCryptoLibException
     */
    public X509CertificateChainValidatorValidationTest()
            throws GeneralCryptoLibException {

    }

    @Test
    public void create2LayerCertificatesExpiredTest()
            throws GeneralCryptoLibException {

        X509Certificate[] chain = null;
        X509DistinguishedName[] subjects = null;
        int timeReference = 1000;
        create2LayerCertificates(chain, subjects, timeReference);

        List<String> lst = _target.validate();
        Assert.assertTrue(!lst.isEmpty());
        Assert.assertArrayEquals(new String[] {"Time" },
            lst.toArray(new String[0]));
    }

    @Test
    public void create2LayerCertificatesOKNullArraysTest()
            throws GeneralCryptoLibException {
        X509Certificate[] chain = null;
        X509DistinguishedName[] subjects = null;

        List<String> lst =
            create2LayerCertificates(chain, subjects, _okTimeReference);
        Assert.assertTrue(lst.isEmpty());
    }

    @Test
    public void create2LayerCertificatesOKEmptyArraysTest()
            throws GeneralCryptoLibException {
        X509Certificate[] chain = new X509Certificate[0];
        X509DistinguishedName[] subjects = new X509DistinguishedName[0];
        List<String> lst =
            create2LayerCertificates(chain, subjects, _okTimeReference);
        Assert.assertTrue(lst.isEmpty());
    }

    private List<String> create2LayerCertificates(
            final X509Certificate[] chain,
            final X509DistinguishedName[] subjects, final int timeReference)
            throws GeneralCryptoLibException {

        KeyPair leafKeys = _oneKeys;

        CertificateData leafCertificateData =
            createLeafCertificateData("11", leafKeys.getPublic(), 1, 10,
                _root1AuthorityX509Certificate.getSubjectDn());

        CryptoAPIX509Certificate leafX509Certificate =
            _certificateService.createSignX509Certificate(
                leafCertificateData, _root1Keys.getPrivate());

        X509CertificateType leafKeyType = X509CertificateType.SIGN;

        _target =
            new X509CertificateChainValidator(
                leafX509Certificate.getCertificate(), leafKeyType,
                leafX509Certificate.getSubjectDn(), new Date(timeReference
                    * gap), chain, subjects,
                _root1AuthorityX509Certificate.getCertificate());
        List<String> validate = _target.validate();
        return validate;
    }

    @Test
    public void twoLayerInvalidOtherIssuer()
            throws GeneralCryptoLibException {

        KeyPair leafKeys = _oneKeys;

        CertificateData leafCertificateData =
            createLeafCertificateData("11", leafKeys.getPublic(), 1, 10,
                _root1AuthorityX509Certificate.getSubjectDn());

        CryptoAPIX509Certificate leafX509Certificate =
            _certificateService.createSignX509Certificate(
                leafCertificateData, _root2Keys.getPrivate());

        X509CertificateType leafKeyType = X509CertificateType.SIGN;

        final X509Certificate[] chain = null;
        final X509DistinguishedName[] subjects = null;

        X509CertificateChainValidator validator =
            new X509CertificateChainValidator(
                leafX509Certificate.getCertificate(), leafKeyType,
                leafX509Certificate.getSubjectDn(), new Date(
                    _okTimeReference * gap), chain, subjects,
                _root1AuthorityX509Certificate.getCertificate());
        List<String> lst = validator.validate();
        Assert.assertArrayEquals(new String[] {"SIGNATURE_0" },
            lst.toArray(new String[0]));

    }

    @Test
    public void twoLayerInvalidOtherSubjectNameIssuer()
            throws GeneralCryptoLibException {

        KeyPair leafKeys = _oneKeys;

        CertificateData leafCertificateData =
            createLeafCertificateData("11", leafKeys.getPublic(), 1, 10,
                _root2AuthorityX509Certificate.getSubjectDn());

        CryptoAPIX509Certificate leafX509Certificate =
            _certificateService.createSignX509Certificate(
                leafCertificateData, _root1Keys.getPrivate());

        X509CertificateType leafKeyType = X509CertificateType.SIGN;

        final X509Certificate[] chain = null;
        final X509DistinguishedName[] subjects = null;

        X509CertificateChainValidator validator =
            new X509CertificateChainValidator(
                leafX509Certificate.getCertificate(), leafKeyType,
                leafX509Certificate.getSubjectDn(), new Date(
                    _okTimeReference * gap), chain, subjects,
                _root1AuthorityX509Certificate.getCertificate());
        List<String> lst = validator.validate();
        Assert.assertArrayEquals(new String[] {"ISSUER_0" },
            lst.toArray(new String[0]));

    }

    @Test
    public void twoLayerInvalidSubjectName()
            throws GeneralCryptoLibException {

        KeyPair leafKeys = _oneKeys;

        CertificateData leafCertificateData =
            createLeafCertificateData("11", leafKeys.getPublic(), 1, 10,
                _root1AuthorityX509Certificate.getSubjectDn());

        CryptoAPIX509Certificate leafX509Certificate =
            _certificateService.createSignX509Certificate(
                leafCertificateData, _root1Keys.getPrivate());

        X509CertificateType leafKeyType = X509CertificateType.SIGN;

        final X509Certificate[] chain = null;
        final X509DistinguishedName[] subjects = null;

        X509CertificateChainValidator validator =
            new X509CertificateChainValidator(
                leafX509Certificate.getCertificate(), leafKeyType,
                _root1AuthorityX509Certificate.getSubjectDn(), new Date(
                    _okTimeReference * gap), chain, subjects,
                _root1AuthorityX509Certificate.getCertificate());
        List<String> lst = validator.validate();
        Assert.assertArrayEquals(new String[] {"SUBJECT_0" },
            lst.toArray(new String[0]));

    }

    @Test
    public void twoLayerInvalidKeyType() throws GeneralCryptoLibException {

        KeyPair leafKeys = _oneKeys;

        CertificateData leafCertificateData =
            createLeafCertificateData("11", leafKeys.getPublic(), 1, 10,
                _root1AuthorityX509Certificate.getSubjectDn());

        CryptoAPIX509Certificate leafX509Certificate =
            _certificateService.createSignX509Certificate(
                leafCertificateData, _root1Keys.getPrivate());

        X509CertificateType leafKeyType = X509CertificateType.ENCRYPT;

        final X509Certificate[] chain = null;
        final X509DistinguishedName[] subjects = null;

        X509CertificateChainValidator validator =
            new X509CertificateChainValidator(
                leafX509Certificate.getCertificate(), leafKeyType,
                leafX509Certificate.getSubjectDn(), new Date(
                    _okTimeReference * gap), chain, subjects,
                _root1AuthorityX509Certificate.getCertificate());
        List<String> lst = validator.validate();
        Assert.assertArrayEquals(new String[] {"KEY_TYPE_0" },
            lst.toArray(new String[0]));
    }

    @Test
    public void twoLayerNoKeyType() throws GeneralCryptoLibException {

        KeyPair leafKeys = _oneKeys;

        CertificateData leafCertificateData =
            createLeafCertificateData("11", leafKeys.getPublic(), 1, 10,
                _root1AuthorityX509Certificate.getSubjectDn());

        CryptoAPIX509Certificate leafX509Certificate =
            X509CertificateTestDataGenerator
                .getX509CertificateWithNoKeyUsage(leafCertificateData,
                    _root1Keys);

        X509CertificateType leafKeyType = X509CertificateType.ENCRYPT;

        final X509Certificate[] chain = null;
        final X509DistinguishedName[] subjects = null;

        X509CertificateChainValidator validator =
            new X509CertificateChainValidator(
                leafX509Certificate.getCertificate(), leafKeyType,
                leafX509Certificate.getSubjectDn(), new Date(
                    _okTimeReference * gap), chain, subjects,
                _root1AuthorityX509Certificate.getCertificate());
        List<String> lst = validator.validate();
        Assert.assertArrayEquals(new String[] {"KEY_TYPE_0" },
            lst.toArray(new String[0]));
    }

    @Test
    public void twoLayerCaNoKeyType() throws GeneralCryptoLibException {

        CertificateData intermediate1CertificateData =
            createIntermediateCertificateData("11",
                _intermediate11Keys.getPublic(), 1, 10,
                _root1AuthorityX509Certificate.getSubjectDn());

        CryptoAPIX509Certificate leafX509Certificate =
            X509CertificateTestDataGenerator
                .getCertificateAuthorityX509CertificateWithNoKeyUsage(
                    intermediate1CertificateData, _root1Keys);

        X509CertificateType leafKeyType =
            X509CertificateType.CERTIFICATE_AUTHORITY;

        final X509Certificate[] chain = null;
        final X509DistinguishedName[] subjects = null;

        X509CertificateChainValidator validator =
            new X509CertificateChainValidator(
                leafX509Certificate.getCertificate(), leafKeyType,
                leafX509Certificate.getSubjectDn(), new Date(
                    _okTimeReference * gap), chain, subjects,
                _root1AuthorityX509Certificate.getCertificate());
        List<String> lst = validator.validate();
        Assert.assertArrayEquals(new String[] {"KEY_TYPE_0" },
            lst.toArray(new String[0]));
    }

    private static CertificateData createIntermediateCertificateData(
            final String identifier, final PublicKey key,
            final int notBefore, final int notAfter,
            final X509DistinguishedName issuer)
            throws GeneralCryptoLibException {
        CertificateData certificateData = new CertificateData();
        X509DistinguishedName subjectDn =
            new X509DistinguishedName.Builder("root" + identifier + "CN",
                "CA").addOrganization("O").addOrganizationalUnit("OU")
                .build();
        certificateData.setSubjectDn(subjectDn);
        certificateData.setIssuerDn(issuer);
        certificateData.setSubjectPublicKey(key);
        ValidityDates validityDates =
            new ValidityDates(new Date(notBefore * gap), new Date(notAfter
                * gap));
        certificateData.setValidityDates(validityDates);
        return certificateData;
    }

    private CertificateData createLeafCertificateData(
            final String identifier, final PublicKey key,
            final int notBefore, final int notAfter,
            final X509DistinguishedName issuer)
            throws GeneralCryptoLibException {

        CertificateData certificateData = new CertificateData();
        X509DistinguishedName leafSubjectDn =
            new X509DistinguishedName.Builder("leafCN", "CA")
                .addOrganization("O").addOrganizationalUnit("OU").build();

        certificateData.setIssuerDn(issuer);
        certificateData.setSubjectDn(leafSubjectDn);
        certificateData.setSubjectPublicKey(key);
        ValidityDates leafValidityDates =
            new ValidityDates(new Date(notBefore * gap), new Date(notAfter
                * gap));
        certificateData.setValidityDates(leafValidityDates);
        return certificateData;
    }

    private static RootCertificateData createRootCertificateData(
            final String identifier, final PublicKey key,
            final int notBefore, final int notAfter)
            throws GeneralCryptoLibException {
        RootCertificateData certificateData = new RootCertificateData();
        X509DistinguishedName subjectDn =
            new X509DistinguishedName.Builder("root" + identifier + "CN",
                "CA").addOrganization("O").addOrganizationalUnit("OU")
                .build();
        certificateData.setSubjectDn(subjectDn);
        certificateData.setSubjectPublicKey(key);
        ValidityDates validityDates =
            new ValidityDates(new Date(notBefore * gap), new Date(notAfter
                * gap));
        certificateData.setValidityDates(validityDates);
        return certificateData;
    }

    private static void setup() throws GeneralCryptoLibException {

        _root1Keys = _asymmetricService.getKeyPairForSigning();
        _root1CertificateData =
            createRootCertificateData("1", _root1Keys.getPublic(), 0, 100);
        _root1AuthorityX509Certificate =
            _certificateService.createRootAuthorityX509Certificate(
                _root1CertificateData, _root1Keys.getPrivate());

        _root2Keys = _asymmetricService.getKeyPairForSigning();
        _root2CertificateData =
            createRootCertificateData("2", _root2Keys.getPublic(), 50, 150);
        _root2AuthorityX509Certificate =
            _certificateService.createRootAuthorityX509Certificate(
                _root2CertificateData, _root2Keys.getPrivate());

        _intermediate11Keys = _asymmetricService.getKeyPairForSigning();
        _intermediate1CertificateData =
            createIntermediateCertificateData("11",
                _intermediate11Keys.getPublic(), 25, 50,
                _root1AuthorityX509Certificate.getSubjectDn());
        _intermediate11x509Certificate =
            _certificateService
                .createIntermediateAuthorityX509Certificate(
                    _intermediate1CertificateData, _root1Keys.getPrivate());

        _intermediate12Keys = _asymmetricService.getKeyPairForSigning();
        _intermediate12CertificateData =
            createIntermediateCertificateData("11",
                _intermediate12Keys.getPublic(), 25, 150,
                _root1AuthorityX509Certificate.getSubjectDn());
        _intermediate12x509Certificate =
            _certificateService
                .createIntermediateAuthorityX509Certificate(
                    _intermediate1CertificateData, _root1Keys.getPrivate());

        _intermediate21Keys = _asymmetricService.getKeyPairForSigning();
        _intermediate21CertificateData =
            createIntermediateCertificateData("11",
                _intermediate21Keys.getPublic(), 0, 150,
                _root1AuthorityX509Certificate.getSubjectDn());
        _intermediate21x509Certificate =
            _certificateService
                .createIntermediateAuthorityX509Certificate(
                    _intermediate1CertificateData, _root2Keys.getPrivate());

        _oneKeys = _asymmetricService.getKeyPairForSigning();
        _otherKeys = _asymmetricService.getKeyPairForSigning();

    }
}
