Skip to content

Commit

Permalink
Improved DPAPI credential parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelGrafnetter committed Apr 13, 2024
1 parent df6e080 commit 4efd132
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 8 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,6 @@ FakesAssemblies/
# MIDL generated code
/Src/DSInternals.Replication.Interop/drsr.h
/Src/DSInternals.Replication.Interop/drsr.cpp

# Test Data
[Tt]est[Dd]ata/
2 changes: 1 addition & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2015-2021 Michael Grafnetter
Copyright (c) 2015-2024 Michael Grafnetter

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
18 changes: 18 additions & 0 deletions Scripts/Get-TestData.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<#
.SYNOPSIS
Downloads the sample data from Azure storage.
.NOTES
Uses AzCopy, which can be installed by running winget.exe install AzCopy.
#>

#Requires -Version 3

# Test that AzCopy is available
Get-Command -Name azcopy.exe -CommandType Application -ErrorAction Stop | Out-Null

# Download the test data
[string] $parentDirectory = Split-Path -Path $PSScriptRoot -Parent -ErrorAction Stop
[string] $destinationDirectory = Join-Path -Path $parentDirectory -ChildPath TestData -ErrorAction Stop
azcopy.exe copy https://dsinternals.blob.core.windows.net/databases/* $destinationDirectory --recursive --skip-version-check --output-level essential
36 changes: 35 additions & 1 deletion Src/DSInternals.Common.Test/DPAPIBackupKeyTester.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,23 @@ public void DPAPIBackupKey_PreferredRSAKeyPointer()
Assert.IsNull(key.KiwiCommand);
}

[TestMethod]
public void DPAPIBackupKey_PreferredRSAKeyPointerConflict()
{
// Test vector
byte[] blob = "ec56e7ef7cf83a49902f259030203445".HexToBinary();
string distinguishedName = "CN=BCKUPKEY_PREFERRED Secret\\0ACNF:26c8edbb-6b48-4f11-9e13-9ddbccedab5a,CN=System,DC=contoso,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()
{
Expand All @@ -73,5 +90,22 @@ public void DPAPIBackupKey_PreferredLegacyKeyPointer()
Assert.IsNull(key.FilePath);
Assert.IsNull(key.KiwiCommand);
}

[TestMethod]
public void DPAPIBackupKey_PreferredLegacyKeyPointerConflict()
{
// Test vector
byte[] blob = "d5525c587817434d9d7bc22b4f7e5fa4".HexToBinary();
string distinguishedName = "CN=BCKUPKEY_P Secret\\0ACNF:202d1e62-cf69-4446-9578-fce798843cde,CN=System,DC=contoso,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);
}
}
}
}
11 changes: 8 additions & 3 deletions Src/DSInternals.Common/Data/DPAPI/DPAPIBackupKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ public class DPAPIBackupKey : DPAPIObject
private const int RSAPrivateKeyOffset = RSACertificateSizeOffset + sizeof(int);
private const string BackupKeyNameFormat = "G$BCKUPKEY_{0}";
private const string BackupKeyDNFormat = "CN=BCKUPKEY_{0} Secret,CN=System,{1}";
private const string BackupKeyDNRegex = "CN=BCKUPKEY_(.*) Secret,CN=System,.*";
// Examples:
// CN=BCKUPKEY_P Secret,CN=System,DC=contoso,DC=com
// CN=BCKUPKEY_PREFERRED Secret,CN=System,DC=contoso,DC=com
// CN=BCKUPKEY_PREFERRED Secret\0ACNF:26c8edbb-6b48-4f11-9e13-9ddbccedab5a,CN=System,DC=contoso,DC=com
// CN=BCKUPKEY_ac9e427c-fa85-4b78-8db1-771d94c03bad Secret,CN=System,DC=contoso,DC=com
private const string BackupKeyDNRegex = "CN=BCKUPKEY_(.+) Secret(\\\\0ACNF:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})?,CN=System,.+";
private const string PreferredLegacyKeyPointerName = "P";
private const string PreferredRSAKeyPointerName = "PREFERRED";
private const string TemporaryKeyContainerName = "DSInternals";
Expand Down Expand Up @@ -237,7 +242,7 @@ public static string GetPreferredLegacyKeyPointerDN(string domainDN)
private static string GetSecretNameFromDN(string distinguishedName)
{
var match = Regex.Match(distinguishedName, BackupKeyDNRegex);
bool success = match.Success && (match.Groups.Count == 2);
bool success = match.Success && (match.Groups.Count >= 2);
return success ? match.Groups[1].Value : null;
}

Expand Down Expand Up @@ -292,4 +297,4 @@ private static DPAPIBackupKeyType GetKeyType(byte[] blob)
return (DPAPIBackupKeyType)BitConverter.ToInt32(blob, KeyVersionOffset);
}
}
}
}
3 changes: 2 additions & 1 deletion Src/DSInternals.Common/Data/DPAPI/RoamedCredential.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ public RoamedCredential(byte[] blob, string accountName, SecurityIdentifier acco
// The actual roamed data
this.Data = reader.ReadBytes(dataSize);

if(this.Type == RoamedCredentialType.CNGPrivateKey && dataSize > 0)
// Note: The data structure might be corrupted and Data.Length can be less than dataSize.
if (this.Type == RoamedCredentialType.CNGPrivateKey && this.Data.Length > 0)
{
// Remove Software KSP NCRYPT_OPAQUETRANSPORT_BLOB header
this.Data = new CngSoftwareProviderTransportBlob(this.Data).KeyData;
Expand Down
2 changes: 1 addition & 1 deletion Src/DSInternals.PowerShell/License.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ DSInternals PowerShell Module and Framework

The MIT License (MIT)

Copyright (c) 2015-2023 Michael Grafnetter
Copyright (c) 2015-2024 Michael Grafnetter

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
3 changes: 2 additions & 1 deletion Src/DSInternals.sln
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{A6D948F4-1847-47B5-8997-83EEBD971892}"
ProjectSection(SolutionItems) = preProject
..\Scripts\Build-Solution.ps1 = ..\Scripts\Build-Solution.ps1
..\Scripts\Get-TestData.ps1 = ..\Scripts\Get-TestData.ps1
..\Scripts\Invoke-SmokeTests.ps1 = ..\Scripts\Invoke-SmokeTests.ps1
..\Scripts\Make.ps1 = ..\Scripts\Make.ps1
..\Scripts\Pack-Chocolatey.ps1 = ..\Scripts\Pack-Chocolatey.ps1
Expand Down Expand Up @@ -93,11 +94,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PowerShell", "PowerShell",
..\Documentation\PowerShell\Disable-ADDBAccount.md = ..\Documentation\PowerShell\Disable-ADDBAccount.md
..\Documentation\PowerShell\Enable-ADDBAccount.md = ..\Documentation\PowerShell\Enable-ADDBAccount.md
..\Documentation\PowerShell\Get-ADDBAccount.md = ..\Documentation\PowerShell\Get-ADDBAccount.md
..\Documentation\PowerShell\Get-ADDBServiceAccount.md = ..\Documentation\PowerShell\Get-ADDBServiceAccount.md
..\Documentation\PowerShell\Get-ADDBBackupKey.md = ..\Documentation\PowerShell\Get-ADDBBackupKey.md
..\Documentation\PowerShell\Get-ADDBDomainController.md = ..\Documentation\PowerShell\Get-ADDBDomainController.md
..\Documentation\PowerShell\Get-ADDBKdsRootKey.md = ..\Documentation\PowerShell\Get-ADDBKdsRootKey.md
..\Documentation\PowerShell\Get-ADDBSchemaAttribute.md = ..\Documentation\PowerShell\Get-ADDBSchemaAttribute.md
..\Documentation\PowerShell\Get-ADDBServiceAccount.md = ..\Documentation\PowerShell\Get-ADDBServiceAccount.md
..\Documentation\PowerShell\Get-ADKeyCredential.md = ..\Documentation\PowerShell\Get-ADKeyCredential.md
..\Documentation\PowerShell\Get-ADReplAccount.md = ..\Documentation\PowerShell\Get-ADReplAccount.md
..\Documentation\PowerShell\Get-ADReplBackupKey.md = ..\Documentation\PowerShell\Get-ADReplBackupKey.md
Expand Down

0 comments on commit 4efd132

Please sign in to comment.