/*
 * 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.au.services.domain.service.challenge;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Base64;

import javax.inject.Inject;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.scytl.cryptolib.api.asymmetric.AsymmetricServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.products.oscore.logging.api.domain.Level;
import com.scytl.products.oscore.logging.api.domain.LogContent;
import com.scytl.products.oscore.logging.api.writer.SecureLoggingWriter;
import com.scytl.products.ov.au.services.domain.model.challenge.ChallengeInformation;
import com.scytl.products.ov.au.services.domain.model.validation.ChallengeInformationValidation;
import com.scytl.products.ov.au.services.infrastructure.log.AuthenticationLogConstants;
import com.scytl.products.ov.au.services.infrastructure.log.AuthenticationLogEvents;
import com.scytl.products.ov.commons.beans.exceptions.ResourceNotFoundException;
import com.scytl.products.ov.commons.beans.validation.CryptographicOperationException;
import com.scytl.products.ov.commons.crypto.InputDataFormatterService;
import com.scytl.products.ov.commons.logging.service.I18nLoggerMessages;
import com.scytl.products.ov.commons.tracking.TrackIdInstance;

/**
 * This class implements the  validation of the server challenge message signature.
 */
public class ChallengeClientInformationSignatureValidation implements ChallengeInformationValidation {

	private static final Logger LOG = LoggerFactory.getLogger("std");

    private static final I18nLoggerMessages I18N = I18nLoggerMessages.getInstance();

    @Inject
    private AsymmetricServiceAPI asymmetricService;

    @Inject
    private InputDataFormatterService inputDataFormatterService;

    @Inject
    private TrackIdInstance trackId;

    @Inject
    private SecureLoggingWriter secureLoggerWriter;

    private static final int RULE_ORDER = 2;

    private int order;

    public ChallengeClientInformationSignatureValidation() {
        this.order = RULE_ORDER;
    }

    /**
     * This method implements the validation of challenge signature verification of both client and server challenge
     * signature.
     *
     * @param tenantId
     *            - the tenant identifier.
     * @param electionEventId
     *            - the election event identifier.
     * @param credentialId
     *            - the voting card identifier.
     * @param challengeInformation
     *            - the challenge information to be validated.
     * @return true, if successfully validated. Otherwise, false.
     * @throws ResourceNotFoundException
     *             if the certificate chain source can not be found.
     * @throws CryptographicOperationException
     *             if an error occurs during the certificate chain location or retrieval.
     */
    @Override
    public boolean execute(final String tenantId, final String electionEventId, final String credentialId,
            final ChallengeInformation challengeInformation)
            throws ResourceNotFoundException, CryptographicOperationException {

        boolean result = false;

        try {

            // X509 certificate
            String certificateString = challengeInformation.getCertificate();
            InputStream inputStream = new ByteArrayInputStream(certificateString.getBytes(StandardCharsets.UTF_8));
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            X509Certificate certificate = (X509Certificate) cf.generateCertificate(inputStream);
            PublicKey publicKey = certificate.getPublicKey();
            LOG.info(I18N.getMessage("ChallengeInformationSignatureValidation.execute.publicKeyOK"), tenantId,
                electionEventId, credentialId);

            // verify client challenge signature
            String clientSignature = challengeInformation.getClientChallengeMessage().getSignature();
            String serverSignature = challengeInformation.getServerChallengeMessage().getSignature();
            byte[] clientChallengeDataToVerify = inputDataFormatterService.concatenate(serverSignature,
                challengeInformation.getClientChallengeMessage().getClientChallenge());
            byte[] clientSignatureBytes = Base64.getDecoder().decode(clientSignature);
            if (asymmetricService.verifySignature(clientSignatureBytes, publicKey, clientChallengeDataToVerify)) {
                LOG.info(I18N
                    .getMessage("ChallengeInformationSignatureValidation.execute.verifyClientChallengeSignatureOK"));


                result = true;
            } else {
                LOG.info(I18N.getMessage(
                    "ChallengeInformationSignatureValidation.execute.verifyClientChallengeSignatureFailed"));

                secureLoggerWriter.log(Level.ERROR,
                    new LogContent.LogContentBuilder()
                        .logEvent(AuthenticationLogEvents.CLIENT_CHALLENGE_SIGNATURE_INVALID).objectId(credentialId)
                        .user(credentialId).electionEvent(electionEventId)
                        .additionalInfo(AuthenticationLogConstants.INFO_TRACK_ID, trackId.getTrackId())
                        .additionalInfo(AuthenticationLogConstants.INFO_ERR_DESC, "Invalid client challenge signature")
                        .createLogInfo());


            }
        } catch (CertificateException | GeneralCryptoLibException e) {
            secureLoggerWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder().logEvent(AuthenticationLogEvents.CLIENT_CERTIFICATE_INVALID)
                    .objectId(credentialId).user(credentialId).electionEvent(electionEventId)
                    .additionalInfo(AuthenticationLogConstants.INFO_TRACK_ID, trackId.getTrackId())
                    .additionalInfo(AuthenticationLogConstants.INFO_ERR_DESC,
                        "Invalid client certificate: " + ExceptionUtils.getRootCauseMessage(e))
                    .createLogInfo());
            throw new CryptographicOperationException("An error occured encoding a certificate object:", e);
        }
        return result;
    }

    @Override
    public int getOrder() {
        return order;
    }
}
