/*
 * 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.config.tenant.service;

import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.primitives.PrimitivesServiceAPI;
import com.scytl.cryptolib.certificates.bean.X509CertificateType;
import com.scytl.cryptolib.certificates.bean.X509DistinguishedName;
import com.scytl.cryptolib.certificates.factory.CryptoX509Certificate;
import com.scytl.cryptolib.certificates.utils.X509CertificateChainValidator;
import com.scytl.cryptolib.scytl.keystore.cryptoapi.CryptoAPIScytlKeyStore;
import com.scytl.cryptolib.securerandom.cryptoapi.CryptoAPIRandomBytes;
import com.scytl.products.ov.config.tenant.ValidationException;

@Service
public class ValidationService {

    private static final int RANDOM_BYTES_LENGTH = 10;

    private final AsymmetricServiceAPI asymmetricServiceAPI;

    private final PrimitivesServiceAPI primitivesServiceAPI;

    @Autowired
    public ValidationService(AsymmetricServiceAPI asymmetricService, PrimitivesServiceAPI primitivesService) {
        this.asymmetricServiceAPI = asymmetricService;
        this.primitivesServiceAPI = primitivesService;
    }

    /**
     * Validates a signed certificate with a keystore using a encrypt/decrypt comparison
     * 
     * @param original
     *            - original certificate
     * @param keyStore
     *            - keystore with a chain of certificates
     * @param alias
     *            - alias of the privatekey
     * @param password
     *            - password of the keystore
     * @throws GeneralCryptoLibException
     * @throws ValidationException
     */
    public void validateKeysInCertificates(Certificate original, CryptoAPIScytlKeyStore keyStore, String alias,
            char[] password) throws GeneralCryptoLibException, ValidationException {

        PublicKey publicKeyOriginal = original.getPublicKey();

        final CryptoAPIRandomBytes cryptoRandomBytes = primitivesServiceAPI.getCryptoRandomBytes();
        // get the randombytes
        byte[] bytesToEncrypt = cryptoRandomBytes.nextRandom(RANDOM_BYTES_LENGTH);

        byte[] bytesEncryptedOriginal = asymmetricServiceAPI.encrypt(publicKeyOriginal, bytesToEncrypt);

        byte[] bytesEncryptedKeystore =
            asymmetricServiceAPI.encrypt(keyStore.getCertificateChain(alias)[0].getPublicKey(), bytesToEncrypt);

        byte[] bytesDecryptedOriginal =
            asymmetricServiceAPI.decrypt(keyStore.getPrivateKeyEntry(alias, password), bytesEncryptedOriginal);
        byte[] bytesDecryptedByKeystore =
            asymmetricServiceAPI.decrypt(keyStore.getPrivateKeyEntry(alias, password), bytesEncryptedKeystore);

        String decryptedOriginalString = new String(bytesDecryptedOriginal, StandardCharsets.UTF_8);
        String decryptedKeystoreString = new String(bytesDecryptedByKeystore, StandardCharsets.UTF_8);

        if (!decryptedOriginalString.equals(decryptedKeystoreString)) {
            throw new ValidationException("The keystore has been wrongly generated");
        }

    }

    /**
     * Validates the generated chain of certificates
     * 
     * @param rootCertificate
     *            - plattformRootCA
     * @param tenantCertificate
     *            - tenantCA
     * @throws ValidationException
     * @throws GeneralCryptoLibException
     * @throws CertificateException
     */
    public void validateChain(Certificate rootCertificate, Certificate tenantCertificate)
            throws ValidationException, GeneralCryptoLibException, CertificateException {

        // Trusted certificate -> Plattform Root CA
        // Get X509 certificate
        X509Certificate plattformRootCA = (X509Certificate) rootCertificate;
        X509DistinguishedName distinguishedPlatformRootCA = getDistinguishName(plattformRootCA);

        // Root vs Root chain validation
        X509Certificate[] certificateChainRoot = {};
        X509DistinguishedName[] subjectDnsRoot = {};

        // Validate
        if (!validateCert(plattformRootCA, distinguishedPlatformRootCA, X509CertificateType.CERTIFICATE_AUTHORITY,
            certificateChainRoot, subjectDnsRoot, plattformRootCA)) {
            throw new ValidationException("The chain of certificates hasn't been validated correctly");
        }

        X509Certificate tenantCA = (X509Certificate) tenantCertificate;
        X509DistinguishedName subjectDnTenantCA = getDistinguishName(tenantCA);

        X509Certificate[] certificateChain = {};
        X509DistinguishedName[] subjectDns = {};

        // Validate
        if (!validateCert(tenantCA, subjectDnTenantCA, X509CertificateType.CERTIFICATE_AUTHORITY, certificateChain,
            subjectDns, plattformRootCA)) {
            throw new ValidationException("The chain of certificates hasn't been validated correctly");
        }
    }

    /**
     * Validates the generated certificates
     *
     * @param rootCertificate
     *            - plattformRootCA
     * @throws ValidationException
     * @throws GeneralCryptoLibException
     * @throws CertificateException
     */
    public Boolean validateCertificate(Certificate rootCertificate)
            throws ValidationException, GeneralCryptoLibException, CertificateException {

        // Trusted certificate -> Plattform Root CA
        // Get X509 certificate
        X509Certificate plattformRootCA = (X509Certificate) rootCertificate;
        X509DistinguishedName distinguishedPlatformRootCA = getDistinguishName(plattformRootCA);

        // Root vs Root chain validation
        X509Certificate[] certificateChainRoot = {};
        X509DistinguishedName[] subjectDnsRoot = {};

        // Validate
        return validateCert(plattformRootCA, distinguishedPlatformRootCA, X509CertificateType.CERTIFICATE_AUTHORITY,
            certificateChainRoot, subjectDnsRoot, plattformRootCA);

    }

    /**
     * Create a new instance of a X509CertificateChainValidator.
     *
     * @param certLeaf
     *            - the leaf certificate.
     * @param subjectDnsLeaf
     *            - the leaf certificate subject distinguished name.
     * @param certType
     *            - the leaf certificate type.
     * @param certChain
     *            - the certificate chain of the leaf certificate.
     * @param subjectDns
     *            - the subject distinguished names of the certificate chain.
     * @param certTrusted
     *            - the trusted certificate.
     * @return X509CertificateChainValidator.
     * @throws GeneralCryptoLibException
     *             if fails trying to create a new object.
     */
    private X509CertificateChainValidator createCertificateChainValidator(X509Certificate certLeaf,
            X509DistinguishedName subjectDnsLeaf, X509CertificateType certType, X509Certificate[] certChain,
            X509DistinguishedName[] subjectDns, X509Certificate certTrusted) throws GeneralCryptoLibException {

        return new X509CertificateChainValidator(certLeaf, certType, subjectDnsLeaf, certChain, subjectDns,
            certTrusted);
    }

    private X509DistinguishedName getDistinguishName(X509Certificate x509Cert) throws GeneralCryptoLibException {
        CryptoX509Certificate wrappedCertificate = new CryptoX509Certificate(x509Cert);
        return wrappedCertificate.getSubjectDn();
    }

    private boolean validateCert(X509Certificate certLeaf, X509DistinguishedName subjectDnsLeaf,
            X509CertificateType certType, X509Certificate[] certChain, X509DistinguishedName[] subjectDns,
            X509Certificate certTrusted) throws GeneralCryptoLibException {
        X509CertificateChainValidator certificateChainValidator =
            createCertificateChainValidator(certLeaf, subjectDnsLeaf, certType, certChain, subjectDns, certTrusted);
        List<String> failedValidations = certificateChainValidator.validate();
        return failedValidations == null || failedValidations.isEmpty();
    }

}
