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

/**
* @author Boris V. Kuznetsov
* @version $Revision$
*/

package java.security;

import java.nio.ByteBuffer;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Iterator;
import java.util.Set;

import org.apache.harmony.security.fortress.Engine;
import org.apache.harmony.security.internal.nls.Messages;


/**
 * {@code Signature} is an engine class which is capable of creating and
 * verifying digital signatures, using different algorithms that have been
 * registered with the {@link Security} class.
 * 
 * @see SignatureSpi
 * @since Android 1.0
 */
public abstract class Signature extends SignatureSpi {
    
    // The service name.
    private static final String SERVICE = "Signature"; //$NON-NLS-1$

    // Used to access common engine functionality
    private static Engine engine = new Engine(SERVICE);

    // The provider
    private Provider provider;

    // The algorithm.
    private String algorithm;

    /**
     * Constant that indicates that this {@code Signature} instance has not yet
     * been initialized.
     * 
     * @since Android 1.0
     */
    protected static final int UNINITIALIZED = 0;

    /**
     * Constant that indicates that this {@code Signature} instance has been
     * initialized for signing.
     * 
     * @since Android 1.0
     */
    protected static final int SIGN = 2;

    /**
     * Constant that indicates that this {@code Signature} instance has been
     * initialized for verification.
     * 
     * @since Android 1.0
     */
    protected static final int VERIFY = 3;

    /**
     * Represents the current state of this {@code Signature}. The three
     * possible states are {@link #UNINITIALIZED}, {@link #SIGN} or
     * {@link #VERIFY}.
     * 
     * @since Android 1.0
     */
    protected int state = UNINITIALIZED;

    /**
     * Constructs a new instance of {@code Signature} with the name of
     * the algorithm to use.
     * 
     * @param algorithm
     *            the name of algorithm to use.
     * @since Android 1.0
     */
    protected Signature(String algorithm) {
        this.algorithm = algorithm;
    }

    /**
     * Returns a new instance of {@code Signature} that utilizes the specified
     * algorithm.
     * 
     * @param algorithm
     *            the name of the algorithm to use.
     * @return a new instance of {@code Signature} that utilizes the specified
     *         algorithm.
     * @throws NoSuchAlgorithmException
     *             if the specified algorithm is not available.
     * @throws NullPointerException
     *             if {@code algorithm} is {@code null}.
     * @since Android 1.0
     */
    public static Signature getInstance(String algorithm)
            throws NoSuchAlgorithmException {
        if (algorithm == null) {
            throw new NullPointerException(Messages.getString("security.01")); //$NON-NLS-1$
        }
        Signature result;
        synchronized (engine) {
            engine.getInstance(algorithm, null);
            if (engine.spi instanceof Signature) {
                result = (Signature) engine.spi;
                result.algorithm = algorithm;
                result.provider = engine.provider;
            } else {
                result = new SignatureImpl((SignatureSpi) engine.spi,
                        engine.provider, algorithm);
            }
        }
        return result;
    }

    /**
     * Returns a new instance of {@code Signature} that utilizes the specified
     * algorithm from the specified provider.
     * 
     * @param algorithm
     *            the name of the algorithm to use.
     * @param provider
     *            the name of the provider.
     * @return a new instance of {@code Signature} that utilizes the specified
     *         algorithm from the specified provider.
     * @throws NoSuchAlgorithmException
     *             if the specified algorithm is not available.
     * @throws NoSuchProviderException
     *             if the specified provider is not available.
     * @throws NullPointerException
     *             if {@code algorithm} is {@code null}.
     * @since Android 1.0
     */
    public static Signature getInstance(String algorithm, String provider)
            throws NoSuchAlgorithmException, NoSuchProviderException {
        if (algorithm == null) {
            throw new NullPointerException(Messages.getString("security.01")); //$NON-NLS-1$
        }
        if ((provider == null) || (provider.length() == 0)) {
            throw new IllegalArgumentException(
                    Messages.getString("security.02")); //$NON-NLS-1$
        }
        Provider p = Security.getProvider(provider);
        if (p == null) {
            throw new NoSuchProviderException(Messages.getString("security.03", provider)); //$NON-NLS-1$
        }
        return getSignatureInstance(algorithm, p);
    }

    /**
     * Returns a new instance of {@code Signature} that utilizes the specified
     * algorithm from the specified provider.
     * 
     * @param algorithm
     *            the name of the algorithm to use.
     * @param provider
     *            the security provider.
     * @return a new instance of {@code Signature} that utilizes the specified
     *         algorithm from the specified provider.
     * @throws NoSuchAlgorithmException
     *             if the specified algorithm is not available.
     * @throws NullPointerException
     *             if {@code algorithm} is {@code null}.
     * @since Android 1.0
     */
    public static Signature getInstance(String algorithm, Provider provider)
            throws NoSuchAlgorithmException {
        if (algorithm == null) {
            throw new NullPointerException(Messages.getString("security.01")); //$NON-NLS-1$
        }
        if (provider == null) {
            throw new IllegalArgumentException(Messages.getString("security.04")); //$NON-NLS-1$
        }
        return getSignatureInstance(algorithm, provider);
    }
    
    private static Signature getSignatureInstance(String algorithm,
            Provider provider) throws NoSuchAlgorithmException {
        Signature result;
        synchronized (engine) {
            engine.getInstance(algorithm, provider, null);
            if (engine.spi instanceof Signature) {
                result = (Signature) engine.spi;
                result.algorithm = algorithm;
                result.provider = provider;
            } else {
                result = new SignatureImpl((SignatureSpi) engine.spi, provider,
                        algorithm);
            }
        }
        return result;
    }

    /**
     * Returns the provider associated with this {@code Signature}.
     * 
     * @return the provider associated with this {@code Signature}.
     * @since Android 1.0
     */
    public final Provider getProvider() {
        return provider;
    }

    /**
     * Returns the name of the algorithm of this {@code Signature}.
     * 
     * @return the name of the algorithm of this {@code Signature}.
     * @since Android 1.0
     */
    public final String getAlgorithm() {
        return algorithm;
    }

    /**
     * Initializes this {@code Signature} instance for signature verification,
     * using the public key of the identity whose signature is going to be
     * verified.
     * 
     * @param publicKey
     *            the public key.
     * @throws InvalidKeyException
     *             if {@code publicKey} is not valid.
     * @since Android 1.0
     */
    public final void initVerify(PublicKey publicKey)
            throws InvalidKeyException {
        engineInitVerify(publicKey);
        state = VERIFY;
    }

    /**
     * Initializes this {@code Signature} instance for signature verification,
     * using the certificate of the identity whose signature is going to be
     * verified.
     * <p>
     * If the given certificate is an instance of {@link X509Certificate} and
     * has a key usage parameter that indicates, that this certificate is not to
     * be used for signing, an {@code InvalidKeyException} is thrown.
     * </p>
     * 
     * @param certificate
     *            the certificate used to verify a signature.
     * @throws InvalidKeyException
     *             if the publicKey in the certificate is not valid or not to be
     *             used for signing.
     * @since Android 1.0
     */
    public final void initVerify(Certificate certificate)
            throws InvalidKeyException {
        if (certificate instanceof X509Certificate) {
            Set ce = ((X509Certificate) certificate).getCriticalExtensionOIDs();
            boolean critical = false;
            if (ce != null && !ce.isEmpty()) {
                for (Iterator i = ce.iterator(); i.hasNext();) {
                    if ("2.5.29.15".equals(i.next())) {  //$NON-NLS-1$
                        //KeyUsage OID = 2.5.29.15
                        critical = true;
                        break;
                    }
                }
                if (critical) {
                    boolean[] keyUsage = ((X509Certificate) certificate)
                            .getKeyUsage();
                    // As specified in RFC 3280 -
                    // Internet X.509 Public Key Infrastructure
                    // Certificate and Certificate Revocation List (CRL) Profile.
                    // (http://www.ietf.org/rfc/rfc3280.txt)
                    //
                    // KeyUsage ::= BIT STRING { digitalSignature (0), <skipped> }
                    if ((keyUsage != null) && (!keyUsage[0])) { // digitalSignature
                        throw new InvalidKeyException(
                                Messages.getString("security.26")); //$NON-NLS-1$
                    }
                }
            }
        }
        engineInitVerify(certificate.getPublicKey());
        state = VERIFY;
    }

    /**
     * Initializes this {@code Signature} instance for signing, using the
     * private key of the identity whose signature is going to be generated.
     * 
     * @param privateKey
     *            the private key.
     * @throws InvalidKeyException
     *             if {@code privateKey} is not valid.
     * @since Android 1.0
     */
    public final void initSign(PrivateKey privateKey)
            throws InvalidKeyException {
        engineInitSign(privateKey);
        state = SIGN;
    }

    /**
     * Initializes this {@code Signature} instance for signing, using the
     * private key of the identity whose signature is going to be generated and
     * the specified source of randomness.
     * 
     * @param privateKey
     *            the private key.
     * @param random
     *            the {@code SecureRandom} to use.
     * @throws InvalidKeyException
     *             if {@code privateKey} is not valid.
     * @since Android 1.0
     */
    public final void initSign(PrivateKey privateKey, SecureRandom random)
            throws InvalidKeyException {
        engineInitSign(privateKey, random);
        state = SIGN;
    }

    /**
     * Generates and returns the signature of all updated data.
     * <p>
     * This {@code Signature} instance is reset to the state of its last
     * initialization for signing and thus can be used for another signature
     * from the same identity.
     * </p>
     * 
     * @return the signature of all updated data.
     * @throws SignatureException
     *             if this {@code Signature} instance is not initialized
     *             properly.
     * @since Android 1.0
     */
    public final byte[] sign() throws SignatureException {
        if (state != SIGN) {
            throw new SignatureException(
                    Messages.getString("security.27")); //$NON-NLS-1$
        }
        return engineSign();
    }

    /**
     * Generates and stores the signature of all updated data in the provided
     * {@code byte[]} at the specified position with the specified length.
     * <p>
     * This {@code Signature} instance is reset to the state of its last
     * initialization for signing and thus can be used for another signature
     * from the same identity.
     * </p>
     * 
     * @param outbuf
     *            the buffer to store the signature.
     * @param offset
     *            the index of the first byte in {@code outbuf} to store.
     * @param len
     *            the number of bytes allocated for the signature.
     * @return the number of bytes stored in {@code outbuf}.
     * @throws SignatureException
     *             if this {@code Signature} instance is not initialized
     *             properly.
     * @throws IllegalArgumentException
     *             if {@code offset} or {@code len} are not valid in respect to
     *             {@code outbuf}.
     * @since Android 1.0
     */
    public final int sign(byte[] outbuf, int offset, int len)
            throws SignatureException {       
        if (outbuf == null || offset < 0 || len < 0 ||
                offset + len > outbuf.length) {
            throw new IllegalArgumentException(
                    Messages.getString("security.05")); //$NON-NLS-1$
        }
        if (state != SIGN) {
            throw new SignatureException(
                    Messages.getString("security.27")); //$NON-NLS-1$
        }
        return engineSign(outbuf, offset, len);
    }

    /**
     * Indicates whether the given {@code signature} can be verified using the
     * public key or a certificate of the signer.
     * <p>
     * This {@code Signature} instance is reset to the state of its last
     * initialization for verifying and thus can be used to verify another
     * signature of the same signer.
     * </p>
     * 
     * @param signature
     *            the signature to verify.
     * @return {@code true} if the signature was verified, {@code false}
     *         otherwise.
     * @throws SignatureException
     *             if this {@code Signature} instance is not initialized
     *             properly.
     * @since Android 1.0
     */
    public final boolean verify(byte[] signature) throws SignatureException {
        if (state != VERIFY) {
            throw new SignatureException(
                    Messages.getString("security.27")); //$NON-NLS-1$
        }
        return engineVerify(signature);
    }

    /**
     * Indicates whether the given {@code signature} starting at index {@code
     * offset} with {@code length} bytes can be verified using the public key or
     * a certificate of the signer.
     * <p>
     * This {@code Signature} instance is reset to the state of its last
     * initialization for verifying and thus can be used to verify another
     * signature of the same signer.
     * </p>
     * 
     * @param signature
     *            the {@code byte[]} containing the signature to verify.
     * @param offset
     *            the start index in {@code signature} of the signature.
     * @param length
     *            the number of bytes allocated for the signature.
     * @return {@code true} if the signature was verified, {@code false}
     *         otherwise.
     * @throws SignatureException
     *             if this {@code Signature} instance is not initialized
     *             properly.
     * @throws IllegalArgumentException
     *             if {@code offset} or {@code length} are not valid in respect
     *             to {@code signature}.
     * @since Android 1.0
     */
    public final boolean verify(byte[] signature, int offset, int length)
            throws SignatureException {
        if (state != VERIFY) {
            throw new SignatureException(
                    Messages.getString("security.27")); //$NON-NLS-1$
        }
        if (signature == null || offset < 0 || length < 0 ||
                offset + length > signature.length) {
            throw new IllegalArgumentException(
                    Messages.getString("security.05")); //$NON-NLS-1$
        }
        return engineVerify(signature, offset, length);
    }

    /**
     * Updates the data to be verified or to be signed, using the specified
     * {@code byte}.
     * 
     * @param b
     *            the byte to update with.
     * @throws SignatureException
     *             if this {@code Signature} instance is not initialized
     *             properly.
     * @since Android 1.0
     */
    public final void update(byte b) throws SignatureException {
        if (state == UNINITIALIZED) {
            throw new SignatureException(
                    Messages.getString("security.27")); //$NON-NLS-1$
        }
        engineUpdate(b);
    }

    /**
     * Updates the data to be verified or to be signed, using the specified
     * {@code byte[]}.
     * 
     * @param data
     *            the byte array to update with.
     * @throws SignatureException
     *             if this {@code Signature} instance is not initialized
     *             properly.
     * @since Android 1.0
     */
    public final void update(byte[] data) throws SignatureException {
        if (state == UNINITIALIZED) {
            throw new SignatureException(
                    Messages.getString("security.27")); //$NON-NLS-1$
        }
        engineUpdate(data, 0, data.length);
    }

    /**
     * Updates the data to be verified or to be signed, using the given {@code
     * byte[]}, starting form the specified index for the specified length.
     * 
     * @param data
     *            the byte array to update with.
     * @param off
     *            the start index in {@code data} of the data.
     * @param len
     *            the number of bytes to use.
     * @throws SignatureException
     *             if this {@code Signature} instance is not initialized
     *             properly.
     * @since Android 1.0
     */
    public final void update(byte[] data, int off, int len)
            throws SignatureException {
        if (state == UNINITIALIZED) {
            throw new SignatureException(
                    Messages.getString("security.27")); //$NON-NLS-1$
        }
        if (data == null || off < 0 || len < 0 ||
                off + len > data.length) {
            throw new IllegalArgumentException(
                    Messages.getString("security.05")); //$NON-NLS-1$
        }
        engineUpdate(data, off, len);
    }

    /**
     * Updates the data to be verified or to be signed, using the specified
     * {@code ByteBuffer}.
     * 
     * @param data
     *            the {@code ByteBuffer} to update with.
     * @throws SignatureException
     *             if this {@code Signature} instance is not initialized
     *             properly.
     * @since Android 1.0
     */
    public final void update(ByteBuffer data) throws SignatureException {
        if (state == UNINITIALIZED) {
            throw new SignatureException(
                    Messages.getString("security.27")); //$NON-NLS-1$
        }
        engineUpdate(data);
    }

    /**
     * Returns a string containing a concise, human-readable description of this
     * {@code Signature} including its algorithm and its state.
     * 
     * @return a printable representation for this {@code Signature}.
     * @since Android 1.0
     */
    public String toString() {
        return "SIGNATURE " + algorithm + " state: " + stateToString(state); //$NON-NLS-1$ //$NON-NLS-2$
    }

    // Convert state to string
    private String stateToString(int state) {
        switch (state) {
        case UNINITIALIZED:
            return "UNINITIALIZED"; //$NON-NLS-1$
        case SIGN:
            return "SIGN"; //$NON-NLS-1$
        case VERIFY:
            return "VERIFY"; //$NON-NLS-1$
        default:
            return ""; //$NON-NLS-1$
        }
    }

    // BEGIN android-note
    // added Deprecated annotation
    // END android-note
    /**
     * Sets the specified parameter to the given value.
     * 
     * @param param
     *            the name of the parameter.
     * @param value
     *            the parameter value.
     * @throws InvalidParameterException
     *             if the parameter is invalid, already set or is not allowed to
     *             be changed.
     * @deprecated Use {@link #setParameter(AlgorithmParameterSpec)}
     * @since Android 1.0
     */
    @Deprecated
    public final void setParameter(String param, Object value)
            throws InvalidParameterException {
        engineSetParameter(param, value);
    }

    /**
     * Sets the specified {@code AlgorithmParameterSpec}.
     * 
     * @param params
     *            the parameter to set.
     * @throws InvalidAlgorithmParameterException
     *             if the parameter is invalid, already set or is not allowed to
     *             be changed.
     * @since Android 1.0
     */
    public final void setParameter(AlgorithmParameterSpec params)
            throws InvalidAlgorithmParameterException {
        engineSetParameter(params);
    }

    /**
     * Returns the {@code AlgorithmParameters} of this {@link Signature}
     * instance.
     * 
     * @return the {@code AlgorithmParameters} of this {@link Signature}
     *         instance, maybe {@code null}.
     * @since Android 1.0
     */
    public final AlgorithmParameters getParameters() {
        return engineGetParameters();
    }

    // BEGIN android-note
    // added Deprecated annotation
    // END android-note
    /**
     * Returns the value of the parameter with the specified name.
     * 
     * @param param
     *            the name of the requested parameter value
     * @return the value of the parameter with the specified name, maybe {@code
     *         null}.
     * @throws InvalidParameterException
     *             if {@code param} is not a valid parameter for this {@code
     *             Signature} or an other error occures.
     * @deprecated There is no generally accepted parameter naming convention.
     * @since Android 1.0
     */
    @Deprecated
    public final Object getParameter(String param)
            throws InvalidParameterException {
        return engineGetParameter(param);
    }

    public Object clone() throws CloneNotSupportedException {
        if (this instanceof Cloneable) {
            return super.clone();
        } else {
            throw new CloneNotSupportedException();
        }
    }

    /**
     * 
     * Internal Signature implementation
     * 
     */
    private static class SignatureImpl extends Signature {

        private SignatureSpi spiImpl;

        // Constructor
        public SignatureImpl(SignatureSpi signatureSpi, Provider provider,
                String algorithm) {
            super(algorithm);
            super.provider = provider;
            spiImpl = signatureSpi;
        }

        // engineSign() implementation
        protected byte[] engineSign() throws SignatureException {
            return spiImpl.engineSign();
        }

        //  engineUpdate() implementation
        protected void engineUpdate(byte arg0) throws SignatureException {
            spiImpl.engineUpdate(arg0);
        }

        // engineVerify() implementation
        protected boolean engineVerify(byte[] arg0) throws SignatureException {
            return spiImpl.engineVerify(arg0);
        }

        // engineUpdate() implementation
        protected void engineUpdate(byte[] arg0, int arg1, int arg2)
                throws SignatureException {
            spiImpl.engineUpdate(arg0, arg1, arg2);
        }

        // engineInitSign() implementation
        protected void engineInitSign(PrivateKey arg0)
                throws InvalidKeyException {
            spiImpl.engineInitSign(arg0);
        }

        // engineInitVerify() implementation
        protected void engineInitVerify(PublicKey arg0)
                throws InvalidKeyException {
            spiImpl.engineInitVerify(arg0);
        }

        // engineGetParameter() implementation
        protected Object engineGetParameter(String arg0)
                throws InvalidParameterException {
            return spiImpl.engineGetParameter(arg0);
        }

        // engineSetParameter() implementation
        protected void engineSetParameter(String arg0, Object arg1)
                throws InvalidParameterException {
            spiImpl.engineSetParameter(arg0, arg1);
        }

        // Returns a clone if the spiImpl is cloneable
        public Object clone() throws CloneNotSupportedException {
            if (spiImpl instanceof Cloneable) {
                SignatureSpi spi = (SignatureSpi) spiImpl.clone();
                return new SignatureImpl(spi, getProvider(), getAlgorithm());
            } else {
                throw new CloneNotSupportedException();
            }
        }
    }
}
