/*
 * 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.digital.envelope.factory;

import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Base64;

import javax.crypto.SecretKey;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.cipher.factory.AsymmetricCipherFactory;
import com.scytl.cryptolib.asymmetric.cipher.factory.CryptoAsymmetricCipher;
import com.scytl.cryptolib.commons.binary.ByteArrays;
import com.scytl.cryptolib.digital.envelope.bean.SecretKeyEncryption;
import com.scytl.cryptolib.digital.envelope.configuration.DigitalEnvelopePolicy;
import com.scytl.cryptolib.symmetric.cipher.factory.CryptoSymmetricCipher;
import com.scytl.cryptolib.symmetric.cipher.factory.SymmetricCipherFactory;
import com.scytl.cryptolib.symmetric.key.factory.CryptoSecretKeyGeneratorForEncryption;
import com.scytl.cryptolib.symmetric.key.factory.CryptoSecretKeyGeneratorForHmac;
import com.scytl.cryptolib.symmetric.key.factory.SecretKeyGeneratorFactory;
import com.scytl.cryptolib.symmetric.mac.factory.CryptoMac;
import com.scytl.cryptolib.symmetric.mac.factory.MacFactory;

/**
 * Defines a digital envelope generator.
 * <P>
 * Instances of this class are immutable.
 */
public class CryptoDigitalEnvelopeGenerator {

    private final CryptoSecretKeyGeneratorForEncryption _secretKeyGeneratorForEncryption;

    private final CryptoSecretKeyGeneratorForHmac _secretKeyGeneratorForMac;

    private final CryptoMac _macGenerator;

    private final CryptoSymmetricCipher _symmetricCipher;

    private final CryptoAsymmetricCipher _asymmetricCipher;

    /**
     * Creates an instance of a digital envelope, using the given cryptographic
     * policy.
     * 
     * @param digitalEnvelopePolicy
     *            the digital envelope cryptographic policy.
     */
    CryptoDigitalEnvelopeGenerator(
            final DigitalEnvelopePolicy digitalEnvelopePolicy) {

        SecretKeyGeneratorFactory secretKeyGeneratorFactory =
            new SecretKeyGeneratorFactory(digitalEnvelopePolicy);
        _secretKeyGeneratorForEncryption =
            secretKeyGeneratorFactory.createGeneratorForEncryption();
        _secretKeyGeneratorForMac =
            secretKeyGeneratorFactory.createGeneratorForHmac();

        _macGenerator = new MacFactory(digitalEnvelopePolicy).create();

        _symmetricCipher =
            new SymmetricCipherFactory(digitalEnvelopePolicy).create();

        _asymmetricCipher =
            new AsymmetricCipherFactory(digitalEnvelopePolicy).create();
    }

    /**
     * Generates a digital envelope for some data.
     * 
     * @param data
     *            the data to store in the digital envelope.
     * @param publicKeys
     *            the one or more public keys, of type
     *            {@link java.security.PublicKey}, used to generate the digital
     *            envelope.
     * @return the newly created digital envelope, as an object of type
     *         {@link CryptoDigitalEnvelope} .
     * @throws GeneralCryptoLibException
     *             if the data to store is invalid, one or more of the public
     *             keys is invalid or an error occurs when generating the
     *             digital envelope.
     */
    public CryptoDigitalEnvelope generate(final byte[] data,
            final PublicKey... publicKeys)
            throws GeneralCryptoLibException {

        // Generate encryption secret key and use it to symmetrically encrypt
        // data.
        SecretKey encryptionSecretKey =
            _secretKeyGeneratorForEncryption.genSecretKey();
        byte[] encryptedData =
            _symmetricCipher.encrypt(encryptionSecretKey, data);

        // Generate MAC secret key and use it to generate MAC of Base64 encoding
        // of symmetrically encrypted data.
        SecretKey macSecretKey = _secretKeyGeneratorForMac.genSecretKey();
        byte[] encryptedDataBase64 = Base64.getEncoder().encode(encryptedData);
        byte[] mac =
            _macGenerator.generate(macSecretKey, encryptedDataBase64);

        // Construct bit-wise concatenation of encryption and MAC secret keys.
        byte[] secretKeyConcatenation = ByteArrays.concatenate(
            encryptionSecretKey.getEncoded(), macSecretKey.getEncoded());

        // Generate list of asymmetric encryptions of the secret key
        // concatenation, each encryption created using a different one of the
        // public keys provided as input for the digital envelope generation.
        ArrayList<SecretKeyEncryption> secretKeyEncryptions =
            new ArrayList<SecretKeyEncryption>();
        for (PublicKey publicKey : publicKeys) {
            byte[] encryptedSecretKeyConcatenation =
                _asymmetricCipher.encrypt(publicKey,
                    secretKeyConcatenation);
            secretKeyEncryptions.add(new SecretKeyEncryption(
                encryptedSecretKeyConcatenation, publicKey));
        }

        return new CryptoDigitalEnvelope(encryptedData, mac,
            secretKeyEncryptions);
    }
}
