/*
 * 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.commons.verify;

import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.certificates.bean.X509CertificateType;
import com.scytl.cryptolib.certificates.bean.X509DistinguishedName;
import com.scytl.cryptolib.certificates.utils.X509CertificateChainValidator;
import com.scytl.products.ov.commons.beans.validation.CertificateTools;
import com.scytl.products.ov.commons.beans.validation.CryptographicOperationException;

public class CryptolibPayloadSigningCertificateValidator implements PayloadSigningCertificateValidator {

    private List<String> errors = new ArrayList<>();

    @Override
    public boolean isValid(X509Certificate[] certificateChain, X509Certificate trustedCertificate)
            throws CertificateChainValidationException {
        // Shortcut for obviously wrong certificate chains.
        if (certificateChain.length == 0) {
            errors.add("Empty certificate chain");
            return false;
        }

        // Measure the intermediate certificate chain (i.e. minus the leaf
        // certificate).
        int intermediateCertificateChainLength = certificateChain.length - 1;
        // Build the intermediate certificate chain.
        X509Certificate[] intermediateCertificateChain = new X509Certificate[intermediateCertificateChainLength];
        System.arraycopy(certificateChain, 1, intermediateCertificateChain, 0, intermediateCertificateChainLength);
        // Get the intermediate certificate subject DNs.
        X509DistinguishedName[] intermediateCertificateSubjectDNs = Stream.of(intermediateCertificateChain)
            .map(CryptolibPayloadSigningCertificateValidator::getDNFromX509Certificate)
            .toArray(X509DistinguishedName[]::new);
        // Verify the certificate chain against the root certificate.
        X509Certificate leafCertificate = certificateChain[0];

        X509CertificateChainValidator certificateChainValidator;
        try {
            certificateChainValidator = new X509CertificateChainValidator(leafCertificate, X509CertificateType.SIGN,
                getDNFromX509Certificate(leafCertificate), intermediateCertificateChain,
                intermediateCertificateSubjectDNs, trustedCertificate);
            errors = certificateChainValidator.validate();

            return errors.isEmpty();
        } catch (GeneralCryptoLibException e) {
            throw new CertificateChainValidationException(e);
        }
    }

    /**
     * Extract an X509 distinguished name from an X509 certificate which,
     * contrary to what the class names might suggest, is not trivial.
     *
     * @param certificate
     *            the X509 certificate
     * @return the X509-compliant distinguished name
     */
    private static X509DistinguishedName getDNFromX509Certificate(X509Certificate certificate) {
        try {
            return CertificateTools.getCryptoX509Certificate(certificate).getSubjectDn();
        } catch (CryptographicOperationException e) {
            throw new IllegalArgumentException("The certificate could not be converted", e);
        }
    }

    @Override
    public Collection<String> getErrors() {
        return errors;
    }

}
