Files
modernkeepass/ModernKeePassLib/Keys/CompositeKey.cs

374 lines
12 KiB
C#
Raw Normal View History

/*
KeePass Password Safe - The Open-Source Password Manager
2017-09-22 15:40:24 +02:00
Copyright (C) 2003-2014 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.Text;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
2017-09-22 15:40:24 +02:00
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Security.Cryptography.Core;
2017-09-22 15:40:24 +02:00
using Windows.Security.ExchangeActiveSyncProvisioning;
#if PCL
using Windows.Security.Cryptography;
#else
using System.Security.Cryptography;
#endif
using ModernKeePassLib.Resources;
using ModernKeePassLib.Security;
using ModernKeePassLib.Utility;
namespace ModernKeePassLib.Keys
{
2017-09-22 15:40:24 +02:00
/// <summary>
/// Represents a key. A key can be build up using several user key data sources
/// like a password, a key file, the currently logged on user credentials,
/// the current computer ID, etc.
/// </summary>
public sealed class CompositeKey
{
private List<IUserKey> m_vUserKeys = new List<IUserKey>();
/// <summary>
/// List of all user keys contained in the current composite key.
/// </summary>
public IEnumerable<IUserKey> UserKeys
{
get { return m_vUserKeys; }
}
2017-09-22 15:40:24 +02:00
public uint UserKeyCount
{
get { return (uint) m_vUserKeys.Count; }
}
2017-09-22 15:40:24 +02:00
/// <summary>
/// Construct a new, empty key object.
/// </summary>
public CompositeKey()
{
}
2017-09-22 15:40:24 +02:00
// /// <summary>
// /// Deconstructor, clears up the key.
// /// </summary>
// ~CompositeKey()
// {
// Clear();
// }
// /// <summary>
// /// Clears the key. This function also erases all previously stored
// /// user key data objects.
// /// </summary>
// public void Clear()
// {
// foreach(IUserKey pKey in m_vUserKeys)
// pKey.Clear();
// m_vUserKeys.Clear();
// }
/// <summary>
/// Add a user key.
/// </summary>
/// <param name="pKey">User key to add.</param>
public void AddUserKey(IUserKey pKey)
{
Debug.Assert(pKey != null);
if (pKey == null) throw new ArgumentNullException("pKey");
m_vUserKeys.Add(pKey);
}
2017-09-22 15:40:24 +02:00
/// <summary>
/// Remove a user key.
/// </summary>
/// <param name="pKey">User key to remove.</param>
/// <returns>Returns <c>true</c> if the key was removed successfully.</returns>
public bool RemoveUserKey(IUserKey pKey)
{
Debug.Assert(pKey != null);
if (pKey == null) throw new ArgumentNullException("pKey");
Debug.Assert(m_vUserKeys.IndexOf(pKey) >= 0);
return m_vUserKeys.Remove(pKey);
}
2017-09-22 15:40:24 +02:00
#if !PCL && !KeePassRT /// <summary>
/// Test whether the composite key contains a specific type of
/// user keys (password, key file, ...). If at least one user
/// key of that type is present, the function returns <c>true</c>.
/// </summary>
/// <param name="tUserKeyType">User key type.</param>
/// <returns>Returns <c>true</c>, if the composite key contains
/// a user key of the specified type.</returns>
public bool ContainsType(Type tUserKeyType)
{
Debug.Assert(tUserKeyType != null);
if(tUserKeyType == null) throw new ArgumentNullException("tUserKeyType");
foreach(IUserKey pKey in m_vUserKeys)
{
if(tUserKeyType.IsInstanceOfType(pKey))
return true;
}
return false;
}
/// <summary>
/// Get the first user key of a specified type.
/// </summary>
/// <param name="tUserKeyType">Type of the user key to get.</param>
/// <returns>Returns the first user key of the specified type
/// or <c>null</c> if no key of that type is found.</returns>
public IUserKey GetUserKey(Type tUserKeyType)
{
Debug.Assert(tUserKeyType != null);
if(tUserKeyType == null) throw new ArgumentNullException("tUserKeyType");
foreach(IUserKey pKey in m_vUserKeys)
{
if(tUserKeyType.IsInstanceOfType(pKey))
return pKey;
}
return null;
}
2017-09-22 15:40:24 +02:00
#endif
2017-09-22 15:40:24 +02:00
/// <summary>
/// Creates the composite key from the supplied user key sources (password,
/// key file, user account, computer ID, etc.).
/// </summary>
private byte[] CreateRawCompositeKey32()
{
ValidateUserKeys();
// Concatenate user key data
MemoryStream ms = new MemoryStream();
foreach (IUserKey pKey in m_vUserKeys)
{
ProtectedBinary b = pKey.KeyData;
if (b != null)
{
byte[] pbKeyData = b.ReadData();
ms.Write(pbKeyData, 0, pbKeyData.Length);
MemUtil.ZeroByteArray(pbKeyData);
}
}
2017-09-22 15:40:24 +02:00
#if PCL
var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
var pbHash = sha256.HashData(ms.ToArray().AsBuffer()).ToArray();
#else
SHA256Managed sha256 = new SHA256Managed();
byte[] pbHash = sha256.ComputeHash(ms.ToArray());
#endif
ms.Dispose();
return pbHash;
}
2017-09-22 15:40:24 +02:00
public bool EqualsValue(CompositeKey ckOther)
{
if (ckOther == null) throw new ArgumentNullException("ckOther");
2017-09-22 15:40:24 +02:00
byte[] pbThis = CreateRawCompositeKey32();
byte[] pbOther = ckOther.CreateRawCompositeKey32();
bool bResult = MemUtil.ArraysEqual(pbThis, pbOther);
Array.Clear(pbOther, 0, pbOther.Length);
Array.Clear(pbThis, 0, pbThis.Length);
2017-09-22 15:40:24 +02:00
return bResult;
}
2017-09-22 15:40:24 +02:00
/// <summary>
/// Generate a 32-bit wide key out of the composite key.
/// </summary>
/// <param name="pbKeySeed32">Seed used in the key transformation
/// rounds. Must be a byte array containing exactly 32 bytes; must
/// not be null.</param>
/// <param name="uNumRounds">Number of key transformation rounds.</param>
/// <returns>Returns a protected binary object that contains the
/// resulting 32-bit wide key.</returns>
public ProtectedBinary GenerateKey32(byte[] pbKeySeed32, ulong uNumRounds)
{
Debug.Assert(pbKeySeed32 != null);
if (pbKeySeed32 == null) throw new ArgumentNullException("pbKeySeed32");
Debug.Assert(pbKeySeed32.Length == 32);
if (pbKeySeed32.Length != 32) throw new ArgumentException("pbKeySeed32");
byte[] pbRaw32 = CreateRawCompositeKey32();
if ((pbRaw32 == null) || (pbRaw32.Length != 32))
{
Debug.Assert(false);
return null;
}
2017-09-22 15:40:24 +02:00
byte[] pbTrf32 = TransformKey(pbRaw32, pbKeySeed32, uNumRounds);
if ((pbTrf32 == null) || (pbTrf32.Length != 32))
{
Debug.Assert(false);
return null;
}
2017-09-22 15:40:24 +02:00
ProtectedBinary pbRet = new ProtectedBinary(true, pbTrf32);
MemUtil.ZeroByteArray(pbTrf32);
MemUtil.ZeroByteArray(pbRaw32);
2017-09-22 15:40:24 +02:00
return pbRet;
}
2017-09-22 15:40:24 +02:00
private void ValidateUserKeys()
{
#if !PCL
int nAccounts = 0;
foreach(IUserKey uKey in m_vUserKeys)
{
if(uKey is KcpUserAccount)
++nAccounts;
}
if(nAccounts >= 2)
{
Debug.Assert(false);
throw new InvalidOperationException();
}
2017-09-22 15:40:24 +02:00
#endif
}
2017-09-22 15:40:24 +02:00
/// <summary>
/// Transform the current key <c>uNumRounds</c> times.
/// </summary>
/// <param name="pbOriginalKey32">The original key which will be transformed.
/// This parameter won't be modified.</param>
/// <param name="pbKeySeed32">Seed used for key transformations. Must not
/// be <c>null</c>. This parameter won't be modified.</param>
/// <param name="uNumRounds">Transformation count.</param>
/// <returns>256-bit transformed key.</returns>
private static byte[] TransformKey(byte[] pbOriginalKey32, byte[] pbKeySeed32,
ulong uNumRounds)
{
Debug.Assert((pbOriginalKey32 != null) && (pbOriginalKey32.Length == 32));
if (pbOriginalKey32 == null) throw new ArgumentNullException("pbOriginalKey32");
if (pbOriginalKey32.Length != 32) throw new ArgumentException();
Debug.Assert((pbKeySeed32 != null) && (pbKeySeed32.Length == 32));
if (pbKeySeed32 == null) throw new ArgumentNullException("pbKeySeed32");
if (pbKeySeed32.Length != 32) throw new ArgumentException();
byte[] pbNewKey = new byte[32];
Array.Copy(pbOriginalKey32, pbNewKey, pbNewKey.Length);
#if !PCL // Try to use the native library first
if(NativeLib.TransformKey256(pbNewKey, pbKeySeed32, uNumRounds))
return (new SHA256Managed()).ComputeHash(pbNewKey);
#endif
2017-09-22 15:40:24 +02:00
if (TransformKeyManaged(pbNewKey, pbKeySeed32, uNumRounds) == false)
return null;
2017-09-22 15:40:24 +02:00
#if PCL
var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
return sha256.HashData(pbNewKey.AsBuffer()).ToArray();
#else
SHA256Managed sha256 = new SHA256Managed();
return sha256.ComputeHash(pbNewKey);
#endif
}
2017-09-22 15:40:24 +02:00
public static bool TransformKeyManaged(byte[] pbNewKey32, byte[] pbKeySeed32,
ulong uNumRounds)
{
#if KeePassRT
KeyParameter kp = new KeyParameter(pbKeySeed32);
AesEngine aes = new AesEngine();
aes.Init(true, kp);
2017-09-22 15:40:24 +02:00
for(ulong i = 0; i < uNumRounds; ++i)
{
2017-09-22 15:40:24 +02:00
aes.ProcessBlock(pbNewKey32, 0, pbNewKey32, 0);
aes.ProcessBlock(pbNewKey32, 16, pbNewKey32, 16);
}
2017-09-22 15:40:24 +02:00
#elif PCL
var aes = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithmNames.AesEcb);
var key = aes.CreateSymmetricKey(pbKeySeed32.AsBuffer());
//var iCrypt = CryptographicEngine.CreateEncryptor(key);
#else
byte[] pbIV = new byte[16];
Array.Clear(pbIV, 0, pbIV.Length);
RijndaelManaged r = new RijndaelManaged();
if(r.BlockSize != 128) // AES block size
{
Debug.Assert(false);
r.BlockSize = 128;
}
r.IV = pbIV;
r.Mode = CipherMode.ECB;
r.KeySize = 256;
2017-09-22 15:40:24 +02:00
r.Key = pbKeySeed32;
ICryptoTransform iCrypt = r.CreateEncryptor();
2017-09-22 15:40:24 +02:00
#endif
2017-09-22 15:40:24 +02:00
// !iCrypt.CanReuseTransform -- doesn't work with Mono
/*if ((iCrypt == null) || (iCrypt.InputBlockSize != 16) ||
(iCrypt.OutputBlockSize != 16))*/
if (aes.BlockLength != 16)
{
Debug.Assert(false, "Invalid ICryptoTransform.");
/*Debug.Assert((iCrypt.InputBlockSize == 16), "Invalid input block size!");
Debug.Assert((iCrypt.OutputBlockSize == 16), "Invalid output block size!");*/
Debug.Assert(aes.BlockLength == 16, "Invalid input block size!");
return false;
}
2017-09-22 15:40:24 +02:00
for (ulong i = 0; i < uNumRounds; ++i)
{
/*iCrypt.TransformBlock(pbNewKey32, 0, 16, pbNewKey32, 0);
iCrypt.TransformBlock(pbNewKey32, 16, 16, pbNewKey32, 16);*/
}
2017-09-22 15:40:24 +02:00
return true;
}
2017-09-22 15:40:24 +02:00
}
2017-09-22 15:40:24 +02:00
public sealed class InvalidCompositeKeyException : Exception
{
public override string Message
{
get
{
2017-09-22 15:40:24 +02:00
return KLRes.InvalidCompositeKey + Environment.NewLine
+ Environment.NewLine + KLRes.InvalidCompositeKeyHint;
}
}
/// <summary>
/// Construct a new invalid composite key exception.
/// </summary>
public InvalidCompositeKeyException()
{
}
}
}