/*
 * 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.security.PublicKey;
import java.security.cert.Certificate;
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.CertificateChainRepository;
import com.scytl.products.ov.commons.crypto.InputDataFormatterService;
import com.scytl.products.ov.commons.crypto.SignatureForObjectService;
import com.scytl.products.ov.commons.logging.service.I18nLoggerMessages;
import com.scytl.products.ov.commons.tracking.TrackIdInstance;

/**
 * This class implements the expiration time validation of the server challenge message signature.
 */
public class ChallengeServerInformationSignatureValidation 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 SignatureForObjectService signatureService;

    private static final String KEYSTORE_ALIAS = "privatekey";

    private static final String CERT_ALIAS = "AuthTokenSigner ";

    @Inject
    private CertificateChainRepository certificateChainRepository;

    @Inject
    private TrackIdInstance trackId;

    @Inject
    private SecureLoggingWriter secureLoggerWriter;

    private static final int RULE_ORDER = 3;

    private int order;

    public ChallengeServerInformationSignatureValidation() {
        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;
        try {

            String serverSignature = challengeInformation.getServerChallengeMessage().getSignature();
            // get the certificate chain for tenant and election event for a given alias
            Certificate[] certChain =
                certificateChainRepository.findByTenantEEIDAlias(tenantId, electionEventId, KEYSTORE_ALIAS);

            // get public key
            PublicKey serverPublicKey =
                signatureService.getPublicKeyByAliasInCertificateChain(certChain, CERT_ALIAS + electionEventId);

            // verify server challenge signature
            byte[] serverChallengeDataToSignInput = inputDataFormatterService.concatenate(
                challengeInformation.getServerChallengeMessage().getServerChallenge(),
                challengeInformation.getServerChallengeMessage().getTimestamp(), electionEventId,
                challengeInformation.getCredentialId());

            // verify signature
            byte[] serverSignatureBytes = Base64.getDecoder().decode(serverSignature);
            result = asymmetricService.verifySignature(serverSignatureBytes, serverPublicKey,
                new byte[][] {serverChallengeDataToSignInput });
        } catch (CryptographicOperationException | GeneralCryptoLibException e) {
            secureLoggerWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder().logEvent(AuthenticationLogEvents.SERVER_CHALLENGE_SIGNATURE_INVALID)
                    .objectId(credentialId).user(credentialId).electionEvent(electionEventId)
                    .additionalInfo(AuthenticationLogConstants.INFO_TRACK_ID, trackId.getTrackId())
                    .additionalInfo(AuthenticationLogConstants.INFO_ERR_DESC,
                        "Invalid server challenge signature: " + ExceptionUtils.getRootCauseMessage(e))
                    .createLogInfo());

            throw new CryptographicOperationException("An error occured verifying signature:", e);
        }

        LOG.info(I18N.getMessage("ChallengeInformationSignatureValidation.execute.verifyServerChallengeSignatureOK"),
            result);

        // server challenge signature not valid
        if (!result) {
            secureLoggerWriter.log(Level.ERROR,
                new LogContent.LogContentBuilder().logEvent(AuthenticationLogEvents.SERVER_CHALLENGE_SIGNATURE_INVALID)
                    .objectId(credentialId).user(credentialId).electionEvent(electionEventId)
                    .additionalInfo(AuthenticationLogConstants.INFO_TRACK_ID, trackId.getTrackId())
                    .additionalInfo(AuthenticationLogConstants.INFO_ERR_DESC, "Invalid server challenge signature")
                    .createLogInfo());
        }
        return result;

    }

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