package org.bouncycastle.jce.provider;

import java.lang.reflect.Constructor;
import java.security.InvalidKeyException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactorySpi;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.params.DESParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;

public class JCESecretKeyFactory
    extends SecretKeyFactorySpi
    implements PBE
{
    protected String algName;

    protected JCESecretKeyFactory(
        String  algName)
    {
        this.algName = algName;
    }

    protected SecretKey engineGenerateSecret(
        KeySpec keySpec)
    throws InvalidKeySpecException
    {
        if ( keySpec instanceof SecretKeySpec )
        {
            return (SecretKey)keySpec;
        }

        throw new InvalidKeySpecException("Invalid KeySpec");
    }

    protected KeySpec engineGetKeySpec(
        SecretKey key,
        Class keySpec)
    throws InvalidKeySpecException
    {
        if (SecretKeySpec.class.isAssignableFrom(keySpec))
        {
            return new SecretKeySpec(key.getEncoded(), algName);
        }

        try
        {
            Class[] parameters = { byte[].class };

            Constructor c = keySpec.getConstructor(parameters);
            Object[]    p = new Object[1];

            p[0] = key.getEncoded();

            return (KeySpec)c.newInstance(p);
        }
        catch (Exception e)
        {
            throw new InvalidKeySpecException(e.toString());
        }
    }

    protected SecretKey engineTranslateKey(
        SecretKey key)
    throws InvalidKeyException
    {
        if (!key.getAlgorithm().equalsIgnoreCase(algName))
        {
            throw new InvalidKeyException("Key not of type " + algName + ".");
        }

        return new SecretKeySpec(key.getEncoded(), algName);
    }

    /*
     * classes that inherit from us
     */
    
    static public class PBEKeyFactory
        extends JCESecretKeyFactory
    {
        private boolean forCipher;
        private int     scheme;
        private int     digest;
        private int     keySize;
        private int     ivSize;
        
        public PBEKeyFactory(
            String  algorithm,
            boolean forCipher,
            int     scheme,
            int     digest,
            int     keySize,
            int     ivSize)
        {
            super(algorithm);
            
            this.forCipher = forCipher;
            this.scheme = scheme;
            this.digest = digest;
            this.keySize = keySize;
            this.ivSize = ivSize;
        }
    
        protected SecretKey engineGenerateSecret(
            KeySpec keySpec)
            throws InvalidKeySpecException
        {
            if (keySpec instanceof PBEKeySpec)
            {
                PBEKeySpec          pbeSpec = (PBEKeySpec)keySpec;
                CipherParameters    param;
                
                if (pbeSpec.getSalt() == null)
                {
                    return new JCEPBEKey(this.algName, scheme, digest, keySize, ivSize, pbeSpec, null);
                }
                
                if (forCipher)
                {
                    param = Util.makePBEParameters(pbeSpec, scheme, digest, keySize, ivSize);
                }
                else
                {
                    param = Util.makePBEMacParameters(pbeSpec, scheme, digest, keySize);
                }
                
                return new JCEPBEKey(this.algName, scheme, digest, keySize, ivSize, pbeSpec, param);
            }
            
            throw new InvalidKeySpecException("Invalid KeySpec");
        }
    }

    static public class DESPBEKeyFactory
        extends JCESecretKeyFactory
    {
        private boolean forCipher;
        private int     scheme;
        private int     digest;
        private int     keySize;
        private int     ivSize;
        
        public DESPBEKeyFactory(
            String  algorithm,
            boolean forCipher,
            int     scheme,
            int     digest,
            int     keySize,
            int     ivSize)
        {
            super(algorithm);
            
            this.forCipher = forCipher;
            this.scheme = scheme;
            this.digest = digest;
            this.keySize = keySize;
            this.ivSize = ivSize;
        }
    
        protected SecretKey engineGenerateSecret(
            KeySpec keySpec)
        throws InvalidKeySpecException
        {
            if (keySpec instanceof PBEKeySpec)
            {
                PBEKeySpec pbeSpec = (PBEKeySpec)keySpec;
                CipherParameters    param;
                
                if (pbeSpec.getSalt() == null)
                {
                    return new JCEPBEKey(this.algName, scheme, digest, keySize, ivSize, pbeSpec, null);
                }
                
                if (forCipher)
                {
                    param = Util.makePBEParameters(pbeSpec, scheme, digest, keySize, ivSize);
                }
                else
                {
                    param = Util.makePBEMacParameters(pbeSpec, scheme, digest, keySize);
                }
                
                if (param instanceof ParametersWithIV)
                {
                    KeyParameter    kParam = (KeyParameter)((ParametersWithIV)param).getParameters();

                    DESParameters.setOddParity(kParam.getKey());
                }
                else
                {
                    KeyParameter    kParam = (KeyParameter)param;

                    DESParameters.setOddParity(kParam.getKey());
                }
                
                return new JCEPBEKey(this.algName, scheme, digest, keySize, ivSize, pbeSpec, param);
            }
            
            throw new InvalidKeySpecException("Invalid KeySpec");
        }
    }
    
    static public class DES
        extends JCESecretKeyFactory
    {
        public DES()
        {
            super("DES");
        }

        protected SecretKey engineGenerateSecret(
            KeySpec keySpec)
        throws InvalidKeySpecException
        {
            if ( keySpec instanceof DESKeySpec )
            {
                DESKeySpec desKeySpec = (DESKeySpec)keySpec;
                return new SecretKeySpec(desKeySpec.getKey(), "DES");
            }

            return super.engineGenerateSecret(keySpec);
        }
    }

    static public class DESede
        extends JCESecretKeyFactory
    {
        public DESede()
        {
            super("DESede");
        }

        protected KeySpec engineGetKeySpec(
            SecretKey key,
            Class keySpec)
        throws InvalidKeySpecException
        {
            if (SecretKeySpec.class.isAssignableFrom(keySpec))
            {
                return new SecretKeySpec(key.getEncoded(), algName);
            }
            else if (DESedeKeySpec.class.isAssignableFrom(keySpec))
            {
                byte[]  bytes = key.getEncoded();

                try
                {
                    if (bytes.length == 16)
                    {
                        byte[]  longKey = new byte[24];

                        System.arraycopy(bytes, 0, longKey, 0, 16);
                        System.arraycopy(bytes, 0, longKey, 16, 8);

                        return new DESedeKeySpec(longKey);
                    }
                    else
                    {
                        return new DESedeKeySpec(bytes);
                    }
                }
                catch (Exception e)
                {
                    throw new InvalidKeySpecException(e.toString());
                }
            }

            throw new InvalidKeySpecException("Invalid KeySpec");
        }

        protected SecretKey engineGenerateSecret(
            KeySpec keySpec)
        throws InvalidKeySpecException
        {
            if ( keySpec instanceof DESedeKeySpec )
            {
                DESedeKeySpec desKeySpec = (DESedeKeySpec)keySpec;
                return new SecretKeySpec(desKeySpec.getKey(), "DESede");
            }

            return super.engineGenerateSecret(keySpec);
        }
    }
    
   /**
    * PBEWithMD5AndDES
    */
   static public class PBEWithMD5AndDES
       extends DESPBEKeyFactory
   {
       public PBEWithMD5AndDES()
       {
           super("PBEwithMD5andDES", true, PKCS5S1, MD5, 64, 64);
       }
   }

   /**
    * PBEWithMD5AndRC2
    */
   static public class PBEWithMD5AndRC2
       extends PBEKeyFactory
   {
       public PBEWithMD5AndRC2()
       {
           super("PBEwithMD5andRC2", true, PKCS5S1, MD5, 64, 64);
       }
   }

   /**
    * PBEWithSHA1AndDES
    */
   static public class PBEWithSHA1AndDES
       extends PBEKeyFactory
   {
       public PBEWithSHA1AndDES()
       {
           super("PBEwithSHA1andDES", true, PKCS5S1, SHA1, 64, 64);
       }
   }

   /**
    * PBEWithSHA1AndRC2
    */
   static public class PBEWithSHA1AndRC2
       extends PBEKeyFactory
   {
       public PBEWithSHA1AndRC2()
       {
           super("PBEwithSHA1andRC2", true, PKCS5S1, SHA1, 64, 64);
       }
   }

   /**
    * PBEWithSHAAnd3-KeyTripleDES-CBC
    */
   static public class PBEWithSHAAndDES3Key
       extends PBEKeyFactory
   {
       public PBEWithSHAAndDES3Key()
       {
           super("PBEwithSHAandDES3Key-CBC", true, PKCS12, SHA1, 192, 64);
       }
   }

   /**
    * PBEWithSHAAnd2-KeyTripleDES-CBC
    */
   static public class PBEWithSHAAndDES2Key
       extends PBEKeyFactory
   {
       public PBEWithSHAAndDES2Key()
       {
           super("PBEwithSHAandDES2Key-CBC", true, PKCS12, SHA1, 128, 64);
       }
   }

   /**
    * PBEWithSHAAnd128BitRC2-CBC
    */
   static public class PBEWithSHAAnd128BitRC2
       extends PBEKeyFactory
   {
       public PBEWithSHAAnd128BitRC2()
       {
           super("PBEwithSHAand128BitRC2-CBC", true, PKCS12, SHA1, 128, 64);
       }
   }

   /**
    * PBEWithSHAAnd40BitRC2-CBC
    */
   static public class PBEWithSHAAnd40BitRC2
       extends PBEKeyFactory
   {
       public PBEWithSHAAnd40BitRC2()
       {
           super("PBEwithSHAand40BitRC2-CBC", true, PKCS12, SHA1, 40, 64);
       }
   }

   /**
    * PBEWithSHAAndTwofish-CBC
    */
   static public class PBEWithSHAAndTwofish
       extends PBEKeyFactory
   {
       public PBEWithSHAAndTwofish()
       {
           super("PBEwithSHAandTwofish-CBC", true, PKCS12, SHA1, 256, 128);
       }
   }

   /**
    * PBEWithSHAAndIDEA-CBC
    */
   static public class PBEWithSHAAndIDEA
       extends PBEKeyFactory
   {
       public PBEWithSHAAndIDEA()
       {
           super("PBEwithSHAandIDEA-CBC", true, PKCS12, SHA1, 128, 64);
       }
   }
   
   /**
    * PBEWithSHAAnd128BitRC4
    */
   static public class PBEWithSHAAnd128BitRC4
       extends PBEKeyFactory
   {
       public PBEWithSHAAnd128BitRC4()
       {
           super("PBEWithSHAAnd128BitRC4", true, PKCS12, SHA1, 128, 0);
       }
   }

   /**
    * PBEWithSHAAnd40BitRC4
    */
   static public class PBEWithSHAAnd40BitRC4
       extends PBEKeyFactory
   {
       public PBEWithSHAAnd40BitRC4()
       {
           super("PBEWithSHAAnd128BitRC4", true, PKCS12, SHA1, 40, 0);
       }
   }
   
   /**
    * PBEWithHmacRIPEMD160
    */
   public static class PBEWithRIPEMD160
       extends PBEKeyFactory
   {
       public PBEWithRIPEMD160()
       {
           super("PBEwithHmacRIPEMD160", false, PKCS12, RIPEMD160, 160, 0);
       }
   }

   /**
    * PBEWithHmacSHA
    */
   public static class PBEWithSHA
       extends PBEKeyFactory
   {
       public PBEWithSHA()
       {
           super("PBEwithHmacSHA", false, PKCS12, SHA1, 160, 0);
       }
   }

   /**
    * PBEWithHmacTiger
    */
   public static class PBEWithTiger
       extends PBEKeyFactory
   {
       public PBEWithTiger()
       {
           super("PBEwithHmacTiger", false, PKCS12, TIGER, 192, 0);
       }
   }
}
