diff --git a/Src/DSInternals.Common.Test/Cryptography/WDigestHashTester.cs b/Src/DSInternals.Common.Test/Cryptography/WDigestHashTester.cs index 803f256..5377072 100644 --- a/Src/DSInternals.Common.Test/Cryptography/WDigestHashTester.cs +++ b/Src/DSInternals.Common.Test/Cryptography/WDigestHashTester.cs @@ -1,6 +1,7 @@ namespace DSInternals.Common.Cryptography.Test { using DSInternals.Common; + using DSInternals.Common.Data; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; @@ -94,6 +95,21 @@ Assert.AreEqual(WDigestHash.HashCount, hashes.Length); } + [TestMethod] + public void WDigestHash_Compute_LDS() + { + // Property value taken from AD LDS + string expectedBlob = "3100011d000000000000000000000000af4156909297baece4e553b731d9d552da58c12e5880cad54a3f909c07b0792eec9d262783c811ad9cc7aeb1ab019fe0af4156909297baece4e553b731d9d552af4156909297baece4e553b731d9d552ec9d262783c811ad9cc7aeb1ab019fe0da58c12e5880cad54a3f909c07b0792e95e11e920c7405ca6639d932888cc11c53d9e953141b322f29eb1a02798c299042eea90067734b2a81abbc79618cc1519d9aac28e63107a495266e755f7a2babac6b5dfb3bfa3b7039058310d5b36ccc7c97824dd59b9a6a053b5fae3d8603f20a5e2bc3111c759efb3a91e3740633e83b12daad09e83cb92477ac59993c5a843b12daad09e83cb92477ac59993c5a843b12daad09e83cb92477ac59993c5a8471e7228faba51df979beef06b34f791820aa1475f40540f0db59f9d2d93fc3da5f88f6d82d185741a82c7fa79e801c6d8eae592a0ce55271a17de0b39cf1e5e9923354fb99887724cf2b10e88108776b427804b8a6d6ed92c11ecb94e40da72c0c997bc1906b1f0092d61bd0efee428b0c997bc1906b1f0092d61bd0efee428b0c997bc1906b1f0092d61bd0efee428bd93e9a9a7452dff036a67a5d5d333d7c4effe21b0afc142382943ef26024649aaa6a6a95c237f2b8efa2f13ecb0cb220"; + string password = @"Pa$$w0rd"; + string userDN = "CN=john,DC=dsinternals,DC=com"; + string namingContext = "DC=dsinternals,DC=com"; + + // Compute and compare + byte[][] hashes = WDigestHash.ComputeHash(password.ToSecureString(), userDN, namingContext); + byte[] blob = WDigestHash.Encode(hashes); + Assert.AreEqual(expectedBlob, blob.ToHex()); + } + [TestMethod] public void WDigestHash_EncodeProperty() { @@ -104,4 +120,4 @@ Assert.AreEqual(blob.ToHex(), newBlob.ToHex()); } } -} \ No newline at end of file +} diff --git a/Src/DSInternals.Common/Cryptography/WDigestHash.cs b/Src/DSInternals.Common/Cryptography/WDigestHash.cs index 8ed70d9..c284d9c 100644 --- a/Src/DSInternals.Common/Cryptography/WDigestHash.cs +++ b/Src/DSInternals.Common/Cryptography/WDigestHash.cs @@ -33,7 +33,7 @@ private const byte DefaultReserved1Value = (byte)'1'; /// - /// Calculates WDigest hashes of a password. + /// Calculates WDigest hashes of a password, as used by AD DS. /// /// User's password. /// The userPrincipalName attribute value. @@ -47,11 +47,11 @@ // Validate the input Validator.AssertNotNull(password, "password"); Validator.AssertNotNullOrWhiteSpace(samAccountName, "samAccountName"); - Validator.AssertNotNullOrWhiteSpace(netBiosDomainName, "netBiosDomainName"); - Validator.AssertNotNullOrWhiteSpace(dnsDomainName, "dnsDomainName"); + Validator.AssertNotNull(netBiosDomainName, "netBiosDomainName"); + Validator.AssertNotNull(dnsDomainName, "dnsDomainName"); // Note that a user does not need to have a UPN. - if(String.IsNullOrEmpty(userPrincipalName)) + if(userPrincipalName == null) { // Construct the UPN as samAccountName@dnsDomainName userPrincipalName = String.Format(@"{0}@{1}", samAccountName, dnsDomainName); @@ -155,6 +155,19 @@ return result; } + /// + /// Calculates WDigest hashes of a password, as used by AD LDS. + /// + /// Account's password. + /// Distinguished name of the account. + /// Distinguished name of the account's naming context. + /// 29 MD5 hashes. + /// SecureString is copied into managed memory while calculating the hashes, which is not the best way to deal with it. + public static byte[][] ComputeHash(SecureString password, string accountDN, string namingContext) + { + return ComputeHash(password, String.Empty, accountDN, String.Empty, namingContext); + } + /// /// Parses the WDIGEST_CREDENTIALS structure within the supplementalCredentials attribute. /// @@ -242,4 +255,4 @@ return Encoding.UTF8.GetBytes(concatenatedString); } } -} \ No newline at end of file +}