diff --git a/.gitignore b/.gitignore index 3fc5429..8aa45f8 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/LICENSE.md b/LICENSE.md index deeeaa4..c674d34 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -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 diff --git a/Scripts/Get-TestData.ps1 b/Scripts/Get-TestData.ps1 new file mode 100644 index 0000000..565d6bf --- /dev/null +++ b/Scripts/Get-TestData.ps1 @@ -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 diff --git a/Src/DSInternals.Common.Test/DPAPIBackupKeyTester.cs b/Src/DSInternals.Common.Test/DPAPIBackupKeyTester.cs index 4f97ad3..3e2c412 100644 --- a/Src/DSInternals.Common.Test/DPAPIBackupKeyTester.cs +++ b/Src/DSInternals.Common.Test/DPAPIBackupKeyTester.cs @@ -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() { @@ -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); + } } -} \ No newline at end of file +} diff --git a/Src/DSInternals.Common/Data/DPAPI/DPAPIBackupKey.cs b/Src/DSInternals.Common/Data/DPAPI/DPAPIBackupKey.cs index 0598813..196b907 100644 --- a/Src/DSInternals.Common/Data/DPAPI/DPAPIBackupKey.cs +++ b/Src/DSInternals.Common/Data/DPAPI/DPAPIBackupKey.cs @@ -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"; @@ -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; } @@ -292,4 +297,4 @@ private static DPAPIBackupKeyType GetKeyType(byte[] blob) return (DPAPIBackupKeyType)BitConverter.ToInt32(blob, KeyVersionOffset); } } -} \ No newline at end of file +} diff --git a/Src/DSInternals.Common/Data/DPAPI/RoamedCredential.cs b/Src/DSInternals.Common/Data/DPAPI/RoamedCredential.cs index a3bc2bd..b6a9db8 100644 --- a/Src/DSInternals.Common/Data/DPAPI/RoamedCredential.cs +++ b/Src/DSInternals.Common/Data/DPAPI/RoamedCredential.cs @@ -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; diff --git a/Src/DSInternals.PowerShell/License.txt b/Src/DSInternals.PowerShell/License.txt index 8a40e1e..4a6700d 100644 --- a/Src/DSInternals.PowerShell/License.txt +++ b/Src/DSInternals.PowerShell/License.txt @@ -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 diff --git a/Src/DSInternals.sln b/Src/DSInternals.sln index e8d377e..f78b251 100644 --- a/Src/DSInternals.sln +++ b/Src/DSInternals.sln @@ -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