/***********************************************************************
 * 
 *  Copyright (C) 2005-2006 Novell, Inc. All Rights Reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; version 2.1
 *  of the License.
 *
 *  This library 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
 *  Library Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, Novell, Inc.
 * 
 *  To contact Novell about this file by physical or electronic mail, 
 *  you may find current contact information at www.novell.com.
 * 
 ***********************************************************************/

using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
#if LINUX
using Mono.Unix;
#endif
using sscs.common;
using sscs.constants;

namespace sscs.crypto
{
    public class CASACrypto
    {

        private const int SALTSIZE = 64;
        private const int ITERATION_COUNT = 1000;
	private const int HASH_SIZE = 32;

        internal static byte[] Generate16ByteKeyFromString(string sTheString)
        {
            byte[] baKey = new byte[16]; //return value
            try
            {                                
                Rfc2898DeriveBytes pkcs5 = new Rfc2898DeriveBytes(sTheString, SALTSIZE, ITERATION_COUNT);
                baKey = pkcs5.GetBytes(16);                    
            }            
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
                CSSSLogger.DbgLog("Key generation failed");
                baKey = null;
            }
            return baKey;
        }
        
        internal static bool StoreKeySetUsingMasterPasscode(byte[] key, 
            byte[] IV, byte[] baMasterPasscode, string fileName)
        {
            bool bRet = false;
            FileStream fsEncrypt = null;
            CryptoStream csEncrypt = null;
            try
            {

                //Get an encryptor.
                RijndaelManaged myRijndael = new RijndaelManaged();
                ICryptoTransform encryptor; 
                encryptor = myRijndael.CreateEncryptor(baMasterPasscode, baMasterPasscode);
                                    
                //Encrypt the data to a file            
                fsEncrypt = new FileStream(fileName, FileMode.Create);

				// make hidden
				File.SetAttributes(fileName, FileAttributes.Hidden);

                SHA256 sha = new SHA256Managed();
                byte[] hash = sha.ComputeHash(key);

                fsEncrypt.Write(hash,0,hash.Length);
                fsEncrypt.Flush();

                csEncrypt = new CryptoStream(fsEncrypt, encryptor, CryptoStreamMode.Write);            
                    
                //Write all data to the crypto stream and flush it.
                csEncrypt.Write(key, 0, key.Length);
                csEncrypt.FlushFinalBlock();
                bRet = true;
            }
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
                CSSSLogger.DbgLog("Unable to store the generated key");
                bRet = false;
            }
            if (csEncrypt != null)
                csEncrypt.Close();
            if( fsEncrypt != null )
                fsEncrypt.Close();
            return bRet;
        }    

        internal static byte[] GetKeySetFromFile(byte[] baMasterPasscode, 
            string fileName ) 
        {
            byte[] baSavedKey = null;
            FileStream fsDecrypt = null;
            CryptoStream csDecrypt = null;
			
            try
            {
#if LINUX
				UnixFileInfo fsTest = new UnixFileInfo (fileName);
                if((fsTest == null) || !(fsTest.Exists) || fsTest.IsSymbolicLink)
#else
                if(!File.Exists(fileName))
#endif
                {
                    return null;
                }
                
                /* Get a decryptor that uses the same key and IV 
                 * as the encryptor.
                 */

                RijndaelManaged myRijndael = new RijndaelManaged();
                ICryptoTransform decryptor = myRijndael.CreateDecryptor(baMasterPasscode, baMasterPasscode);
                //Now decrypt             
                fsDecrypt = new FileStream(fileName, FileMode.Open);

                byte[] storedHash = new byte[32];
                fsDecrypt.Read(storedHash,0,storedHash.Length);

                csDecrypt = new CryptoStream(fsDecrypt, decryptor, CryptoStreamMode.Read);            
                baSavedKey = new byte[32];
  
                //Read the data out of the crypto stream.
                csDecrypt.Read(baSavedKey, 0, baSavedKey.Length);

                SHA256 sha = new SHA256Managed();
                byte[] newHash = sha.ComputeHash(baSavedKey);
                for( int i = 0 ; i < 32; i++ )
                {
                    if(storedHash[i] != newHash[i])
                    {
                        CSSSLogger.DbgLog("Hash doesnot match");
                        csDecrypt.Close();
                        fsDecrypt.Close();                        
                        return null;
                    }
                }
            }
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
                CSSSLogger.DbgLog("Unable to get the stored key");
                baSavedKey = null;
            }

            
            if (csDecrypt != null)
                csDecrypt.Close();

            if ( fsDecrypt != null )
                fsDecrypt.Close();
             

            return baSavedKey;
        }

        internal static void EncryptDataAndWriteToFile(byte[] xmlData, 
            byte[] key, string fileName)
        {
            FileStream fsEncrypt = null;
            CryptoStream csEncrypt = null;            
            try
            {
                byte[] IV = new byte[16];
                for(int z = 0 ; z < 16; z++ )
                    IV[z] = key[z];

                //Get an encryptor.
                RijndaelManaged myRijndael = new RijndaelManaged();
                ICryptoTransform encryptor = myRijndael.CreateEncryptor(key, IV);
             
                //Encrypt the data to a file            
                fsEncrypt = new FileStream(fileName, FileMode.Create);

				// make hidden
				File.SetAttributes(fileName, FileAttributes.Hidden);

                SHA256 sha = new SHA256Managed();

                byte[] hash = sha.ComputeHash(xmlData);
            
                fsEncrypt.Write(hash,0,hash.Length);           
                fsEncrypt.Flush();
      
                csEncrypt = new CryptoStream(fsEncrypt, encryptor, CryptoStreamMode.Write);            
                    
                //Write all data to the crypto stream and flush it.
                csEncrypt.Write(xmlData, 0, xmlData.Length);
                csEncrypt.FlushFinalBlock();
            }
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
                CSSSLogger.DbgLog("Encrypting and storing to file failed.");
            }
            if (csEncrypt != null)
                csEncrypt.Close();
            if( fsEncrypt != null )
                fsEncrypt.Close();
        }

        internal static byte[] ReadFileAndDecryptData(byte[] key, 
            string fileName)
        {
            FileStream fsDecrypt = null;
            CryptoStream csDecrypt = null;
            try
            {
                byte[] IV = new byte[16];
                for(int z = 0 ; z < 16; z++ )
                    IV[z] = key[z];

                //Get a decryptor that uses the same key and IV as the encryptor.
                RijndaelManaged myRijndael = new RijndaelManaged();
                ICryptoTransform decryptor = myRijndael.CreateDecryptor(key, IV);
#if LINUX
				UnixFileInfo fsTest = new UnixFileInfo (fileName);
                if((fsTest == null) || !(fsTest.Exists) || fsTest.IsSymbolicLink)
#else
				if(!File.Exists(fileName))
#endif
				{
                    return null;
                }

                //Now decrypt             
                fsDecrypt = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);                        
                byte[] storedHash = new byte[HASH_SIZE];
                fsDecrypt.Read(storedHash,0,storedHash.Length);

                csDecrypt = new CryptoStream(fsDecrypt, decryptor, CryptoStreamMode.Read);            
				if(fsDecrypt.Length  < HASH_SIZE )
				{
					csDecrypt.Close();
								fsDecrypt.Close();                        
								return null;
				}

				ulong fileLen = (ulong)(fsDecrypt.Length - HASH_SIZE);
				byte[] fromEncrypt = new byte[fileLen];

                //Read the data out of the crypto stream.
                int bytesRead = csDecrypt.Read(fromEncrypt, 0, fromEncrypt.Length);
                byte[] tmpEncrypt = new byte[bytesRead];
                for(int i = 0 ; i < bytesRead; i++ )
                    tmpEncrypt[i] = fromEncrypt[i];

                SHA256 sha = new SHA256Managed();
                byte[] newHash = sha.ComputeHash(tmpEncrypt);

                for( int i = 0 ; i < 32; i++ )
                {
                    if(storedHash[i] != newHash[i])
                    {
                        CSSSLogger.DbgLog("Hash doesnot match");
                        csDecrypt.Close();
                        fsDecrypt.Close();                        
                        return null;
                    }
                }

                csDecrypt.Close();
                fsDecrypt.Close();                
                return tmpEncrypt;
            }
            catch(Exception e)
            {
                Console.WriteLine(e.ToString());
            }
            if (csDecrypt != null)
            {
                csDecrypt.Close();
            }
            if( fsDecrypt != null )
            {
                fsDecrypt.Close();
            }            
            return null;
        }

        /* The methods EncryptData() and DecryptData() would be 
         * required when we use a database to store secrets.
         */
 
        /* Encrypts the data with the key and returns the encrypted buffer.
         */

        internal static byte[] EncryptData(byte[] data, byte[] key) 
        {

            try
            {
                byte[] IV = new byte[16];
                int i = 0;
                for(i = 0 ; i < 16; i++ )
                    IV[i] = key[i];

                //Get an encryptor.
                RijndaelManaged myRijndael = new RijndaelManaged();
                ICryptoTransform encryptor = myRijndael.CreateEncryptor(key, IV);
                MemoryStream ms1 = new MemoryStream();
                CryptoStream csEncrypt = new CryptoStream(ms1, encryptor, CryptoStreamMode.Write);            
                    
                //Write all data to the crypto stream and flush it.
                csEncrypt.Write(data, 0, data.Length);
                csEncrypt.FlushFinalBlock();
                return ms1.ToArray();
            }   
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
            }
            return null;
        }

        /* Decrypts the buffer(encrypted) with the key and returns the
         * decrypted data.
         */      

        internal static byte[] DecryptData(byte[] buffer, byte[] key)
        {
            try
            {
                byte[] IV = new byte[16];
                for(int i = 0 ; i < 16; i++ )
                    IV[i] = key[i];
                //Get a decryptor that uses the same key and IV as the encryptor.
                RijndaelManaged myRijndael = new RijndaelManaged();
                ICryptoTransform decryptor = myRijndael.CreateDecryptor(key, IV);
                MemoryStream ms1 = new MemoryStream(buffer);           
                CryptoStream csDecrypt = new CryptoStream(ms1, decryptor, CryptoStreamMode.Read);            
                byte[] fromEncrypt = new byte[buffer.Length];
                //Read the data out of the crypto stream.
                csDecrypt.Read(fromEncrypt, 0, fromEncrypt.Length);
                return fromEncrypt;
            }
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
            }
            return null;            
        }

        /* This method checks if we can get the master passcode by
         * decrypting the passwds file ( where we store all possible
         * passwds cross-encrypted. 
         * 
         * TBD : As we are storing master passcode and the keys in 2
         * different files, we need to take care of cases when 1 of the files
         * is deleted.
         */

        internal static bool CheckIfMasterPasscodeIsAvailable(string desktopPasswd, string fileName)
        {
            return (File.Exists(fileName));
        }

        internal static byte[] GetMasterPasscode(string desktopPasswd, string fileName)
        {
            byte[] mp = DecryptMasterPasscodeUsingString(desktopPasswd, fileName);
            return mp;
        }

        /* TBD - There must be a way, where we establish the integrity of
         * the files where we store the keys and master passcode. 
         * Use a marker ?
         */

        // Used to save the MasterPasscode encrypted with Desktop login, etc
        internal static void EncryptAndStoreMasterPasscodeUsingString(
            byte[] baMasterPasscode, 
            string passwd,
            string fileName)
        {
            FileStream fsEncrypt = null;
            CryptoStream csEncrypt = null;
            try
            {
                if(File.Exists(fileName))
                    File.Delete(fileName);
                byte[] baKey = Generate16ByteKeyFromString(passwd);
                

                //Get an encryptor.
                RijndaelManaged myRijndael = new RijndaelManaged();
                ICryptoTransform encryptor;
                encryptor = myRijndael.CreateEncryptor(baKey, baKey);
  
                //Encrypt the data to a file
                fsEncrypt = new FileStream(fileName,FileMode.Create);

				// make hidden
				File.SetAttributes(fileName, FileAttributes.Hidden);

                csEncrypt = new CryptoStream(fsEncrypt, encryptor, 
                    CryptoStreamMode.Write);

                //Write all data to the crypto stream and flush it.
                
                csEncrypt.Write(baMasterPasscode, 0, baMasterPasscode.Length);
                csEncrypt.FlushFinalBlock();
                csEncrypt.Close();
                fsEncrypt.Close();                
            }
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
            }
            if (csEncrypt != null)
            {
                csEncrypt.Close();
            }
            if( fsEncrypt != null )
            {
                fsEncrypt.Close();
            }
            
        }

        public static byte[] DecryptMasterPasscodeUsingString(string passwd,
            string fileName)
        {
            FileStream fsDecrypt = null;
            CryptoStream csDecrypt = null;
            byte[] baSavedMasterPasscode = null;
            try
            {
                byte[] baKey = Generate16ByteKeyFromString(passwd);

                /* Get a decryptor that uses the same key and 
                 * IV as the encryptor. 
                 */
                RijndaelManaged myRijndael = new RijndaelManaged();		
                ICryptoTransform decryptor = myRijndael.CreateDecryptor(baKey,
                    baKey);
                //Now decrypt
#if LINUX
				UnixFileInfo fsTest = new UnixFileInfo (fileName);
                if((fsTest == null) || !(fsTest.Exists) || fsTest.IsSymbolicLink)
#else
				if(!File.Exists(fileName))
#endif
				{
                    return null;
                }

                fsDecrypt = new FileStream(fileName, FileMode.Open);
                csDecrypt = new CryptoStream(fsDecrypt, decryptor,
                    CryptoStreamMode.Read);
                baSavedMasterPasscode = new byte[16];

                //Read the data out of the crypto stream.
                csDecrypt.Read(baSavedMasterPasscode, 0, 16);
            }
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
                CSSSLogger.DbgLog("Unable to decrypt master passode");
                baSavedMasterPasscode = null;
            }

            
            if (csDecrypt != null)
                csDecrypt.Close();

            if ( fsDecrypt != null )
                fsDecrypt.Close();
             

            return baSavedMasterPasscode;
        }

        internal static byte[] GetMasterPasscodeUsingMasterPasswd(
            string mPasswd,
            string fileName)
        {
            byte[] baMasterPasscode;
            try
            {
                if(File.Exists(fileName))
                {
                    /* Decrypt the passcode from the file using master passwd.
                     * and return the decrypted passcode.
                     */
                    baMasterPasscode = DecryptMasterPasscodeUsingString(mPasswd,
                        fileName);   
                    return baMasterPasscode;
                }
                else
                    return null;
            }
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
                CSSSLogger.DbgLog("Failed to get master passcode from master password.");
            }
            return null;
        }

        internal static byte[] GetMasterPasscodeUsingDesktopPasswd(
            string desktopPasswd,
            string fileName)
        {
            byte[] passcode;
            try
            {
                if(File.Exists(fileName))
                {
                    /* Decrypt the passcode from the file using desktop passwd.
                     * and return the decrypted passcode.
                     */
                    passcode = DecryptMasterPasscodeUsingString(desktopPasswd,
                        fileName);   
                    return passcode;
                
                }
                else
                    return null;
            }
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
                CSSSLogger.DbgLog("Failed to get master passcode using desktop passwd");
            }
            return null;
        }
        //internal static string GenerateMasterPasscodeUsingDesktopPasswd(
        internal static byte[] GenerateMasterPasscodeUsingString(
            string desktopPasswd,
            string fileName,
            string validationFile,
            UserIdentifier userId
            )
        {
            try
            {
                byte[] baPasscode;
                // use AES to generate a random 16 byte key;                
                RijndaelManaged myRijndael = new RijndaelManaged();                
                myRijndael.KeySize = 128;
                //Create a new key and initialization vector.
                myRijndael.GenerateKey();
                baPasscode = myRijndael.Key;

                EncryptAndStoreMasterPasscodeUsingString(baPasscode,
                    desktopPasswd,
                    fileName);                                              
                EncryptDataAndWriteToFile(
                    Encoding.Default.GetBytes(
                    ConstStrings.MICASA_VALIDATION_STRING),
                    baPasscode,
                    validationFile);
                return baPasscode;         
            }
            catch(Exception e)
            {
                CSSSLogger.ExpLog(e.ToString());
                CSSSLogger.DbgLog("Generation of master passcode failed.");
            }
            return null;
        }

        public static bool ValidatePasscode(byte[] baPasscode, string fileName)
        {
            /* Here we decrpyt a well known string, throw exception 
             * if not successful
             * A well-known string is encrpyted by the Passcode and saved
             */

            if ((baPasscode == null) || baPasscode.Length < 1 )
                return false;

            try
            {                
                byte[] baString = ReadFileAndDecryptData(baPasscode, fileName);
                string sString = Encoding.Default.GetString(baString);
                char[] trimChars = {'\0'};
                sString = sString.TrimEnd(trimChars);
                if( ConstStrings.MICASA_VALIDATION_STRING.Equals(sString))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch(Exception e)
            {
				CSSSLogger.ExpLog(e.ToString());
				CSSSLogger.DbgLog("Validation of passcode failed.");
			}
			return false;
		}
        
	}
}