Added unit tests for DPAPI backup keys

This commit is contained in:
Michael Grafnetter 2018-07-14 12:17:41 +02:00
parent 1e5f7eef28
commit 285a3720e3
3 changed files with 120 additions and 38 deletions

View File

@ -0,0 +1,77 @@
using DSInternals.Common.Data;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Security.Principal;
namespace DSInternals.Common.Test
{
[TestClass]
public class DPAPIBackupKeyTester
{
[TestMethod]
public void DPAPIBackupKey_RSAKey()
{
// Test vector
byte[] blob = "0200000094040000EC0200000702000000A400005253413200080000010001007F7236400B68C070E3767D331080B5136B7AF17F4A8FC220CA4C73F6BC65EEE194D5015DDFD83D0980463C6218A7B8E1E42947439B5B904028ECA5DEDD57F1C5C8A7A05715894D4690D9C9444D76D75832D4261842B9F897F6A311412C7A8DBE1FAEE513F9960ADB8FB68D00E4D8D7F35F86486FCE3A75773E7F8260829319ACC389277F25C9F26C1BF0902710460FDA8C675650761FF2A40AE67896BEB416FC994826E4B4B6971E2AE42BD901B42D23BB01809AA1C304404972C20D1CB768BE4631E113293E82DE5CE862B010F4EB450D2D92F640A6A115A8A9E21CA6709BE56A4A88A12540821B6D33BBEBEC2B2A5329EB308F9E487729F9438193B45FFDBA31F1A3FFCABF75E85A14B1FF863260F2D5483BB8260E38F2F212988673D2369535257A84E1B5E6CBAB4BB90263E8E18DF751ED99C37CAEC2BFF81F77047284AEEC6472636CDCCC36AE5C5634CA83A67BDB6774BE1F1551C01D2530D7D55C92273A619942E5DD3D67F1FFC7333D8F49B30BF1572EF1E86C592BE4F83943F63DE2AF3259A08B7ADE2446D0B42A260E4DD740F85C06C343ECB152D12F2B3183A384EF2E263442F089219581C43ED91FAC602AC316A5973844DBB1BC6F8F7BE4A02F430466586C31CD0E73904143D0CD8902D7F941BF9A96F81099813EEBD8101060661AE53442C1EECD85E2F9B4E531E3ED466A49B0C3B5134FCF46ED29ABB495D35139F953D39A6A23109A406EFB48CCC545A98DAD9B2ACACFDF5C57527CC006AB419B787E23F837C234FC2A5FFBFEEF369E056EBD7AD5EE53BB87CE7927EBD7345E1C52A0C95BAA449BBDDCE645728FFE0B70A21BF6A4667664FC5AC7F4DB89EC803CD5FA105973580CE6DE55FC5E5FDE2AA2AB191B82A949399473AB1AEEB4C5E900B7FE0C273BFA7F0E0595A5496A844D459F7A23524EA8816F4599115BA991FB4E2C1ADD1C973E4233B21C0B94433ADABACB86D2592CF1CC0CD6CA391EC0274C13BE17D998F99300E4AFB6A59888DA69FD4D2BBBB0885E4064AE49E621C4D24DE617E161C2447F4B1953E90C35D5BE3538633D628B5C2242E9A649EA63877C733CCAC3BF50F7ED657B30BA2836155026B5124BE28B4CA75ECEA01B86EEC37101B2F828484015E356CEB1CF718E57B48047706BCCAFB2E9F5CEC9C60EA6F4A9180429DF9D815CCD4A84C81D2FE8CADFAAF2B97AF7D4B62B4A137D497F70F8CD8B1C8AF049C57142578AB075E971DE63714A82DD74EBD2793F1751E01C3B8C84B11BDB0C79CA59079EF27A4D5754C7AA6B185B04A41609A651E85A00C4194960B02CE39EF3AACF8C2C6F23CF40F780A51EA4DB610FCB2BE63C5BC29EFC14654AAC7231A5FD12647682697376808DCB540DAC1D1E2FB4BDA228495CA9F1987D48DE092BC01D4797651FE18ED0B0BB448F2D262AB4185FA10B9AAB9C66D9551902CCABB1D0473C9B0FF91DD850971ABF7AA2DEA5E4F10A9A93A263A715AB4CBEB7C4B445131E98B3A2117CA09931CFD9AF8DBC0683F0BAB4ED4EE608A121FE3065C223A4ED4634B45BE774BA1C4B49A6C7AFD68F908210177A333C1F7DB6EBE3BC278C80B07339FB10D64A5BE44E805AB5668C3B67A9B6F25AE99BA3B9976EA259308202E8308201D4A00302010202104534203090252F90493AF87CEFE756EC300906052B0E03021D05003021311F301D06035504031316410064006100740075006D002E0063006F006D000000301E170D3136313032363136333630375A170D3137313032363136333630375A3021311F301D06035504031316410064006100740075006D002E0063006F006D00000030820122300D06092A864886F70D01010105000382010F003082010A0282010100BAFD5FB4938143F92977489E8F30EB29532A2BECEBBB336D1B824025A1884A6AE59B70A61CE2A9A815A1A640F6922D0D45EBF410B062E85CDE823E2913E13146BE68B71C0DC272494004C3A19A8001BB232DB401D92BE42A1E97B6B4E4264899FC16B4BE9678E60AA4F21F765056678CDA0F46102790F01B6CF2C9257F2789C3AC19938260827F3E77753ACE6F48865FF3D7D8E4008DB68FDB0A96F913E5AE1FBE8D7A2C4111A3F697F8B9421826D43258D7764D44C9D990464D891557A0A7C8C5F157DDDEA5EC2840905B9B434729E4E1B8A718623C4680093DD8DF5D01D594E1EE65BCF6734CCA20C28F4A7FF17A6B13B58010337D76E370C0680B4036727F0203010001811100EC56E7EF7CF83A49902F259030203445821100EC56E7EF7CF83A49902F259030203445300906052B0E03021D050003820101009FDA525ABEC73C47FAC9E77588D36F8D505B2B0558FF9D2B0EE13295974598CB7003836C037493218F032AD7CFED40DA6F99A7620A9CE0517752A18D56D7D6F47969FDFED28DE8C982E499924C5F8843528C099C8D183D6DC4D77283B9700773084B7A4F11CDF4A31A32B198B824C904626D4C3C192133D18AA8C376C1E49C342FEC949E3C8D1CB1C6E9E914015AB61B59BD7CA5E9C61CCD02D65D09DC511D7F0F8B7E63501498F942DBE564139848BE3C15931CC85D599E9CC1638E4E1DD65F25F2CACD2F40542341E2438E4762DFE80A96851EAB51D0E792CFFEA855E7563B770F8FD31AB288FDFA60A9B4B785A32AC2D5644FD32241BB340406DAF38B45BE".HexToBinary();
string distinguishedName = "CN=BCKUPKEY_efe756ec-f87c-493a-902f-259030203445 Secret,CN=System,DC=Adatum,DC=com";
// Parse the input
var key = new DPAPIBackupKey(distinguishedName, blob);
// Validate the results
Assert.AreEqual(DPAPIBackupKeyType.RSAKey, key.Type);
Assert.AreEqual(Guid.Parse("efe756ec-f87c-493a-902f-259030203445"), key.KeyId);
Assert.AreEqual("ntds_capi_efe756ec-f87c-493a-902f-259030203445.pvk", key.FilePath);
}
[TestMethod]
public void DPAPIBackupKey_LegacyKey()
{
// Test vector
byte[] blob = "01000000B97B2D146BB28CB7BAE5E6D31079AA88DAE5A771844FFC7E27EC55633AE513EBDDE29BE619E4D0CF7308DC360F4B63ED87CF9CBC382A56B924C1ADFF4958100AE441B79B83BB8F5649E18C13532831F892C86FE47D71C3AC554A029F3A61841D93CF8208429FE130F059AA40D368B770CDD4BF1EB17876109DCE0F4E6087658994F3EDF5E1589F3C8484370A351741D1BDDF2BA14444E2D822A4A085606F09E341D407969F742D498C462E9147CFBEDF035CF4B49413860C402C4139EACD81C7508EEE1F061D3C40F6B811E89178C617CD598D76D0A776F7D3A31BD8124EDF55B197DBBE5F30416FC62B6A3327148DDC23312D61FEC0CB3E572EC32346BC9A91".HexToBinary();
string distinguishedName = "CN=BCKUPKEY_585c52d5-1778-4d43-9d7b-c22b4f7e5fa4 Secret,CN=System,DC=Adatum,DC=com";
// Parse the input
var key = new DPAPIBackupKey(distinguishedName, blob);
// Validate the results
Assert.AreEqual(DPAPIBackupKeyType.LegacyKey, key.Type);
Assert.AreEqual(Guid.Parse("585c52d5-1778-4d43-9d7b-c22b4f7e5fa4"), key.KeyId);
Assert.AreEqual("ntds_legacy_585c52d5-1778-4d43-9d7b-c22b4f7e5fa4.key", key.FilePath);
}
[TestMethod]
public void DPAPIBackupKey_PreferredRSAKeyPointer()
{
// Test vector
byte[] blob = "ec56e7ef7cf83a49902f259030203445".HexToBinary();
string distinguishedName = "CN=BCKUPKEY_PREFERRED Secret,CN=System,DC=Adatum,DC=com";
// Parse the input
var key = new DPAPIBackupKey(distinguishedName, blob);
// Validate the results
Assert.AreEqual(DPAPIBackupKeyType.PreferredRSAKeyPointer, key.Type);
Assert.AreEqual(Guid.Parse("efe756ec-f87c-493a-902f-259030203445"), key.KeyId);
Assert.IsNull(key.FilePath);
Assert.IsNull(key.KiwiCommand);
}
[TestMethod]
public void DPAPIBackupKey_PreferredLegacyKeyPointer()
{
// Test vector
byte[] blob = "d5525c587817434d9d7bc22b4f7e5fa4".HexToBinary();
string distinguishedName = "CN=BCKUPKEY_P Secret,CN=System,DC=Adatum,DC=com";
// Parse the input
var key = new DPAPIBackupKey(distinguishedName, blob);
// Validate the results
Assert.AreEqual(DPAPIBackupKeyType.PreferredLegacyKeyPointer, key.Type);
Assert.AreEqual(Guid.Parse("585c52d5-1778-4d43-9d7b-c22b4f7e5fa4"), key.KeyId);
Assert.IsNull(key.FilePath);
Assert.IsNull(key.KiwiCommand);
}
}
}

View File

@ -54,6 +54,7 @@
</Choose>
<ItemGroup>
<Compile Include="ByteArrayExtensionsTester.cs" />
<Compile Include="DPAPIBackupKeyTester.cs" />
<Compile Include="Cryptography\GPPrefPwdObfuscatorTester.cs" />
<Compile Include="Cryptography\LMHashTester.cs" />
<Compile Include="Cryptography\NTHashTester.cs" />

View File

@ -24,7 +24,6 @@
private const string RSAP12FileNameFormat = "ntds_capi_{0}.pfx";
private const string LegacyKeyFileNameFormat = "ntds_legacy_{0}.key";
private const string UnknownKeyFileNameFormat = "ntds_unknown_{0}_{1}.key";
private const string DummyNamingContext = "DC=unknown,DC=int";
private const string KiwiCommandFormat = "REM Add this parameter to at least the first dpapi::masterkey command: /pvk:\"{0}\"";
private const int PVKHeaderSize = 6 * sizeof(int);
private const uint PVKHeaderMagic = 0xb0b5f11e;
@ -41,50 +40,19 @@
// Decrypt the secret value
byte[] encryptedSecret;
dsObject.ReadAttribute(CommonDirectoryAttributes.CurrentValue, out encryptedSecret);
this.Data = pek.DecryptSecret(encryptedSecret);
byte[] decryptedBlob = pek.DecryptSecret(encryptedSecret);
// Parse DN to get key ID or pointer type:
this.DistinguishedName = dsObject.DistinguishedName;
var keyName = GetSecretNameFromDN(this.DistinguishedName);
switch(keyName)
{
case null:
// We could not parse the DN, so exit with Unknown as the key type
this.Type = DPAPIBackupKeyType.Unknown;
break;
case PreferredRSAKeyPointerName:
this.Type = DPAPIBackupKeyType.PreferredRSAKeyPointer;
// Interpret the raw data as Guid
this.KeyId = new Guid(this.Data);
break;
case PreferredLegacyKeyPointerName:
this.Type = DPAPIBackupKeyType.PreferredLegacyKeyPointer;
// Interpret the raw data as Guid
this.KeyId = new Guid(this.Data);
break;
default:
// Actual Key, so we parse its Guid and version
this.KeyId = Guid.Parse(keyName);
this.Type = (DPAPIBackupKeyType)BitConverter.ToInt32(this.Data, KeyVersionOffset);
break;
}
// Initialize properties
this.Initialize(dsObject.DistinguishedName, decryptedBlob);
}
// TODO: Create unit tests for DPAPIBackupKey
public DPAPIBackupKey(Guid id, byte[] blob)
public DPAPIBackupKey(string distinguishedName, byte[] blob)
{
// Validate the input
Validator.AssertNotNullOrWhiteSpace(distinguishedName, "distinguishedName");
Validator.AssertNotNull(blob, "blob");
Validator.AssertMinLength(blob, KeyVersionSize, "blob");
// Process the parameters
this.KeyId = id;
this.Data = blob;
this.Type = (DPAPIBackupKeyType)BitConverter.ToInt32(this.Data, KeyVersionOffset);
// Generate some dummy distinguished name
this.DistinguishedName = String.Format(BackupKeyDNFormat, this.KeyId, DummyNamingContext);
this.Initialize(distinguishedName, blob);
}
public override string FilePath
@ -185,6 +153,42 @@
}
}
/// <summary>
/// Object initializer that is shared between multiple constructors.
/// </summary>
/// <param name="distinguishedName">Distinguished name of the DPAPI backup key object.</param>
/// <param name="blob">Decrypted data blob.</param>
private void Initialize(string distinguishedName, byte[] blob)
{
this.DistinguishedName = distinguishedName;
this.Data = blob;
// Parse DN to get key ID or pointer type:
var keyName = GetSecretNameFromDN(distinguishedName);
switch (keyName)
{
case null:
// We could not parse the DN, so exit with Unknown as the key type
this.Type = DPAPIBackupKeyType.Unknown;
break;
case PreferredRSAKeyPointerName:
this.Type = DPAPIBackupKeyType.PreferredRSAKeyPointer;
// Interpret the raw data as Guid
this.KeyId = new Guid(blob);
break;
case PreferredLegacyKeyPointerName:
this.Type = DPAPIBackupKeyType.PreferredLegacyKeyPointer;
// Interpret the raw data as Guid
this.KeyId = new Guid(blob);
break;
default:
// Actual Key, so we parse its Guid and version
this.KeyId = Guid.Parse(keyName);
this.Type = (DPAPIBackupKeyType)BitConverter.ToInt32(blob, KeyVersionOffset);
break;
}
}
public static string GetKeyDN(Guid keyId, string domainDN)
{
return String.Format(BackupKeyDNFormat, keyId, domainDN);