/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package javax.crypto;

import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;
import org.apache.harmony.security.fortress.Engine;

/**
 * This class implements the functionality of an exemption mechanism such as
 * <i>key recovery</i>, <i>key weakening</i>, or <i>key escrow</i>.
 */
public class ExemptionMechanism {

    // Used to access common engine functionality
    private static final Engine ENGINE = new Engine("ExemptionMechanism");

    // Store used provider
    private final Provider provider;

    // Store used spi implementation
    private final ExemptionMechanismSpi spiImpl;

    // Store mechanism name
    private final String mechanism;

    // Store state (initialized or not)
    private boolean isInit;

    // Store initKey value
    private Key initKey;

    // Indicates if blob generated successfully
    private boolean generated;

    /**
     * Creates a {@code ExemptionMechanism} instance.
     *
     * @param exmechSpi
     *            the implementation delegate.
     * @param provider
     *            the associated provider.
     * @param mechanism
     *            the name of the mechanism.
     */
    protected ExemptionMechanism(ExemptionMechanismSpi exmechSpi,
            Provider provider, String mechanism) {
        this.mechanism = mechanism;
        this.spiImpl = exmechSpi;
        this.provider = provider;
        isInit = false;
    }

    /**
     * Returns the name of this {@code ExemptionMechanism}.
     *
     * @return the name of this {@code ExemptionMechanism}.
     */
    public final String getName() {
        return mechanism;
    }

    /**
     * Returns a new {@code ExemptionMechanism} instance that provides the
     * specified exemption mechanism algorithm.
     *
     * @param algorithm
     *            the name of the requested exemption mechanism.
     * @return the new {@code ExemptionMechanism} instance.
     * @throws NoSuchAlgorithmException
     *             if the specified algorithm is not available by any provider.
     * @throws NullPointerException
     *             if the algorithm parameter is {@code null}.
     */
    public static final ExemptionMechanism getInstance(String algorithm)
            throws NoSuchAlgorithmException {
        if (algorithm == null) {
            throw new NullPointerException("algorithm == null");
        }
        Engine.SpiAndProvider sap = ENGINE.getInstance(algorithm, null);
        return new ExemptionMechanism((ExemptionMechanismSpi) sap.spi, sap.provider, algorithm);
    }

    /**
     * Returns a new {@code ExemptionMechansm} instance that provides the
     * specified exemption mechanism algorithm from the specified provider.
     *
     * @param algorithm
     *            the name of the requested exemption mechanism.
     * @param provider
     *            the name of the provider that is providing the algorithm.
     * @return the new {@code ExemptionMechanism} instance.
     * @throws NoSuchAlgorithmException
     *             if the specified algorithm is not provided by the specified
     *             provider.
     * @throws NoSuchProviderException
     *             if the specified provider is not available.
     * @throws NullPointerException
     *             if the algorithm parameter is {@code null}.
     * @throws IllegalArgumentException
     *             if the provider parameter is {@code null}.
     */
    public static final ExemptionMechanism getInstance(String algorithm,
            String provider) throws NoSuchAlgorithmException,
            NoSuchProviderException {
        if (provider == null) {
            throw new IllegalArgumentException("provider == null");
        }
        Provider impProvider = Security.getProvider(provider);
        if (impProvider == null) {
            throw new NoSuchProviderException(provider);
        }
        if (algorithm == null) {
            throw new NullPointerException("algorithm == null");
        }
        return getInstance(algorithm, impProvider);
    }

    /**
     * Returns a new {@code ExemptionMechanism} instance that provides the
     * specified exemption mechanism algorithm from the specified provider.
     *
     * @param algorithm
     *            the name of the requested exemption mechanism.
     * @param provider
     *            the provider that is providing the algorithm.
     * @return the new {@code ExemptionMechanism} instance.
     * @throws NoSuchAlgorithmException
     *             if the specified algorithm is not provided by the specified
     *             provider.
     * @throws NullPointerException
     *             if the algorithm parameter is {@code null}.
     * @throws IllegalArgumentException
     *             if the provider parameter is {@code null}.
     */
    public static final ExemptionMechanism getInstance(String algorithm,
            Provider provider) throws NoSuchAlgorithmException {
        if (algorithm == null) {
            throw new NullPointerException("algorithm == null");
        }
        if (provider == null) {
            throw new IllegalArgumentException("provider == null");
        }
        Object spi = ENGINE.getInstance(algorithm, provider, null);
        return new ExemptionMechanism((ExemptionMechanismSpi) spi, provider, algorithm);
    }

    /**
     * Returns the provider of this {@code ExemptionMechanism} instance.
     *
     * @return the provider of this {@code ExemptionMechanism} instance.
     */
    public final Provider getProvider() {
        return provider;
    }

    /**
     * Returns whether the result blob for this {@code ExemptionMechanism}
     * instance has been generated successfully and that the specified key is
     * the same as the one that was used to initialize and generate.
     *
     * @param key
     *            the key to verify.
     * @return whether the result blob for this {@code ExemptionMechanism}
     *         instance has been generated successfully.
     * @throws ExemptionMechanismException
     *             if an error occurs while determining whether the result blob
     *             has been generated successfully.
     */
    public final boolean isCryptoAllowed(Key key)
            throws ExemptionMechanismException {

        if (generated
                && (initKey.equals(key) || Arrays.equals(initKey.getEncoded(),
                        key.getEncoded()))) {
            return true;
        }
        return false;
    }

    /**
     * Returns the size in bytes for the output buffer needed to hold the output
     * of the next {@link #genExemptionBlob} call, given the specified {@code
     * inputLen} (in bytes).
     *
     * @param inputLen
     *            the specified input length (in bytes).
     * @return the size in bytes for the output buffer.
     * @throws IllegalStateException
     *             if this {@code ExemptionMechanism} instance is not
     *             initialized.
     */
    public final int getOutputSize(int inputLen) throws IllegalStateException {
        if (!isInit) {
            throw new IllegalStateException("ExemptionMechanism is not initialized");
        }
        return spiImpl.engineGetOutputSize(inputLen);
    }

    /**
     * Initializes this {@code ExemptionMechanism} instance with the
     * specified key.
     *
     * @param key
     *            the key to initialize this instance with.
     * @throws InvalidKeyException
     *             if the key cannot be used to initialize this mechanism.
     * @throws ExemptionMechanismException
     *             if error(s) occur during initialization.
     */
    public final void init(Key key) throws InvalidKeyException,
            ExemptionMechanismException {
        generated = false;
        spiImpl.engineInit(key);
        initKey = key;
        isInit = true;
    }

    /**
     * Initializes this {@code ExemptionMechanism} instance with the
     * specified key and algorithm parameters.
     *
     * @param key
     *            the key to initialize this instance with.
     * @param param
     *            the parameters for this exemption mechanism algorithm.
     * @throws InvalidKeyException
     *             if the key cannot be used to initialize this mechanism.
     * @throws InvalidAlgorithmParameterException
     *             if the parameters cannot be used to initialize this
     *             mechanism.
     * @throws ExemptionMechanismException
     *             if error(s) occur during initialization.
     */
    public final void init(Key key, AlgorithmParameters param)
            throws InvalidKeyException, InvalidAlgorithmParameterException,
            ExemptionMechanismException {
        generated = false;
        spiImpl.engineInit(key, param);
        initKey = key;
        isInit = true;
    }

    /**
     * Initializes this {@code ExemptionMechanism} instance with the
     * specified key and algorithm parameters.
     *
     * @param key
     *            the key to initialize this instance with.
     * @param param
     *            the parameters for this exemption mechanism algorithm.
     * @throws InvalidKeyException
     *             if the key cannot be used to initialize this mechanism.
     * @throws InvalidAlgorithmParameterException
     *             the the parameters cannot be used to initialize this
     *             mechanism.
     * @throws ExemptionMechanismException
     *             if error(s) occur during initialization.
     */
    public final void init(Key key, AlgorithmParameterSpec param)
            throws InvalidKeyException, InvalidAlgorithmParameterException,
            ExemptionMechanismException {
        generated = false;
        spiImpl.engineInit(key, param);
        initKey = key;
        isInit = true;
    }

    /**
     * Generates the result key blob for this exemption mechanism.
     *
     * @return the result key blob for this exemption mechanism.
     * @throws IllegalStateException
     *             if this {@code ExemptionMechanism} instance is not
     *             initialized.
     * @throws ExemptionMechanismException
     *             if error(s) occur during generation.
     */
    public final byte[] genExemptionBlob() throws IllegalStateException,
            ExemptionMechanismException {
        if (!isInit) {
            throw new IllegalStateException("ExemptionMechanism is not initialized");
        }
        generated = false;
        byte[] result = spiImpl.engineGenExemptionBlob();
        generated = true;
        return result;
    }

    /**
     * Generates the result key blob for this exemption mechanism and stores it
     * into the {@code output} buffer.
     *
     * @param output
     *            the output buffer for the result key blob.
     * @return the number of bytes written to the {@code output} buffer.
     * @throws IllegalStateException
     *             if this {@code ExemptionMechanism} instance is not
     *             initialized.
     * @throws ShortBufferException
     *             if the provided buffer is too small for the result key blob.
     * @throws ExemptionMechanismException
     *             if error(s) occur during generation.
     */
    public final int genExemptionBlob(byte[] output)
            throws IllegalStateException, ShortBufferException,
            ExemptionMechanismException {
        return genExemptionBlob(output, 0);
    }

    /**
     * Generates the result key blob for this exemption mechanism and stores it
     * into the {@code output} buffer at offset {@code outputOffset}.
     *
     * @param output
     *            the output buffer for the result key blob.
     * @param outputOffset
     *            the offset in the output buffer to start.
     * @return the number of bytes written to the {@code output} buffer.
     * @throws IllegalStateException
     *             if this {@code ExemptionMechanism} instance is not
     *             initialized.
     * @throws ShortBufferException
     *             if the provided buffer is too small for the result key blob.
     * @throws ExemptionMechanismException
     *             if error(s) occur during generation.
     */
    public final int genExemptionBlob(byte[] output, int outputOffset)
            throws IllegalStateException, ShortBufferException,
            ExemptionMechanismException {
        if (!isInit) {
            throw new IllegalStateException("ExemptionMechanism is not initialized");
        }
        generated = false;
        int len = spiImpl.engineGenExemptionBlob(output, outputOffset);
        generated = true;
        return len;
    }

    /**
     * Override to clear any key state in the instance.
     */
    @Override protected void finalize() {
        try {
            super.finalize();
        } catch (Throwable t) {
            // for consistency with the RI, we must override Object.finalize() to
            // remove the throws clause.
            throw new AssertionError(t);
        }
    }
}
