package javax.crypto;

import java.io.*;

import java.security.*;
import java.security.spec.*;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;

/**
 * This class implements the <code>EncryptedPrivateKeyInfo</code> type
 *  as defined in PKCS #8.
 * <p>Its ASN.1 definition is as follows:
 * 
 * <pre>
 * EncryptedPrivateKeyInfo ::=  SEQUENCE {
 *     encryptionAlgorithm   AlgorithmIdentifier,
 *     encryptedData   OCTET STRING }
 * 
 * AlgorithmIdentifier  ::=  SEQUENCE  {
 *     algorithm              OBJECT IDENTIFIER,
 *     parameters             ANY DEFINED BY algorithm OPTIONAL  }
 * </pre>
 */
public class EncryptedPrivateKeyInfo
{
    private org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo infoObj;
    private AlgorithmParameters algP;

    /*
     * Constructs (i.e., parses) an <code>EncryptedPrivateKeyInfo</code> from
     * its ASN.1 encoding.
     *
     * @param encoded the ASN.1 encoding of this object.
     * @exception NullPointerException if the <code>encoded</code> is null.
     * @exception IOException if error occurs when parsing the ASN.1 encoding.
     */
    public EncryptedPrivateKeyInfo(
        byte[] encoded)
        throws NullPointerException, IOException
    {
        if (encoded == null)
        {
            throw new NullPointerException("parameters null");
        }

        ByteArrayInputStream    bIn = new ByteArrayInputStream(encoded);
        ASN1InputStream         dIn = new ASN1InputStream(bIn);

        infoObj = org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo.getInstance((ASN1Sequence)dIn.readObject());

        try
        {
            algP = this.getParameters();
        }
        catch (NoSuchAlgorithmException e)
        {
            throw new IOException("can't create parameters: " + e.toString());
        }
    }

    /*
     * Constructs an <code>EncryptedPrivateKeyInfo</code> from the
     * encryption algorithm name and the encrypted data.
     * <p>Note: the <code>encrypedData</code> is cloned when constructing
     * this object.
     * <p>
     * If encryption algorithm has associated parameters use the constructor
     * with AlgorithmParameters as the parameter.
     *
     * @param algName algorithm name.
     * @param encryptedData encrypted data.
     * @exception NullPointerException if <code>algName</code> or <code>encryptedData</code> is null.
     * @exception IllegalArgumentException if <code>encryptedData</code> is empty, i.e. 0-length.
     * @exception NoSuchAlgorithmException if the specified algName is not supported.
     */
    public EncryptedPrivateKeyInfo(
        String algName,
        byte[] encryptedData)
        throws NullPointerException, IllegalArgumentException, NoSuchAlgorithmException
    {
        if (algName == null || encryptedData == null)
        {
            throw new NullPointerException("parameters null");
        }

        org.bouncycastle.asn1.x509.AlgorithmIdentifier      kAlgId = new AlgorithmIdentifier(new DERObjectIdentifier(algName), null);

        infoObj = new org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo(kAlgId, (byte[])encryptedData.clone());
        algP = this.getParameters();
    }

    /**
     * Constructs an <code>EncryptedPrivateKeyInfo</code> from the
     * encryption algorithm parameters and the encrypted data.
     * <p>Note: the <code>encrypedData</code> is cloned when constructing
     * this object.
     *
     * @param algParams the algorithm parameters for the encryption 
     * algorithm. <code>algParams.getEncoded()</code> should return
     * the ASN.1 encoded bytes of the <code>parameters</code> field
     * of the <code>AlgorithmIdentifer</code> component of the
     * <code>EncryptedPrivateKeyInfo</code> type.
     * @param encryptedData encrypted data.
     * @exception NullPointerException if <code>algParams</code> or <code>encryptedData</code> is null.
     * @exception IllegalArgumentException if <code>encryptedData</code> is empty, i.e. 0-length.
     * @exception NoSuchAlgorithmException if the specified algName of the specified <code>algParams</code> parameter is not supported.
     */
    public EncryptedPrivateKeyInfo(
        AlgorithmParameters algParams,
        byte[]              encryptedData)
        throws NullPointerException, IllegalArgumentException, NoSuchAlgorithmException
    {
        if (algParams == null || encryptedData == null)
        {
            throw new NullPointerException("parameters null");
        }

        org.bouncycastle.asn1.x509.AlgorithmIdentifier      kAlgId = null;

        try
        {
            ByteArrayInputStream    bIn = new ByteArrayInputStream(algParams.getEncoded());
            ASN1InputStream          dIn = new ASN1InputStream(bIn);

            kAlgId = new AlgorithmIdentifier(
                    new DERObjectIdentifier(algParams.getAlgorithm()), dIn.readObject());
        }
        catch (IOException e)
        {
            throw new IllegalArgumentException("error in encoding: " + e.toString());
        }

        infoObj = new org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo(kAlgId, (byte[])encryptedData.clone());
        algP = this.getParameters();
    }

    /**
     * Returns the encryption algorithm.
     *
     * @returns the algorithm name.
     */
    public String getAlgName()
    {
        return infoObj.getEncryptionAlgorithm().getObjectId().getId();
    }

    private AlgorithmParameters getParameters()
        throws NoSuchAlgorithmException
    {
        AlgorithmParameters     ap = AlgorithmParameters.getInstance(this.getAlgName());
        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
        DEROutputStream         dOut = new DEROutputStream(bOut);

        try
        {
            dOut.writeObject(infoObj.getEncryptionAlgorithm().getParameters());
            dOut.close();

            ap.init(bOut.toByteArray());
        }
        catch (IOException e)
        {
            throw new NoSuchAlgorithmException("unable to parse parameters");
        }

        return ap;
    }

    /**
     * Returns the algorithm parameters used by the encryption algorithm.
     *
     * @returns the algorithm parameters.
     */
    public AlgorithmParameters getAlgParameters()
    {
        return algP;
    }

    /**
     * Returns a copy of the encrypted data.
     *
     * @returns a copy of the encrypted data.
     */
    public byte[] getEncryptedData()
    {
        return infoObj.getEncryptedData();
    }

    /**
     * Extract the enclosed PKCS8EncodedKeySpec object from the 
     * encrypted data and return it.
     *
     * @return the PKCS8EncodedKeySpec object.
     * @exception InvalidKeySpecException if the given cipher is 
     * inappropriate for the encrypted data or the encrypted
     * data is corrupted and cannot be decrypted.
     */
    public PKCS8EncodedKeySpec getKeySpec(
        Cipher  c)
    throws InvalidKeySpecException
    {
        try
        {
            return new PKCS8EncodedKeySpec(c.doFinal(this.getEncryptedData()));
        }
        catch (Exception e)
        {
            throw new InvalidKeySpecException("can't get keySpec: " + e.toString());
        }
    }

    /**
     * Returns the ASN.1 encoding of this object.
     *
     * @returns the ASN.1 encoding.
     * @throws IOException if error occurs when constructing its ASN.1 encoding.
     */
    public byte[] getEncoded()
        throws IOException
    {
        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
        DEROutputStream         dOut = new DEROutputStream(bOut);

        dOut.writeObject(infoObj);
        dOut.close();

        return bOut.toByteArray();
    }
}
