mirror of
https://github.com/wismna/ModernKeePassLib.git
synced 2025-10-03 15:40:20 -04:00
Setup solution
This commit is contained in:
465
ModernKeePassLib/Security/ProtectedBinary.cs
Normal file
465
ModernKeePassLib/Security/ProtectedBinary.cs
Normal file
@@ -0,0 +1,465 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2019 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
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.Threading;
|
||||
|
||||
#if !KeePassUAP
|
||||
using System.Security.Cryptography;
|
||||
#endif
|
||||
|
||||
using ModernKeePassLib.Cryptography;
|
||||
using ModernKeePassLib.Cryptography.Cipher;
|
||||
using ModernKeePassLib.Native;
|
||||
using ModernKeePassLib.Utility;
|
||||
|
||||
#if KeePassLibSD
|
||||
using KeePassLibSD;
|
||||
#endif
|
||||
|
||||
namespace ModernKeePassLib.Security
|
||||
{
|
||||
[Flags]
|
||||
public enum PbCryptFlags
|
||||
{
|
||||
None = 0,
|
||||
Encrypt = 1,
|
||||
Decrypt = 2
|
||||
}
|
||||
|
||||
public delegate void PbCryptDelegate(byte[] pbData, PbCryptFlags cf,
|
||||
long lID);
|
||||
|
||||
/// <summary>
|
||||
/// A protected binary, i.e. a byte array that is encrypted in memory.
|
||||
/// A <c>ProtectedBinary</c> object is immutable and thread-safe.
|
||||
/// </summary>
|
||||
public sealed class ProtectedBinary : IEquatable<ProtectedBinary>
|
||||
{
|
||||
private const int BlockSize = 16;
|
||||
|
||||
private static PbCryptDelegate g_fExtCrypt = null;
|
||||
/// <summary>
|
||||
/// A plugin can provide a custom memory protection method
|
||||
/// by assigning a non-null delegate to this property.
|
||||
/// </summary>
|
||||
public static PbCryptDelegate ExtCrypt
|
||||
{
|
||||
get { return g_fExtCrypt; }
|
||||
set { g_fExtCrypt = value; }
|
||||
}
|
||||
|
||||
// Local copy of the delegate that was used for encryption,
|
||||
// in order to allow correct decryption even when the global
|
||||
// delegate changes
|
||||
private PbCryptDelegate m_fExtCrypt = null;
|
||||
|
||||
private enum PbMemProt
|
||||
{
|
||||
None = 0,
|
||||
ProtectedMemory, // DPAPI on Windows
|
||||
ChaCha20,
|
||||
ExtCrypt
|
||||
}
|
||||
|
||||
// ProtectedMemory is supported only on Windows 2000 SP3 and higher
|
||||
#if !KeePassLibSD
|
||||
private static bool? g_obProtectedMemorySupported = null;
|
||||
#endif
|
||||
private static bool ProtectedMemorySupported
|
||||
{
|
||||
get
|
||||
{
|
||||
#if KeePassLibSD
|
||||
return false;
|
||||
#else
|
||||
bool? ob = g_obProtectedMemorySupported;
|
||||
if(ob.HasValue) return ob.Value;
|
||||
|
||||
// Mono does not implement any encryption for ProtectedMemory
|
||||
// on Linux (Mono uses DPAPI on Windows);
|
||||
// https://sourceforge.net/p/keepass/feature-requests/1907/
|
||||
if(NativeLib.IsUnix())
|
||||
{
|
||||
g_obProtectedMemorySupported = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
ob = false;
|
||||
try // Test whether ProtectedMemory is supported
|
||||
{
|
||||
// BlockSize * 3 in order to test encryption for multiple
|
||||
// blocks, but not introduce a power of 2 as factor
|
||||
byte[] pb = new byte[ProtectedBinary.BlockSize * 3];
|
||||
for(int i = 0; i < pb.Length; ++i) pb[i] = (byte)i;
|
||||
|
||||
ProtectedMemory.Protect(pb, MemoryProtectionScope.SameProcess);
|
||||
|
||||
for(int i = 0; i < pb.Length; ++i)
|
||||
{
|
||||
if(pb[i] != (byte)i) { ob = true; break; }
|
||||
}
|
||||
}
|
||||
catch(Exception) { } // Windows 98 / ME
|
||||
|
||||
g_obProtectedMemorySupported = ob;
|
||||
return ob.Value;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private static long g_lCurID = 0;
|
||||
private long m_lID;
|
||||
|
||||
private byte[] m_pbData; // Never null
|
||||
|
||||
// The real length of the data; this value can be different from
|
||||
// m_pbData.Length, as the length of m_pbData always is a multiple
|
||||
// of BlockSize (required for ProtectedMemory)
|
||||
private uint m_uDataLen;
|
||||
|
||||
private bool m_bProtected; // Protection requested by the caller
|
||||
|
||||
private PbMemProt m_mp = PbMemProt.None; // Actual protection
|
||||
|
||||
private readonly object m_objSync = new object();
|
||||
|
||||
private static byte[] g_pbKey32 = null;
|
||||
|
||||
/// <summary>
|
||||
/// A flag specifying whether the <c>ProtectedBinary</c> object has
|
||||
/// turned on memory protection or not.
|
||||
/// </summary>
|
||||
public bool IsProtected
|
||||
{
|
||||
get { return m_bProtected; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Length of the stored data.
|
||||
/// </summary>
|
||||
public uint Length
|
||||
{
|
||||
get { return m_uDataLen; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new, empty protected binary data object.
|
||||
/// Protection is disabled.
|
||||
/// </summary>
|
||||
public ProtectedBinary()
|
||||
{
|
||||
Init(false, MemUtil.EmptyByteArray, 0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new protected binary data object.
|
||||
/// </summary>
|
||||
/// <param name="bEnableProtection">If this paremeter is <c>true</c>,
|
||||
/// the data will be encrypted in memory. If it is <c>false</c>, the
|
||||
/// data is stored in plain-text in the process memory.</param>
|
||||
/// <param name="pbData">Value of the protected object.
|
||||
/// The input parameter is not modified and
|
||||
/// <c>ProtectedBinary</c> doesn't take ownership of the data,
|
||||
/// i.e. the caller is responsible for clearing it.</param>
|
||||
public ProtectedBinary(bool bEnableProtection, byte[] pbData)
|
||||
{
|
||||
if(pbData == null) throw new ArgumentNullException("pbData"); // For .Length
|
||||
|
||||
Init(bEnableProtection, pbData, 0, pbData.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new protected binary data object.
|
||||
/// </summary>
|
||||
/// <param name="bEnableProtection">If this paremeter is <c>true</c>,
|
||||
/// the data will be encrypted in memory. If it is <c>false</c>, the
|
||||
/// data is stored in plain-text in the process memory.</param>
|
||||
/// <param name="pbData">Value of the protected object.
|
||||
/// The input parameter is not modified and
|
||||
/// <c>ProtectedBinary</c> doesn't take ownership of the data,
|
||||
/// i.e. the caller is responsible for clearing it.</param>
|
||||
/// <param name="iOffset">Offset for <paramref name="pbData" />.</param>
|
||||
/// <param name="cbSize">Size for <paramref name="pbData" />.</param>
|
||||
public ProtectedBinary(bool bEnableProtection, byte[] pbData,
|
||||
int iOffset, int cbSize)
|
||||
{
|
||||
Init(bEnableProtection, pbData, iOffset, cbSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new protected binary data object.
|
||||
/// Copy the data from a <c>XorredBuffer</c> object.
|
||||
/// </summary>
|
||||
/// <param name="bEnableProtection">Enable protection or not.</param>
|
||||
/// <param name="xb"><c>XorredBuffer</c> object containing the data.</param>
|
||||
public ProtectedBinary(bool bEnableProtection, XorredBuffer xb)
|
||||
{
|
||||
if(xb == null) { Debug.Assert(false); throw new ArgumentNullException("xb"); }
|
||||
|
||||
byte[] pb = xb.ReadPlainText();
|
||||
try { Init(bEnableProtection, pb, 0, pb.Length); }
|
||||
finally { if(bEnableProtection) MemUtil.ZeroByteArray(pb); }
|
||||
}
|
||||
|
||||
private void Init(bool bEnableProtection, byte[] pbData, int iOffset,
|
||||
int cbSize)
|
||||
{
|
||||
if(pbData == null) throw new ArgumentNullException("pbData");
|
||||
if(iOffset < 0) throw new ArgumentOutOfRangeException("iOffset");
|
||||
if(cbSize < 0) throw new ArgumentOutOfRangeException("cbSize");
|
||||
if(iOffset > (pbData.Length - cbSize))
|
||||
throw new ArgumentOutOfRangeException("cbSize");
|
||||
|
||||
#if KeePassLibSD
|
||||
m_lID = ++g_lCurID;
|
||||
#else
|
||||
m_lID = Interlocked.Increment(ref g_lCurID);
|
||||
#endif
|
||||
|
||||
m_bProtected = bEnableProtection;
|
||||
m_uDataLen = (uint)cbSize;
|
||||
|
||||
const int bs = ProtectedBinary.BlockSize;
|
||||
int nBlocks = cbSize / bs;
|
||||
if((nBlocks * bs) < cbSize) ++nBlocks;
|
||||
Debug.Assert((nBlocks * bs) >= cbSize);
|
||||
|
||||
m_pbData = new byte[nBlocks * bs];
|
||||
Array.Copy(pbData, iOffset, m_pbData, 0, cbSize);
|
||||
|
||||
Encrypt();
|
||||
}
|
||||
|
||||
private void Encrypt()
|
||||
{
|
||||
Debug.Assert(m_mp == PbMemProt.None);
|
||||
|
||||
// Nothing to do if caller didn't request protection
|
||||
if(!m_bProtected) return;
|
||||
|
||||
// ProtectedMemory.Protect throws for data size == 0
|
||||
if(m_pbData.Length == 0) return;
|
||||
|
||||
PbCryptDelegate f = g_fExtCrypt;
|
||||
if(f != null)
|
||||
{
|
||||
f(m_pbData, PbCryptFlags.Encrypt, m_lID);
|
||||
|
||||
m_fExtCrypt = f;
|
||||
m_mp = PbMemProt.ExtCrypt;
|
||||
return;
|
||||
}
|
||||
|
||||
if(ProtectedBinary.ProtectedMemorySupported)
|
||||
{
|
||||
ProtectedMemory.Protect(m_pbData, MemoryProtectionScope.SameProcess);
|
||||
|
||||
m_mp = PbMemProt.ProtectedMemory;
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] pbKey32 = g_pbKey32;
|
||||
if(pbKey32 == null)
|
||||
{
|
||||
pbKey32 = GetRandom32();
|
||||
|
||||
byte[] pbUpd = Interlocked.Exchange<byte[]>(ref g_pbKey32, pbKey32);
|
||||
if(pbUpd != null) pbKey32 = pbUpd;
|
||||
}
|
||||
|
||||
byte[] pbIV = new byte[12];
|
||||
MemUtil.UInt64ToBytesEx((ulong)m_lID, pbIV, 4);
|
||||
using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey32, pbIV, true))
|
||||
{
|
||||
c.Encrypt(m_pbData, 0, m_pbData.Length);
|
||||
}
|
||||
m_mp = PbMemProt.ChaCha20;
|
||||
}
|
||||
|
||||
private void Decrypt()
|
||||
{
|
||||
if(m_pbData.Length == 0) return;
|
||||
|
||||
if(m_mp == PbMemProt.ProtectedMemory)
|
||||
ProtectedMemory.Unprotect(m_pbData, MemoryProtectionScope.SameProcess);
|
||||
else if(m_mp == PbMemProt.ChaCha20)
|
||||
{
|
||||
byte[] pbIV = new byte[12];
|
||||
MemUtil.UInt64ToBytesEx((ulong)m_lID, pbIV, 4);
|
||||
using(ChaCha20Cipher c = new ChaCha20Cipher(g_pbKey32, pbIV, true))
|
||||
{
|
||||
c.Decrypt(m_pbData, 0, m_pbData.Length);
|
||||
}
|
||||
}
|
||||
else if(m_mp == PbMemProt.ExtCrypt)
|
||||
m_fExtCrypt(m_pbData, PbCryptFlags.Decrypt, m_lID);
|
||||
else { Debug.Assert(m_mp == PbMemProt.None); }
|
||||
|
||||
m_mp = PbMemProt.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a copy of the protected data as a byte array.
|
||||
/// Please note that the returned byte array is not protected and
|
||||
/// can therefore been read by any other application.
|
||||
/// Make sure that your clear it properly after usage.
|
||||
/// </summary>
|
||||
/// <returns>Unprotected byte array. This is always a copy of the internal
|
||||
/// protected data and can therefore be cleared safely.</returns>
|
||||
public byte[] ReadData()
|
||||
{
|
||||
if(m_uDataLen == 0) return MemUtil.EmptyByteArray;
|
||||
|
||||
byte[] pbReturn = new byte[m_uDataLen];
|
||||
|
||||
lock(m_objSync)
|
||||
{
|
||||
Decrypt();
|
||||
Array.Copy(m_pbData, pbReturn, (int)m_uDataLen);
|
||||
Encrypt();
|
||||
}
|
||||
|
||||
return pbReturn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the data xorred with bytes from a <c>CryptoRandomStream</c>.
|
||||
/// </summary>
|
||||
public byte[] ReadXorredData(CryptoRandomStream crsRandomSource)
|
||||
{
|
||||
if(crsRandomSource == null) { Debug.Assert(false); throw new ArgumentNullException("crsRandomSource"); }
|
||||
|
||||
byte[] pbData = ReadData();
|
||||
int cb = pbData.Length;
|
||||
|
||||
byte[] pbPad = crsRandomSource.GetRandomBytes((uint)cb);
|
||||
Debug.Assert(pbPad.Length == cb);
|
||||
|
||||
for(int i = 0; i < cb; ++i)
|
||||
pbData[i] ^= pbPad[i];
|
||||
|
||||
MemUtil.ZeroByteArray(pbPad);
|
||||
return pbData;
|
||||
}
|
||||
|
||||
private int? m_hash = null;
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if(m_hash.HasValue) return m_hash.Value;
|
||||
|
||||
int h = (m_bProtected ? 0x7B11D289 : 0);
|
||||
|
||||
byte[] pb = ReadData();
|
||||
unchecked
|
||||
{
|
||||
for(int i = 0; i < pb.Length; ++i)
|
||||
h = (h << 3) + h + (int)pb[i];
|
||||
}
|
||||
if(m_bProtected) MemUtil.ZeroByteArray(pb);
|
||||
|
||||
m_hash = h;
|
||||
return h;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return this.Equals(obj as ProtectedBinary, true);
|
||||
}
|
||||
|
||||
public bool Equals(ProtectedBinary other)
|
||||
{
|
||||
return this.Equals(other, true);
|
||||
}
|
||||
|
||||
public bool Equals(ProtectedBinary other, bool bCheckProtEqual)
|
||||
{
|
||||
if(other == null) return false; // No assert
|
||||
if(object.ReferenceEquals(this, other)) return true; // Perf. opt.
|
||||
|
||||
if(bCheckProtEqual && (m_bProtected != other.m_bProtected))
|
||||
return false;
|
||||
|
||||
if(m_uDataLen != other.m_uDataLen) return false;
|
||||
|
||||
byte[] pbL = ReadData(), pbR = null;
|
||||
bool bEq;
|
||||
try
|
||||
{
|
||||
pbR = other.ReadData();
|
||||
bEq = MemUtil.ArraysEqual(pbL, pbR);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(m_bProtected) MemUtil.ZeroByteArray(pbL);
|
||||
if(other.m_bProtected && (pbR != null)) MemUtil.ZeroByteArray(pbR);
|
||||
}
|
||||
|
||||
return bEq;
|
||||
}
|
||||
|
||||
private static byte[] GetRandom32()
|
||||
{
|
||||
// Do not use CryptoRandom here, as it uses ProtectedBinary;
|
||||
// we would have an infinite recursion when trying to
|
||||
// construct a ProtectedBinary object
|
||||
// return CryptoRandom.Instance.GetRandomBytes(32);
|
||||
|
||||
byte[] pbAll = new byte[32 + 16 + 8 + 4];
|
||||
int i = 0;
|
||||
|
||||
try
|
||||
{
|
||||
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
|
||||
byte[] pb = new byte[32];
|
||||
rng.GetBytes(pb);
|
||||
|
||||
Array.Copy(pb, 0, pbAll, i, 32);
|
||||
i += 32;
|
||||
|
||||
MemUtil.ZeroByteArray(pb);
|
||||
// In .NET 2.0, RNGCryptoServiceProvider does not
|
||||
// implement IDisposable; in later .NET versions it does
|
||||
MemUtil.DisposeIfPossible(rng);
|
||||
}
|
||||
catch(Exception) { Debug.Assert(false); }
|
||||
|
||||
try // In case RNGCryptoServiceProvider doesn't work properly
|
||||
{
|
||||
byte[] pb = Guid.NewGuid().ToByteArray();
|
||||
Array.Copy(pb, 0, pbAll, i, 16);
|
||||
i += 16;
|
||||
|
||||
MemUtil.Int64ToBytesEx(DateTime.UtcNow.ToBinary(), pbAll, i);
|
||||
i += 8;
|
||||
|
||||
MemUtil.Int32ToBytesEx(Environment.TickCount, pbAll, i);
|
||||
i += 4;
|
||||
}
|
||||
catch(Exception) { Debug.Assert(false); }
|
||||
|
||||
Debug.Assert(i == pbAll.Length);
|
||||
|
||||
byte[] pbHash = CryptoUtil.HashSha256(pbAll);
|
||||
MemUtil.ZeroByteArray(pbAll);
|
||||
return pbHash;
|
||||
}
|
||||
}
|
||||
}
|
434
ModernKeePassLib/Security/ProtectedString.cs
Normal file
434
ModernKeePassLib/Security/ProtectedString.cs
Normal file
@@ -0,0 +1,434 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2019 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// A string that is protected in process memory.
|
||||
/// <c>ProtectedString</c> objects are immutable and thread-safe.
|
||||
/// </summary>
|
||||
#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();
|
||||
/// <summary>
|
||||
/// Get an empty <c>ProtectedString</c> object, without protection.
|
||||
/// </summary>
|
||||
public static ProtectedString Empty
|
||||
{
|
||||
get { return m_psEmpty; }
|
||||
}
|
||||
|
||||
private static readonly ProtectedString m_psEmptyEx = new ProtectedString(
|
||||
true, new byte[0]);
|
||||
/// <summary>
|
||||
/// Get an empty <c>ProtectedString</c> object, with protection turned on.
|
||||
/// </summary>
|
||||
public static ProtectedString EmptyEx
|
||||
{
|
||||
get { return m_psEmptyEx; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A flag specifying whether the <c>ProtectedString</c> object
|
||||
/// has turned on memory protection or not.
|
||||
/// </summary>
|
||||
public bool IsProtected
|
||||
{
|
||||
get { return m_bIsProtected; }
|
||||
}
|
||||
|
||||
public bool IsEmpty
|
||||
{
|
||||
get
|
||||
{
|
||||
ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety
|
||||
if(p != null) return (p.Length == 0);
|
||||
|
||||
Debug.Assert(m_strPlainText != null);
|
||||
return (m_strPlainText.Length == 0);
|
||||
}
|
||||
}
|
||||
|
||||
private int m_nCachedLength = -1;
|
||||
/// <summary>
|
||||
/// Length of the protected string, in characters.
|
||||
/// </summary>
|
||||
public int Length
|
||||
{
|
||||
get
|
||||
{
|
||||
if(m_nCachedLength >= 0) return m_nCachedLength;
|
||||
|
||||
ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety
|
||||
if(p != null)
|
||||
{
|
||||
byte[] pbPlain = p.ReadData();
|
||||
try { m_nCachedLength = StrUtil.Utf8.GetCharCount(pbPlain); }
|
||||
finally { MemUtil.ZeroByteArray(pbPlain); }
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(m_strPlainText != null);
|
||||
m_nCachedLength = m_strPlainText.Length;
|
||||
}
|
||||
|
||||
return m_nCachedLength;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new protected string object. Protection is
|
||||
/// disabled.
|
||||
/// </summary>
|
||||
public ProtectedString()
|
||||
{
|
||||
Init(false, string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new protected string. The string is initialized
|
||||
/// to the value supplied in the parameters.
|
||||
/// </summary>
|
||||
/// <param name="bEnableProtection">If this parameter is <c>true</c>,
|
||||
/// the string will be protected in memory (encrypted). If it
|
||||
/// is <c>false</c>, the string will be stored as plain-text.</param>
|
||||
/// <param name="strValue">The initial string value.</param>
|
||||
public ProtectedString(bool bEnableProtection, string strValue)
|
||||
{
|
||||
Init(bEnableProtection, strValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new protected string. The string is initialized
|
||||
/// to the value supplied in the parameters (UTF-8 encoded string).
|
||||
/// </summary>
|
||||
/// <param name="bEnableProtection">If this parameter is <c>true</c>,
|
||||
/// the string will be protected in memory (encrypted). If it
|
||||
/// is <c>false</c>, the string will be stored as plain-text.</param>
|
||||
/// <param name="vUtf8Value">The initial string value, encoded as
|
||||
/// UTF-8 byte array. This parameter won't be modified; the caller
|
||||
/// is responsible for clearing it.</param>
|
||||
public ProtectedString(bool bEnableProtection, byte[] vUtf8Value)
|
||||
{
|
||||
Init(bEnableProtection, vUtf8Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new protected string. The string is initialized
|
||||
/// to the value passed in the <c>XorredBuffer</c> object.
|
||||
/// </summary>
|
||||
/// <param name="bEnableProtection">Enable protection or not.</param>
|
||||
/// <param name="xb"><c>XorredBuffer</c> object containing the
|
||||
/// string in UTF-8 representation. The UTF-8 string must not
|
||||
/// be <c>null</c>-terminated.</param>
|
||||
public ProtectedString(bool bEnableProtection, XorredBuffer xb)
|
||||
{
|
||||
if(xb == null) { Debug.Assert(false); throw new ArgumentNullException("xb"); }
|
||||
|
||||
byte[] pb = xb.ReadPlainText();
|
||||
try { Init(bEnableProtection, pb); }
|
||||
finally { if(bEnableProtection) MemUtil.ZeroByteArray(pb); }
|
||||
}
|
||||
|
||||
private void Init(bool bEnableProtection, string str)
|
||||
{
|
||||
if(str == null) throw new ArgumentNullException("str");
|
||||
|
||||
m_bIsProtected = bEnableProtection;
|
||||
|
||||
// As 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the protected string to a standard string object.
|
||||
/// Be careful with this function, as the returned string object
|
||||
/// isn't protected anymore and stored in plain-text in the
|
||||
/// process memory.
|
||||
/// </summary>
|
||||
/// <returns>Plain-text string. Is never <c>null</c>.</returns>
|
||||
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 (strings are
|
||||
// immutable and thus cannot be overwritten)
|
||||
m_strPlainText = str;
|
||||
m_pbUtf8 = null; // Thread-safe order
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read out the string and return it as a char array.
|
||||
/// The returned array is not protected and should be cleared by
|
||||
/// the caller.
|
||||
/// </summary>
|
||||
/// <returns>Plain-text char array.</returns>
|
||||
public char[] ReadChars()
|
||||
{
|
||||
if(m_strPlainText != null) return m_strPlainText.ToCharArray();
|
||||
|
||||
byte[] pb = ReadUtf8();
|
||||
char[] v;
|
||||
try { v = StrUtil.Utf8.GetChars(pb); }
|
||||
finally { MemUtil.ZeroByteArray(pb); }
|
||||
return v;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read out the string and return a byte array that contains the
|
||||
/// string encoded using UTF-8.
|
||||
/// The returned array is not protected and should be cleared by
|
||||
/// the caller.
|
||||
/// </summary>
|
||||
/// <returns>Plain-text UTF-8 byte array.</returns>
|
||||
public byte[] ReadUtf8()
|
||||
{
|
||||
ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety
|
||||
if(p != null) return p.ReadData();
|
||||
|
||||
return StrUtil.Utf8.GetBytes(m_strPlainText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the string as an UTF-8 sequence xorred with bytes
|
||||
/// from a <c>CryptoRandomStream</c>.
|
||||
/// </summary>
|
||||
public byte[] ReadXorredString(CryptoRandomStream crsRandomSource)
|
||||
{
|
||||
if(crsRandomSource == null) { Debug.Assert(false); throw new ArgumentNullException("crsRandomSource"); }
|
||||
|
||||
byte[] pbData = ReadUtf8();
|
||||
int cb = pbData.Length;
|
||||
|
||||
byte[] pbPad = crsRandomSource.GetRandomBytes((uint)cb);
|
||||
Debug.Assert(pbPad.Length == cb);
|
||||
|
||||
for(int i = 0; i < cb; ++i)
|
||||
pbData[i] ^= pbPad[i];
|
||||
|
||||
MemUtil.ZeroByteArray(pbPad);
|
||||
return pbData;
|
||||
}
|
||||
|
||||
public ProtectedString WithProtection(bool bProtect)
|
||||
{
|
||||
if(bProtect == m_bIsProtected) return this;
|
||||
|
||||
byte[] pb = ReadUtf8();
|
||||
|
||||
// No need to clear pb; either the current or the new object is unprotected
|
||||
return new ProtectedString(bProtect, pb);
|
||||
}
|
||||
|
||||
public bool Equals(ProtectedString ps, bool bCheckProtEqual)
|
||||
{
|
||||
if(ps == null) throw new ArgumentNullException("ps");
|
||||
if(object.ReferenceEquals(this, ps)) return true; // Perf. opt.
|
||||
|
||||
bool bPA = m_bIsProtected, bPB = ps.m_bIsProtected;
|
||||
if(bCheckProtEqual && (bPA != bPB)) return false;
|
||||
if(!bPA && !bPB) return (ReadString() == ps.ReadString());
|
||||
|
||||
byte[] pbA = ReadUtf8(), pbB = null;
|
||||
bool bEq;
|
||||
try
|
||||
{
|
||||
pbB = ps.ReadUtf8();
|
||||
bEq = MemUtil.ArraysEqual(pbA, pbB);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(bPA) MemUtil.ZeroByteArray(pbA);
|
||||
if(bPB && (pbB != null)) MemUtil.ZeroByteArray(pbB);
|
||||
}
|
||||
|
||||
return bEq;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if(!m_bIsProtected)
|
||||
return new ProtectedString(false, ReadString().Insert(
|
||||
iStart, strInsert));
|
||||
|
||||
UTF8Encoding utf8 = StrUtil.Utf8;
|
||||
char[] v = ReadChars(), vNew = null;
|
||||
byte[] pbNew = null;
|
||||
ProtectedString ps;
|
||||
|
||||
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);
|
||||
|
||||
pbNew = utf8.GetBytes(vNew);
|
||||
ps = new ProtectedString(true, pbNew);
|
||||
|
||||
Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) ==
|
||||
ReadString().Insert(iStart, strInsert));
|
||||
}
|
||||
finally
|
||||
{
|
||||
MemUtil.ZeroArray<char>(v);
|
||||
if(vNew != null) MemUtil.ZeroArray<char>(vNew);
|
||||
if(pbNew != null) 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;
|
||||
|
||||
if(!m_bIsProtected)
|
||||
return new ProtectedString(false, ReadString().Remove(
|
||||
iStart, nCount));
|
||||
|
||||
UTF8Encoding utf8 = StrUtil.Utf8;
|
||||
char[] v = ReadChars(), vNew = null;
|
||||
byte[] pbNew = null;
|
||||
ProtectedString ps;
|
||||
|
||||
try
|
||||
{
|
||||
if((iStart + nCount) > v.Length)
|
||||
throw new ArgumentException("(iStart + nCount) > v.Length");
|
||||
|
||||
vNew = new char[v.Length - nCount];
|
||||
Array.Copy(v, 0, vNew, 0, iStart);
|
||||
Array.Copy(v, iStart + nCount, vNew, iStart, v.Length -
|
||||
(iStart + nCount));
|
||||
|
||||
pbNew = utf8.GetBytes(vNew);
|
||||
ps = new ProtectedString(true, pbNew);
|
||||
|
||||
Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) ==
|
||||
ReadString().Remove(iStart, nCount));
|
||||
}
|
||||
finally
|
||||
{
|
||||
MemUtil.ZeroArray<char>(v);
|
||||
if(vNew != null) MemUtil.ZeroArray<char>(vNew);
|
||||
if(pbNew != null) MemUtil.ZeroByteArray(pbNew);
|
||||
}
|
||||
|
||||
return ps;
|
||||
}
|
||||
|
||||
public static ProtectedString operator +(ProtectedString a, ProtectedString b)
|
||||
{
|
||||
if(a == null) throw new ArgumentNullException("a");
|
||||
if(b == null) throw new ArgumentNullException("b");
|
||||
|
||||
if(b.IsEmpty) return a;
|
||||
if(a.IsEmpty) return b;
|
||||
if(!a.IsProtected && !b.IsProtected)
|
||||
return new ProtectedString(false, a.ReadString() + b.ReadString());
|
||||
|
||||
char[] vA = a.ReadChars(), vB = null, vNew = null;
|
||||
byte[] pbNew = null;
|
||||
ProtectedString ps;
|
||||
|
||||
try
|
||||
{
|
||||
vB = b.ReadChars();
|
||||
|
||||
vNew = new char[vA.Length + vB.Length];
|
||||
Array.Copy(vA, vNew, vA.Length);
|
||||
Array.Copy(vB, 0, vNew, vA.Length, vB.Length);
|
||||
|
||||
pbNew = StrUtil.Utf8.GetBytes(vNew);
|
||||
ps = new ProtectedString(true, pbNew);
|
||||
}
|
||||
finally
|
||||
{
|
||||
MemUtil.ZeroArray<char>(vA);
|
||||
if(vB != null) MemUtil.ZeroArray<char>(vB);
|
||||
if(vNew != null) MemUtil.ZeroArray<char>(vNew);
|
||||
if(pbNew != null) MemUtil.ZeroByteArray(pbNew);
|
||||
}
|
||||
|
||||
return ps;
|
||||
}
|
||||
|
||||
public static ProtectedString operator +(ProtectedString a, string b)
|
||||
{
|
||||
ProtectedString psB = new ProtectedString(false, b);
|
||||
return (a + psB);
|
||||
}
|
||||
}
|
||||
}
|
109
ModernKeePassLib/Security/XorredBuffer.cs
Normal file
109
ModernKeePassLib/Security/XorredBuffer.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2019 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
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 ModernKeePassLib.Utility;
|
||||
|
||||
namespace ModernKeePassLib.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// A <c>XorredBuffer</c> object stores data that is encrypted
|
||||
/// using a XOR pad.
|
||||
/// </summary>
|
||||
public sealed class XorredBuffer : IDisposable
|
||||
{
|
||||
private byte[] m_pbCT;
|
||||
private byte[] m_pbXorPad;
|
||||
|
||||
public uint Length
|
||||
{
|
||||
get
|
||||
{
|
||||
if(m_pbCT == null) { Debug.Assert(false); throw new ObjectDisposedException(null); }
|
||||
return (uint)m_pbCT.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new <c>XorredBuffer</c> object.
|
||||
/// The <paramref name="pbCT" /> byte array must have the same
|
||||
/// length as the <paramref name="pbXorPad" /> byte array.
|
||||
/// The <c>XorredBuffer</c> object takes ownership of the two byte
|
||||
/// arrays, i.e. the caller must not use them afterwards.
|
||||
/// </summary>
|
||||
/// <param name="pbCT">Data with XOR pad applied.</param>
|
||||
/// <param name="pbXorPad">XOR pad that can be used to decrypt the
|
||||
/// <paramref name="pbCT" /> byte array.</param>
|
||||
public XorredBuffer(byte[] pbCT, byte[] pbXorPad)
|
||||
{
|
||||
if(pbCT == null) { Debug.Assert(false); throw new ArgumentNullException("pbCT"); }
|
||||
if(pbXorPad == null) { Debug.Assert(false); throw new ArgumentNullException("pbXorPad"); }
|
||||
if(pbCT.Length != pbXorPad.Length)
|
||||
{
|
||||
Debug.Assert(false);
|
||||
throw new ArgumentOutOfRangeException("pbXorPad");
|
||||
}
|
||||
|
||||
m_pbCT = pbCT;
|
||||
m_pbXorPad = pbXorPad;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
~XorredBuffer()
|
||||
{
|
||||
Debug.Assert((m_pbCT == null) && (m_pbXorPad == null));
|
||||
}
|
||||
#endif
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if(m_pbCT == null) return;
|
||||
|
||||
MemUtil.ZeroByteArray(m_pbCT);
|
||||
m_pbCT = null;
|
||||
|
||||
MemUtil.ZeroByteArray(m_pbXorPad);
|
||||
m_pbXorPad = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a copy of the plain-text. The caller is responsible
|
||||
/// for clearing the byte array safely after using it.
|
||||
/// </summary>
|
||||
/// <returns>Plain-text byte array.</returns>
|
||||
public byte[] ReadPlainText()
|
||||
{
|
||||
byte[] pbCT = m_pbCT, pbX = m_pbXorPad;
|
||||
if((pbCT == null) || (pbX == null) || (pbCT.Length != pbX.Length))
|
||||
{
|
||||
Debug.Assert(false);
|
||||
throw new ObjectDisposedException(null);
|
||||
}
|
||||
|
||||
byte[] pbPT = new byte[pbCT.Length];
|
||||
|
||||
for(int i = 0; i < pbPT.Length; ++i)
|
||||
pbPT[i] = (byte)(pbCT[i] ^ pbX[i]);
|
||||
|
||||
return pbPT;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user