/* KeePass Password Safe - The Open-Source Password Manager Copyright (C) 2003-2018 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ using System; using System.Diagnostics; using System.Text; using ModernKeePassLib.Cryptography; using ModernKeePassLib.Utility; #if KeePassLibSD using KeePassLibSD; #endif // SecureString objects are limited to 65536 characters, don't use namespace ModernKeePassLib.Security { /// /// Represents an in-memory encrypted string. /// ProtectedString objects are immutable and thread-safe. /// #if (DEBUG && !KeePassLibSD) [DebuggerDisplay(@"{ReadString()}")] #endif public sealed class ProtectedString { // Exactly one of the following will be non-null private ProtectedBinary m_pbUtf8 = null; private string m_strPlainText = null; private bool m_bIsProtected; private static readonly ProtectedString m_psEmpty = new ProtectedString(); public static ProtectedString Empty { get { return m_psEmpty; } } /// /// A flag specifying whether the ProtectedString object /// has turned on memory protection or not. /// public bool IsProtected { get { return m_bIsProtected; } } public bool IsEmpty { get { ProtectedBinary pBin = m_pbUtf8; // Local ref for thread-safety if(pBin != null) return (pBin.Length == 0); Debug.Assert(m_strPlainText != null); return (m_strPlainText.Length == 0); } } private int m_nCachedLength = -1; public int Length { get { if(m_nCachedLength >= 0) return m_nCachedLength; ProtectedBinary pBin = m_pbUtf8; // Local ref for thread-safety if(pBin != null) { byte[] pbPlain = pBin.ReadData(); m_nCachedLength = StrUtil.Utf8.GetCharCount(pbPlain); MemUtil.ZeroByteArray(pbPlain); } else { Debug.Assert(m_strPlainText != null); m_nCachedLength = m_strPlainText.Length; } return m_nCachedLength; } } /// /// Construct a new protected string object. Protection is /// disabled. /// public ProtectedString() { Init(false, string.Empty); } /// /// Construct a new protected string. The string is initialized /// to the value supplied in the parameters. /// /// If this parameter is true, /// the string will be protected in memory (encrypted). If it /// is false, the string will be stored as plain-text. /// The initial string value. public ProtectedString(bool bEnableProtection, string strValue) { Init(bEnableProtection, strValue); } /// /// Construct a new protected string. The string is initialized /// to the value supplied in the parameters (UTF-8 encoded string). /// /// If this parameter is true, /// the string will be protected in memory (encrypted). If it /// is false, the string will be stored as plain-text. /// The initial string value, encoded as /// UTF-8 byte array. This parameter won't be modified; the caller /// is responsible for clearing it. public ProtectedString(bool bEnableProtection, byte[] vUtf8Value) { Init(bEnableProtection, vUtf8Value); } /// /// Construct a new protected string. The string is initialized /// to the value passed in the XorredBuffer object. /// /// Enable protection or not. /// XorredBuffer object containing the /// string in UTF-8 representation. The UTF-8 string must not /// be null-terminated. public ProtectedString(bool bEnableProtection, XorredBuffer xbProtected) { Debug.Assert(xbProtected != null); if(xbProtected == null) throw new ArgumentNullException("xbProtected"); byte[] pb = xbProtected.ReadPlainText(); Init(bEnableProtection, pb); if(bEnableProtection) MemUtil.ZeroByteArray(pb); } private void Init(bool bEnableProtection, string str) { if(str == null) throw new ArgumentNullException("str"); m_bIsProtected = bEnableProtection; // The string already is in memory and immutable, // protection would be useless m_strPlainText = str; } private void Init(bool bEnableProtection, byte[] pbUtf8) { if(pbUtf8 == null) throw new ArgumentNullException("pbUtf8"); m_bIsProtected = bEnableProtection; if(bEnableProtection) m_pbUtf8 = new ProtectedBinary(true, pbUtf8); else m_strPlainText = StrUtil.Utf8.GetString(pbUtf8, 0, pbUtf8.Length); } /// /// Convert the protected string to a normal string object. /// Be careful with this function, the returned string object /// isn't protected anymore and stored in plain-text in the /// process memory. /// /// Plain-text string. Is never null. public string ReadString() { if(m_strPlainText != null) return m_strPlainText; byte[] pb = ReadUtf8(); string str = ((pb.Length == 0) ? string.Empty : StrUtil.Utf8.GetString(pb, 0, pb.Length)); // No need to clear pb // As the text is now visible in process memory anyway, // there's no need to protect it anymore m_strPlainText = str; m_pbUtf8 = null; // Thread-safe order return str; } /// /// Read out the string and return a byte array that contains the /// string encoded using UTF-8. The returned string is not protected /// anymore! /// /// Plain-text UTF-8 byte array. public byte[] ReadUtf8() { ProtectedBinary pBin = m_pbUtf8; // Local ref for thread-safety if(pBin != null) return pBin.ReadData(); return StrUtil.Utf8.GetBytes(m_strPlainText); } /// /// Read the protected string and return it protected with a sequence /// of bytes generated by a random stream. /// /// Random number source. /// Protected string. public byte[] ReadXorredString(CryptoRandomStream crsRandomSource) { Debug.Assert(crsRandomSource != null); if(crsRandomSource == null) throw new ArgumentNullException("crsRandomSource"); byte[] pbData = ReadUtf8(); uint uLen = (uint)pbData.Length; byte[] randomPad = crsRandomSource.GetRandomBytes(uLen); Debug.Assert(randomPad.Length == pbData.Length); for(uint i = 0; i < uLen; ++i) pbData[i] ^= randomPad[i]; return pbData; } public ProtectedString WithProtection(bool bProtect) { if(bProtect == m_bIsProtected) return this; byte[] pb = ReadUtf8(); ProtectedString ps = new ProtectedString(bProtect, pb); if(bProtect) MemUtil.ZeroByteArray(pb); return ps; } public ProtectedString Insert(int iStart, string strInsert) { if(iStart < 0) throw new ArgumentOutOfRangeException("iStart"); if(strInsert == null) throw new ArgumentNullException("strInsert"); if(strInsert.Length == 0) return this; // Only operate directly with strings when m_bIsProtected is // false, not in the case of non-null m_strPlainText, because // the operation creates a new sequence in memory if(!m_bIsProtected) return new ProtectedString(false, ReadString().Insert( iStart, strInsert)); UTF8Encoding utf8 = StrUtil.Utf8; byte[] pb = ReadUtf8(); char[] v = utf8.GetChars(pb); char[] vNew; try { if(iStart > v.Length) throw new ArgumentOutOfRangeException("iStart"); char[] vIns = strInsert.ToCharArray(); vNew = new char[v.Length + vIns.Length]; Array.Copy(v, 0, vNew, 0, iStart); Array.Copy(vIns, 0, vNew, iStart, vIns.Length); Array.Copy(v, iStart, vNew, iStart + vIns.Length, v.Length - iStart); } finally { MemUtil.ZeroArray(v); MemUtil.ZeroByteArray(pb); } byte[] pbNew = utf8.GetBytes(vNew); ProtectedString ps = new ProtectedString(m_bIsProtected, pbNew); Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) == ReadString().Insert(iStart, strInsert)); MemUtil.ZeroArray(vNew); MemUtil.ZeroByteArray(pbNew); return ps; } public ProtectedString Remove(int iStart, int nCount) { if(iStart < 0) throw new ArgumentOutOfRangeException("iStart"); if(nCount < 0) throw new ArgumentOutOfRangeException("nCount"); if(nCount == 0) return this; // Only operate directly with strings when m_bIsProtected is // false, not in the case of non-null m_strPlainText, because // the operation creates a new sequence in memory if(!m_bIsProtected) return new ProtectedString(false, ReadString().Remove( iStart, nCount)); UTF8Encoding utf8 = StrUtil.Utf8; byte[] pb = ReadUtf8(); char[] v = utf8.GetChars(pb); char[] vNew; try { if((iStart + nCount) > v.Length) throw new ArgumentException("iStart + nCount"); vNew = new char[v.Length - nCount]; Array.Copy(v, 0, vNew, 0, iStart); Array.Copy(v, iStart + nCount, vNew, iStart, v.Length - (iStart + nCount)); } finally { MemUtil.ZeroArray(v); MemUtil.ZeroByteArray(pb); } byte[] pbNew = utf8.GetBytes(vNew); ProtectedString ps = new ProtectedString(m_bIsProtected, pbNew); Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) == ReadString().Remove(iStart, nCount)); MemUtil.ZeroArray(vNew); MemUtil.ZeroByteArray(pbNew); return ps; } } }