//////////////////////////////////////////////////////////////////////////// 
// 
// Copyright (C) DSTC Pty Ltd (ACN 052 372 577) 1993, 1994, 1995.
// Unpublished work.  All Rights Reserved.
// 
// The software contained on this media is the property of the
// DSTC Pty Ltd.  Use of this software is strictly in accordance
// with the license agreement in the accompanying LICENSE.DOC 
// file. If your distribution of this software does not contain 
// a LICENSE.DOC file then you have no rights to use this 
// software in any manner and should contact DSTC at the address 
// below to determine an appropriate licensing arrangement.
// 
//      DSTC Pty Ltd
//      Level 7, GP South
//      University of Queensland
//      St Lucia, 4072
//      Australia
//      Tel: +61 7 3365 4310
//      Fax: +61 7 3365 4311
//      Email: jcsi@dstc.qut.edu.au
// 
// This software is being provided "AS IS" without warranty of
// any kind.  In no event shall DSTC Pty Ltd be liable for
// damage of any kind arising out of or in connection with
// the use or performance of this software.
// 
//////////////////////////////////////////////////////////////////////////// 

package com.dstc.security.provider;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.security.KeyFactorySpi;
import java.security.Key;
import java.security.InvalidKeyException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.KeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPrivateKeySpec;

import javax.crypto.Cipher;
import javax.crypto.BadPaddingException;
import javax.crypto.spec.PBEParameterSpec;

import com.dstc.security.x509.AlgorithmId;
import com.dstc.security.x509.SubjectPublicKeyInfo;
import com.dstc.security.pkcs8.PrivateKeyInfo;
import com.dstc.security.pkcs8.EncryptedPrivateKeyInfo;
import com.dstc.security.pkcs8.PKCS8EncryptedPrivateKey;
import com.dstc.security.pkcs8.PKCS5KeyDerivation;

/**
 * <p>Implements a Factory class for public or private keys to use with 
 * the RSA signature algorithm.
 *
 * @see java.security.KeyFactory
 * @see com.dstc.security.provider.RSAPublicKey
 * @see com.dstc.security.provider.RSAPrivateKey
 * @see com.dstc.security.provider.RSA
 *
 * @version 0.98, 98/07/01
 * @author Ming Yung
 */
public class RSAKeyFactory extends KeyFactorySpi
{
  protected PublicKey engineGeneratePublic(KeySpec keySpec) 
    throws InvalidKeySpecException
  {
    if(keySpec instanceof RSAPublicKeySpec)
    {
      RSAPublicKeySpec spec = (RSAPublicKeySpec)keySpec;

      return new RSAPublicKey(spec.getModulus(), spec.getPublicExponent());
    }

    if(keySpec instanceof X509EncodedKeySpec)
    {
      try
      {
        SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
          ((X509EncodedKeySpec)keySpec).getEncoded());
        return new RSAPublicKey(info.getEncodedKey());
      }
      catch (Exception e)
      {
        e.printStackTrace();
        throw new InvalidKeySpecException("Bad key spec");
      }
    }
      
    throw new InvalidKeySpecException("Cannot convert to RSA Public Key");
  }

  protected PrivateKey engineGeneratePrivate(KeySpec keySpec)
    throws InvalidKeySpecException
  {
    if(keySpec instanceof RSAPrivateCrtKeySpec)
    {
      RSAPrivateCrtKeySpec spec = (RSAPrivateCrtKeySpec)keySpec;

      return new RSAPrivateCrtKey(spec.getModulus(), spec.getPublicExponent(),
                                  spec.getPrivateExponent(), spec.getPrimeP(),
                                  spec.getPrimeQ());
    }

    if(keySpec instanceof RSAPrivateKeySpec)
    {
      RSAPrivateKeySpec spec = (RSAPrivateKeySpec)keySpec;

      return new RSAPrivateKey(spec.getModulus(), spec.getPrivateExponent());
    }

    //Unencrypted
    if(keySpec instanceof X509EncodedKeySpec)
    {
      try
      {
        return new RSAPrivateCrtKey(((X509EncodedKeySpec)keySpec).getEncoded());
      }
      catch (Exception e)
      {
        e.printStackTrace();
        throw new InvalidKeySpecException("Bad key spec");
      }
    }

    if(keySpec instanceof PKCS8EncodedKeySpec)
    {
      try
      {
        PrivateKeyInfo pki 
          = new PrivateKeyInfo(((PKCS8EncodedKeySpec)keySpec).getEncoded());

        return new RSAPrivateCrtKey(pki.getEncodedKey());
      }
      catch (Exception e)
      {
        //ignore
      }

      try
      {
        PKCS8EncryptedPrivateKey encKey 
          = new PKCS8EncryptedPrivateKey(
              ((PKCS8EncodedKeySpec)keySpec).getEncoded());

        //Prompt for password and generate PBEKey
        System.err.print("Enter password: ");
        InputStreamReader isr = new InputStreamReader(System.in);
        BufferedReader in = new BufferedReader(isr);
        PKCS5KeyDerivation keyStyle= new PKCS5KeyDerivation();
        encKey.decrypt(in.readLine(), keyStyle);
        return encKey.getPrivateKey();
      }
      catch (Exception e)
      {
        e.printStackTrace();
        throw new InvalidKeySpecException("Bad key spec: Wrong password?");
      }
    }

    throw new InvalidKeySpecException("Cannot convert to RSA Private Key");
  }

  protected KeySpec engineGetKeySpec(Key key, Class keySpec) 
    throws InvalidKeySpecException
  {
    if (!key.getAlgorithm().equals("RSA"))
      throw new InvalidKeySpecException("Not an RSA Key");

    if (keySpec.getName().equals("java.security.spec.X509EncodedKeySpec"))
    {
      return new X509EncodedKeySpec(key.getEncoded());
    }

    if (keySpec.getName().equals("java.security.spec.RSAPublicKeySpec"))
    {
      return new RSAPublicKeySpec(((RSAPublicKey)key).getModulus(), 
                                  ((RSAPublicKey)key).getPublicExponent());
    }

    throw new InvalidKeySpecException("Cannot convert to this key spec");
  }

  protected Key engineTranslateKey(Key key) throws InvalidKeyException
  {
    throw new InvalidKeyException("Not yet supported");
  }
}
