s4:dsdb: Add functions for Group Managed Service Accounts implementation
[Samba.git] / source4 / dsdb / gmsa / util.c
blob30ea532f70920d632ab089576ee7cf7cf0ab3093
1 /*
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/>.
21 #include "includes.h"
22 #include "ldb.h"
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"
41 #undef strcasecmp
43 enum RootKeyType {
44 ROOT_KEY_NONE,
45 ROOT_KEY_SPECIFIC,
46 ROOT_KEY_NONSPECIFIC,
47 ROOT_KEY_OBTAINED,
50 struct RootKey {
51 TALLOC_CTX *mem_ctx;
52 enum RootKeyType type;
53 union {
54 struct KeyEnvelopeId specific;
55 struct {
56 NTTIME key_start_time;
57 } nonspecific;
58 struct {
59 struct gmsa_update_pwd_part key;
60 struct gmsa_null_terminated_password *password;
61 } obtained;
62 } u;
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,
71 bool *allowed_out)
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) {
80 ret = ldb_operr(ldb);
81 goto out;
83 *allowed_out = false;
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) {
92 *allowed_out = true;
93 ret = LDB_SUCCESS;
94 goto out;
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");
102 goto out;
105 user_token = session_info->security_token;
108 tmp_ctx = talloc_new(msg);
109 if (tmp_ctx == NULL) {
110 ret = ldb_oom(ldb);
111 goto out;
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");
126 goto out;
129 err = ndr_pull_struct_blob_all(
130 group_msa_membership,
131 tmp_ctx,
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",
137 nt_errstr(status));
138 ret = ldb_operr(ldb);
139 goto out;
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,
148 user_token,
149 access_desired,
150 &access_granted,
151 NULL,
152 account_sid);
153 if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
155 * The principal is not allowed to view the managed
156 * password.
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",
162 access_desired,
163 access_granted,
164 nt_errstr(status));
166 ret = dsdb_werror(
167 ldb,
168 LDB_ERR_OPERATIONS_ERROR,
169 WERR_DS_CANT_RETRIEVE_ATTS,
170 "access check to view managed password failed");
171 goto out;
172 } else {
173 /* Cool, the principal may view the password. */
174 *allowed_out = true;
178 out:
179 TALLOC_FREE(tmp_ctx);
180 return ret;
183 static NTSTATUS gmsa_managed_pwd_id(struct ldb_context *ldb,
184 TALLOC_CTX *mem_ctx,
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,
220 return NT_STATUS_OK;
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,
240 mem_ctx,
241 pwd_id,
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));
248 return 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) {
262 ret = ldb_oom(ldb);
263 goto out;
267 const struct ldb_message *root_key_msg = NULL;
269 ret = gkdi_root_key_from_id(tmp_ctx,
270 ldb,
271 &pwd_id.root_key_id,
272 &root_key_msg);
273 if (ret) {
274 goto out;
277 status = gkdi_root_key_from_msg(mem_ctx,
278 pwd_id.root_key_id,
279 root_key_msg,
280 &new_pwd_out->root_key);
281 if (!NT_STATUS_IS_OK(status)) {
282 ret = ldb_operr(ldb);
283 goto out;
287 new_pwd_out->gkid = pwd_id.gkid;
289 out:
290 talloc_free(tmp_ctx);
291 return ret;
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) {
305 ret = ldb_oom(ldb);
306 goto out;
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,
315 ldb,
316 current_time,
317 key_start_time,
318 &root_key_id,
319 &root_key_msg);
320 if (ret) {
321 goto out;
324 status = gkdi_root_key_from_msg(mem_ctx,
325 root_key_id,
326 root_key_msg,
327 &new_pwd_out->root_key);
328 if (!NT_STATUS_IS_OK(status)) {
329 ret = ldb_operr(ldb);
330 goto out;
334 new_pwd_out->gkid = gkdi_get_interval_id(key_start_time);
336 out:
337 talloc_free(tmp_ctx);
338 return ret;
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};
352 return LDB_SUCCESS;
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){
364 .mem_ctx = mem_ctx,
365 .type = ROOT_KEY_NONSPECIFIC,
366 .u.nonspecific.key_start_time = key_start_time};
367 return LDB_SUCCESS;
370 static int gmsa_obtained_root_key_steal(
371 TALLOC_CTX *mem_ctx,
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}};
388 return LDB_SUCCESS;
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);
402 goto out;
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) {
413 ret = ldb_oom(ldb);
414 goto out;
417 if (root_key->type == ROOT_KEY_SPECIFIC) {
418 /* Search for a specific root key. */
419 ret = gmsa_specific_password(tmp_ctx,
420 ldb,
421 root_key->u.specific,
422 &key);
423 if (ret) {
425 * We couldn’t find a specific key — treat this
426 * as an error.
428 goto out;
430 } else {
432 * Search for the most recent root key meeting the start
433 * time requirement.
435 ret = gmsa_nonspecific_password(
436 tmp_ctx,
437 ldb,
438 root_key->u.nonspecific.key_start_time,
439 current_time,
440 &key);
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
448 * example.
450 ret = LDB_SUCCESS;
451 *root_key = empty_root_key;
452 } else if (ret) {
453 goto out;
454 } else {
455 /* Derive the password. */
456 status = gmsa_talloc_password_based_on_key_id(
457 tmp_ctx,
458 key.gkid,
459 current_time,
460 key.root_key,
461 account_sid,
462 &password);
463 if (!NT_STATUS_IS_OK(status)) {
464 ret = ldb_operr(ldb);
465 goto out;
469 * Initialize the obtained structure, and give it the
470 * appropriate memory context.
472 ret = gmsa_obtained_root_key_steal(root_key->mem_ctx,
473 key,
474 password,
475 root_key);
476 if (ret) {
477 goto out;
480 } break;
481 case ROOT_KEY_NONE:
482 /* No key is available. */
483 break;
484 case ROOT_KEY_OBTAINED:
485 /* The key has already been obtained. */
486 break;
487 default:
488 ret = ldb_operr(ldb);
489 goto out;
492 out:
493 TALLOC_FREE(tmp_ctx);
494 return ret;
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
500 * ownership.
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);
514 goto out;
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);
524 if (ret) {
525 goto out;
528 switch (root_key->type) {
529 case ROOT_KEY_NONE:
530 /* No key is available. */
531 break;
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;
537 break;
538 default:
539 /* Unexpected. */
540 ret = ldb_operr(ldb);
541 goto out;
544 out:
545 return ret;
548 static int gmsa_system_update_password_id_req(
549 struct ldb_context *ldb,
550 TALLOC_CTX *mem_ctx,
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) {
565 ret = ldb_oom(ldb);
566 goto out;
569 /* Create a new ldb message. */
570 mod_msg = ldb_msg_new(tmp_ctx);
571 if (mod_msg == NULL) {
572 ret = ldb_oom(ldb);
573 goto out;
576 struct ldb_dn *dn = ldb_dn_copy(mod_msg, msg->dn);
577 if (dn == NULL) {
578 ret = ldb_oom(ldb);
579 goto out;
581 mod_msg->dn = 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);
589 goto out;
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,
600 &pwd_id,
601 &new_pwd_id_blob);
602 if (!NT_STATUS_IS_OK(status)) {
603 ret = ldb_operr(ldb);
604 goto out;
607 /* Update the msDS-ManagedPasswordId attribute. */
608 ret = ldb_msg_append_steal_value(mod_msg,
609 "msDS-ManagedPasswordId",
610 &new_pwd_id_blob,
611 LDB_FLAG_MOD_REPLACE);
612 if (ret) {
613 goto out;
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
625 * and root key ID.
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,
631 &pwd_id,
632 &prev_pwd_id);
633 if (!NT_STATUS_IS_OK(status)) {
634 ret = ldb_operr(ldb);
635 goto out;
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) {
643 ret = ldb_oom(ldb);
644 goto out;
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(
655 mod_msg,
656 "msDS-ManagedPasswordPreviousId",
657 prev_pwd_id_blob,
658 LDB_FLAG_MOD_REPLACE);
659 if (ret) {
660 goto out;
666 struct ldb_request *req = NULL;
668 /* Build the ldb request to return. */
669 ret = ldb_build_mod_req(&req,
670 ldb,
671 tmp_ctx,
672 mod_msg,
673 NULL,
674 NULL,
675 ldb_op_default_callback,
676 NULL);
677 if (ret) {
678 goto out;
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);
686 if (ret) {
687 goto out;
690 *req_out = talloc_steal(mem_ctx, req);
693 out:
694 talloc_free(tmp_ctx);
695 return ret;
698 int gmsa_generate_blobs(struct ldb_context *ldb,
699 TALLOC_CTX *mem_ctx,
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) {
713 ret = ldb_oom(ldb);
714 goto out;
718 const struct ldb_message *root_key_msg = NULL;
719 struct GUID root_key_id;
720 const NTTIME one_interval = gkdi_key_cycle_duration +
721 gkdi_max_clock_skew;
722 const NTTIME one_interval_ago = current_time -
723 MIN(one_interval, current_time);
725 ret = gkdi_most_recently_created_root_key(tmp_ctx,
726 ldb,
727 current_time,
728 one_interval_ago,
729 &root_key_id,
730 &root_key_msg);
731 if (ret) {
732 goto out;
735 status = gkdi_root_key_from_msg(tmp_ctx,
736 root_key_id,
737 root_key_msg,
738 &root_key);
739 if (!NT_STATUS_IS_OK(status)) {
740 ret = ldb_operr(ldb);
741 goto out;
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);
749 goto out;
753 const struct Gkid current_gkid = gkdi_get_interval_id(
754 current_time);
756 /* Derive the password. */
757 status = gmsa_talloc_password_based_on_key_id(tmp_ctx,
758 current_gkid,
759 current_time,
760 root_key,
761 account_sid,
762 password_out);
763 if (!NT_STATUS_IS_OK(status)) {
764 ret = ldb_operr(ldb);
765 goto out;
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
776 * root key ID.
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);
786 goto out;
789 /* Transfer ownership of the password to the caller’s memory context. */
790 talloc_steal(mem_ctx, *password_out);
792 out:
793 talloc_free(tmp_ctx);
794 return ret;
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);
818 goto out;
820 *update_out = NULL;
822 if (current_key == NULL) {
823 ret = ldb_operr(ldb);
824 goto out;
827 tmp_ctx = talloc_new(mem_ctx);
828 if (tmp_ctx == NULL) {
829 ret = ldb_oom(ldb);
830 goto out;
835 * The password_hash module expects these passwords to be
836 * null‐terminated.
838 struct gmsa_null_terminated_password *new_password = NULL;
840 ret = gmsa_get_root_key(ldb,
841 current_time,
842 account_sid,
843 current_key,
844 &new_password,
845 &new_pwd.new_id);
846 if (ret) {
847 goto out;
850 if (new_password == NULL) {
851 ret = ldb_operr(ldb);
852 goto out;
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);
859 goto out;
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.
870 } else {
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,
875 current_time,
876 account_sid,
877 previous_key,
878 &old_password,
879 &new_pwd.prev_id);
880 if (ret) {
881 goto out;
884 if (old_password != NULL) {
885 status = gmsa_system_password_update_request(
886 ldb,
887 tmp_ctx,
888 msg->dn,
889 old_password->buf,
890 &old_pw_req);
891 if (!NT_STATUS_IS_OK(status)) {
892 ret = ldb_operr(ldb);
893 goto out;
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);
901 if (ret) {
902 goto out;
905 update = talloc(tmp_ctx, struct gmsa_update);
906 if (update == NULL) {
907 ret = ldb_oom(ldb);
908 goto out;
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);
918 out:
919 TALLOC_FREE(tmp_ctx);
920 return ret;
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,
939 mem_ctx,
940 &managed_pwd,
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",
946 nt_errstr(status));
949 return status;
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,
960 msg,
961 "objectclass",
962 "msDS-GroupManagedServiceAccount") != NULL;
965 static struct new_key {
966 NTTIME start_time;
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 -
981 new_key_start_time;
982 const uint64_t stale_count = time_to_advance_by /
983 rollover_interval;
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;
989 } else {
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
994 * weirdness).
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;
1009 int ret;
1011 when_created = ldb_msg_find_ldb_val(msg, "whenCreated");
1012 ret = ldb_val_to_time(when_created, &creation_unix_time);
1013 if (ret) {
1014 /* Fail if we can’t read the attribute or it isn’t present. */
1015 return false;
1018 unix_to_nt_time(creation_time_out, creation_unix_time);
1019 return true;
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,
1028 attr_name);
1029 if (pwd_id_blob == NULL) {
1030 return 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",
1042 key_env_out);
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;
1090 bool ok = true;
1091 bool current_key_is_valid = false;
1093 if (update_out == NULL) {
1094 ret = ldb_operr(ldb);
1095 goto out;
1097 *update_out = NULL;
1100 /* Is the account a Group Managed Service Account? */
1101 const bool is_gmsa = dsdb_account_is_gmsa(ldb, msg);
1102 if (!is_gmsa) {
1103 /* It’s not a GMSA — we’re done here. */
1104 *update_out = NULL;
1105 if (return_out != NULL) {
1106 *return_out = (struct gmsa_return_pwd){};
1108 ret = LDB_SUCCESS;
1109 goto out;
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);
1118 goto out;
1121 ok = gmsa_creation_time(msg, current_time, &creation_time);
1122 if (!ok) {
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);
1140 goto out;
1142 current_key_expiration_time = key_start_time +
1143 rollover_interval;
1144 } else {
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
1148 * at random times.
1150 current_key_expiration_time = gkdi_get_interval_start_time(
1151 creation_time);
1154 /* Fetch the account’s SID, necessary for deriving passwords. */
1155 ret = samdb_result_dom_sid_buf(msg, "objectSid", &account_sid);
1156 if (ret) {
1157 goto out;
1160 tmp_ctx = talloc_new(mem_ctx);
1161 if (tmp_ctx == NULL) {
1162 ret = ldb_oom(ldb);
1163 goto out;
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
1173 * on its presence.
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
1180 * the keys.
1183 current_key_is_valid = pwd_id != NULL &&
1184 current_time < current_key_expiration_time;
1185 if (current_key_is_valid) {
1186 key_expiration_time = current_key_expiration_time;
1188 if (return_out != NULL) {
1189 struct KeyEnvelopeId prev_pwd_id_buf;
1190 const struct KeyEnvelopeId *prev_pwd_id = NULL;
1192 ret = gmsa_specifc_root_key(tmp_ctx,
1193 *pwd_id,
1194 &current_key);
1195 if (ret) {
1196 goto out;
1199 if (account_age >= rollover_interval) {
1200 prev_pwd_id = gmsa_get_managed_pwd_prev_id(
1201 msg, &prev_pwd_id_buf);
1202 if (prev_pwd_id != NULL) {
1203 ret = gmsa_specifc_root_key(
1204 tmp_ctx,
1205 *prev_pwd_id,
1206 &previous_key);
1207 if (ret) {
1208 goto out;
1210 } else if (have_key_start_time &&
1211 key_start_time >= rollover_interval)
1214 * The account’s old enough to have a
1215 * previous password, but it doesn’t
1216 * have a previous password ID for some
1217 * reason. This can happen in our tests
1218 * (python/samba/krb5/gmsa_tests.py)
1219 * when we’re mucking about with times.
1220 * Just produce what would have been the
1221 * previous key.
1223 ret = gmsa_nonspecifc_root_key(
1224 tmp_ctx,
1225 key_start_time -
1226 rollover_interval,
1227 &previous_key);
1228 if (ret) {
1229 goto out;
1232 } else {
1234 * The account is not old enough to have a
1235 * previous password. The old password will not
1236 * be returned.
1240 } else {
1241 /* Calculate the start time of the new key. */
1242 const struct new_key new_key = calculate_new_key(
1243 current_time,
1244 current_key_expiration_time,
1245 rollover_interval);
1246 const bool current_key_becomes_previous =
1247 pwd_id != NULL && new_key.immediately_follows_previous;
1249 /* Check for overflow. */
1250 if (new_key.start_time > UINT64_MAX - rollover_interval) {
1251 ret = ldb_operr(ldb);
1252 goto out;
1254 key_expiration_time = new_key.start_time + rollover_interval;
1256 ret = gmsa_nonspecifc_root_key(tmp_ctx,
1257 new_key.start_time,
1258 &current_key);
1259 if (ret) {
1260 goto out;
1263 if (account_age >= rollover_interval) {
1264 /* Check for underflow. */
1265 if (new_key.start_time < rollover_interval) {
1266 ret = ldb_operr(ldb);
1267 goto out;
1269 ret = gmsa_nonspecifc_root_key(
1270 tmp_ctx,
1271 new_key.start_time - rollover_interval,
1272 &previous_key);
1273 if (ret) {
1274 goto out;
1276 } else {
1278 * The account is not old enough to have a previous
1279 * password. The old password will not be returned.
1284 * The current GMSA key, according to the Managed Password ID,
1285 * is no longer valid. We should update the account’s Managed
1286 * Password ID and keys in anticipation of their being needed in
1287 * the near future.
1290 ret = gmsa_create_update(tmp_ctx,
1291 ldb,
1292 msg,
1293 current_time,
1294 &account_sid,
1295 current_key_becomes_previous,
1296 &current_key,
1297 &previous_key,
1298 &update);
1299 if (ret) {
1300 goto out;
1304 if (return_out != NULL) {
1305 bool return_future_key;
1307 unchanged_interval = query_interval = key_expiration_time -
1308 MIN(current_time,
1309 key_expiration_time);
1311 /* Derive the current and previous passwords. */
1312 return_future_key = query_interval <= gkdi_max_clock_skew;
1313 if (return_future_key) {
1314 struct RootKey future_key = empty_root_key;
1317 * The current key hasn’t expired yet, but it
1318 * soon will. Return a new key that will be valid in the
1319 * next epoch.
1322 ret = gmsa_nonspecifc_root_key(tmp_ctx,
1323 key_expiration_time,
1324 &future_key);
1325 if (ret) {
1326 goto out;
1329 ret = gmsa_get_root_key(ldb,
1330 current_time,
1331 &account_sid,
1332 &future_key,
1333 &current_password,
1334 NULL);
1335 if (ret) {
1336 goto out;
1339 ret = gmsa_get_root_key(ldb,
1340 current_time,
1341 &account_sid,
1342 &current_key,
1343 &previous_password,
1344 NULL);
1345 if (ret) {
1346 goto out;
1349 /* Check for overflow. */
1350 if (unchanged_interval > UINT64_MAX - rollover_interval)
1352 ret = ldb_operr(ldb);
1353 goto out;
1355 unchanged_interval += rollover_interval;
1356 } else {
1358 * Note that a gMSA will become unusable (at least until
1359 * the next rollover) if its associated root key is ever
1360 * deleted.
1363 ret = gmsa_get_root_key(ldb,
1364 current_time,
1365 &account_sid,
1366 &current_key,
1367 &current_password,
1368 NULL);
1369 if (ret) {
1370 goto out;
1373 ret = gmsa_get_root_key(ldb,
1374 current_time,
1375 &account_sid,
1376 &previous_key,
1377 &previous_password,
1378 NULL);
1379 if (ret) {
1380 goto out;
1384 unchanged_interval -= MIN(gkdi_max_clock_skew,
1385 unchanged_interval);
1388 *update_out = talloc_steal(mem_ctx, update);
1389 if (return_out != NULL) {
1390 *return_out = (struct gmsa_return_pwd){
1391 .prev_pwd = talloc_steal(mem_ctx, previous_password),
1392 .new_pwd = talloc_steal(mem_ctx, current_password),
1393 .query_interval = query_interval,
1394 .unchanged_interval = unchanged_interval,
1398 out:
1399 TALLOC_FREE(tmp_ctx);
1400 return ret;
1403 bool dsdb_gmsa_current_time(struct ldb_context *ldb, NTTIME *current_time_out)
1405 const unsigned long long *gmsa_time = talloc_get_type(
1406 ldb_get_opaque(ldb, DSDB_GMSA_TIME_OPAQUE), unsigned long long);
1408 if (gmsa_time != NULL) {
1409 *current_time_out = *gmsa_time;
1410 return true;
1413 return gmsa_current_time(current_time_out);