diff --git a/Documentation/PowerShell/Get-ADDBAccount.md b/Documentation/PowerShell/Get-ADDBAccount.md index 378a89a4..93553867 100644 --- a/Documentation/PowerShell/Get-ADDBAccount.md +++ b/Documentation/PowerShell/Get-ADDBAccount.md @@ -399,6 +399,7 @@ This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable ## RELATED LINKS [Get-BootKey](Get-BootKey.md) +[Get-ADDBServiceAccount](Get-ADDBServiceAccount.md) [Get-ADReplAccount](Get-ADDBAccount.md) [Get-ADSIAccount](Get-ADSIAccount.md) [Test-PasswordQuality](Test-PasswordQuality.md) diff --git a/Documentation/PowerShell/Get-ADDBKdsRootKey.md b/Documentation/PowerShell/Get-ADDBKdsRootKey.md index 2a113af2..d6712d3c 100644 --- a/Documentation/PowerShell/Get-ADDBKdsRootKey.md +++ b/Documentation/PowerShell/Get-ADDBKdsRootKey.md @@ -109,3 +109,5 @@ This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable ## NOTES ## RELATED LINKS + +[Get-ADDBServiceAccount](Get-ADDBServiceAccount.md) diff --git a/Documentation/PowerShell/Get-ADDBServiceAccount.md b/Documentation/PowerShell/Get-ADDBServiceAccount.md new file mode 100644 index 00000000..62a1c21b --- /dev/null +++ b/Documentation/PowerShell/Get-ADDBServiceAccount.md @@ -0,0 +1,129 @@ +--- +external help file: DSInternals.PowerShell.dll-Help.xml +Module Name: DSInternals +online version: +schema: 2.0.0 +--- + +# Get-ADDBServiceAccount + +## SYNOPSIS +Reads all Group Managed Service Accounts (gMSAs) from a ntds.dit file, while deriving their current passwords from KDS root keys. + +## SYNTAX + +``` +Get-ADDBServiceAccount [-EffectiveTime ] -DatabasePath [-LogPath ] + [] +``` + +## DESCRIPTION +As none of the required information is encrypted, the BootKey is not required. +Does not work on database files from RODCs. + +## EXAMPLES + +### Example 1 +```powershell +PS C:\> Get-ADDBServiceAccount -DatabasePath 'C:\ADBackup\ntds.dit' +<# Sample Output: +DistinguishedName: CN=svc_adfs,CN=Managed Service Accounts,DC=contoso,DC=com +Sid: S-1-5-21-2468531440-3719951020-3687476655-1109 +Guid: 53c845f7-d9cd-471b-a364-e733641dcc86 +SamAccountName: svc_adfs$ +Description: ADFS Service Account +Enabled: True +Deleted: False +UserAccountControl: WorkstationAccount +SupportedEncryptionTypes: RC4_HMAC, AES128_CTS_HMAC_SHA1_96, AES256_CTS_HMAC_SHA1_96 +ServicePrincipalName: {http/login.contoso.com, host/login.contoso.com} +WhenCreated: 9/9/2023 5:02:05 PM +PasswordLastSet: 9/9/2023 5:02:06 PM +ManagedPasswordInterval: 30 +ManagedPasswordId: RootKey=7dc95c96-fa85-183a-dff5-f70696bf0b11, Cycle=9/9/2023 10:00:00 AM (L0=361, L1=26, L2=24) +ManagedPasswordPreviousId: +KDS Derived Secrets + NTHash: 0b5fbfb646dd7bce4f160ad69edb86ba + Kerberos Keys + AES256_CTS_HMAC_SHA1_96 + Key: 5dcc418cd0a30453b267e6e5b158be4b4d80d23fd72a6ae4d5bd07f023517117 + Iterations: 4096 + AES128_CTS_HMAC_SHA1_96 + Key: 8e1e66438a15d764ae2242eefd15e09a + Iterations: 4096 +#> +``` + +Reads all Group Managed Service Accounts (gMSAs) from the specified ntds.dit file, while deriving their current passwords from KDS root keys. + +### Example 2 +```powershell +PS C:\> Get-ADDBServiceAccount -EffectiveTime (Get-Date).AddMonths(1) -DatabasePath 'C:\ADBackup\ntds.dit' +``` + +Reads all Group Managed Service Accounts (gMSAs) from the specified ntds.dit file, while deriving their future passwords from KDS root keys. + +## PARAMETERS + +### -DatabasePath +Specifies the path to a domain database, for instance, C:\Windows\NTDS\ntds.dit. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: Database, DBPath, DatabaseFilePath, DBFilePath + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -EffectiveTime +Specifies the date and time at which the fenerated credentials should be valid. Defaults to the current time. + +```yaml +Type: DateTime +Parameter Sets: (All) +Aliases: EffectiveDate, PasswordLastSet, PwdLastSet, Date, Time, d, t + +Required: False +Position: Named +Default value: [datetime]::Now +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LogPath +Specifies the path to a directory where the transaction log files are located. For instance, C:\Windows\NTDS. The default log directory is the one that contains the database file itself. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: Log, TransactionLogPath + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### None + +## OUTPUTS + +### DSInternals.Common.Data.GroupManagedServiceAccount + +## NOTES + +## RELATED LINKS + +[Get-ADDBKdsRootKey](Get-ADDBKdsRootKey.md) +[Get-ADDBAccount](Get-ADDBAccount.md) diff --git a/Documentation/PowerShell/Readme.md b/Documentation/PowerShell/Readme.md index ba07c0ad..96378dab 100644 --- a/Documentation/PowerShell/Readme.md +++ b/Documentation/PowerShell/Readme.md @@ -54,6 +54,9 @@ Reads the DPAPI backup keys from a ntds.dit file. ### [Get-ADDBKdsRootKey](Get-ADDBKdsRootKey.md#get-addbkdsrootkey) Reads KDS Root Keys from a ntds.dit. file. Can be used to aid DPAPI-NG decryption, e.g. SID-protected PFX files. +### [Get-ADDBServiceAccount](Get-ADDBServiceAccount.md#get-addbserviceaccount) +Reads all Group Managed Service Accounts (gMSAs) from a ntds.dit file, while deriving their current passwords from KDS root keys. + ### [Get-ADDBDomainController](Get-ADDBDomainController.md#get-addbdomaincontroller) Reads information about the originating DC from a ntds.dit file, including domain name, domain SID, DC name and DC site. diff --git a/Src/Configuration/Common.props b/Src/Configuration/Common.props index e46eaee0..3596441f 100644 --- a/Src/Configuration/Common.props +++ b/Src/Configuration/Common.props @@ -19,6 +19,10 @@ true true + + + false + true @@ -31,4 +35,4 @@ $(PublicKeyPath) $(PrivateKeyPath) - \ No newline at end of file + diff --git a/Src/DSInternals.Common.Test/Cryptography/KerberosKeyDerivationTester.cs b/Src/DSInternals.Common.Test/Cryptography/KerberosKeyDerivationTester.cs index 695e46a2..17c6771d 100644 --- a/Src/DSInternals.Common.Test/Cryptography/KerberosKeyDerivationTester.cs +++ b/Src/DSInternals.Common.Test/Cryptography/KerberosKeyDerivationTester.cs @@ -3,12 +3,13 @@ using DSInternals.Common.Data; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.ComponentModel; + using System.Security; [TestClass] public class KerberosKeyDerivationTester { [TestMethod] - public void KerberosKeyDerivation_DES_CBC_MD5() + public void KerberosKeyDerivation_DES_CBC_MD5_User() { var password = "Pa$$w0rd".ToSecureString(); string salt = "ADATUM.COMApril"; @@ -20,7 +21,22 @@ public void KerberosKeyDerivation_DES_CBC_MD5() } [TestMethod] - public void KerberosKeyDerivation_AES256_CTS_HMAC_SHA1_96() + public void KerberosKeyDerivation_DES_CBC_MD5_Service() + { + SecureString password = "f81377aacff9cafe039d91a8f758de148200332b062dc1ac59d8cfcb4f14d9fe0def16e33e4b1a7d90645407860797097ac424570c0664f50d3f3433cea5c3e8594eada2797ef1e27cda6d92fe72d3425206e3ca173f01ac04325d0eaab2eac06b3ff7b4668f3a62a1696e27c1c32e7f06e09adb7784290a2704dc02416bb46c19e91bc4b5a842ce0879459439f685b20225134ed4562cb5bcd944d0acb07986308466385a455e65fd0ee325cae97709a33bc0f413b66ef40bbc59ce7a2a20f500cc1f80a13849b86efbfd59f037277c017ac4bfda3596a75cf06d84a5e118a948653f1aef02dec76501d26ea3cc3b63bb587824a727d02373ea8a5a9e7a71f5".HexToBinary().ReadSecureWString(0); + string salt = "CONTOSO.COMhostsvc_adfs.contoso.com"; + int iterations = 4096; + string expected = "16bab507d3dad66d"; + + byte[] result = KerberosKeyDerivation.DeriveKey(KerberosKeyType.DES_CBC_MD5, password, salt, iterations); + + // TODO: Properly implement DES key derivation for service accounts. + Assert.Inconclusive("DES key derivation does not yield proper results for service accounts yet."); + Assert.AreEqual(expected, result.ToHex(false)); + } + + [TestMethod] + public void KerberosKeyDerivation_AES256_CTS_HMAC_SHA1_96_User() { var password = "Pa$$w0rd".ToSecureString(); string salt = "ADATUM.COMApril"; @@ -31,6 +47,18 @@ public void KerberosKeyDerivation_AES256_CTS_HMAC_SHA1_96() Assert.AreEqual(expected, result.ToHex(false)); } + [TestMethod] + public void KerberosKeyDerivation_AES256_CTS_HMAC_SHA1_96_Service() + { + SecureString password = "f81377aacff9cafe039d91a8f758de148200332b062dc1ac59d8cfcb4f14d9fe0def16e33e4b1a7d90645407860797097ac424570c0664f50d3f3433cea5c3e8594eada2797ef1e27cda6d92fe72d3425206e3ca173f01ac04325d0eaab2eac06b3ff7b4668f3a62a1696e27c1c32e7f06e09adb7784290a2704dc02416bb46c19e91bc4b5a842ce0879459439f685b20225134ed4562cb5bcd944d0acb07986308466385a455e65fd0ee325cae97709a33bc0f413b66ef40bbc59ce7a2a20f500cc1f80a13849b86efbfd59f037277c017ac4bfda3596a75cf06d84a5e118a948653f1aef02dec76501d26ea3cc3b63bb587824a727d02373ea8a5a9e7a71f5".HexToBinary().ReadSecureWString(0); + string salt = "CONTOSO.COMhostsvc_adfs.contoso.com"; + int iterations = 4096; + string expected = "5dcc418cd0a30453b267e6e5b158be4b4d80d23fd72a6ae4d5bd07f023517117"; + + byte[] result = KerberosKeyDerivation.DeriveKey(KerberosKeyType.AES256_CTS_HMAC_SHA1_96, password, salt, iterations); + Assert.AreEqual(expected, result.ToHex(false)); + } + [TestMethod] public void KerberosKeyDerivation_AES128_CTS_HMAC_SHA1_96() { @@ -55,9 +83,15 @@ public void KerberosKeyDerivation_NULL() } [TestMethod] - public void KerberosKeyDerivation_Salt() + public void KerberosKeyDerivation_UserSalt() { Assert.AreEqual("ADATUM.COMApril", KerberosKeyDerivation.DeriveSalt("April", "Adatum.com")); } + + [TestMethod] + public void KerberosKeyDerivation_ComputerSalt() + { + Assert.AreEqual("CONTOSO.COMhostdc01.contoso.com", KerberosKeyDerivation.DeriveSalt("DC01$", "Contoso.com")); + } } } diff --git a/Src/DSInternals.Common.Test/DSInternals.Common.Test.csproj b/Src/DSInternals.Common.Test/DSInternals.Common.Test.csproj index ad04055d..b87a70af 100644 --- a/Src/DSInternals.Common.Test/DSInternals.Common.Test.csproj +++ b/Src/DSInternals.Common.Test/DSInternals.Common.Test.csproj @@ -26,7 +26,7 @@ DEBUG;TRACE prompt 4 - x64 + AnyCPU true @@ -34,7 +34,7 @@ prompt 4 false - x64 + AnyCPU diff --git a/Src/DSInternals.Common.Test/KdsRootKeyTester.cs b/Src/DSInternals.Common.Test/KdsRootKeyTester.cs index 2cbdf393..29db0553 100644 --- a/Src/DSInternals.Common.Test/KdsRootKeyTester.cs +++ b/Src/DSInternals.Common.Test/KdsRootKeyTester.cs @@ -36,22 +36,26 @@ public void ParseKdfParameters_EmptyInput() public void ParseSecretAgreementParameters_Vector1() { byte[] blob = "0c0200004448504d0001000087a8e61db4b6663cffbbd19c651959998ceef608660dd0f25d2ceed4435e3b00e00df8f1d61957d4faf7df4561b2aa3016c3d91134096faa3bf4296d830e9a7c209e0c6497517abd5a8a9d306bcf67ed91f9e6725b4758c022e0b1ef4275bf7b6c5bfc11d45f9088b941f54eb1e59bb8bc39a0bf12307f5c4fdb70c581b23f76b63acae1caa6b7902d52526735488a0ef13c6d9a51bfa4ab3ad8347796524d8ef6a167b5a41825d967e144e5140564251ccacb83e6b486f6b3ca3f7971506026c0b857f689962856ded4010abd0be621c3a3960a54e710c375f26375d7014103a4b54330c198af126116d2276e11715f693877fad7ef09cadb094ae91e1a15973fb32c9b73134d0b2e77506660edbd484ca7b18f21ef205407f4793a1a0ba12510dbc15077be463fff4fed4aac0bb555be3a6c1b0c6b47b1bc3773bf7e8c6f62901228f8c28cbb18a55ae31341000a650196f931c77a57f2ddf463e5e9ec144b777de62aaab8a8628ac376d282d6ed3864e67982428ebc831d14348f6f2f9193b5045af2767164e1dfc967c1fb3f2e55a4bd1bffe83b9c80d052b985d182ea0adb2a3b7313d3fe14c8484b1e052588b9b7d2bbd2df016199ecd06e1557cd0915b3353bbb64e0ec377fd028370df92b52c7891428cdc67eb6184b523d1db246c32f63078490f00ef8d647d148d47954515e2327cfef98c582664b4c0f6cc41659".HexToBinary(); - KdsRootKey.ParseSecretAgreementParameters(blob); - throw new AssertInconclusiveException(); + (byte[] p, byte[] g) = KdsRootKey.ParseSecretAgreementParameters(blob); + Assert.IsNotNull(p); + Assert.IsNotNull(g); + Assert.AreEqual(p.Length, g.Length); } [TestMethod] public void ParseSecretAgreementParameters_NullInput() { - KdsRootKey.ParseSecretAgreementParameters(null); - throw new AssertInconclusiveException(); + (byte[] p, byte[] g) = KdsRootKey.ParseSecretAgreementParameters(null); + Assert.IsNull(p); + Assert.IsNull(g); } [TestMethod] public void ParseSecretAgreementParameters_EmptyInput() { - KdsRootKey.ParseSecretAgreementParameters(new byte[0] { }); - throw new AssertInconclusiveException(); + (byte[] p, byte[] g) = KdsRootKey.ParseSecretAgreementParameters(new byte[0] { }); + Assert.IsNull(p); + Assert.IsNull(g); } [TestMethod] @@ -75,7 +79,6 @@ public void GetGmsaPassword_Vector1() { byte[] binaryPassword = KdsRootKey.GetPassword( new SecurityIdentifier("S-1-5-21-2468531440-3719951020-3687476655-1109"), - null, Guid.Parse("7dc95c96-fa85-183a-dff5-f70696bf0b11"), "814ad2f3928ff96d3650487967392feab3924f3d0dff8629d46a723640101cff8ca2cbd6aba40805cf03b380803b27837d80663eb4d18fd4cec414ebb2271fe2".HexToBinary(), "SP800_108_CTR_HMAC", @@ -87,18 +90,60 @@ public void GetGmsaPassword_Vector1() [TestMethod] public void GetGmsaPassword_Vector2() + { + byte[] binaryPassword = KdsRootKey.GetPassword( + new SecurityIdentifier("S-1-5-21-2468531440-3719951020-3687476655-1109"), + Guid.Parse("7dc95c96-fa85-183a-dff5-f70696bf0b11"), + "814ad2f3928ff96d3650487967392feab3924f3d0dff8629d46a723640101cff8ca2cbd6aba40805cf03b380803b27837d80663eb4d18fd4cec414ebb2271fe2".HexToBinary(), + "SP800_108_CTR_HMAC", + "00000000010000000e000000000000005300480041003500310032000000".HexToBinary(), + DateTime.FromFileTimeUtc(133387453261266352), + DateTime.FromFileTimeUtc(133403352475182719), + 30 + ); + + Assert.AreEqual("0b5fbfb646dd7bce4f160ad69edb86ba", NTHash.ComputeHash(binaryPassword).ToHex()); + } + + [TestMethod] + public void GetGmsaPassword_Vector3() { var managedPasswordId = new ProtectionKeyIdentifier("010000004b44534b02000000690100001a00000018000000965cc97d85fa3a18dff5f70696bf0b1100000000180000001800000063006f006e0074006f0073006f002e0063006f006d00000063006f006e0074006f0073006f002e0063006f006d000000".HexToBinary()); byte[] binaryPassword = KdsRootKey.GetPassword( new SecurityIdentifier("S-1-5-21-2468531440-3719951020-3687476655-1109"), - managedPasswordId, Guid.Parse("7dc95c96-fa85-183a-dff5-f70696bf0b11"), "814ad2f3928ff96d3650487967392feab3924f3d0dff8629d46a723640101cff8ca2cbd6aba40805cf03b380803b27837d80663eb4d18fd4cec414ebb2271fe2".HexToBinary(), "SP800_108_CTR_HMAC", "00000000010000000e000000000000005300480041003500310032000000".HexToBinary(), - DateTime.FromFileTimeUtc(133403352475182719)); + managedPasswordId.L0KeyId, + managedPasswordId.L1KeyId, + managedPasswordId.L2KeyId + ); Assert.AreEqual("0b5fbfb646dd7bce4f160ad69edb86ba", NTHash.ComputeHash(binaryPassword).ToHex()); } + [TestMethod] + public void GetGmsaPassword_Vector4() + { + byte[] binaryPassword = KdsRootKey.GetPassword( + new SecurityIdentifier("S-1-5-21-1040335485-253814736-2627409954-1145"), + Guid.Parse("0670b5ed-f2aa-9a86-dd0e-49cfc2130533"), + "902bc244751f7cfb1bbafff7586585d467496953da553fd3decae08421b6c0ab5f60637541655b8be90fa319e24041875eccd465e253ceba238e1d475c80f64b".HexToBinary(), + "SP800_108_CTR_HMAC", + "00000000010000000e000000000000005300480041003500310032000000".HexToBinary(), + DateTime.FromFileTimeUtc(133211195280000000), // whenCreated ( 6 months diff) + DateTime.FromFileTimeUtc(133404554396754922) + ); + + Assert.AreEqual("e510057c721830f0b27482833cff4986", NTHash.ComputeHash(binaryPassword).ToHex()); + } + + [TestMethod] + public void IntervalCalculation_Reverse() + { + DateTime effectiveTime = KdsRootKey.GetRootIntervalStart(361, 28, 4); + + Assert.AreEqual(new DateTime(2023, 9, 27).Date, effectiveTime.Date); + } } } diff --git a/Src/DSInternals.Common/Cryptography/KerberosKeyDerivation.cs b/Src/DSInternals.Common/Cryptography/KerberosKeyDerivation.cs index 03113ac5..e9cc32c6 100644 --- a/Src/DSInternals.Common/Cryptography/KerberosKeyDerivation.cs +++ b/Src/DSInternals.Common/Cryptography/KerberosKeyDerivation.cs @@ -17,8 +17,8 @@ public static class KerberosKeyDerivation /// https://tools.ietf.org/html/rfc3961 public static byte[] DeriveKey(KerberosKeyType type, SecureString password, string salt, int iterations = DefaultIterationCount) { - Validator.AssertNotNull(password, "password"); - Validator.AssertNotNull(salt, "salt"); + Validator.AssertNotNull(password, nameof(password)); + Validator.AssertNotNull(salt, nameof(salt)); KerberosCryptoSystem cryptoSystem; var status = NativeMethods.CDLocateCSystem(type, out cryptoSystem); @@ -46,9 +46,24 @@ public static byte[] DeriveKey(KerberosKeyType type, SecureString password, stri /// public static string DeriveSalt(string principal, string realm) { - Validator.AssertNotNull(principal, "principal"); - Validator.AssertNotNull(realm, "realm"); - return realm.ToUpper() + principal; + Validator.AssertNotNull(principal, nameof(principal)); + Validator.AssertNotNull(realm, nameof(realm)); + + bool isComputerAccount = principal.EndsWith("$"); + + if (isComputerAccount) + { + // Computer / managed service account salt, e.g. CONTOSO.COMhostdc01.contoso.com + return string.Format("{0}host{1}.{2}", + realm.ToUpperInvariant(), + principal.TrimEnd('$').ToLowerInvariant(), + realm.ToLowerInvariant()); + } + else + { + // User account salt, e.g. CONTOSO.COMjohn + return realm.ToUpperInvariant() + principal; + } } } } diff --git a/Src/DSInternals.Common/DSInternals.Common.csproj b/Src/DSInternals.Common/DSInternals.Common.csproj index 3b4cd332..9271e430 100644 --- a/Src/DSInternals.Common/DSInternals.Common.csproj +++ b/Src/DSInternals.Common/DSInternals.Common.csproj @@ -128,6 +128,7 @@ + diff --git a/Src/DSInternals.Common/Data/DPAPI/KdsRootKey.cs b/Src/DSInternals.Common/Data/DPAPI/KdsRootKey.cs index 5f784554..21d88727 100644 --- a/Src/DSInternals.Common/Data/DPAPI/KdsRootKey.cs +++ b/Src/DSInternals.Common/Data/DPAPI/KdsRootKey.cs @@ -12,14 +12,18 @@ namespace DSInternals.Common.Data /// public class KdsRootKey { - private const int L0KeyIteration = 1; - private const int L1KeyIteration = 32; - private const int L2KeyIteration = 32; + private const int L0KeyModulus = 1; + private const int L1KeyModulus = 32; + private const int L2KeyModulus = 32; private const long KdsKeyCycleDuration = 360000000000; // 10 hrs in FileTime private const long MaxClockSkew = 3000000000; // 5 min in FileTime private const string GmsaKdfLabel = "GMSA PASSWORD"; + private const string SecretAgreementParametersHeaderMagic = "DHPM"; + private const int ExpectedKdfParametersVersion = 0; + private const int SecretAgreementParametersMinSize = 2 * sizeof(int) + 4; // Lengths + magic private const int DefaultKdsKeySize = 64; private const int GmsaPasswordLength = 256; + private const int DefaultManagedPasswordInterval = 30; // 30 days // TODO: Move to GMSA private static readonly byte[] DefaultGMSASecurityDescriptor = { @@ -167,7 +171,7 @@ public string KdfAlgorithm /// /// Parameters for the key derivation algorithm. /// - public Dictionary KdfParameters + public Dictionary KdfParameters { get { @@ -219,15 +223,14 @@ public int SecretAgreementPrivateKeyLength } } - public byte[] ComputeL0Key(int l0KeyId) + public byte[] GetManagedPassword( + SecurityIdentifier gMsaSid, + DateTime previousPasswordChange, + DateTime effectiveTime, + int? managedPasswordInterval = null + ) { - return ComputeL0Key( - this.KeyId, - this.KeyValue, - this.KdfAlgorithm, - this.rawKdfParameters, - l0KeyId - ); + return GetPassword(gMsaSid, this.KeyId, this.KeyValue, this.KdfAlgorithm, this.rawKdfParameters, previousPasswordChange, effectiveTime, managedPasswordInterval ?? DefaultManagedPasswordInterval); } public static byte[] ComputeL0Key( @@ -256,7 +259,7 @@ out int counterOffset kdfContext, null, null, - L0KeyIteration, + L0KeyModulus, DefaultKdsKeySize, out byte[] l0Key, out string invalidAtribute @@ -281,7 +284,7 @@ public static (byte[] l1KeyCurrent, byte[] l1KeyPrevious) GenerateL1Key( var result = NativeMethods.GenerateKDFContext( kdsRootKeyId, l0KeyId, - L1KeyIteration - 1, + L1KeyModulus - 1, -1, GroupKeyLevel.L1, out byte[] context, @@ -309,7 +312,7 @@ public static (byte[] l1KeyCurrent, byte[] l1KeyPrevious) GenerateL1Key( byte[] l1KeyCurrent; - int iteration = L1KeyIteration - l1KeyId - 1; + int iteration = L1KeyModulus - l1KeyId - 1; if(iteration > 0) { @@ -373,12 +376,11 @@ public static byte[] GenerateL2Key( int l1KeyId, int l2KeyId) { - var result = NativeMethods.GenerateKDFContext( kdsRootKeyId, l0KeyId, l1KeyId, - L2KeyIteration - 1, + L2KeyModulus - 1, GroupKeyLevel.L2, out byte[] context, out int counterOffset); @@ -392,7 +394,7 @@ public static byte[] GenerateL2Key( context, counterOffset, null, - L2KeyIteration - l2KeyId, + L2KeyModulus - l2KeyId, DefaultKdsKeySize, out byte[] l2Key, out string invalidAttribute); @@ -411,7 +413,6 @@ public static byte[] ClientComputeL2Key( byte[] l2Key, int l0KeyId, int l1KeyId, - int l2KeyId, int l1KeyIteration, int l2KeyIteration, int nextL1KeyId, @@ -499,10 +500,11 @@ public static (byte[] l1Key, byte[] l2Key) ComputeSidPrivateKey( bool isPublicKey ) { + // TODO: Consider caching the L0 key. (byte[] l1KeyCurrent, byte[] l1KeyPrevious) = GenerateL1Key(kdsRootKeyId, kdfAlgorithm, kdfParameters, l0Key, l0KeyId, l1KeyId, securityDescriptor); - if(l2KeyId == L2KeyIteration - 1 && isPublicKey == false) + if(l2KeyId == L2KeyModulus - 1 && isPublicKey == false) { return (l1KeyCurrent, null); } @@ -514,7 +516,7 @@ bool isPublicKey } } - public static (byte[] l0Key, byte[] l1Key, byte[] l2Key) GetSidKeyLocal( + public static (byte[] l1Key, byte[] l2Key) GetSidKeyLocal( Guid kdsRootKeyId, byte[] kdsRootKey, string kdfAlgorithm, @@ -527,42 +529,106 @@ int l2KeyId { byte[] l0Key = ComputeL0Key(kdsRootKeyId, kdsRootKey, kdfAlgorithm, kdfParameters, l0KeyId); - (byte[] l1Key, byte[] l2Key) = ComputeSidPrivateKey(kdsRootKeyId, kdfAlgorithm, kdfParameters, l0Key, securityDescriptor, l0KeyId, l1KeyId, l2KeyId, false); + return ComputeSidPrivateKey(kdsRootKeyId, kdfAlgorithm, kdfParameters, l0Key, securityDescriptor, l0KeyId, l1KeyId, l2KeyId, false); + } - return (l0Key, l1Key, l2Key); + public static byte[] GetPassword( + SecurityIdentifier gMsaSid, + Guid kdsRootKeyId, + byte[] kdsRootKey, + string kdfAlgorithm, + byte[] kdfParameters, + DateTime lastPasswordChange + ) + { + return GetPassword(gMsaSid, kdsRootKeyId, kdsRootKey, kdfAlgorithm, kdfParameters, lastPasswordChange, lastPasswordChange); } public static byte[] GetPassword( - SecurityIdentifier sid, - ProtectionKeyIdentifier managedPasswordId, + SecurityIdentifier gMsaSid, Guid kdsRootKeyId, byte[] kdsRootKey, string kdfAlgorithm, byte[] kdfParameters, - DateTime effectiveTime + DateTime previousPasswordChange, + DateTime effectiveTime, + int managedPasswordInterval = DefaultManagedPasswordInterval ) { - (int l0KeyId, int l1KeyId, int l2KeyId) = GetIntervalId(effectiveTime); - (byte[] l0Key, byte[] l1Key, byte[] l2Key) = GetSidKeyLocal(kdsRootKeyId, kdsRootKey, kdfAlgorithm, kdfParameters, DefaultGMSASecurityDescriptor, l0KeyId, l1KeyId, l2KeyId); + (int l0KeyId, int l1KeyId, int l2KeyId) = GetIntervalId(previousPasswordChange, effectiveTime, managedPasswordInterval); - return GenerateGmsaPassword( - managedPasswordId, - sid, - kdsRootKeyId, - kdsRootKey, - kdfAlgorithm, - kdfParameters, - l0KeyId, - l1KeyId, - l2KeyId, - l0Key, - l1Key, - l2Key); + return GetPassword(gMsaSid, kdsRootKeyId, kdsRootKey, kdfAlgorithm, kdfParameters, l0KeyId, l1KeyId, l2KeyId); + } + + public static byte[] GetPassword( + SecurityIdentifier gMsaSid, + Guid kdsRootKeyId, + byte[] kdsRootKey, + string kdfAlgorithm, + byte[] kdfParameters, + int l0KeyId, + int l1KeyId, + int l2KeyId + ) + { + Validator.AssertNotNull(kdsRootKey, nameof(kdsRootKey)); + Validator.AssertNotNullOrWhiteSpace(kdfAlgorithm, nameof(kdfAlgorithm)); + Validator.AssertNotNull(gMsaSid, nameof(gMsaSid)); + + if(l0KeyId < 0) + { + throw new ArgumentOutOfRangeException(nameof(l0KeyId)); + } + + if(l1KeyId < 0 || l1KeyId >= L1KeyModulus) + { + throw new ArgumentOutOfRangeException(nameof(l1KeyId)); + } + + if (l2KeyId < 0 || l2KeyId >= L2KeyModulus) + { + throw new ArgumentOutOfRangeException(nameof(l2KeyId)); + } + + (byte[] l1Key, byte[] l2Key) = GetSidKeyLocal(kdsRootKeyId, kdsRootKey, kdfAlgorithm, kdfParameters, DefaultGMSASecurityDescriptor, l0KeyId, l1KeyId, l2KeyId); + + if (l2Key == null || l2Key.Length == 0) + { + // Recalculate the L2 key with new parameters + int nextl2KeyId = L2KeyModulus - 1; + l2Key = ClientComputeL2Key( + null, + kdsRootKeyId, + kdfAlgorithm, + kdfParameters, + l1Key, + null, + l0KeyId, + l1KeyId, + 0, + 1, + 0, + nextl2KeyId); + } + + NativeMethods.GenerateDerivedKey( + kdfAlgorithm, + kdfParameters, + l2Key, + gMsaSid.GetBinaryForm(), + null, + GmsaKdfLabel, + 1, + GmsaPasswordLength, + out byte[] generatedPassword, + out string invalidAttribute + ); + + return generatedPassword; } public static void ParseSIDKeyResult( ProtectionKeyIdentifier managedPasswordId, - int l0KeyId, int l1KeyId, int l2KeyId, bool isL2KeyEmpty, @@ -597,8 +663,8 @@ public static void ParseSIDKeyResult( if (isL2KeyEmpty || l1KeyId > managedPasswordId.L1KeyId) { - l2KeyIteration = L2KeyIteration - managedPasswordId.L2KeyId; - nextL2KeyId = L2KeyIteration - 1; + l2KeyIteration = L2KeyModulus - managedPasswordId.L2KeyId; + nextL2KeyId = L2KeyModulus - 1; } else { @@ -614,159 +680,145 @@ public static void ParseSIDKeyResult( if (isL2KeyEmpty) { l2KeyIteration = 1; - nextL2KeyId = L2KeyIteration - 1; + nextL2KeyId = L2KeyModulus - 1; } } } - - public static byte[] GenerateGmsaPassword( - ProtectionKeyIdentifier managedPasswordId, - SecurityIdentifier sid, - Guid kdsRootKeyId, - byte[] kdsRootKey, - string kdfAlgorithm, - byte[] kdfParameters, - int l0KeyId, - int l1KeyId, - int l2KeyId, - byte[] l0Key, - byte[] l1Key, - byte[] l2Key + + public static (int l0KeyId, int l1KeyId, int l2KeyId) GetIntervalId( + DateTime previousPasswordChange, + DateTime effectiveTime, + int managedPasswordInterval = DefaultManagedPasswordInterval, + bool isClockSkewConsidered = false ) { - ParseSIDKeyResult( - managedPasswordId, - l0KeyId, - l1KeyId, - l2KeyId, - l2Key == null, - out int l1KeyIteration, - out int nextL1KeyId, - out int l2KeyIteration, - out int nextL2KeyId); + if(managedPasswordInterval <= 0) + { + throw new ArgumentOutOfRangeException(nameof(managedPasswordInterval)); + } - byte[] nextL2Key = l2Key; + if(effectiveTime < previousPasswordChange) + { + throw new ArgumentOutOfRangeException(nameof(effectiveTime)); + } + + int daysDifference = (int)(effectiveTime - previousPasswordChange).TotalDays; + int totalPasswordCycles = daysDifference / managedPasswordInterval; + DateTime effectiveIntervalStartTime = previousPasswordChange.AddDays(managedPasswordInterval * totalPasswordCycles); + + long effectiveIntervalId = effectiveIntervalStartTime.ToFileTimeUtc(); - if (l1KeyIteration > 0 || l2KeyIteration > 0) + if(isClockSkewConsidered) { - nextL2Key = ClientComputeL2Key( - managedPasswordId, - kdsRootKeyId, - kdfAlgorithm, - kdfParameters, - l1Key, - l2Key, - l0KeyId, - l1KeyId, - l2KeyId, - l1KeyIteration, - l2KeyIteration, - nextL1KeyId, - nextL2KeyId); + effectiveIntervalId += MaxClockSkew; } - NativeMethods.GenerateDerivedKey( - kdfAlgorithm, - kdfParameters, - nextL2Key, - sid.GetBinaryForm(), - null, - GmsaKdfLabel, - 1, - GmsaPasswordLength, - out byte[] generatedPassword, - out string invalidAttribute - ); + int effectiveKdsCycleId = (int)(effectiveIntervalId / KdsKeyCycleDuration); - return generatedPassword; - } + int l0KeyId = effectiveKdsCycleId / (L1KeyModulus * L2KeyModulus); + int l1KeyId = (effectiveKdsCycleId / L2KeyModulus) % L1KeyModulus; + int l2KeyId = effectiveKdsCycleId % L2KeyModulus; - public static (int l0KeyId, int l1KeyId, int l2KeyId) GetCurrentIntervalId( - bool isClockSkewConsidered = false - ) - { - return GetIntervalId(DateTime.Now, isClockSkewConsidered); + return (l0KeyId, l1KeyId, l2KeyId); } - public static (int l0KeyId, int l1KeyId, int l2KeyId) GetIntervalId( - DateTime effectiveTime, - bool isClockSkewConsidered = false - ) + public static DateTime GetRootIntervalStart(int l0KeyId, int l1KeyId, int l2KeyId) { - long effectiveFileTime = effectiveTime.ToFileTimeUtc(); + if(l0KeyId < 0) + { + throw new ArgumentOutOfRangeException(nameof(l0KeyId)); + } - if(isClockSkewConsidered) + if(l1KeyId < 0 || l1KeyId >= L1KeyModulus) { - effectiveFileTime += MaxClockSkew; + throw new ArgumentOutOfRangeException(nameof(l1KeyId)); } - int effectiveCycleId = (int)(effectiveFileTime / KdsKeyCycleDuration); + if (l2KeyId < 0 || l2KeyId >= L2KeyModulus) + { + throw new ArgumentOutOfRangeException(nameof(l2KeyId)); + } - int l0KeyId = effectiveCycleId / (L1KeyIteration * L2KeyIteration); - int l1KeyId = (effectiveCycleId / L2KeyIteration) % L1KeyIteration; - int l2KeyId = effectiveCycleId % L2KeyIteration; + long effectiveTimestamp = l2KeyId + l1KeyId * L1KeyModulus + l0KeyId * L1KeyModulus * L2KeyModulus; - return (l0KeyId, l1KeyId, l2KeyId); + return DateTime.FromFileTime(effectiveTimestamp * KdsKeyCycleDuration); } - public static Dictionary ParseKdfParameters(byte[] blob) + + public static Dictionary ParseKdfParameters(byte[] blob) { if(blob == null || blob.Length == 0) { return null; } - // TODO: Validate length - var result = new Dictionary(); + int aggregatedMinLength = 2 * sizeof(int); // version + param count + Validator.AssertMinLength(blob, aggregatedMinLength, nameof(blob)); + + var result = new Dictionary(); using (Stream stream = new MemoryStream(blob)) { using (BinaryReader reader = new BinaryReader(stream)) { - uint version = reader.ReadUInt32(); - // TODO: Test that version == 0 + int version = reader.ReadInt32(); + Validator.AssertEquals(ExpectedKdfParametersVersion, version, nameof(version)); - uint parameterCount = reader.ReadUInt32(); + int parameterCount = reader.ReadInt32(); + + aggregatedMinLength += parameterCount * 2 * sizeof(int); // + valueLength + parameterId + Validator.AssertMinLength(blob, aggregatedMinLength, nameof(blob)); for (uint i = 0; i < parameterCount; i++) { int valueLength = reader.ReadInt32(); - uint parameterId = reader.ReadUInt32(); - // TODO: Test that paramId == 0 + int parameterId = reader.ReadInt32(); + + aggregatedMinLength += valueLength; + Validator.AssertMinLength(blob, aggregatedMinLength, nameof(blob)); byte[] binaryValue = reader.ReadBytes(valueLength); - + // Remove the trailing 0 at the end. string value = UnicodeEncoding.Unicode.GetString(binaryValue, 0, valueLength - sizeof(char)); result.Add(parameterId, value); } + + // Check that there are no unread bytes left. + Validator.AssertLength(blob, aggregatedMinLength, nameof(blob)); } } + return result; } - public static void ParseSecretAgreementParameters(byte[] blob) + public static (byte[] p, byte[] g) ParseSecretAgreementParameters(byte[] blob) { if (blob == null || blob.Length == 0) { - return; + return (null, null); } - // TODO: Validate minimum length + + Validator.AssertMinLength(blob, SecretAgreementParametersMinSize, nameof(blob)); using (Stream stream = new MemoryStream(blob)) { using (BinaryReader reader = new BinaryReader(stream)) { - var length = reader.ReadInt32(); - // TODO: validate actual length + int length = reader.ReadInt32(); + Validator.AssertLength(blob, length, nameof(blob)); - var binaryMagic = reader.ReadBytes(sizeof(int)); + byte[] binaryMagic = reader.ReadBytes(sizeof(int)); string magic = ASCIIEncoding.ASCII.GetString(binaryMagic); - // TODO: Test that magic is DHPM + Validator.AssertEquals(SecretAgreementParametersHeaderMagic, magic, nameof(blob)); - // DH: + // DH public parameters: int keySize = reader.ReadInt32(); - var p = reader.ReadBytes(keySize); - var g = reader.ReadBytes(keySize); + Validator.AssertEquals(length, SecretAgreementParametersMinSize + 2 * keySize, nameof(length)); + + byte[] p = reader.ReadBytes(keySize); + byte[] g = reader.ReadBytes(keySize); + + return (p, g); } } } diff --git a/Src/DSInternals.Common/Data/DPAPI/ProtectionKeyIdentifier.cs b/Src/DSInternals.Common/Data/DPAPI/ProtectionKeyIdentifier.cs index b6dd8583..d7d0b165 100644 --- a/Src/DSInternals.Common/Data/DPAPI/ProtectionKeyIdentifier.cs +++ b/Src/DSInternals.Common/Data/DPAPI/ProtectionKeyIdentifier.cs @@ -51,6 +51,17 @@ public string ForestName private set; } + public override string ToString() + { + return string.Format("RootKey={0}, Cycle={1} (L0={2}, L1={3}, L2={4})", + this.RootKeyId, + KdsRootKey.GetRootIntervalStart(this.L0KeyId, this.L1KeyId, this.L2KeyId), + this.L0KeyId, + this.L1KeyId, + this.L2KeyId + ); + } + public ProtectionKeyIdentifier(byte[] blob) { Validator.AssertMinLength(blob, StructureHeaderLength, nameof(blob)); diff --git a/Src/DSInternals.Common/Data/Principals/DSAccount.cs b/Src/DSInternals.Common/Data/Principals/DSAccount.cs index ab167db7..2bd379b4 100644 --- a/Src/DSInternals.Common/Data/Principals/DSAccount.cs +++ b/Src/DSInternals.Common/Data/Principals/DSAccount.cs @@ -247,6 +247,7 @@ public int PrimaryGroupId get; private set; } + /// /// Gets the type of the account object. /// @@ -267,6 +268,10 @@ public bool AdminCount private set; } + /// + /// List of principal names used for mutual authentication with an instance of a service. + /// + public string[] ServicePrincipalName { get; diff --git a/Src/DSInternals.Common/Data/Principals/GroupManagedServiceAccount.cs b/Src/DSInternals.Common/Data/Principals/GroupManagedServiceAccount.cs index afdff05c..0fdb8694 100644 --- a/Src/DSInternals.Common/Data/Principals/GroupManagedServiceAccount.cs +++ b/Src/DSInternals.Common/Data/Principals/GroupManagedServiceAccount.cs @@ -1,4 +1,6 @@ -using DSInternals.Common.Cryptography; +using System; +using System.Security; +using System.Security.Principal; namespace DSInternals.Common.Data { @@ -6,32 +8,196 @@ namespace DSInternals.Common.Data /// Group Managed Service Account. /// /// https://learn.microsoft.com/en-us/windows/win32/adschema/c-msds-groupmanagedserviceaccount - public class GroupManagedServiceAccount : DSAccount + public class GroupManagedServiceAccount { - private const int DefaultPasswordValidityInterval = 30; - + /// + /// Key identifier for the current managed password data. + /// public ProtectionKeyIdentifier ManagedPasswordId { get; private set; } + /// + /// Key identifier for the previous managed password data. + /// public ProtectionKeyIdentifier ManagedPasswordPreviousId { get; private set; } - public int ManagedPasswordInterval + /// + /// Number of days before the managed password is automatically changed. + /// + public int? ManagedPasswordInterval + { + get; + private set; + } + + /// + /// Gets the distinguished name (DN) for this . + /// + public string DistinguishedName + { + get; + private set; + } + + /// + /// Gets the Security ID (SID) of the . + /// + public SecurityIdentifier Sid + { + get; + private set; + } + + /// + /// Gets the GUID associated with this . + /// + /// + /// The unique identifier. + /// + public Guid Guid + { + get; + private set; + } + + /// + /// Gets the description of the . + /// + /// + /// The description. + /// + public string Description + { + get; + private set; + } + + /// + /// Gets a boolean value indicating whether this is enabled. + /// + /// + /// true if enabled; otherwise, false. + /// + public bool Enabled + { + get + { + return !this.UserAccountControl.HasFlag(UserAccountControl.Disabled); + } + } + + /// + /// Gets the flags that control the behavior of the user account. + /// + /// + /// The value can be zero or a combination of one or more flags. + /// + public UserAccountControl UserAccountControl + { + get; + private set; + } + + /// + /// Gets the encryption types supported by this trust relationship. + /// + /// Implemented on Windows Server 2008 operating system and later. + public SupportedEncryptionTypes? SupportedEncryptionTypes + { + get; + private set; + } + + /// + /// Gets a boolean value indicating whether this is deleted. + /// + /// + /// true if deleted; otherwise, false. + /// + public bool Deleted + { + get; + private set; + } + + /// + /// Gets or sets the SAM account name for this . + /// + public string SamAccountName + { + get; + private set; + } + + /// + /// List of principal names used for mutual authentication with an instance of a service. + /// + public string[] ServicePrincipalName { get; private set; } - public GroupManagedServiceAccount(DirectoryObject dsObject, string netBIOSDomainName, DirectorySecretDecryptor pek) : base(dsObject, netBIOSDomainName, pek) + public DateTime? PasswordLastSet + { + get; + private set; + } + + public DateTime WhenCreated + { + get; + private set; + } + + /// + /// Gets the current password. + /// + public SecureString SecureManagedPassword + { + get; + private set; + } + + /// + /// Gets the current password. + /// + public string ManagedPassword + { + get + { + return this.SecureManagedPassword.ToUnicodeString(); + } + } + + /// + /// Gets the account's password in Windows NT operating system one-way format (OWF). + /// + public byte[] NTHash + { + get; + private set; + } + + public KerberosKeyDataNew[] KerberosKeys + { + get; + private set; + } + + public GroupManagedServiceAccount(DirectoryObject dsObject) { // TODO: Check that this object is a gMSA + /* Load gMSA-specific attributes first */ + // Read and parse msDS-ManagedPasswordId dsObject.ReadAttribute(CommonDirectoryAttributes.ManagedPasswordId, out byte[] rawManagedPasswordId); if(rawManagedPasswordId != null ) @@ -48,7 +214,72 @@ public GroupManagedServiceAccount(DirectoryObject dsObject, string netBIOSDomain // Read msDS-ManagedPasswordInterval dsObject.ReadAttribute(CommonDirectoryAttributes.ManagedPasswordInterval, out int? managedPasswordInterval); - this.ManagedPasswordInterval = managedPasswordInterval ?? DefaultPasswordValidityInterval; + this.ManagedPasswordInterval = managedPasswordInterval; + + /* Load other account attributes */ + + // Guid: + this.Guid = dsObject.Guid; + + // DN: + this.DistinguishedName = dsObject.DistinguishedName; + + // Sid: + this.Sid = dsObject.Sid; + + // Description + dsObject.ReadAttribute(CommonDirectoryAttributes.Description, out string description); + this.Description = description; + + // Service Principal Name(s) + dsObject.ReadAttribute(CommonDirectoryAttributes.ServicePrincipalName, out string[] spn); + this.ServicePrincipalName = spn; + + // UAC: + dsObject.ReadAttribute(CommonDirectoryAttributes.UserAccountControl, out int? numericUac); + this.UserAccountControl = (UserAccountControl)numericUac.Value; + + // Deleted: + dsObject.ReadAttribute(CommonDirectoryAttributes.IsDeleted, out bool isDeleted); + this.Deleted = isDeleted; + + // SamAccountName: + dsObject.ReadAttribute(CommonDirectoryAttributes.SAMAccountName, out string samAccountName); + this.SamAccountName = samAccountName; + + // SuportedEncryptionTypes + dsObject.ReadAttribute(CommonDirectoryAttributes.SupportedEncryptionTypes, out int? numericSupportedEncryptionTypes); + // Note: The value is store as int in the DB, but the documentation says that it is an unsigned int + this.SupportedEncryptionTypes = (SupportedEncryptionTypes?)numericSupportedEncryptionTypes; + + // pwdLastSet + dsObject.ReadAttribute(CommonDirectoryAttributes.PasswordLastSet, out DateTime? passwordLastSet); + this.PasswordLastSet = passwordLastSet; + + // whenCreated (should never be null) + dsObject.ReadAttribute(CommonDirectoryAttributes.WhenCreated, out long? whenCreated); + this.WhenCreated = whenCreated.Value.FromGeneralizedTime(); + } + + public void CalculatePassword(KdsRootKey kdsRootKey, DateTime effectiveTime) + { + Validator.AssertNotNull(kdsRootKey, nameof(kdsRootKey)); + + // Calculate the managed password + DateTime previousPasswordChange = this.PasswordLastSet ?? this.WhenCreated; + byte[] managedPassword = kdsRootKey.GetManagedPassword(this.Sid, previousPasswordChange, effectiveTime, this.ManagedPasswordInterval); + this.SecureManagedPassword = managedPassword.ReadSecureWString(0); + managedPassword.ZeroFill(); // Remove the cleartext password from memory + + // Derive password hashes, as the password itself is not that important + this.NTHash = Cryptography.NTHash.ComputeHash(this.SecureManagedPassword); + + if (this.ManagedPasswordId != null) + { + // We only need the DNS domain name from the managed password id. This attribute should always be populated. + // TODO: We are not generating DES keys, as this feature does not yield expected results yet. + this.KerberosKeys = new KerberosCredentialNew(this.SecureManagedPassword, this.SamAccountName, this.ManagedPasswordId.DomainName, false).Credentials; + } } } } diff --git a/Src/DSInternals.Common/Data/Principals/KerberosCredentialNew.cs b/Src/DSInternals.Common/Data/Principals/KerberosCredentialNew.cs index 17b37eaf..ebfee3f5 100644 --- a/Src/DSInternals.Common/Data/Principals/KerberosCredentialNew.cs +++ b/Src/DSInternals.Common/Data/Principals/KerberosCredentialNew.cs @@ -22,12 +22,12 @@ public KerberosCredentialNew(byte[] blob) this.ReadCredentials(blob); } - public KerberosCredentialNew(SecureString password, string principal, string realm) : - this(password, KerberosKeyDerivation.DeriveSalt(principal, realm)) + public KerberosCredentialNew(SecureString password, string principal, string realm, bool includeDES = true) : + this(password, KerberosKeyDerivation.DeriveSalt(principal, realm), includeDES) { } - public KerberosCredentialNew(SecureString password, string salt) + public KerberosCredentialNew(SecureString password, string salt, bool includeDES = true) { Validator.AssertNotNull(password, "password"); Validator.AssertNotNull(salt, "salt"); @@ -35,10 +35,6 @@ public KerberosCredentialNew(SecureString password, string salt) this.DefaultSalt = salt; this.DefaultIterationCount = KerberosKeyDerivation.DefaultIterationCount; - // Generate DES key - byte[] desKey = KerberosKeyDerivation.DeriveKey(KerberosKeyType.DES_CBC_MD5, password, this.DefaultSalt); - var desKeyData = new KerberosKeyDataNew(KerberosKeyType.DES_CBC_MD5, desKey, this.DefaultIterationCount); - // Generate AES keys byte[] aes128Key = KerberosKeyDerivation.DeriveKey(KerberosKeyType.AES128_CTS_HMAC_SHA1_96, password, this.DefaultSalt); var aes128KeyData = new KerberosKeyDataNew(KerberosKeyType.AES128_CTS_HMAC_SHA1_96, aes128Key, this.DefaultIterationCount); @@ -46,7 +42,19 @@ public KerberosCredentialNew(SecureString password, string salt) byte[] aes256Key = KerberosKeyDerivation.DeriveKey(KerberosKeyType.AES256_CTS_HMAC_SHA1_96, password, this.DefaultSalt); var aes256KeyData = new KerberosKeyDataNew(KerberosKeyType.AES256_CTS_HMAC_SHA1_96, aes256Key, this.DefaultIterationCount); - this.Credentials = new KerberosKeyDataNew[] { aes256KeyData, aes128KeyData, desKeyData }; + if(includeDES) + { + // Generate DES key + byte[] desKey = KerberosKeyDerivation.DeriveKey(KerberosKeyType.DES_CBC_MD5, password, this.DefaultSalt); + var desKeyData = new KerberosKeyDataNew(KerberosKeyType.DES_CBC_MD5, desKey, this.DefaultIterationCount); + + this.Credentials = new KerberosKeyDataNew[] { aes256KeyData, aes128KeyData, desKeyData }; + } + else + { + // AES keys only + this.Credentials = new KerberosKeyDataNew[] { aes256KeyData, aes128KeyData }; + } } public short Flags diff --git a/Src/DSInternals.Common/Data/Schema/CommonDirectoryClasses.cs b/Src/DSInternals.Common/Data/Schema/CommonDirectoryClasses.cs index 3b42c9f2..fd1a3532 100644 --- a/Src/DSInternals.Common/Data/Schema/CommonDirectoryClasses.cs +++ b/Src/DSInternals.Common/Data/Schema/CommonDirectoryClasses.cs @@ -10,5 +10,6 @@ public static class CommonDirectoryClasses public const int AttributeSchemaId = 196622; public const string Schema = "dMD"; public const string KdsRootKey = "msKds-ProvRootKey"; + public const string GroupManagedServiceAccount = "msDS-GroupManagedServiceAccount"; } } diff --git a/Src/DSInternals.Common/Extensions/ByteArrayExtensions.cs b/Src/DSInternals.Common/Extensions/ByteArrayExtensions.cs index ea5074a2..5fd72e48 100644 --- a/Src/DSInternals.Common/Extensions/ByteArrayExtensions.cs +++ b/Src/DSInternals.Common/Extensions/ByteArrayExtensions.cs @@ -125,9 +125,8 @@ public static SecureString ReadSecureWString(this byte[] buffer, int startIndex) result.AppendChar(c); } - // If we reached this point, the \0 char has not been found, so throw an exception. - // TODO: Add a reasonable exception message - throw new ArgumentException(); + // If we reached this point, the \0 char has not been found. + return result; } public static void SwapBytes(this byte[] bytes, int index1, int index2) diff --git a/Src/DSInternals.Common/Extensions/DateTimeExtensions.cs b/Src/DSInternals.Common/Extensions/DateTimeExtensions.cs new file mode 100644 index 00000000..e5d98a3d --- /dev/null +++ b/Src/DSInternals.Common/Extensions/DateTimeExtensions.cs @@ -0,0 +1,24 @@ +using System; + +namespace DSInternals.Common +{ + public static class DateTimeExtensions + { + private const int GeneralizedTimeCoefficient = 10000000; + + public static long ToGeneralizedTime(this DateTime time) + { + return time.ToFileTime() / GeneralizedTimeCoefficient; + } + + public static DateTime FromGeneralizedTime(this long timestamp) + { + return DateTime.FromFileTime(timestamp * GeneralizedTimeCoefficient); + } + + public static long ToTimeStamp(this DateTime time) + { + return time.ToFileTime(); + } + } +} diff --git a/Src/DSInternals.DataStore/ADConstants.cs b/Src/DSInternals.DataStore/ADConstants.cs index 94dcedde..6def02a9 100644 --- a/Src/DSInternals.DataStore/ADConstants.cs +++ b/Src/DSInternals.DataStore/ADConstants.cs @@ -10,7 +10,6 @@ internal static class ADConstants public const string SystemTableName = "hiddentable"; public const string LinkTableName = "link_table"; public const string SecurityDescriptorTableName = "sd_table"; - public const int GeneralizedTimeCoefficient = 10000000; public const string EseBaseName = "edb"; public const string EseTempDatabaseName = "temp.edb"; public const int PageSize = 8192; // 8k diff --git a/Src/DSInternals.DataStore/AttributeMetadata.cs b/Src/DSInternals.DataStore/AttributeMetadata.cs index ea9dd048..94104650 100644 --- a/Src/DSInternals.DataStore/AttributeMetadata.cs +++ b/Src/DSInternals.DataStore/AttributeMetadata.cs @@ -1,4 +1,5 @@ using System; +using DSInternals.Common; namespace DSInternals.DataStore { @@ -81,12 +82,12 @@ public DateTime LastOriginatingChangeTime { get { - DateTime result = DateTime.FromFileTime(this.LastOriginatingChangeTimestamp * ADConstants.GeneralizedTimeCoefficient); + DateTime result = this.LastOriginatingChangeTimestamp.FromGeneralizedTime(); return result; } private set { - this.LastOriginatingChangeTimestamp = value.ToFileTime() / ADConstants.GeneralizedTimeCoefficient; + this.LastOriginatingChangeTimestamp = value.ToGeneralizedTime(); } } diff --git a/Src/DSInternals.DataStore/DSInternals.DataStore.csproj b/Src/DSInternals.DataStore/DSInternals.DataStore.csproj index d0669ce0..8b3f5079 100644 --- a/Src/DSInternals.DataStore/DSInternals.DataStore.csproj +++ b/Src/DSInternals.DataStore/DSInternals.DataStore.csproj @@ -62,7 +62,6 @@ - diff --git a/Src/DSInternals.DataStore/DirectoryAgent.cs b/Src/DSInternals.DataStore/DirectoryAgent.cs index 4db13c07..9feee38c 100644 --- a/Src/DSInternals.DataStore/DirectoryAgent.cs +++ b/Src/DSInternals.DataStore/DirectoryAgent.cs @@ -136,6 +136,43 @@ public IEnumerable GetKdsRootKeys() } } + public IEnumerable GetGroupManagedServiceAccounts(DateTime effectiveTime) + { + // Fetch all KDS root keys first. + var rootKeys = new Dictionary(); + + foreach (var rootKey in this.GetKdsRootKeys()) + { + // Some servers, like RODCs might not contain key values + if (rootKey.KeyValue != null) + { + // Allow the key to be found by ID + rootKeys.Add(rootKey.KeyId, rootKey); + } + } + + // Now fetch all gMSAs and associate them with the KDS root keys + // TODO: Test if schema contains the msDS-GroupManagedServiceAccount class. + foreach (var gmsaObject in this.FindObjectsByCategory(CommonDirectoryClasses.GroupManagedServiceAccount)) + { + var gmsa = new GroupManagedServiceAccount(gmsaObject); + + if (gmsa.ManagedPasswordId != null) + { + // Find the proper key by Guid + Guid associateRootKeyId = gmsa.ManagedPasswordId.RootKeyId; + bool keyFound = rootKeys.TryGetValue(associateRootKeyId, out var associatedRootKey); + + if (keyFound) + { + gmsa.CalculatePassword(associatedRootKey, effectiveTime); + } + } + + yield return gmsa; + } + } + public IEnumerable FindObjectsByCategory(string className, bool includeDeleted = false) { // Find all objects with the right objectCategory diff --git a/Src/DSInternals.DataStore/Extensions/CursorExtensions.cs b/Src/DSInternals.DataStore/Extensions/CursorExtensions.cs index c2cfd8f0..153c53c5 100644 --- a/Src/DSInternals.DataStore/Extensions/CursorExtensions.cs +++ b/Src/DSInternals.DataStore/Extensions/CursorExtensions.cs @@ -224,7 +224,7 @@ public static bool RetrieveColumnAsBoolean(this Cursor cursor, Columnid columnId if (timestamp.HasValue) { // 0 = January 1, 1601 1:00:00 AM = Never - return DateTime.FromFileTime(timestamp.Value * ADConstants.GeneralizedTimeCoefficient); + return timestamp.Value.FromGeneralizedTime(); } else { @@ -395,9 +395,9 @@ public static bool SetValue(this Cursor cursor, Columnid columnId, DateTime? new if (newValue.HasValue) { // Treat the value as generalized time - newTimeStamp = newValue.Value.ToFileTime() / ADConstants.GeneralizedTimeCoefficient; + newTimeStamp = newValue.Value.ToGeneralizedTime(); } - + // Push the value to the DB return cursor.SetValue(columnId, newTimeStamp); } diff --git a/Src/DSInternals.DataStore/Extensions/DateTimeExtensions.cs b/Src/DSInternals.DataStore/Extensions/DateTimeExtensions.cs deleted file mode 100644 index a857ef22..00000000 --- a/Src/DSInternals.DataStore/Extensions/DateTimeExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ - -using System; - -namespace DSInternals.DataStore -{ - public static class DateTimeExtensions - { - public static long ToGeneralizedTime(this DateTime time) - { - return time.ToFileTime() / ADConstants.GeneralizedTimeCoefficient; - } - public static long ToTimeStamp(this DateTime time) - { - return time.ToFileTime(); - } - } -} diff --git a/Src/DSInternals.PowerShell/Commands/Datastore/GetADDBKdsRootKeyCommand.cs b/Src/DSInternals.PowerShell/Commands/Datastore/GetADDBKdsRootKeyCommand.cs index b7aa1986..1faf36d8 100644 --- a/Src/DSInternals.PowerShell/Commands/Datastore/GetADDBKdsRootKeyCommand.cs +++ b/Src/DSInternals.PowerShell/Commands/Datastore/GetADDBKdsRootKeyCommand.cs @@ -1,9 +1,6 @@ namespace DSInternals.PowerShell.Commands { - using System; using System.Management.Automation; - using DSInternals.Common; - using DSInternals.Common.Cryptography; using DSInternals.DataStore; [Cmdlet(VerbsCommon.Get, "ADDBKdsRootKey")] @@ -25,4 +22,4 @@ protected override void BeginProcessing() // TODO: Exception handling } } -} \ No newline at end of file +} diff --git a/Src/DSInternals.PowerShell/Commands/Datastore/GetADDBServiceAccountCommand.cs b/Src/DSInternals.PowerShell/Commands/Datastore/GetADDBServiceAccountCommand.cs new file mode 100644 index 00000000..b02c13f3 --- /dev/null +++ b/Src/DSInternals.PowerShell/Commands/Datastore/GetADDBServiceAccountCommand.cs @@ -0,0 +1,34 @@ +namespace DSInternals.PowerShell.Commands +{ + using System; + using System.Management.Automation; + using DSInternals.DataStore; + + [Cmdlet(VerbsCommon.Get, "ADDBServiceAccount")] + [OutputType(typeof(DSInternals.Common.Data.GroupManagedServiceAccount))] + public class GetADDBServiceAccountCommand : ADDBCommandBase + { + [Parameter(Mandatory = false)] + [Alias("EffectiveDate", "PasswordLastSet", "PwdLastSet", "Date", "Time", "d", "t")] + public DateTime? EffectiveTime { get; set; } + + // TODO: Implement gMSA filtering + protected override void BeginProcessing() + { + base.BeginProcessing(); + + // Current date is the default value + DateTime passwordEffectiveTime = this.EffectiveTime ?? DateTime.Now; + + using(var directoryAgent = new DirectoryAgent(this.DirectoryContext)) + { + // Now fetch all gMSAs and associate them with the KDS root keys + foreach (var gmsa in directoryAgent.GetGroupManagedServiceAccounts(passwordEffectiveTime)) + { + this.WriteObject(gmsa); + } + } + // TODO: Exception handling + } + } +} diff --git a/Src/DSInternals.PowerShell/DSInternals.Bootstrap.psm1 b/Src/DSInternals.PowerShell/DSInternals.Bootstrap.psm1 index b380d23d..5c82bab5 100644 --- a/Src/DSInternals.PowerShell/DSInternals.Bootstrap.psm1 +++ b/Src/DSInternals.PowerShell/DSInternals.Bootstrap.psm1 @@ -92,32 +92,33 @@ Update-TypeData -TypeName 'DSInternals.Common.Data.SupplementalCredentials' ` # Cmdlet aliases # -New-Alias -Name Set-ADAccountPasswordHash -Value Set-SamAccountPasswordHash -New-Alias -Name Set-WinUserPasswordHash -Value Set-SamAccountPasswordHash -New-Alias -Name Get-ADPasswordPolicy -Value Get-SamPasswordPolicy -New-Alias -Name Get-ADDefaultPasswordPolicy -Value Get-SamPasswordPolicy -New-Alias -Name ConvertFrom-UnattendXmlPassword -Value ConvertFrom-UnicodePassword -New-Alias -Name ConvertTo-AADHash -Value ConvertTo-OrgIdHash -New-Alias -Name ConvertTo-MsoPasswordHash -Value ConvertTo-OrgIdHash -New-Alias -Name Get-ADReplicationAccount -Value Get-ADReplAccount -New-Alias -Name ConvertFrom-ManagedPasswordBlob -Value ConvertFrom-ADManagedPasswordBlob -New-Alias -Name Get-SysKey -Value Get-BootKey -New-Alias -Name Get-SystemKey -Value Get-BootKey -New-Alias -Name Set-ADDBSysKey -Value Set-ADDBBootKey -New-Alias -Name Test-ADPasswordQuality -Value Test-PasswordQuality -New-Alias -Name Test-ADDBPasswordQuality -Value Test-PasswordQuality -New-Alias -Name Test-ADReplPasswordQuality -Value Test-PasswordQuality -New-Alias -Name Get-KeyCredential -Value Get-ADKeyCredential -New-Alias -Name Get-KeyCredentialLink -Value Get-ADKeyCredential -New-Alias -Name Get-ADKeyCredentialLink -Value Get-ADKeyCredential -New-Alias -Name New-ADKeyCredential -Value Get-ADKeyCredential -New-Alias -Name New-ADKeyCredentialLink -Value Get-ADKeyCredential -New-Alias -Name New-ADNgcKey -Value Get-ADKeyCredential -New-Alias -Name Get-LsaPolicy -Value Get-LsaPolicyInformation -New-Alias -Name Set-LsaPolicy -Value Set-LsaPolicyInformation -New-Alias -Name Write-ADReplNgcKey -Value Add-ADReplNgcKey -New-Alias -Name Write-ADNgcKey -Value Add-ADReplNgcKey -New-Alias -Name Add-ADNgcKey -Value Add-ADReplNgcKey +New-Alias -Name Set-ADAccountPasswordHash -Value Set-SamAccountPasswordHash +New-Alias -Name Set-WinUserPasswordHash -Value Set-SamAccountPasswordHash +New-Alias -Name Get-ADPasswordPolicy -Value Get-SamPasswordPolicy +New-Alias -Name Get-ADDefaultPasswordPolicy -Value Get-SamPasswordPolicy +New-Alias -Name ConvertFrom-UnattendXmlPassword -Value ConvertFrom-UnicodePassword +New-Alias -Name ConvertTo-AADHash -Value ConvertTo-OrgIdHash +New-Alias -Name ConvertTo-MsoPasswordHash -Value ConvertTo-OrgIdHash +New-Alias -Name Get-ADReplicationAccount -Value Get-ADReplAccount +New-Alias -Name ConvertFrom-ManagedPasswordBlob -Value ConvertFrom-ADManagedPasswordBlob +New-Alias -Name Get-SysKey -Value Get-BootKey +New-Alias -Name Get-SystemKey -Value Get-BootKey +New-Alias -Name Set-ADDBSysKey -Value Set-ADDBBootKey +New-Alias -Name Test-ADPasswordQuality -Value Test-PasswordQuality +New-Alias -Name Test-ADDBPasswordQuality -Value Test-PasswordQuality +New-Alias -Name Test-ADReplPasswordQuality -Value Test-PasswordQuality +New-Alias -Name Get-KeyCredential -Value Get-ADKeyCredential +New-Alias -Name Get-KeyCredentialLink -Value Get-ADKeyCredential +New-Alias -Name Get-ADKeyCredentialLink -Value Get-ADKeyCredential +New-Alias -Name New-ADKeyCredential -Value Get-ADKeyCredential +New-Alias -Name New-ADKeyCredentialLink -Value Get-ADKeyCredential +New-Alias -Name New-ADNgcKey -Value Get-ADKeyCredential +New-Alias -Name Get-LsaPolicy -Value Get-LsaPolicyInformation +New-Alias -Name Set-LsaPolicy -Value Set-LsaPolicyInformation +New-Alias -Name Write-ADReplNgcKey -Value Add-ADReplNgcKey +New-Alias -Name Write-ADNgcKey -Value Add-ADReplNgcKey +New-Alias -Name Add-ADNgcKey -Value Add-ADReplNgcKey +New-Alias -Name Get-ADDBGroupManagedServiceAccount -Value Get-ADDBServiceAccount # Export the aliases Export-ModuleMember -Alias * -Cmdlet * diff --git a/Src/DSInternals.PowerShell/DSInternals.PowerShell.csproj b/Src/DSInternals.PowerShell/DSInternals.PowerShell.csproj index dfa77de1..b3d84266 100644 --- a/Src/DSInternals.PowerShell/DSInternals.PowerShell.csproj +++ b/Src/DSInternals.PowerShell/DSInternals.PowerShell.csproj @@ -59,6 +59,7 @@ + @@ -242,6 +243,10 @@ Designer PreserveNewest + + Designer + PreserveNewest + diff --git a/Src/DSInternals.PowerShell/DSInternals.psd1 b/Src/DSInternals.PowerShell/DSInternals.psd1 index b598e6b6..d382deec 100644 --- a/Src/DSInternals.PowerShell/DSInternals.psd1 +++ b/Src/DSInternals.PowerShell/DSInternals.psd1 @@ -56,6 +56,7 @@ FormatsToProcess = 'Views\DSInternals.AzureADUser.format.ps1xml', 'Views\DSInternals.FidoKeyMaterial.format.ps1xml', 'Views\DSInternals.DSAccount.format.ps1xml', 'Views\DSInternals.DSAccount.ExportViews.format.ps1xml', + 'Views\DSInternals.GroupManagedServiceAccount.format.ps1xml', 'Views\DSInternals.PasswordQualityTestResult.format.ps1xml', 'Views\DSInternals.KdsRootKey.format.ps1xml', 'Views\DSInternals.SamDomainPasswordInformation.format.ps1xml', @@ -78,7 +79,7 @@ CmdletsToExport = 'ConvertTo-NTHash', 'ConvertTo-LMHash', 'Set-SamAccountPasswor 'Get-ADReplAccount', 'ConvertTo-Hex', 'ConvertTo-KerberosKey', 'ConvertFrom-ADManagedPasswordBlob', 'Get-ADDBBackupKey', 'Get-ADReplBackupKey', 'Save-DPAPIBlob', - 'Set-ADDBBootKey', 'Test-PasswordQuality', + 'Set-ADDBBootKey', 'Test-PasswordQuality', 'Get-ADDBServiceAccount', 'Get-ADDBKdsRootKey', 'Get-SamPasswordPolicy', 'Get-ADSIAccount', 'Enable-ADDBAccount', 'Disable-ADDBAccount', 'Get-ADKeyCredential', 'Set-ADDBAccountPassword', 'Set-ADDBAccountPasswordHash', 'Get-LsaPolicyInformation', @@ -99,7 +100,7 @@ AliasesToExport = 'Set-WinUserPasswordHash', 'Set-ADAccountPasswordHash', 'Get-KeyCredentialLink', 'Get-ADKeyCredentialLink', 'Get-LsaPolicy', 'Set-LsaPolicy', 'Get-SystemKey', 'Write-ADReplNgcKey', 'Write-ADNgcKey', 'Add-ADNgcKey', 'New-ADKeyCredential', 'New-ADKeyCredentialLink', - 'New-ADNgcKey' + 'New-ADNgcKey','Get-ADDBGroupManagedServiceAccount' # List of assemblies that must be loaded prior to importing this module RequiredAssemblies = @('DSInternals.Common.dll') diff --git a/Src/DSInternals.PowerShell/Views/DSInternals.GroupManagedServiceAccount.format.ps1xml b/Src/DSInternals.PowerShell/Views/DSInternals.GroupManagedServiceAccount.format.ps1xml new file mode 100644 index 00000000..582d02dd --- /dev/null +++ b/Src/DSInternals.PowerShell/Views/DSInternals.GroupManagedServiceAccount.format.ps1xml @@ -0,0 +1,119 @@ + + + + + GroupManagedServiceAccount + + DSInternals.Common.Data.GroupManagedServiceAccount + + + + + + DistinguishedName: + + DistinguishedName + + + Sid: + + Sid + + + Guid: + + Guid + + + SamAccountName: + + SamAccountName + + + Description: + + Description + + + Enabled: + + Enabled + + + Deleted: + + Deleted + + + UserAccountControl: + + UserAccountControl + + + SupportedEncryptionTypes: + + SupportedEncryptionTypes + + + ServicePrincipalName: + + ServicePrincipalName + + + WhenCreated: + + WhenCreated + + + PasswordLastSet: + + PasswordLastSet + + + ManagedPasswordInterval: + + ManagedPasswordInterval + + + ManagedPasswordId: + + ManagedPasswordId + + + ManagedPasswordPreviousId: + + ManagedPasswordPreviousId + + + KDS Derived Secrets + + + 2 + + NTHash: + + NTHash + Hash + + + Kerberos Keys + + + 2 + + + KerberosKeys + KerberosKeyDataNew + + + + + + + + + + + + + diff --git a/Src/DSInternals.PowerShell/en-US/DSInternals.PowerShell.dll-Help.xml b/Src/DSInternals.PowerShell/en-US/DSInternals.PowerShell.dll-Help.xml index 7a7ac7c0..96d3616c 100644 --- a/Src/DSInternals.PowerShell/en-US/DSInternals.PowerShell.dll-Help.xml +++ b/Src/DSInternals.PowerShell/en-US/DSInternals.PowerShell.dll-Help.xml @@ -3669,6 +3669,10 @@ PS C:\> $account = Get-ADDBAccount -Sid $adminSid ` Get-BootKey + + Get-ADDBServiceAccount + + Get-ADReplAccount @@ -4182,6 +4186,10 @@ Successfully decrypted password: VBGpKPryuiWBSyq/+CjC0WjNsnZ1xS3Hs6IqGZwa0BM= Online Version: https://github.com/MichaelGrafnetter/DSInternals/blob/master/Documentation/PowerShell/Get-ADDBKdsRootKey.md + + Get-ADDBServiceAccount + + @@ -4316,6 +4324,175 @@ Successfully decrypted password: VBGpKPryuiWBSyq/+CjC0WjNsnZ1xS3Hs6IqGZwa0BM= + + + Get-ADDBServiceAccount + Get + ADDBServiceAccount + + Reads all Group Managed Service Accounts (gMSAs) from a ntds.dit file, while deriving their current passwords from KDS root keys. + + + + As none of the required information is encrypted, the BootKey is not required. Does not work on database files from RODCs. + + + + Get-ADDBServiceAccount + + DatabasePath + + Specifies the path to a domain database, for instance, C:\Windows\NTDS\ntds.dit. + + String + + String + + + None + + + EffectiveTime + + Specifies the date and time at which the fenerated credentials should be valid. Defaults to the current time. + + DateTime + + DateTime + + + [datetime]::Now + + + LogPath + + Specifies the path to a directory where the transaction log files are located. For instance, C:\Windows\NTDS. The default log directory is the one that contains the database file itself. + + String + + String + + + None + + + + + + DatabasePath + + Specifies the path to a domain database, for instance, C:\Windows\NTDS\ntds.dit. + + String + + String + + + None + + + EffectiveTime + + Specifies the date and time at which the fenerated credentials should be valid. Defaults to the current time. + + DateTime + + DateTime + + + [datetime]::Now + + + LogPath + + Specifies the path to a directory where the transaction log files are located. For instance, C:\Windows\NTDS. The default log directory is the one that contains the database file itself. + + String + + String + + + None + + + + + + None + + + + + + + + + + DSInternals.Common.Data.GroupManagedServiceAccount + + + + + + + + + + + + + + -------------------------- Example 1 -------------------------- + PS C:\> Get-ADDBServiceAccount -DatabasePath 'C:\ADBackup\ntds.dit' +<# Sample Output: +DistinguishedName: CN=svc_adfs,CN=Managed Service Accounts,DC=contoso,DC=com +Sid: S-1-5-21-2468531440-3719951020-3687476655-1109 +Guid: 53c845f7-d9cd-471b-a364-e733641dcc86 +SamAccountName: svc_adfs$ +Description: ADFS Service Account +Enabled: True +Deleted: False +UserAccountControl: WorkstationAccount +SupportedEncryptionTypes: RC4_HMAC, AES128_CTS_HMAC_SHA1_96, AES256_CTS_HMAC_SHA1_96 +ServicePrincipalName: {http/login.contoso.com, host/login.contoso.com} +WhenCreated: 9/9/2023 5:02:05 PM +PasswordLastSet: 9/9/2023 5:02:06 PM +ManagedPasswordInterval: 30 +ManagedPasswordId: RootKey=7dc95c96-fa85-183a-dff5-f70696bf0b11, Cycle=9/9/2023 10:00:00 AM (L0=361, L1=26, L2=24) +ManagedPasswordPreviousId: +KDS Derived Secrets + NTHash: 0b5fbfb646dd7bce4f160ad69edb86ba + Kerberos Keys + AES256_CTS_HMAC_SHA1_96 + Key: 5dcc418cd0a30453b267e6e5b158be4b4d80d23fd72a6ae4d5bd07f023517117 + Iterations: 4096 + AES128_CTS_HMAC_SHA1_96 + Key: 8e1e66438a15d764ae2242eefd15e09a + Iterations: 4096 +#> + + Reads all Group Managed Service Accounts (gMSAs) from the specified ntds.dit file, while deriving their current passwords from KDS root keys. + + + + -------------------------- Example 2 -------------------------- + PS C:\> Get-ADDBServiceAccount -EffectiveTime (Get-Date).AddMonths(1) -DatabasePath 'C:\ADBackup\ntds.dit' + + Reads all Group Managed Service Accounts (gMSAs) from the specified ntds.dit file, while deriving their future passwords from KDS root keys. + + + + + + Get-ADDBKdsRootKey + + + + Get-ADDBAccount + + + + Get-ADKeyCredential diff --git a/Src/DSInternals.Replication.Model/DSInternals.Replication.Model.csproj b/Src/DSInternals.Replication.Model/DSInternals.Replication.Model.csproj index 5cd31dde..6db87762 100644 --- a/Src/DSInternals.Replication.Model/DSInternals.Replication.Model.csproj +++ b/Src/DSInternals.Replication.Model/DSInternals.Replication.Model.csproj @@ -22,14 +22,12 @@ DEBUG;TRACE prompt 4 - false true TRACE prompt 4 - false false diff --git a/Src/DSInternals.Replication.Test/DSInternals.Replication.Test.csproj b/Src/DSInternals.Replication.Test/DSInternals.Replication.Test.csproj index 7dfcc92c..6cd11f1d 100644 --- a/Src/DSInternals.Replication.Test/DSInternals.Replication.Test.csproj +++ b/Src/DSInternals.Replication.Test/DSInternals.Replication.Test.csproj @@ -26,14 +26,12 @@ DEBUG;TRACE prompt 4 - false true TRACE prompt 4 - false diff --git a/Src/DSInternals.SAM/DSInternals.SAM.csproj b/Src/DSInternals.SAM/DSInternals.SAM.csproj index f8b7d1d9..50cbcf6f 100644 --- a/Src/DSInternals.SAM/DSInternals.SAM.csproj +++ b/Src/DSInternals.SAM/DSInternals.SAM.csproj @@ -23,7 +23,6 @@ DEBUG;TRACE prompt 4 - false false MinimumRecommendedRules.ruleset @@ -32,7 +31,6 @@ TRACE prompt 4 - false false false diff --git a/Src/DSInternals.sln b/Src/DSInternals.sln index 38d2cfdd..e8d377e8 100644 --- a/Src/DSInternals.sln +++ b/Src/DSInternals.sln @@ -93,6 +93,7 @@ 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 diff --git a/Src/Microsoft.Database.Isam/Microsoft.Database.Isam.csproj b/Src/Microsoft.Database.Isam/Microsoft.Database.Isam.csproj index 2be9b36f..f333c73e 100644 --- a/Src/Microsoft.Database.Isam/Microsoft.Database.Isam.csproj +++ b/Src/Microsoft.Database.Isam/Microsoft.Database.Isam.csproj @@ -46,7 +46,6 @@ prompt 4 AllRules.ruleset - false true @@ -54,7 +53,6 @@ prompt 4 AllRules.ruleset - false diff --git a/Src/Microsoft.Isam.Esent.Interop/Microsoft.Isam.Esent.Interop.csproj b/Src/Microsoft.Isam.Esent.Interop/Microsoft.Isam.Esent.Interop.csproj index 280d3c97..a0e3c557 100644 --- a/Src/Microsoft.Isam.Esent.Interop/Microsoft.Isam.Esent.Interop.csproj +++ b/Src/Microsoft.Isam.Esent.Interop/Microsoft.Isam.Esent.Interop.csproj @@ -38,7 +38,6 @@ true true AllRules.ruleset - false true @@ -49,7 +48,6 @@ true true AllRules.ruleset - false