KDS KDF L0
This commit is contained in:
parent
b387697c20
commit
13385b8716
|
@ -26,6 +26,7 @@
|
|||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<Optimize>true</Optimize>
|
||||
|
@ -33,6 +34,7 @@
|
|||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="CBOR, Version=4.5.2.0, Culture=neutral, PublicKeyToken=9cd62db60ea5554c, processorArchitecture=MSIL">
|
||||
|
@ -76,6 +78,7 @@
|
|||
<Compile Include="KerberosCredentialTester.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Cryptography\SecureStringExtensionsTester.cs" />
|
||||
<Compile Include="ProtectionKeyIdentifierTester.cs" />
|
||||
<Compile Include="SearchableDeviceKeyTester.cs" />
|
||||
<Compile Include="SecurityIdentifierExtensionsTester.cs" />
|
||||
<Compile Include="KeyCredentialTester.cs" />
|
||||
|
|
|
@ -52,5 +52,21 @@ namespace DSInternals.Common.Test
|
|||
KdsRootKey.ParseSecretAgreementParameters(new byte[0] { });
|
||||
throw new AssertInconclusiveException();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ComputeL0Key_Vector1()
|
||||
{
|
||||
byte[] l0Key = KdsRootKey.ComputeL0Key(
|
||||
Guid.Parse("7dc95c96-fa85-183a-dff5-f70696bf0b11"),
|
||||
"814ad2f3928ff96d3650487967392feab3924f3d0dff8629d46a723640101cff8ca2cbd6aba40805cf03b380803b27837d80663eb4d18fd4cec414ebb2271fe2".HexToBinary(),
|
||||
"SP800_108_CTR_HMAC",
|
||||
"00000000010000000e000000000000005300480041003500310032000000".HexToBinary(),
|
||||
361
|
||||
);
|
||||
|
||||
Assert.AreEqual(
|
||||
"76d7341bbf6f85f439a14d3f68c6de31a83d2c55b1371c9c122f5b6f0eccff282973da43349da2b21a0a89b050b49e9ace951323f27638ccbfce8b6a0ead782b",
|
||||
l0Key.ToHex());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
using DSInternals.Common.Data;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
|
||||
namespace DSInternals.Common.Test
|
||||
{
|
||||
[TestClass]
|
||||
public class ProtectionKeyIdentifierTester
|
||||
{
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void ProtectionKeyIdentifier_Null()
|
||||
{
|
||||
var obj = new ProtectionKeyIdentifier(null);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ProtectionKeyIdentifier_Empty()
|
||||
{
|
||||
var obj = new ProtectionKeyIdentifier(new byte[0]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ProtectionKeyIdentifier_Truncated1()
|
||||
{
|
||||
// Only contains version
|
||||
byte[] blob = "01000000".HexToBinary();
|
||||
var parsed = new ProtectionKeyIdentifier(blob);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void ProtectionKeyIdentifier_Truncated2()
|
||||
{
|
||||
// The last byte has been trimmed
|
||||
byte[] blob = "010000004b44534b02000000690100001a00000018000000965cc97d85fa3a18dff5f70696bf0b1100000000180000001800000063006f006e0074006f0073006f002e0063006f006d00000063006f006e0074006f0073006f002e0063006f006d0000".HexToBinary();
|
||||
var parsed = new ProtectionKeyIdentifier(blob);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProtectionKeyIdentifier_Parse()
|
||||
{
|
||||
byte[] blob = "010000004b44534b02000000690100001a00000018000000965cc97d85fa3a18dff5f70696bf0b1100000000180000001800000063006f006e0074006f0073006f002e0063006f006d00000063006f006e0074006f0073006f002e0063006f006d000000".HexToBinary();
|
||||
var parsed = new ProtectionKeyIdentifier(blob);
|
||||
Assert.AreEqual("contoso.com", parsed.DomainName);
|
||||
Assert.AreEqual("contoso.com", parsed.ForestName);
|
||||
Assert.AreEqual("7dc95c96-fa85-183a-dff5-f70696bf0b11", parsed.RootKeyId.ToString());
|
||||
Assert.AreEqual(361, parsed.L0KeyId);
|
||||
Assert.AreEqual(26, parsed.L1KeyId);
|
||||
Assert.AreEqual(24, parsed.L2KeyId);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -67,6 +67,7 @@
|
|||
<Compile Include="Cryptography\Pbkdf2.cs" />
|
||||
<Compile Include="Cryptography\PrivateKeyEncryptionType.cs" />
|
||||
<Compile Include="Data\DNWithBinary.cs" />
|
||||
<Compile Include="Data\DPAPI\ProtectionKeyIdentifier.cs" />
|
||||
<Compile Include="Data\DPAPI\DPAPIObject.cs" />
|
||||
<Compile Include="Data\DPAPI\KdsRootKey.cs" />
|
||||
<Compile Include="Data\DPAPI\RoamedCredential.cs" />
|
||||
|
@ -89,6 +90,7 @@
|
|||
<Compile Include="Data\Hello\KeyMaterialFido.cs" />
|
||||
<Compile Include="Data\Hello\VolumeType.cs" />
|
||||
<Compile Include="Data\Hello\KeyUsage.cs" />
|
||||
<Compile Include="Data\Principals\GroupManagedServiceAccount.cs" />
|
||||
<Compile Include="Data\Principals\SupportedEncryptionTypes.cs" />
|
||||
<Compile Include="Extensions\ByteArrayExtensions.cs" />
|
||||
<Compile Include="Cryptography\Crc32.cs" />
|
||||
|
@ -130,6 +132,7 @@
|
|||
<Compile Include="Extensions\NTAccountExtensions.cs" />
|
||||
<Compile Include="Extensions\RSAExtensions.cs" />
|
||||
<Compile Include="Interop\CryptoBuffer.cs" />
|
||||
<Compile Include="Interop\Enums\GroupKeyLevel.cs" />
|
||||
<Compile Include="Interop\Enums\NetCancelOptions.cs" />
|
||||
<Compile Include="Interop\Enums\NetConnectOptions.cs" />
|
||||
<Compile Include="Interop\Enums\NetResourceDisplayType.cs" />
|
||||
|
@ -145,6 +148,7 @@
|
|||
<Compile Include="Interop\SafeOemStringPointer.cs" />
|
||||
<Compile Include="Interop\Enums\Win32ErrorCode.cs" />
|
||||
<Compile Include="Interop\Enums\NtStatus.cs" />
|
||||
<Compile Include="Interop\SafeSidKeyProviderHandle.cs" />
|
||||
<Compile Include="Interop\SafeUnicodeSecureStringPointer.cs" />
|
||||
<Compile Include="Interop\OemString.cs" />
|
||||
<Compile Include="Interop\SecureUnicodeString.cs" />
|
||||
|
@ -183,4 +187,4 @@
|
|||
<Folder Include="Data\FIDO2\" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
</Project>
|
|
@ -2,6 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using DSInternals.Common.Interop;
|
||||
|
||||
namespace DSInternals.Common.Data
|
||||
{
|
||||
|
@ -10,11 +11,16 @@ namespace DSInternals.Common.Data
|
|||
/// </summary>
|
||||
public class KdsRootKey
|
||||
{
|
||||
private const int L0KeyIteration = 1;
|
||||
private const int L1KeyIteration = 32;
|
||||
private const int L2KeyIteration = 32;
|
||||
|
||||
private int? version;
|
||||
private DateTime ?creationTime;
|
||||
private DateTime ?effectiveTime;
|
||||
private byte[] privateKey;
|
||||
private string kdfAlgorithmName;
|
||||
private byte[] rawKdfParameters;
|
||||
private string secretAgreementAlgorithmName;
|
||||
private byte[] secretAgreementAlgorithmParam;
|
||||
private int? privateKeyLength;
|
||||
|
@ -53,9 +59,7 @@ namespace DSInternals.Common.Data
|
|||
dsObject.ReadAttribute(CommonDirectoryAttributes.KdsKdfAlgorithm, out this.kdfAlgorithmName);
|
||||
|
||||
// KDF algorithm parameters (only 1 in current implementation)
|
||||
byte[] rawKdfParams;
|
||||
dsObject.ReadAttribute(CommonDirectoryAttributes.KdsKdfParameters, out rawKdfParams);
|
||||
this.KdfParameters = ParseKdfParameters(rawKdfParams);
|
||||
dsObject.ReadAttribute(CommonDirectoryAttributes.KdsKdfParameters, out this.rawKdfParameters);
|
||||
|
||||
// Secret agreement algorithm
|
||||
dsObject.ReadAttribute(CommonDirectoryAttributes.KdsSecretAgreementAlgorithm, out this.secretAgreementAlgorithmName);
|
||||
|
@ -148,8 +152,10 @@ namespace DSInternals.Common.Data
|
|||
/// </summary>
|
||||
public Dictionary<uint, string> KdfParameters
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
get
|
||||
{
|
||||
return ParseKdfParameters(this.rawKdfParameters);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -196,6 +202,53 @@ namespace DSInternals.Common.Data
|
|||
}
|
||||
}
|
||||
|
||||
public byte[] ComputeL0Key(int l0KeyId)
|
||||
{
|
||||
return ComputeL0Key(
|
||||
this.KeyId,
|
||||
this.KeyValue,
|
||||
this.KdfAlgorithm,
|
||||
this.rawKdfParameters,
|
||||
l0KeyId
|
||||
);
|
||||
}
|
||||
|
||||
public static byte[] ComputeL0Key(
|
||||
Guid kdsRootKeyId,
|
||||
byte[] kdsRootKey,
|
||||
string kdfAlgorithm,
|
||||
byte[] kdfParameters,
|
||||
int l0KeyId)
|
||||
{
|
||||
var result = NativeMethods.GenerateKDFContext(
|
||||
kdsRootKeyId,
|
||||
l0KeyId,
|
||||
-1,
|
||||
-1,
|
||||
GroupKeyLevel.L0,
|
||||
out byte[] kdfContext,
|
||||
out int counterOffset
|
||||
);
|
||||
|
||||
Validator.AssertSuccess(result);
|
||||
|
||||
result = NativeMethods.GenerateDerivedKey(
|
||||
kdfAlgorithm,
|
||||
kdfParameters,
|
||||
kdsRootKey,
|
||||
kdfContext,
|
||||
null,
|
||||
null,
|
||||
L0KeyIteration,
|
||||
out byte[] l0Key,
|
||||
out string invalidAtribute
|
||||
);
|
||||
|
||||
Validator.AssertSuccess(result);
|
||||
|
||||
return l0Key;
|
||||
}
|
||||
|
||||
public static Dictionary<uint,string> ParseKdfParameters(byte[] blob)
|
||||
{
|
||||
if(blob == null || blob.Length == 0)
|
||||
|
@ -259,4 +312,4 @@ namespace DSInternals.Common.Data
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
namespace DSInternals.Common.Data
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// The Protection Key Identifier data structure is used to store metadata about keys used to cryptographically wrap DPAPI-NG encryption keys and to derive managed passwords.
|
||||
/// </summary>
|
||||
/// <see>https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dnsp/98a575da-ca48-4afd-ba79-f77a8bed4e4e</see>
|
||||
public class ProtectionKeyIdentifier
|
||||
{
|
||||
private const string KdsKeyMagic = "KDSK";
|
||||
private const int ExpectedVersion = 1;
|
||||
private const int StructureHeaderLength = 9 * sizeof(int) + 16;
|
||||
|
||||
public int L0KeyId
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public int L1KeyId
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public int L2KeyId
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Guid RootKeyId
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string DomainName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string ForestName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public ProtectionKeyIdentifier(byte[] blob)
|
||||
{
|
||||
Validator.AssertMinLength(blob, StructureHeaderLength, nameof(blob));
|
||||
|
||||
using (Stream stream = new MemoryStream(blob))
|
||||
{
|
||||
using (BinaryReader reader = new BinaryReader(stream))
|
||||
{
|
||||
// Version must be 0x00000001
|
||||
int version = reader.ReadInt32();
|
||||
Validator.AssertEquals(ExpectedVersion, version, nameof(version));
|
||||
|
||||
// Magic must be 0x4B53444B
|
||||
byte[] binaryMagic = reader.ReadBytes(sizeof(int));
|
||||
string magic = ASCIIEncoding.ASCII.GetString(binaryMagic);
|
||||
Validator.AssertEquals(KdsKeyMagic, magic, nameof(magic));
|
||||
|
||||
// Flags must be 0x00000000
|
||||
int flags = reader.ReadInt32();
|
||||
|
||||
// An L0 index
|
||||
this.L0KeyId = reader.ReadInt32();
|
||||
|
||||
// An L1 index
|
||||
this.L1KeyId = reader.ReadInt32();
|
||||
|
||||
// An L2 index
|
||||
this.L2KeyId = reader.ReadInt32();
|
||||
|
||||
// A root key identifier
|
||||
byte[] binaryRootKeyId = reader.ReadBytes(Marshal.SizeOf(typeof(Guid)));
|
||||
this.RootKeyId = new Guid(binaryRootKeyId);
|
||||
|
||||
// Variable data lengths
|
||||
int additionalInfoLength = reader.ReadInt32();
|
||||
int domainNameLength = reader.ReadInt32();
|
||||
int forestNameLength = reader.ReadInt32();
|
||||
|
||||
// Validate variable data length
|
||||
int expectedLength = StructureHeaderLength + additionalInfoLength + domainNameLength + forestNameLength;
|
||||
Validator.AssertMinLength(blob, expectedLength, nameof(blob));
|
||||
|
||||
if (additionalInfoLength > 0)
|
||||
{
|
||||
// Additional info used in key derivation
|
||||
byte[] additionalInfo = reader.ReadBytes(additionalInfoLength);
|
||||
}
|
||||
|
||||
if(domainNameLength > 0)
|
||||
{
|
||||
// DNS-style name of the Active Directory domain in which this identifier was created.
|
||||
byte[] binaryDomainName = reader.ReadBytes(domainNameLength);
|
||||
// Trim \0
|
||||
this.DomainName = Encoding.Unicode.GetString(binaryDomainName, 0, binaryDomainName.Length - sizeof(char));
|
||||
}
|
||||
|
||||
if(forestNameLength > 0)
|
||||
{
|
||||
// DNS-style name of the Active Directory forest in which this identifier was created.
|
||||
byte[] binaryForestName = reader.ReadBytes(forestNameLength);
|
||||
// Trim \0
|
||||
this.ForestName = Encoding.Unicode.GetString(binaryForestName, 0, binaryForestName.Length - sizeof(char));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
using DSInternals.Common.Cryptography;
|
||||
|
||||
namespace DSInternals.Common.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Group Managed Service Account.
|
||||
/// </summary>
|
||||
/// <see>https://learn.microsoft.com/en-us/windows/win32/adschema/c-msds-groupmanagedserviceaccount</see>
|
||||
public class GroupManagedServiceAccount : DSAccount
|
||||
{
|
||||
private const int DefaultPasswordValidityInterval = 30;
|
||||
|
||||
public ProtectionKeyIdentifier ManagedPasswordId
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public ProtectionKeyIdentifier ManagedPasswordPreviousId
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public int ManagedPasswordInterval
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public GroupManagedServiceAccount(DirectoryObject dsObject, string netBIOSDomainName, DirectorySecretDecryptor pek) : base(dsObject, netBIOSDomainName, pek)
|
||||
{
|
||||
// TODO: Check that this object is a gMSA
|
||||
|
||||
// Read and parse msDS-ManagedPasswordId
|
||||
dsObject.ReadAttribute(CommonDirectoryAttributes.ManagedPasswordId, out byte[] rawManagedPasswordId);
|
||||
if(rawManagedPasswordId != null )
|
||||
{
|
||||
this.ManagedPasswordId = new ProtectionKeyIdentifier(rawManagedPasswordId);
|
||||
}
|
||||
|
||||
// Read and parse msDS-ManagedPasswordPreviousId
|
||||
dsObject.ReadAttribute(CommonDirectoryAttributes.ManagedPasswordPreviousId, out byte[] rawManagedPasswordPreviousId);
|
||||
if(rawManagedPasswordPreviousId != null)
|
||||
{
|
||||
this.ManagedPasswordPreviousId = new ProtectionKeyIdentifier(rawManagedPasswordPreviousId);
|
||||
}
|
||||
|
||||
// Read msDS-ManagedPasswordInterval
|
||||
dsObject.ReadAttribute(CommonDirectoryAttributes.ManagedPasswordInterval, out int? managedPasswordInterval);
|
||||
this.ManagedPasswordInterval = managedPasswordInterval ?? DefaultPasswordValidityInterval;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -63,6 +63,9 @@
|
|||
public const int LMHashHistoryId = 589984;
|
||||
public const int LMHashId = 589879;
|
||||
public const string LockoutTime = "lockoutTime";
|
||||
public const string ManagedPasswordId = "msDS-ManagedPasswordId";
|
||||
public const string ManagedPasswordPreviousId = "msDS-ManagedPasswordPreviousId";
|
||||
public const string ManagedPasswordInterval = "msDS-ManagedPasswordInterval";
|
||||
public const string MasterNamingContexts = "msDS-hasMasterNCs";
|
||||
public const string Member = "member";
|
||||
public const string Name = "name";
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
namespace DSInternals.Common.Interop
|
||||
{
|
||||
/// <see>https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/4cac87a3-521e-4918-a272-240f8fabed39</see>
|
||||
internal enum GroupKeyLevel : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// L0 key
|
||||
/// </summary>
|
||||
L0 = 0,
|
||||
/// <summary>
|
||||
/// L1 key
|
||||
/// </summary>
|
||||
L1 = 1,
|
||||
/// <summary>
|
||||
/// L2 key
|
||||
/// </summary>
|
||||
L2 = 2
|
||||
}
|
||||
}
|
|
@ -15,12 +15,14 @@ namespace DSInternals.Common.Interop
|
|||
internal const int LMHashNumBytes = NTHashNumBits / 8;
|
||||
internal const int LMPasswordMaxChars = 14;
|
||||
internal const int NTPasswordMaxChars = 128;
|
||||
internal const int KdsRootKeySize = 64;
|
||||
|
||||
private const int MaxRegistryKeyClassSize = 256;
|
||||
private const string Advapi = "advapi32.dll";
|
||||
private const string CryptDll = "cryptdll.Dll";
|
||||
private const string Ntdll = "ntdll.dll";
|
||||
private const string Mpr = "mpr.dll";
|
||||
private const string KdsCli = "KdsCli.dll";
|
||||
private const string LMOwfInternalName = "SystemFunction006";
|
||||
private const string NTOwfInternalName = "SystemFunction007";
|
||||
private const string LMOwfEncryptInternalName = "SystemFunction024";
|
||||
|
@ -48,7 +50,7 @@ namespace DSInternals.Common.Interop
|
|||
/// <see>https://github.com/wine-mirror/wine/blob/master/dlls/advapi32/crypt_md4.c</see>
|
||||
[DllImport(Advapi, SetLastError = true, EntryPoint = NTOwfInternalName, CharSet = CharSet.Unicode)]
|
||||
private static extern NtStatus RtlCalculateNtOwfPassword([In] ref SecureUnicodeString password, [MarshalAs(UnmanagedType.LPArray, SizeConst = NTHashNumBytes), In, Out] byte[] hash);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Takes the passed NtPassword and performs a one-way-function on it.
|
||||
/// Uses the RSA MD4 function
|
||||
|
@ -161,7 +163,7 @@ namespace DSInternals.Common.Interop
|
|||
// Wrap to get rid of the unnecessary pointer to int
|
||||
return RtlEncryptLmOwfPwdWithIndex(lmOwfPassword, ref index, encryptedLmOwfPassword);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Faster arbitrary length data encryption function (using RC4)
|
||||
/// </summary>
|
||||
|
@ -281,7 +283,7 @@ namespace DSInternals.Common.Interop
|
|||
{
|
||||
uint securityDescriptorSize;
|
||||
bool result = ConvertStringSecurityDescriptorToSecurityDescriptor(stringSecurityDescriptor, stringSDRevision, out securityDescriptor, out securityDescriptorSize);
|
||||
if(result)
|
||||
if (result)
|
||||
{
|
||||
return Win32ErrorCode.Success;
|
||||
}
|
||||
|
@ -290,5 +292,130 @@ namespace DSInternals.Common.Interop
|
|||
return (Win32ErrorCode)Marshal.GetLastWin32Error();
|
||||
}
|
||||
}
|
||||
|
||||
/// <param name="rootKeyId">Root key identifier of the requested key. It can be set to NULL.</param>
|
||||
/// <param name="l0KeyId">L0 index of the requested group key. It MUST be a signed 32-bit integer greater than or equal to -1.</param>
|
||||
/// <param name="l1KeyId">L1 index of the requested group key. It MUST be a signed 32-bit integer between -1 and 31 (inclusive).</param>
|
||||
/// <param name="l2KeyId">L2 index of the requested group key. It MUST be a 32-bit integer between -1 and 31 (inclusive).</param>
|
||||
/// <param name="level">Group key level.</param>
|
||||
/// <returns>If the function succeeds, the return value is NO_ERROR.</returns>
|
||||
internal static Win32ErrorCode GenerateKDFContext(
|
||||
Guid rootKeyId,
|
||||
int l0KeyId,
|
||||
int l1KeyId,
|
||||
int l2KeyId,
|
||||
GroupKeyLevel level,
|
||||
out byte[] context,
|
||||
out int counterOffset)
|
||||
{
|
||||
var result = GenerateKDFContext(
|
||||
rootKeyId,
|
||||
l0KeyId,
|
||||
l1KeyId,
|
||||
l2KeyId,
|
||||
level,
|
||||
out SafeSidKeyProviderHandle contextHandle,
|
||||
out int contextLength,
|
||||
out counterOffset
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
context = contextHandle.ToArray(contextLength);
|
||||
}
|
||||
finally
|
||||
{
|
||||
contextHandle.Close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[DllImport(KdsCli, SetLastError = true)]
|
||||
private static extern Win32ErrorCode GenerateKDFContext(
|
||||
Guid rootKeyId,
|
||||
int l0KeyId,
|
||||
int l1KeyId,
|
||||
int l2KeyId,
|
||||
GroupKeyLevel level,
|
||||
out SafeSidKeyProviderHandle context,
|
||||
out int contextLength,
|
||||
out int counterOffset);
|
||||
|
||||
internal static Win32ErrorCode GenerateDerivedKey(
|
||||
string kdfAlgorithmName,
|
||||
byte[] kdfParameters,
|
||||
byte[] secret,
|
||||
byte[] context,
|
||||
int? counterOffset,
|
||||
byte[] label,
|
||||
int iteration,
|
||||
out byte[] derivedKey,
|
||||
out string invalidAttribute)
|
||||
{
|
||||
|
||||
int kdfParametersLength = kdfParameters?.Length ?? 0;
|
||||
int secretLength = secret?.Length ?? 0;
|
||||
int contextLength = context?.Length ?? 0;
|
||||
int labelLength = label?.Length ?? 0;
|
||||
byte[] derivedKeyBuffer = new byte[KdsRootKeySize];
|
||||
StringBuilder invalidAttributeBuffer = new StringBuilder(byte.MaxValue);
|
||||
|
||||
// Deal with the optional int parameter
|
||||
int counterOffsetValue = counterOffset.GetValueOrDefault();
|
||||
var counterOffsetHandle = GCHandle.Alloc(counterOffsetValue);
|
||||
|
||||
try
|
||||
{
|
||||
Win32ErrorCode result = GenerateDerivedKey(
|
||||
kdfAlgorithmName,
|
||||
kdfParameters,
|
||||
kdfParametersLength,
|
||||
secret,
|
||||
secretLength,
|
||||
context,
|
||||
contextLength,
|
||||
(counterOffset.HasValue ? (IntPtr) counterOffsetHandle : IntPtr.Zero),
|
||||
label,
|
||||
labelLength,
|
||||
iteration,
|
||||
derivedKeyBuffer,
|
||||
KdsRootKeySize,
|
||||
ref invalidAttributeBuffer
|
||||
);
|
||||
|
||||
derivedKey = derivedKeyBuffer;
|
||||
invalidAttribute = invalidAttributeBuffer.ToString();
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
counterOffsetHandle.Free();
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport(KdsCli, CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern Win32ErrorCode GenerateDerivedKey(
|
||||
string kdfAlgorithmName,
|
||||
byte[] kdfParameters,
|
||||
int kdfParametersLength,
|
||||
byte[] secret,
|
||||
int secretLength,
|
||||
byte[] context,
|
||||
int contextLength,
|
||||
IntPtr counterOffset,
|
||||
byte[] label,
|
||||
int labelLength,
|
||||
int iteration,
|
||||
[MarshalAs(UnmanagedType.LPArray)] byte[] key,
|
||||
int keyLength,
|
||||
ref StringBuilder invalidAttribute);
|
||||
|
||||
/// <summary>
|
||||
/// Frees memory allocated for a credentials structure by the GenerateKDFContext and GenerateDerivedKey functions.
|
||||
/// </summary>
|
||||
/// <param name="memory">Memory to be freed.</param>
|
||||
[DllImport(KdsCli)]
|
||||
internal static extern void SIDKeyProvFree([In] IntPtr memory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
|
||||
namespace DSInternals.Common.Interop
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a wrapper class for SID Key Provider object handles.
|
||||
/// </summary>
|
||||
[SecurityCritical]
|
||||
internal class SafeSidKeyProviderHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
private SafeSidKeyProviderHandle() : base(true)
|
||||
{
|
||||
}
|
||||
|
||||
public SafeSidKeyProviderHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHandle)
|
||||
{
|
||||
this.SetHandle(preexistingHandle);
|
||||
}
|
||||
|
||||
public byte[] ToArray(int size)
|
||||
{
|
||||
if(this.IsInvalid)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] binaryData = new byte[size];
|
||||
Marshal.Copy(this.handle, binaryData, 0, size);
|
||||
|
||||
return binaryData;
|
||||
}
|
||||
|
||||
[SecurityCritical]
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
NativeMethods.SIDKeyProvFree(this.handle);
|
||||
|
||||
// Presume that the memory release has been successful.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -81,6 +81,15 @@ namespace DSInternals.Common
|
|||
}
|
||||
}
|
||||
|
||||
public static void AssertEquals(int expectedValue, int actualValue, string paramName)
|
||||
{
|
||||
if (expectedValue != actualValue)
|
||||
{
|
||||
string message = String.Format(Resources.UnexpectedValueMessage, actualValue, expectedValue);
|
||||
throw new ArgumentException(message, paramName);
|
||||
}
|
||||
}
|
||||
|
||||
public static void AssertEquals(char expectedValue, char actualValue, string paramName)
|
||||
{
|
||||
if (expectedValue.CompareTo(actualValue) != 0)
|
||||
|
|
Loading…
Reference in New Issue