-
-
Notifications
You must be signed in to change notification settings - Fork 256
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b387697
commit 13385b8
Showing
12 changed files
with
520 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
Src/DSInternals.Common.Test/ProtectionKeyIdentifierTester.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
121 changes: 121 additions & 0 deletions
121
Src/DSInternals.Common/Data/DPAPI/ProtectionKeyIdentifier.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.