mirror of
https://github.com/wismna/ModernKeePass.git
synced 2025-10-04 16:10:16 -04:00
WIP Lib version 2.39.1
This commit is contained in:
@@ -44,9 +44,8 @@ namespace ModernKeePassLib.Security
|
||||
long lID);
|
||||
|
||||
/// <summary>
|
||||
/// Represents a protected binary, i.e. a byte array that is encrypted
|
||||
/// in memory. A <c>ProtectedBinary</c> object is immutable and
|
||||
/// thread-safe.
|
||||
/// 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>
|
||||
{
|
||||
@@ -71,7 +70,7 @@ namespace ModernKeePassLib.Security
|
||||
private enum PbMemProt
|
||||
{
|
||||
None = 0,
|
||||
ProtectedMemory,
|
||||
ProtectedMemory, // DPAPI on Windows
|
||||
ChaCha20,
|
||||
ExtCrypt
|
||||
}
|
||||
@@ -90,7 +89,8 @@ namespace ModernKeePassLib.Security
|
||||
bool? ob = g_obProtectedMemorySupported;
|
||||
if(ob.HasValue) return ob.Value;
|
||||
|
||||
// Mono does not implement any encryption for ProtectedMemory;
|
||||
// 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())
|
||||
{
|
||||
@@ -177,7 +177,7 @@ namespace ModernKeePassLib.Security
|
||||
/// i.e. the caller is responsible for clearing it.</param>
|
||||
public ProtectedBinary(bool bEnableProtection, byte[] pbData)
|
||||
{
|
||||
if(pbData == null) throw new ArgumentNullException("pbData");
|
||||
if(pbData == null) throw new ArgumentNullException("pbData"); // For .Length
|
||||
|
||||
Init(bEnableProtection, pbData, 0, pbData.Length);
|
||||
}
|
||||
@@ -213,9 +213,8 @@ namespace ModernKeePassLib.Security
|
||||
if(xbProtected == null) throw new ArgumentNullException("xbProtected");
|
||||
|
||||
byte[] pb = xbProtected.ReadPlainText();
|
||||
Init(bEnableProtection, pb, 0, pb.Length);
|
||||
|
||||
if(bEnableProtection) MemUtil.ZeroByteArray(pb);
|
||||
try { Init(bEnableProtection, pb, 0, pb.Length); }
|
||||
finally { if(bEnableProtection) MemUtil.ZeroByteArray(pb); }
|
||||
}
|
||||
|
||||
private void Init(bool bEnableProtection, byte[] pbData, int iOffset,
|
||||
@@ -374,7 +373,7 @@ namespace ModernKeePassLib.Security
|
||||
for(int i = 0; i < pb.Length; ++i)
|
||||
h = (h << 3) + h + (int)pb[i];
|
||||
}
|
||||
MemUtil.ZeroByteArray(pb);
|
||||
if(m_bProtected) MemUtil.ZeroByteArray(pb);
|
||||
|
||||
m_hash = h;
|
||||
return h;
|
||||
@@ -382,25 +381,36 @@ namespace ModernKeePassLib.Security
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as ProtectedBinary);
|
||||
return this.Equals(obj as ProtectedBinary, true);
|
||||
}
|
||||
|
||||
public bool Equals(ProtectedBinary other)
|
||||
{
|
||||
if(other == null) return false; // No assert
|
||||
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_bProtected != other.m_bProtected) return false;
|
||||
if(m_uDataLen != other.m_uDataLen) return false;
|
||||
|
||||
byte[] pbL = ReadData();
|
||||
byte[] pbR = other.ReadData();
|
||||
bool bEq = MemUtil.ArraysEqual(pbL, pbR);
|
||||
MemUtil.ZeroByteArray(pbL);
|
||||
MemUtil.ZeroByteArray(pbR);
|
||||
|
||||
#if DEBUG
|
||||
if(bEq) { Debug.Assert(GetHashCode() == other.GetHashCode()); }
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
|
@@ -33,11 +33,11 @@ using KeePassLibSD;
|
||||
namespace ModernKeePassLib.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an in-memory encrypted string.
|
||||
/// A string that is protected in process memory.
|
||||
/// <c>ProtectedString</c> objects are immutable and thread-safe.
|
||||
/// </summary>
|
||||
#if (DEBUG && !KeePassLibSD)
|
||||
[DebuggerDisplay(@"{ReadString()}")]
|
||||
[DebuggerDisplay("{ReadString()}")]
|
||||
#endif
|
||||
public sealed class ProtectedString
|
||||
{
|
||||
@@ -48,11 +48,24 @@ namespace ModernKeePassLib.Security
|
||||
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.
|
||||
@@ -66,8 +79,8 @@ namespace ModernKeePassLib.Security
|
||||
{
|
||||
get
|
||||
{
|
||||
ProtectedBinary pBin = m_pbUtf8; // Local ref for thread-safety
|
||||
if(pBin != null) return (pBin.Length == 0);
|
||||
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);
|
||||
@@ -75,18 +88,21 @@ namespace ModernKeePassLib.Security
|
||||
}
|
||||
|
||||
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 pBin = m_pbUtf8; // Local ref for thread-safety
|
||||
if(pBin != null)
|
||||
ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety
|
||||
if(p != null)
|
||||
{
|
||||
byte[] pbPlain = pBin.ReadData();
|
||||
m_nCachedLength = StrUtil.Utf8.GetCharCount(pbPlain);
|
||||
MemUtil.ZeroByteArray(pbPlain);
|
||||
byte[] pbPlain = p.ReadData();
|
||||
try { m_nCachedLength = StrUtil.Utf8.GetCharCount(pbPlain); }
|
||||
finally { MemUtil.ZeroByteArray(pbPlain); }
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -149,9 +165,8 @@ namespace ModernKeePassLib.Security
|
||||
if(xbProtected == null) throw new ArgumentNullException("xbProtected");
|
||||
|
||||
byte[] pb = xbProtected.ReadPlainText();
|
||||
Init(bEnableProtection, pb);
|
||||
|
||||
if(bEnableProtection) MemUtil.ZeroByteArray(pb);
|
||||
try { Init(bEnableProtection, pb); }
|
||||
finally { if(bEnableProtection) MemUtil.ZeroByteArray(pb); }
|
||||
}
|
||||
|
||||
private void Init(bool bEnableProtection, string str)
|
||||
@@ -160,7 +175,7 @@ namespace ModernKeePassLib.Security
|
||||
|
||||
m_bIsProtected = bEnableProtection;
|
||||
|
||||
// The string already is in memory and immutable,
|
||||
// As the string already is in memory and immutable,
|
||||
// protection would be useless
|
||||
m_strPlainText = str;
|
||||
}
|
||||
@@ -178,8 +193,8 @@ namespace ModernKeePassLib.Security
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the protected string to a normal string object.
|
||||
/// Be careful with this function, the returned string object
|
||||
/// 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>
|
||||
@@ -194,23 +209,42 @@ namespace ModernKeePassLib.Security
|
||||
// No need to clear pb
|
||||
|
||||
// As the text is now visible in process memory anyway,
|
||||
// there's no need to protect it anymore
|
||||
// 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 string is not protected
|
||||
/// anymore!
|
||||
/// 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 pBin = m_pbUtf8; // Local ref for thread-safety
|
||||
if(pBin != null) return pBin.ReadData();
|
||||
ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety
|
||||
if(p != null) return p.ReadData();
|
||||
|
||||
return StrUtil.Utf8.GetBytes(m_strPlainText);
|
||||
}
|
||||
@@ -223,7 +257,7 @@ namespace ModernKeePassLib.Security
|
||||
/// <returns>Protected string.</returns>
|
||||
public byte[] ReadXorredString(CryptoRandomStream crsRandomSource)
|
||||
{
|
||||
Debug.Assert(crsRandomSource != null); if(crsRandomSource == null) throw new ArgumentNullException("crsRandomSource");
|
||||
if(crsRandomSource == null) { Debug.Assert(false); throw new ArgumentNullException("crsRandomSource"); }
|
||||
|
||||
byte[] pbData = ReadUtf8();
|
||||
uint uLen = (uint)pbData.Length;
|
||||
@@ -242,10 +276,34 @@ namespace ModernKeePassLib.Security
|
||||
if(bProtect == m_bIsProtected) return this;
|
||||
|
||||
byte[] pb = ReadUtf8();
|
||||
ProtectedString ps = new ProtectedString(bProtect, pb);
|
||||
|
||||
if(bProtect) MemUtil.ZeroByteArray(pb);
|
||||
return ps;
|
||||
// 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)
|
||||
@@ -254,18 +312,14 @@ namespace ModernKeePassLib.Security
|
||||
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;
|
||||
char[] v = ReadChars(), vNew = null;
|
||||
byte[] pbNew = null;
|
||||
ProtectedString ps;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -279,21 +333,20 @@ namespace ModernKeePassLib.Security
|
||||
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);
|
||||
MemUtil.ZeroByteArray(pb);
|
||||
if(vNew != null) MemUtil.ZeroArray<char>(vNew);
|
||||
if(pbNew != null) MemUtil.ZeroByteArray(pbNew);
|
||||
}
|
||||
|
||||
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<char>(vNew);
|
||||
MemUtil.ZeroByteArray(pbNew);
|
||||
return ps;
|
||||
}
|
||||
|
||||
@@ -303,44 +356,81 @@ namespace ModernKeePassLib.Security
|
||||
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;
|
||||
char[] v = ReadChars(), vNew = null;
|
||||
byte[] pbNew = null;
|
||||
ProtectedString ps;
|
||||
|
||||
try
|
||||
{
|
||||
if((iStart + nCount) > v.Length)
|
||||
throw new ArgumentException("iStart + nCount");
|
||||
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);
|
||||
MemUtil.ZeroByteArray(pb);
|
||||
if(vNew != null) MemUtil.ZeroArray<char>(vNew);
|
||||
if(pbNew != null) MemUtil.ZeroByteArray(pbNew);
|
||||
}
|
||||
|
||||
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<char>(vNew);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user