Setup solution

This commit is contained in:
Geoffroy BONNEVILLE
2019-07-25 16:39:43 +02:00
parent 81509be167
commit 1b2007e6dd
136 changed files with 35834 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
/*
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.Collections.Generic;
using System.IO;
using System.Text;
using ModernKeePassLib.Utility;
namespace ModernKeePassLib.Serialization
{
public sealed class BinaryReaderEx
{
private Stream m_s;
// private Encoding m_enc; // See constructor
private string m_strReadExcp; // May be null
public string ReadExceptionText
{
get { return m_strReadExcp; }
set { m_strReadExcp = value; }
}
private Stream m_sCopyTo = null;
/// <summary>
/// If this property is set to a non-null stream, all data that
/// is read from the input stream is automatically written to
/// the copy stream (before returning the read data).
/// </summary>
public Stream CopyDataTo
{
get { return m_sCopyTo; }
set { m_sCopyTo = value; }
}
public BinaryReaderEx(Stream input, Encoding encoding,
string strReadExceptionText)
{
if(input == null) throw new ArgumentNullException("input");
m_s = input;
// m_enc = encoding; // Not used yet
m_strReadExcp = strReadExceptionText;
}
public byte[] ReadBytes(int nCount)
{
try
{
byte[] pb = MemUtil.Read(m_s, nCount);
if((pb == null) || (pb.Length != nCount))
{
if(!string.IsNullOrEmpty(m_strReadExcp))
throw new EndOfStreamException(m_strReadExcp);
else throw new EndOfStreamException();
}
if(m_sCopyTo != null) m_sCopyTo.Write(pb, 0, pb.Length);
return pb;
}
catch(Exception)
{
if(!string.IsNullOrEmpty(m_strReadExcp))
throw new IOException(m_strReadExcp);
else throw;
}
}
public byte ReadByte()
{
byte[] pb = ReadBytes(1);
return pb[0];
}
}
}

View File

@@ -0,0 +1,274 @@
/*
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.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
#if ModernKeePassLib
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Storage.Streams;
#endif
using ModernKeePassLib.Cryptography;
using ModernKeePassLib.Resources;
using ModernKeePassLib.Utility;
namespace ModernKeePassLib.Serialization
{
public sealed class FileLockException : Exception
{
private readonly string m_strMsg;
public override string Message
{
get { return m_strMsg; }
}
public FileLockException(string strBaseFile, string strUser)
{
StringBuilder sb = new StringBuilder();
if(!string.IsNullOrEmpty(strBaseFile))
{
sb.Append(strBaseFile);
sb.Append(MessageService.NewParagraph);
}
sb.Append(KLRes.FileLockedWrite);
sb.Append(MessageService.NewLine);
if(!string.IsNullOrEmpty(strUser)) sb.Append(strUser);
else sb.Append("?");
sb.Append(MessageService.NewParagraph);
sb.Append(KLRes.TryAgainSecs);
m_strMsg = sb.ToString();
}
}
public sealed class FileLock : IDisposable
{
private const string LockFileExt = ".lock";
private const string LockFileHeader = "KeePass Lock File";
private IOConnectionInfo m_iocLockFile;
private sealed class LockFileInfo
{
public readonly string ID;
public readonly DateTime Time;
public readonly string UserName;
public readonly string Machine;
public readonly string Domain;
private LockFileInfo(string strID, string strTime, string strUserName,
string strMachine, string strDomain)
{
this.ID = (strID ?? string.Empty).Trim();
DateTime dt;
if(TimeUtil.TryDeserializeUtc(strTime.Trim(), out dt))
this.Time = dt;
else
{
Debug.Assert(false);
this.Time = DateTime.UtcNow;
}
this.UserName = (strUserName ?? string.Empty).Trim();
this.Machine = (strMachine ?? string.Empty).Trim();
this.Domain = (strDomain ?? string.Empty).Trim();
if(this.Domain.Equals(this.Machine, StrUtil.CaseIgnoreCmp))
this.Domain = string.Empty;
}
public string GetOwner()
{
StringBuilder sb = new StringBuilder();
sb.Append((this.UserName.Length > 0) ? this.UserName : "?");
bool bMachine = (this.Machine.Length > 0);
bool bDomain = (this.Domain.Length > 0);
if(bMachine || bDomain)
{
sb.Append(" (");
sb.Append(this.Machine);
if(bMachine && bDomain) sb.Append(" @ ");
sb.Append(this.Domain);
sb.Append(")");
}
return sb.ToString();
}
public static LockFileInfo Load(IOConnectionInfo iocLockFile)
{
Stream s = null;
try
{
s = IOConnection.OpenRead(iocLockFile);
if(s == null) return null;
string str = null;
using(StreamReader sr = new StreamReader(s, StrUtil.Utf8))
{
str = sr.ReadToEnd();
}
if(str == null) { Debug.Assert(false); return null; }
str = StrUtil.NormalizeNewLines(str, false);
string[] v = str.Split('\n');
if((v == null) || (v.Length < 6)) { Debug.Assert(false); return null; }
if(!v[0].StartsWith(LockFileHeader)) { Debug.Assert(false); return null; }
return new LockFileInfo(v[1], v[2], v[3], v[4], v[5]);
}
catch(FileNotFoundException) { }
catch(Exception) { Debug.Assert(false); }
finally { if(s != null) s.Dispose(); }
return null;
}
// Throws on error
public static LockFileInfo Create(IOConnectionInfo iocLockFile)
{
LockFileInfo lfi;
Stream s = null;
try
{
byte[] pbID = CryptoRandom.Instance.GetRandomBytes(16);
string strTime = TimeUtil.SerializeUtc(DateTime.UtcNow);
lfi = new LockFileInfo(Convert.ToBase64String(pbID), strTime,
#if KeePassUAP
EnvironmentExt.UserName, EnvironmentExt.MachineName,
EnvironmentExt.UserDomainName);
#elif ModernKeePassLib|| KeePassLibSD
string.Empty, string.Empty, string.Empty);
#else
Environment.UserName, Environment.MachineName,
Environment.UserDomainName);
#endif
StringBuilder sb = new StringBuilder();
#if !KeePassLibSD
sb.AppendLine(LockFileHeader);
sb.AppendLine(lfi.ID);
sb.AppendLine(strTime);
sb.AppendLine(lfi.UserName);
sb.AppendLine(lfi.Machine);
sb.AppendLine(lfi.Domain);
#else
sb.Append(LockFileHeader + MessageService.NewLine);
sb.Append(lfi.ID + MessageService.NewLine);
sb.Append(strTime + MessageService.NewLine);
sb.Append(lfi.UserName + MessageService.NewLine);
sb.Append(lfi.Machine + MessageService.NewLine);
sb.Append(lfi.Domain + MessageService.NewLine);
#endif
byte[] pbFile = StrUtil.Utf8.GetBytes(sb.ToString());
s = IOConnection.OpenWrite(iocLockFile);
if(s == null) throw new IOException(iocLockFile.GetDisplayName());
s.Write(pbFile, 0, pbFile.Length);
}
finally { if(s != null) s.Dispose(); }
return lfi;
}
}
public FileLock(IOConnectionInfo iocBaseFile)
{
if(iocBaseFile == null) throw new ArgumentNullException("strBaseFile");
m_iocLockFile = iocBaseFile.CloneDeep();
m_iocLockFile.Path += LockFileExt;
LockFileInfo lfiEx = LockFileInfo.Load(m_iocLockFile);
if(lfiEx != null)
{
m_iocLockFile = null; // Otherwise Dispose deletes the existing one
throw new FileLockException(iocBaseFile.GetDisplayName(),
lfiEx.GetOwner());
}
LockFileInfo.Create(m_iocLockFile);
}
~FileLock()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool bDisposing)
{
if(m_iocLockFile == null) return;
bool bFileDeleted = false;
for(int r = 0; r < 5; ++r)
{
// if(!OwnLockFile()) { bFileDeleted = true; break; }
try
{
IOConnection.DeleteFile(m_iocLockFile);
bFileDeleted = true;
}
catch(Exception) { Debug.Assert(false); }
if(bFileDeleted) break;
#if ModernKeePassLib
if(bDisposing)
Task.Delay(50).Wait();
#else
if(bDisposing) Thread.Sleep(50);
#endif
}
// if(bDisposing && !bFileDeleted)
// IOConnection.DeleteFile(m_iocLockFile); // Possibly with exception
m_iocLockFile = null;
}
// private bool OwnLockFile()
// {
// if(m_iocLockFile == null) { Debug.Assert(false); return false; }
// if(m_strLockID == null) { Debug.Assert(false); return false; }
// LockFileInfo lfi = LockFileInfo.Load(m_iocLockFile);
// if(lfi == null) return false;
// return m_strLockID.Equals(lfi.ID);
// }
}
}

View File

@@ -0,0 +1,474 @@
/*
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.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Text;
#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT)
using System.Security.AccessControl;
#endif
using ModernKeePassLib.Native;
using ModernKeePassLib.Utility;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.Streams;
using ModernKeePassLib.Cryptography;
using ModernKeePassLib.Resources;
namespace ModernKeePassLib.Serialization
{
public sealed class FileTransactionEx : IDisposable
{
private bool m_bTransacted;
private IOConnectionInfo m_iocBase; // Null means disposed
private IOConnectionInfo m_iocTemp;
private IOConnectionInfo m_iocTxfMidFallback = null; // Null <=> TxF not used
private bool m_bMadeUnhidden = false;
private List<IOConnectionInfo> m_lToDelete = new List<IOConnectionInfo>();
private const string StrTempSuffix = ".tmp";
private static readonly string StrTxfTempPrefix = PwDefs.ShortProductName + "_TxF_";
private const string StrTxfTempSuffix = ".tmp";
private static Dictionary<string, bool> g_dEnabled =
new Dictionary<string, bool>(StrUtil.CaseIgnoreComparer);
private static bool g_bExtraSafe = false;
internal static bool ExtraSafe
{
get { return g_bExtraSafe; }
set { g_bExtraSafe = value; }
}
public FileTransactionEx(IOConnectionInfo iocBaseFile) :
this(iocBaseFile, true)
{
}
public FileTransactionEx(IOConnectionInfo iocBaseFile, bool bTransacted)
{
if(iocBaseFile == null) throw new ArgumentNullException("iocBaseFile");
m_bTransacted = bTransacted;
m_iocBase = iocBaseFile.CloneDeep();
if(m_iocBase.IsLocalFile())
m_iocBase.Path = UrlUtil.GetShortestAbsolutePath(m_iocBase.Path);
string strPath = m_iocBase.Path;
#if !ModernKeePassLib
if(m_iocBase.IsLocalFile())
{
try
{
if(File.Exists(strPath))
{
// Symbolic links are realized via reparse points;
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365503.aspx
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365680.aspx
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365006.aspx
// Performing a file transaction on a symbolic link
// would delete/replace the symbolic link instead of
// writing to its target
FileAttributes fa = File.GetAttributes(strPath);
if((long)(fa & FileAttributes.ReparsePoint) != 0)
m_bTransacted = false;
}
else
{
// If the base and the temporary file are in different
// folders and the base file doesn't exist (i.e. we can't
// backup the ACL), a transaction would cause the new file
// to have the default ACL of the temporary folder instead
// of the one of the base folder; therefore, we don't use
// a transaction when the base file doesn't exist (this
// also results in other applications monitoring the folder
// to see one file creation only)
m_bTransacted = false;
}
}
catch(Exception) { Debug.Assert(false); }
}
#endif
#if !ModernKeePassLib
// Prevent transactions for FTP URLs under .NET 4.0 in order to
// avoid/workaround .NET bug 621450:
// https://connect.microsoft.com/VisualStudio/feedback/details/621450/problem-renaming-file-on-ftp-server-using-ftpwebrequest-in-net-framework-4-0-vs2010-only
if(strPath.StartsWith("ftp:", StrUtil.CaseIgnoreCmp) &&
(Environment.Version.Major >= 4) && !NativeLib.IsUnix())
m_bTransacted = false;
#endif
foreach(KeyValuePair<string, bool> kvp in g_dEnabled)
{
if(strPath.StartsWith(kvp.Key, StrUtil.CaseIgnoreCmp))
{
m_bTransacted = kvp.Value;
break;
}
}
if(m_bTransacted)
{
m_iocTemp = m_iocBase.CloneDeep();
m_iocTemp.Path += StrTempSuffix;
TxfPrepare(); // Adjusts m_iocTemp
}
else m_iocTemp = m_iocBase;
}
~FileTransactionEx()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool bDisposing)
{
m_iocBase = null;
if(!bDisposing) return;
try
{
foreach(IOConnectionInfo ioc in m_lToDelete)
{
if(IOConnection.FileExists(ioc, false))
IOConnection.DeleteFile(ioc);
}
m_lToDelete.Clear();
}
catch(Exception) { Debug.Assert(false); }
}
public Stream OpenWrite()
{
if(m_iocBase == null) { Debug.Assert(false); throw new ObjectDisposedException(null); }
if(!m_bTransacted) m_bMadeUnhidden |= UrlUtil.UnhideFile(m_iocTemp.Path);
return IOConnection.OpenWrite(m_iocTemp);
}
public void CommitWrite()
{
if(m_iocBase == null) { Debug.Assert(false); throw new ObjectDisposedException(null); }
if(!m_bTransacted)
{
if(m_bMadeUnhidden) UrlUtil.HideFile(m_iocTemp.Path, true);
}
else CommitWriteTransaction();
m_iocBase = null; // Dispose
}
private void CommitWriteTransaction()
{
if(g_bExtraSafe)
{
if(!IOConnection.FileExists(m_iocTemp))
throw new FileNotFoundException(m_iocTemp.Path +
MessageService.NewLine + KLRes.FileSaveFailed);
}
bool bMadeUnhidden = UrlUtil.UnhideFile(m_iocBase.Path);
#if !ModernKeePassLib
// 'All' includes 'Audit' (SACL), which requires SeSecurityPrivilege,
// which we usually don't have and therefore get an exception;
// trying to set 'Owner' or 'Group' can result in an
// UnauthorizedAccessException; thus we restore 'Access' (DACL) only
const AccessControlSections acs = AccessControlSections.Access;
#endif
bool bEfsEncrypted = false;
byte[] pbSec = null;
DateTime? otCreation = null;
bool bBaseExists = IOConnection.FileExists(m_iocBase);
if(bBaseExists && m_iocBase.IsLocalFile())
{
// FileAttributes faBase = FileAttributes.Normal;
try
{
#if !ModernKeePassLib
FileAttributes faBase = File.GetAttributes(m_iocBase.Path);
bEfsEncrypted = ((long)(faBase & FileAttributes.Encrypted) != 0);
try { if(bEfsEncrypted) File.Decrypt(m_iocBase.Path); } // For TxF
catch(Exception) { Debug.Assert(false); }
#endif
#if ModernKeePassLib
otCreation = m_iocBase.StorageFile.DateCreated.UtcDateTime;
#else
otCreation = File.GetCreationTimeUtc(m_iocBase.Path);
#endif
#if !ModernKeePassLib
// May throw with Mono
FileSecurity sec = File.GetAccessControl(m_iocBase.Path, acs);
if(sec != null) pbSec = sec.GetSecurityDescriptorBinaryForm();
#endif
}
catch(Exception) { Debug.Assert(NativeLib.IsUnix()); }
// if((long)(faBase & FileAttributes.ReadOnly) != 0)
// throw new UnauthorizedAccessException();
}
if(!TxfMove())
{
if(bBaseExists) IOConnection.DeleteFile(m_iocBase);
IOConnection.RenameFile(m_iocTemp, m_iocBase);
}
else { Debug.Assert(pbSec != null); } // TxF success => NTFS => has ACL
try
{
// If File.GetCreationTimeUtc fails, it may return a
// date with year 1601, and Unix times start in 1970,
// so testing for 1971 should ensure validity;
// https://msdn.microsoft.com/en-us/library/system.io.file.getcreationtimeutc.aspx
#if !ModernKeePassLib
if(otCreation.HasValue && (otCreation.Value.Year >= 1971))
File.SetCreationTimeUtc(m_iocBase.Path, otCreation.Value);
#endif
#if !ModernKeePassLib
if(bEfsEncrypted)
{
try { File.Encrypt(m_iocBase.Path); }
catch(Exception) { Debug.Assert(false); }
}
// File.SetAccessControl(m_iocBase.Path, secPrev);
// Directly calling File.SetAccessControl with the previous
// FileSecurity object does not work; the binary form
// indirection is required;
// https://sourceforge.net/p/keepass/bugs/1738/
// https://msdn.microsoft.com/en-us/library/system.io.file.setaccesscontrol.aspx
if((pbSec != null) && (pbSec.Length != 0))
{
FileSecurity sec = new FileSecurity();
sec.SetSecurityDescriptorBinaryForm(pbSec, acs);
File.SetAccessControl(m_iocBase.Path, sec);
}
#endif
}
catch(Exception) { Debug.Assert(false); }
if(bMadeUnhidden) UrlUtil.HideFile(m_iocBase.Path, true);
}
// For plugins
public static void Configure(string strPrefix, bool? obTransacted)
{
if(string.IsNullOrEmpty(strPrefix)) { Debug.Assert(false); return; }
if(obTransacted.HasValue)
g_dEnabled[strPrefix] = obTransacted.Value;
else g_dEnabled.Remove(strPrefix);
}
private static bool TxfIsSupported(char chDriveLetter)
{
if(chDriveLetter == '\0') return false;
#if ModernKeePassLib
return true;
#else
try
{
string strRoot = (new string(chDriveLetter, 1)) + ":\\";
const int cch = NativeMethods.MAX_PATH + 1;
StringBuilder sbName = new StringBuilder(cch + 1);
uint uSerial = 0, cchMaxComp = 0, uFlags = 0;
StringBuilder sbFileSystem = new StringBuilder(cch + 1);
if(!NativeMethods.GetVolumeInformation(strRoot, sbName, (uint)cch,
ref uSerial, ref cchMaxComp, ref uFlags, sbFileSystem, (uint)cch))
{
Debug.Assert(false, (new Win32Exception()).Message);
return false;
}
return ((uFlags & NativeMethods.FILE_SUPPORTS_TRANSACTIONS) != 0);
}
catch(Exception) { Debug.Assert(false); }
return false;
#endif
}
private void TxfPrepare()
{
try
{
if(NativeLib.IsUnix()) return;
if(!m_iocBase.IsLocalFile()) return;
string strID = StrUtil.AlphaNumericOnly(Convert.ToBase64String(
CryptoRandom.Instance.GetRandomBytes(16)));
string strTempDir = UrlUtil.GetTempPath();
// See also ClearOld method
string strTemp = UrlUtil.EnsureTerminatingSeparator(strTempDir,
false) + StrTxfTempPrefix + strID + StrTxfTempSuffix;
char chB = UrlUtil.GetDriveLetter(m_iocBase.Path);
char chT = UrlUtil.GetDriveLetter(strTemp);
if(!TxfIsSupported(chB)) return;
if((chT != chB) && !TxfIsSupported(chT)) return;
m_iocTxfMidFallback = m_iocTemp;
#if ModernKeePassLib
var tempFile = ApplicationData.Current.TemporaryFolder.CreateFileAsync(m_iocTemp.Path).GetAwaiter()
.GetResult();
m_iocTemp = IOConnectionInfo.FromFile(tempFile);
#else
m_iocTemp = IOConnectionInfo.FromPath(strTemp);
#endif
m_lToDelete.Add(m_iocTemp);
}
catch(Exception) { Debug.Assert(false); m_iocTxfMidFallback = null; }
}
private bool TxfMove()
{
if(m_iocTxfMidFallback == null) return false;
if(TxfMoveWithTx()) return true;
// Move the temporary file onto the base file's drive first,
// such that it cannot happen that both the base file and
// the temporary file are deleted/corrupted
#if ModernKeePassLib
m_iocTemp.StorageFile = ApplicationData.Current.TemporaryFolder.CreateFileAsync(m_iocTemp.Path).GetAwaiter()
.GetResult();
#else
const uint f = (NativeMethods.MOVEFILE_COPY_ALLOWED |
NativeMethods.MOVEFILE_REPLACE_EXISTING);
bool b = NativeMethods.MoveFileEx(m_iocTemp.Path, m_iocTxfMidFallback.Path, f);
if(b) b = NativeMethods.MoveFileEx(m_iocTxfMidFallback.Path, m_iocBase.Path, f);
if(!b) throw new Win32Exception();
Debug.Assert(!File.Exists(m_iocTemp.Path));
Debug.Assert(!File.Exists(m_iocTxfMidFallback.Path));
#endif
return true;
}
private bool TxfMoveWithTx()
{
#if ModernKeePassLib
return true;
#else
IntPtr hTx = new IntPtr((int)NativeMethods.INVALID_HANDLE_VALUE);
Debug.Assert(hTx.ToInt64() == NativeMethods.INVALID_HANDLE_VALUE);
try
{
string strTx = PwDefs.ShortProductName + " TxF - " +
StrUtil.AlphaNumericOnly(Convert.ToBase64String(
CryptoRandom.Instance.GetRandomBytes(16)));
const int mchTx = NativeMethods.MAX_TRANSACTION_DESCRIPTION_LENGTH;
if(strTx.Length >= mchTx) strTx = strTx.Substring(0, mchTx - 1);
hTx = NativeMethods.CreateTransaction(IntPtr.Zero,
IntPtr.Zero, 0, 0, 0, 0, strTx);
if(hTx.ToInt64() == NativeMethods.INVALID_HANDLE_VALUE)
{
Debug.Assert(false, (new Win32Exception()).Message);
return false;
}
if(!NativeMethods.MoveFileTransacted(m_iocTemp.Path, m_iocBase.Path,
IntPtr.Zero, IntPtr.Zero, (NativeMethods.MOVEFILE_COPY_ALLOWED |
NativeMethods.MOVEFILE_REPLACE_EXISTING), hTx))
{
Debug.Assert(false, (new Win32Exception()).Message);
return false;
}
if(!NativeMethods.CommitTransaction(hTx))
{
Debug.Assert(false, (new Win32Exception()).Message);
return false;
}
Debug.Assert(!File.Exists(m_iocTemp.Path));
return true;
}
catch(Exception) { Debug.Assert(false); }
finally
{
if(hTx.ToInt64() != NativeMethods.INVALID_HANDLE_VALUE)
{
try { if(!NativeMethods.CloseHandle(hTx)) { Debug.Assert(false); } }
catch(Exception) { Debug.Assert(false); }
}
}
return false;
#endif
}
internal static void ClearOld()
{
try
{
#if ModernKeePassLib
ApplicationData.Current.TemporaryFolder.GetFileAsync(UrlUtil.GetTempPath()).GetAwaiter()
.GetResult().DeleteAsync().GetAwaiter().GetResult();
#else
// See also TxfPrepare method
DirectoryInfo di = new DirectoryInfo(UrlUtil.GetTempPath());
List<FileInfo> l = UrlUtil.GetFileInfos(di, StrTxfTempPrefix +
"*" + StrTxfTempSuffix, SearchOption.TopDirectoryOnly);
foreach(FileInfo fi in l)
{
if(fi == null) { Debug.Assert(false); continue; }
if(!fi.Name.StartsWith(StrTxfTempPrefix, StrUtil.CaseIgnoreCmp) ||
!fi.Name.EndsWith(StrTxfTempSuffix, StrUtil.CaseIgnoreCmp))
continue;
if((DateTime.UtcNow - fi.LastWriteTimeUtc).TotalDays > 1.0)
fi.Delete();
}
#endif
}
catch(Exception) { Debug.Assert(false); }
}
}
}

View File

@@ -0,0 +1,312 @@
/*
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.IO;
using System.Text;
using ModernKeePassLib.Cryptography;
using ModernKeePassLib.Native;
using ModernKeePassLib.Utility;
#if KeePassLibSD
using KeePassLibSD;
#endif
namespace ModernKeePassLib.Serialization
{
public sealed class HashedBlockStream : Stream
{
private const int NbDefaultBufferSize = 1024 * 1024; // 1 MB
private Stream m_sBaseStream;
private bool m_bWriting;
private bool m_bVerify;
private bool m_bEos = false;
private BinaryReader m_brInput;
private BinaryWriter m_bwOutput;
private byte[] m_pbBuffer;
private int m_nBufferPos = 0;
private uint m_uBlockIndex = 0;
public override bool CanRead
{
get { return !m_bWriting; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return m_bWriting; }
}
public override long Length
{
get { Debug.Assert(false); throw new NotSupportedException(); }
}
public override long Position
{
get { Debug.Assert(false); throw new NotSupportedException(); }
set { Debug.Assert(false); throw new NotSupportedException(); }
}
public HashedBlockStream(Stream sBaseStream, bool bWriting)
{
Initialize(sBaseStream, bWriting, 0, true);
}
public HashedBlockStream(Stream sBaseStream, bool bWriting, int nBufferSize)
{
Initialize(sBaseStream, bWriting, nBufferSize, true);
}
public HashedBlockStream(Stream sBaseStream, bool bWriting, int nBufferSize,
bool bVerify)
{
Initialize(sBaseStream, bWriting, nBufferSize, bVerify);
}
private void Initialize(Stream sBaseStream, bool bWriting, int nBufferSize,
bool bVerify)
{
if(sBaseStream == null) throw new ArgumentNullException("sBaseStream");
if(nBufferSize < 0) throw new ArgumentOutOfRangeException("nBufferSize");
if(nBufferSize == 0) nBufferSize = NbDefaultBufferSize;
m_sBaseStream = sBaseStream;
m_bWriting = bWriting;
m_bVerify = bVerify;
UTF8Encoding utf8 = StrUtil.Utf8;
if(!m_bWriting) // Reading mode
{
if(!m_sBaseStream.CanRead)
throw new InvalidOperationException();
m_brInput = new BinaryReader(sBaseStream, utf8);
m_pbBuffer = MemUtil.EmptyByteArray;
}
else // Writing mode
{
if(!m_sBaseStream.CanWrite)
throw new InvalidOperationException();
m_bwOutput = new BinaryWriter(sBaseStream, utf8);
m_pbBuffer = new byte[nBufferSize];
}
}
protected override void Dispose(bool disposing)
{
if(disposing && (m_sBaseStream != null))
{
if(!m_bWriting) // Reading mode
{
m_brInput.Dispose();
m_brInput = null;
}
else // Writing mode
{
if(m_nBufferPos == 0) // No data left in buffer
WriteHashedBlock(); // Write terminating block
else
{
WriteHashedBlock(); // Write remaining buffered data
WriteHashedBlock(); // Write terminating block
}
Flush();
m_bwOutput.Dispose();
m_bwOutput = null;
}
m_sBaseStream.Dispose();
m_sBaseStream = null;
}
base.Dispose(disposing);
}
public override void Flush()
{
if(m_bWriting) m_bwOutput.Flush();
}
public override long Seek(long lOffset, SeekOrigin soOrigin)
{
throw new NotSupportedException();
}
public override void SetLength(long lValue)
{
throw new NotSupportedException();
}
public override int Read(byte[] pbBuffer, int nOffset, int nCount)
{
if(m_bWriting) throw new InvalidOperationException();
int nRemaining = nCount;
while(nRemaining > 0)
{
if(m_nBufferPos == m_pbBuffer.Length)
{
if(ReadHashedBlock() == false)
return (nCount - nRemaining); // Bytes actually read
}
int nCopy = Math.Min(m_pbBuffer.Length - m_nBufferPos, nRemaining);
Array.Copy(m_pbBuffer, m_nBufferPos, pbBuffer, nOffset, nCopy);
nOffset += nCopy;
m_nBufferPos += nCopy;
nRemaining -= nCopy;
}
return nCount;
}
private bool ReadHashedBlock()
{
if(m_bEos) return false; // End of stream reached already
m_nBufferPos = 0;
if(m_brInput.ReadUInt32() != m_uBlockIndex)
throw new InvalidDataException();
++m_uBlockIndex;
byte[] pbStoredHash = m_brInput.ReadBytes(32);
if((pbStoredHash == null) || (pbStoredHash.Length != 32))
throw new InvalidDataException();
int nBufferSize = 0;
try { nBufferSize = m_brInput.ReadInt32(); }
catch(NullReferenceException) // Mono bug workaround (LaunchPad 783268)
{
if(!NativeLib.IsUnix()) throw;
}
if(nBufferSize < 0)
throw new InvalidDataException();
if(nBufferSize == 0)
{
for(int iHash = 0; iHash < 32; ++iHash)
{
if(pbStoredHash[iHash] != 0)
throw new InvalidDataException();
}
m_bEos = true;
m_pbBuffer = MemUtil.EmptyByteArray;
return false;
}
m_pbBuffer = m_brInput.ReadBytes(nBufferSize);
if((m_pbBuffer == null) || ((m_pbBuffer.Length != nBufferSize) && m_bVerify))
throw new InvalidDataException();
if(m_bVerify)
{
byte[] pbComputedHash = CryptoUtil.HashSha256(m_pbBuffer);
if((pbComputedHash == null) || (pbComputedHash.Length != 32))
throw new InvalidOperationException();
if(!MemUtil.ArraysEqual(pbStoredHash, pbComputedHash))
throw new InvalidDataException();
}
return true;
}
public override void Write(byte[] pbBuffer, int nOffset, int nCount)
{
if(!m_bWriting) throw new InvalidOperationException();
while(nCount > 0)
{
if(m_nBufferPos == m_pbBuffer.Length)
WriteHashedBlock();
int nCopy = Math.Min(m_pbBuffer.Length - m_nBufferPos, nCount);
Array.Copy(pbBuffer, nOffset, m_pbBuffer, m_nBufferPos, nCopy);
nOffset += nCopy;
m_nBufferPos += nCopy;
nCount -= nCopy;
}
}
private void WriteHashedBlock()
{
m_bwOutput.Write(m_uBlockIndex);
++m_uBlockIndex;
if(m_nBufferPos > 0)
{
byte[] pbHash = CryptoUtil.HashSha256(m_pbBuffer, 0, m_nBufferPos);
// For KeePassLibSD:
// SHA256Managed sha256 = new SHA256Managed();
// byte[] pbHash;
// if(m_nBufferPos == m_pbBuffer.Length)
// pbHash = sha256.ComputeHash(m_pbBuffer);
// else
// {
// byte[] pbData = new byte[m_nBufferPos];
// Array.Copy(m_pbBuffer, 0, pbData, 0, m_nBufferPos);
// pbHash = sha256.ComputeHash(pbData);
// }
m_bwOutput.Write(pbHash);
}
else
{
m_bwOutput.Write((ulong)0); // Zero hash
m_bwOutput.Write((ulong)0);
m_bwOutput.Write((ulong)0);
m_bwOutput.Write((ulong)0);
}
m_bwOutput.Write(m_nBufferPos);
if(m_nBufferPos > 0)
m_bwOutput.Write(m_pbBuffer, 0, m_nBufferPos);
m_nBufferPos = 0;
}
}
}

View File

@@ -0,0 +1,324 @@
/*
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.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
#if !KeePassUAP
using System.Security.Cryptography;
#endif
using ModernKeePassLib.Resources;
using ModernKeePassLib.Utility;
namespace ModernKeePassLib.Serialization
{
public sealed class HmacBlockStream : Stream
{
private const int NbDefaultBufferSize = 1024 * 1024; // 1 MB
private Stream m_sBase;
private readonly bool m_bWriting;
private readonly bool m_bVerify;
private byte[] m_pbKey;
private bool m_bEos = false;
private byte[] m_pbBuffer;
private int m_iBufferPos = 0;
private ulong m_uBlockIndex = 0;
public override bool CanRead
{
get { return !m_bWriting; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return m_bWriting; }
}
public override long Length
{
get { Debug.Assert(false); throw new NotSupportedException(); }
}
public override long Position
{
get { Debug.Assert(false); throw new NotSupportedException(); }
set { Debug.Assert(false); throw new NotSupportedException(); }
}
public HmacBlockStream(Stream sBase, bool bWriting, bool bVerify,
byte[] pbKey)
{
if(sBase == null) throw new ArgumentNullException("sBase");
if(pbKey == null) throw new ArgumentNullException("pbKey");
m_sBase = sBase;
m_bWriting = bWriting;
m_bVerify = bVerify;
m_pbKey = pbKey;
if(!m_bWriting) // Reading mode
{
if(!m_sBase.CanRead) throw new InvalidOperationException();
m_pbBuffer = MemUtil.EmptyByteArray;
}
else // Writing mode
{
if(!m_sBase.CanWrite) throw new InvalidOperationException();
m_pbBuffer = new byte[NbDefaultBufferSize];
}
}
protected override void Dispose(bool disposing)
{
if(disposing && (m_sBase != null))
{
if(m_bWriting)
{
if(m_iBufferPos == 0) // No data left in buffer
WriteSafeBlock(); // Write terminating block
else
{
WriteSafeBlock(); // Write remaining buffered data
WriteSafeBlock(); // Write terminating block
}
Flush();
}
m_sBase.Dispose();
m_sBase = null;
}
base.Dispose(disposing);
}
public override void Flush()
{
Debug.Assert(m_sBase != null); // Object should not be disposed
if(m_bWriting && (m_sBase != null)) m_sBase.Flush();
}
public override long Seek(long lOffset, SeekOrigin soOrigin)
{
Debug.Assert(false);
throw new NotSupportedException();
}
public override void SetLength(long lValue)
{
Debug.Assert(false);
throw new NotSupportedException();
}
internal static byte[] GetHmacKey64(byte[] pbKey, ulong uBlockIndex)
{
if(pbKey == null) throw new ArgumentNullException("pbKey");
Debug.Assert(pbKey.Length == 64);
// We are computing the HMAC using SHA-256, whose internal
// block size is 512 bits; thus create a key that is 512
// bits long (using SHA-512)
byte[] pbBlockKey;
using(SHA512Managed h = new SHA512Managed())
{
byte[] pbIndex = MemUtil.UInt64ToBytes(uBlockIndex);
h.TransformBlock(pbIndex, 0, pbIndex.Length, pbIndex, 0);
h.TransformBlock(pbKey, 0, pbKey.Length, pbKey, 0);
h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0);
pbBlockKey = h.Hash;
}
#if DEBUG
byte[] pbZero = new byte[64];
Debug.Assert((pbBlockKey.Length == 64) && !MemUtil.ArraysEqual(
pbBlockKey, pbZero)); // Ensure we own pbBlockKey
#endif
return pbBlockKey;
}
public override int Read(byte[] pbBuffer, int iOffset, int nCount)
{
if(m_bWriting) throw new InvalidOperationException();
int nRemaining = nCount;
while(nRemaining > 0)
{
if(m_iBufferPos == m_pbBuffer.Length)
{
if(!ReadSafeBlock())
return (nCount - nRemaining); // Bytes actually read
}
int nCopy = Math.Min(m_pbBuffer.Length - m_iBufferPos, nRemaining);
Debug.Assert(nCopy > 0);
Array.Copy(m_pbBuffer, m_iBufferPos, pbBuffer, iOffset, nCopy);
iOffset += nCopy;
m_iBufferPos += nCopy;
nRemaining -= nCopy;
}
return nCount;
}
private bool ReadSafeBlock()
{
if(m_bEos) return false; // End of stream reached already
byte[] pbStoredHmac = MemUtil.Read(m_sBase, 32);
if((pbStoredHmac == null) || (pbStoredHmac.Length != 32))
throw new EndOfStreamException(KLRes.FileCorrupted + " " +
KLRes.FileIncomplete);
// Block index is implicit: it's used in the HMAC computation,
// but does not need to be stored
// byte[] pbBlockIndex = MemUtil.Read(m_sBase, 8);
// if((pbBlockIndex == null) || (pbBlockIndex.Length != 8))
// throw new EndOfStreamException();
// ulong uBlockIndex = MemUtil.BytesToUInt64(pbBlockIndex);
// if((uBlockIndex != m_uBlockIndex) && m_bVerify)
// throw new InvalidDataException();
byte[] pbBlockIndex = MemUtil.UInt64ToBytes(m_uBlockIndex);
byte[] pbBlockSize = MemUtil.Read(m_sBase, 4);
if((pbBlockSize == null) || (pbBlockSize.Length != 4))
throw new EndOfStreamException(KLRes.FileCorrupted + " " +
KLRes.FileIncomplete);
int nBlockSize = MemUtil.BytesToInt32(pbBlockSize);
if(nBlockSize < 0)
throw new InvalidDataException(KLRes.FileCorrupted);
m_iBufferPos = 0;
m_pbBuffer = MemUtil.Read(m_sBase, nBlockSize);
if((m_pbBuffer == null) || ((m_pbBuffer.Length != nBlockSize) && m_bVerify))
throw new EndOfStreamException(KLRes.FileCorrupted + " " +
KLRes.FileIncompleteExpc);
if(m_bVerify)
{
byte[] pbCmpHmac;
byte[] pbBlockKey = GetHmacKey64(m_pbKey, m_uBlockIndex);
using(HMACSHA256 h = new HMACSHA256(pbBlockKey))
{
h.TransformBlock(pbBlockIndex, 0, pbBlockIndex.Length,
pbBlockIndex, 0);
h.TransformBlock(pbBlockSize, 0, pbBlockSize.Length,
pbBlockSize, 0);
if(m_pbBuffer.Length > 0)
h.TransformBlock(m_pbBuffer, 0, m_pbBuffer.Length,
m_pbBuffer, 0);
h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0);
pbCmpHmac = h.Hash;
}
MemUtil.ZeroByteArray(pbBlockKey);
if(!MemUtil.ArraysEqual(pbCmpHmac, pbStoredHmac))
throw new InvalidDataException(KLRes.FileCorrupted);
}
++m_uBlockIndex;
if(nBlockSize == 0)
{
m_bEos = true;
return false; // No further data available
}
return true;
}
public override void Write(byte[] pbBuffer, int iOffset, int nCount)
{
if(!m_bWriting) throw new InvalidOperationException();
while(nCount > 0)
{
if(m_iBufferPos == m_pbBuffer.Length)
WriteSafeBlock();
int nCopy = Math.Min(m_pbBuffer.Length - m_iBufferPos, nCount);
Debug.Assert(nCopy > 0);
Array.Copy(pbBuffer, iOffset, m_pbBuffer, m_iBufferPos, nCopy);
iOffset += nCopy;
m_iBufferPos += nCopy;
nCount -= nCopy;
}
}
private void WriteSafeBlock()
{
byte[] pbBlockIndex = MemUtil.UInt64ToBytes(m_uBlockIndex);
int cbBlockSize = m_iBufferPos;
byte[] pbBlockSize = MemUtil.Int32ToBytes(cbBlockSize);
byte[] pbBlockHmac;
byte[] pbBlockKey = GetHmacKey64(m_pbKey, m_uBlockIndex);
using(HMACSHA256 h = new HMACSHA256(pbBlockKey))
{
h.TransformBlock(pbBlockIndex, 0, pbBlockIndex.Length,
pbBlockIndex, 0);
h.TransformBlock(pbBlockSize, 0, pbBlockSize.Length,
pbBlockSize, 0);
if(cbBlockSize > 0)
h.TransformBlock(m_pbBuffer, 0, cbBlockSize, m_pbBuffer, 0);
h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0);
pbBlockHmac = h.Hash;
}
MemUtil.ZeroByteArray(pbBlockKey);
MemUtil.Write(m_sBase, pbBlockHmac);
// MemUtil.Write(m_sBase, pbBlockIndex); // Implicit
MemUtil.Write(m_sBase, pbBlockSize);
if(cbBlockSize > 0)
m_sBase.Write(m_pbBuffer, 0, cbBlockSize);
++m_uBlockIndex;
m_iBufferPos = 0;
}
}
}

View File

@@ -0,0 +1,926 @@
/*
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.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Reflection;
using System.Text;
#if (!ModernKeePassLib && !KeePassLibSD && !KeePassUAP)
using System.Net.Cache;
using System.Net.Security;
#endif
#if !ModernKeePassLib && !KeePassUAP
using System.Security.Cryptography.X509Certificates;
#endif
#if ModernKeePassLib
using Windows.Storage;
using Windows.Storage.Streams;
#endif
using ModernKeePassLib.Native;
using ModernKeePassLib.Utility;
namespace ModernKeePassLib.Serialization
{
#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT)
internal sealed class IOWebClient : WebClient
{
private IOConnectionInfo m_ioc;
public IOWebClient(IOConnectionInfo ioc) : base()
{
m_ioc = ioc;
}
protected override WebRequest GetWebRequest(Uri address)
{
WebRequest request = base.GetWebRequest(address);
IOConnection.ConfigureWebRequest(request, m_ioc);
return request;
}
}
#endif
#if !ModernKeePassLib
internal abstract class WrapperStream : Stream
{
private readonly Stream m_s;
protected Stream BaseStream
{
get { return m_s; }
}
public override bool CanRead
{
get { return m_s.CanRead; }
}
public override bool CanSeek
{
get { return m_s.CanSeek; }
}
public override bool CanTimeout
{
get { return m_s.CanTimeout; }
}
public override bool CanWrite
{
get { return m_s.CanWrite; }
}
public override long Length
{
get { return m_s.Length; }
}
public override long Position
{
get { return m_s.Position; }
set { m_s.Position = value; }
}
public override int ReadTimeout
{
get { return m_s.ReadTimeout; }
set { m_s.ReadTimeout = value; }
}
public override int WriteTimeout
{
get { return m_s.WriteTimeout; }
set { m_s.WriteTimeout = value; }
}
public WrapperStream(Stream sBase) : base()
{
if(sBase == null) throw new ArgumentNullException("sBase");
m_s = sBase;
}
#if !KeePassUAP
public override IAsyncResult BeginRead(byte[] buffer, int offset,
int count, AsyncCallback callback, object state)
{
return m_s.BeginRead(buffer, offset, count, callback, state);
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset,
int count, AsyncCallback callback, object state)
{
return BeginWrite(buffer, offset, count, callback, state);
}
#endif
protected override void Dispose(bool disposing)
{
if(disposing) m_s.Dispose();
base.Dispose(disposing);
}
#if !KeePassUAP
public override int EndRead(IAsyncResult asyncResult)
{
return m_s.EndRead(asyncResult);
}
public override void EndWrite(IAsyncResult asyncResult)
{
m_s.EndWrite(asyncResult);
}
#endif
public override void Flush()
{
m_s.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
return m_s.Read(buffer, offset, count);
}
public override int ReadByte()
{
return m_s.ReadByte();
}
public override long Seek(long offset, SeekOrigin origin)
{
return m_s.Seek(offset, origin);
}
public override void SetLength(long value)
{
m_s.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
m_s.Write(buffer, offset, count);
}
public override void WriteByte(byte value)
{
m_s.WriteByte(value);
}
}
internal sealed class IocStream : WrapperStream
{
private readonly bool m_bWrite; // Initially opened for writing
private bool m_bDisposed = false;
public IocStream(Stream sBase) : base(sBase)
{
m_bWrite = sBase.CanWrite;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if(disposing && MonoWorkarounds.IsRequired(10163) && m_bWrite &&
!m_bDisposed)
{
try
{
Stream s = this.BaseStream;
Type t = s.GetType();
if(t.Name == "WebConnectionStream")
{
PropertyInfo pi = t.GetProperty("Request",
BindingFlags.Instance | BindingFlags.NonPublic);
if(pi != null)
{
WebRequest wr = (pi.GetValue(s, null) as WebRequest);
if(wr != null)
IOConnection.DisposeResponse(wr.GetResponse(), false);
else { Debug.Assert(false); }
}
else { Debug.Assert(false); }
}
}
catch(Exception) { Debug.Assert(false); }
}
m_bDisposed = true;
}
public static Stream WrapIfRequired(Stream s)
{
if(s == null) { Debug.Assert(false); return null; }
if(MonoWorkarounds.IsRequired(10163) && s.CanWrite)
return new IocStream(s);
return s;
}
}
#endif
public static class IOConnection
{
#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT)
private static ProxyServerType m_pstProxyType = ProxyServerType.System;
private static string m_strProxyAddr = string.Empty;
private static string m_strProxyPort = string.Empty;
private static ProxyAuthType m_patProxyAuthType = ProxyAuthType.Auto;
private static string m_strProxyUserName = string.Empty;
private static string m_strProxyPassword = string.Empty;
#if !KeePassUAP
private static bool? m_obDefaultExpect100Continue = null;
private static bool m_bSslCertsAcceptInvalid = false;
internal static bool SslCertsAcceptInvalid
{
// get { return m_bSslCertsAcceptInvalid; }
set { m_bSslCertsAcceptInvalid = value; }
}
#endif
#endif
// Web request methods
public static readonly string WrmDeleteFile = "DELETEFILE";
public static readonly string WrmMoveFile = "MOVEFILE";
// Web request headers
public static readonly string WrhMoveFileTo = "MoveFileTo";
public static event EventHandler<IOAccessEventArgs> IOAccessPre;
#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT)
// Allow self-signed certificates, expired certificates, etc.
private static bool AcceptCertificate(object sender,
X509Certificate certificate, X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
return true;
}
internal static void SetProxy(ProxyServerType pst, string strAddr,
string strPort, ProxyAuthType pat, string strUserName,
string strPassword)
{
m_pstProxyType = pst;
m_strProxyAddr = (strAddr ?? string.Empty);
m_strProxyPort = (strPort ?? string.Empty);
m_patProxyAuthType = pat;
m_strProxyUserName = (strUserName ?? string.Empty);
m_strProxyPassword = (strPassword ?? string.Empty);
}
internal static void ConfigureWebRequest(WebRequest request,
IOConnectionInfo ioc)
{
if(request == null) { Debug.Assert(false); return; } // No throw
IocProperties p = ((ioc != null) ? ioc.Properties : null);
if(p == null) { Debug.Assert(false); p = new IocProperties(); }
IHasIocProperties ihpReq = (request as IHasIocProperties);
if(ihpReq != null)
{
IocProperties pEx = ihpReq.IOConnectionProperties;
if(pEx != null) p.CopyTo(pEx);
else ihpReq.IOConnectionProperties = p.CloneDeep();
}
if(IsHttpWebRequest(request))
{
// WebDAV support
#if !KeePassUAP
request.PreAuthenticate = true; // Also auth GET
#endif
if(string.Equals(request.Method, WebRequestMethods.Http.Post,
StrUtil.CaseIgnoreCmp))
request.Method = WebRequestMethods.Http.Put;
#if !KeePassUAP
HttpWebRequest hwr = (request as HttpWebRequest);
if(hwr != null)
{
string strUA = p.Get(IocKnownProperties.UserAgent);
if(!string.IsNullOrEmpty(strUA)) hwr.UserAgent = strUA;
}
else { Debug.Assert(false); }
#endif
}
#if !KeePassUAP
else if(IsFtpWebRequest(request))
{
FtpWebRequest fwr = (request as FtpWebRequest);
if(fwr != null)
{
bool? obPassive = p.GetBool(IocKnownProperties.Passive);
if(obPassive.HasValue) fwr.UsePassive = obPassive.Value;
}
else { Debug.Assert(false); }
}
#endif
#if !KeePassUAP
// Not implemented and ignored in Mono < 2.10
try
{
request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
}
catch(NotImplementedException) { }
catch(Exception) { Debug.Assert(false); }
#endif
try
{
IWebProxy prx;
if(GetWebProxy(out prx)) request.Proxy = prx;
}
catch(Exception) { Debug.Assert(false); }
#if !KeePassUAP
long? olTimeout = p.GetLong(IocKnownProperties.Timeout);
if(olTimeout.HasValue && (olTimeout.Value >= 0))
request.Timeout = (int)Math.Min(olTimeout.Value, (long)int.MaxValue);
bool? ob = p.GetBool(IocKnownProperties.PreAuth);
if(ob.HasValue) request.PreAuthenticate = ob.Value;
#endif
}
internal static void ConfigureWebClient(WebClient wc)
{
#if !KeePassUAP
// Not implemented and ignored in Mono < 2.10
try
{
wc.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
}
catch(NotImplementedException) { }
catch(Exception) { Debug.Assert(false); }
#endif
try
{
IWebProxy prx;
if(GetWebProxy(out prx)) wc.Proxy = prx;
}
catch(Exception) { Debug.Assert(false); }
}
private static bool GetWebProxy(out IWebProxy prx)
{
bool b = GetWebProxyServer(out prx);
if(b) AssignCredentials(prx);
return b;
}
private static bool GetWebProxyServer(out IWebProxy prx)
{
prx = null;
if(m_pstProxyType == ProxyServerType.None)
return true; // Use null proxy
if(m_pstProxyType == ProxyServerType.Manual)
{
try
{
if(m_strProxyAddr.Length == 0)
{
// First try default (from config), then system
prx = WebRequest.DefaultWebProxy;
#if !KeePassUAP
if(prx == null) prx = WebRequest.GetSystemWebProxy();
#endif
}
else if(m_strProxyPort.Length > 0)
prx = new WebProxy(m_strProxyAddr, int.Parse(m_strProxyPort));
else prx = new WebProxy(m_strProxyAddr);
return (prx != null);
}
#if KeePassUAP
catch(Exception) { Debug.Assert(false); }
#else
catch(Exception ex)
{
string strInfo = m_strProxyAddr;
if(m_strProxyPort.Length > 0)
strInfo += ":" + m_strProxyPort;
MessageService.ShowWarning(strInfo, ex);
}
#endif
return false; // Use default
}
Debug.Assert(m_pstProxyType == ProxyServerType.System);
try
{
// First try system, then default (from config)
#if !KeePassUAP
prx = WebRequest.GetSystemWebProxy();
#endif
if(prx == null) prx = WebRequest.DefaultWebProxy;
return (prx != null);
}
catch(Exception) { Debug.Assert(false); }
return false;
}
private static void AssignCredentials(IWebProxy prx)
{
if(prx == null) return; // No assert
string strUserName = m_strProxyUserName;
string strPassword = m_strProxyPassword;
ProxyAuthType pat = m_patProxyAuthType;
if(pat == ProxyAuthType.Auto)
{
if((strUserName.Length > 0) || (strPassword.Length > 0))
pat = ProxyAuthType.Manual;
else pat = ProxyAuthType.Default;
}
try
{
if(pat == ProxyAuthType.None)
prx.Credentials = null;
else if(pat == ProxyAuthType.Default)
prx.Credentials = CredentialCache.DefaultCredentials;
else if(pat == ProxyAuthType.Manual)
{
if((strUserName.Length > 0) || (strPassword.Length > 0))
prx.Credentials = new NetworkCredential(
strUserName, strPassword);
}
else { Debug.Assert(false); }
}
catch(Exception) { Debug.Assert(false); }
}
private static void PrepareWebAccess(IOConnectionInfo ioc)
{
#if !KeePassUAP
IocProperties p = ((ioc != null) ? ioc.Properties : null);
if(p == null) { Debug.Assert(false); p = new IocProperties(); }
try
{
if(m_bSslCertsAcceptInvalid)
ServicePointManager.ServerCertificateValidationCallback =
IOConnection.AcceptCertificate;
else
ServicePointManager.ServerCertificateValidationCallback = null;
}
catch(Exception) { Debug.Assert(false); }
try
{
SecurityProtocolType spt = (SecurityProtocolType.Ssl3 |
SecurityProtocolType.Tls);
// The flags Tls11 and Tls12 in SecurityProtocolType have been
// introduced in .NET 4.5 and must not be set when running under
// older .NET versions (otherwise an exception is thrown)
Type tSpt = typeof(SecurityProtocolType);
string[] vSpt = Enum.GetNames(tSpt);
foreach(string strSpt in vSpt)
{
if(strSpt.Equals("Tls11", StrUtil.CaseIgnoreCmp))
spt |= (SecurityProtocolType)Enum.Parse(tSpt, "Tls11", true);
else if(strSpt.Equals("Tls12", StrUtil.CaseIgnoreCmp))
spt |= (SecurityProtocolType)Enum.Parse(tSpt, "Tls12", true);
}
ServicePointManager.SecurityProtocol = spt;
}
catch(Exception) { Debug.Assert(false); }
try
{
bool bCurCont = ServicePointManager.Expect100Continue;
if(!m_obDefaultExpect100Continue.HasValue)
{
Debug.Assert(bCurCont); // Default should be true
m_obDefaultExpect100Continue = bCurCont;
}
bool bNewCont = m_obDefaultExpect100Continue.Value;
bool? ob = p.GetBool(IocKnownProperties.Expect100Continue);
if(ob.HasValue) bNewCont = ob.Value;
if(bNewCont != bCurCont)
ServicePointManager.Expect100Continue = bNewCont;
}
catch(Exception) { Debug.Assert(false); }
#endif
}
private static IOWebClient CreateWebClient(IOConnectionInfo ioc)
{
PrepareWebAccess(ioc);
IOWebClient wc = new IOWebClient(ioc);
ConfigureWebClient(wc);
if((ioc.UserName.Length > 0) || (ioc.Password.Length > 0))
wc.Credentials = new NetworkCredential(ioc.UserName, ioc.Password);
else if(NativeLib.IsUnix()) // Mono requires credentials
wc.Credentials = new NetworkCredential("anonymous", string.Empty);
return wc;
}
private static WebRequest CreateWebRequest(IOConnectionInfo ioc)
{
PrepareWebAccess(ioc);
WebRequest req = WebRequest.Create(ioc.Path);
ConfigureWebRequest(req, ioc);
if((ioc.UserName.Length > 0) || (ioc.Password.Length > 0))
req.Credentials = new NetworkCredential(ioc.UserName, ioc.Password);
else if(NativeLib.IsUnix()) // Mono requires credentials
req.Credentials = new NetworkCredential("anonymous", string.Empty);
return req;
}
public static Stream OpenRead(IOConnectionInfo ioc)
{
RaiseIOAccessPreEvent(ioc, IOAccessType.Read);
if(StrUtil.IsDataUri(ioc.Path))
{
byte[] pbData = StrUtil.DataUriToData(ioc.Path);
if(pbData != null) return new MemoryStream(pbData, false);
}
if(ioc.IsLocalFile()) return OpenReadLocal(ioc);
return IocStream.WrapIfRequired(CreateWebClient(ioc).OpenRead(
new Uri(ioc.Path)));
}
#else
public static Stream OpenRead(IOConnectionInfo ioc)
{
RaiseIOAccessPreEvent(ioc, IOAccessType.Read);
return OpenReadLocal(ioc);
}
#endif
private static Stream OpenReadLocal(IOConnectionInfo ioc)
{
return ioc.StorageFile.OpenAsync(FileAccessMode.Read).GetAwaiter().GetResult().AsStream();
}
#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT)
public static Stream OpenWrite(IOConnectionInfo ioc)
{
if(ioc == null) { Debug.Assert(false); return null; }
RaiseIOAccessPreEvent(ioc, IOAccessType.Write);
if(ioc.IsLocalFile()) return OpenWriteLocal(ioc);
Uri uri = new Uri(ioc.Path);
Stream s;
// Mono does not set HttpWebRequest.Method to POST for writes,
// so one needs to set the method to PUT explicitly
if(NativeLib.IsUnix() && IsHttpWebRequest(uri))
s = CreateWebClient(ioc).OpenWrite(uri, WebRequestMethods.Http.Put);
else s = CreateWebClient(ioc).OpenWrite(uri);
return IocStream.WrapIfRequired(s);
}
#else
public static Stream OpenWrite(IOConnectionInfo ioc)
{
RaiseIOAccessPreEvent(ioc, IOAccessType.Write);
return OpenWriteLocal(ioc);
}
#endif
private static Stream OpenWriteLocal(IOConnectionInfo ioc)
{
#if ModernKeePassLib
return ioc.StorageFile.OpenAsync(FileAccessMode.ReadWrite).GetAwaiter().GetResult().AsStream();
#else
return new FileStream(ioc.Path, FileMode.Create, FileAccess.Write,
FileShare.None);
#endif
}
public static bool FileExists(IOConnectionInfo ioc)
{
return FileExists(ioc, false);
}
public static bool FileExists(IOConnectionInfo ioc, bool bThrowErrors)
{
if(ioc == null) { Debug.Assert(false); return false; }
RaiseIOAccessPreEvent(ioc, IOAccessType.Exists);
#if ModernKeePassLib
return ioc.StorageFile.IsAvailable;
#else
if(ioc.IsLocalFile()) return File.Exists(ioc.Path);
#if !KeePassLibSD
if(ioc.Path.StartsWith("ftp://", StrUtil.CaseIgnoreCmp))
{
bool b = SendCommand(ioc, WebRequestMethods.Ftp.GetDateTimestamp);
if(!b && bThrowErrors) throw new InvalidOperationException();
return b;
}
#endif
try
{
Stream s = OpenRead(ioc);
if(s == null) throw new FileNotFoundException();
try { s.ReadByte(); }
catch(Exception) { }
// We didn't download the file completely; close may throw
// an exception -- that's okay
try { s.Close(); }
catch(Exception) { }
}
catch(Exception)
{
if(bThrowErrors) throw;
return false;
}
return true;
#endif
}
public static void DeleteFile(IOConnectionInfo ioc)
{
RaiseIOAccessPreEvent(ioc, IOAccessType.Delete);
#if ModernKeePassLib
if (!ioc.IsLocalFile()) return;
ioc.StorageFile?.DeleteAsync().GetAwaiter().GetResult();
#else
if(ioc.IsLocalFile()) { File.Delete(ioc.Path); return; }
#if !KeePassLibSD
WebRequest req = CreateWebRequest(ioc);
if(req != null)
{
if(IsHttpWebRequest(req)) req.Method = "DELETE";
else if(IsFtpWebRequest(req))
req.Method = WebRequestMethods.Ftp.DeleteFile;
else if(IsFileWebRequest(req))
{
File.Delete(UrlUtil.FileUrlToPath(ioc.Path));
return;
}
else req.Method = WrmDeleteFile;
DisposeResponse(req.GetResponse(), true);
}
#endif
#endif
}
/// <summary>
/// Rename/move a file. For local file system and WebDAV, the
/// specified file is moved, i.e. the file destination can be
/// in a different directory/path. In contrast, for FTP the
/// file is renamed, i.e. its destination must be in the same
/// directory/path.
/// </summary>
/// <param name="iocFrom">Source file path.</param>
/// <param name="iocTo">Target file path.</param>
public static void RenameFile(IOConnectionInfo iocFrom, IOConnectionInfo iocTo)
{
RaiseIOAccessPreEvent(iocFrom, iocTo, IOAccessType.Move);
#if ModernKeePassLib
if (!iocFrom.IsLocalFile()) return;
iocFrom.StorageFile?.RenameAsync(iocTo.Path).GetAwaiter().GetResult();
#else
if(iocFrom.IsLocalFile()) { File.Move(iocFrom.Path, iocTo.Path); return; }
#if !KeePassLibSD
WebRequest req = CreateWebRequest(iocFrom);
if(req != null)
{
if(IsHttpWebRequest(req))
{
#if KeePassUAP
throw new NotSupportedException();
#else
req.Method = "MOVE";
req.Headers.Set("Destination", iocTo.Path); // Full URL supported
#endif
}
else if(IsFtpWebRequest(req))
{
#if KeePassUAP
throw new NotSupportedException();
#else
req.Method = WebRequestMethods.Ftp.Rename;
string strTo = UrlUtil.GetFileName(iocTo.Path);
// We're affected by .NET bug 621450:
// https://connect.microsoft.com/VisualStudio/feedback/details/621450/problem-renaming-file-on-ftp-server-using-ftpwebrequest-in-net-framework-4-0-vs2010-only
// Prepending "./", "%2E/" or "Dummy/../" doesn't work.
((FtpWebRequest)req).RenameTo = strTo;
#endif
}
else if(IsFileWebRequest(req))
{
File.Move(UrlUtil.FileUrlToPath(iocFrom.Path),
UrlUtil.FileUrlToPath(iocTo.Path));
return;
}
else
{
#if KeePassUAP
throw new NotSupportedException();
#else
req.Method = WrmMoveFile;
req.Headers.Set(WrhMoveFileTo, iocTo.Path);
#endif
}
#if !KeePassUAP // Unreachable code
DisposeResponse(req.GetResponse(), true);
#endif
}
#endif
// using(Stream sIn = IOConnection.OpenRead(iocFrom))
// {
// using(Stream sOut = IOConnection.OpenWrite(iocTo))
// {
// MemUtil.CopyStream(sIn, sOut);
// sOut.Close();
// }
//
// sIn.Close();
// }
// DeleteFile(iocFrom);
#endif
}
#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT)
private static bool SendCommand(IOConnectionInfo ioc, string strMethod)
{
try
{
WebRequest req = CreateWebRequest(ioc);
req.Method = strMethod;
DisposeResponse(req.GetResponse(), true);
}
catch(Exception) { return false; }
return true;
}
#endif
#if !ModernKeePassLib
internal static void DisposeResponse(WebResponse wr, bool bGetStream)
{
if(wr == null) return;
try
{
if(bGetStream)
{
Stream s = wr.GetResponseStream();
if(s != null) s.Close();
}
}
catch(Exception) { Debug.Assert(false); }
try { wr.Close(); }
catch(Exception) { Debug.Assert(false); }
}
#endif
public static byte[] ReadFile(IOConnectionInfo ioc)
{
Stream sIn = null;
MemoryStream ms = null;
try
{
sIn = IOConnection.OpenRead(ioc);
if(sIn == null) return null;
ms = new MemoryStream();
MemUtil.CopyStream(sIn, ms);
return ms.ToArray();
}
catch(Exception) { }
finally
{
if(sIn != null) sIn.Dispose();
if(ms != null) ms.Dispose();
}
return null;
}
private static void RaiseIOAccessPreEvent(IOConnectionInfo ioc, IOAccessType t)
{
RaiseIOAccessPreEvent(ioc, null, t);
}
private static void RaiseIOAccessPreEvent(IOConnectionInfo ioc,
IOConnectionInfo ioc2, IOAccessType t)
{
if(ioc == null) { Debug.Assert(false); return; }
// ioc2 may be null
if(IOConnection.IOAccessPre != null)
{
IOConnectionInfo ioc2Lcl = ((ioc2 != null) ? ioc2.CloneDeep() : null);
IOAccessEventArgs e = new IOAccessEventArgs(ioc.CloneDeep(), ioc2Lcl, t);
IOConnection.IOAccessPre(null, e);
}
}
#if !ModernKeePassLib
private static bool IsHttpWebRequest(Uri uri)
{
if(uri == null) { Debug.Assert(false); return false; }
string sch = uri.Scheme;
if(sch == null) { Debug.Assert(false); return false; }
return (sch.Equals("http", StrUtil.CaseIgnoreCmp) || // Uri.UriSchemeHttp
sch.Equals("https", StrUtil.CaseIgnoreCmp)); // Uri.UriSchemeHttps
}
internal static bool IsHttpWebRequest(WebRequest wr)
{
if(wr == null) { Debug.Assert(false); return false; }
#if KeePassUAP
return IsHttpWebRequest(wr.RequestUri);
#else
return (wr is HttpWebRequest);
#endif
}
internal static bool IsFtpWebRequest(WebRequest wr)
{
if(wr == null) { Debug.Assert(false); return false; }
#if KeePassUAP
return string.Equals(wr.RequestUri.Scheme, "ftp", StrUtil.CaseIgnoreCmp);
#else
return (wr is FtpWebRequest);
#endif
}
private static bool IsFileWebRequest(WebRequest wr)
{
if(wr == null) { Debug.Assert(false); return false; }
#if KeePassUAP
return string.Equals(wr.RequestUri.Scheme, "file", StrUtil.CaseIgnoreCmp);
#else
return (wr is FileWebRequest);
#endif
}
#endif // ModernKeePass
}
}

View File

@@ -0,0 +1,407 @@
/*
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.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml.Serialization;
#if ModernKeePassLib
using Windows.Storage;
//using PCLStorage;
#endif
using ModernKeePassLib.Interfaces;
using ModernKeePassLib.Utility;
using System.Threading.Tasks;
using Windows.Storage.AccessCache;
namespace ModernKeePassLib.Serialization
{
public enum IOCredSaveMode
{
/// <summary>
/// Do not remember user name or password.
/// </summary>
NoSave = 0,
/// <summary>
/// Remember the user name only, not the password.
/// </summary>
UserNameOnly,
/// <summary>
/// Save both user name and password.
/// </summary>
SaveCred
}
public enum IOCredProtMode
{
None = 0,
Obf
}
/* public enum IOFileFormatHint
{
None = 0,
Deprecated
} */
public sealed class IOConnectionInfo : IDeepCloneable<IOConnectionInfo>
{
// private IOFileFormatHint m_ioHint = IOFileFormatHint.None;
private string m_strUrl = string.Empty;
public string Path
{
get { return m_strUrl; }
set
{
Debug.Assert(value != null);
if(value == null) throw new ArgumentNullException("value");
m_strUrl = value;
}
}
private string m_strUser = string.Empty;
[DefaultValue("")]
public string UserName
{
get { return m_strUser; }
set
{
Debug.Assert(value != null);
if(value == null) throw new ArgumentNullException("value");
m_strUser = value;
}
}
private string m_strPassword = string.Empty;
[DefaultValue("")]
public string Password
{
get { return m_strPassword; }
set
{
Debug.Assert(value != null);
if(value == null) throw new ArgumentNullException("value");
m_strPassword = value;
}
}
private IOCredProtMode m_ioCredProtMode = IOCredProtMode.None;
public IOCredProtMode CredProtMode
{
get { return m_ioCredProtMode; }
set { m_ioCredProtMode = value; }
}
private IOCredSaveMode m_ioCredSaveMode = IOCredSaveMode.NoSave;
public IOCredSaveMode CredSaveMode
{
get { return m_ioCredSaveMode; }
set { m_ioCredSaveMode = value; }
}
private bool m_bComplete = false;
[XmlIgnore]
public bool IsComplete // Credentials etc. fully specified
{
get { return m_bComplete; }
set { m_bComplete = value; }
}
/* public IOFileFormatHint FileFormatHint
{
get { return m_ioHint; }
set { m_ioHint = value; }
} */
private IocProperties m_props = new IocProperties();
[XmlIgnore]
public IocProperties Properties
{
get { return m_props; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_props = value;
}
}
/// <summary>
/// For serialization only; use <c>Properties</c> in code.
/// </summary>
[DefaultValue("")]
public string PropertiesEx
{
get { return m_props.Serialize(); }
set
{
if(value == null) throw new ArgumentNullException("value");
IocProperties p = IocProperties.Deserialize(value);
Debug.Assert(p != null);
m_props = (p ?? new IocProperties());
}
}
public IOConnectionInfo CloneDeep()
{
IOConnectionInfo ioc = (IOConnectionInfo)this.MemberwiseClone();
ioc.m_props = m_props.CloneDeep();
return ioc;
}
#if DEBUG // For debugger display only
public override string ToString()
{
return GetDisplayName();
}
#endif
/*
/// <summary>
/// Serialize the current connection info to a string. Credentials
/// are serialized based on the <c>CredSaveMode</c> property.
/// </summary>
/// <param name="iocToCompile">Input object to be serialized.</param>
/// <returns>Serialized object as string.</returns>
public static string SerializeToString(IOConnectionInfo iocToCompile)
{
Debug.Assert(iocToCompile != null);
if(iocToCompile == null) throw new ArgumentNullException("iocToCompile");
string strUrl = iocToCompile.Path;
string strUser = TransformUnreadable(iocToCompile.UserName, true);
string strPassword = TransformUnreadable(iocToCompile.Password, true);
string strAll = strUrl + strUser + strPassword + "CUN";
char chSep = StrUtil.GetUnusedChar(strAll);
if(chSep == char.MinValue) throw new FormatException();
StringBuilder sb = new StringBuilder();
sb.Append(chSep);
sb.Append(strUrl);
sb.Append(chSep);
if(iocToCompile.CredSaveMode == IOCredSaveMode.SaveCred)
{
sb.Append('C');
sb.Append(chSep);
sb.Append(strUser);
sb.Append(chSep);
sb.Append(strPassword);
}
else if(iocToCompile.CredSaveMode == IOCredSaveMode.UserNameOnly)
{
sb.Append('U');
sb.Append(chSep);
sb.Append(strUser);
sb.Append(chSep);
}
else // Don't remember credentials
{
sb.Append('N');
sb.Append(chSep);
sb.Append(chSep);
}
return sb.ToString();
}
public static IOConnectionInfo UnserializeFromString(string strToDecompile)
{
Debug.Assert(strToDecompile != null);
if(strToDecompile == null) throw new ArgumentNullException("strToDecompile");
if(strToDecompile.Length <= 1) throw new ArgumentException();
char chSep = strToDecompile[0];
string[] vParts = strToDecompile.Substring(1, strToDecompile.Length -
1).Split(new char[]{ chSep });
if(vParts.Length < 4) throw new ArgumentException();
IOConnectionInfo s = new IOConnectionInfo();
s.Path = vParts[0];
if(vParts[1] == "C")
s.CredSaveMode = IOCredSaveMode.SaveCred;
else if(vParts[1] == "U")
s.CredSaveMode = IOCredSaveMode.UserNameOnly;
else
s.CredSaveMode = IOCredSaveMode.NoSave;
s.UserName = TransformUnreadable(vParts[2], false);
s.Password = TransformUnreadable(vParts[3], false);
return s;
}
*/
/*
/// <summary>
/// Very simple string protection. Doesn't really encrypt the input
/// string, only encodes it that it's not readable on the first glance.
/// </summary>
/// <param name="strToEncode">The string to encode/decode.</param>
/// <param name="bEncode">If <c>true</c>, the string will be encoded,
/// otherwise it'll be decoded.</param>
/// <returns>Encoded/decoded string.</returns>
private static string TransformUnreadable(string strToEncode, bool bEncode)
{
Debug.Assert(strToEncode != null);
if(strToEncode == null) throw new ArgumentNullException("strToEncode");
if(bEncode)
{
byte[] pbUtf8 = StrUtil.Utf8.GetBytes(strToEncode);
unchecked
{
for(int iPos = 0; iPos < pbUtf8.Length; ++iPos)
pbUtf8[iPos] += (byte)(iPos * 11);
}
return Convert.ToBase64String(pbUtf8);
}
else // Decode
{
byte[] pbBase = Convert.FromBase64String(strToEncode);
unchecked
{
for(int iPos = 0; iPos < pbBase.Length; ++iPos)
pbBase[iPos] -= (byte)(iPos * 11);
}
return StrUtil.Utf8.GetString(pbBase, 0, pbBase.Length);
}
}
*/
public string GetDisplayName()
{
string str = m_strUrl;
if(m_strUser.Length > 0)
str += (" (" + m_strUser + ")");
return str;
}
public bool IsEmpty()
{
return (m_strUrl.Length == 0);
}
#if ModernKeePassLib
public static IOConnectionInfo FromFile(StorageFile file)
{
IOConnectionInfo ioc = new IOConnectionInfo();
ioc.StorageFile = file;
ioc.Path = file.Path;
ioc.CredSaveMode = IOCredSaveMode.NoSave;
return ioc;
}
public static IOConnectionInfo FromPath(string strPath)
{
IOConnectionInfo ioc = new IOConnectionInfo();
ioc.Path = strPath;
ioc.StorageFile = StorageApplicationPermissions.FutureAccessList.GetFileAsync(strPath).GetAwaiter().GetResult();
ioc.CredSaveMode = IOCredSaveMode.NoSave;
return ioc;
}
#else
public static IOConnectionInfo FromPath(string strPath)
{
IOConnectionInfo ioc = new IOConnectionInfo();
ioc.Path = strPath;
ioc.CredSaveMode = IOCredSaveMode.NoSave;
return ioc;
}
#endif
public StorageFile StorageFile { get; set; }
public bool CanProbablyAccess()
{
#if ModernKeePassLib
if (IsLocalFile())
{
//return (FileSystem.Current.GetFileFromPathAsync(m_strUrl).Result != null);
var file = StorageFile.GetFileFromPathAsync(m_strUrl).GetAwaiter().GetResult();
return file != null;
}
#else
if(IsLocalFile()) return File.Exists(m_strUrl);
#endif
return true;
}
public bool IsLocalFile()
{
// Not just ":/", see e.g. AppConfigEx.ChangePathRelAbs
return (m_strUrl.IndexOf("://") < 0);
}
public void ClearCredentials(bool bDependingOnRememberMode)
{
if((bDependingOnRememberMode == false) ||
(m_ioCredSaveMode == IOCredSaveMode.NoSave))
{
m_strUser = string.Empty;
}
if((bDependingOnRememberMode == false) ||
(m_ioCredSaveMode == IOCredSaveMode.NoSave) ||
(m_ioCredSaveMode == IOCredSaveMode.UserNameOnly))
{
m_strPassword = string.Empty;
}
}
public void Obfuscate(bool bObf)
{
if(bObf && (m_ioCredProtMode == IOCredProtMode.None))
{
m_strPassword = StrUtil.Obfuscate(m_strPassword);
m_ioCredProtMode = IOCredProtMode.Obf;
}
else if(!bObf && (m_ioCredProtMode == IOCredProtMode.Obf))
{
m_strPassword = StrUtil.Deobfuscate(m_strPassword);
m_ioCredProtMode = IOCredProtMode.None;
}
}
}
}

View File

@@ -0,0 +1,192 @@
/*
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.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Xml;
using ModernKeePassLib.Interfaces;
using ModernKeePassLib.Utility;
using StrDict = System.Collections.Generic.Dictionary<string, string>;
namespace ModernKeePassLib.Serialization
{
public interface IHasIocProperties
{
IocProperties IOConnectionProperties { get; set; }
}
public sealed class IocProperties : IDeepCloneable<IocProperties>
{
private StrDict m_dict = new StrDict();
public IocProperties()
{
}
public IocProperties CloneDeep()
{
IocProperties p = new IocProperties();
p.m_dict = new StrDict(m_dict);
return p;
}
public string Get(string strKey)
{
if(string.IsNullOrEmpty(strKey)) return null;
foreach(KeyValuePair<string, string> kvp in m_dict)
{
if(kvp.Key.Equals(strKey, StrUtil.CaseIgnoreCmp))
return kvp.Value;
}
return null;
}
public void Set(string strKey, string strValue)
{
if(string.IsNullOrEmpty(strKey)) { Debug.Assert(false); return; }
foreach(KeyValuePair<string, string> kvp in m_dict)
{
if(kvp.Key.Equals(strKey, StrUtil.CaseIgnoreCmp))
{
if(string.IsNullOrEmpty(strValue)) m_dict.Remove(kvp.Key);
else m_dict[kvp.Key] = strValue;
return;
}
}
if(!string.IsNullOrEmpty(strValue)) m_dict[strKey] = strValue;
}
public bool? GetBool(string strKey)
{
string str = Get(strKey);
if(string.IsNullOrEmpty(str)) return null;
return StrUtil.StringToBool(str);
}
public void SetBool(string strKey, bool? ob)
{
if(ob.HasValue) Set(strKey, (ob.Value ? "1" : "0"));
else Set(strKey, null);
}
public long? GetLong(string strKey)
{
string str = Get(strKey);
if(string.IsNullOrEmpty(str)) return null;
long l;
if(StrUtil.TryParseLongInvariant(str, out l)) return l;
Debug.Assert(false);
return null;
}
public void SetLong(string strKey, long? ol)
{
if(ol.HasValue)
Set(strKey, ol.Value.ToString(NumberFormatInfo.InvariantInfo));
else Set(strKey, null);
}
public string Serialize()
{
if(m_dict.Count == 0) return string.Empty;
StringBuilder sbAll = new StringBuilder();
foreach(KeyValuePair<string, string> kvp in m_dict)
{
sbAll.Append(kvp.Key);
sbAll.Append(kvp.Value);
}
string strAll = sbAll.ToString();
char chSepOuter = ';';
if(strAll.IndexOf(chSepOuter) >= 0)
chSepOuter = StrUtil.GetUnusedChar(strAll);
strAll += chSepOuter;
char chSepInner = '=';
if(strAll.IndexOf(chSepInner) >= 0)
chSepInner = StrUtil.GetUnusedChar(strAll);
StringBuilder sb = new StringBuilder();
sb.Append(chSepOuter);
sb.Append(chSepInner);
foreach(KeyValuePair<string, string> kvp in m_dict)
{
sb.Append(chSepOuter);
sb.Append(kvp.Key);
sb.Append(chSepInner);
sb.Append(kvp.Value);
}
return sb.ToString();
}
public static IocProperties Deserialize(string strSerialized)
{
IocProperties p = new IocProperties();
if(string.IsNullOrEmpty(strSerialized)) return p; // No assert
char chSepOuter = strSerialized[0];
string[] v = strSerialized.Substring(1).Split(new char[] { chSepOuter });
if((v == null) || (v.Length < 2)) { Debug.Assert(false); return p; }
string strMeta = v[0];
if(string.IsNullOrEmpty(strMeta)) { Debug.Assert(false); return p; }
char chSepInner = strMeta[0];
char[] vSepInner = new char[] { chSepInner };
for(int i = 1; i < v.Length; ++i)
{
string strProp = v[i];
if(string.IsNullOrEmpty(strProp)) { Debug.Assert(false); continue; }
string[] vProp = strProp.Split(vSepInner);
if((vProp == null) || (vProp.Length < 2)) { Debug.Assert(false); continue; }
Debug.Assert(vProp.Length == 2);
p.Set(vProp[0], vProp[1]);
}
return p;
}
public void CopyTo(IocProperties p)
{
if(p == null) { Debug.Assert(false); return; }
foreach(KeyValuePair<string, string> kvp in m_dict)
{
p.m_dict[kvp.Key] = kvp.Value;
}
}
}
}

View File

@@ -0,0 +1,99 @@
/*
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.Collections.Generic;
using System.Diagnostics;
using System.Text;
using ModernKeePassLib.Utility;
namespace ModernKeePassLib.Serialization
{
public sealed class IocPropertyInfo
{
private readonly string m_strName;
public string Name
{
get { return m_strName; }
}
private readonly Type m_t;
public Type Type
{
get { return m_t; }
}
private string m_strDisplayName;
public string DisplayName
{
get { return m_strDisplayName; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_strDisplayName = value;
}
}
private List<string> m_lProtocols = new List<string>();
public IEnumerable<string> Protocols
{
get { return m_lProtocols; }
}
public IocPropertyInfo(string strName, Type t, string strDisplayName,
string[] vProtocols)
{
if(strName == null) throw new ArgumentNullException("strName");
if(t == null) throw new ArgumentNullException("t");
if(strDisplayName == null) throw new ArgumentNullException("strDisplayName");
m_strName = strName;
m_t = t;
m_strDisplayName = strDisplayName;
AddProtocols(vProtocols);
}
public void AddProtocols(string[] v)
{
if(v == null) { Debug.Assert(false); return; }
foreach(string strProtocol in v)
{
if(strProtocol == null) continue;
string str = strProtocol.Trim();
if(str.Length == 0) continue;
bool bFound = false;
foreach(string strEx in m_lProtocols)
{
if(strEx.Equals(str, StrUtil.CaseIgnoreCmp))
{
bFound = true;
break;
}
}
if(!bFound) m_lProtocols.Add(str);
}
}
}
}

View File

@@ -0,0 +1,123 @@
/*
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.Collections.Generic;
using System.Diagnostics;
using System.Text;
using ModernKeePassLib.Resources;
using ModernKeePassLib.Utility;
namespace ModernKeePassLib.Serialization
{
public static class IocKnownProtocols
{
public static readonly string Http = "HTTP";
public static readonly string Https = "HTTPS";
public static readonly string WebDav = "WebDAV";
public static readonly string Ftp = "FTP";
}
public static class IocKnownProperties
{
public static readonly string Timeout = "Timeout";
public static readonly string PreAuth = "PreAuth";
public static readonly string UserAgent = "UserAgent";
public static readonly string Expect100Continue = "Expect100Continue";
public static readonly string Passive = "Passive";
}
public static class IocPropertyInfoPool
{
private static List<IocPropertyInfo> m_l = null;
public static IEnumerable<IocPropertyInfo> PropertyInfos
{
get { EnsureInitialized(); return m_l; }
}
private static void EnsureInitialized()
{
if(m_l != null) return;
string strGen = KLRes.General;
string strHttp = IocKnownProtocols.Http;
string strHttps = IocKnownProtocols.Https;
string strWebDav = IocKnownProtocols.WebDav;
string strFtp = IocKnownProtocols.Ftp;
string[] vGen = new string[] { strGen };
string[] vHttp = new string[] { strHttp, strHttps, strWebDav };
string[] vFtp = new string[] { strFtp };
List<IocPropertyInfo> l = new List<IocPropertyInfo>();
l.Add(new IocPropertyInfo(IocKnownProperties.Timeout,
typeof(long), KLRes.Timeout + " [ms]", vGen));
l.Add(new IocPropertyInfo(IocKnownProperties.PreAuth,
typeof(bool), KLRes.PreAuth, vGen));
l.Add(new IocPropertyInfo(IocKnownProperties.UserAgent,
typeof(string), KLRes.UserAgent, vHttp));
l.Add(new IocPropertyInfo(IocKnownProperties.Expect100Continue,
typeof(bool), KLRes.Expect100Continue, vHttp));
l.Add(new IocPropertyInfo(IocKnownProperties.Passive,
typeof(bool), KLRes.Passive, vFtp));
// l.Add(new IocPropertyInfo("Test", typeof(bool),
// "Long long long long long long long long long long long long long long long long long long long long",
// new string[] { "Proto 1/9", "Proto 2/9", "Proto 3/9", "Proto 4/9", "Proto 5/9",
// "Proto 6/9", "Proto 7/9", "Proto 8/9", "Proto 9/9" }));
m_l = l;
}
public static IocPropertyInfo Get(string strName)
{
if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return null; }
EnsureInitialized();
foreach(IocPropertyInfo pi in m_l)
{
if(pi.Name.Equals(strName, StrUtil.CaseIgnoreCmp))
return pi;
}
return null;
}
public static bool Add(IocPropertyInfo pi)
{
if(pi == null) { Debug.Assert(false); return false; }
// Name must be non-empty
string strName = pi.Name;
if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return false; }
IocPropertyInfo piEx = Get(strName); // Ensures initialized
if(piEx != null) { Debug.Assert(false); return false; } // Exists already
m_l.Add(pi);
return true;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,602 @@
/*
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
*/
// #define KDBX_BENCHMARK
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Security;
using System.Text;
using System.Xml;
#if !ModernKeePassLib && !KeePassUAP
using System.Security.Cryptography;
#else
using Windows.Storage;
#endif
#if !KeePassLibSD
using System.IO.Compression;
#else
using KeePassLibSD;
#endif
using ModernKeePassLib.Collections;
using ModernKeePassLib.Cryptography;
using ModernKeePassLib.Cryptography.Cipher;
using ModernKeePassLib.Cryptography.KeyDerivation;
using ModernKeePassLib.Interfaces;
using ModernKeePassLib.Keys;
using ModernKeePassLib.Resources;
using ModernKeePassLib.Security;
using ModernKeePassLib.Utility;
namespace ModernKeePassLib.Serialization
{
/// <summary>
/// Serialization to KeePass KDBX files.
/// </summary>
public sealed partial class KdbxFile
{
#if ModernKeePassLib
public void Load(StorageFile file, KdbxFormat fmt, IStatusLogger slLogger)
{
IOConnectionInfo ioc = IOConnectionInfo.FromFile(file);
Load(IOConnection.OpenRead(ioc), fmt, slLogger);
}
#else
/// <summary>
/// Load a KDBX file.
/// </summary>
/// <param name="strFilePath">File to load.</param>
/// <param name="fmt">Format.</param>
/// <param name="slLogger">Status logger (optional).</param>
public void Load(string strFilePath, KdbxFormat fmt, IStatusLogger slLogger)
{
IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFilePath);
Load(IOConnection.OpenRead(ioc), fmt, slLogger);
}
#endif
/// <summary>
/// Load a KDBX file from a stream.
/// </summary>
/// <param name="sSource">Stream to read the data from. Must contain
/// a KDBX stream.</param>
/// <param name="fmt">Format.</param>
/// <param name="slLogger">Status logger (optional).</param>
public void Load(Stream sSource, KdbxFormat fmt, IStatusLogger slLogger)
{
Debug.Assert(sSource != null);
if(sSource == null) throw new ArgumentNullException("sSource");
if(m_bUsedOnce)
throw new InvalidOperationException("Do not reuse KdbxFile objects!");
m_bUsedOnce = true;
#if KDBX_BENCHMARK
Stopwatch swTime = Stopwatch.StartNew();
#endif
m_format = fmt;
m_slLogger = slLogger;
m_pbsBinaries.Clear();
UTF8Encoding encNoBom = StrUtil.Utf8;
byte[] pbCipherKey = null;
byte[] pbHmacKey64 = null;
List<Stream> lStreams = new List<Stream>();
lStreams.Add(sSource);
HashingStreamEx sHashing = new HashingStreamEx(sSource, false, null);
lStreams.Add(sHashing);
try
{
Stream sXml;
if(fmt == KdbxFormat.Default)
{
BinaryReaderEx br = new BinaryReaderEx(sHashing,
encNoBom, KLRes.FileCorrupted);
byte[] pbHeader = LoadHeader(br);
m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader);
int cbEncKey, cbEncIV;
ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV);
ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64);
string strIncomplete = KLRes.FileHeaderCorrupted + " " +
KLRes.FileIncomplete;
Stream sPlain;
if(m_uFileVersion < FileVersion32_4)
{
Stream sDecrypted = EncryptStream(sHashing, iCipher,
pbCipherKey, cbEncIV, false);
if((sDecrypted == null) || (sDecrypted == sHashing))
throw new SecurityException(KLRes.CryptoStreamFailed);
lStreams.Add(sDecrypted);
BinaryReaderEx brDecrypted = new BinaryReaderEx(sDecrypted,
encNoBom, strIncomplete);
byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32);
if((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32))
throw new EndOfStreamException(strIncomplete);
if(!MemUtil.ArraysEqual(pbStoredStartBytes, m_pbStreamStartBytes))
throw new InvalidCompositeKeyException();
sPlain = new HashedBlockStream(sDecrypted, false, 0, !m_bRepairMode);
}
else // KDBX >= 4
{
byte[] pbStoredHash = MemUtil.Read(sHashing, 32);
if((pbStoredHash == null) || (pbStoredHash.Length != 32))
throw new EndOfStreamException(strIncomplete);
if(!MemUtil.ArraysEqual(m_pbHashOfHeader, pbStoredHash))
throw new InvalidDataException(KLRes.FileHeaderCorrupted);
byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64);
byte[] pbStoredHmac = MemUtil.Read(sHashing, 32);
if((pbStoredHmac == null) || (pbStoredHmac.Length != 32))
throw new EndOfStreamException(strIncomplete);
if(!MemUtil.ArraysEqual(pbHeaderHmac, pbStoredHmac))
throw new InvalidCompositeKeyException();
HmacBlockStream sBlocks = new HmacBlockStream(sHashing,
false, !m_bRepairMode, pbHmacKey64);
lStreams.Add(sBlocks);
sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey,
cbEncIV, false);
if((sPlain == null) || (sPlain == sBlocks))
throw new SecurityException(KLRes.CryptoStreamFailed);
}
lStreams.Add(sPlain);
if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
{
sXml = new GZipStream(sPlain, CompressionMode.Decompress);
lStreams.Add(sXml);
}
else sXml = sPlain;
if(m_uFileVersion >= FileVersion32_4)
LoadInnerHeader(sXml); // Binary header before XML
}
else if(fmt == KdbxFormat.PlainXml)
sXml = sHashing;
else { Debug.Assert(false); throw new ArgumentOutOfRangeException("fmt"); }
if(fmt == KdbxFormat.Default)
{
if(m_pbInnerRandomStreamKey == null)
{
Debug.Assert(false);
throw new SecurityException("Invalid inner random stream key!");
}
m_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
m_pbInnerRandomStreamKey);
}
#if KeePassDebug_WriteXml
// FileStream fsOut = new FileStream("Raw.xml", FileMode.Create,
// FileAccess.Write, FileShare.None);
// try
// {
// while(true)
// {
// int b = sXml.ReadByte();
// if(b == -1) break;
// fsOut.WriteByte((byte)b);
// }
// }
// catch(Exception) { }
// fsOut.Close();
#endif
ReadXmlStreamed(sXml, sHashing);
// ReadXmlDom(sXml);
}
#if !ModernKeePassLib
catch(CryptographicException) // Thrown on invalid padding
{
throw new CryptographicException(KLRes.FileCorrupted);
}
#endif
finally
{
if(pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey);
if(pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64);
CommonCleanUpRead(lStreams, sHashing);
}
#if KDBX_BENCHMARK
swTime.Stop();
MessageService.ShowInfo("Loading KDBX took " +
swTime.ElapsedMilliseconds.ToString() + " ms.");
#endif
}
private void CommonCleanUpRead(List<Stream> lStreams, HashingStreamEx sHashing)
{
CloseStreams(lStreams);
Debug.Assert(lStreams.Contains(sHashing)); // sHashing must be closed
m_pbHashOfFileOnDisk = sHashing.Hash;
Debug.Assert(m_pbHashOfFileOnDisk != null);
CleanUpInnerRandomStream();
// Reset memory protection settings (to always use reasonable
// defaults)
m_pwDatabase.MemoryProtection = new MemoryProtectionConfig();
// Remove old backups (this call is required here in order to apply
// the default history maintenance settings for people upgrading from
// KeePass <= 2.14 to >= 2.15; also it ensures history integrity in
// case a different application has created the KDBX file and ignored
// the history maintenance settings)
m_pwDatabase.MaintainBackups(); // Don't mark database as modified
// Expand the root group, such that in case the user accidently
// collapses the root group he can simply reopen the database
PwGroup pgRoot = m_pwDatabase.RootGroup;
if(pgRoot != null) pgRoot.IsExpanded = true;
else { Debug.Assert(false); }
m_pbHashOfHeader = null;
}
private byte[] LoadHeader(BinaryReaderEx br)
{
string strPrevExcpText = br.ReadExceptionText;
br.ReadExceptionText = KLRes.FileHeaderCorrupted + " " +
KLRes.FileIncompleteExpc;
MemoryStream msHeader = new MemoryStream();
Debug.Assert(br.CopyDataTo == null);
br.CopyDataTo = msHeader;
byte[] pbSig1 = br.ReadBytes(4);
uint uSig1 = MemUtil.BytesToUInt32(pbSig1);
byte[] pbSig2 = br.ReadBytes(4);
uint uSig2 = MemUtil.BytesToUInt32(pbSig2);
if((uSig1 == FileSignatureOld1) && (uSig2 == FileSignatureOld2))
throw new OldFormatException(PwDefs.ShortProductName + @" 1.x",
OldFormatException.OldFormatType.KeePass1x);
if((uSig1 == FileSignature1) && (uSig2 == FileSignature2)) { }
else if((uSig1 == FileSignaturePreRelease1) && (uSig2 ==
FileSignaturePreRelease2)) { }
else throw new FormatException(KLRes.FileSigInvalid);
byte[] pb = br.ReadBytes(4);
uint uVersion = MemUtil.BytesToUInt32(pb);
if((uVersion & FileVersionCriticalMask) > (FileVersion32 & FileVersionCriticalMask))
throw new FormatException(KLRes.FileVersionUnsupported +
MessageService.NewParagraph + KLRes.FileNewVerReq);
m_uFileVersion = uVersion;
while(true)
{
if(!ReadHeaderField(br)) break;
}
br.CopyDataTo = null;
byte[] pbHeader = msHeader.ToArray();
msHeader.Dispose();
br.ReadExceptionText = strPrevExcpText;
return pbHeader;
}
private bool ReadHeaderField(BinaryReaderEx brSource)
{
Debug.Assert(brSource != null);
if(brSource == null) throw new ArgumentNullException("brSource");
byte btFieldID = brSource.ReadByte();
int cbSize;
Debug.Assert(m_uFileVersion > 0);
if(m_uFileVersion < FileVersion32_4)
cbSize = (int)MemUtil.BytesToUInt16(brSource.ReadBytes(2));
else cbSize = MemUtil.BytesToInt32(brSource.ReadBytes(4));
if(cbSize < 0) throw new FormatException(KLRes.FileCorrupted);
byte[] pbData = MemUtil.EmptyByteArray;
if(cbSize > 0) pbData = brSource.ReadBytes(cbSize);
bool bResult = true;
KdbxHeaderFieldID kdbID = (KdbxHeaderFieldID)btFieldID;
switch(kdbID)
{
case KdbxHeaderFieldID.EndOfHeader:
bResult = false; // Returning false indicates end of header
break;
case KdbxHeaderFieldID.CipherID:
SetCipher(pbData);
break;
case KdbxHeaderFieldID.CompressionFlags:
SetCompressionFlags(pbData);
break;
case KdbxHeaderFieldID.MasterSeed:
m_pbMasterSeed = pbData;
CryptoRandom.Instance.AddEntropy(pbData);
break;
// Obsolete; for backward compatibility only
case KdbxHeaderFieldID.TransformSeed:
Debug.Assert(m_uFileVersion < FileVersion32_4);
AesKdf kdfS = new AesKdf();
if(!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfS.Uuid))
m_pwDatabase.KdfParameters = kdfS.GetDefaultParameters();
// m_pbTransformSeed = pbData;
m_pwDatabase.KdfParameters.SetByteArray(AesKdf.ParamSeed, pbData);
CryptoRandom.Instance.AddEntropy(pbData);
break;
// Obsolete; for backward compatibility only
case KdbxHeaderFieldID.TransformRounds:
Debug.Assert(m_uFileVersion < FileVersion32_4);
AesKdf kdfR = new AesKdf();
if(!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfR.Uuid))
m_pwDatabase.KdfParameters = kdfR.GetDefaultParameters();
// m_pwDatabase.KeyEncryptionRounds = MemUtil.BytesToUInt64(pbData);
m_pwDatabase.KdfParameters.SetUInt64(AesKdf.ParamRounds,
MemUtil.BytesToUInt64(pbData));
break;
case KdbxHeaderFieldID.EncryptionIV:
m_pbEncryptionIV = pbData;
break;
case KdbxHeaderFieldID.InnerRandomStreamKey:
Debug.Assert(m_uFileVersion < FileVersion32_4);
Debug.Assert(m_pbInnerRandomStreamKey == null);
m_pbInnerRandomStreamKey = pbData;
CryptoRandom.Instance.AddEntropy(pbData);
break;
case KdbxHeaderFieldID.StreamStartBytes:
Debug.Assert(m_uFileVersion < FileVersion32_4);
m_pbStreamStartBytes = pbData;
break;
case KdbxHeaderFieldID.InnerRandomStreamID:
Debug.Assert(m_uFileVersion < FileVersion32_4);
SetInnerRandomStreamID(pbData);
break;
case KdbxHeaderFieldID.KdfParameters:
m_pwDatabase.KdfParameters = KdfParameters.DeserializeExt(pbData);
break;
case KdbxHeaderFieldID.PublicCustomData:
Debug.Assert(m_pwDatabase.PublicCustomData.Count == 0);
m_pwDatabase.PublicCustomData = VariantDictionary.Deserialize(pbData);
break;
default:
Debug.Assert(false);
if(m_slLogger != null)
m_slLogger.SetText(KLRes.UnknownHeaderId + @": " +
kdbID.ToString() + "!", LogStatusType.Warning);
break;
}
return bResult;
}
private void LoadInnerHeader(Stream s)
{
BinaryReaderEx br = new BinaryReaderEx(s, StrUtil.Utf8,
KLRes.FileCorrupted + " " + KLRes.FileIncompleteExpc);
while(true)
{
if(!ReadInnerHeaderField(br)) break;
}
}
private bool ReadInnerHeaderField(BinaryReaderEx br)
{
Debug.Assert(br != null);
if(br == null) throw new ArgumentNullException("br");
byte btFieldID = br.ReadByte();
int cbSize = MemUtil.BytesToInt32(br.ReadBytes(4));
if(cbSize < 0) throw new FormatException(KLRes.FileCorrupted);
byte[] pbData = MemUtil.EmptyByteArray;
if(cbSize > 0) pbData = br.ReadBytes(cbSize);
bool bResult = true;
KdbxInnerHeaderFieldID kdbID = (KdbxInnerHeaderFieldID)btFieldID;
switch(kdbID)
{
case KdbxInnerHeaderFieldID.EndOfHeader:
bResult = false; // Returning false indicates end of header
break;
case KdbxInnerHeaderFieldID.InnerRandomStreamID:
SetInnerRandomStreamID(pbData);
break;
case KdbxInnerHeaderFieldID.InnerRandomStreamKey:
Debug.Assert(m_pbInnerRandomStreamKey == null);
m_pbInnerRandomStreamKey = pbData;
CryptoRandom.Instance.AddEntropy(pbData);
break;
case KdbxInnerHeaderFieldID.Binary:
if(pbData.Length < 1) throw new FormatException();
KdbxBinaryFlags f = (KdbxBinaryFlags)pbData[0];
bool bProt = ((f & KdbxBinaryFlags.Protected) != KdbxBinaryFlags.None);
ProtectedBinary pb = new ProtectedBinary(bProt, pbData,
1, pbData.Length - 1);
m_pbsBinaries.Add(pb);
if(bProt) MemUtil.ZeroByteArray(pbData);
break;
default:
Debug.Assert(false);
break;
}
return bResult;
}
private void SetCipher(byte[] pbID)
{
if((pbID == null) || (pbID.Length != (int)PwUuid.UuidSize))
throw new FormatException(KLRes.FileUnknownCipher);
m_pwDatabase.DataCipherUuid = new PwUuid(pbID);
}
private void SetCompressionFlags(byte[] pbFlags)
{
int nID = (int)MemUtil.BytesToUInt32(pbFlags);
if((nID < 0) || (nID >= (int)PwCompressionAlgorithm.Count))
throw new FormatException(KLRes.FileUnknownCompression);
m_pwDatabase.Compression = (PwCompressionAlgorithm)nID;
}
private void SetInnerRandomStreamID(byte[] pbID)
{
uint uID = MemUtil.BytesToUInt32(pbID);
if(uID >= (uint)CrsAlgorithm.Count)
throw new FormatException(KLRes.FileUnknownCipher);
m_craInnerRandomStream = (CrsAlgorithm)uID;
}
[Obsolete]
public static List<PwEntry> ReadEntries(Stream msData)
{
return ReadEntries(msData, null, false);
}
[Obsolete]
public static List<PwEntry> ReadEntries(PwDatabase pdContext, Stream msData)
{
return ReadEntries(msData, pdContext, true);
}
/// <summary>
/// Read entries from a stream.
/// </summary>
/// <param name="msData">Input stream to read the entries from.</param>
/// <param name="pdContext">Context database (e.g. for storing icons).</param>
/// <param name="bCopyIcons">If <c>true</c>, custom icons required by
/// the loaded entries are copied to the context database.</param>
/// <returns>Loaded entries.</returns>
public static List<PwEntry> ReadEntries(Stream msData, PwDatabase pdContext,
bool bCopyIcons)
{
List<PwEntry> lEntries = new List<PwEntry>();
if(msData == null) { Debug.Assert(false); return lEntries; }
// pdContext may be null
/* KdbxFile f = new KdbxFile(pwDatabase);
f.m_format = KdbxFormat.PlainXml;
XmlDocument doc = XmlUtilEx.CreateXmlDocument();
doc.Load(msData);
XmlElement el = doc.DocumentElement;
if(el.Name != ElemRoot) throw new FormatException();
List<PwEntry> vEntries = new List<PwEntry>();
foreach(XmlNode xmlChild in el.ChildNodes)
{
if(xmlChild.Name == ElemEntry)
{
PwEntry pe = f.ReadEntry(xmlChild);
pe.Uuid = new PwUuid(true);
foreach(PwEntry peHistory in pe.History)
peHistory.Uuid = pe.Uuid;
vEntries.Add(pe);
}
else { Debug.Assert(false); }
}
return vEntries; */
PwDatabase pd = new PwDatabase();
pd.New(new IOConnectionInfo(), new CompositeKey());
KdbxFile f = new KdbxFile(pd);
f.Load(msData, KdbxFormat.PlainXml, null);
foreach(PwEntry pe in pd.RootGroup.Entries)
{
pe.SetUuid(new PwUuid(true), true);
lEntries.Add(pe);
if(bCopyIcons && (pdContext != null))
{
PwUuid pu = pe.CustomIconUuid;
if(!pu.Equals(PwUuid.Zero))
{
int iSrc = pd.GetCustomIconIndex(pu);
int iDst = pdContext.GetCustomIconIndex(pu);
if(iSrc < 0) { Debug.Assert(false); }
else if(iDst < 0)
{
pdContext.CustomIcons.Add(pd.CustomIcons[iSrc]);
pdContext.Modified = true;
pdContext.UINeedsIconUpdate = true;
}
}
}
}
return lEntries;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,544 @@
/*
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.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Security;
using System.Text;
using System.Xml;
#if !KeePassUAP
using System.Security.Cryptography;
#endif
#if ModernKeePassLib
using Windows.Storage;
#endif
using ModernKeePassLib.Collections;
using ModernKeePassLib.Cryptography;
using ModernKeePassLib.Cryptography.Cipher;
using ModernKeePassLib.Cryptography.KeyDerivation;
using ModernKeePassLib.Delegates;
using ModernKeePassLib.Interfaces;
using ModernKeePassLib.Resources;
using ModernKeePassLib.Security;
using ModernKeePassLib.Utility;
namespace ModernKeePassLib.Serialization
{
/// <summary>
/// The <c>KdbxFile</c> class supports saving the data to various
/// formats.
/// </summary>
public enum KdbxFormat
{
/// <summary>
/// The default, encrypted file format.
/// </summary>
Default = 0,
/// <summary>
/// Use this flag when exporting data to a plain-text XML file.
/// </summary>
PlainXml
}
/// <summary>
/// Serialization to KeePass KDBX files.
/// </summary>
public sealed partial class KdbxFile
{
/// <summary>
/// File identifier, first 32-bit value.
/// </summary>
internal const uint FileSignature1 = 0x9AA2D903;
/// <summary>
/// File identifier, second 32-bit value.
/// </summary>
internal const uint FileSignature2 = 0xB54BFB67;
/// <summary>
/// File version of files saved by the current <c>KdbxFile</c> class.
/// KeePass 2.07 has version 1.01, 2.08 has 1.02, 2.09 has 2.00,
/// 2.10 has 2.02, 2.11 has 2.04, 2.15 has 3.00, 2.20 has 3.01.
/// The first 2 bytes are critical (i.e. loading will fail, if the
/// file version is too high), the last 2 bytes are informational.
/// </summary>
private const uint FileVersion32 = 0x00040000;
internal const uint FileVersion32_4 = 0x00040000; // First of 4.x series
internal const uint FileVersion32_3 = 0x00030001; // Old format 3.1
private const uint FileVersionCriticalMask = 0xFFFF0000;
// KeePass 1.x signature
internal const uint FileSignatureOld1 = 0x9AA2D903;
internal const uint FileSignatureOld2 = 0xB54BFB65;
// KeePass 2.x pre-release (alpha and beta) signature
internal const uint FileSignaturePreRelease1 = 0x9AA2D903;
internal const uint FileSignaturePreRelease2 = 0xB54BFB66;
private const string ElemDocNode = "KeePassFile";
private const string ElemMeta = "Meta";
private const string ElemRoot = "Root";
private const string ElemGroup = "Group";
private const string ElemEntry = "Entry";
private const string ElemGenerator = "Generator";
private const string ElemHeaderHash = "HeaderHash";
private const string ElemSettingsChanged = "SettingsChanged";
private const string ElemDbName = "DatabaseName";
private const string ElemDbNameChanged = "DatabaseNameChanged";
private const string ElemDbDesc = "DatabaseDescription";
private const string ElemDbDescChanged = "DatabaseDescriptionChanged";
private const string ElemDbDefaultUser = "DefaultUserName";
private const string ElemDbDefaultUserChanged = "DefaultUserNameChanged";
private const string ElemDbMntncHistoryDays = "MaintenanceHistoryDays";
private const string ElemDbColor = "Color";
private const string ElemDbKeyChanged = "MasterKeyChanged";
private const string ElemDbKeyChangeRec = "MasterKeyChangeRec";
private const string ElemDbKeyChangeForce = "MasterKeyChangeForce";
private const string ElemDbKeyChangeForceOnce = "MasterKeyChangeForceOnce";
private const string ElemRecycleBinEnabled = "RecycleBinEnabled";
private const string ElemRecycleBinUuid = "RecycleBinUUID";
private const string ElemRecycleBinChanged = "RecycleBinChanged";
private const string ElemEntryTemplatesGroup = "EntryTemplatesGroup";
private const string ElemEntryTemplatesGroupChanged = "EntryTemplatesGroupChanged";
private const string ElemHistoryMaxItems = "HistoryMaxItems";
private const string ElemHistoryMaxSize = "HistoryMaxSize";
private const string ElemLastSelectedGroup = "LastSelectedGroup";
private const string ElemLastTopVisibleGroup = "LastTopVisibleGroup";
private const string ElemMemoryProt = "MemoryProtection";
private const string ElemProtTitle = "ProtectTitle";
private const string ElemProtUserName = "ProtectUserName";
private const string ElemProtPassword = "ProtectPassword";
private const string ElemProtUrl = "ProtectURL";
private const string ElemProtNotes = "ProtectNotes";
// private const string ElemProtAutoHide = "AutoEnableVisualHiding";
private const string ElemCustomIcons = "CustomIcons";
private const string ElemCustomIconItem = "Icon";
private const string ElemCustomIconItemID = "UUID";
private const string ElemCustomIconItemData = "Data";
private const string ElemAutoType = "AutoType";
private const string ElemHistory = "History";
private const string ElemName = "Name";
private const string ElemNotes = "Notes";
private const string ElemUuid = "UUID";
private const string ElemIcon = "IconID";
private const string ElemCustomIconID = "CustomIconUUID";
private const string ElemFgColor = "ForegroundColor";
private const string ElemBgColor = "BackgroundColor";
private const string ElemOverrideUrl = "OverrideURL";
private const string ElemTimes = "Times";
private const string ElemTags = "Tags";
private const string ElemCreationTime = "CreationTime";
private const string ElemLastModTime = "LastModificationTime";
private const string ElemLastAccessTime = "LastAccessTime";
private const string ElemExpiryTime = "ExpiryTime";
private const string ElemExpires = "Expires";
private const string ElemUsageCount = "UsageCount";
private const string ElemLocationChanged = "LocationChanged";
private const string ElemGroupDefaultAutoTypeSeq = "DefaultAutoTypeSequence";
private const string ElemEnableAutoType = "EnableAutoType";
private const string ElemEnableSearching = "EnableSearching";
private const string ElemString = "String";
private const string ElemBinary = "Binary";
private const string ElemKey = "Key";
private const string ElemValue = "Value";
private const string ElemAutoTypeEnabled = "Enabled";
private const string ElemAutoTypeObfuscation = "DataTransferObfuscation";
private const string ElemAutoTypeDefaultSeq = "DefaultSequence";
private const string ElemAutoTypeItem = "Association";
private const string ElemWindow = "Window";
private const string ElemKeystrokeSequence = "KeystrokeSequence";
private const string ElemBinaries = "Binaries";
private const string AttrId = "ID";
private const string AttrRef = "Ref";
private const string AttrProtected = "Protected";
private const string AttrProtectedInMemPlainXml = "ProtectInMemory";
private const string AttrCompressed = "Compressed";
private const string ElemIsExpanded = "IsExpanded";
private const string ElemLastTopVisibleEntry = "LastTopVisibleEntry";
private const string ElemDeletedObjects = "DeletedObjects";
private const string ElemDeletedObject = "DeletedObject";
private const string ElemDeletionTime = "DeletionTime";
private const string ValFalse = "False";
private const string ValTrue = "True";
private const string ElemCustomData = "CustomData";
private const string ElemStringDictExItem = "Item";
private PwDatabase m_pwDatabase; // Not null, see constructor
private bool m_bUsedOnce = false;
private XmlWriter m_xmlWriter = null;
private CryptoRandomStream m_randomStream = null;
private KdbxFormat m_format = KdbxFormat.Default;
private IStatusLogger m_slLogger = null;
private uint m_uFileVersion = 0;
private byte[] m_pbMasterSeed = null;
// private byte[] m_pbTransformSeed = null;
private byte[] m_pbEncryptionIV = null;
private byte[] m_pbStreamStartBytes = null;
// ArcFourVariant only for backward compatibility; KeePass defaults
// to a more secure algorithm when *writing* databases
private CrsAlgorithm m_craInnerRandomStream = CrsAlgorithm.ArcFourVariant;
private byte[] m_pbInnerRandomStreamKey = null;
private ProtectedBinarySet m_pbsBinaries = new ProtectedBinarySet();
private byte[] m_pbHashOfHeader = null;
private byte[] m_pbHashOfFileOnDisk = null;
private readonly DateTime m_dtNow = DateTime.UtcNow; // Cache current time
private const uint NeutralLanguageOffset = 0x100000; // 2^20, see 32-bit Unicode specs
private const uint NeutralLanguageIDSec = 0x7DC5C; // See 32-bit Unicode specs
private const uint NeutralLanguageID = NeutralLanguageOffset + NeutralLanguageIDSec;
private static bool m_bLocalizedNames = false;
private enum KdbxHeaderFieldID : byte
{
EndOfHeader = 0,
Comment = 1,
CipherID = 2,
CompressionFlags = 3,
MasterSeed = 4,
TransformSeed = 5, // KDBX 3.1, for backward compatibility only
TransformRounds = 6, // KDBX 3.1, for backward compatibility only
EncryptionIV = 7,
InnerRandomStreamKey = 8, // KDBX 3.1, for backward compatibility only
StreamStartBytes = 9, // KDBX 3.1, for backward compatibility only
InnerRandomStreamID = 10, // KDBX 3.1, for backward compatibility only
KdfParameters = 11, // KDBX 4, superseding Transform*
PublicCustomData = 12 // KDBX 4
}
// Inner header in KDBX >= 4 files
private enum KdbxInnerHeaderFieldID : byte
{
EndOfHeader = 0,
InnerRandomStreamID = 1, // Supersedes KdbxHeaderFieldID.InnerRandomStreamID
InnerRandomStreamKey = 2, // Supersedes KdbxHeaderFieldID.InnerRandomStreamKey
Binary = 3
}
[Flags]
private enum KdbxBinaryFlags : byte
{
None = 0,
Protected = 1
}
public byte[] HashOfFileOnDisk
{
get { return m_pbHashOfFileOnDisk; }
}
private bool m_bRepairMode = false;
public bool RepairMode
{
get { return m_bRepairMode; }
set { m_bRepairMode = value; }
}
private uint m_uForceVersion = 0;
internal uint ForceVersion
{
get { return m_uForceVersion; }
set { m_uForceVersion = value; }
}
private string m_strDetachBins = null;
/// <summary>
/// Detach binaries when opening a file. If this isn't <c>null</c>,
/// all binaries are saved to the specified path and are removed
/// from the database.
/// </summary>
public string DetachBinaries
{
get { return m_strDetachBins; }
set { m_strDetachBins = value; }
}
/// <summary>
/// Default constructor.
/// </summary>
/// <param name="pwDataStore">The <c>PwDatabase</c> instance that the
/// class will load file data into or use to create a KDBX file.</param>
public KdbxFile(PwDatabase pwDataStore)
{
Debug.Assert(pwDataStore != null);
if(pwDataStore == null) throw new ArgumentNullException("pwDataStore");
m_pwDatabase = pwDataStore;
}
/// <summary>
/// Call this once to determine the current localization settings.
/// </summary>
public static void DetermineLanguageId()
{
// Test if localized names should be used. If localized names are used,
// the m_bLocalizedNames value must be set to true. By default, localized
// names should be used! (Otherwise characters could be corrupted
// because of different code pages).
unchecked
{
uint uTest = 0;
foreach(char ch in PwDatabase.LocalizedAppName)
uTest = uTest * 5 + ch;
m_bLocalizedNames = (uTest != NeutralLanguageID);
}
}
private uint GetMinKdbxVersion()
{
if(m_uForceVersion != 0) return m_uForceVersion;
// See also KeePassKdb2x3.Export (KDBX 3.1 export module)
AesKdf kdfAes = new AesKdf();
if(!kdfAes.Uuid.Equals(m_pwDatabase.KdfParameters.KdfUuid))
return FileVersion32;
if(m_pwDatabase.PublicCustomData.Count > 0)
return FileVersion32;
bool bCustomData = false;
GroupHandler gh = delegate(PwGroup pg)
{
if(pg == null) { Debug.Assert(false); return true; }
if(pg.CustomData.Count > 0) { bCustomData = true; return false; }
return true;
};
EntryHandler eh = delegate(PwEntry pe)
{
if(pe == null) { Debug.Assert(false); return true; }
if(pe.CustomData.Count > 0) { bCustomData = true; return false; }
return true;
};
gh(m_pwDatabase.RootGroup);
m_pwDatabase.RootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh);
if(bCustomData) return FileVersion32;
return FileVersion32_3; // KDBX 3.1 is sufficient
}
private void ComputeKeys(out byte[] pbCipherKey, int cbCipherKey,
out byte[] pbHmacKey64)
{
byte[] pbCmp = new byte[32 + 32 + 1];
try
{
Debug.Assert(m_pbMasterSeed != null);
if(m_pbMasterSeed == null)
throw new ArgumentNullException("m_pbMasterSeed");
Debug.Assert(m_pbMasterSeed.Length == 32);
if(m_pbMasterSeed.Length != 32)
throw new FormatException(KLRes.MasterSeedLengthInvalid);
Array.Copy(m_pbMasterSeed, 0, pbCmp, 0, 32);
Debug.Assert(m_pwDatabase != null);
Debug.Assert(m_pwDatabase.MasterKey != null);
ProtectedBinary pbinUser = m_pwDatabase.MasterKey.GenerateKey32(
m_pwDatabase.KdfParameters);
Debug.Assert(pbinUser != null);
if(pbinUser == null)
throw new SecurityException(KLRes.InvalidCompositeKey);
byte[] pUserKey32 = pbinUser.ReadData();
if((pUserKey32 == null) || (pUserKey32.Length != 32))
throw new SecurityException(KLRes.InvalidCompositeKey);
Array.Copy(pUserKey32, 0, pbCmp, 32, 32);
MemUtil.ZeroByteArray(pUserKey32);
pbCipherKey = CryptoUtil.ResizeKey(pbCmp, 0, 64, cbCipherKey);
pbCmp[64] = 1;
using(SHA512Managed h = new SHA512Managed())
{
pbHmacKey64 = h.ComputeHash(pbCmp);
}
}
finally { MemUtil.ZeroByteArray(pbCmp); }
}
private ICipherEngine GetCipher(out int cbEncKey, out int cbEncIV)
{
PwUuid pu = m_pwDatabase.DataCipherUuid;
ICipherEngine iCipher = CipherPool.GlobalPool.GetCipher(pu);
if(iCipher == null) // CryptographicExceptions are translated to "file corrupted"
throw new Exception(KLRes.FileUnknownCipher +
MessageService.NewParagraph + KLRes.FileNewVerOrPlgReq +
MessageService.NewParagraph + "UUID: " + pu.ToHexString() + ".");
ICipherEngine2 iCipher2 = (iCipher as ICipherEngine2);
if(iCipher2 != null)
{
cbEncKey = iCipher2.KeyLength;
if(cbEncKey < 0) throw new InvalidOperationException("EncKey.Length");
cbEncIV = iCipher2.IVLength;
if(cbEncIV < 0) throw new InvalidOperationException("EncIV.Length");
}
else
{
cbEncKey = 32;
cbEncIV = 16;
}
return iCipher;
}
private Stream EncryptStream(Stream s, ICipherEngine iCipher,
byte[] pbKey, int cbIV, bool bEncrypt)
{
byte[] pbIV = (m_pbEncryptionIV ?? MemUtil.EmptyByteArray);
if(pbIV.Length != cbIV)
{
Debug.Assert(false);
throw new Exception(KLRes.FileCorrupted);
}
if(bEncrypt)
return iCipher.EncryptStream(s, pbKey, pbIV);
return iCipher.DecryptStream(s, pbKey, pbIV);
}
private byte[] ComputeHeaderHmac(byte[] pbHeader, byte[] pbKey)
{
byte[] pbHeaderHmac;
byte[] pbBlockKey = HmacBlockStream.GetHmacKey64(
pbKey, ulong.MaxValue);
using(HMACSHA256 h = new HMACSHA256(pbBlockKey))
{
pbHeaderHmac = h.ComputeHash(pbHeader);
}
MemUtil.ZeroByteArray(pbBlockKey);
return pbHeaderHmac;
}
private void CloseStreams(List<Stream> lStreams)
{
if(lStreams == null) { Debug.Assert(false); return; }
// Typically, closing a stream also closes its base
// stream; however, there may be streams that do not
// do this (e.g. some cipher plugin), thus for safety
// we close all streams manually, from the innermost
// to the outermost
for(int i = lStreams.Count - 1; i >= 0; --i)
{
// Check for duplicates
Debug.Assert((lStreams.IndexOf(lStreams[i]) == i) &&
(lStreams.LastIndexOf(lStreams[i]) == i));
try { lStreams[i].Dispose(); }
catch(Exception) { Debug.Assert(false); }
}
// Do not clear the list
}
private void CleanUpInnerRandomStream()
{
if(m_randomStream != null) m_randomStream.Dispose();
if(m_pbInnerRandomStreamKey != null)
MemUtil.ZeroByteArray(m_pbInnerRandomStreamKey);
}
private static void SaveBinary(string strName, ProtectedBinary pb,
string strSaveDir)
{
if(pb == null) { Debug.Assert(false); return; }
if(string.IsNullOrEmpty(strName)) strName = "File.bin";
string strPath;
int iTry = 1;
do
{
strPath = UrlUtil.EnsureTerminatingSeparator(strSaveDir, false);
string strExt = UrlUtil.GetExtension(strName);
string strDesc = UrlUtil.StripExtension(strName);
strPath += strDesc;
if(iTry > 1)
strPath += " (" + iTry.ToString(NumberFormatInfo.InvariantInfo) +
")";
if(!string.IsNullOrEmpty(strExt)) strPath += "." + strExt;
++iTry;
}
#if ModernKeePassLib
while (StorageFile.GetFileFromPathAsync(strPath).GetResults() != null);
#else
while(File.Exists(strPath));
#endif
#if ModernKeePassLib
byte[] pbData = pb.ReadData();
/*var file = FileSystem.Current.GetFileFromPathAsync(strPath).Result;
using (var stream = file.OpenAsync(FileAccess.ReadAndWrite).Result) {*/
var file = StorageFile.GetFileFromPathAsync(strPath).GetAwaiter().GetResult();
using (var stream = file.OpenAsync(FileAccessMode.ReadWrite).GetAwaiter().GetResult().AsStream())
{
stream.Write (pbData, 0, pbData.Length);
}
MemUtil.ZeroByteArray(pbData);
#elif !KeePassLibSD
byte[] pbData = pb.ReadData();
File.WriteAllBytes(strPath, pbData);
MemUtil.ZeroByteArray(pbData);
#else
FileStream fs = new FileStream(strPath, FileMode.Create,
FileAccess.Write, FileShare.None);
byte[] pbData = pb.ReadData();
try { File.WriteAllBytes(strPath, pbData); }
finally { if(pb.IsProtected) MemUtil.ZeroByteArray(pbData); }
#endif
}
}
}

View File

@@ -0,0 +1,66 @@
/*
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.Collections.Generic;
using System.Text;
using ModernKeePassLib.Resources;
using ModernKeePassLib.Utility;
namespace ModernKeePassLib.Serialization
{
public sealed class OldFormatException : Exception
{
private string m_strFormat = string.Empty;
private OldFormatType m_type = OldFormatType.Unknown;
public enum OldFormatType
{
Unknown = 0,
KeePass1x = 1
}
public override string Message
{
get
{
string str = KLRes.OldFormat + ((m_strFormat.Length > 0) ?
(@" (" + m_strFormat + @")") : string.Empty) + ".";
if(m_type == OldFormatType.KeePass1x)
str += MessageService.NewParagraph + KLRes.KeePass1xHint;
return str;
}
}
public OldFormatException(string strFormatName)
{
if(strFormatName != null) m_strFormat = strFormatName;
}
public OldFormatException(string strFormatName, OldFormatType t)
{
if(strFormatName != null) m_strFormat = strFormatName;
m_type = t;
}
}
}