diff --git a/Src/DSInternals.Common.Test/DSInternals.Common.Test.csproj b/Src/DSInternals.Common.Test/DSInternals.Common.Test.csproj
index bfb7177..ad04055 100644
--- a/Src/DSInternals.Common.Test/DSInternals.Common.Test.csproj
+++ b/Src/DSInternals.Common.Test/DSInternals.Common.Test.csproj
@@ -26,6 +26,7 @@
DEBUG;TRACE
prompt
4
+ x64
true
@@ -33,6 +34,7 @@
prompt
4
false
+ x64
@@ -76,6 +78,7 @@
+
diff --git a/Src/DSInternals.Common.Test/KdsRootKeyTester.cs b/Src/DSInternals.Common.Test/KdsRootKeyTester.cs
index 2105fe4..1994219 100644
--- a/Src/DSInternals.Common.Test/KdsRootKeyTester.cs
+++ b/Src/DSInternals.Common.Test/KdsRootKeyTester.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());
+ }
}
}
diff --git a/Src/DSInternals.Common.Test/ProtectionKeyIdentifierTester.cs b/Src/DSInternals.Common.Test/ProtectionKeyIdentifierTester.cs
new file mode 100644
index 0000000..f5410df
--- /dev/null
+++ b/Src/DSInternals.Common.Test/ProtectionKeyIdentifierTester.cs
@@ -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);
+ }
+ }
+}
diff --git a/Src/DSInternals.Common/DSInternals.Common.csproj b/Src/DSInternals.Common/DSInternals.Common.csproj
index 946f84a..3b4cd33 100644
--- a/Src/DSInternals.Common/DSInternals.Common.csproj
+++ b/Src/DSInternals.Common/DSInternals.Common.csproj
@@ -67,6 +67,7 @@
+
@@ -89,6 +90,7 @@
+
@@ -130,6 +132,7 @@
+
@@ -145,6 +148,7 @@
+
@@ -183,4 +187,4 @@
-
+
\ No newline at end of file
diff --git a/Src/DSInternals.Common/Data/DPAPI/KdsRootKey.cs b/Src/DSInternals.Common/Data/DPAPI/KdsRootKey.cs
index 04f6bbd..ebe09eb 100644
--- a/Src/DSInternals.Common/Data/DPAPI/KdsRootKey.cs
+++ b/Src/DSInternals.Common/Data/DPAPI/KdsRootKey.cs
@@ -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
///
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
///
public Dictionary KdfParameters
{
- get;
- private set;
+ get
+ {
+ return ParseKdfParameters(this.rawKdfParameters);
+ }
}
///
@@ -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 ParseKdfParameters(byte[] blob)
{
if(blob == null || blob.Length == 0)
@@ -259,4 +312,4 @@ namespace DSInternals.Common.Data
}
}
}
-}
\ No newline at end of file
+}
diff --git a/Src/DSInternals.Common/Data/DPAPI/ProtectionKeyIdentifier.cs b/Src/DSInternals.Common/Data/DPAPI/ProtectionKeyIdentifier.cs
new file mode 100644
index 0000000..b6dd858
--- /dev/null
+++ b/Src/DSInternals.Common/Data/DPAPI/ProtectionKeyIdentifier.cs
@@ -0,0 +1,121 @@
+namespace DSInternals.Common.Data
+{
+ using System;
+ using System.IO;
+ using System.Runtime.InteropServices;
+ using System.Text;
+
+ ///
+ /// 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.
+ ///
+ /// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dnsp/98a575da-ca48-4afd-ba79-f77a8bed4e4e
+ 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));
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Src/DSInternals.Common/Data/Principals/GroupManagedServiceAccount.cs b/Src/DSInternals.Common/Data/Principals/GroupManagedServiceAccount.cs
new file mode 100644
index 0000000..afdff05
--- /dev/null
+++ b/Src/DSInternals.Common/Data/Principals/GroupManagedServiceAccount.cs
@@ -0,0 +1,54 @@
+using DSInternals.Common.Cryptography;
+
+namespace DSInternals.Common.Data
+{
+ ///
+ /// Group Managed Service Account.
+ ///
+ /// https://learn.microsoft.com/en-us/windows/win32/adschema/c-msds-groupmanagedserviceaccount
+ 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;
+ }
+ }
+}
diff --git a/Src/DSInternals.Common/Data/Schema/CommonDirectoryAttributes.cs b/Src/DSInternals.Common/Data/Schema/CommonDirectoryAttributes.cs
index b7fe201..e64e61e 100644
--- a/Src/DSInternals.Common/Data/Schema/CommonDirectoryAttributes.cs
+++ b/Src/DSInternals.Common/Data/Schema/CommonDirectoryAttributes.cs
@@ -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";
diff --git a/Src/DSInternals.Common/Interop/Enums/GroupKeyLevel.cs b/Src/DSInternals.Common/Interop/Enums/GroupKeyLevel.cs
new file mode 100644
index 0000000..3872f3f
--- /dev/null
+++ b/Src/DSInternals.Common/Interop/Enums/GroupKeyLevel.cs
@@ -0,0 +1,19 @@
+namespace DSInternals.Common.Interop
+{
+ /// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/4cac87a3-521e-4918-a272-240f8fabed39
+ internal enum GroupKeyLevel : uint
+ {
+ ///
+ /// L0 key
+ ///
+ L0 = 0,
+ ///
+ /// L1 key
+ ///
+ L1 = 1,
+ ///
+ /// L2 key
+ ///
+ L2 = 2
+ }
+}
diff --git a/Src/DSInternals.Common/Interop/NativeMethods.cs b/Src/DSInternals.Common/Interop/NativeMethods.cs
index 1a98a07..36f9f71 100644
--- a/Src/DSInternals.Common/Interop/NativeMethods.cs
+++ b/Src/DSInternals.Common/Interop/NativeMethods.cs
@@ -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
/// https://github.com/wine-mirror/wine/blob/master/dlls/advapi32/crypt_md4.c
[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);
-
+
///
/// 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);
}
-
+
///
/// Faster arbitrary length data encryption function (using RC4)
///
@@ -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();
}
}
+
+ /// Root key identifier of the requested key. It can be set to NULL.
+ /// L0 index of the requested group key. It MUST be a signed 32-bit integer greater than or equal to -1.
+ /// L1 index of the requested group key. It MUST be a signed 32-bit integer between -1 and 31 (inclusive).
+ /// L2 index of the requested group key. It MUST be a 32-bit integer between -1 and 31 (inclusive).
+ /// Group key level.
+ /// If the function succeeds, the return value is NO_ERROR.
+ 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);
+
+ ///
+ /// Frees memory allocated for a credentials structure by the GenerateKDFContext and GenerateDerivedKey functions.
+ ///
+ /// Memory to be freed.
+ [DllImport(KdsCli)]
+ internal static extern void SIDKeyProvFree([In] IntPtr memory);
}
-}
\ No newline at end of file
+}
diff --git a/Src/DSInternals.Common/Interop/SafeSidKeyProviderHandle.cs b/Src/DSInternals.Common/Interop/SafeSidKeyProviderHandle.cs
new file mode 100644
index 0000000..8487888
--- /dev/null
+++ b/Src/DSInternals.Common/Interop/SafeSidKeyProviderHandle.cs
@@ -0,0 +1,45 @@
+using Microsoft.Win32.SafeHandles;
+using System;
+using System.Runtime.InteropServices;
+using System.Security;
+
+namespace DSInternals.Common.Interop
+{
+ ///
+ /// Represents a wrapper class for SID Key Provider object handles.
+ ///
+ [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;
+ }
+ }
+}
diff --git a/Src/DSInternals.Common/Validator.cs b/Src/DSInternals.Common/Validator.cs
index aec9f5f..ccb3640 100644
--- a/Src/DSInternals.Common/Validator.cs
+++ b/Src/DSInternals.Common/Validator.cs
@@ -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)