/*********************************************************************** * * 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, string sFilepath, bool bUseOldMethod) { byte[] baKey = new byte[16]; //return value try { Rfc2898DeriveBytes pkcs5 = new Rfc2898DeriveBytes(sTheString, SALTSIZE, ITERATION_COUNT, bUseOldMethod); 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, GenerateAndSaveIV(fileName, myRijndael)); //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, RetrieveIV(fileName, 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 { //Get an encryptor. RijndaelManaged myRijndael = new RijndaelManaged(); byte[] baIV = GenerateAndSaveIV(fileName, myRijndael); ICryptoTransform encryptor = myRijndael.CreateEncryptor(key, baIV); //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(); #if CLEAR byte[] dup = (byte[])xmlData.Clone(); // write clear file FileStream fsClear = new FileStream(fileName + ".xml", FileMode.Create); fsClear.Write(dup, 0, dup.Length); fsClear.Flush(); fsClear.Close(); #endif 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(); byte[] baIV = RetrieveIV(fileName, IV); ICryptoTransform decryptor = myRijndael.CreateDecryptor(key, baIV); #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; } } try { csDecrypt.Close(); } catch { } try { fsDecrypt.Close(); } catch { } return tmpEncrypt; } catch(Exception e) { CSSSLogger.DbgLog(e.ToString()); } if (csDecrypt != null) { try { csDecrypt.Close(); } catch { } } if( fsDecrypt != null ) { try { fsDecrypt.Close(); } catch { } } 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, false); 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, null, false); //Get an encryptor. RijndaelManaged myRijndael = new RijndaelManaged(); ICryptoTransform encryptor; encryptor = myRijndael.CreateEncryptor(baKey, GenerateAndSaveIV(fileName, myRijndael)); //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, bool bTryOldMethod) { FileStream fsDecrypt = null; CryptoStream csDecrypt = null; byte[] baSavedMasterPasscode = null; try { byte[] baKey = Generate16ByteKeyFromString(passwd, fileName, bTryOldMethod); /* Get a decryptor that uses the same key and * IV as the encryptor. */ RijndaelManaged myRijndael = new RijndaelManaged(); ICryptoTransform decryptor = myRijndael.CreateDecryptor(baKey, RetrieveIV(fileName, 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; } try { if (csDecrypt != null) csDecrypt.Close(); } catch { } if (fsDecrypt != null) fsDecrypt.Close(); return baSavedMasterPasscode; } internal static byte[] GetMasterPasscodeUsingMasterPasswd( string mPasswd, string fileName, bool bUseOldMethod) { 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, bUseOldMethod); 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, bool bUseOldMethod) { 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, bUseOldMethod); 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 */ CSSSLogger.DbgLog("Validate called"); 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)) { CSSSLogger.DbgLog("Passed"); return true; } else { CSSSLogger.DbgLog("Failed"); return false; } } catch(Exception e) { CSSSLogger.ExpLog(e.ToString()); CSSSLogger.DbgLog("Validation of passcode failed."); } return false; } private static byte[] GenerateAndSaveIV(string sFileName, RijndaelManaged theRiManaged) { theRiManaged.GenerateIV(); byte[] baIV = theRiManaged.IV; try { if (File.Exists(sFileName + ".IV")) File.Delete(sFileName + ".IV"); // now save this FileStream fs = new FileStream(sFileName + ".IV", FileMode.Create); fs.Write(baIV, 0, 16); fs.Flush(); fs.Close(); File.SetAttributes(sFileName + ".IV", FileAttributes.Hidden); } catch (Exception e) { CSSSLogger.DbgLog(e.ToString()); } return baIV; } private static byte[] RetrieveIV(string sFileName, byte[] baOrigValue) { byte[] IV = new byte[16]; // check for file existence try { FileStream fs = new FileStream(sFileName + ".IV", FileMode.Open); fs.Read(IV, 0, 16); fs.Close(); return IV; } catch (Exception e) { CSSSLogger.DbgLog(e.ToString()); } // original IV size was 16 bytes, copy that much if (baOrigValue.Length == 16) { return (byte[])baOrigValue.Clone(); } else { for (int i=0; i<16; i++) { IV[i] = baOrigValue[i]; } return IV; } } private static void DumpIV(byte[] iv) { for (int i=0; i