mirror of
https://github.com/wismna/ModernKeePass.git
synced 2025-10-03 23:50:18 -04:00
WIP Update lib to 2.37
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2014 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Copyright (C) 2003-2017 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
|
||||
@@ -19,8 +19,8 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
using ModernKeePassLib.Utility;
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace ModernKeePassLib.Serialization
|
||||
private Stream m_s;
|
||||
// private Encoding m_enc; // See constructor
|
||||
|
||||
private string m_strReadExcp;
|
||||
private string m_strReadExcp; // May be null
|
||||
public string ReadExceptionText
|
||||
{
|
||||
get { return m_strReadExcp; }
|
||||
@@ -53,8 +53,7 @@ namespace ModernKeePassLib.Serialization
|
||||
public BinaryReaderEx(Stream input, Encoding encoding,
|
||||
string strReadExceptionText)
|
||||
{
|
||||
if(input == null)
|
||||
throw new ArgumentNullException("input");
|
||||
if(input == null) throw new ArgumentNullException("input");
|
||||
|
||||
m_s = input;
|
||||
// m_enc = encoding; // Not used yet
|
||||
@@ -68,20 +67,18 @@ namespace ModernKeePassLib.Serialization
|
||||
byte[] pb = MemUtil.Read(m_s, nCount);
|
||||
if((pb == null) || (pb.Length != nCount))
|
||||
{
|
||||
if(m_strReadExcp != null)
|
||||
throw new IOException(m_strReadExcp);
|
||||
else
|
||||
throw new EndOfStreamException();
|
||||
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);
|
||||
if(m_sCopyTo != null) m_sCopyTo.Write(pb, 0, pb.Length);
|
||||
return pb;
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
if(m_strReadExcp != null)
|
||||
throw new IOException(m_strReadExcp);
|
||||
if(!string.IsNullOrEmpty(m_strReadExcp))
|
||||
throw new IOException(m_strReadExcp);
|
||||
else throw;
|
||||
}
|
||||
}
|
||||
|
@@ -125,34 +125,26 @@ namespace ModernKeePassLib.Serialization
|
||||
|
||||
public static LockFileInfo Load(IOConnectionInfo iocLockFile)
|
||||
{
|
||||
using (var s = IOConnection.OpenRead(iocLockFile))
|
||||
Stream s = null;
|
||||
try
|
||||
{
|
||||
s = IOConnection.OpenRead(iocLockFile);
|
||||
if(s == null) return null;
|
||||
using (var sr = new StreamReader(s, StrUtil.Utf8))
|
||||
{
|
||||
string str = sr.ReadToEnd();
|
||||
if (str == null)
|
||||
{
|
||||
Debug.Assert(false);
|
||||
}
|
||||
StreamReader sr = new StreamReader(s, StrUtil.Utf8);
|
||||
string str = sr.ReadToEnd();
|
||||
sr.Dispose();
|
||||
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);
|
||||
}
|
||||
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 new LockFileInfo(v[1], v[2], v[3], v[4], v[5]);
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -160,27 +152,48 @@ namespace ModernKeePassLib.Serialization
|
||||
// Throws on error
|
||||
public static LockFileInfo Create(IOConnectionInfo iocLockFile)
|
||||
{
|
||||
byte[] pbID = CryptoRandom.Instance.GetRandomBytes(16);
|
||||
string strTime = TimeUtil.SerializeUtc(DateTime.Now);
|
||||
|
||||
var lfi = new LockFileInfo(Convert.ToBase64String(pbID), strTime,
|
||||
string.Empty, string.Empty, string.Empty);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine(LockFileHeader);
|
||||
sb.AppendLine(lfi.ID);
|
||||
sb.AppendLine(strTime);
|
||||
sb.AppendLine(lfi.UserName);
|
||||
sb.AppendLine(lfi.Machine);
|
||||
sb.AppendLine(lfi.Domain);
|
||||
|
||||
using (var s = IOConnection.OpenWrite(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 + Environment.NewLine);
|
||||
sb.Append(lfi.ID + Environment.NewLine);
|
||||
sb.Append(strTime + Environment.NewLine);
|
||||
sb.Append(lfi.UserName + Environment.NewLine);
|
||||
sb.Append(lfi.Machine + Environment.NewLine);
|
||||
sb.Append(lfi.Domain + Environment.NewLine);
|
||||
#endif
|
||||
|
||||
byte[] pbFile = StrUtil.Utf8.GetBytes(sb.ToString());
|
||||
if (s == null) throw new IOException(iocLockFile.GetDisplayName());
|
||||
s.WriteAsync(pbFile, 0, pbFile.Length).GetAwaiter().GetResult();
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -241,8 +254,8 @@ namespace ModernKeePassLib.Serialization
|
||||
#endif
|
||||
}
|
||||
|
||||
if(bDisposing && !bFileDeleted)
|
||||
IOConnection.DeleteFile(m_iocLockFile); // Possibly with exception
|
||||
// if(bDisposing && !bFileDeleted)
|
||||
// IOConnection.DeleteFile(m_iocLockFile); // Possibly with exception
|
||||
|
||||
m_iocLockFile = null;
|
||||
}
|
||||
|
@@ -30,7 +30,9 @@ using System.Security.AccessControl;
|
||||
using ModernKeePassLib.Native;
|
||||
using ModernKeePassLib.Utility;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
using ModernKeePassLib.Resources;
|
||||
|
||||
namespace ModernKeePassLib.Serialization
|
||||
{
|
||||
@@ -44,6 +46,16 @@ namespace ModernKeePassLib.Serialization
|
||||
|
||||
private const string StrTempSuffix = ".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)
|
||||
{
|
||||
Initialize(iocBaseFile, true);
|
||||
@@ -61,16 +73,47 @@ namespace ModernKeePassLib.Serialization
|
||||
m_bTransacted = bTransacted;
|
||||
m_iocBase = iocBaseFile.CloneDeep();
|
||||
|
||||
// ModernKeePassLib is currently targeting .NET 4.5
|
||||
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;
|
||||
}
|
||||
}
|
||||
catch(Exception) { Debug.Assert(false); }
|
||||
}
|
||||
|
||||
// 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(m_iocBase.Path.StartsWith("ftp:", StrUtil.CaseIgnoreCmp) &&
|
||||
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();
|
||||
@@ -109,6 +152,13 @@ namespace ModernKeePassLib.Serialization
|
||||
bool bEfsEncrypted = false;
|
||||
#endif
|
||||
|
||||
if(g_bExtraSafe)
|
||||
{
|
||||
if(!IOConnection.FileExists(m_iocTemp))
|
||||
throw new FileNotFoundException(m_iocTemp.Path +
|
||||
Environment.NewLine + KLRes.FileSaveFailed);
|
||||
}
|
||||
|
||||
if(IOConnection.FileExists(m_iocBase))
|
||||
{
|
||||
#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT)
|
||||
@@ -119,10 +169,10 @@ namespace ModernKeePassLib.Serialization
|
||||
FileAttributes faBase = File.GetAttributes(m_iocBase.Path);
|
||||
bEfsEncrypted = ((long)(faBase & FileAttributes.Encrypted) != 0);
|
||||
|
||||
DateTime tCreation = File.GetCreationTime(m_iocBase.Path);
|
||||
DateTime tCreation = File.GetCreationTimeUtc(m_iocBase.Path);
|
||||
bkSecurity = File.GetAccessControl(m_iocBase.Path);
|
||||
|
||||
File.SetCreationTime(m_iocTemp.Path, tCreation);
|
||||
File.SetCreationTimeUtc(m_iocTemp.Path, tCreation);
|
||||
}
|
||||
catch(Exception) { Debug.Assert(false); }
|
||||
}
|
||||
@@ -153,5 +203,15 @@ namespace ModernKeePassLib.Serialization
|
||||
|
||||
if(bMadeUnhidden) UrlUtil.HideFile(m_iocBase.Path, true); // Hide again
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -31,6 +31,7 @@ using System.Text;
|
||||
using ModernKeePassLib.Native;
|
||||
using ModernKeePassLib.Utility;
|
||||
using Windows.Security.Cryptography.Core;
|
||||
using ModernKeePassLib.Cryptography;
|
||||
|
||||
#if KeePassLibSD
|
||||
using KeePassLibSD;
|
||||
@@ -40,7 +41,7 @@ namespace ModernKeePassLib.Serialization
|
||||
{
|
||||
public sealed class HashedBlockStream : Stream
|
||||
{
|
||||
private const int m_nDefaultBufferSize = 1024 * 1024; // 1 MB
|
||||
private const int NbDefaultBufferSize = 1024 * 1024; // 1 MB
|
||||
|
||||
private Stream m_sBaseStream;
|
||||
private bool m_bWriting;
|
||||
@@ -53,7 +54,7 @@ namespace ModernKeePassLib.Serialization
|
||||
private byte[] m_pbBuffer;
|
||||
private int m_nBufferPos = 0;
|
||||
|
||||
private uint m_uBufferIndex = 0;
|
||||
private uint m_uBlockIndex = 0;
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
@@ -72,13 +73,13 @@ namespace ModernKeePassLib.Serialization
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get { throw new NotSupportedException(); }
|
||||
get { Debug.Assert(false); throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get { throw new NotSupportedException(); }
|
||||
set { throw new NotSupportedException(); }
|
||||
get { Debug.Assert(false); throw new NotSupportedException(); }
|
||||
set { Debug.Assert(false); throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public HashedBlockStream(Stream sBaseStream, bool bWriting)
|
||||
@@ -100,29 +101,28 @@ namespace ModernKeePassLib.Serialization
|
||||
private void Initialize(Stream sBaseStream, bool bWriting, int nBufferSize,
|
||||
bool bVerify)
|
||||
{
|
||||
if (sBaseStream != null) m_sBaseStream = sBaseStream;
|
||||
else throw new ArgumentNullException(nameof(sBaseStream));
|
||||
if (nBufferSize < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(nBufferSize));
|
||||
if(sBaseStream == null) throw new ArgumentNullException("sBaseStream");
|
||||
if(nBufferSize < 0) throw new ArgumentOutOfRangeException("nBufferSize");
|
||||
|
||||
if(nBufferSize == 0)
|
||||
nBufferSize = m_nDefaultBufferSize;
|
||||
if(nBufferSize == 0) nBufferSize = NbDefaultBufferSize;
|
||||
|
||||
m_sBaseStream = sBaseStream;
|
||||
m_bWriting = bWriting;
|
||||
m_bVerify = bVerify;
|
||||
|
||||
UTF8Encoding utf8 = StrUtil.Utf8;
|
||||
if(m_bWriting == false) // Reading mode
|
||||
if(!m_bWriting) // Reading mode
|
||||
{
|
||||
if(m_sBaseStream.CanRead == false)
|
||||
if(!m_sBaseStream.CanRead)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
m_brInput = new BinaryReader(sBaseStream, utf8);
|
||||
|
||||
m_pbBuffer = new byte[0];
|
||||
m_pbBuffer = MemUtil.EmptyByteArray;
|
||||
}
|
||||
else // Writing mode
|
||||
{
|
||||
if(m_sBaseStream.CanWrite == false)
|
||||
if(!m_sBaseStream.CanWrite)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
m_bwOutput = new BinaryWriter(sBaseStream, utf8);
|
||||
@@ -131,25 +131,13 @@ namespace ModernKeePassLib.Serialization
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
if(m_bWriting) m_bwOutput.Flush();
|
||||
}
|
||||
|
||||
#if ModernKeePassLib || KeePassRT
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if(!disposing) return;
|
||||
#else
|
||||
public override void Close()
|
||||
{
|
||||
#endif
|
||||
if(m_sBaseStream != null)
|
||||
if(disposing && (m_sBaseStream != null))
|
||||
{
|
||||
if(m_bWriting == false) // Reading mode
|
||||
if(!m_bWriting) // Reading mode
|
||||
{
|
||||
try { m_brInput.Dispose(); } catch { }
|
||||
|
||||
m_brInput.Dispose();
|
||||
m_brInput = null;
|
||||
}
|
||||
else // Writing mode
|
||||
@@ -167,9 +155,16 @@ namespace ModernKeePassLib.Serialization
|
||||
m_bwOutput = null;
|
||||
}
|
||||
|
||||
try { m_sBaseStream.Dispose(); } catch { }
|
||||
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)
|
||||
@@ -192,7 +187,7 @@ namespace ModernKeePassLib.Serialization
|
||||
if(m_nBufferPos == m_pbBuffer.Length)
|
||||
{
|
||||
if(ReadHashedBlock() == false)
|
||||
return nCount - nRemaining; // Bytes actually read
|
||||
return (nCount - nRemaining); // Bytes actually read
|
||||
}
|
||||
|
||||
int nCopy = Math.Min(m_pbBuffer.Length - m_nBufferPos, nRemaining);
|
||||
@@ -214,9 +209,9 @@ namespace ModernKeePassLib.Serialization
|
||||
|
||||
m_nBufferPos = 0;
|
||||
|
||||
if(m_brInput.ReadUInt32() != m_uBufferIndex)
|
||||
if(m_brInput.ReadUInt32() != m_uBlockIndex)
|
||||
throw new InvalidDataException();
|
||||
++m_uBufferIndex;
|
||||
++m_uBlockIndex;
|
||||
|
||||
byte[] pbStoredHash = m_brInput.ReadBytes(32);
|
||||
if((pbStoredHash == null) || (pbStoredHash.Length != 32))
|
||||
@@ -241,7 +236,7 @@ namespace ModernKeePassLib.Serialization
|
||||
}
|
||||
|
||||
m_bEos = true;
|
||||
m_pbBuffer = new byte[0];
|
||||
m_pbBuffer = MemUtil.EmptyByteArray;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -251,25 +246,12 @@ namespace ModernKeePassLib.Serialization
|
||||
|
||||
if(m_bVerify)
|
||||
{
|
||||
#if ModernKeePassLib
|
||||
/*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256);
|
||||
var pbComputedHash = sha256.HashData(m_pbBuffer);*/
|
||||
var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
|
||||
var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(m_pbBuffer));
|
||||
byte[] pbComputedHash;
|
||||
CryptographicBuffer.CopyToByteArray(buffer, out pbComputedHash);
|
||||
#else
|
||||
SHA256Managed sha256 = new SHA256Managed();
|
||||
byte[] pbComputedHash = sha256.ComputeHash(m_pbBuffer);
|
||||
#endif
|
||||
if ((pbComputedHash == null) || (pbComputedHash.Length != 32))
|
||||
byte[] pbComputedHash = CryptoUtil.HashSha256(m_pbBuffer);
|
||||
if((pbComputedHash == null) || (pbComputedHash.Length != 32))
|
||||
throw new InvalidOperationException();
|
||||
|
||||
for(int iHashPos = 0; iHashPos < 32; ++iHashPos)
|
||||
{
|
||||
if(pbStoredHash[iHashPos] != pbComputedHash[iHashPos])
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
if(!MemUtil.ArraysEqual(pbStoredHash, pbComputedHash))
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -297,39 +279,26 @@ namespace ModernKeePassLib.Serialization
|
||||
|
||||
private void WriteHashedBlock()
|
||||
{
|
||||
m_bwOutput.Write(m_uBufferIndex);
|
||||
++m_uBufferIndex;
|
||||
m_bwOutput.Write(m_uBlockIndex);
|
||||
++m_uBlockIndex;
|
||||
|
||||
if(m_nBufferPos > 0)
|
||||
{
|
||||
#if ModernKeePassLib
|
||||
/*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256);
|
||||
var pbHash = sha256.HashData(m_pbBuffer.Where((x, i) => i < m_nBufferPos).ToArray());*/
|
||||
var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
|
||||
var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(m_pbBuffer.Where((x, i) => i < m_nBufferPos).ToArray()));
|
||||
byte[] pbHash;
|
||||
CryptographicBuffer.CopyToByteArray(buffer, out pbHash);
|
||||
#else
|
||||
byte[] pbHash = CryptoUtil.HashSha256(m_pbBuffer, 0, m_nBufferPos);
|
||||
|
||||
SHA256Managed sha256 = new SHA256Managed();
|
||||
// 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);
|
||||
// }
|
||||
|
||||
#if !KeePassLibSD
|
||||
byte[] pbHash = sha256.ComputeHash(m_pbBuffer, 0, m_nBufferPos);
|
||||
#else
|
||||
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);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
m_bwOutput.Write(pbHash);
|
||||
m_bwOutput.Write(pbHash);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
356
ModernKeePassLib/Serialization/HmacBlockStream.cs
Normal file
356
ModernKeePassLib/Serialization/HmacBlockStream.cs
Normal file
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2017 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 Windows.Security.Cryptography;
|
||||
using Windows.Security.Cryptography.Core;
|
||||
using ModernKeePassLib.Resources;
|
||||
using ModernKeePassLib.Utility;
|
||||
using Org.BouncyCastle.Crypto.Digests;
|
||||
using Org.BouncyCastle.Crypto.Macs;
|
||||
|
||||
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.Close();
|
||||
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 = MemUtil.EmptyByteArray;
|
||||
byte[] pbIndex = MemUtil.UInt64ToBytes(uBlockIndex);
|
||||
var h = new Sha512Digest();
|
||||
h.BlockUpdate(pbIndex, 0, pbIndex.Length);
|
||||
h.BlockUpdate(pbKey, 0, pbKey.Length);
|
||||
h.DoFinal(pbBlockKey, 0);
|
||||
h.Reset();
|
||||
|
||||
/*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 = MemUtil.EmptyByteArray;
|
||||
byte[] pbBlockKey = GetHmacKey64(m_pbKey, m_uBlockIndex);
|
||||
|
||||
#if ModernKeePassLib
|
||||
var h = new HMac(new Sha256Digest());
|
||||
h.BlockUpdate(pbBlockIndex, 0, pbBlockIndex.Length);
|
||||
h.BlockUpdate(pbBlockSize, 0, pbBlockSize.Length);
|
||||
if (m_pbBuffer.Length > 0)
|
||||
h.BlockUpdate(m_pbBuffer, 0, m_pbBuffer.Length);
|
||||
|
||||
h.DoFinal(pbCmpHmac, 0);
|
||||
h.Reset();
|
||||
#else
|
||||
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;
|
||||
}
|
||||
#endif
|
||||
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 = MemUtil.EmptyByteArray;
|
||||
byte[] pbBlockKey = GetHmacKey64(m_pbKey, m_uBlockIndex);
|
||||
|
||||
#if ModernKeePassLib
|
||||
var h = new HMac(new Sha256Digest());
|
||||
h.BlockUpdate(pbBlockIndex, 0, pbBlockIndex.Length);
|
||||
h.BlockUpdate(pbBlockSize, 0, pbBlockSize.Length);
|
||||
if (m_pbBuffer.Length > 0)
|
||||
h.BlockUpdate(m_pbBuffer, 0, m_pbBuffer.Length);
|
||||
|
||||
h.DoFinal(pbBlockHmac, 0);
|
||||
h.Reset();
|
||||
#else
|
||||
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;
|
||||
}
|
||||
#endif
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -24,6 +24,7 @@ using System.Net;
|
||||
using System.Diagnostics;
|
||||
using Windows.Storage.Streams;
|
||||
using System.Threading.Tasks;
|
||||
using ModernKeePassLib.Native;
|
||||
#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT)
|
||||
using System.Net.Cache;
|
||||
using System.Net.Security;
|
||||
@@ -35,7 +36,6 @@ using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
#if ModernKeePassLib
|
||||
using Windows.Storage;
|
||||
//using PCLStorage;
|
||||
#endif
|
||||
using ModernKeePassLib.Utility;
|
||||
|
||||
@@ -112,6 +112,7 @@ namespace ModernKeePassLib.Serialization
|
||||
m_s = sBase;
|
||||
}
|
||||
|
||||
#if !KeePassUAP
|
||||
public override IAsyncResult BeginRead(byte[] buffer, int offset,
|
||||
int count, AsyncCallback callback, object state)
|
||||
{
|
||||
@@ -123,12 +124,16 @@ namespace ModernKeePassLib.Serialization
|
||||
{
|
||||
return BeginWrite(buffer, offset, count, callback, state);
|
||||
}
|
||||
#endif
|
||||
|
||||
public override void Close()
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
m_s.Close();
|
||||
if(disposing) m_s.Dispose();
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#if !KeePassUAP
|
||||
public override int EndRead(IAsyncResult asyncResult)
|
||||
{
|
||||
return m_s.EndRead(asyncResult);
|
||||
@@ -138,6 +143,7 @@ namespace ModernKeePassLib.Serialization
|
||||
{
|
||||
m_s.EndWrite(asyncResult);
|
||||
}
|
||||
#endif
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
@@ -178,17 +184,19 @@ namespace ModernKeePassLib.Serialization
|
||||
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;
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Close();
|
||||
base.Dispose(disposing);
|
||||
|
||||
if(MonoWorkarounds.IsRequired(10163) && m_bWrite)
|
||||
if(disposing && MonoWorkarounds.IsRequired(10163) && m_bWrite &&
|
||||
!m_bDisposed)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -210,6 +218,8 @@ namespace ModernKeePassLib.Serialization
|
||||
}
|
||||
catch(Exception) { Debug.Assert(false); }
|
||||
}
|
||||
|
||||
m_bDisposed = true;
|
||||
}
|
||||
|
||||
public static Stream WrapIfRequired(Stream s)
|
||||
@@ -230,15 +240,20 @@ namespace ModernKeePassLib.Serialization
|
||||
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
|
||||
@@ -260,31 +275,67 @@ namespace ModernKeePassLib.Serialization
|
||||
}
|
||||
|
||||
internal static void SetProxy(ProxyServerType pst, string strAddr,
|
||||
string strPort, string strUserName, string strPassword)
|
||||
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)
|
||||
internal static void ConfigureWebRequest(WebRequest request,
|
||||
IOConnectionInfo ioc)
|
||||
{
|
||||
if(request == null) { Debug.Assert(false); return; } // No throw
|
||||
|
||||
// WebDAV support
|
||||
if(request is HttpWebRequest)
|
||||
{
|
||||
request.PreAuthenticate = true; // Also auth GET
|
||||
if(request.Method == WebRequestMethods.Http.Post)
|
||||
request.Method = WebRequestMethods.Http.Put;
|
||||
}
|
||||
// else if(request is FtpWebRequest)
|
||||
// {
|
||||
// Debug.Assert(((FtpWebRequest)request).UsePassive);
|
||||
// }
|
||||
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
|
||||
{
|
||||
@@ -292,6 +343,7 @@ namespace ModernKeePassLib.Serialization
|
||||
}
|
||||
catch(NotImplementedException) { }
|
||||
catch(Exception) { Debug.Assert(false); }
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
@@ -299,10 +351,20 @@ namespace ModernKeePassLib.Serialization
|
||||
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
|
||||
{
|
||||
@@ -310,6 +372,7 @@ namespace ModernKeePassLib.Serialization
|
||||
}
|
||||
catch(NotImplementedException) { }
|
||||
catch(Exception) { Debug.Assert(false); }
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
@@ -320,67 +383,163 @@ namespace ModernKeePassLib.Serialization
|
||||
}
|
||||
|
||||
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_strProxyPort.Length > 0)
|
||||
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);
|
||||
|
||||
if((m_strProxyUserName.Length > 0) || (m_strProxyPassword.Length > 0))
|
||||
prx.Credentials = new NetworkCredential(m_strProxyUserName,
|
||||
m_strProxyPassword);
|
||||
|
||||
return true; // Use manual proxy
|
||||
return (prx != null);
|
||||
}
|
||||
catch(Exception exProxy)
|
||||
#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, exProxy.Message);
|
||||
if(m_strProxyPort.Length > 0)
|
||||
strInfo += ":" + m_strProxyPort;
|
||||
MessageService.ShowWarning(strInfo, ex.Message);
|
||||
}
|
||||
#endif
|
||||
|
||||
return false; // Use default
|
||||
}
|
||||
|
||||
if((m_strProxyUserName.Length == 0) && (m_strProxyPassword.Length == 0))
|
||||
return false; // Use default proxy, no auth
|
||||
|
||||
Debug.Assert(m_pstProxyType == ProxyServerType.System);
|
||||
try
|
||||
{
|
||||
prx = WebRequest.DefaultWebProxy;
|
||||
if(prx == null) prx = WebRequest.GetSystemWebProxy();
|
||||
if(prx == null) throw new InvalidOperationException();
|
||||
// First try system, then default (from config)
|
||||
#if !KeePassUAP
|
||||
prx = WebRequest.GetSystemWebProxy();
|
||||
#endif
|
||||
if(prx == null) prx = WebRequest.DefaultWebProxy;
|
||||
|
||||
prx.Credentials = new NetworkCredential(m_strProxyUserName,
|
||||
m_strProxyPassword);
|
||||
return true;
|
||||
return (prx != null);
|
||||
}
|
||||
catch(Exception) { Debug.Assert(false); }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void PrepareWebAccess()
|
||||
private static void AssignCredentials(IWebProxy prx)
|
||||
{
|
||||
if(m_bSslCertsAcceptInvalid)
|
||||
ServicePointManager.ServerCertificateValidationCallback =
|
||||
IOConnection.AcceptCertificate;
|
||||
else
|
||||
ServicePointManager.ServerCertificateValidationCallback = null;
|
||||
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();
|
||||
PrepareWebAccess(ioc);
|
||||
|
||||
IOWebClient wc = new IOWebClient();
|
||||
IOWebClient wc = new IOWebClient(ioc);
|
||||
ConfigureWebClient(wc);
|
||||
|
||||
if((ioc.UserName.Length > 0) || (ioc.Password.Length > 0))
|
||||
@@ -393,10 +552,10 @@ namespace ModernKeePassLib.Serialization
|
||||
|
||||
private static WebRequest CreateWebRequest(IOConnectionInfo ioc)
|
||||
{
|
||||
PrepareWebAccess();
|
||||
PrepareWebAccess(ioc);
|
||||
|
||||
WebRequest req = WebRequest.Create(ioc.Path);
|
||||
ConfigureWebRequest(req);
|
||||
ConfigureWebRequest(req, ioc);
|
||||
|
||||
if((ioc.UserName.Length > 0) || (ioc.Password.Length > 0))
|
||||
req.Credentials = new NetworkCredential(ioc.UserName, ioc.Password);
|
||||
@@ -422,7 +581,7 @@ namespace ModernKeePassLib.Serialization
|
||||
new Uri(ioc.Path)));
|
||||
}
|
||||
#else
|
||||
public static Stream OpenRead(IOConnectionInfo ioc)
|
||||
public static Stream OpenRead(IOConnectionInfo ioc)
|
||||
{
|
||||
RaiseIOAccessPreEvent(ioc, IOAccessType.Read);
|
||||
|
||||
@@ -449,9 +608,7 @@ namespace ModernKeePassLib.Serialization
|
||||
|
||||
// Mono does not set HttpWebRequest.Method to POST for writes,
|
||||
// so one needs to set the method to PUT explicitly
|
||||
if(NativeLib.IsUnix() && (uri.Scheme.Equals(Uri.UriSchemeHttp,
|
||||
StrUtil.CaseIgnoreCmp) || uri.Scheme.Equals(Uri.UriSchemeHttps,
|
||||
StrUtil.CaseIgnoreCmp)))
|
||||
if(NativeLib.IsUnix() && IsHttpWebRequest(uri))
|
||||
s = CreateWebClient(ioc).OpenWrite(uri, WebRequestMethods.Http.Put);
|
||||
else s = CreateWebClient(ioc).OpenWrite(uri);
|
||||
|
||||
@@ -478,8 +635,7 @@ namespace ModernKeePassLib.Serialization
|
||||
|
||||
public static bool FileExists(IOConnectionInfo ioc, bool bThrowErrors)
|
||||
{
|
||||
if(ioc == null) { Debug.Assert(false);
|
||||
}
|
||||
if(ioc == null) { Debug.Assert(false); return false; }
|
||||
|
||||
RaiseIOAccessPreEvent(ioc, IOAccessType.Exists);
|
||||
|
||||
@@ -526,7 +682,7 @@ namespace ModernKeePassLib.Serialization
|
||||
}
|
||||
#endif
|
||||
#if !ModernKeePassLib
|
||||
internal static void DisposeResponse(WebResponse wr, bool bGetStream)
|
||||
internal static void DisposeResponse(WebResponse wr, bool bGetStream)
|
||||
{
|
||||
if(wr == null) return;
|
||||
|
||||
@@ -535,26 +691,25 @@ namespace ModernKeePassLib.Serialization
|
||||
if(bGetStream)
|
||||
{
|
||||
Stream s = wr.GetResponseStream();
|
||||
if(s != null) s.Dispose();
|
||||
if(s != null) s.Close();
|
||||
}
|
||||
}
|
||||
catch(Exception) { Debug.Assert(false); }
|
||||
|
||||
try { wr.Dispose(); }
|
||||
try { wr.Close(); }
|
||||
catch(Exception) { Debug.Assert(false); }
|
||||
}
|
||||
#endif
|
||||
public static byte[] ReadFile(IOConnectionInfo ioc)
|
||||
{
|
||||
Stream sIn = null;
|
||||
Stream sIn = null;
|
||||
MemoryStream ms = null;
|
||||
try
|
||||
{
|
||||
sIn = OpenRead(ioc);
|
||||
sIn = IOConnection.OpenRead(ioc);
|
||||
if(sIn == null) return null;
|
||||
|
||||
ms = new MemoryStream();
|
||||
|
||||
MemUtil.CopyStream(sIn, ms);
|
||||
|
||||
return ms.ToArray();
|
||||
@@ -587,5 +742,49 @@ namespace ModernKeePassLib.Serialization
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@@ -139,9 +139,40 @@ namespace ModernKeePassLib.Serialization
|
||||
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()
|
||||
{
|
||||
return (IOConnectionInfo)this.MemberwiseClone();
|
||||
IOConnectionInfo ioc = (IOConnectionInfo)this.MemberwiseClone();
|
||||
ioc.m_props = m_props.CloneDeep();
|
||||
return ioc;
|
||||
}
|
||||
|
||||
#if DEBUG // For debugger display only
|
||||
@@ -274,14 +305,14 @@ namespace ModernKeePassLib.Serialization
|
||||
string str = m_strUrl;
|
||||
|
||||
if(m_strUser.Length > 0)
|
||||
str += " (" + m_strUser + ")";
|
||||
str += (" (" + m_strUser + ")");
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
public bool IsEmpty()
|
||||
{
|
||||
return (m_strUrl.Length > 0);
|
||||
return (m_strUrl.Length == 0);
|
||||
}
|
||||
|
||||
public static IOConnectionInfo FromPath(string strPath)
|
||||
@@ -320,13 +351,13 @@ namespace ModernKeePassLib.Serialization
|
||||
if(IsLocalFile()) return File.Exists(m_strUrl);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsLocalFile()
|
||||
{
|
||||
// Not just ":/", see e.g. AppConfigEx.ChangePathRelAbs
|
||||
return (m_strUrl.IndexOf(@"://") < 0);
|
||||
return (m_strUrl.IndexOf("://") < 0);
|
||||
}
|
||||
|
||||
public void ClearCredentials(bool bDependingOnRememberMode)
|
||||
|
192
ModernKeePassLib/Serialization/IocProperties.cs
Normal file
192
ModernKeePassLib/Serialization/IocProperties.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2017 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
99
ModernKeePassLib/Serialization/IocPropertyInfo.cs
Normal file
99
ModernKeePassLib/Serialization/IocPropertyInfo.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2017 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
123
ModernKeePassLib/Serialization/IocPropertyInfoPool.cs
Normal file
123
ModernKeePassLib/Serialization/IocPropertyInfoPool.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2017 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 const string Http = "HTTP";
|
||||
public const string Https = "HTTPS";
|
||||
public const string WebDav = "WebDAV";
|
||||
public const string Ftp = "FTP";
|
||||
}
|
||||
|
||||
public static class IocKnownProperties
|
||||
{
|
||||
public const string Timeout = "Timeout";
|
||||
public const string PreAuth = "PreAuth";
|
||||
|
||||
public const string UserAgent = "UserAgent";
|
||||
public const string Expect100Continue = "Expect100Continue";
|
||||
|
||||
public const 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2014 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Copyright (C) 2003-2017 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
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Security;
|
||||
using System.Drawing;
|
||||
@@ -58,13 +60,17 @@ namespace ModernKeePassLib.Serialization
|
||||
DeletedObject,
|
||||
Group,
|
||||
GroupTimes,
|
||||
GroupCustomData,
|
||||
GroupCustomDataItem,
|
||||
Entry,
|
||||
EntryTimes,
|
||||
EntryString,
|
||||
EntryBinary,
|
||||
EntryAutoType,
|
||||
EntryAutoTypeItem,
|
||||
EntryHistory
|
||||
EntryHistory,
|
||||
EntryCustomData,
|
||||
EntryCustomDataItem
|
||||
}
|
||||
|
||||
private bool m_bReadNextNode = true;
|
||||
@@ -84,10 +90,14 @@ namespace ModernKeePassLib.Serialization
|
||||
private byte[] m_pbCustomIconData = null;
|
||||
private string m_strCustomDataKey = null;
|
||||
private string m_strCustomDataValue = null;
|
||||
private string m_strGroupCustomDataKey = null;
|
||||
private string m_strGroupCustomDataValue = null;
|
||||
private string m_strEntryCustomDataKey = null;
|
||||
private string m_strEntryCustomDataValue = null;
|
||||
|
||||
private void ReadXmlStreamed(Stream readerStream, Stream sParentStream)
|
||||
private void ReadXmlStreamed(Stream sXml, Stream sParent)
|
||||
{
|
||||
ReadDocumentStreamed(CreateXmlReader(readerStream), sParentStream);
|
||||
ReadDocumentStreamed(CreateXmlReader(sXml), sParent);
|
||||
}
|
||||
|
||||
internal static XmlReaderSettings CreateStdXmlReaderSettings()
|
||||
@@ -125,7 +135,6 @@ namespace ModernKeePassLib.Serialization
|
||||
if(xr == null) throw new ArgumentNullException("xr");
|
||||
|
||||
m_ctxGroups.Clear();
|
||||
m_dictBinPool = new Dictionary<string, ProtectedBinary>();
|
||||
|
||||
KdbContext ctx = KdbContext.Null;
|
||||
|
||||
@@ -216,15 +225,25 @@ namespace ModernKeePassLib.Serialization
|
||||
ReadString(xr); // Ignore
|
||||
else if(xr.Name == ElemHeaderHash)
|
||||
{
|
||||
// The header hash is typically only stored in
|
||||
// KDBX <= 3.1 files, not in KDBX >= 4 files
|
||||
// (here, the header is verified via a HMAC),
|
||||
// but we also support it for KDBX >= 4 files
|
||||
// (i.e. if it's present, we check it)
|
||||
|
||||
string strHash = ReadString(xr);
|
||||
if(!string.IsNullOrEmpty(strHash) && (m_pbHashOfHeader != null) &&
|
||||
!m_bRepairMode)
|
||||
{
|
||||
Debug.Assert(m_uFileVersion < FileVersion32_4);
|
||||
|
||||
byte[] pbHash = Convert.FromBase64String(strHash);
|
||||
if(!MemUtil.ArraysEqual(pbHash, m_pbHashOfHeader))
|
||||
throw new IOException(KLRes.FileCorrupted);
|
||||
throw new InvalidDataException(KLRes.FileCorrupted);
|
||||
}
|
||||
}
|
||||
else if(xr.Name == ElemSettingsChanged)
|
||||
m_pwDatabase.SettingsChanged = ReadTime(xr);
|
||||
else if(xr.Name == ElemDbName)
|
||||
m_pwDatabase.Name = ReadString(xr);
|
||||
else if(xr.Name == ElemDbNameChanged)
|
||||
@@ -251,6 +270,8 @@ namespace ModernKeePassLib.Serialization
|
||||
m_pwDatabase.MasterKeyChangeRec = ReadLong(xr, -1);
|
||||
else if(xr.Name == ElemDbKeyChangeForce)
|
||||
m_pwDatabase.MasterKeyChangeForce = ReadLong(xr, -1);
|
||||
else if(xr.Name == ElemDbKeyChangeForceOnce)
|
||||
m_pwDatabase.MasterKeyChangeForceOnce = ReadBool(xr, false);
|
||||
else if(xr.Name == ElemMemoryProt)
|
||||
return SwitchContext(ctx, KdbContext.MemoryProtection, xr);
|
||||
else if(xr.Name == ElemCustomIcons)
|
||||
@@ -323,7 +344,14 @@ namespace ModernKeePassLib.Serialization
|
||||
string strKey = xr.Value;
|
||||
ProtectedBinary pbData = ReadProtectedBinary(xr);
|
||||
|
||||
m_dictBinPool[strKey ?? string.Empty] = pbData;
|
||||
int iKey;
|
||||
if(!StrUtil.TryParseIntInvariant(strKey, out iKey))
|
||||
throw new FormatException();
|
||||
if(iKey < 0) throw new FormatException();
|
||||
|
||||
Debug.Assert(m_pbsBinaries.Get(iKey) == null);
|
||||
Debug.Assert(m_pbsBinaries.Find(pbData) < 0);
|
||||
m_pbsBinaries.Set(iKey, pbData);
|
||||
}
|
||||
else ReadUnknown(xr);
|
||||
}
|
||||
@@ -369,7 +397,7 @@ namespace ModernKeePassLib.Serialization
|
||||
else if(xr.Name == ElemNotes)
|
||||
m_ctxGroup.Notes = ReadString(xr);
|
||||
else if(xr.Name == ElemIcon)
|
||||
m_ctxGroup.IconId = (PwIcon)ReadInt(xr, (int)PwIcon.Folder);
|
||||
m_ctxGroup.IconId = ReadIconId(xr, PwIcon.Folder);
|
||||
else if(xr.Name == ElemCustomIconID)
|
||||
m_ctxGroup.CustomIconUuid = ReadUuid(xr);
|
||||
else if(xr.Name == ElemTimes)
|
||||
@@ -384,6 +412,8 @@ namespace ModernKeePassLib.Serialization
|
||||
m_ctxGroup.EnableSearching = StrUtil.StringToBoolEx(ReadString(xr));
|
||||
else if(xr.Name == ElemLastTopVisibleEntry)
|
||||
m_ctxGroup.LastTopVisibleEntry = ReadUuid(xr);
|
||||
else if(xr.Name == ElemCustomData)
|
||||
return SwitchContext(ctx, KdbContext.GroupCustomData, xr);
|
||||
else if(xr.Name == ElemGroup)
|
||||
{
|
||||
m_ctxGroup = new PwGroup(false, false);
|
||||
@@ -404,11 +434,25 @@ namespace ModernKeePassLib.Serialization
|
||||
else ReadUnknown(xr);
|
||||
break;
|
||||
|
||||
case KdbContext.GroupCustomData:
|
||||
if(xr.Name == ElemStringDictExItem)
|
||||
return SwitchContext(ctx, KdbContext.GroupCustomDataItem, xr);
|
||||
else ReadUnknown(xr);
|
||||
break;
|
||||
|
||||
case KdbContext.GroupCustomDataItem:
|
||||
if(xr.Name == ElemKey)
|
||||
m_strGroupCustomDataKey = ReadString(xr);
|
||||
else if(xr.Name == ElemValue)
|
||||
m_strGroupCustomDataValue = ReadString(xr);
|
||||
else ReadUnknown(xr);
|
||||
break;
|
||||
|
||||
case KdbContext.Entry:
|
||||
if(xr.Name == ElemUuid)
|
||||
m_ctxEntry.Uuid = ReadUuid(xr);
|
||||
else if(xr.Name == ElemIcon)
|
||||
m_ctxEntry.IconId = (PwIcon)ReadInt(xr, (int)PwIcon.Key);
|
||||
m_ctxEntry.IconId = ReadIconId(xr, PwIcon.Key);
|
||||
else if(xr.Name == ElemCustomIconID)
|
||||
m_ctxEntry.CustomIconUuid = ReadUuid(xr);
|
||||
else if(xr.Name == ElemFgColor)
|
||||
@@ -435,6 +479,8 @@ namespace ModernKeePassLib.Serialization
|
||||
return SwitchContext(ctx, KdbContext.EntryBinary, xr);
|
||||
else if(xr.Name == ElemAutoType)
|
||||
return SwitchContext(ctx, KdbContext.EntryAutoType, xr);
|
||||
else if(xr.Name == ElemCustomData)
|
||||
return SwitchContext(ctx, KdbContext.EntryCustomData, xr);
|
||||
else if(xr.Name == ElemHistory)
|
||||
{
|
||||
Debug.Assert(m_bEntryInHistory == false);
|
||||
@@ -509,6 +555,20 @@ namespace ModernKeePassLib.Serialization
|
||||
else ReadUnknown(xr);
|
||||
break;
|
||||
|
||||
case KdbContext.EntryCustomData:
|
||||
if(xr.Name == ElemStringDictExItem)
|
||||
return SwitchContext(ctx, KdbContext.EntryCustomDataItem, xr);
|
||||
else ReadUnknown(xr);
|
||||
break;
|
||||
|
||||
case KdbContext.EntryCustomDataItem:
|
||||
if(xr.Name == ElemKey)
|
||||
m_strEntryCustomDataKey = ReadString(xr);
|
||||
else if(xr.Name == ElemValue)
|
||||
m_strEntryCustomDataValue = ReadString(xr);
|
||||
else ReadUnknown(xr);
|
||||
break;
|
||||
|
||||
case KdbContext.EntryHistory:
|
||||
if(xr.Name == ElemEntry)
|
||||
{
|
||||
@@ -610,6 +670,19 @@ namespace ModernKeePassLib.Serialization
|
||||
}
|
||||
else if((ctx == KdbContext.GroupTimes) && (xr.Name == ElemTimes))
|
||||
return KdbContext.Group;
|
||||
else if((ctx == KdbContext.GroupCustomData) && (xr.Name == ElemCustomData))
|
||||
return KdbContext.Group;
|
||||
else if((ctx == KdbContext.GroupCustomDataItem) && (xr.Name == ElemStringDictExItem))
|
||||
{
|
||||
if((m_strGroupCustomDataKey != null) && (m_strGroupCustomDataValue != null))
|
||||
m_ctxGroup.CustomData.Set(m_strGroupCustomDataKey, m_strGroupCustomDataValue);
|
||||
else { Debug.Assert(false); }
|
||||
|
||||
m_strGroupCustomDataKey = null;
|
||||
m_strGroupCustomDataValue = null;
|
||||
|
||||
return KdbContext.GroupCustomData;
|
||||
}
|
||||
else if((ctx == KdbContext.Entry) && (xr.Name == ElemEntry))
|
||||
{
|
||||
// Create new UUID if absent
|
||||
@@ -660,6 +733,19 @@ namespace ModernKeePassLib.Serialization
|
||||
m_ctxATSeq = null;
|
||||
return KdbContext.EntryAutoType;
|
||||
}
|
||||
else if((ctx == KdbContext.EntryCustomData) && (xr.Name == ElemCustomData))
|
||||
return KdbContext.Entry;
|
||||
else if((ctx == KdbContext.EntryCustomDataItem) && (xr.Name == ElemStringDictExItem))
|
||||
{
|
||||
if((m_strEntryCustomDataKey != null) && (m_strEntryCustomDataValue != null))
|
||||
m_ctxEntry.CustomData.Set(m_strEntryCustomDataKey, m_strEntryCustomDataValue);
|
||||
else { Debug.Assert(false); }
|
||||
|
||||
m_strEntryCustomDataKey = null;
|
||||
m_strEntryCustomDataValue = null;
|
||||
|
||||
return KdbContext.EntryCustomData;
|
||||
}
|
||||
else if((ctx == KdbContext.EntryHistory) && (xr.Name == ElemHistory))
|
||||
{
|
||||
m_bEntryInHistory = false;
|
||||
@@ -707,6 +793,55 @@ namespace ModernKeePassLib.Serialization
|
||||
#endif
|
||||
}
|
||||
|
||||
private byte[] ReadBase64(XmlReader xr, bool bRaw)
|
||||
{
|
||||
// if(bRaw) return ReadBase64RawInChunks(xr);
|
||||
|
||||
string str = (bRaw ? ReadStringRaw(xr) : ReadString(xr));
|
||||
if(string.IsNullOrEmpty(str)) return MemUtil.EmptyByteArray;
|
||||
|
||||
return Convert.FromBase64String(str);
|
||||
}
|
||||
|
||||
/* private byte[] m_pbBase64ReadBuf = new byte[1024 * 1024 * 3];
|
||||
private byte[] ReadBase64RawInChunks(XmlReader xr)
|
||||
{
|
||||
xr.MoveToContent();
|
||||
|
||||
List<byte[]> lParts = new List<byte[]>();
|
||||
byte[] pbBuf = m_pbBase64ReadBuf;
|
||||
while(true)
|
||||
{
|
||||
int cb = xr.ReadElementContentAsBase64(pbBuf, 0, pbBuf.Length);
|
||||
if(cb == 0) break;
|
||||
|
||||
byte[] pb = new byte[cb];
|
||||
Array.Copy(pbBuf, 0, pb, 0, cb);
|
||||
lParts.Add(pb);
|
||||
|
||||
// No break when cb < pbBuf.Length, because ReadElementContentAsBase64
|
||||
// moves to the next XML node only when returning 0
|
||||
}
|
||||
m_bReadNextNode = false;
|
||||
|
||||
if(lParts.Count == 0) return MemUtil.EmptyByteArray;
|
||||
if(lParts.Count == 1) return lParts[0];
|
||||
|
||||
long cbRes = 0;
|
||||
for(int i = 0; i < lParts.Count; ++i)
|
||||
cbRes += lParts[i].Length;
|
||||
|
||||
byte[] pbRes = new byte[cbRes];
|
||||
int cbCur = 0;
|
||||
for(int i = 0; i < lParts.Count; ++i)
|
||||
{
|
||||
Array.Copy(lParts[i], 0, pbRes, cbCur, lParts[i].Length);
|
||||
cbCur += lParts[i].Length;
|
||||
}
|
||||
|
||||
return pbRes;
|
||||
} */
|
||||
|
||||
private bool ReadBool(XmlReader xr, bool bDefault)
|
||||
{
|
||||
string str = ReadString(xr);
|
||||
@@ -719,9 +854,9 @@ namespace ModernKeePassLib.Serialization
|
||||
|
||||
private PwUuid ReadUuid(XmlReader xr)
|
||||
{
|
||||
string str = ReadString(xr);
|
||||
if(string.IsNullOrEmpty(str)) return PwUuid.Zero;
|
||||
return new PwUuid(Convert.FromBase64String(str));
|
||||
byte[] pb = ReadBase64(xr, false);
|
||||
if(pb.Length == 0) return PwUuid.Zero;
|
||||
return new PwUuid(pb);
|
||||
}
|
||||
|
||||
private int ReadInt(XmlReader xr, int nDefault)
|
||||
@@ -782,15 +917,44 @@ namespace ModernKeePassLib.Serialization
|
||||
|
||||
private DateTime ReadTime(XmlReader xr)
|
||||
{
|
||||
string str = ReadString(xr);
|
||||
// Cf. WriteObject(string, DateTime)
|
||||
if((m_format == KdbxFormat.Default) && (m_uFileVersion >= FileVersion32_4))
|
||||
{
|
||||
// long l = ReadLong(xr, -1);
|
||||
// if(l != -1) return DateTime.FromBinary(l);
|
||||
|
||||
DateTime dt;
|
||||
if(TimeUtil.TryDeserializeUtc(str, out dt)) return dt;
|
||||
byte[] pb = ReadBase64(xr, false);
|
||||
if(pb.Length != 8)
|
||||
{
|
||||
Debug.Assert(false);
|
||||
byte[] pb8 = new byte[8];
|
||||
Array.Copy(pb, pb8, Math.Min(pb.Length, 8)); // Little-endian
|
||||
pb = pb8;
|
||||
}
|
||||
long lSec = MemUtil.BytesToInt64(pb);
|
||||
return new DateTime(lSec * TimeSpan.TicksPerSecond, DateTimeKind.Utc);
|
||||
}
|
||||
else
|
||||
{
|
||||
string str = ReadString(xr);
|
||||
|
||||
DateTime dt;
|
||||
if(TimeUtil.TryDeserializeUtc(str, out dt)) return dt;
|
||||
}
|
||||
|
||||
Debug.Assert(false);
|
||||
return m_dtNow;
|
||||
}
|
||||
|
||||
private PwIcon ReadIconId(XmlReader xr, PwIcon icDefault)
|
||||
{
|
||||
int i = ReadInt(xr, (int)icDefault);
|
||||
if((i >= 0) && (i < (int)PwIcon.Count)) return (PwIcon)i;
|
||||
|
||||
Debug.Assert(false);
|
||||
return icDefault;
|
||||
}
|
||||
|
||||
private ProtectedString ReadProtectedString(XmlReader xr)
|
||||
{
|
||||
XorredBuffer xb = ProcessNode(xr);
|
||||
@@ -815,10 +979,27 @@ namespace ModernKeePassLib.Serialization
|
||||
if(xr.MoveToAttribute(AttrRef))
|
||||
{
|
||||
string strRef = xr.Value;
|
||||
if(strRef != null)
|
||||
if(!string.IsNullOrEmpty(strRef))
|
||||
{
|
||||
ProtectedBinary pb = BinPoolGet(strRef);
|
||||
if(pb != null) return pb;
|
||||
int iRef;
|
||||
if(StrUtil.TryParseIntInvariant(strRef, out iRef))
|
||||
{
|
||||
ProtectedBinary pb = m_pbsBinaries.Get(iRef);
|
||||
if(pb != null)
|
||||
{
|
||||
// https://sourceforge.net/p/keepass/feature-requests/2023/
|
||||
xr.MoveToElement();
|
||||
#if DEBUG
|
||||
string strInner = ReadStringRaw(xr);
|
||||
Debug.Assert(string.IsNullOrEmpty(strInner));
|
||||
#else
|
||||
ReadStringRaw(xr);
|
||||
#endif
|
||||
|
||||
return pb;
|
||||
}
|
||||
else { Debug.Assert(false); }
|
||||
}
|
||||
else { Debug.Assert(false); }
|
||||
}
|
||||
else { Debug.Assert(false); }
|
||||
@@ -835,10 +1016,9 @@ namespace ModernKeePassLib.Serialization
|
||||
return new ProtectedBinary(true, xb);
|
||||
}
|
||||
|
||||
string strValue = ReadString(xr);
|
||||
if(strValue.Length == 0) return new ProtectedBinary();
|
||||
byte[] pbData = ReadBase64(xr, true);
|
||||
if(pbData.Length == 0) return new ProtectedBinary();
|
||||
|
||||
byte[] pbData = Convert.FromBase64String(strValue);
|
||||
if(bCompressed) pbData = MemUtil.Decompress(pbData);
|
||||
return new ProtectedBinary(false, pbData);
|
||||
}
|
||||
@@ -875,13 +1055,8 @@ namespace ModernKeePassLib.Serialization
|
||||
if(xr.Value == ValTrue)
|
||||
{
|
||||
xr.MoveToElement();
|
||||
string strEncrypted = ReadStringRaw(xr);
|
||||
|
||||
byte[] pbEncrypted;
|
||||
if(strEncrypted.Length > 0)
|
||||
pbEncrypted = Convert.FromBase64String(strEncrypted);
|
||||
else pbEncrypted = new byte[0];
|
||||
|
||||
byte[] pbEncrypted = ReadBase64(xr, true);
|
||||
byte[] pbPad = m_randomStream.GetRandomBytes((uint)pbEncrypted.Length);
|
||||
|
||||
xb = new XorredBuffer(pbEncrypted, pbPad);
|
||||
|
@@ -44,6 +44,9 @@ using ModernKeePassLib.Resources;
|
||||
using ModernKeePassLib.Utility;
|
||||
using Windows.Security.Cryptography.Core;
|
||||
using Windows.Storage.Streams;
|
||||
using ModernKeePassLib.Collections;
|
||||
using ModernKeePassLib.Cryptography.KeyDerivation;
|
||||
using ModernKeePassLib.Security;
|
||||
|
||||
namespace ModernKeePassLib.Serialization
|
||||
{
|
||||
@@ -53,85 +56,141 @@ namespace ModernKeePassLib.Serialization
|
||||
public sealed partial class KdbxFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Load a KDB file from a file.
|
||||
/// Load a KDBX file.
|
||||
/// </summary>
|
||||
/// <param name="strFilePath">File to load.</param>
|
||||
/// <param name="kdbFormat">Format specifier.</param>
|
||||
/// <param name="fmt">Format.</param>
|
||||
/// <param name="slLogger">Status logger (optional).</param>
|
||||
public void Load(string strFilePath, KdbxFormat kdbFormat, IStatusLogger slLogger)
|
||||
public void Load(string strFilePath, KdbxFormat fmt, IStatusLogger slLogger)
|
||||
{
|
||||
IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFilePath);
|
||||
Load(IOConnection.OpenRead(ioc), kdbFormat, slLogger);
|
||||
Load(IOConnection.OpenRead(ioc), fmt, slLogger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a KDB file from a stream.
|
||||
/// 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="kdbFormat">Format specifier.</param>
|
||||
/// <param name="fmt">Format.</param>
|
||||
/// <param name="slLogger">Status logger (optional).</param>
|
||||
public void Load(Stream sSource, KdbxFormat kdbFormat, IStatusLogger slLogger)
|
||||
public void Load(Stream sSource, KdbxFormat fmt, IStatusLogger slLogger)
|
||||
{
|
||||
Debug.Assert(sSource != null);
|
||||
if(sSource == null) throw new ArgumentNullException("sSource");
|
||||
|
||||
m_format = kdbFormat;
|
||||
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;
|
||||
|
||||
HashingStreamEx hashedStream = new HashingStreamEx(sSource, false, null);
|
||||
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
|
||||
{
|
||||
BinaryReaderEx br = null;
|
||||
BinaryReaderEx brDecrypted = null;
|
||||
Stream readerStream = null;
|
||||
|
||||
if(kdbFormat == KdbxFormat.Default)
|
||||
Stream sXml;
|
||||
if(fmt == KdbxFormat.Default)
|
||||
{
|
||||
br = new BinaryReaderEx(hashedStream, encNoBom, KLRes.FileCorrupted);
|
||||
ReadHeader(br);
|
||||
BinaryReaderEx br = new BinaryReaderEx(sHashing,
|
||||
encNoBom, KLRes.FileCorrupted);
|
||||
byte[] pbHeader = LoadHeader(br);
|
||||
m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader);
|
||||
|
||||
Stream sDecrypted = AttachStreamDecryptor(hashedStream);
|
||||
if((sDecrypted == null) || (sDecrypted == hashedStream))
|
||||
throw new SecurityException(KLRes.CryptoStreamFailed);
|
||||
int cbEncKey, cbEncIV;
|
||||
ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV);
|
||||
|
||||
brDecrypted = new BinaryReaderEx(sDecrypted, encNoBom, KLRes.FileCorrupted);
|
||||
byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32);
|
||||
ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64);
|
||||
|
||||
if((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32))
|
||||
throw new InvalidDataException();
|
||||
string strIncomplete = KLRes.FileHeaderCorrupted + " " +
|
||||
KLRes.FileIncomplete;
|
||||
|
||||
for(int iStart = 0; iStart < 32; ++iStart)
|
||||
Stream sPlain;
|
||||
if(m_uFileVersion < FileVersion32_4)
|
||||
{
|
||||
if(pbStoredStartBytes[iStart] != m_pbStreamStartBytes[iStart])
|
||||
throw new InvalidCompositeKeyException();
|
||||
}
|
||||
Stream sDecrypted = EncryptStream(sHashing, iCipher,
|
||||
pbCipherKey, cbEncIV, false);
|
||||
if((sDecrypted == null) || (sDecrypted == sHashing))
|
||||
throw new SecurityException(KLRes.CryptoStreamFailed);
|
||||
lStreams.Add(sDecrypted);
|
||||
|
||||
Stream sHashed = new HashedBlockStream(sDecrypted, false, 0,
|
||||
!m_bRepairMode);
|
||||
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)
|
||||
readerStream = new GZipStream(sHashed, CompressionMode.Decompress);
|
||||
else readerStream = sHashed;
|
||||
}
|
||||
else if(kdbFormat == KdbxFormat.PlainXml)
|
||||
readerStream = hashedStream;
|
||||
else { Debug.Assert(false); throw new FormatException("KdbFormat"); }
|
||||
{
|
||||
sXml = new GZipStream(sPlain, CompressionMode.Decompress);
|
||||
lStreams.Add(sXml);
|
||||
}
|
||||
else sXml = sPlain;
|
||||
|
||||
if(kdbFormat != KdbxFormat.PlainXml) // Is an encrypted format
|
||||
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_pbProtectedStreamKey == null)
|
||||
if(m_pbInnerRandomStreamKey == null)
|
||||
{
|
||||
Debug.Assert(false);
|
||||
throw new SecurityException("Invalid protected stream key!");
|
||||
throw new SecurityException("Invalid inner random stream key!");
|
||||
}
|
||||
|
||||
m_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
|
||||
m_pbProtectedStreamKey);
|
||||
m_pbInnerRandomStreamKey);
|
||||
}
|
||||
else m_randomStream = null; // No random stream for plain-text files
|
||||
|
||||
#if KeePassDebug_WriteXml
|
||||
// FileStream fsOut = new FileStream("Raw.xml", FileMode.Create,
|
||||
@@ -140,7 +199,7 @@ namespace ModernKeePassLib.Serialization
|
||||
// {
|
||||
// while(true)
|
||||
// {
|
||||
// int b = readerStream.ReadByte();
|
||||
// int b = sXml.ReadByte();
|
||||
// if(b == -1) break;
|
||||
// fsOut.WriteByte((byte)b);
|
||||
// }
|
||||
@@ -149,12 +208,8 @@ namespace ModernKeePassLib.Serialization
|
||||
// fsOut.Close();
|
||||
#endif
|
||||
|
||||
ReadXmlStreamed(readerStream, hashedStream);
|
||||
// ReadXmlDom(readerStream);
|
||||
|
||||
readerStream.Dispose();
|
||||
// GC.KeepAlive(br);
|
||||
// GC.KeepAlive(brDecrypted);
|
||||
ReadXmlStreamed(sXml, sHashing);
|
||||
// ReadXmlDom(sXml);
|
||||
}
|
||||
#if !ModernKeePassLib
|
||||
catch(CryptographicException) // Thrown on invalid padding
|
||||
@@ -162,15 +217,30 @@ namespace ModernKeePassLib.Serialization
|
||||
throw new CryptographicException(KLRes.FileCorrupted);
|
||||
}
|
||||
#endif
|
||||
finally { CommonCleanUpRead(sSource, hashedStream); }
|
||||
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(Stream sSource, HashingStreamEx hashedStream)
|
||||
private void CommonCleanUpRead(List<Stream> lStreams, HashingStreamEx sHashing)
|
||||
{
|
||||
hashedStream.Dispose();
|
||||
m_pbHashOfFileOnDisk = hashedStream.Hash;
|
||||
CloseStreams(lStreams);
|
||||
|
||||
sSource.Dispose();
|
||||
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)
|
||||
@@ -183,11 +253,21 @@ namespace ModernKeePassLib.Serialization
|
||||
// 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 void ReadHeader(BinaryReaderEx br)
|
||||
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;
|
||||
@@ -210,29 +290,21 @@ namespace ModernKeePassLib.Serialization
|
||||
uint uVersion = MemUtil.BytesToUInt32(pb);
|
||||
if((uVersion & FileVersionCriticalMask) > (FileVersion32 & FileVersionCriticalMask))
|
||||
throw new FormatException(KLRes.FileVersionUnsupported +
|
||||
Environment.NewLine + Environment.NewLine + KLRes.FileNewVerReq);
|
||||
Environment.NewLine + KLRes.FileNewVerReq);
|
||||
m_uFileVersion = uVersion;
|
||||
|
||||
while(true)
|
||||
{
|
||||
if(ReadHeaderField(br) == false)
|
||||
break;
|
||||
if(!ReadHeaderField(br)) break;
|
||||
}
|
||||
|
||||
br.CopyDataTo = null;
|
||||
byte[] pbHeader = msHeader.ToArray();
|
||||
msHeader.Dispose();
|
||||
|
||||
#if ModernKeePassLib
|
||||
/*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256);
|
||||
m_pbHashOfHeader = sha256.HashData(pbHeader);*/
|
||||
var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
|
||||
var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(pbHeader));
|
||||
CryptographicBuffer.CopyToByteArray(buffer, out m_pbHashOfHeader);
|
||||
#else
|
||||
SHA256Managed sha256 = new SHA256Managed();
|
||||
m_pbHashOfHeader = sha256.ComputeHash(pbHeader);
|
||||
#endif
|
||||
}
|
||||
br.ReadExceptionText = strPrevExcpText;
|
||||
return pbHeader;
|
||||
}
|
||||
|
||||
private bool ReadHeaderField(BinaryReaderEx brSource)
|
||||
{
|
||||
@@ -240,18 +312,16 @@ namespace ModernKeePassLib.Serialization
|
||||
if(brSource == null) throw new ArgumentNullException("brSource");
|
||||
|
||||
byte btFieldID = brSource.ReadByte();
|
||||
ushort uSize = MemUtil.BytesToUInt16(brSource.ReadBytes(2));
|
||||
|
||||
byte[] pbData = null;
|
||||
if(uSize > 0)
|
||||
{
|
||||
string strPrevExcpText = brSource.ReadExceptionText;
|
||||
brSource.ReadExceptionText = KLRes.FileHeaderEndEarly;
|
||||
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);
|
||||
|
||||
pbData = brSource.ReadBytes(uSize);
|
||||
|
||||
brSource.ReadExceptionText = strPrevExcpText;
|
||||
}
|
||||
byte[] pbData = MemUtil.EmptyByteArray;
|
||||
if(cbSize > 0) pbData = brSource.ReadBytes(cbSize);
|
||||
|
||||
bool bResult = true;
|
||||
KdbxHeaderFieldID kdbID = (KdbxHeaderFieldID)btFieldID;
|
||||
@@ -274,32 +344,63 @@ namespace ModernKeePassLib.Serialization
|
||||
CryptoRandom.Instance.AddEntropy(pbData);
|
||||
break;
|
||||
|
||||
// Obsolete; for backward compatibility only
|
||||
case KdbxHeaderFieldID.TransformSeed:
|
||||
m_pbTransformSeed = pbData;
|
||||
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:
|
||||
m_pwDatabase.KeyEncryptionRounds = MemUtil.BytesToUInt64(pbData);
|
||||
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.ProtectedStreamKey:
|
||||
m_pbProtectedStreamKey = pbData;
|
||||
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)
|
||||
@@ -311,9 +412,71 @@ namespace ModernKeePassLib.Serialization
|
||||
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 != 16))
|
||||
if((pbID == null) || (pbID.Length != (int)PwUuid.UuidSize))
|
||||
throw new FormatException(KLRes.FileUnknownCipher);
|
||||
|
||||
m_pwDatabase.DataCipherUuid = new PwUuid(pbID);
|
||||
@@ -337,57 +500,34 @@ namespace ModernKeePassLib.Serialization
|
||||
m_craInnerRandomStream = (CrsAlgorithm)uID;
|
||||
}
|
||||
|
||||
private Stream AttachStreamDecryptor(Stream s)
|
||||
[Obsolete]
|
||||
public static List<PwEntry> ReadEntries(Stream msData)
|
||||
{
|
||||
MemoryStream ms = new MemoryStream();
|
||||
|
||||
Debug.Assert(m_pbMasterSeed.Length == 32);
|
||||
if(m_pbMasterSeed.Length != 32)
|
||||
throw new FormatException(KLRes.MasterSeedLengthInvalid);
|
||||
ms.Write(m_pbMasterSeed, 0, 32);
|
||||
|
||||
byte[] pKey32 = m_pwDatabase.MasterKey.GenerateKey32(m_pbTransformSeed,
|
||||
m_pwDatabase.KeyEncryptionRounds).ReadData();
|
||||
if((pKey32 == null) || (pKey32.Length != 32))
|
||||
throw new SecurityException(KLRes.InvalidCompositeKey);
|
||||
ms.Write(pKey32, 0, 32);
|
||||
|
||||
#if ModernKeePassLib
|
||||
/*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256);
|
||||
var aesKey = sha256.HashData(ms.ToArray());*/
|
||||
var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
|
||||
var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(ms.ToArray()));
|
||||
byte[] aesKey;
|
||||
CryptographicBuffer.CopyToByteArray(buffer, out aesKey);
|
||||
#else
|
||||
SHA256Managed sha256 = new SHA256Managed();
|
||||
byte[] aesKey = sha256.ComputeHash(ms.ToArray());
|
||||
#endif
|
||||
|
||||
ms.Dispose();
|
||||
Array.Clear(pKey32, 0, 32);
|
||||
|
||||
if((aesKey == null) || (aesKey.Length != 32))
|
||||
throw new SecurityException(KLRes.FinalKeyCreationFailed);
|
||||
|
||||
ICipherEngine iEngine = CipherPool.GlobalPool.GetCipher(m_pwDatabase.DataCipherUuid);
|
||||
if(iEngine == null) throw new SecurityException(KLRes.FileUnknownCipher);
|
||||
return iEngine.DecryptStream(s, aesKey, m_pbEncryptionIV);
|
||||
return ReadEntries(msData, null, false);
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public static List<PwEntry> ReadEntries(PwDatabase pwDatabase, Stream msData)
|
||||
public static List<PwEntry> ReadEntries(PwDatabase pdContext, Stream msData)
|
||||
{
|
||||
return ReadEntries(msData);
|
||||
return ReadEntries(msData, pdContext, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read entries from a stream.
|
||||
/// </summary>
|
||||
/// <param name="msData">Input stream to read the entries from.</param>
|
||||
/// <returns>Extracted entries.</returns>
|
||||
public static List<PwEntry> ReadEntries(Stream msData)
|
||||
/// <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;
|
||||
|
||||
@@ -417,17 +557,37 @@ namespace ModernKeePassLib.Serialization
|
||||
return vEntries; */
|
||||
|
||||
PwDatabase pd = new PwDatabase();
|
||||
pd.New(new IOConnectionInfo(), new CompositeKey());
|
||||
|
||||
KdbxFile f = new KdbxFile(pd);
|
||||
f.Load(msData, KdbxFormat.PlainXml, null);
|
||||
|
||||
List<PwEntry> vEntries = new List<PwEntry>();
|
||||
foreach(PwEntry pe in pd.RootGroup.Entries)
|
||||
{
|
||||
pe.SetUuid(new PwUuid(true), true);
|
||||
vEntries.Add(pe);
|
||||
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 vEntries;
|
||||
return lEntries;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -49,6 +49,7 @@ using ModernKeePassLib.Security;
|
||||
using ModernKeePassLib.Utility;
|
||||
using Windows.Security.Cryptography.Core;
|
||||
using Windows.Storage.Streams;
|
||||
using ModernKeePassLib.Cryptography.KeyDerivation;
|
||||
|
||||
namespace ModernKeePassLib.Serialization
|
||||
{
|
||||
@@ -57,7 +58,7 @@ namespace ModernKeePassLib.Serialization
|
||||
/// </summary>
|
||||
public sealed partial class KdbxFile
|
||||
{
|
||||
// public void Save(string strFile, PwGroup pgDataSource, KdbxFormat format,
|
||||
// public void Save(string strFile, PwGroup pgDataSource, KdbxFormat fmt,
|
||||
// IStatusLogger slLogger)
|
||||
// {
|
||||
// bool bMadeUnhidden = UrlUtil.UnhideFile(strFile);
|
||||
@@ -75,212 +76,324 @@ namespace ModernKeePassLib.Serialization
|
||||
/// <param name="pgDataSource">Group containing all groups and
|
||||
/// entries to write. If <c>null</c>, the complete database will
|
||||
/// be written.</param>
|
||||
/// <param name="format">Format of the file to create.</param>
|
||||
/// <param name="fmt">Format of the file to create.</param>
|
||||
/// <param name="slLogger">Logger that recieves status information.</param>
|
||||
public void Save(Stream sSaveTo, PwGroup pgDataSource, KdbxFormat format,
|
||||
public void Save(Stream sSaveTo, PwGroup pgDataSource, KdbxFormat fmt,
|
||||
IStatusLogger slLogger)
|
||||
{
|
||||
Debug.Assert(sSaveTo != null);
|
||||
if(sSaveTo == null) throw new ArgumentNullException("sSaveTo");
|
||||
|
||||
m_format = format;
|
||||
if(m_bUsedOnce)
|
||||
throw new InvalidOperationException("Do not reuse KdbxFile objects!");
|
||||
m_bUsedOnce = true;
|
||||
|
||||
m_format = fmt;
|
||||
m_slLogger = slLogger;
|
||||
|
||||
HashingStreamEx hashedStream = new HashingStreamEx(sSaveTo, true, null);
|
||||
|
||||
PwGroup pgRoot = (pgDataSource ?? m_pwDatabase.RootGroup);
|
||||
UTF8Encoding encNoBom = StrUtil.Utf8;
|
||||
CryptoRandom cr = CryptoRandom.Instance;
|
||||
byte[] pbCipherKey = null;
|
||||
byte[] pbHmacKey64 = null;
|
||||
|
||||
m_pbsBinaries.Clear();
|
||||
m_pbsBinaries.AddFrom(pgRoot);
|
||||
|
||||
List<Stream> lStreams = new List<Stream>();
|
||||
lStreams.Add(sSaveTo);
|
||||
|
||||
HashingStreamEx sHashing = new HashingStreamEx(sSaveTo, true, null);
|
||||
lStreams.Add(sHashing);
|
||||
|
||||
try
|
||||
{
|
||||
m_uFileVersion = GetMinKdbxVersion();
|
||||
|
||||
int cbEncKey, cbEncIV;
|
||||
ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV);
|
||||
|
||||
m_pbMasterSeed = cr.GetRandomBytes(32);
|
||||
m_pbTransformSeed = cr.GetRandomBytes(32);
|
||||
m_pbEncryptionIV = cr.GetRandomBytes(16);
|
||||
m_pbEncryptionIV = cr.GetRandomBytes((uint)cbEncIV);
|
||||
|
||||
m_pbProtectedStreamKey = cr.GetRandomBytes(32);
|
||||
m_craInnerRandomStream = CrsAlgorithm.Salsa20;
|
||||
m_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
|
||||
m_pbProtectedStreamKey);
|
||||
// m_pbTransformSeed = cr.GetRandomBytes(32);
|
||||
PwUuid puKdf = m_pwDatabase.KdfParameters.KdfUuid;
|
||||
KdfEngine kdf = KdfPool.Get(puKdf);
|
||||
if(kdf == null)
|
||||
throw new Exception(KLRes.UnknownKdf + Environment.NewLine +
|
||||
// KLRes.FileNewVerOrPlgReq + Environment.NewLine +
|
||||
"UUID: " + puKdf.ToHexString() + ".");
|
||||
kdf.Randomize(m_pwDatabase.KdfParameters);
|
||||
|
||||
m_pbStreamStartBytes = cr.GetRandomBytes(32);
|
||||
|
||||
Stream writerStream;
|
||||
if(m_format == KdbxFormat.Default)
|
||||
{
|
||||
WriteHeader(hashedStream); // Also flushes the stream
|
||||
if(m_uFileVersion < FileVersion32_4)
|
||||
{
|
||||
m_craInnerRandomStream = CrsAlgorithm.Salsa20;
|
||||
m_pbInnerRandomStreamKey = cr.GetRandomBytes(32);
|
||||
}
|
||||
else // KDBX >= 4
|
||||
{
|
||||
m_craInnerRandomStream = CrsAlgorithm.ChaCha20;
|
||||
m_pbInnerRandomStreamKey = cr.GetRandomBytes(64);
|
||||
}
|
||||
|
||||
Stream sEncrypted = AttachStreamEncryptor(hashedStream);
|
||||
if((sEncrypted == null) || (sEncrypted == hashedStream))
|
||||
throw new SecurityException(KLRes.CryptoStreamFailed);
|
||||
m_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
|
||||
m_pbInnerRandomStreamKey);
|
||||
}
|
||||
|
||||
sEncrypted.Write(m_pbStreamStartBytes, 0, m_pbStreamStartBytes.Length);
|
||||
if(m_uFileVersion < FileVersion32_4)
|
||||
m_pbStreamStartBytes = cr.GetRandomBytes(32);
|
||||
|
||||
Stream sHashed = new HashedBlockStream(sEncrypted, true);
|
||||
Stream sXml;
|
||||
if(m_format == KdbxFormat.Default)
|
||||
{
|
||||
byte[] pbHeader = GenerateHeader();
|
||||
m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader);
|
||||
|
||||
MemUtil.Write(sHashing, pbHeader);
|
||||
sHashing.Flush();
|
||||
|
||||
ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64);
|
||||
|
||||
Stream sPlain;
|
||||
if(m_uFileVersion < FileVersion32_4)
|
||||
{
|
||||
Stream sEncrypted = EncryptStream(sHashing, iCipher,
|
||||
pbCipherKey, cbEncIV, true);
|
||||
if((sEncrypted == null) || (sEncrypted == sHashing))
|
||||
throw new SecurityException(KLRes.CryptoStreamFailed);
|
||||
lStreams.Add(sEncrypted);
|
||||
|
||||
MemUtil.Write(sEncrypted, m_pbStreamStartBytes);
|
||||
|
||||
sPlain = new HashedBlockStream(sEncrypted, true);
|
||||
}
|
||||
else // KDBX >= 4
|
||||
{
|
||||
// For integrity checking (without knowing the master key)
|
||||
MemUtil.Write(sHashing, m_pbHashOfHeader);
|
||||
|
||||
byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64);
|
||||
MemUtil.Write(sHashing, pbHeaderHmac);
|
||||
|
||||
Stream sBlocks = new HmacBlockStream(sHashing, true,
|
||||
true, pbHmacKey64);
|
||||
lStreams.Add(sBlocks);
|
||||
|
||||
sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey,
|
||||
cbEncIV, true);
|
||||
if((sPlain == null) || (sPlain == sBlocks))
|
||||
throw new SecurityException(KLRes.CryptoStreamFailed);
|
||||
}
|
||||
lStreams.Add(sPlain);
|
||||
|
||||
if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
|
||||
writerStream = new GZipStream(sHashed, CompressionMode.Compress);
|
||||
else
|
||||
writerStream = sHashed;
|
||||
{
|
||||
sXml = new GZipStream(sPlain, CompressionMode.Compress);
|
||||
lStreams.Add(sXml);
|
||||
}
|
||||
else sXml = sPlain;
|
||||
|
||||
if(m_uFileVersion >= FileVersion32_4)
|
||||
WriteInnerHeader(sXml); // Binary header before XML
|
||||
}
|
||||
else if(m_format == KdbxFormat.PlainXml)
|
||||
writerStream = hashedStream;
|
||||
else { Debug.Assert(false); throw new FormatException("KdbFormat"); }
|
||||
sXml = sHashing;
|
||||
else
|
||||
{
|
||||
Debug.Assert(false);
|
||||
throw new ArgumentOutOfRangeException("fmt");
|
||||
}
|
||||
|
||||
#if ModernKeePassLib
|
||||
var settings = new XmlWriterSettings() {
|
||||
Encoding = encNoBom,
|
||||
Indent = true,
|
||||
IndentChars = "\t",
|
||||
NewLineChars = "\r\n",
|
||||
};
|
||||
m_xmlWriter = XmlWriter.Create(writerStream, settings);
|
||||
#if ModernKeePassLib || KeePassUAP
|
||||
XmlWriterSettings xws = new XmlWriterSettings();
|
||||
xws.Encoding = encNoBom;
|
||||
xws.Indent = true;
|
||||
xws.IndentChars = "\t";
|
||||
xws.NewLineOnAttributes = false;
|
||||
|
||||
XmlWriter xw = XmlWriter.Create(sXml, xws);
|
||||
#else
|
||||
m_xmlWriter = new XmlTextWriter(writerStream, encNoBom);
|
||||
XmlTextWriter xw = new XmlTextWriter(sXml, encNoBom);
|
||||
|
||||
xw.Formatting = Formatting.Indented;
|
||||
xw.IndentChar = '\t';
|
||||
xw.Indentation = 1;
|
||||
#endif
|
||||
WriteDocument(pgDataSource);
|
||||
m_xmlWriter = xw;
|
||||
|
||||
WriteDocument(pgRoot);
|
||||
|
||||
m_xmlWriter.Flush();
|
||||
m_xmlWriter.Dispose();
|
||||
writerStream.Dispose();
|
||||
}
|
||||
finally { CommonCleanUpWrite(sSaveTo, hashedStream); }
|
||||
finally
|
||||
{
|
||||
if(pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey);
|
||||
if(pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64);
|
||||
|
||||
CommonCleanUpWrite(lStreams, sHashing);
|
||||
}
|
||||
}
|
||||
|
||||
private void CommonCleanUpWrite(Stream sSaveTo, HashingStreamEx hashedStream)
|
||||
private void CommonCleanUpWrite(List<Stream> lStreams, HashingStreamEx sHashing)
|
||||
{
|
||||
hashedStream.Dispose();
|
||||
m_pbHashOfFileOnDisk = hashedStream.Hash;
|
||||
CloseStreams(lStreams);
|
||||
|
||||
sSaveTo.Dispose();
|
||||
Debug.Assert(lStreams.Contains(sHashing)); // sHashing must be closed
|
||||
m_pbHashOfFileOnDisk = sHashing.Hash;
|
||||
Debug.Assert(m_pbHashOfFileOnDisk != null);
|
||||
|
||||
CleanUpInnerRandomStream();
|
||||
|
||||
m_xmlWriter = null;
|
||||
m_pbHashOfHeader = null;
|
||||
}
|
||||
|
||||
private void WriteHeader(Stream s)
|
||||
private byte[] GenerateHeader()
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
byte[] pbHeader;
|
||||
using(MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature1));
|
||||
MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature2));
|
||||
MemUtil.Write(ms, MemUtil.UInt32ToBytes(m_uFileVersion));
|
||||
|
||||
MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature1));
|
||||
MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature2));
|
||||
MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileVersion32));
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.CipherID,
|
||||
m_pwDatabase.DataCipherUuid.UuidBytes);
|
||||
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.CipherID,
|
||||
m_pwDatabase.DataCipherUuid.UuidBytes);
|
||||
int nCprID = (int)m_pwDatabase.Compression;
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.CompressionFlags,
|
||||
MemUtil.UInt32ToBytes((uint)nCprID));
|
||||
|
||||
int nCprID = (int) m_pwDatabase.Compression;
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.CompressionFlags,
|
||||
MemUtil.UInt32ToBytes((uint) nCprID));
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.MasterSeed, m_pbMasterSeed);
|
||||
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.MasterSeed, m_pbMasterSeed);
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.TransformSeed, m_pbTransformSeed);
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.TransformRounds,
|
||||
MemUtil.UInt64ToBytes(m_pwDatabase.KeyEncryptionRounds));
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.EncryptionIV, m_pbEncryptionIV);
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.ProtectedStreamKey, m_pbProtectedStreamKey);
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.StreamStartBytes, m_pbStreamStartBytes);
|
||||
if(m_uFileVersion < FileVersion32_4)
|
||||
{
|
||||
Debug.Assert(m_pwDatabase.KdfParameters.KdfUuid.Equals(
|
||||
(new AesKdf()).Uuid));
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.TransformSeed,
|
||||
m_pwDatabase.KdfParameters.GetByteArray(AesKdf.ParamSeed));
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.TransformRounds,
|
||||
MemUtil.UInt64ToBytes(m_pwDatabase.KdfParameters.GetUInt64(
|
||||
AesKdf.ParamRounds, PwDefs.DefaultKeyEncryptionRounds)));
|
||||
}
|
||||
else
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.KdfParameters,
|
||||
KdfParameters.SerializeExt(m_pwDatabase.KdfParameters));
|
||||
|
||||
int nIrsID = (int) m_craInnerRandomStream;
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID,
|
||||
MemUtil.UInt32ToBytes((uint) nIrsID));
|
||||
if(m_pbEncryptionIV.Length > 0)
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.EncryptionIV, m_pbEncryptionIV);
|
||||
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.EndOfHeader, new byte[]
|
||||
{
|
||||
(byte) '\r', (byte) '\n', (byte) '\r', (byte) '\n'
|
||||
});
|
||||
if(m_uFileVersion < FileVersion32_4)
|
||||
{
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamKey,
|
||||
m_pbInnerRandomStreamKey);
|
||||
|
||||
byte[] pbHeader = ms.ToArray();
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.StreamStartBytes,
|
||||
m_pbStreamStartBytes);
|
||||
|
||||
#if ModernKeePassLib
|
||||
/*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256);
|
||||
m_pbHashOfHeader = sha256.HashData(pbHeader);*/
|
||||
var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
|
||||
var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(pbHeader));
|
||||
CryptographicBuffer.CopyToByteArray(buffer, out m_pbHashOfHeader);
|
||||
#else
|
||||
SHA256Managed sha256 = new SHA256Managed();
|
||||
m_pbHashOfHeader = sha256.ComputeHash(pbHeader);
|
||||
#endif
|
||||
int nIrsID = (int)m_craInnerRandomStream;
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID,
|
||||
MemUtil.Int32ToBytes(nIrsID));
|
||||
}
|
||||
|
||||
s.Write(pbHeader, 0, pbHeader.Length);
|
||||
s.Flush();
|
||||
}
|
||||
// Write public custom data only when there is at least one item,
|
||||
// because KDBX 3.1 didn't support this field yet
|
||||
if(m_pwDatabase.PublicCustomData.Count > 0)
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.PublicCustomData,
|
||||
VariantDictionary.Serialize(m_pwDatabase.PublicCustomData));
|
||||
|
||||
WriteHeaderField(ms, KdbxHeaderFieldID.EndOfHeader, new byte[] {
|
||||
(byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' });
|
||||
|
||||
pbHeader = ms.ToArray();
|
||||
}
|
||||
|
||||
return pbHeader;
|
||||
}
|
||||
|
||||
private static void WriteHeaderField(Stream s, KdbxHeaderFieldID kdbID,
|
||||
private void WriteHeaderField(Stream s, KdbxHeaderFieldID kdbID,
|
||||
byte[] pbData)
|
||||
{
|
||||
s.WriteByte((byte)kdbID);
|
||||
|
||||
if(pbData != null)
|
||||
byte[] pb = (pbData ?? MemUtil.EmptyByteArray);
|
||||
int cb = pb.Length;
|
||||
if(cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); }
|
||||
|
||||
Debug.Assert(m_uFileVersion > 0);
|
||||
if(m_uFileVersion < FileVersion32_4)
|
||||
{
|
||||
ushort uLength = (ushort)pbData.Length;
|
||||
MemUtil.Write(s, MemUtil.UInt16ToBytes(uLength));
|
||||
if(cb > (int)ushort.MaxValue)
|
||||
{
|
||||
Debug.Assert(false);
|
||||
throw new ArgumentOutOfRangeException("pbData");
|
||||
}
|
||||
|
||||
if(uLength > 0) s.Write(pbData, 0, pbData.Length);
|
||||
MemUtil.Write(s, MemUtil.UInt16ToBytes((ushort)cb));
|
||||
}
|
||||
else MemUtil.Write(s, MemUtil.UInt16ToBytes((ushort)0));
|
||||
else MemUtil.Write(s, MemUtil.Int32ToBytes(cb));
|
||||
|
||||
MemUtil.Write(s, pb);
|
||||
}
|
||||
|
||||
private Stream AttachStreamEncryptor(Stream s)
|
||||
private void WriteInnerHeader(Stream s)
|
||||
{
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
Debug.Assert(m_pbMasterSeed != null);
|
||||
Debug.Assert(m_pbMasterSeed.Length == 32);
|
||||
ms.Write(m_pbMasterSeed, 0, 32);
|
||||
int nIrsID = (int)m_craInnerRandomStream;
|
||||
WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.InnerRandomStreamID,
|
||||
MemUtil.Int32ToBytes(nIrsID), null);
|
||||
|
||||
Debug.Assert(m_pwDatabase != null);
|
||||
Debug.Assert(m_pwDatabase.MasterKey != null);
|
||||
ProtectedBinary pbinKey = m_pwDatabase.MasterKey.GenerateKey32(
|
||||
m_pbTransformSeed, m_pwDatabase.KeyEncryptionRounds);
|
||||
Debug.Assert(pbinKey != null);
|
||||
if (pbinKey == null)
|
||||
throw new SecurityException(KLRes.InvalidCompositeKey);
|
||||
byte[] pKey32 = pbinKey.ReadData();
|
||||
if ((pKey32 == null) || (pKey32.Length != 32))
|
||||
throw new SecurityException(KLRes.InvalidCompositeKey);
|
||||
ms.Write(pKey32, 0, 32);
|
||||
WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.InnerRandomStreamKey,
|
||||
m_pbInnerRandomStreamKey, null);
|
||||
|
||||
#if ModernKeePassLib
|
||||
/*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256);
|
||||
var aesKey = sha256.HashData(ms.ToArray());*/
|
||||
var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
|
||||
var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(ms.ToArray()));
|
||||
byte[] aesKey;
|
||||
CryptographicBuffer.CopyToByteArray(buffer, out aesKey);
|
||||
#else
|
||||
SHA256Managed sha256 = new SHA256Managed();
|
||||
byte[] aesKey = sha256.ComputeHash(ms.ToArray());
|
||||
#endif
|
||||
Array.Clear(pKey32, 0, 32);
|
||||
ProtectedBinary[] vBin = m_pbsBinaries.ToArray();
|
||||
for(int i = 0; i < vBin.Length; ++i)
|
||||
{
|
||||
ProtectedBinary pb = vBin[i];
|
||||
if(pb == null) throw new InvalidOperationException();
|
||||
|
||||
Debug.Assert(CipherPool.GlobalPool != null);
|
||||
ICipherEngine iEngine = CipherPool.GlobalPool.GetCipher(m_pwDatabase.DataCipherUuid);
|
||||
if (iEngine == null) throw new SecurityException(KLRes.FileUnknownCipher);
|
||||
return iEngine.EncryptStream(s, aesKey, m_pbEncryptionIV);
|
||||
}
|
||||
KdbxBinaryFlags f = KdbxBinaryFlags.None;
|
||||
if(pb.IsProtected) f |= KdbxBinaryFlags.Protected;
|
||||
|
||||
byte[] pbFlags = new byte[1] { (byte)f };
|
||||
byte[] pbData = pb.ReadData();
|
||||
|
||||
WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.Binary,
|
||||
pbFlags, pbData);
|
||||
|
||||
if(pb.IsProtected) MemUtil.ZeroByteArray(pbData);
|
||||
}
|
||||
|
||||
WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.EndOfHeader,
|
||||
null, null);
|
||||
}
|
||||
|
||||
private void WriteDocument(PwGroup pgDataSource)
|
||||
private void WriteInnerHeaderField(Stream s, KdbxInnerHeaderFieldID kdbID,
|
||||
byte[] pbData1, byte[] pbData2)
|
||||
{
|
||||
s.WriteByte((byte)kdbID);
|
||||
|
||||
byte[] pb1 = (pbData1 ?? MemUtil.EmptyByteArray);
|
||||
byte[] pb2 = (pbData2 ?? MemUtil.EmptyByteArray);
|
||||
|
||||
int cb = pb1.Length + pb2.Length;
|
||||
if(cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); }
|
||||
|
||||
MemUtil.Write(s, MemUtil.Int32ToBytes(cb));
|
||||
MemUtil.Write(s, pb1);
|
||||
MemUtil.Write(s, pb2);
|
||||
}
|
||||
|
||||
private void WriteDocument(PwGroup pgRoot)
|
||||
{
|
||||
Debug.Assert(m_xmlWriter != null);
|
||||
if(m_xmlWriter == null) throw new InvalidOperationException();
|
||||
|
||||
PwGroup pgRoot = (pgDataSource ?? m_pwDatabase.RootGroup);
|
||||
|
||||
uint uNumGroups, uNumEntries, uCurEntry = 0;
|
||||
pgRoot.GetCounts(true, out uNumGroups, out uNumEntries);
|
||||
|
||||
BinPoolBuild(pgRoot);
|
||||
|
||||
#if !ModernKeePassLib
|
||||
m_xmlWriter.Formatting = Formatting.Indented;
|
||||
m_xmlWriter.IndentChar = '\t';
|
||||
m_xmlWriter.Indentation = 1;
|
||||
#endif
|
||||
|
||||
m_xmlWriter.WriteStartDocument(true);
|
||||
m_xmlWriter.WriteStartElement(ElemDocNode);
|
||||
|
||||
@@ -352,12 +465,15 @@ namespace ModernKeePassLib.Serialization
|
||||
{
|
||||
m_xmlWriter.WriteStartElement(ElemMeta);
|
||||
|
||||
WriteObject(ElemGenerator, PwDatabase.LocalizedAppName, false); // Generator name
|
||||
WriteObject(ElemGenerator, PwDatabase.LocalizedAppName, false);
|
||||
|
||||
if(m_pbHashOfHeader != null)
|
||||
if((m_pbHashOfHeader != null) && (m_uFileVersion < FileVersion32_4))
|
||||
WriteObject(ElemHeaderHash, Convert.ToBase64String(
|
||||
m_pbHashOfHeader), false);
|
||||
|
||||
if(m_uFileVersion >= FileVersion32_4)
|
||||
WriteObject(ElemSettingsChanged, m_pwDatabase.SettingsChanged);
|
||||
|
||||
WriteObject(ElemDbName, m_pwDatabase.Name, true);
|
||||
WriteObject(ElemDbNameChanged, m_pwDatabase.NameChanged);
|
||||
WriteObject(ElemDbDesc, m_pwDatabase.Description, true);
|
||||
@@ -369,6 +485,8 @@ namespace ModernKeePassLib.Serialization
|
||||
WriteObject(ElemDbKeyChanged, m_pwDatabase.MasterKeyChanged);
|
||||
WriteObject(ElemDbKeyChangeRec, m_pwDatabase.MasterKeyChangeRec);
|
||||
WriteObject(ElemDbKeyChangeForce, m_pwDatabase.MasterKeyChangeForce);
|
||||
if(m_pwDatabase.MasterKeyChangeForceOnce)
|
||||
WriteObject(ElemDbKeyChangeForceOnce, true);
|
||||
|
||||
WriteList(ElemMemoryProt, m_pwDatabase.MemoryProtection);
|
||||
|
||||
@@ -385,7 +503,9 @@ namespace ModernKeePassLib.Serialization
|
||||
WriteObject(ElemLastSelectedGroup, m_pwDatabase.LastSelectedGroup);
|
||||
WriteObject(ElemLastTopVisibleGroup, m_pwDatabase.LastTopVisibleGroup);
|
||||
|
||||
WriteBinPool();
|
||||
if((m_format != KdbxFormat.Default) || (m_uFileVersion < FileVersion32_4))
|
||||
WriteBinPool();
|
||||
|
||||
WriteList(ElemCustomData, m_pwDatabase.CustomData);
|
||||
|
||||
m_xmlWriter.WriteEndElement();
|
||||
@@ -408,6 +528,9 @@ namespace ModernKeePassLib.Serialization
|
||||
WriteObject(ElemEnableAutoType, StrUtil.BoolToStringEx(pg.EnableAutoType), false);
|
||||
WriteObject(ElemEnableSearching, StrUtil.BoolToStringEx(pg.EnableSearching), false);
|
||||
WriteObject(ElemLastTopVisibleEntry, pg.LastTopVisibleEntry);
|
||||
|
||||
if(pg.CustomData.Count > 0)
|
||||
WriteList(ElemCustomData, pg.CustomData);
|
||||
}
|
||||
|
||||
private void EndGroup()
|
||||
@@ -423,7 +546,7 @@ namespace ModernKeePassLib.Serialization
|
||||
|
||||
WriteObject(ElemUuid, pe.Uuid);
|
||||
WriteObject(ElemIcon, (int)pe.IconId);
|
||||
|
||||
|
||||
if(!pe.CustomIconUuid.Equals(PwUuid.Zero))
|
||||
WriteObject(ElemCustomIconID, pe.CustomIconUuid);
|
||||
|
||||
@@ -438,6 +561,9 @@ namespace ModernKeePassLib.Serialization
|
||||
WriteList(pe.Binaries);
|
||||
WriteList(ElemAutoType, pe.AutoType);
|
||||
|
||||
if(pe.CustomData.Count > 0)
|
||||
WriteList(ElemCustomData, pe.CustomData);
|
||||
|
||||
if(!bIsHistory) WriteList(ElemHistory, pe.History, true);
|
||||
else { Debug.Assert(pe.History.UCount == 0); }
|
||||
|
||||
@@ -647,8 +773,28 @@ namespace ModernKeePassLib.Serialization
|
||||
private void WriteObject(string name, DateTime value)
|
||||
{
|
||||
Debug.Assert(name != null);
|
||||
Debug.Assert(value.Kind == DateTimeKind.Utc);
|
||||
|
||||
WriteObject(name, TimeUtil.SerializeUtc(value), false);
|
||||
// Cf. ReadTime
|
||||
if((m_format == KdbxFormat.Default) && (m_uFileVersion >= FileVersion32_4))
|
||||
{
|
||||
DateTime dt = TimeUtil.ToUtc(value, false);
|
||||
|
||||
// DateTime dtBase = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
// dt -= new TimeSpan(dtBase.Ticks);
|
||||
|
||||
// WriteObject(name, dt.ToBinary());
|
||||
|
||||
// dt = TimeUtil.RoundToMultOf2PowLess1s(dt);
|
||||
// long lBin = dt.ToBinary();
|
||||
|
||||
long lSec = dt.Ticks / TimeSpan.TicksPerSecond;
|
||||
// WriteObject(name, lSec);
|
||||
|
||||
byte[] pb = MemUtil.Int64ToBytes(lSec);
|
||||
WriteObject(name, Convert.ToBase64String(pb), false);
|
||||
}
|
||||
else WriteObject(name, TimeUtil.SerializeUtc(value), false);
|
||||
}
|
||||
|
||||
private void WriteObject(string name, string strKeyName,
|
||||
@@ -695,7 +841,7 @@ namespace ModernKeePassLib.Serialization
|
||||
bProtected = m_pwDatabase.MemoryProtection.ProtectNotes;
|
||||
}
|
||||
|
||||
if(bProtected && (m_format != KdbxFormat.PlainXml))
|
||||
if(bProtected && (m_format == KdbxFormat.Default))
|
||||
{
|
||||
m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue);
|
||||
|
||||
@@ -770,11 +916,15 @@ namespace ModernKeePassLib.Serialization
|
||||
m_xmlWriter.WriteEndElement();
|
||||
m_xmlWriter.WriteStartElement(ElemValue);
|
||||
|
||||
string strRef = (bAllowRef ? BinPoolFind(value) : null);
|
||||
if(strRef != null)
|
||||
string strRef = null;
|
||||
if(bAllowRef)
|
||||
{
|
||||
m_xmlWriter.WriteAttributeString(AttrRef, strRef);
|
||||
int iRef = m_pbsBinaries.Find(value);
|
||||
if(iRef >= 0) strRef = iRef.ToString(NumberFormatInfo.InvariantInfo);
|
||||
else { Debug.Assert(false); }
|
||||
}
|
||||
if(strRef != null)
|
||||
m_xmlWriter.WriteAttributeString(AttrRef, strRef);
|
||||
else SubWriteValue(value);
|
||||
|
||||
m_xmlWriter.WriteEndElement(); // ElemValue
|
||||
@@ -783,7 +933,7 @@ namespace ModernKeePassLib.Serialization
|
||||
|
||||
private void SubWriteValue(ProtectedBinary value)
|
||||
{
|
||||
if(value.IsProtected && (m_format != KdbxFormat.PlainXml))
|
||||
if(value.IsProtected && (m_format == KdbxFormat.Default))
|
||||
{
|
||||
m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue);
|
||||
|
||||
@@ -793,18 +943,26 @@ namespace ModernKeePassLib.Serialization
|
||||
}
|
||||
else
|
||||
{
|
||||
if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
|
||||
if(m_pwDatabase.Compression != PwCompressionAlgorithm.None)
|
||||
{
|
||||
m_xmlWriter.WriteAttributeString(AttrCompressed, ValTrue);
|
||||
|
||||
byte[] pbRaw = value.ReadData();
|
||||
byte[] pbCmp = MemUtil.Compress(pbRaw);
|
||||
m_xmlWriter.WriteBase64(pbCmp, 0, pbCmp.Length);
|
||||
|
||||
if(value.IsProtected)
|
||||
{
|
||||
MemUtil.ZeroByteArray(pbRaw);
|
||||
MemUtil.ZeroByteArray(pbCmp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] pbRaw = value.ReadData();
|
||||
m_xmlWriter.WriteBase64(pbRaw, 0, pbRaw.Length);
|
||||
|
||||
if(value.IsProtected) MemUtil.ZeroByteArray(pbRaw);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -824,11 +982,13 @@ namespace ModernKeePassLib.Serialization
|
||||
{
|
||||
m_xmlWriter.WriteStartElement(ElemBinaries);
|
||||
|
||||
foreach(KeyValuePair<string, ProtectedBinary> kvp in m_dictBinPool)
|
||||
ProtectedBinary[] v = m_pbsBinaries.ToArray();
|
||||
for(int i = 0; i < v.Length; ++i)
|
||||
{
|
||||
m_xmlWriter.WriteStartElement(ElemBinary);
|
||||
m_xmlWriter.WriteAttributeString(AttrId, kvp.Key);
|
||||
SubWriteValue(kvp.Value);
|
||||
m_xmlWriter.WriteAttributeString(AttrId,
|
||||
i.ToString(NumberFormatInfo.InvariantInfo));
|
||||
SubWriteValue(v[i]);
|
||||
m_xmlWriter.WriteEndElement();
|
||||
}
|
||||
|
||||
@@ -836,21 +996,18 @@ namespace ModernKeePassLib.Serialization
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public static bool WriteEntries(Stream msOutput, PwDatabase pwDatabase,
|
||||
PwEntry[] vEntries)
|
||||
{
|
||||
return WriteEntries(msOutput, vEntries);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write entries to a stream.
|
||||
/// </summary>
|
||||
/// <param name="msOutput">Output stream to which the entries will be written.</param>
|
||||
/// <param name="vEntries">Entries to serialize.</param>
|
||||
/// <returns>Returns <c>true</c>, if the entries were written successfully
|
||||
/// to the stream.</returns>
|
||||
public static bool WriteEntries(Stream msOutput, PwEntry[] vEntries)
|
||||
{
|
||||
return WriteEntries(msOutput, null, vEntries);
|
||||
}
|
||||
|
||||
public static bool WriteEntries(Stream msOutput, PwDatabase pdContext,
|
||||
PwEntry[] vEntries)
|
||||
{
|
||||
if(msOutput == null) { Debug.Assert(false); return false; }
|
||||
// pdContext may be null
|
||||
if(vEntries == null) { Debug.Assert(false); return false; }
|
||||
|
||||
/* KdbxFile f = new KdbxFile(pwDatabase);
|
||||
f.m_format = KdbxFormat.PlainXml;
|
||||
|
||||
@@ -881,8 +1038,27 @@ namespace ModernKeePassLib.Serialization
|
||||
PwDatabase pd = new PwDatabase();
|
||||
pd.New(new IOConnectionInfo(), new CompositeKey());
|
||||
|
||||
foreach(PwEntry peCopy in vEntries)
|
||||
pd.RootGroup.AddEntry(peCopy.CloneDeep(), true);
|
||||
PwGroup pg = pd.RootGroup;
|
||||
if(pg == null) { Debug.Assert(false); return false; }
|
||||
|
||||
foreach(PwEntry pe in vEntries)
|
||||
{
|
||||
PwUuid pu = pe.CustomIconUuid;
|
||||
if(!pu.Equals(PwUuid.Zero) && (pd.GetCustomIconIndex(pu) < 0))
|
||||
{
|
||||
int i = -1;
|
||||
if(pdContext != null) i = pdContext.GetCustomIconIndex(pu);
|
||||
if(i >= 0)
|
||||
{
|
||||
PwCustomIcon ci = pdContext.CustomIcons[i];
|
||||
pd.CustomIcons.Add(ci);
|
||||
}
|
||||
else { Debug.Assert(pdContext == null); }
|
||||
}
|
||||
|
||||
PwEntry peCopy = pe.CloneDeep();
|
||||
pg.AddEntry(peCopy, true);
|
||||
}
|
||||
|
||||
KdbxFile f = new KdbxFile(pd);
|
||||
f.Save(msOutput, null, KdbxFormat.PlainXml, null);
|
||||
|
@@ -24,7 +24,9 @@ using System.Text;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Diagnostics;
|
||||
|
||||
using System.Security;
|
||||
using Windows.Security.Cryptography;
|
||||
using Windows.Security.Cryptography.Core;
|
||||
#if !KeePassLibSD
|
||||
using System.IO.Compression;
|
||||
#endif
|
||||
@@ -36,8 +38,11 @@ using Windows.Storage;
|
||||
|
||||
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;
|
||||
|
||||
@@ -82,7 +87,10 @@ namespace ModernKeePassLib.Serialization
|
||||
/// 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 = 0x00030001;
|
||||
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;
|
||||
|
||||
@@ -101,6 +109,7 @@ namespace ModernKeePassLib.Serialization
|
||||
|
||||
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";
|
||||
@@ -112,6 +121,7 @@ namespace ModernKeePassLib.Serialization
|
||||
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";
|
||||
@@ -195,6 +205,7 @@ namespace ModernKeePassLib.Serialization
|
||||
private const string ElemStringDictExItem = "Item";
|
||||
|
||||
private PwDatabase m_pwDatabase; // Not null, see constructor
|
||||
private bool m_bUsedOnce = false;
|
||||
|
||||
#if ModernKeePassLib
|
||||
private XmlWriter m_xmlWriter = null;
|
||||
@@ -205,23 +216,23 @@ namespace ModernKeePassLib.Serialization
|
||||
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_pbTransformSeed = null;
|
||||
private byte[] m_pbEncryptionIV = null;
|
||||
private byte[] m_pbProtectedStreamKey = null;
|
||||
private byte[] m_pbStreamStartBytes = null;
|
||||
|
||||
// ArcFourVariant only for compatibility; KeePass will default to a
|
||||
// different (more secure) algorithm when *writing* databases
|
||||
// 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 Dictionary<string, ProtectedBinary> m_dictBinPool =
|
||||
new Dictionary<string, ProtectedBinary>();
|
||||
private ProtectedBinarySet m_pbsBinaries = new ProtectedBinarySet();
|
||||
|
||||
private byte[] m_pbHashOfHeader = null;
|
||||
private byte[] m_pbHashOfFileOnDisk = null;
|
||||
|
||||
private readonly DateTime m_dtNow = DateTime.Now; // Cache current time
|
||||
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
|
||||
@@ -235,12 +246,30 @@ namespace ModernKeePassLib.Serialization
|
||||
CipherID = 2,
|
||||
CompressionFlags = 3,
|
||||
MasterSeed = 4,
|
||||
TransformSeed = 5,
|
||||
TransformRounds = 6,
|
||||
TransformSeed = 5, // KDBX 3.1, for backward compatibility only
|
||||
TransformRounds = 6, // KDBX 3.1, for backward compatibility only
|
||||
EncryptionIV = 7,
|
||||
ProtectedStreamKey = 8,
|
||||
StreamStartBytes = 9,
|
||||
InnerRandomStreamID = 10
|
||||
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
|
||||
@@ -255,6 +284,13 @@ namespace ModernKeePassLib.Serialization
|
||||
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>,
|
||||
@@ -299,64 +335,173 @@ namespace ModernKeePassLib.Serialization
|
||||
}
|
||||
}
|
||||
|
||||
private void BinPoolBuild(PwGroup pgDataSource)
|
||||
private uint GetMinKdbxVersion()
|
||||
{
|
||||
m_dictBinPool = new Dictionary<string, ProtectedBinary>();
|
||||
if(m_uForceVersion != 0) return m_uForceVersion;
|
||||
|
||||
if(pgDataSource == null) { Debug.Assert(false); return; }
|
||||
// See also KeePassKdb2x3.Export (KDBX 3.1 export module)
|
||||
|
||||
EntryHandler eh = delegate(PwEntry pe)
|
||||
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)
|
||||
{
|
||||
foreach(PwEntry peHistory in pe.History)
|
||||
{
|
||||
BinPoolAdd(peHistory.Binaries);
|
||||
}
|
||||
|
||||
BinPoolAdd(pe.Binaries);
|
||||
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;
|
||||
|
||||
pgDataSource.TraverseTree(TraversalMethod.PreOrder, null, eh);
|
||||
return FileVersion32_3; // KDBX 3.1 is sufficient
|
||||
}
|
||||
|
||||
private void BinPoolAdd(ProtectedBinaryDictionary dict)
|
||||
private void ComputeKeys(out byte[] pbCipherKey, int cbCipherKey,
|
||||
out byte[] pbHmacKey64)
|
||||
{
|
||||
foreach(KeyValuePair<string, ProtectedBinary> kvp in dict)
|
||||
byte[] pbCmp = new byte[32 + 32 + 1];
|
||||
try
|
||||
{
|
||||
BinPoolAdd(kvp.Value);
|
||||
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;
|
||||
#if ModernKeePassLib
|
||||
var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
|
||||
var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(pbCmp));
|
||||
CryptographicBuffer.CopyToByteArray(buffer, out pbHmacKey64);
|
||||
#else
|
||||
using(SHA512Managed h = new SHA512Managed())
|
||||
{
|
||||
pbHmacKey64 = h.ComputeHash(pbCmp);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
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 +
|
||||
Environment.NewLine + KLRes.FileNewVerOrPlgReq +
|
||||
Environment.NewLine + "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");
|
||||
}
|
||||
}
|
||||
|
||||
private void BinPoolAdd(ProtectedBinary pb)
|
||||
{
|
||||
if(pb == null) { Debug.Assert(false); return; }
|
||||
|
||||
if(BinPoolFind(pb) != null) return; // Exists already
|
||||
|
||||
m_dictBinPool.Add(m_dictBinPool.Count.ToString(
|
||||
NumberFormatInfo.InvariantInfo), pb);
|
||||
}
|
||||
|
||||
private string BinPoolFind(ProtectedBinary pb)
|
||||
{
|
||||
if(pb == null) { Debug.Assert(false); return null; }
|
||||
|
||||
foreach(KeyValuePair<string, ProtectedBinary> kvp in m_dictBinPool)
|
||||
else
|
||||
{
|
||||
if(pb.Equals(kvp.Value)) return kvp.Key;
|
||||
cbEncKey = 32;
|
||||
cbEncIV = 16;
|
||||
}
|
||||
|
||||
return null;
|
||||
return iCipher;
|
||||
}
|
||||
|
||||
private ProtectedBinary BinPoolGet(string strKey)
|
||||
private Stream EncryptStream(Stream s, ICipherEngine iCipher,
|
||||
byte[] pbKey, int cbIV, bool bEncrypt)
|
||||
{
|
||||
if(strKey == null) { Debug.Assert(false); return null; }
|
||||
byte[] pbIV = (m_pbEncryptionIV ?? MemUtil.EmptyByteArray);
|
||||
if(pbIV.Length != cbIV)
|
||||
{
|
||||
Debug.Assert(false);
|
||||
throw new Exception(KLRes.FileCorrupted);
|
||||
}
|
||||
|
||||
ProtectedBinary pb;
|
||||
if(m_dictBinPool.TryGetValue(strKey, out pb)) return pb;
|
||||
if(bEncrypt)
|
||||
return iCipher.EncryptStream(s, pbKey, pbIV);
|
||||
return iCipher.DecryptStream(s, pbKey, pbIV);
|
||||
}
|
||||
|
||||
return null;
|
||||
private byte[] ComputeHeaderHmac(byte[] pbHeader, byte[] pbKey)
|
||||
{
|
||||
byte[] pbHeaderHmac;
|
||||
byte[] pbBlockKey = HmacBlockStream.GetHmacKey64(
|
||||
pbKey, ulong.MaxValue);
|
||||
#if ModernKeePassLib
|
||||
var h = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha256).CreateHash(CryptographicBuffer.CreateFromByteArray(pbHeader));
|
||||
CryptographicBuffer.CopyToByteArray(h.GetValueAndReset(), out pbHeaderHmac);
|
||||
#else
|
||||
using (HMACSHA256 h = new HMACSHA256(pbBlockKey))
|
||||
{
|
||||
pbHeaderHmac = h.ComputeHash(pbHeader);
|
||||
}
|
||||
#endif
|
||||
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,
|
||||
@@ -368,22 +513,22 @@ namespace ModernKeePassLib.Serialization
|
||||
|
||||
string strPath;
|
||||
int iTry = 1;
|
||||
do
|
||||
{
|
||||
strPath = UrlUtil.EnsureTerminatingSeparator(strSaveDir, false);
|
||||
do
|
||||
{
|
||||
strPath = UrlUtil.EnsureTerminatingSeparator(strSaveDir, false);
|
||||
|
||||
string strExt = UrlUtil.GetExtension(strName);
|
||||
string strDesc = UrlUtil.StripExtension(strName);
|
||||
string strExt = UrlUtil.GetExtension(strName);
|
||||
string strDesc = UrlUtil.StripExtension(strName);
|
||||
|
||||
strPath += strDesc;
|
||||
if (iTry > 1)
|
||||
strPath += " (" + iTry.ToString(NumberFormatInfo.InvariantInfo) +
|
||||
")";
|
||||
strPath += strDesc;
|
||||
if(iTry > 1)
|
||||
strPath += " (" + iTry.ToString(NumberFormatInfo.InvariantInfo) +
|
||||
")";
|
||||
|
||||
if (!string.IsNullOrEmpty(strExt)) strPath += "." + strExt;
|
||||
if(!string.IsNullOrEmpty(strExt)) strPath += "." + strExt;
|
||||
|
||||
++iTry;
|
||||
}
|
||||
++iTry;
|
||||
}
|
||||
#if ModernKeePassLib
|
||||
//while(FileSystem.Current.GetFileFromPathAsync(strPath).Result != null);
|
||||
while (StorageFile.GetFileFromPathAsync(strPath).GetResults() != null);
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
KeePass Password Safe - The Open-Source Password Manager
|
||||
Copyright (C) 2003-2014 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
Copyright (C) 2003-2017 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
|
||||
@@ -45,7 +45,7 @@ namespace ModernKeePassLib.Serialization
|
||||
(@" (" + m_strFormat + @")") : string.Empty) + ".";
|
||||
|
||||
if(m_type == OldFormatType.KeePass1x)
|
||||
str += Environment.NewLine + Environment.NewLine + KLRes.KeePass1xHint;
|
||||
str += Environment.NewLine + KLRes.KeePass1xHint;
|
||||
|
||||
return str;
|
||||
}
|
||||
|
Reference in New Issue
Block a user