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

import static junitparams.JUnitParamsRunner.$;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.Certificate;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.AfterClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.primitives.securerandom.constants.SecureRandomConstants;
import com.scytl.cryptolib.test.tools.bean.TestPrivateKey;
import com.scytl.cryptolib.test.tools.bean.TestPublicKey;
import com.scytl.cryptolib.test.tools.utils.CommonTestDataGenerator;

import junitparams.JUnitParamsRunner;
import junitparams.Parameters;

/**
 * Tests of the PEM utilities input validation.
 */
@RunWith(JUnitParamsRunner.class)
public class PemUtilsValidationTest {

    private static final String RSA_PRIVATE_KEY_WITH_NO_HEADER_PATH =
        "/pem/rsaPrivateKeyWithNoHeader.pem";

    private static final String RSA_PUBLIC_KEY_WITH_NO_HEADER_PATH =
        "/pem/rsaPublicKeyWithNoHeader.pem";

    private static final String X509_CERTIFICATE_WITH_NO_HEADER_PATH =
        "/pem/x509CertificateWithNoHeader.pem";

    private static final String RSA_PRIVATE_KEY_WITH_NO_FOOTER_PATH =
        "/pem/rsaPrivateKeyWithNoFooter.pem";

    private static final String RSA_PUBLIC_KEY_WITH_NO_FOOTER_PATH =
        "/pem/rsaPublicKeyWithNoFooter.pem";

    private static final String X509_CERTIFICATE_WITH_NO_FOOTER_PATH =
        "/pem/x509CertificateWithNoFooter.pem";

    private static final String RSA_PRIVATE_KEY_WITH_BAD_HEADER_PATH =
        "/pem/rsaPrivateKeyWithBadHeader.pem";

    private static final String RSA_PUBLIC_KEY_WITH_BAD_HEADER_PATH =
        "/pem/rsaPublicKeyWithBadHeader.pem";

    private static final String X509_CERTIFICATE_WITH_BAD_HEADER_PATH =
        "/pem/x509CertificateWithBadHeader.pem";

    private static final String RSA_PRIVATE_KEY_WITH_BAD_FOOTER_PATH =
        "/pem/rsaPrivateKeyWithBadFooter.pem";

    private static final String RSA_PUBLIC_KEY_WITH_BAD_FOOTER_PATH =
        "/pem/rsaPublicKeyWithBadFooter.pem";

    private static final String X509_CERTIFICATE_WITH_BAD_FOOTER_PATH =
        "/pem/x509CertificateWithBadFooter.pem";

    private static final String CORRUPT_RSA_PRIVATE_KEY_PATH =
        "/pem/corruptRsaPrivateKey.pem";

    private static final String CORRUPT_RSA_PUBLIC_KEY_PATH =
        "/pem/corruptRsaPublicKey.pem";

    private static final String CORRUPT_X509_CERTIFICATE_PATH =
        "/pem/corruptX509Certificate.pem";

    private static String _whiteSpaceString;

    static {
        try {
            setUp();
        } catch (GeneralCryptoLibException e) {
            throw new CryptoLibException("Setup failed for class "
                + PemUtilsValidationTest.class.getSimpleName());
        }
    }

    public static void setUp() throws GeneralCryptoLibException {

        Security.addProvider(new BouncyCastleProvider());

        _whiteSpaceString =
            CommonTestDataGenerator
                .getWhiteSpaceString(CommonTestDataGenerator.getInt(1,
                    SecureRandomConstants.MAXIMUM_GENERATED_STRING_LENGTH));
    }

    @AfterClass
    public static void tearDown() {

        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    @Parameters(method = "convertPublicKeyToPem")
    public void testPublicKeyToPemConversionValidation(PublicKey key,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        PemUtils.publicKeyToPem(key);
    }

    public static Object[] convertPublicKeyToPem() {

        return $(
            $(null, "Public key is null."),
            $(new TestPublicKey(null), "Public key content is null."),
            $(new TestPublicKey(new byte[0]),
                "Public key content is empty."));
    }

    @Test
    @Parameters(method = "convertPrivateKeyToPem")
    public void testPrivateKeyToPemConversionValidation(PrivateKey key,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        PemUtils.privateKeyToPem(key);
    }

    public static Object[] convertPrivateKeyToPem() {

        return $(
            $(null, "Private key is null."),
            $(new TestPrivateKey(null), "Private key content is null."),
            $(new TestPrivateKey(new byte[0]),
                "Private key content is empty."));
    }

    @Test
    @Parameters(method = "convertCertificateToPem")
    public void testCertificateToPemConversionValidation(Certificate cert,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        PemUtils.certificateToPem(cert);
    }

    public static Object[] convertCertificateToPem()
            throws GeneralCryptoLibException, IOException {

        return $($(null, "Certificate is null."));
    }

    @Test
    @Parameters(method = "getPublicKeyFromPem")
    public void testPublicKeyFromPemRetrievalValidation(String pemStr,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        PemUtils.publicKeyFromPem(pemStr);
    }

    public static Object[] getPublicKeyFromPem() throws IOException {

        String noHeaderPem = loadPem(RSA_PUBLIC_KEY_WITH_NO_HEADER_PATH);

        String noFooterPem = loadPem(RSA_PUBLIC_KEY_WITH_NO_FOOTER_PATH);

        String badHeaderPem = loadPem(RSA_PUBLIC_KEY_WITH_BAD_HEADER_PATH);

        String badFooterPem = loadPem(RSA_PUBLIC_KEY_WITH_BAD_FOOTER_PATH);

        String corruptPem = loadPem(CORRUPT_RSA_PUBLIC_KEY_PATH);

        return $(
            $(null, "Public key PEM string is null."),
            $(new String(), "Public key PEM string is blank."),
            $(_whiteSpaceString, "Public key PEM string is blank."),
            $(noHeaderPem,
                "Public key PEM string does not contain valid header."),
            $(noFooterPem,
                "Public key PEM string does not contain valid footer."),
            $(badHeaderPem,
                "Public key PEM string does not contain valid header."),
            $(badFooterPem,
                "Public key PEM string does not contain valid footer."),
            $(corruptPem, "Could not convert PEM string "));
    }

    @Test
    @Parameters(method = "getPrivateKeyFromPem")
    public void testPrivateKeyFromPemRetrievalValidation(String pemStr,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        PemUtils.privateKeyFromPem(pemStr);
    }

    public static Object[] getPrivateKeyFromPem() throws IOException {

        String noHeaderPem = loadPem(RSA_PRIVATE_KEY_WITH_NO_HEADER_PATH);

        String noFooterPem = loadPem(RSA_PRIVATE_KEY_WITH_NO_FOOTER_PATH);

        String badHeaderPem = loadPem(RSA_PRIVATE_KEY_WITH_BAD_HEADER_PATH);

        String badFooterPem = loadPem(RSA_PRIVATE_KEY_WITH_BAD_FOOTER_PATH);

        String corruptPem = loadPem(CORRUPT_RSA_PRIVATE_KEY_PATH);

        return $(
            $(null, "Private key PEM string is null."),
            $(new String(), "Private key PEM string is blank."),
            $(_whiteSpaceString, "Private key PEM string is blank."),
            $(noHeaderPem,
                "Private key PEM string does not contain valid header."),
            $(noFooterPem,
                "Private key PEM string does not contain valid footer."),
            $(badHeaderPem,
                "Private key PEM string does not contain valid header."),
            $(badFooterPem,
                "Private key PEM string does not contain valid footer."),
            $(corruptPem, "Could not convert PEM string "));
    }

    @Test
    @Parameters(method = "getCertificateFromPem")
    public void testCertificateFromPemRetrievalValidation(String pemStr,
            String errorMsg) throws GeneralCryptoLibException {

        thrown.expect(GeneralCryptoLibException.class);
        thrown.expectMessage(errorMsg);
        PemUtils.certificateFromPem(pemStr);
    }

    public static Object[] getCertificateFromPem() throws IOException {

        String noHeaderPem = loadPem(X509_CERTIFICATE_WITH_NO_HEADER_PATH);

        String noFooterPem = loadPem(X509_CERTIFICATE_WITH_NO_FOOTER_PATH);

        String badHeaderPem =
            loadPem(X509_CERTIFICATE_WITH_BAD_HEADER_PATH);

        String badFooterPem = loadPem(X509_CERTIFICATE_WITH_BAD_FOOTER_PATH);

        String corruptPem = loadPem(CORRUPT_X509_CERTIFICATE_PATH);

        return $(
            $(null, "Certificate PEM string is null."),
            $(new String(), "Certificate PEM string is blank."),
            $(_whiteSpaceString, "Certificate PEM string is blank."),
            $(noHeaderPem,
                "Certificate PEM string does not contain valid header."),
            $(noFooterPem,
                "Certificate PEM string does not contain valid footer."),
            $(badHeaderPem,
                "Certificate PEM string does not contain valid header."),
            $(badFooterPem,
                "Certificate PEM string does not contain valid footer."),
            $(corruptPem, "Could not convert PEM string "));
    }
    
    private static String loadPem(String resource) throws IOException {
        StringBuilder pem = new StringBuilder();
        try (InputStream stream =
            PemUtilsValidationTest.class.getResourceAsStream(resource);
                Reader reader = new InputStreamReader(stream,
                    StandardCharsets.UTF_8);) {
            int c;
            while ((c = reader.read()) != -1) {
                pem.append((char) c);
            }
        }
        return pem.toString();
    }
}
