/*******************************************************************************
 * This file is part of GECAMed.
 * 
 * GECAMed is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License (L-GPL) as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GECAMed is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License (L-GPL)
 * along with GECAMed.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * GECAMed is Copyrighted by the Centre de Recherche Public Henri Tudor (http://www.tudor.lu)
 * (c) CRP Henri Tudor, Luxembourg, 2008
 *******************************************************************************/
package lu.tudor.santec.gecamed.labo.utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;

import lu.tudor.santec.gecamed.core.utils.Logger;

import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Level;

public class PasswordEncrypter implements Serializable
	{
	private String				m_EncryptionPassword = null;
    private SecretKey           m_SecretKey			 = null;
	
    private static Logger		m_Logger  = new Logger (PasswordEncrypter.class);	
	
//---------------------------------------------------------------------------
//***************************************************************************
//* Constants	                                                            *
//***************************************************************************
//---------------------------------------------------------------------------
   
	private static final long serialVersionUID = 1L;

	private static final int		c_BufferSize		= 4096;
	private static final int		c_MinPasswordLength	= 8;
	private static final String		c_PasswordPadding   = "********";
    private static final String		c_DefaultPassword	= "Ti1dPW4SD";

//---------------------------------------------------------------------------
//***************************************************************************
//* Constructor	                                                            *
//***************************************************************************
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//***************************************************************************
//* Primitives	                                                            *
//***************************************************************************
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
/**
 * Returns a DES encryption Key generated from the encryption password. If
 * no encryption password was set for this instance of the SmimeDecoder, the
 * method will resort to use the default password specified in the code.
 * @return A DES encryption key if successful, <code>null</code> if an
 * error occured while generating the key.
 */
//---------------------------------------------------------------------------

private SecretKey	getSecretKey ()
	{
	DESKeySpec          l_KeySpecification;
    SecretKeyFactory    l_KeyFactory;
    String				l_Password;
    
    l_Password = (m_EncryptionPassword != null)?m_EncryptionPassword:c_DefaultPassword;
    
    try {
        if (m_SecretKey == null)
            {
            l_KeySpecification = new DESKeySpec (l_Password.getBytes());
            l_KeyFactory       = SecretKeyFactory.getInstance ("DES");
            m_SecretKey        = l_KeyFactory.generateSecret (l_KeySpecification);
            }
        else return m_SecretKey;
    	}    
    catch (Exception p_Exception)
        {
        m_Logger.log (Level.FATAL, "Failed to setup secret key!", p_Exception);
        m_SecretKey = null;
        }
    
    return m_SecretKey;	
	}

//---------------------------------------------------------------------------
/**
 * Generates a cipher object destined to be used for encryption. The cipher
 * object depends on DES encryption key generated by getSecretKey () method.
 * @return a cipher object ready to be used for encryption or <code>null</code>
 * if an error occured.
 * @see #getSecretKey()
 */
//---------------------------------------------------------------------------

private Cipher getEncryptionCipher ()
    {
    SecretKey	l_SecretKey;
    Cipher      l_Cipher = null;
   
    try {
        l_SecretKey = this.getSecretKey();
    	if (l_SecretKey != null)
            {
            l_Cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
            l_Cipher.init (Cipher.ENCRYPT_MODE, l_SecretKey);
            }
        }    
    catch (Exception p_Exception)
        {
        m_Logger.log (Level.FATAL, "Failed to setup encryption cipher!", p_Exception);
        }
    
    return l_Cipher;
    }

//---------------------------------------------------------------------------
/**
 * Generates a cipher object destined to be used for decryption. The cipher
 * object depends on DES encryption key generated by getSecretKey () method.
 * @return a cipher object ready to be used for decryption or <code>null</code>
 * if an error occured.
 * @see #getSecretKey()
 */
//---------------------------------------------------------------------------

	private Cipher getDecryptionCipher()
	{
		SecretKey l_SecretKey;
		Cipher l_Cipher = null;
		
		try
		{
			l_SecretKey = this.getSecretKey();
			if (l_SecretKey != null)
			{
				l_Cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
				l_Cipher.init(Cipher.DECRYPT_MODE, l_SecretKey);
			}
		}
		catch (Exception p_Exception)
		{
			m_Logger.log(Level.FATAL, "Failed to setup decryption cipher!", p_Exception);
		}
		
		return l_Cipher;
	}

//---------------------------------------------------------------------------
//***************************************************************************
//* Class Body	                                                            *
//***************************************************************************
//---------------------------------------------------------------------------

public void setEncryptionPassword (String p_Password)
	{
	if (p_Password != null) 
        {
        if (p_Password.length() < c_MinPasswordLength)
            p_Password += c_PasswordPadding.substring (p_Password.length(),
                                                       c_PasswordPadding.length());
         
        m_EncryptionPassword = p_Password;
        m_SecretKey          = null;
        }
	else m_EncryptionPassword = String.valueOf (c_DefaultPassword);
	}

//---------------------------------------------------------------------------

public String getEncryptionPassword ()
	{
	return m_EncryptionPassword;
	
	}
//---------------------------------------------------------------------------

public boolean encryptionPasswordSet ()
	{
	return ((m_EncryptionPassword != null) && (m_EncryptionPassword.length() > 0));	
	}

//---------------------------------------------------------------------------

public String encryptPassword (String p_Password)
	{		
	ByteArrayInputStream	l_Decrypted;	
	CipherOutputStream   	l_CipherStream;
	ByteArrayOutputStream	l_Encrypted;	
	String					l_Password;
	byte []          		l_Buffer;
	int						l_BytesRead;
	
	if ((p_Password == null) || (p_Password.length() == 0)) 
		return null;
	
	l_Decrypted 			= new ByteArrayInputStream (p_Password.getBytes());
	l_Encrypted				= new ByteArrayOutputStream ();
	l_CipherStream			= new CipherOutputStream    (l_Encrypted,this.getEncryptionCipher());
			
	l_Buffer   = new byte [c_BufferSize];
	l_Password = null;
	
	try	{
		do	{
			l_BytesRead = l_Decrypted.read(l_Buffer, 0, c_BufferSize);
			if (l_BytesRead > 0) l_CipherStream.write(l_Buffer, 0, l_BytesRead);
			}
		while (l_BytesRead > 0);
		l_CipherStream.close();
		l_Password = new String (Base64.encodeBase64 (l_Encrypted.toByteArray()),"US-ASCII");
		}
	catch (IOException p_Exception)
		{
		m_Logger.log (Level.FATAL, "Failed to read from encrypted password stream!", p_Exception);	
		l_Encrypted = null;
		}	
		
	return l_Password;
	}

//---------------------------------------------------------------------------

	public String decryptPassword (String p_Password) throws LaboException
	{
		ByteArrayInputStream	l_Encrypted;
		CipherInputStream		l_CipherStream = null;
		ByteArrayOutputStream	l_Decrypted;
		byte[]					l_Buffer;
		int						l_BytesRead;
		
		if ((p_Password == null) || (p_Password.length() == 0))
		{
			m_Logger.log(Level.FATAL, "Password not set");
			throw new LaboException(LaboException.ERROR_PASSWORD_NOT_SET);
		}
		
		try
		{
			l_Encrypted		= new ByteArrayInputStream(Base64.decodeBase64(p_Password.getBytes()));
			l_Decrypted		= new ByteArrayOutputStream();
			l_CipherStream	= new CipherInputStream(l_Encrypted, this.getDecryptionCipher());
			l_Buffer		= new byte[c_BufferSize];
			
			try
			{
				do
				{
					l_BytesRead = l_CipherStream.read(l_Buffer, 0, c_BufferSize);
					if (l_BytesRead > 0)
						l_Decrypted.write(l_Buffer, 0, l_BytesRead);
				}
				while (l_BytesRead > 0);
			}
			catch (IOException p_Exception)
			{
				m_Logger.log(Level.FATAL, "Failed to read from encrypted password stream!", p_Exception);
				throw new LaboException(LaboException.ERROR_READING_PASSWORD);
			}
			
			return l_Decrypted.toString();
		}
		finally
		{
			try
			{
				if (l_CipherStream != null)
					l_CipherStream.close();
			}
			catch (IOException e)
			{
				m_Logger.log(Level.WARN, "Couldn't close cipher stream");
			}
		}
	}
//---------------------------------------------------------------------------
//***************************************************************************
//* End of Class                                                            *
//***************************************************************************
//---------------------------------------------------------------------------

	}
