2 Unix SMB/CIFS implementation.
3 msDS-ManagedPassword attribute for Group Managed Service Accounts
5 Copyright (C) Catalyst.Net Ltd 2024
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <https://www.gnu.org/licenses/>.
23 #include "ldb_module.h"
24 #include "ldb_errors.h"
25 #include "ldb_private.h"
26 #include "lib/crypto/gkdi.h"
27 #include "lib/crypto/gmsa.h"
28 #include "lib/util/data_blob.h"
29 #include "lib/util/fault.h"
30 #include "lib/util/time.h"
31 #include "libcli/security/access_check.h"
32 #include "librpc/gen_ndr/auth.h"
33 #include "librpc/gen_ndr/ndr_gkdi.h"
34 #include "librpc/gen_ndr/ndr_gmsa.h"
35 #include "librpc/gen_ndr/ndr_security.h"
36 #include "dsdb/common/util.h"
37 #include "dsdb/gmsa/gkdi.h"
38 #include "dsdb/gmsa/util.h"
39 #include "dsdb/samdb/samdb.h"
52 enum RootKeyType type
;
54 struct KeyEnvelopeId specific
;
56 NTTIME key_start_time
;
59 struct gmsa_update_pwd_part key
;
60 struct gmsa_null_terminated_password
*password
;
65 static const struct RootKey empty_root_key
= {.type
= ROOT_KEY_NONE
};
67 int gmsa_allowed_to_view_managed_password(TALLOC_CTX
*mem_ctx
,
68 struct ldb_context
*ldb
,
69 const struct ldb_message
*msg
,
70 const struct dom_sid
*account_sid
,
73 TALLOC_CTX
*tmp_ctx
= NULL
;
74 struct security_descriptor group_msa_membership_sd
= {};
75 const struct security_token
*user_token
= NULL
;
76 NTSTATUS status
= NT_STATUS_OK
;
77 int ret
= LDB_SUCCESS
;
79 if (allowed_out
== NULL
) {
86 const struct auth_session_info
*session_info
= ldb_get_opaque(
87 ldb
, DSDB_SESSION_INFO
);
88 const enum security_user_level level
=
89 security_session_user_level(session_info
, NULL
);
91 if (level
== SECURITY_SYSTEM
) {
97 if (session_info
== NULL
) {
98 ret
= dsdb_werror(ldb
,
99 LDB_ERR_OPERATIONS_ERROR
,
100 WERR_DS_CANT_RETRIEVE_ATTS
,
101 "no right to view attribute");
105 user_token
= session_info
->security_token
;
108 tmp_ctx
= talloc_new(msg
);
109 if (tmp_ctx
== NULL
) {
115 const struct ldb_val
*group_msa_membership
= NULL
;
116 enum ndr_err_code err
;
118 /* [MS-ADTS] 3.1.1.4.4: Extended Access Checks. */
119 group_msa_membership
= ldb_msg_find_ldb_val(
120 msg
, "msDS-GroupMSAMembership");
121 if (group_msa_membership
== NULL
) {
122 ret
= dsdb_werror(ldb
,
123 LDB_ERR_OPERATIONS_ERROR
,
124 WERR_DS_CANT_RETRIEVE_ATTS
,
125 "no right to view attribute");
129 err
= ndr_pull_struct_blob_all(
130 group_msa_membership
,
132 &group_msa_membership_sd
,
133 (ndr_pull_flags_fn_t
)ndr_pull_security_descriptor
);
134 if (!NDR_ERR_CODE_IS_SUCCESS(err
)) {
135 status
= ndr_map_error2ntstatus(err
);
136 DBG_WARNING("msDS-GroupMSAMembership pull failed: %s\n",
138 ret
= ldb_operr(ldb
);
144 const uint32_t access_desired
= SEC_ADS_READ_PROP
;
145 uint32_t access_granted
= 0;
147 status
= sec_access_check_ds(&group_msa_membership_sd
,
153 if (NT_STATUS_EQUAL(status
, NT_STATUS_ACCESS_DENIED
)) {
155 * The principal is not allowed to view the managed
158 } else if (!NT_STATUS_IS_OK(status
)) {
159 DBG_WARNING("msDS-GroupMSAMembership: "
160 "sec_access_check_ds(access_desired=%#08x, "
161 "access_granted:%#08x) failed with: %s\n",
168 LDB_ERR_OPERATIONS_ERROR
,
169 WERR_DS_CANT_RETRIEVE_ATTS
,
170 "access check to view managed password failed");
173 /* Cool, the principal may view the password. */
179 TALLOC_FREE(tmp_ctx
);
183 static NTSTATUS
gmsa_managed_pwd_id(struct ldb_context
*ldb
,
185 const struct ldb_val
*pwd_id_blob
,
186 const struct ProvRootKey
*root_key
,
187 struct KeyEnvelope
*pwd_id_out
)
189 if (root_key
== NULL
) {
190 return NT_STATUS_INVALID_PARAMETER
;
193 if (pwd_id_blob
!= NULL
) {
194 return gkdi_pull_KeyEnvelope(mem_ctx
, pwd_id_blob
, pwd_id_out
);
198 const char *domain_name
= NULL
;
199 const char *forest_name
= NULL
;
201 domain_name
= samdb_default_domain_name(ldb
, mem_ctx
);
202 if (domain_name
== NULL
) {
203 return NT_STATUS_NO_MEMORY
;
206 forest_name
= samdb_forest_name(ldb
, mem_ctx
);
207 if (forest_name
== NULL
) {
208 /* We leak ‘domain_name’, but that can’t be helped. */
209 return NT_STATUS_NO_MEMORY
;
212 *pwd_id_out
= (struct KeyEnvelope
){
213 .version
= root_key
->version
,
214 .flags
= ENVELOPE_FLAG_KEY_MAY_ENCRYPT_NEW_DATA
,
215 .domain_name
= domain_name
,
216 .forest_name
= forest_name
,
223 void gmsa_update_managed_pwd_id(struct KeyEnvelope
*pwd_id
,
224 const struct gmsa_update_pwd_part
*new_pwd
)
226 pwd_id
->l0_index
= new_pwd
->gkid
.l0_idx
;
227 pwd_id
->l1_index
= new_pwd
->gkid
.l1_idx
;
228 pwd_id
->l2_index
= new_pwd
->gkid
.l2_idx
;
229 pwd_id
->root_key_id
= new_pwd
->root_key
->id
;
232 NTSTATUS
gmsa_pack_managed_pwd_id(TALLOC_CTX
*mem_ctx
,
233 const struct KeyEnvelope
*pwd_id
,
234 DATA_BLOB
*pwd_id_out
)
236 NTSTATUS status
= NT_STATUS_OK
;
237 enum ndr_err_code err
;
239 err
= ndr_push_struct_blob(pwd_id_out
,
242 (ndr_push_flags_fn_t
)ndr_push_KeyEnvelope
);
243 status
= ndr_map_error2ntstatus(err
);
244 if (!NT_STATUS_IS_OK(status
)) {
245 DBG_WARNING("KeyEnvelope push failed: %s\n", nt_errstr(status
));
251 static int gmsa_specific_password(TALLOC_CTX
*mem_ctx
,
252 struct ldb_context
*ldb
,
253 const struct KeyEnvelopeId pwd_id
,
254 struct gmsa_update_pwd_part
*new_pwd_out
)
256 TALLOC_CTX
*tmp_ctx
= NULL
;
257 NTSTATUS status
= NT_STATUS_OK
;
258 int ret
= LDB_SUCCESS
;
260 tmp_ctx
= talloc_new(mem_ctx
);
261 if (tmp_ctx
== NULL
) {
267 const struct ldb_message
*root_key_msg
= NULL
;
269 ret
= gkdi_root_key_from_id(tmp_ctx
,
277 status
= gkdi_root_key_from_msg(mem_ctx
,
280 &new_pwd_out
->root_key
);
281 if (!NT_STATUS_IS_OK(status
)) {
282 ret
= ldb_operr(ldb
);
287 new_pwd_out
->gkid
= pwd_id
.gkid
;
290 talloc_free(tmp_ctx
);
294 static int gmsa_nonspecific_password(TALLOC_CTX
*mem_ctx
,
295 struct ldb_context
*ldb
,
296 const NTTIME key_start_time
,
297 const NTTIME current_time
,
298 struct gmsa_update_pwd_part
*new_pwd_out
)
300 TALLOC_CTX
*tmp_ctx
= NULL
;
301 int ret
= LDB_SUCCESS
;
303 tmp_ctx
= talloc_new(mem_ctx
);
304 if (tmp_ctx
== NULL
) {
310 const struct ldb_message
*root_key_msg
= NULL
;
311 struct GUID root_key_id
;
312 NTSTATUS status
= NT_STATUS_OK
;
314 ret
= gkdi_most_recently_created_root_key(tmp_ctx
,
324 status
= gkdi_root_key_from_msg(mem_ctx
,
327 &new_pwd_out
->root_key
);
328 if (!NT_STATUS_IS_OK(status
)) {
329 ret
= ldb_operr(ldb
);
334 new_pwd_out
->gkid
= gkdi_get_interval_id(key_start_time
);
337 talloc_free(tmp_ctx
);
341 static int gmsa_specifc_root_key(TALLOC_CTX
*mem_ctx
,
342 const struct KeyEnvelopeId pwd_id
,
343 struct RootKey
*root_key_out
)
345 if (root_key_out
== NULL
) {
346 return LDB_ERR_OPERATIONS_ERROR
;
349 *root_key_out
= (struct RootKey
){.mem_ctx
= mem_ctx
,
350 .type
= ROOT_KEY_SPECIFIC
,
351 .u
.specific
= pwd_id
};
355 static int gmsa_nonspecifc_root_key(TALLOC_CTX
*mem_ctx
,
356 const NTTIME key_start_time
,
357 struct RootKey
*root_key_out
)
359 if (root_key_out
== NULL
) {
360 return LDB_ERR_OPERATIONS_ERROR
;
363 *root_key_out
= (struct RootKey
){
365 .type
= ROOT_KEY_NONSPECIFIC
,
366 .u
.nonspecific
.key_start_time
= key_start_time
};
370 static int gmsa_obtained_root_key_steal(
372 const struct gmsa_update_pwd_part key
,
373 struct gmsa_null_terminated_password
*password
,
374 struct RootKey
*root_key_out
)
376 if (root_key_out
== NULL
) {
377 return LDB_ERR_OPERATIONS_ERROR
;
380 /* Steal the data on to the appropriate memory context. */
381 talloc_steal(mem_ctx
, key
.root_key
);
382 talloc_steal(mem_ctx
, password
);
384 *root_key_out
= (struct RootKey
){.mem_ctx
= mem_ctx
,
385 .type
= ROOT_KEY_OBTAINED
,
386 .u
.obtained
= {.key
= key
,
387 .password
= password
}};
391 static int gmsa_fetch_root_key(struct ldb_context
*ldb
,
392 const NTTIME current_time
,
393 struct RootKey
*root_key
,
394 const struct dom_sid
*const account_sid
)
396 TALLOC_CTX
*tmp_ctx
= NULL
;
397 NTSTATUS status
= NT_STATUS_OK
;
398 int ret
= LDB_SUCCESS
;
400 if (root_key
== NULL
) {
401 ret
= ldb_operr(ldb
);
405 switch (root_key
->type
) {
406 case ROOT_KEY_SPECIFIC
:
407 case ROOT_KEY_NONSPECIFIC
: {
408 struct gmsa_null_terminated_password
*password
= NULL
;
409 struct gmsa_update_pwd_part key
;
411 tmp_ctx
= talloc_new(NULL
);
412 if (tmp_ctx
== NULL
) {
417 if (root_key
->type
== ROOT_KEY_SPECIFIC
) {
418 /* Search for a specific root key. */
419 ret
= gmsa_specific_password(tmp_ctx
,
421 root_key
->u
.specific
,
425 * We couldn’t find a specific key — treat this
432 * Search for the most recent root key meeting the start
435 ret
= gmsa_nonspecific_password(
438 root_key
->u
.nonspecific
.key_start_time
,
441 /* Handle errors below. */
443 if (ret
== LDB_ERR_NO_SUCH_OBJECT
) {
445 * We couldn’t find a key meeting the requirements —
446 * that’s OK, presumably. It’s not critical if we can’t
447 * find a key for deriving a previous gMSA password, for
451 *root_key
= empty_root_key
;
455 /* Derive the password. */
456 status
= gmsa_talloc_password_based_on_key_id(
463 if (!NT_STATUS_IS_OK(status
)) {
464 ret
= ldb_operr(ldb
);
469 * Initialize the obtained structure, and give it the
470 * appropriate memory context.
472 ret
= gmsa_obtained_root_key_steal(root_key
->mem_ctx
,
482 /* No key is available. */
484 case ROOT_KEY_OBTAINED
:
485 /* The key has already been obtained. */
488 ret
= ldb_operr(ldb
);
493 TALLOC_FREE(tmp_ctx
);
498 * Get the password and update information associated with a root key. The
499 * caller *does not* own these structures; the root key object retains
502 static int gmsa_get_root_key(
503 struct ldb_context
*ldb
,
504 const NTTIME current_time
,
505 const struct dom_sid
*const account_sid
,
506 struct RootKey
*root_key
,
507 struct gmsa_null_terminated_password
**password_out
,
508 struct gmsa_update_pwd_part
*update_out
)
510 int ret
= LDB_SUCCESS
;
512 if (password_out
== NULL
) {
513 ret
= ldb_operr(ldb
);
516 *password_out
= NULL
;
518 if (update_out
!= NULL
) {
519 *update_out
= (struct gmsa_update_pwd_part
){};
522 /* Fetch the root key from the database and obtain the password. */
523 ret
= gmsa_fetch_root_key(ldb
, current_time
, root_key
, account_sid
);
528 switch (root_key
->type
) {
530 /* No key is available. */
532 case ROOT_KEY_OBTAINED
:
533 *password_out
= root_key
->u
.obtained
.password
;
534 if (update_out
!= NULL
) {
535 *update_out
= root_key
->u
.obtained
.key
;
540 ret
= ldb_operr(ldb
);
548 static int gmsa_system_update_password_id_req(
549 struct ldb_context
*ldb
,
551 const struct ldb_message
*msg
,
552 const struct gmsa_update_pwd
*new_pwd
,
553 struct ldb_request
**req_out
)
555 TALLOC_CTX
*tmp_ctx
= NULL
;
556 const struct ldb_val
*pwd_id_blob
= ldb_msg_find_ldb_val(
557 msg
, "msDS-ManagedPasswordId");
558 struct KeyEnvelope pwd_id
;
559 struct ldb_message
*mod_msg
= NULL
;
560 NTSTATUS status
= NT_STATUS_OK
;
561 int ret
= LDB_SUCCESS
;
563 tmp_ctx
= talloc_new(mem_ctx
);
564 if (tmp_ctx
== NULL
) {
569 /* Create a new ldb message. */
570 mod_msg
= ldb_msg_new(tmp_ctx
);
571 if (mod_msg
== NULL
) {
576 struct ldb_dn
*dn
= ldb_dn_copy(mod_msg
, msg
->dn
);
584 /* Get the Managed Password ID. */
585 status
= gmsa_managed_pwd_id(
586 ldb
, tmp_ctx
, pwd_id_blob
, new_pwd
->new_id
.root_key
, &pwd_id
);
587 if (!NT_STATUS_IS_OK(status
)) {
588 ret
= ldb_operr(ldb
);
592 /* Update the password ID to contain the new GKID and root key ID. */
593 gmsa_update_managed_pwd_id(&pwd_id
, &new_pwd
->new_id
);
596 DATA_BLOB new_pwd_id_blob
= {};
598 /* Pack the current password ID. */
599 status
= gmsa_pack_managed_pwd_id(tmp_ctx
,
602 if (!NT_STATUS_IS_OK(status
)) {
603 ret
= ldb_operr(ldb
);
607 /* Update the msDS-ManagedPasswordId attribute. */
608 ret
= ldb_msg_append_steal_value(mod_msg
,
609 "msDS-ManagedPasswordId",
611 LDB_FLAG_MOD_REPLACE
);
618 DATA_BLOB
*prev_pwd_id_blob
= NULL
;
619 DATA_BLOB _prev_pwd_id_blob
;
620 DATA_BLOB prev_pwd_id
= {};
622 if (new_pwd
->prev_id
.root_key
!= NULL
) {
624 * Update the password ID to contain the previous GKID
627 gmsa_update_managed_pwd_id(&pwd_id
, &new_pwd
->prev_id
);
629 /* Pack the previous password ID. */
630 status
= gmsa_pack_managed_pwd_id(tmp_ctx
,
633 if (!NT_STATUS_IS_OK(status
)) {
634 ret
= ldb_operr(ldb
);
638 prev_pwd_id_blob
= &prev_pwd_id
;
639 } else if (pwd_id_blob
!= NULL
) {
640 /* Copy the current password ID to the previous ID. */
641 _prev_pwd_id_blob
= ldb_val_dup(tmp_ctx
, pwd_id_blob
);
642 if (_prev_pwd_id_blob
.length
!= pwd_id_blob
->length
) {
647 prev_pwd_id_blob
= &_prev_pwd_id_blob
;
650 if (prev_pwd_id_blob
!= NULL
) {
652 * Update the msDS-ManagedPasswordPreviousId attribute.
654 ret
= ldb_msg_append_steal_value(
656 "msDS-ManagedPasswordPreviousId",
658 LDB_FLAG_MOD_REPLACE
);
666 struct ldb_request
*req
= NULL
;
668 /* Build the ldb request to return. */
669 ret
= ldb_build_mod_req(&req
,
675 ldb_op_default_callback
,
681 /* Tie the lifetime of the message to that of the request. */
682 talloc_steal(req
, mod_msg
);
684 /* Make sure the password ID update happens as System. */
685 ret
= dsdb_request_add_controls(req
, DSDB_FLAG_AS_SYSTEM
);
690 *req_out
= talloc_steal(mem_ctx
, req
);
694 talloc_free(tmp_ctx
);
698 int gmsa_generate_blobs(struct ldb_context
*ldb
,
700 const NTTIME current_time
,
701 const struct dom_sid
*const account_sid
,
702 DATA_BLOB
*pwd_id_blob_out
,
703 struct gmsa_null_terminated_password
**password_out
)
705 TALLOC_CTX
*tmp_ctx
= NULL
;
706 struct KeyEnvelope pwd_id
;
707 const struct ProvRootKey
*root_key
= NULL
;
708 NTSTATUS status
= NT_STATUS_OK
;
709 int ret
= LDB_SUCCESS
;
711 tmp_ctx
= talloc_new(mem_ctx
);
712 if (tmp_ctx
== NULL
) {
718 const struct ldb_message
*root_key_msg
= NULL
;
719 struct GUID root_key_id
;
720 const NTTIME one_interval
= gkdi_key_cycle_duration
+
722 const NTTIME one_interval_ago
= current_time
-
723 MIN(one_interval
, current_time
);
725 ret
= gkdi_most_recently_created_root_key(tmp_ctx
,
735 status
= gkdi_root_key_from_msg(tmp_ctx
,
739 if (!NT_STATUS_IS_OK(status
)) {
740 ret
= ldb_operr(ldb
);
745 /* Get the Managed Password ID. */
746 status
= gmsa_managed_pwd_id(ldb
, tmp_ctx
, NULL
, root_key
, &pwd_id
);
747 if (!NT_STATUS_IS_OK(status
)) {
748 ret
= ldb_operr(ldb
);
753 const struct Gkid current_gkid
= gkdi_get_interval_id(
756 /* Derive the password. */
757 status
= gmsa_talloc_password_based_on_key_id(tmp_ctx
,
763 if (!NT_STATUS_IS_OK(status
)) {
764 ret
= ldb_operr(ldb
);
769 const struct gmsa_update_pwd_part new_id
= {
770 .root_key
= root_key
,
771 .gkid
= current_gkid
,
775 * Update the password ID to contain the new GKID and
778 gmsa_update_managed_pwd_id(&pwd_id
, &new_id
);
782 /* Pack the current password ID. */
783 status
= gmsa_pack_managed_pwd_id(mem_ctx
, &pwd_id
, pwd_id_blob_out
);
784 if (!NT_STATUS_IS_OK(status
)) {
785 ret
= ldb_operr(ldb
);
789 /* Transfer ownership of the password to the caller’s memory context. */
790 talloc_steal(mem_ctx
, *password_out
);
793 talloc_free(tmp_ctx
);
797 static int gmsa_create_update(TALLOC_CTX
*mem_ctx
,
798 struct ldb_context
*ldb
,
799 const struct ldb_message
*msg
,
800 const NTTIME current_time
,
801 const struct dom_sid
*account_sid
,
802 const bool current_key_becomes_previous
,
803 struct RootKey
*current_key
,
804 struct RootKey
*previous_key
,
805 struct gmsa_update
**update_out
)
807 TALLOC_CTX
*tmp_ctx
= NULL
;
808 struct ldb_request
*old_pw_req
= NULL
;
809 struct ldb_request
*new_pw_req
= NULL
;
810 struct ldb_request
*pwd_id_req
= NULL
;
811 struct gmsa_update_pwd new_pwd
= {};
812 struct gmsa_update
*update
= NULL
;
813 NTSTATUS status
= NT_STATUS_OK
;
814 int ret
= LDB_SUCCESS
;
816 if (update_out
== NULL
) {
817 ret
= ldb_operr(ldb
);
822 if (current_key
== NULL
) {
823 ret
= ldb_operr(ldb
);
827 tmp_ctx
= talloc_new(mem_ctx
);
828 if (tmp_ctx
== NULL
) {
835 * The password_hash module expects these passwords to be
838 struct gmsa_null_terminated_password
*new_password
= NULL
;
840 ret
= gmsa_get_root_key(ldb
,
850 if (new_password
== NULL
) {
851 ret
= ldb_operr(ldb
);
855 status
= gmsa_system_password_update_request(
856 ldb
, tmp_ctx
, msg
->dn
, new_password
->buf
, &new_pw_req
);
857 if (!NT_STATUS_IS_OK(status
)) {
858 ret
= ldb_operr(ldb
);
863 /* Does the previous password need to be updated? */
864 if (current_key_becomes_previous
) {
866 * When we perform the password set, the now‐current password
867 * will become the previous password automatically. We don’t
868 * have to manage that ourselves.
871 struct gmsa_null_terminated_password
*old_password
= NULL
;
873 /* The current key cannot be reused as the previous key. */
874 ret
= gmsa_get_root_key(ldb
,
884 if (old_password
!= NULL
) {
885 status
= gmsa_system_password_update_request(
891 if (!NT_STATUS_IS_OK(status
)) {
892 ret
= ldb_operr(ldb
);
898 /* Ready the update of the msDS-ManagedPasswordId attribute. */
899 ret
= gmsa_system_update_password_id_req(
900 ldb
, tmp_ctx
, msg
, &new_pwd
, &pwd_id_req
);
905 update
= talloc(tmp_ctx
, struct gmsa_update
);
906 if (update
== NULL
) {
911 *update
= (struct gmsa_update
){
912 .old_pw_req
= talloc_steal(update
, old_pw_req
),
913 .new_pw_req
= talloc_steal(update
, new_pw_req
),
914 .pwd_id_req
= talloc_steal(update
, pwd_id_req
)};
916 *update_out
= talloc_steal(mem_ctx
, update
);
919 TALLOC_FREE(tmp_ctx
);
923 NTSTATUS
gmsa_pack_managed_pwd(TALLOC_CTX
*mem_ctx
,
924 const uint8_t *new_password
,
925 const uint8_t *old_password
,
926 uint64_t query_interval
,
927 uint64_t unchanged_interval
,
928 DATA_BLOB
*managed_pwd_out
)
930 const struct MANAGEDPASSWORD_BLOB managed_pwd
= {
931 .passwords
= {.current
= new_password
,
932 .previous
= old_password
,
933 .query_interval
= &query_interval
,
934 .unchanged_interval
= &unchanged_interval
}};
935 NTSTATUS status
= NT_STATUS_OK
;
936 enum ndr_err_code err
;
938 err
= ndr_push_struct_blob(managed_pwd_out
,
941 (ndr_push_flags_fn_t
)
942 ndr_push_MANAGEDPASSWORD_BLOB
);
943 status
= ndr_map_error2ntstatus(err
);
944 if (!NT_STATUS_IS_OK(status
)) {
945 DBG_WARNING("MANAGEDPASSWORD_BLOB push failed: %s\n",
952 bool dsdb_account_is_gmsa(struct ldb_context
*ldb
,
953 const struct ldb_message
*msg
)
956 * Check if the account has objectClass
957 * ‘msDS-GroupManagedServiceAccount’.
959 return samdb_find_attribute(ldb
,
962 "msDS-GroupManagedServiceAccount") != NULL
;
965 static struct new_key
{
967 bool immediately_follows_previous
;
968 } calculate_new_key(const NTTIME current_time
,
969 const NTTIME current_key_expiration_time
,
970 const NTTIME rollover_interval
)
972 NTTIME new_key_start_time
= current_key_expiration_time
;
973 bool immediately_follows_previous
= false;
975 if (new_key_start_time
< current_time
&& rollover_interval
) {
977 * Advance the key start time by the rollover interval until it
978 * would be greater than the current time.
980 const NTTIME time_to_advance_by
= current_time
+ 1 -
982 const uint64_t stale_count
= time_to_advance_by
/
984 new_key_start_time
+= stale_count
* rollover_interval
;
986 SMB_ASSERT(new_key_start_time
<= current_time
);
988 immediately_follows_previous
= stale_count
== 0;
991 * It is possible that new_key_start_time ≥ current_time;
992 * specifically, if there is no password ID, and the creation
993 * time of the gMSA is in the future (perhaps due to replication
998 return (struct new_key
){
999 .start_time
= new_key_start_time
,
1000 .immediately_follows_previous
= immediately_follows_previous
};
1003 static bool gmsa_creation_time(const struct ldb_message
*msg
,
1004 const NTTIME current_time
,
1005 NTTIME
*creation_time_out
)
1007 const struct ldb_val
*when_created
= NULL
;
1008 time_t creation_unix_time
;
1011 when_created
= ldb_msg_find_ldb_val(msg
, "whenCreated");
1012 ret
= ldb_val_to_time(when_created
, &creation_unix_time
);
1014 /* Fail if we can’t read the attribute or it isn’t present. */
1018 unix_to_nt_time(creation_time_out
, creation_unix_time
);
1022 static const struct KeyEnvelopeId
*gmsa_get_managed_pwd_id_attr_name(
1023 const struct ldb_message
*msg
,
1024 const char *attr_name
,
1025 struct KeyEnvelopeId
*key_env_out
)
1027 const struct ldb_val
*pwd_id_blob
= ldb_msg_find_ldb_val(msg
,
1029 if (pwd_id_blob
== NULL
) {
1033 return gkdi_pull_KeyEnvelopeId(*pwd_id_blob
, key_env_out
);
1036 const struct KeyEnvelopeId
*gmsa_get_managed_pwd_id(
1037 const struct ldb_message
*msg
,
1038 struct KeyEnvelopeId
*key_env_out
)
1040 return gmsa_get_managed_pwd_id_attr_name(msg
,
1041 "msDS-ManagedPasswordId",
1045 static const struct KeyEnvelopeId
*gmsa_get_managed_pwd_prev_id(
1046 const struct ldb_message
*msg
,
1047 struct KeyEnvelopeId
*key_env_out
)
1049 return gmsa_get_managed_pwd_id_attr_name(
1050 msg
, "msDS-ManagedPasswordPreviousId", key_env_out
);
1053 static bool samdb_result_gkdi_rollover_interval(const struct ldb_message
*msg
,
1054 NTTIME
*rollover_interval_out
)
1056 int64_t managed_password_interval
;
1058 managed_password_interval
= ldb_msg_find_attr_as_int64(
1059 msg
, "msDS-ManagedPasswordInterval", 30);
1060 return gkdi_rollover_interval(managed_password_interval
,
1061 rollover_interval_out
);
1064 int gmsa_recalculate_managed_pwd(TALLOC_CTX
*mem_ctx
,
1065 struct ldb_context
*ldb
,
1066 const struct ldb_message
*msg
,
1067 const NTTIME current_time
,
1068 struct gmsa_update
**update_out
,
1069 struct gmsa_return_pwd
*return_out
)
1071 TALLOC_CTX
*tmp_ctx
= NULL
;
1072 int ret
= LDB_SUCCESS
;
1073 NTTIME rollover_interval
;
1074 NTTIME current_key_expiration_time
;
1075 NTTIME key_expiration_time
;
1076 struct dom_sid account_sid
;
1077 struct KeyEnvelopeId pwd_id_buf
;
1078 const struct KeyEnvelopeId
*pwd_id
= NULL
;
1079 struct RootKey previous_key
= empty_root_key
;
1080 struct RootKey current_key
= empty_root_key
;
1081 struct gmsa_update
*update
= NULL
;
1082 struct gmsa_null_terminated_password
*previous_password
= NULL
;
1083 struct gmsa_null_terminated_password
*current_password
= NULL
;
1084 NTTIME query_interval
= 0;
1085 NTTIME unchanged_interval
= 0;
1086 NTTIME creation_time
= 0;
1087 NTTIME account_age
= 0;
1088 NTTIME key_start_time
= 0;
1089 bool have_key_start_time
= false;
1091 bool current_key_is_valid
= false;
1093 if (update_out
== NULL
) {
1094 ret
= ldb_operr(ldb
);
1100 /* Is the account a Group Managed Service Account? */
1101 const bool is_gmsa
= dsdb_account_is_gmsa(ldb
, msg
);
1103 /* It’s not a GMSA — we’re done here. */
1105 if (return_out
!= NULL
) {
1106 *return_out
= (struct gmsa_return_pwd
){};
1113 /* Calculate the rollover interval. */
1114 ok
= samdb_result_gkdi_rollover_interval(msg
, &rollover_interval
);
1115 if (!ok
|| rollover_interval
== 0) {
1116 /* We can’t do anything if the rollover interval is zero. */
1117 ret
= ldb_operr(ldb
);
1121 ok
= gmsa_creation_time(msg
, current_time
, &creation_time
);
1123 return ldb_error(ldb
,
1124 LDB_ERR_OPERATIONS_ERROR
,
1125 "unable to determine creation time of Group "
1126 "Managed Service Account");
1128 account_age
= current_time
- MIN(creation_time
, current_time
);
1130 /* Calculate the expiration time of the current key. */
1131 pwd_id
= gmsa_get_managed_pwd_id(msg
, &pwd_id_buf
);
1132 if (pwd_id
!= NULL
&&
1133 gkdi_get_key_start_time(pwd_id
->gkid
, &key_start_time
))
1135 have_key_start_time
= true;
1137 /* Check for overflow. */
1138 if (key_start_time
> UINT64_MAX
- rollover_interval
) {
1139 ret
= ldb_operr(ldb
);
1142 current_key_expiration_time
= key_start_time
+
1146 * [MS-ADTS] does not say to use gkdi_get_interval_start_time(),
1147 * but surely it makes no sense to have keys starting or ending
1150 current_key_expiration_time
= gkdi_get_interval_start_time(
1154 /* Fetch the account’s SID, necessary for deriving passwords. */
1155 ret
= samdb_result_dom_sid_buf(msg
, "objectSid", &account_sid
);
1160 tmp_ctx
= talloc_new(mem_ctx
);
1161 if (tmp_ctx
== NULL
) {
1167 * In determining whether the account’s passwords should be updated, we
1168 * do not validate that the unicodePwd attribute is up‐to‐date, or even
1169 * that it exists. We rely entirely on the fact that the managed
1170 * password ID should be updated *only* as part of a successful gMSA
1171 * password update. In any case, unicodePwd is optional in Samba — save
1172 * for machine accounts (which gMSAs are :)) — and we can’t always rely
1175 * All this means that an admin (or a DC that doesn’t support gMSAs)
1176 * could reset a gMSA’s password outside of the normal procedure, and
1177 * the account would then have the wrong password until the key was due
1178 * to roll over again. There’s nothing much we can do about this if we
1179 * don’t want to re‐derive and verify the password every time we look up
1184 * Administrators should be careful not to set a DC’s clock too far in
1185 * the future, or a gMSA’s keys may be stuck at that future time and
1186 * stop updating until said time rolls around for real.
1189 current_key_is_valid
= pwd_id
!= NULL
&&
1190 current_time
< current_key_expiration_time
;
1191 if (current_key_is_valid
) {
1192 key_expiration_time
= current_key_expiration_time
;
1194 if (return_out
!= NULL
) {
1195 struct KeyEnvelopeId prev_pwd_id_buf
;
1196 const struct KeyEnvelopeId
*prev_pwd_id
= NULL
;
1198 ret
= gmsa_specifc_root_key(tmp_ctx
,
1205 if (account_age
>= rollover_interval
) {
1206 prev_pwd_id
= gmsa_get_managed_pwd_prev_id(
1207 msg
, &prev_pwd_id_buf
);
1208 if (prev_pwd_id
!= NULL
) {
1209 ret
= gmsa_specifc_root_key(
1216 } else if (have_key_start_time
&&
1217 key_start_time
>= rollover_interval
)
1220 * The account’s old enough to have a
1221 * previous password, but it doesn’t
1222 * have a previous password ID for some
1223 * reason. This can happen in our tests
1224 * (python/samba/krb5/gmsa_tests.py)
1225 * when we’re mucking about with times.
1226 * Just produce what would have been the
1229 ret
= gmsa_nonspecifc_root_key(
1240 * The account is not old enough to have a
1241 * previous password. The old password will not
1247 /* Calculate the start time of the new key. */
1248 const struct new_key new_key
= calculate_new_key(
1250 current_key_expiration_time
,
1252 const bool current_key_becomes_previous
=
1253 pwd_id
!= NULL
&& new_key
.immediately_follows_previous
;
1255 /* Check for overflow. */
1256 if (new_key
.start_time
> UINT64_MAX
- rollover_interval
) {
1257 ret
= ldb_operr(ldb
);
1260 key_expiration_time
= new_key
.start_time
+ rollover_interval
;
1262 ret
= gmsa_nonspecifc_root_key(tmp_ctx
,
1269 if (account_age
>= rollover_interval
) {
1270 /* Check for underflow. */
1271 if (new_key
.start_time
< rollover_interval
) {
1272 ret
= ldb_operr(ldb
);
1275 ret
= gmsa_nonspecifc_root_key(
1277 new_key
.start_time
- rollover_interval
,
1284 * The account is not old enough to have a previous
1285 * password. The old password will not be returned.
1290 * The current GMSA key, according to the Managed Password ID,
1291 * is no longer valid. We should update the account’s Managed
1292 * Password ID and keys in anticipation of their being needed in
1296 ret
= gmsa_create_update(tmp_ctx
,
1301 current_key_becomes_previous
,
1310 if (return_out
!= NULL
) {
1311 bool return_future_key
;
1313 unchanged_interval
= query_interval
= key_expiration_time
-
1315 key_expiration_time
);
1317 /* Derive the current and previous passwords. */
1318 return_future_key
= query_interval
<= gkdi_max_clock_skew
;
1319 if (return_future_key
) {
1320 struct RootKey future_key
= empty_root_key
;
1323 * The current key hasn’t expired yet, but it
1324 * soon will. Return a new key that will be valid in the
1328 ret
= gmsa_nonspecifc_root_key(tmp_ctx
,
1329 key_expiration_time
,
1335 ret
= gmsa_get_root_key(ldb
,
1345 ret
= gmsa_get_root_key(ldb
,
1355 /* Check for overflow. */
1356 if (unchanged_interval
> UINT64_MAX
- rollover_interval
)
1358 ret
= ldb_operr(ldb
);
1361 unchanged_interval
+= rollover_interval
;
1364 * Note that a gMSA will become unusable (at least until
1365 * the next rollover) if its associated root key is ever
1369 ret
= gmsa_get_root_key(ldb
,
1379 ret
= gmsa_get_root_key(ldb
,
1390 unchanged_interval
-= MIN(gkdi_max_clock_skew
,
1391 unchanged_interval
);
1394 *update_out
= talloc_steal(mem_ctx
, update
);
1395 if (return_out
!= NULL
) {
1396 *return_out
= (struct gmsa_return_pwd
){
1397 .prev_pwd
= talloc_steal(mem_ctx
, previous_password
),
1398 .new_pwd
= talloc_steal(mem_ctx
, current_password
),
1399 .query_interval
= query_interval
,
1400 .unchanged_interval
= unchanged_interval
,
1405 TALLOC_FREE(tmp_ctx
);
1409 bool dsdb_gmsa_current_time(struct ldb_context
*ldb
, NTTIME
*current_time_out
)
1411 const unsigned long long *gmsa_time
= talloc_get_type(
1412 ldb_get_opaque(ldb
, DSDB_GMSA_TIME_OPAQUE
), unsigned long long);
1414 if (gmsa_time
!= NULL
) {
1415 *current_time_out
= *gmsa_time
;
1419 return gmsa_current_time(current_time_out
);