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

import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Arrays;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import com.scytl.crypto.destroy.SecretKeyDestroyer;
import com.scytl.shares.Share;
import com.scytl.shares.ShareException;
import com.scytl.shares.SharesCrypto;

/**
 * Note: this class is based on the class EncryptedShare from the Shares library. This class serves the same purpose for
 * MultipleSharesContainer as EncryptedShare does for Share.
 */
public class EncryptedMultipleSharesContainer {

    // private part
    private transient byte[] secretKeyBytes;

    // public part
    private final transient byte[] encryptedShare;

    // public part
    private final transient byte[] encryptedShareSignature;

    // cryptographic helper
    private final SharesCrypto sharesCrypto = new SharesCrypto();

    /**
     * Constructor in decryption mode: The information is retrieved from the smartcard, and this class will be used to
     * validate and decrypt the share.
     * <p>
     * The constructor will verify the signature before finishing, and throw a {@link ShareException} if the signature
     * is not correct. See {@link #decrypt(byte[])}.
     *
     * @param encryptedShare
     *            : byte[] with the encrypted share.
     * @param encryptedShareSignature
     *            byte[] with a signature over the encryptedShare
     * @param boardPublic
     *            : Public key to verify the encryptedShareSignature
     * @throws ShareException
     *             if the signature is not correct
     */
    public EncryptedMultipleSharesContainer(final byte[] encryptedShare, final byte[] encryptedShareSignature,
            final PublicKey boardPublic) throws ShareException {
        this.encryptedShare = clone(encryptedShare);
        this.encryptedShareSignature = clone(encryptedShareSignature);
        if (!sharesCrypto.verifyShare(this.encryptedShare, this.encryptedShareSignature, boardPublic)) {
            throw new ShareException("This share does not belong to this board");
        }
    }
    
    /**
     * Constructor in encryption mode: The information to encrypt is provided in the {@link Share} object, and the
     * {@link PrivateKey} is used to encrypt it. The constructor generates a {@link SecretKey} which is used to encrypt
     * the {@link Share} content.
     * <p>
     * It is the responsibility of the caller to control the {@link PrivateKey} and {@link Share} life cycles, which
     * contain sensitive information. This method will not make any copy of them.
     *
     * @param share
     * @param boardPrivate
     */
    public EncryptedMultipleSharesContainer(final Share share, final PrivateKey boardPrivate) {
        SecretKeyDestroyer secretKeyDestroyer = new SecretKeyDestroyer();
        SecretKey shareSecretKey = sharesCrypto.generateSecretKey();
        secretKeyBytes = sharesCrypto.serializeSecretKey(shareSecretKey);
        encryptedShare = sharesCrypto.encryptShare(share.serialize(), shareSecretKey);
        secretKeyDestroyer.destroyInstances((SecretKeySpec) shareSecretKey);
        encryptedShareSignature = sharesCrypto.signShare(encryptedShare, boardPrivate);
    }
    
    /**
     * Use the secret key serialized form to decrypt the content of the share.
     * <p>
     * The caller is responsible for the secret key life cycle. This method will not make any copies of it.
     *
     * @param secretKeyBytes
     *            The serialized secret key.
     * @return The decrypted {@link Share} if the key is correct.
     * @throws ShareException
     *             if the share cannot be decoded from the provided byte[]
     */
    public MultipleSharesContainer decrypt(final byte[] secretKeyBytes) throws ShareException {
        if (secretKeyBytes == null) {
            return null;
        }
        return new MultipleSharesContainer(sharesCrypto.decryptShare(encryptedShare, secretKeyBytes));
    }

    
    private byte[] clone(final byte[] value) {
        if (value == null) {
            return null;
        } else {
            return value.clone();
        }
    }

    /**
     * Overwrite all the object attributes with 0x00 in memory. This method must always be called in order to guarantee
     * no memory analysis can reveal the secret.
     */
    public void destroy() {
        if (secretKeyBytes != null) {
            Arrays.fill(secretKeyBytes, (byte) 0x00);
        }
        if (encryptedShare != null) {
            Arrays.fill(encryptedShare, (byte) 0x00);
        }
        if (encryptedShareSignature != null) {
            Arrays.fill(encryptedShareSignature, (byte) 0x00);
        }
    }

    /**
     * WARNING: This value is what keeps the share secret. Only use it to store in the cryptographic token, and on the
     * private part. Do not store, make copies, or log this value ever.
     *
     * @return Returns the secretKeyBytes.
     */
    public byte[] getSecretKeyBytes() {
        return secretKeyBytes.clone();
    }

    /**
     * Return the encrypted share content as a byte[].
     *
     * @return Returns the encryptedShare.
     */
    public byte[] getEncryptedShare() {
        return encryptedShare.clone();
    }

    /**
     * Return the encrypted share signature as a byte[].
     * 
     * @return Returns the encryptedShareSignature.
     */
    public byte[] getEncryptedShareSignature() {
        return encryptedShareSignature.clone();
    }
}
