package org.bouncycastle.jce.provider;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactorySpi;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHPrivateKeySpec;
import javax.crypto.spec.DHPublicKeySpec;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.cryptopro.CryptoProObjectIdentifiers;
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.pkcs.RSAPrivateKeyStructure;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.jce.interfaces.ElGamalPrivateKey;
import org.bouncycastle.jce.interfaces.ElGamalPublicKey;
import org.bouncycastle.jce.provider.JCEDHPrivateKey;
import org.bouncycastle.jce.provider.JCEDHPublicKey;
import org.bouncycastle.jce.provider.JCEElGamalPrivateKey;
import org.bouncycastle.jce.provider.JCEElGamalPublicKey;
import org.bouncycastle.jce.provider.JDKGOST3410PrivateKey;
import org.bouncycastle.jce.provider.JDKGOST3410PublicKey;
import org.bouncycastle.jce.provider.JDKKeyFactory.EC;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.jce.spec.ElGamalPrivateKeySpec;
import org.bouncycastle.jce.spec.ElGamalPublicKeySpec;
import org.bouncycastle.jce.spec.GOST3410PrivateKeySpec;
import org.bouncycastle.jce.spec.GOST3410PublicKeySpec;

public abstract class JDKKeyFactory
    extends KeyFactorySpi
{
    protected boolean elGamalFactory = false;
    
    public JDKKeyFactory()
    {
    }

    protected KeySpec engineGetKeySpec(
        Key    key,
        Class    spec)
    throws InvalidKeySpecException
    {
       if (spec.isAssignableFrom(PKCS8EncodedKeySpec.class) && key.getFormat().equals("PKCS#8"))
       {
               return new PKCS8EncodedKeySpec(key.getEncoded());
       }
       else if (spec.isAssignableFrom(X509EncodedKeySpec.class) && key.getFormat().equals("X.509"))
       {
               return new X509EncodedKeySpec(key.getEncoded());
       }
       else if (spec.isAssignableFrom(RSAPublicKeySpec.class) && key instanceof RSAPublicKey)
       {
            RSAPublicKey    k = (RSAPublicKey)key;

            return new RSAPublicKeySpec(k.getModulus(), k.getPublicExponent());
       }
       else if (spec.isAssignableFrom(RSAPrivateKeySpec.class) && key instanceof RSAPrivateKey)
       {
            RSAPrivateKey    k = (RSAPrivateKey)key;

            return new RSAPrivateKeySpec(k.getModulus(), k.getPrivateExponent());
       }
       else if (spec.isAssignableFrom(RSAPrivateCrtKeySpec.class) && key instanceof RSAPrivateCrtKey)
       {
            RSAPrivateCrtKey    k = (RSAPrivateCrtKey)key;

            return new RSAPrivateCrtKeySpec(
                            k.getModulus(), k.getPublicExponent(),
                            k.getPrivateExponent(),
                            k.getPrimeP(), k.getPrimeQ(),
                            k.getPrimeExponentP(), k.getPrimeExponentQ(),
                            k.getCrtCoefficient());
       }
       else if (spec.isAssignableFrom(DHPrivateKeySpec.class) && key instanceof DHPrivateKey)
       {
           DHPrivateKey k = (DHPrivateKey)key;
           
           return new DHPrivateKeySpec(k.getX(), k.getParams().getP(), k.getParams().getG());
       }
       else if (spec.isAssignableFrom(DHPublicKeySpec.class) && key instanceof DHPublicKey)
       {
           DHPublicKey k = (DHPublicKey)key;
           
           return new DHPublicKeySpec(k.getY(), k.getParams().getP(), k.getParams().getG());
       }

        throw new RuntimeException("not implemented yet " + key + " " + spec);
    }

    protected Key engineTranslateKey(
        Key    key)
        throws InvalidKeyException
    {
        if (key instanceof RSAPublicKey)
        {
            return new JCERSAPublicKey((RSAPublicKey)key);
        }
        else if (key instanceof RSAPrivateCrtKey)
        {
            return new JCERSAPrivateCrtKey((RSAPrivateCrtKey)key);
        }
        else if (key instanceof RSAPrivateKey)
        {
            return new JCERSAPrivateKey((RSAPrivateKey)key);
        }
        else if (key instanceof DHPublicKey)
        {
            if (elGamalFactory)
            {
                return new JCEElGamalPublicKey((DHPublicKey)key);
            }
            else
            {
                return new JCEDHPublicKey((DHPublicKey)key);
            }
        }
        else if (key instanceof DHPrivateKey)
        {
            if (elGamalFactory)
            {
                return new JCEElGamalPrivateKey((DHPrivateKey)key);
            }
            else
            {
                return new JCEDHPrivateKey((DHPrivateKey)key);
            }
        }
        else if (key instanceof DSAPublicKey)
        {
            return new JDKDSAPublicKey((DSAPublicKey)key);
        }
        else if (key instanceof DSAPrivateKey)
        {
            return new JDKDSAPrivateKey((DSAPrivateKey)key);
        }
        else if (key instanceof ElGamalPublicKey)
        {
            return new JCEElGamalPublicKey((ElGamalPublicKey)key);
        }
        else if (key instanceof ElGamalPrivateKey)
        {
            return new JCEElGamalPrivateKey((ElGamalPrivateKey)key);
        }

        throw new InvalidKeyException("key type unknown");
    }

    /**
     * create a public key from the given DER encoded input stream. 
     */ 
    static PublicKey createPublicKeyFromDERStream(
        InputStream         in)
        throws IOException
    {
        return createPublicKeyFromPublicKeyInfo(
                new SubjectPublicKeyInfo((ASN1Sequence)(new ASN1InputStream(in).readObject())));
    }

    /**
     * create a public key from the given public key info object.
     */ 
    static PublicKey createPublicKeyFromPublicKeyInfo(
        SubjectPublicKeyInfo         info)
    {
        AlgorithmIdentifier     algId = info.getAlgorithmId();
        
        if (algId.getObjectId().equals(PKCSObjectIdentifiers.rsaEncryption)
            || algId.getObjectId().equals(X509ObjectIdentifiers.id_ea_rsa))
        {
              return new JCERSAPublicKey(info);
        }
        else if (algId.getObjectId().equals(PKCSObjectIdentifiers.dhKeyAgreement))
        {
              return new JCEDHPublicKey(info);
        }
        else if (algId.getObjectId().equals(X9ObjectIdentifiers.dhpublicnumber))
        {
              return new JCEDHPublicKey(info);
        }
        else if (algId.getObjectId().equals(OIWObjectIdentifiers.elGamalAlgorithm))
        {
              return new JCEElGamalPublicKey(info);
        }
        else if (algId.getObjectId().equals(X9ObjectIdentifiers.id_dsa))
        {
              return new JDKDSAPublicKey(info);
        }
        else if (algId.getObjectId().equals(OIWObjectIdentifiers.dsaWithSHA1))
        {
              return new JDKDSAPublicKey(info);
        }
        else if (algId.getObjectId().equals(X9ObjectIdentifiers.id_ecPublicKey))
        {
              return new JCEECPublicKey(info);
        }
        else if (algId.getObjectId().equals(CryptoProObjectIdentifiers.gostR3410_94))
        {
              return new JDKGOST3410PublicKey(info);
        }
        else if (algId.getObjectId().equals(CryptoProObjectIdentifiers.gostR3410_2001))
        {
              return new JCEECPublicKey(info);
        }
        else
        {
            throw new RuntimeException("algorithm identifier in key not recognised");
        }
    }

    /**
     * create a private key from the given DER encoded input stream. 
     */ 
    static PrivateKey createPrivateKeyFromDERStream(
        InputStream         in)
        throws IOException
    {
        return createPrivateKeyFromPrivateKeyInfo(
                new PrivateKeyInfo((ASN1Sequence)(new ASN1InputStream(in).readObject())));
    }

    /**
     * create a private key from the given public key info object.
     */ 
    static PrivateKey createPrivateKeyFromPrivateKeyInfo(
        PrivateKeyInfo      info)
    {
        AlgorithmIdentifier     algId = info.getAlgorithmId();
        
        if (algId.getObjectId().equals(PKCSObjectIdentifiers.rsaEncryption))
        {
              return new JCERSAPrivateCrtKey(info);
        }
        else if (algId.getObjectId().equals(PKCSObjectIdentifiers.dhKeyAgreement))
        {
              return new JCEDHPrivateKey(info);
        }
        else if (algId.getObjectId().equals(OIWObjectIdentifiers.elGamalAlgorithm))
        {
              return new JCEElGamalPrivateKey(info);
        }
        else if (algId.getObjectId().equals(X9ObjectIdentifiers.id_dsa))
        {
              return new JDKDSAPrivateKey(info);
        }
        else if (algId.getObjectId().equals(X9ObjectIdentifiers.id_ecPublicKey))
        {
              return new JCEECPrivateKey(info);
        }
        else if (algId.getObjectId().equals(CryptoProObjectIdentifiers.gostR3410_94))
        {
              return new JDKGOST3410PrivateKey(info);
        }
        else if (algId.getObjectId().equals(CryptoProObjectIdentifiers.gostR3410_2001))
        {
              return new JCEECPrivateKey(info);
        }
        else
        {
            throw new RuntimeException("algorithm identifier in key not recognised");
        }
    }

    public static class RSA
        extends JDKKeyFactory
    {
        public RSA()
        {
        }

        protected PrivateKey engineGeneratePrivate(
            KeySpec    keySpec)
            throws InvalidKeySpecException
        {
            if (keySpec instanceof PKCS8EncodedKeySpec)
            {
                try
                {
                    return JDKKeyFactory.createPrivateKeyFromDERStream(
                                new ByteArrayInputStream(((PKCS8EncodedKeySpec)keySpec).getEncoded()));
                }
                catch (Exception e)
                {
                    //
                    // in case it's just a RSAPrivateKey object...
                    //
                    try
                    {
                        return new JCERSAPrivateCrtKey(
                            new RSAPrivateKeyStructure(
                                (ASN1Sequence)new ASN1InputStream(new ByteArrayInputStream(((PKCS8EncodedKeySpec)keySpec).getEncoded())).readObject()));
                    }
                    catch (Exception ex)
                    {
                        throw new InvalidKeySpecException(ex.toString());
                    }
                }
            }
            else if (keySpec instanceof RSAPrivateCrtKeySpec)
            {
                return new JCERSAPrivateCrtKey((RSAPrivateCrtKeySpec)keySpec);
            }
            else if (keySpec instanceof RSAPrivateKeySpec)
            {
                return new JCERSAPrivateKey((RSAPrivateKeySpec)keySpec);
            }
    
            throw new InvalidKeySpecException("Unknown KeySpec type.");
        }
    
        protected PublicKey engineGeneratePublic(
            KeySpec    keySpec)
            throws InvalidKeySpecException
        {
            if (keySpec instanceof X509EncodedKeySpec)
            {
                try
                {
                    return JDKKeyFactory.createPublicKeyFromDERStream(
                                new ByteArrayInputStream(((X509EncodedKeySpec)keySpec).getEncoded()));
                }
                catch (Exception e)
                {
                    throw new InvalidKeySpecException(e.toString());
                }
            }
            else if (keySpec instanceof RSAPublicKeySpec)
            {
                return new JCERSAPublicKey((RSAPublicKeySpec)keySpec);
            }
    
            throw new InvalidKeySpecException("Unknown KeySpec type.");
        }
    }

    public static class DH
        extends JDKKeyFactory
    {
        public DH()
        {
        }

        protected PrivateKey engineGeneratePrivate(
            KeySpec    keySpec)
            throws InvalidKeySpecException
        {
            if (keySpec instanceof PKCS8EncodedKeySpec)
            {
                try
                {
                    return JDKKeyFactory.createPrivateKeyFromDERStream(
                                new ByteArrayInputStream(((PKCS8EncodedKeySpec)keySpec).getEncoded()));
                }
                catch (Exception e)
                {
                    throw new InvalidKeySpecException(e.toString());
                }
            }
            else if (keySpec instanceof DHPrivateKeySpec)
            {
                return new JCEDHPrivateKey((DHPrivateKeySpec)keySpec);
            }
    
            throw new InvalidKeySpecException("Unknown KeySpec type.");
        }
    
        protected PublicKey engineGeneratePublic(
            KeySpec    keySpec)
            throws InvalidKeySpecException
        {
            if (keySpec instanceof X509EncodedKeySpec)
            {
                try
                {
                    return JDKKeyFactory.createPublicKeyFromDERStream(
                                new ByteArrayInputStream(((X509EncodedKeySpec)keySpec).getEncoded()));
                }
                catch (Exception e)
                {
                    throw new InvalidKeySpecException(e.toString());
                }
            }
            else if (keySpec instanceof DHPublicKeySpec)
            {
                return new JCEDHPublicKey((DHPublicKeySpec)keySpec);
            }
    
            throw new InvalidKeySpecException("Unknown KeySpec type.");
        }
    }

    public static class DSA
        extends JDKKeyFactory
    {
        public DSA()
        {
        }

        protected PrivateKey engineGeneratePrivate(
            KeySpec    keySpec)
            throws InvalidKeySpecException
        {
            if (keySpec instanceof PKCS8EncodedKeySpec)
            {
                try
                {
                    return JDKKeyFactory.createPrivateKeyFromDERStream(
                                new ByteArrayInputStream(((PKCS8EncodedKeySpec)keySpec).getEncoded()));
                }
                catch (Exception e)
                {
                    throw new InvalidKeySpecException(e.toString());
                }
            }
            else if (keySpec instanceof DSAPrivateKeySpec)
            {
                return new JDKDSAPrivateKey((DSAPrivateKeySpec)keySpec);
            }
    
            throw new InvalidKeySpecException("Unknown KeySpec type.");
        }
    
        protected PublicKey engineGeneratePublic(
            KeySpec    keySpec)
            throws InvalidKeySpecException
        {
            if (keySpec instanceof X509EncodedKeySpec)
            {
                try
                {
                    return JDKKeyFactory.createPublicKeyFromDERStream(
                                new ByteArrayInputStream(((X509EncodedKeySpec)keySpec).getEncoded()));
                }
                catch (Exception e)
                {
                    throw new InvalidKeySpecException(e.toString());
                }
            }
            else if (keySpec instanceof DSAPublicKeySpec)
            {
                return new JDKDSAPublicKey((DSAPublicKeySpec)keySpec);
            }
    
            throw new InvalidKeySpecException("Unknown KeySpec type.");
        }
    }

    public static class GOST3410
        extends JDKKeyFactory
    {
        public GOST3410()
        {
        }
        
        protected PrivateKey engineGeneratePrivate(
                KeySpec    keySpec)
        throws InvalidKeySpecException
        {
            if (keySpec instanceof PKCS8EncodedKeySpec)
            {
                try
                {
                    return JDKKeyFactory.createPrivateKeyFromDERStream(
                            new ByteArrayInputStream(((PKCS8EncodedKeySpec)keySpec).getEncoded()));
                }
                catch (Exception e)
                {
                    throw new InvalidKeySpecException(e.toString());
                }
            }
            else if (keySpec instanceof GOST3410PrivateKeySpec)
            {
                return new JDKGOST3410PrivateKey((GOST3410PrivateKeySpec)keySpec);
            }
            
            throw new InvalidKeySpecException("Unknown KeySpec type.");
        }
        
        protected PublicKey engineGeneratePublic(
                KeySpec    keySpec)
        throws InvalidKeySpecException
        {
            if (keySpec instanceof X509EncodedKeySpec)
            {
                try
                {
                    return JDKKeyFactory.createPublicKeyFromDERStream(
                            new ByteArrayInputStream(((X509EncodedKeySpec)keySpec).getEncoded()));
                }
                catch (Exception e)
                {
                    throw new InvalidKeySpecException(e.toString());
                }
            }
            else if (keySpec instanceof GOST3410PublicKeySpec)
            {
                return new JDKGOST3410PublicKey((GOST3410PublicKeySpec)keySpec);
            }
            
            throw new InvalidKeySpecException("Unknown KeySpec type.");
        }
    }
    
    public static class ElGamal
        extends JDKKeyFactory
    {
        public ElGamal()
        {
            elGamalFactory = true;
        }

        protected PrivateKey engineGeneratePrivate(
            KeySpec    keySpec)
            throws InvalidKeySpecException
        {
            if (keySpec instanceof PKCS8EncodedKeySpec)
            {
                try
                {
                    return JDKKeyFactory.createPrivateKeyFromDERStream(
                                new ByteArrayInputStream(((PKCS8EncodedKeySpec)keySpec).getEncoded()));
                }
                catch (Exception e)
                {
                    throw new InvalidKeySpecException(e.toString());
                }
            }
            else if (keySpec instanceof ElGamalPrivateKeySpec)
            {
                return new JCEElGamalPrivateKey((ElGamalPrivateKeySpec)keySpec);
            }
    
            throw new InvalidKeySpecException("Unknown KeySpec type.");
        }
    
        protected PublicKey engineGeneratePublic(
            KeySpec    keySpec)
            throws InvalidKeySpecException
        {
            if (keySpec instanceof X509EncodedKeySpec)
            {
                try
                {
                    return JDKKeyFactory.createPublicKeyFromDERStream(
                                new ByteArrayInputStream(((X509EncodedKeySpec)keySpec).getEncoded()));
                }
                catch (Exception e)
                {
                    throw new InvalidKeySpecException(e.toString());
                }
            }
            else if (keySpec instanceof ElGamalPublicKeySpec)
            {
                return new JCEElGamalPublicKey((ElGamalPublicKeySpec)keySpec);
            }
    
            throw new InvalidKeySpecException("Unknown KeySpec type.");
        }
    }

    public static class EC
        extends JDKKeyFactory
    {
        String  algorithm;

        public EC()
        {
            this("EC");
        }

        public EC(
            String  algorithm)
        {
            this.algorithm = algorithm;
        }

        protected PrivateKey engineGeneratePrivate(
            KeySpec    keySpec)
            throws InvalidKeySpecException
        {
            if (keySpec instanceof PKCS8EncodedKeySpec)
            {
                try
                {
                    return JDKKeyFactory.createPrivateKeyFromDERStream(
                                new ByteArrayInputStream(((PKCS8EncodedKeySpec)keySpec).getEncoded()));
                }
                catch (Exception e)
                {
                    throw new InvalidKeySpecException(e.toString());
                }
            }
            else if (keySpec instanceof ECPrivateKeySpec)
            {
                return new JCEECPrivateKey(algorithm, (ECPrivateKeySpec)keySpec);
            }
    
            throw new InvalidKeySpecException("Unknown KeySpec type.");
        }
    
        protected PublicKey engineGeneratePublic(
            KeySpec    keySpec)
            throws InvalidKeySpecException
        {
            if (keySpec instanceof X509EncodedKeySpec)
            {
                try
                {
                    return JDKKeyFactory.createPublicKeyFromDERStream(
                                new ByteArrayInputStream(((X509EncodedKeySpec)keySpec).getEncoded()));
                }
                catch (Exception e)
                {
                    throw new InvalidKeySpecException(e.toString());
                }
            }
            else if (keySpec instanceof ECPublicKeySpec)
            {
                return new JCEECPublicKey(algorithm, (ECPublicKeySpec)keySpec);
            }
    
            throw new InvalidKeySpecException("Unknown KeySpec type.");
        }
    }

    public static class ECDSA
        extends EC
    {
        public ECDSA()
        {
            super("ECDSA");
        }
    }
    
    public static class ECGOST3410
      extends EC
    {
        public ECGOST3410()
        {
            super("ECGOST3410");
        }
    }
    
    public static class ECDH
        extends EC
    {
        public ECDH()
        {
            super("ECDH");
        }
    }

    public static class ECDHC
        extends EC
    {
        public ECDHC()
        {
            super("ECDHC");
        }
    }
}
