/*
 * 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.PublicKey;
import java.util.Arrays;

import com.scytl.shares.ShareException;
import com.scytl.token.TokenOps;

import iaik.pkcs.pkcs11.Session;
import iaik.pkcs.pkcs11.Token;
import iaik.pkcs.pkcs11.TokenException;
import iaik.pkcs.pkcs11.objects.Data;

/**
 * This class extends TokenOps (from the Shares library). It was necessary to extend that class to allow for the reading
 * of serialized MultipleSharesContainer objects from smartcards. Note: an instance of TokenOps can write a serialized
 * MultipleSharesContainer object to a smartcard, but it is not able to read one again (because in the reading operation
 * a instance of the new read object is created).
 * <P>
 * A refactor should be considered to create a single reading and writing mechanism that supports all of the required
 * data.
 * <P>
 * Operations on a {@link Token}. This class deals with saving and recovering a share from a {@link Token}, as well as
 * recovering the number of retries until the PIN or PUK is locked.
 */
public class TokenOpsForMultipleSharesContainer extends TokenOps {

    /**
     * Creates a {@link Data} object to use as a template to search {@link Data} associated to a given label.
     *
     * @param label
     *            The label to which the {@link Data} is associated.
     * @param privateSection
     *            True if the object searched is in the private section of the token, false otherwise.
     * @return A template {@link Data} object for the given label and privateness.
     */
    private Data createSearchTemplate(final char[] label, final boolean privateSection) {
        // create certificate object template
        Data dataObjectTemplate = new Data();

        // Set the name that manages this data object
        dataObjectTemplate.getApplication().setCharArrayValue("Shares API".toCharArray());

        // set the data object's label
        dataObjectTemplate.getLabel().setCharArrayValue(label);

        // set the data as private
        dataObjectTemplate.getPrivate().setBooleanValue(privateSection);

        // ensure that it is stored on the token and not just in this
        // session
        dataObjectTemplate.getToken().setBooleanValue(Boolean.TRUE);

        // do not allow to modify. Can delete.
        dataObjectTemplate.getModifiable().setBooleanValue(Boolean.FALSE);
        return dataObjectTemplate;
    }

    /**
     * Recover a single object (the first one) matching the template from the session. Extracts the byte[] content of
     * that object to return it.
     *
     * @param session
     * @param templateData
     * @return
     * @throws TokenException
     */
    private byte[] recoverData(final Session session, final Data templateData) throws TokenException {
        session.findObjectsInit(templateData);
        try {
            Object[] objects = session.findObjects(1);
            return extractBytes(objects);
        } finally {
            session.findObjectsFinal();
        }
    }

    /**
     * Extract the byte[] content of one {@link Data} object that is the first and only content of the given
     * {@link Object} array.
     *
     * @param objects
     * @return
     */
    private byte[] extractBytes(final Object[] objects) {
        Object obj;
        if (objects == null || objects.length != 1) {
            throw new IllegalArgumentException(
                "Expected 1 object, but found " + (objects == null ? 0 : objects.length));
        } else {
        	obj = objects[0];
        }
        if (obj instanceof Data) {
            Data data = (Data) obj;
            return data.getValue().getByteArrayValue();
        } else {
            throw new IllegalArgumentException("Expected object of type Data, but found " + obj.getClass());
        }
    }

    /**
     * Can fail if bad pin, share does not contain expected labels, wrong signature, general token failures.
     *
     * @param token
     * @param pin
     * @param boardPublic
     * @return
     * @throws TokenException
     * @throws ShareException
     */
    @Override
    public MultipleSharesContainer readShare(final Token token, final char[] pin, final PublicKey boardPublic)
            throws TokenException, ShareException {

        // get session to read data from token.
        Session sessionRO =
            token.openSession(Token.SessionType.SERIAL_SESSION, Token.SessionReadWriteBehavior.RO_SESSION, null, null);

        byte[] encryptedShare = recoverData(sessionRO, createSearchTemplate(ENCRYPTED_SHARE_LABEL, Boolean.FALSE));
        byte[] encryptedShareSignature =
            recoverData(sessionRO, createSearchTemplate(SHARE_SIGNATURE_LABEL, Boolean.FALSE));

        EncryptedMultipleSharesContainer es =
            new EncryptedMultipleSharesContainer(encryptedShare, encryptedShareSignature, boardPublic);
        byte[] secretKeyBytes = null;
        try {
            sessionRO.login(Session.UserType.USER, pin);
            secretKeyBytes = recoverData(sessionRO, createSearchTemplate(SECRET_KEY_SHARE_LABEL, Boolean.TRUE));
            return es.decrypt(secretKeyBytes);
        } finally {
            es.destroy();
            if (secretKeyBytes != null) {
                Arrays.fill(secretKeyBytes, (byte) 0x00);
            }
            sessionRO.logout();
            sessionRO.closeSession();
        }
    }
}
