3.1.1.4.5.39 msDS-ManagedPassword

The msDS-ManagedPassword attribute exists in AD DS on Windows Server 2012 operating system and later. This attribute contains a BLOB with password information for group-managed service accounts.

Let TO be the object on which msDS-ManagedPassword is being read. If TO is not an msDS-GroupManagedServiceAccount object, then TO!msDS-ManagedPassword is not present. If the DC is not writable, then TO!msDS-ManagedPassword cannot be constructed and the request is forwarded to a writable DC by the RODC.

The value of TO!msDS-ManagedPassword is obtained by calling GetgMSAPasswordBlob(TO) (defined later in this section), which uses the functions defined next.

Define function PostProcessPasswordBuffer(Password: OCTET STRING), which returns an octet string [ITUX680] as follows:

  1. Let RESULT be set to Password, which is a BLOB.

  2. Take RESULT and convert each wide (2-byte) NULL character into a wide value of 1 (0x00 0x01) to guarantee that the resulting string is a Unicode string with no intervening NULL characters that would limit its length.

  3. Set the last wide character in RESULT to NULL to terminate the string.

  4. Return RESULT.

Define function MaxClockSkew(), which returns the integer 3,000,000,000. This is a quantity of 10^(-7) second units of time; that is, five minutes in 100ns units.

Define function GmsaSD(), which returns the security descriptor corresponding to the policy on all msDS-GroupManagedServiceAccount object keys:

 static const BYTE gmsaSecurityDescriptor[] = {/* O:SYD:(A;;FRFW;;;S-1-5-9) */
     0x01, 0x00, 0x04, 0x80, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x14, 0x00, 0x9f, 0x01, 0x12, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x09,
     0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x12, 0x00, 0x00, 0x00
     };

Define function GenerateGmsaPassword(Key: L1 or L2 key ([MS-GKDI] section 2.2.4), HashAlg: null-terminated Unicode string, AccountSID: SID), which returns a password and a key identifier, KeyID, as follows:

  1. Use the same processing rules as defined for KDF ([MS-GKDI] section 3.1.4.1.2) where:

    • HashAlg (for KDF) contains HashAlg.

    • KI contains Key.

    • Label contains the Unicode (wide-character) NULL-terminated string "GMSA PASSWORD" (without the quotes).

    • Context contains the binary representation of the AccountSID parameter.

    • L contains the password length, in bytes, including the terminating NULL.

  2. Return KeyID and the password to the caller.

Define function MarshalPassword(Current_Password: OCTET STRING, Previous_Password: OCTET STRING (optional), QueryPasswordInterval: FILETIME, UnchangedPasswordInterval: FILETIME). This function returns an msDS-ManagedPassword BLOB using the MSDS-MANAGEDPASSWORD_BLOB structure from section 2.2.19, which is constructed as follows:

  • The Version field is set to 0x0001.

  • The Reserved field is set to 0x0000.

  • The Length field is set to the length, in bytes, of the msDS-ManagedPassword BLOB.

  • The CurrentPasswordOffset field is set to the offset, in bytes, from the beginning of this structure to the CurrentPassword field.

  • The PreviousPasswordOffset field is set to the offset, in bytes, from the beginning of this structure to the PreviousPassword field. If the Previous_Password parameter is not included, this field is set to 0x0000.

  • The QueryPasswordIntervalOffset field is set to the offset, in bytes, from the beginning of this structure to the QueryPasswordInterval field.

  • The UnchangedPasswordIntervalOffset field is set to the offset, in bytes, from the beginning of this structure to the UnchangedPasswordInterval field.

  • The CurrentPassword field is set to Current_Password.

  • The PreviousPassword field is set to Previous_Password. If the Previous_Password parameter is not included, then this field MUST be absent.

  • The AlignmentPadding field is constructed with enough bytes of padding to align the QueryPasswordInterval field to a 64-bit boundary.

  • The QueryPasswordInterval field is set to QueryPasswordInterval.

  • The UnchangedPasswordInterval field is set to UnchangedPasswordInterval.

Define function GetIntervalId(TimeStamp: FILETIME), which returns L0KeyID: INTEGER, L1KeyID: INTEGER, and L2KeyID: INTEGER as follows:

  1. Let KeyCycleDuration be the integer 360,000,000,000. This is a quantity of 10^(-7) second units of time; that is, 10 hours in 100ns units.

  2. Let TimeStamp be the number of 100ns intervals since January 1,1601, UTC.

  3. Let L1_KEY_ITERATION be 32.

  4. Let L2_KEY_ITERATION be 32.

  5. L0KeyID = (ULONG)(TimeStamp / KeyCycleDuration / L2_KEY_ITERATION / L1_KEY_ITERATION)

  6. L1KeyID = (TimeStamp / KeyCycleDuration / L2_KEY_ITERATION) & (L1_KEY_ITERATION - 1)

  7. L2KeyID = (TimeStamp / KeyCycleDuration) & (L2_KEY_ITERATION - 1)

  8. Return L0KeyID, L1KeyID, and L2KeyID.

Define function GKDIGetKeyStartTime(KeyID: GUID), which returns a FILETIME structure as follows:

  1. Extract the variables L0, L1, and L2 from the Group Key Envelope structure ([MS-GKDI] section 2.2.4) identified by KeyID. The Group Key Envelope fields of relevance are L0 index, L1 index, and L2 index, respectively.

  2. Let KeyCycleDuration be the integer 360,000,000,000. This is a quantity of 10^(-7) second units of time; that is, 10 hours in 100ns units.

  3. Let L1_KEY_ITERATION be 32.

  4. Let L2_KEY_ITERATION be 32.

  5. Return ((L0 * L1_KEY_ITERATION * L2_KEY_ITERATION) + (L1 * L2_KEY_ITERATION) + L2) * KeyCycleDuration

Define function GetPasswordBasedOnTimeStamp(TimeStamp: FILETIME, AccountSID: SID), which returns an msDS-ManagedPassword BLOB (section 2.2.19) and KeyID: GUID as follows:

  1. Call GetIntervalID() with the supplied TimeStamp to compute variables L0, L1, and L2.

  2. Call GetKey() ([MS-GKDI] section 3.1.4.1) to compute the output key where:

    • hBinding contains an RPC binding handle ([C706] and [MS-RPCE]) to a GKDI server.

    • cbTargetSD contains the length, in bytes, of the security descriptor supplied in pbTargetSD.

    • pbTargetSD contains a pointer to the security descriptor returned by GmsaSD().

    • pRootKeyID is set to NULL.

    • L0KeyID contains L0 returned in step 1.

    • L1KeyID contains L1 returned in step 1.

    • L2KeyID contains L2 returned in step 1.

  3. Compute the group key using the output key from step 2 and the same processing rules as defined in step 2 of [MS-GKDI] section 3.2.4.3.

  4. Call GenerateGmsaPassword() to obtain the password BLOB and KeyID where:

    • Key contains the group key from step 3.

    • HashAlg contains Hash algorithm name from the KDF parameters ([MS-GKDI] section 2.2.1) of the output key from step 2.

    • AccountSID contains the AccountSID parameter passed to this function.

  5. Call PostProcessPasswordBuffer() with the returned password BLOB to make it into a properly NULL-terminated Unicode string.

  6. Return the password BLOB and KeyID to the caller.

Define function GetPasswordBasedOnKeyID(Key-ID: GUID, Account-SID: SID), which returns an msDS-ManagedPassword BLOB (section 2.2.19) as follows:

  1. Extract the variables L0, L1, and L2 and the root key ID from the Group Key Envelope data structure ([MS-GKDI] section 2.2.4) identified by Key-ID. The Group Key Envelope fields of relevance are L0 index, L1 index, L2 index, and Root key identifier, respectively.

  2. Call GetKey() ([MS-GKDI] section 3.1.4.1) to compute the output key where:

    • hBinding contains an RPC binding handle ([C706] and [MS-RPCE]) to a GKDI server.

    • cbTargetSD contains the length, in bytes, of the security descriptor supplied in pbTargetSD.

    • pbTargetSD contains a pointer to the security descriptor returned by GmsaSD().

    • pRootKeyID is set to the root key ID returned in step 1.

    • L0KeyID contains L0 returned in step 1.

    • L1KeyID contains L1 returned in step 1.

    • L2KeyID contains L2 returned in step 1.

  3. Compute the group key using the output key from step 2 and the same processing rules as defined in step 3 of [MS-GKDI] section 3.2.4.3.

  4. Call GenerateGmsaPassword() where:

    • Key contains the group key from step 3.

    • HashAlg contains Hash algorithm name from the KDF parameters ([MS-GKDI] section 2.2.1) of the output key from step 2.

    • AccountSID contains the Account-SID parameter passed to this function.

  5. Call PostProcessPasswordBuffer() to convert the returned BLOB into a properly NULL-terminated Unicode string.

  6. Return the password BLOB to the caller.

Define function GetgMSAPasswordBlob(TO: OBJECT), which returns an msDS-ManagedPassword BLOB structure (section 2.2.19) as follows using integer arithmetic where divisions are rounded down without a remainder.

  1. If the connection is not encrypted, ERROR_DS_CONFIDENTIALITY_REQUIRED is returned.

  2. If the caller does not have the RIGHT_DS_READ_PROPERTY control access right on the security descriptor in the TO!msDS-GroupMSAMembership attribute ([MS-ADA2] section 2.330), the error operationsError / ERROR_DS_CANT_RETRIEVE_ATTRS is returned. This access check is also specified in section 3.1.1.4.4.

  3. Convert the TO!msDS-ManagedPasswordInterval attribute ([MS-ADA2] section 2.381) into the rollover interval as follows:

    1. Let KeyCycleDuration be the integer value 360,000,000,000. This is a quantity of 10^(-7) second units of time; that is, 10 hours in 100ns units.

    2. Let KeyCycleDurationInHours be the integer value 10.

    3. Let GKDIRolloverInterval = (TO!msDS-ManagedPasswordInterval * 24 / KeyCycleDurationInHours) * KeyCycleDuration.

  4. Let a variable called CurrentKeyExpirationTime be computed as follows:

    1. If the TO!msDS-ManagedPasswordId  attribute ([MS-ADA2] section 2.380) exists, call GKDIGetKeyStartTime() where:

      • KeyID contains TO!msDS-ManagedPasswordId.

        Set CurrentKeyExpirationTime = the time returned by GKDIGetKeyStartTime() + GKDIRolloverInterval.

    2. Otherwise, set CurrentKeyExpirationTime = the TO!whenCreated attribute ([MS-ADA3] section 2.371).

  5. If TO!msDS-ManagedPasswordId does not exist or CurrentKeyExpirationTime is less than the current time, then:

    1. Let StaleCount be zero.

    2. Let NewKeyStartTime = CurrentKeyExpirationTime.

    3. Let NewKeyStartTime = NewKeyStartTime + GDKIRolloverInterval and StaleCount = StaleCount +1 until NewKeyStartTime is greater than the current time.

    4. Call GetPasswordBasedOnTimestamp() where:

      • Timestamp contains NewKeyStartTime.

      • AccountSID contains the TO!objectSid attribute ([MS-ADA3] section 2.45).

        Let NewKeyID be the returned KeyID. Set TO!msDS-ManagedPasswordId to the value of NewKeyID. Let NewPassword be the returned password.

    5. Let a variable called OldKeyID be computed as follows:

      1. If StaleCount is zero AND TO!msDS-ManagedPasswordId exists and is not NULL:

        1. Call GetPasswordBasedOnKeyID() where:

          • Key-ID contains TO!msDS-ManagedPasswordId.

          • Account-SID contains TO!objectSid ([MS-ADA3] section 2.45).

        2. Let OldPassword be the returned password and set OldKeyID to the value of TO!msDS-ManagedPasswordId.

      2. Otherwise, if the current time - TO!whenCreated >= GDKIRolloverInterval, the current key cannot be reused as the previous key. Call GetPasswordBasedOnTimeStamp() where:

        • Timestamp contains NewKeyStartTime – GDKIRolloverInterval.

        • AccountSID contains TO!objectSid.

          Set OldKeyID to the returned KeyID. Let OldPassword be the returned password.

      3. Otherwise, the account is not old enough to have a previous password and neither the OldKeyID nor the OldPassword will be returned.

    6. Let variables called QueryPasswordInterval and UnchangedPasswordInterval be computed as follows:

      1. Let NewKeyExpirationTime = NewKeyStartTime + GKDIRolloverInterval.

      2. Call MaxClockSkew() and let MaxClockSkew be the returned value.

      3. If NewKeyExpirationTime - the current time <= MaxClockSkew:

        • Let QueryPasswordInterval be NewKeyExpirationTime - the current time.

        • Let UnchangedPasswordInterval be 0.

      4. Otherwise:

        • Let QueryPasswordInterval be NewKeyExpirationTime - the current time.

        • Let UnchangedPasswordInterval be NewKeyExpirationTime - the current time - MaxClockSkew.

    7. Call MarshalPassword() where:

      • Current_Password contains NewPassword.

      • Previous_Password contains OldPassword.

      • QueryPasswordInterval contains QueryPasswordInterval.

      • UnchangedPasswordInterval contains UnchangedPasswordInterval.

        Return the resulting msDS-ManagedPassword BLOB.

  6. If CurrentKeyExpirationTime - the current time <= MaxClockSkew(), create a new key that will be valid in the next epoch:

    1. Call GetPasswordBasedOnTimeStamp() where:

      • Timestamp contains CurrentKeyExpirationTime.

      • AccountSID contains TO!objectSid.

        Let NewKeyID be the returned KeyID. Let NewPassword be the returned password.

    2. Call GetPasswordBasedOnKeyID() where:

      • Key-ID contains TO!msDS-ManagedPasswordId.

      • Account-SID contains TO!objectSid.

        Let OldPassword be the returned password and do not return OldKeyID.

    3. Let QueryPasswordInterval = CurrentKeyExpirationTime - the current time.

    4. Let UnchangedPasswordInterval = CurrentKeyExpirationTime + GKDIRolloverInterval - MaxClockSkew - the current time.

    5. Call MarshalPassword() where:

      • Current_Password contains NewPassword.

      • Previous_Password contains OldPassword.

      • QueryPasswordInterval contains QueryPasswordInterval.

      • UnchangedPasswordInterval contains UnchangedPasswordInterval.

        Return the resulting msDS-ManagedPassword BLOB.

  7. Otherwise, create a current key:

    1. Call GetPasswordBasedOnKeyID() where:

      • Key-ID contains TO!msDS-ManagedPasswordId.

      • Account-SID contains TO!objectSid.

        Let NewPassword be the returned password.

    2. If the TO!msDS-ManagedPasswordPreviousId attribute ([MS-ADA2] section 2.382) exists, call GetPasswordBasedOnKeyID() where:

      • Key-ID contains TO!msDS-ManagedPasswordPreviousId.

      • Account-SID contains TO!objectSid.

        Let OldPassword be the returned password.

    3. Let QueryPasswordInterval = CurrentKeyExpirationTime - the current time.

    4. Let UnchangedPasswordInterval = CurrentKeyExpirationTime - MaxClockSkew - the current time.

    5. Call MarshalPassword() where:

      • Current_Password contains NewPassword.

      • Previous_Password contains OldPassword.

      • QueryPasswordInterval contains QueryPasswordInterval.

      • UnchangedPasswordInterval contains UnchangedPasswordInterval.

        Return the resulting msDS-ManagedPassword BLOB.