/*********************************************************************** * * 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.Collections; using System.Threading; using System.Security.Cryptography; using System.Xml; #if LINUX using Mono.Unix.Native; #endif using sscs.cache; using sscs.crypto; using sscs.common; using sscs.constants; using Novell.CASA.MiCasa.Common; namespace sscs.lss { /// /* * This class is a service to store data persistently. * How it does this is determined by implementation within the * private methods (File system using file(s), database, etc) * The MasterPasscode can be used to generate the key for * encyption and decryption. * If encrpytion is used, the private methods will also manage * how the encyption key is to be stored and retrieved. * Each piece of data is located by a DataID. * This might be an individual credentail or * a complete store. */ /* We might not need this as a separate class. * Depending on the db changes, we can change this later. */ /// public class LocalStorage { private byte[] m_baGeneratedKey = null; private SecretStore userStore = null; private int persistThreadSleepTime = 1000 * 60 * 5; //1000 * 30; private Thread persistThread = null; #if LINUX Mono.Unix.UnixFileSystemInfo sockFileInfo; Mono.Unix.UnixUserInfo sockFileOwner; #endif private static string LINUXID = "Unix"; internal LocalStorage(SecretStore store,byte[] baMasterPasscode) { userStore = store; m_baGeneratedKey = baMasterPasscode; LoadPersistentStore(); userStore.DumpSecretstore(); } ~LocalStorage() { if(persistThread != null) { persistThread.Abort(); persistThread.Join(); } } // allowing a user to choose the storage location is not approved yet private LocalStorage(SecretStore store, byte[] baMasterPasscode, string sStorageDirectory) { userStore = store; m_baGeneratedKey = baMasterPasscode; LoadPersistentStore(); userStore.DumpSecretstore(); } private void StorePersistentData(string sDataID, byte[] baData) { } private byte[] RetrievePersistentData(string sDataID) { return null; } public void PersistStoreWithDelay() { if (persistThread == null) { persistThread = new Thread(new ThreadStart(PersistStoreDelayThreadFn)); persistThread.Start(); } } public bool StopPersistence() { if(persistThread != null) { persistThread.Abort(); persistThread.Join(); } return true; } public bool IsOwnedByRoot(string fileName) { #if LINUX sockFileInfo = new Mono.Unix.UnixFileInfo(fileName); sockFileOwner = sockFileInfo.OwnerUser; if(0==sockFileOwner.UserId) return true; else return false; #else return true; #endif } private string GetDecryptedXml() { try { string fileName = userStore.GetPersistenceFilePath(); string tempFile = fileName; int count = 0; if(!File.Exists(fileName)) { while(true) { // check for tmp file if (File.Exists(tempFile+".tmp")) { if(IsOwnedByRoot(tempFile+".tmp")) { File.Move(tempFile+".tmp", fileName); break; } else { count++; tempFile = fileName + count.ToString(); } } else return null; } // delete tmp file if there if (File.Exists(tempFile+".tmp")) { if(IsOwnedByRoot(tempFile+".tmp")) File.Delete(tempFile+".tmp"); } } byte[] baPasscode = null; if (null != m_baGeneratedKey) baPasscode = m_baGeneratedKey; else baPasscode = CASACrypto.GetMasterPasscode(userStore.GetDesktopPasswd(),userStore.GetPasscodeByDesktopFilePath()); if( null == baPasscode ) return null; byte[] key = CASACrypto.GetKeySetFromFile(baPasscode,userStore.GetKeyFilePath()); if( null == key ) return null; byte[] decryptedBuffer = CASACrypto.ReadFileAndDecryptData(key,fileName); if( null == decryptedBuffer ) return null; string temp = Encoding.UTF8.GetString(decryptedBuffer, 0, decryptedBuffer.Length); return temp; } catch(Exception e) { CSSSLogger.ExpLog(e.ToString()); CSSSLogger.DbgLog("Unable to get persistent store"); } return null; } /* This method, uses the key to decrypt the persistent store * and populates userStore with the persistent data. */ private bool LoadPersistentStore() { try { string xpath = ""; XmlDocument doc = new XmlDocument(); string xmlToLoad = GetDecryptedXml(); if(xmlToLoad != null) { doc.LoadXml(xmlToLoad); #if false XmlTextWriter writer = new XmlTextWriter("/home/poorna/.miCASA.xml",null); writer.Formatting = Formatting.Indented; doc.Save(writer); writer.Close(); #endif } else { return false; } xpath = "//" + XmlConsts.miCASANode; XmlNode miCASANode = doc.SelectSingleNode(xpath); if(miCASANode != null) { xpath = "descendant::" + XmlConsts.keyChainNode; XmlNodeList keyChainNodeList = miCASANode.SelectNodes(xpath); foreach(XmlNode node in keyChainNodeList) { XmlAttributeCollection attrColl = node.Attributes; string keyChainId = (attrColl[XmlConsts.idAttr]).Value + "\0"; KeyChain keyChain = null; if( userStore.CheckIfKeyChainExists(keyChainId) == false ) { keyChain = new KeyChain(keyChainId); userStore.AddKeyChain(keyChain); } else { keyChain = userStore.GetKeyChain(keyChainId); } xpath = "descendant::" + XmlConsts.secretNode; XmlNodeList secretNodeList = node.SelectNodes(xpath); foreach(XmlNode secretNode in secretNodeList) { attrColl = secretNode.Attributes; string secretId = (attrColl[XmlConsts.idAttr]).Value + "\0"; xpath = "descendant::" + XmlConsts.valueNode; Secret secret = new Secret(secretId); if( keyChain.CheckIfSecretExists(secretId) == false) { keyChain.AddSecret(secret); XmlNode secretValNode = (secretNode.SelectSingleNode(xpath)); xpath = "descendant::" + XmlConsts.keyNode; XmlNodeList keyNodeList = secretValNode.SelectNodes(xpath); secret = keyChain.GetSecret(secretId); foreach(XmlNode keyNode in keyNodeList) { attrColl = keyNode.Attributes; string key; try { key = (attrColl[XmlConsts.idAttr]).Value; } catch (Exception) { // LinkedKey node, continue continue; } xpath = "descendant::" + XmlConsts.keyValueNode; XmlNode keyValNode = keyNode.SelectSingleNode(xpath); string keyValue = keyValNode.InnerText; secret.SetKeyValue(key,keyValue); // add linked keys xpath = "descendant::" + XmlConsts.linkedKeyNode; XmlNodeList linkNodeList = keyNode.SelectNodes(xpath); foreach(XmlNode linkNode in linkNodeList) { // get TargetSecretID xpath = "descendant::" + XmlConsts.linkedTargetSecretNode; XmlNode targetSecretNode = linkNode.SelectSingleNode(xpath); string sSecretID = targetSecretNode.InnerText + "\0"; // get TargetSecretKey xpath = "descendant::" + XmlConsts.linkedTargetKeyNode; XmlNode targetKeyNode = linkNode.SelectSingleNode(xpath); string sKeyID = targetKeyNode.InnerText; LinkedKeyInfo lki = new LinkedKeyInfo(sSecretID, sKeyID, true); KeyValue kv = secret.GetKeyValue(key); kv.AddLink(lki); } } }//if ends } }//end of traversing keyChainNodeList } } catch(Exception e) { CSSSLogger.ExpLog(e.ToString()); } // collect now to remove old data from memory GC.Collect(); return true; } private void PersistStoreDelayThreadFn() { Thread.Sleep(15000); PersistStore(); persistThread = null; } private void PersistStoreThreadFn() { while(true) { Thread.Sleep(persistThreadSleepTime); PersistStore(); } } /* Persists the store to an xml file. * TBD : Would we require any form of encoding? */ internal void PersistStore() { // userStore.DumpSecretstore(); try { MemoryStream ms1 = new MemoryStream(); XmlTextWriter writer = new XmlTextWriter(ms1,null); writer.Formatting = Formatting.Indented; writer.WriteStartDocument(); writer.WriteStartElement(XmlConsts.miCASANode); writer.WriteAttributeString(XmlConsts.versionAttr,"1.5"); { IDictionaryEnumerator iter = (IDictionaryEnumerator)userStore.GetKeyChainEnumerator(); char [] tmpId; string sTmpId; while( iter.MoveNext() ) { KeyChain kc = (KeyChain)iter.Value; writer.WriteStartElement(XmlConsts.keyChainNode); string kcId = kc.GetKey(); tmpId = new char[kcId.Length-1]; for(int i = 0; i < kcId.Length-1; i++ ) tmpId[i] = kcId[i]; sTmpId = new string(tmpId); writer.WriteAttributeString(XmlConsts.idAttr,sTmpId); /* If we need to store time writer.WriteStartElement(XmlConsts.timeNode); writer.WriteAttributeString(XmlConsts.createdTimeNode,kc.CreatedTime.ToString()); writer.WriteAttributeString(XmlConsts.modifiedTimeNode,kc.ModifiedTime.ToString()); writer.WriteEndElement(); */ IDictionaryEnumerator secIter = (IDictionaryEnumerator)(kc.GetAllSecrets()); while(secIter.MoveNext()) { Secret secret = (Secret)secIter.Value; writer.WriteStartElement(XmlConsts.secretNode); string secretId = secret.GetKey(); tmpId = new char[secretId.Length-1]; for(int i = 0; i < secretId.Length-1; i++ ) tmpId[i] = secretId[i]; sTmpId = new string(tmpId); writer.WriteAttributeString(XmlConsts.idAttr,sTmpId); /* If we need to store time writer.WriteStartElement(XmlConsts.timeNode); writer.WriteAttributeString(XmlConsts.createdTimeNode,secret.CreatedTime.ToString()); writer.WriteAttributeString(XmlConsts.modifiedTimeNode,secret.ModifiedTime.ToString()); writer.WriteEndElement(); */ writer.WriteStartElement(XmlConsts.valueNode); // byte[] byteArr = secret.GetValue(); IDictionaryEnumerator etor = (IDictionaryEnumerator)secret.GetKeyValueEnumerator(); while(etor.MoveNext()) { string sKey = (string)etor.Key; string value = secret.GetKeyValue(sKey).GetValue(); writer.WriteStartElement(XmlConsts.keyNode); writer.WriteAttributeString(XmlConsts.idAttr, sKey); writer.WriteStartElement(XmlConsts.keyValueNode); writer.WriteString(value); writer.WriteEndElement(); /* If we need to store time writer.WriteStartElement(XmlConsts.timeNode); writer.WriteAttributeString(XmlConsts.createdTimeNode,(secret.GetKeyValueCreatedTime(sKey)).ToString()); writer.WriteAttributeString(XmlConsts.modifiedTimeNode,(secret.GetKeyValueModifiedTime(sKey)).ToString()); writer.WriteEndElement(); */ // write all LinkKeys Hashtable htLinkedKeys = secret.GetLinkedKeys(sKey); if (htLinkedKeys != null) { IDictionaryEnumerator etorLinked = (IDictionaryEnumerator)htLinkedKeys.GetEnumerator(); while(etorLinked.MoveNext()) { LinkedKeyInfo lki = (LinkedKeyInfo)etorLinked.Value; writer.WriteStartElement(XmlConsts.linkedKeyNode); writer.WriteStartElement(XmlConsts.linkedTargetSecretNode); writer.WriteString(lki.GetLinkedSecretID().Substring(0, lki.GetLinkedSecretID().Length-1)); writer.WriteEndElement(); writer.WriteStartElement(XmlConsts.linkedTargetKeyNode); writer.WriteString(lki.GetLinkedKeyID()); writer.WriteEndElement(); writer.WriteEndElement(); } } writer.WriteEndElement(); } /* char[] chArr = new char[byteArr.Length]; for(int z = 0; z < byteArr.Length; z++) chArr[z] = (char)byteArr[z]; string stringToStore = new string(chArr); writer.WriteString(stringToStore); */ writer.WriteEndElement(); //end of value node writer.WriteEndElement(); } writer.WriteEndElement(); //keychain } } writer.WriteEndElement(); //miCASA node writer.WriteEndDocument(); writer.Flush(); writer.Close(); //byte[] key = CASACrypto.GetKeySetFromFile(CASACrypto.GetMasterPasscode(userStore.GetDesktopPasswd(),userStore.GetPasscodeByDesktopFilePath()),userStore.GetKeyFilePath()); byte[] key = CASACrypto.GetKeySetFromFile(m_baGeneratedKey, userStore.GetKeyFilePath()); string fileName = userStore.GetPersistenceFilePath(); string tempFile = fileName; int count=0; // rename existing file if(File.Exists(fileName)) { while(true) { if (File.Exists(tempFile+".tmp")) { if(IsOwnedByRoot(tempFile+".tmp")) { File.Delete(tempFile+".tmp"); break; } else { count++; tempFile = fileName + count.ToString(); } } else break; } File.Move(fileName, tempFile+".tmp"); } CASACrypto.EncryptDataAndWriteToFile(ms1.ToArray(),key,fileName); //remove temp if(File.Exists(tempFile+".tmp")) { if(IsOwnedByRoot(tempFile+".tmp")) File.Delete(tempFile+".tmp"); } } catch(Exception e) { CSSSLogger.ExpLog(e.ToString()); } } } }