Improved DPAPI credential parsing

This commit is contained in:
Michael Grafnetter 2024-04-13 19:24:10 +02:00
parent df6e0806ef
commit 4efd132ff5
8 changed files with 70 additions and 8 deletions

3
.gitignore vendored
View File

@ -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/

View File

@ -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

18
Scripts/Get-TestData.ps1 Normal file
View File

@ -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

View File

@ -57,6 +57,23 @@ namespace DSInternals.Common.Test
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()
{
@ -73,5 +90,22 @@ namespace DSInternals.Common.Test
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);
}
}
}
}

View File

@ -16,7 +16,12 @@
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";
@ -237,7 +242,7 @@
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;
}
@ -292,4 +297,4 @@
return (DPAPIBackupKeyType)BitConverter.ToInt32(blob, KeyVersionOffset);
}
}
}
}

View File

@ -86,7 +86,8 @@
// 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;

View File

@ -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

View File

@ -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
@ -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