/*
 * 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.Security;
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.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.utils.AsymmetricTestDataGenerator;
import com.scytl.cryptolib.certificates.bean.CertificateData;
import com.scytl.cryptolib.certificates.bean.X509CertificateType;
import com.scytl.cryptolib.certificates.bean.X509DistinguishedName;
import com.scytl.cryptolib.certificates.configuration.X509CertificateValidationData;
import com.scytl.cryptolib.certificates.configuration.X509CertificateValidationResult;
import com.scytl.cryptolib.certificates.configuration.X509CertificateValidationType;
import com.scytl.cryptolib.certificates.factory.CryptoX509Certificate;

/**
 * Tests of X509 certificate validator for negative cases.
 */
public class X509CertificateValidatorNegativeTest {

    private static KeyPair _rootKeyPair;

    private static CryptoX509Certificate _rootCertificate;

    private static X509DistinguishedName _rootSubjectDn;

    private static Date _validDate;

    private static KeyPair _keyPair;

    private static X509DistinguishedName _subjectDn;

    private static Date _invalidDate;

    private static X509CertificateValidationType[] _validationTypes;

    @BeforeClass
    public static void setUp()
            throws GeneralCryptoLibException {

        Security.addProvider(new BouncyCastleProvider());

        _rootKeyPair = AsymmetricTestDataGenerator.getKeyPairForSigning();
        _rootCertificate =
            (CryptoX509Certificate) X509CertificateTestDataGenerator
                .getRootAuthorityX509Certificate(_rootKeyPair);
        _rootSubjectDn =
            X509CertificateTestDataGenerator.getRootDistinguishedName();
        _validDate =
            X509CertificateTestDataGenerator.getDateWithinValidityPeriod();

        _keyPair =
            AsymmetricTestDataGenerator
                .getUniqueKeyPairForSigning(_rootKeyPair);
        _subjectDn =
            X509CertificateTestDataGenerator.getDistinguishedName();
        _invalidDate =
            X509CertificateTestDataGenerator
                .getDateOutsideValidityPeriod();

        _validationTypes = new X509CertificateValidationType[5];
        _validationTypes[0] = X509CertificateValidationType.DATE;
        _validationTypes[1] = X509CertificateValidationType.SUBJECT;
        _validationTypes[2] = X509CertificateValidationType.ISSUER;
        _validationTypes[3] = X509CertificateValidationType.SIGNATURE;
        _validationTypes[4] = X509CertificateValidationType.KEY_TYPE;

        X509CertificateTestDataGenerator.getDateWithinValidityPeriod();
    }

    @AfterClass
    public static void tearDown() {

        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

    @Test
    public void checkWhenDateInvalidThenCertificateValidationFails()
            throws GeneralCryptoLibException {

        X509CertificateValidationData validationData =
            new X509CertificateValidationData.Builder()
                .addDate(_invalidDate).addSubjectDn(_rootSubjectDn)
                .addIssuerDn(_rootSubjectDn)
                .addKeyType(X509CertificateType.CERTIFICATE_AUTHORITY)
                .addCaPublicKey(_rootKeyPair.getPublic()).build();

        X509CertificateValidator validator =
            new X509CertificateValidator(_rootCertificate, validationData,
                _validationTypes);

        X509CertificateValidationResult validationResult =
            validator.validate();

        boolean isValidated = validationResult.isValidated();
        int numFailedValidations =
            validationResult.getFailedValidationTypes().size();
        X509CertificateValidationType failedValidationType =
            validationResult.getFailedValidationTypes().get(0);

        Assert
            .assertFalse(
                "Unexpected validation result. expected:<failure> but was:<success>",
                isValidated);
        Assert.assertEquals("Unexpected number of failed validations.", 1,
            numFailedValidations);
        Assert.assertEquals("Unexpected failed validation type",
            X509CertificateValidationType.DATE, failedValidationType);
    }

    @Test
    public void checkWhenSubjectDnInvalidThenCertificateValidationFails()
            throws GeneralCryptoLibException {

        X509CertificateValidationData validationData =
            new X509CertificateValidationData.Builder()
                .addDate(_validDate).addSubjectDn(_subjectDn)
                .addIssuerDn(_rootSubjectDn)
                .addKeyType(X509CertificateType.CERTIFICATE_AUTHORITY)
                .addCaPublicKey(_rootKeyPair.getPublic()).build();

        X509CertificateValidator validator =
            new X509CertificateValidator(_rootCertificate, validationData,
                _validationTypes);

        X509CertificateValidationResult validationResult =
            validator.validate();

        boolean isValidated = validationResult.isValidated();
        int numFailedValidations =
            validationResult.getFailedValidationTypes().size();
        X509CertificateValidationType failedValidationType =
            validationResult.getFailedValidationTypes().get(0);

        Assert
            .assertFalse(
                "Unexpected validation result. expected:<failure> but was:<success>",
                isValidated);
        Assert.assertEquals("Unexpected number of failed validations.", 1,
            numFailedValidations);
        Assert.assertEquals("Unexpected failed validation type",
            X509CertificateValidationType.SUBJECT, failedValidationType);
    }

    @Test
    public void checkWhenIssuerDnInvalidThenCertificateValidationFails()
            throws GeneralCryptoLibException {

        X509CertificateValidationData validationData =
            new X509CertificateValidationData.Builder()
                .addDate(_validDate).addSubjectDn(_rootSubjectDn)
                .addIssuerDn(_subjectDn)
                .addKeyType(X509CertificateType.CERTIFICATE_AUTHORITY)
                .addCaPublicKey(_rootKeyPair.getPublic()).build();

        X509CertificateValidator validator =
            new X509CertificateValidator(_rootCertificate, validationData,
                _validationTypes);

        X509CertificateValidationResult validationResult =
            validator.validate();

        boolean isValidated = validationResult.isValidated();
        int numFailedValidations =
            validationResult.getFailedValidationTypes().size();
        X509CertificateValidationType failedValidationType =
            validationResult.getFailedValidationTypes().get(0);

        Assert
            .assertFalse(
                "Unexpected validation result. expected:<failure> but was:<success>",
                isValidated);
        Assert.assertEquals("Unexpected number of failed validations.", 1,
            numFailedValidations);
        Assert.assertEquals("Unexpected failed validation type",
            X509CertificateValidationType.ISSUER, failedValidationType);
    }

    @Test
    public void checkWhenCaPublicKeyInvalidThenCertificateValidationFails()
            throws GeneralCryptoLibException {

        X509CertificateValidationData validationData =
            new X509CertificateValidationData.Builder()
                .addDate(_validDate).addSubjectDn(_rootSubjectDn)
                .addIssuerDn(_rootSubjectDn)
                .addKeyType(X509CertificateType.CERTIFICATE_AUTHORITY)
                .addCaPublicKey(_keyPair.getPublic()).build();

        X509CertificateValidator validator =
            new X509CertificateValidator(_rootCertificate, validationData,
                _validationTypes);

        X509CertificateValidationResult validationResult =
            validator.validate();

        boolean isValidated = validationResult.isValidated();
        int numFailedValidations =
            validationResult.getFailedValidationTypes().size();
        X509CertificateValidationType failedValidationType =
            validationResult.getFailedValidationTypes().get(0);

        Assert
            .assertFalse(
                "Unexpected validation result. expected:<failure> but was:<success>",
                isValidated);
        Assert.assertEquals("Unexpected number of failed validations.", 1,
            numFailedValidations);
        Assert.assertEquals("Unexpected failed validation type",
            X509CertificateValidationType.SIGNATURE, failedValidationType);
    }

    @Test
    public void checkWhenKeyUsageInvalidThenCertificateValidationFails()
            throws GeneralCryptoLibException {

        X509CertificateValidationData validationData =
            new X509CertificateValidationData.Builder()
                .addDate(_validDate).addSubjectDn(_rootSubjectDn)
                .addIssuerDn(_rootSubjectDn)
                .addKeyType(X509CertificateType.SIGN)
                .addCaPublicKey(_rootKeyPair.getPublic()).build();

        X509CertificateValidator validator =
            new X509CertificateValidator(_rootCertificate, validationData,
                _validationTypes);

        X509CertificateValidationResult validationResult =
            validator.validate();

        boolean isValidated = validationResult.isValidated();
        int numFailedValidations =
            validationResult.getFailedValidationTypes().size();
        X509CertificateValidationType failedValidationType =
            validationResult.getFailedValidationTypes().get(0);

        Assert
            .assertFalse(
                "Unexpected validation result. expected:<failure> but was:<success>",
                isValidated);
        Assert.assertEquals("Unexpected number of failed validations.", 1,
            numFailedValidations);
        Assert.assertEquals("Unexpected failed validation type",
            X509CertificateValidationType.KEY_TYPE, failedValidationType);
    }

    @Test
    public void checkWhenKeyUsageNotSetThenCertificateValidationFails()
            throws GeneralCryptoLibException {

        CertificateData certificateData =
            X509CertificateTestDataGenerator.getCertificateData(_keyPair);

        CryptoX509Certificate certificateWithNoKeyUsage =
            (CryptoX509Certificate) X509CertificateTestDataGenerator
                .getX509CertificateWithNoKeyUsage(certificateData,
                    _rootKeyPair);

        X509CertificateValidationData validationData =
            new X509CertificateValidationData.Builder().addKeyType(
                X509CertificateType.SIGN).build();

        X509CertificateValidationType[] validationTypes =
            new X509CertificateValidationType[1];
        validationTypes[0] = X509CertificateValidationType.KEY_TYPE;

        X509CertificateValidator validator =
            new X509CertificateValidator(certificateWithNoKeyUsage,
                validationData, validationTypes);

        X509CertificateValidationResult validationResult =
            validator.validate();

        boolean isValidated = validationResult.isValidated();
        int numFailedValidations =
            validationResult.getFailedValidationTypes().size();
        X509CertificateValidationType failedValidationType =
            validationResult.getFailedValidationTypes().get(0);

        Assert
            .assertFalse(
                "Unexpected validation result. expected:<failure> but was:<success>",
                isValidated);
        Assert.assertEquals("Unexpected number of failed validations.", 1,
            numFailedValidations);
        Assert.assertEquals("Unexpected failed validation type",
            X509CertificateValidationType.KEY_TYPE, failedValidationType);
    }

    @Test
    public void checkWhenKeyUsageNotSetThenCertificateAuthorityCertificateValidationFails()
            throws GeneralCryptoLibException {

        CertificateData certificateData =
            X509CertificateTestDataGenerator
                .getCertificateData(_rootKeyPair);

        CryptoX509Certificate certificateWithNoKeyUsage =
            (CryptoX509Certificate) X509CertificateTestDataGenerator
                .getCertificateAuthorityX509CertificateWithNoKeyUsage(
                    certificateData, _rootKeyPair);

        X509CertificateValidationData validationData =
            new X509CertificateValidationData.Builder().addKeyType(
                X509CertificateType.SIGN).build();

        X509CertificateValidationType[] validationTypes =
            new X509CertificateValidationType[1];
        validationTypes[0] = X509CertificateValidationType.KEY_TYPE;

        X509CertificateValidator validator =
            new X509CertificateValidator(certificateWithNoKeyUsage,
                validationData, validationTypes);

        X509CertificateValidationResult validationResult =
            validator.validate();

        boolean isValidated = validationResult.isValidated();
        int numFailedValidations =
            validationResult.getFailedValidationTypes().size();
        X509CertificateValidationType failedValidationType =
            validationResult.getFailedValidationTypes().get(0);

        Assert
            .assertFalse(
                "Unexpected validation result. expected:<failure> but was:<success>",
                isValidated);
        Assert.assertEquals("Unexpected number of failed validations.", 1,
            numFailedValidations);
        Assert.assertEquals("Unexpected failed validation type",
            X509CertificateValidationType.KEY_TYPE, failedValidationType);
    }

    @Test
    public void checkWhenAllDataIsInvalidThenAllCertificateValidationFails()
            throws GeneralCryptoLibException {

        X509CertificateValidationData validationData =
            new X509CertificateValidationData.Builder()
                .addDate(_invalidDate).addSubjectDn(_subjectDn)
                .addIssuerDn(_subjectDn)
                .addKeyType(X509CertificateType.SIGN)
                .addCaPublicKey(_keyPair.getPublic()).build();

        X509CertificateValidator validator =
            new X509CertificateValidator(_rootCertificate, validationData,
                _validationTypes);

        X509CertificateValidationResult validationResult =
            validator.validate();

        boolean isValidated = validationResult.isValidated();
        List<X509CertificateValidationType> failedValidationTypes =
            validationResult.getFailedValidationTypes();
        int numFailedValidations = failedValidationTypes.size();

        Assert
            .assertFalse(
                "Unexpected validation result. expected:<failure> but was:<success>",
                isValidated);
        Assert.assertEquals("Unexpected number of failed validations.",
            _validationTypes.length, numFailedValidations);
        Assert.assertTrue(
            "Could not find expected failed validation type <DATE>",
            failedValidationTypes
                .contains(X509CertificateValidationType.DATE));
        Assert.assertTrue(
            "Could not find expected failed validation type <SUBJECT>",
            failedValidationTypes
                .contains(X509CertificateValidationType.SUBJECT));
        Assert.assertTrue(
            "Could not find expected failed validation type <ISSUER>",
            failedValidationTypes
                .contains(X509CertificateValidationType.ISSUER));
        Assert.assertTrue(
            "Could not find expected failed validation type <SIGNATURE>",
            failedValidationTypes
                .contains(X509CertificateValidationType.SIGNATURE));
        Assert.assertTrue(
            "Could not find expected failed validation type <KEY_TYPE>",
            failedValidationTypes
                .contains(X509CertificateValidationType.KEY_TYPE));
    }
}
