Line data Source code
1 : /*
2 : Unix SMB/CIFS implementation.
3 : msDS-ManagedPassword attribute for Group Managed Service Accounts
4 :
5 : Copyright (C) Catalyst.Net Ltd 2024
6 :
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.
11 :
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.
16 :
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/>.
19 : */
20 :
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"
40 :
41 : #undef strcasecmp
42 :
43 : enum RootKeyType {
44 : ROOT_KEY_NONE,
45 : ROOT_KEY_SPECIFIC,
46 : ROOT_KEY_NONSPECIFIC,
47 : ROOT_KEY_OBTAINED,
48 : };
49 :
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;
63 : };
64 :
65 : static const struct RootKey empty_root_key = {.type = ROOT_KEY_NONE};
66 :
67 56 : 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)
72 : {
73 56 : TALLOC_CTX *tmp_ctx = NULL;
74 56 : struct security_descriptor group_msa_membership_sd = {};
75 56 : const struct security_token *user_token = NULL;
76 56 : NTSTATUS status = NT_STATUS_OK;
77 56 : int ret = LDB_SUCCESS;
78 :
79 56 : if (allowed_out == NULL) {
80 0 : ret = ldb_operr(ldb);
81 0 : goto out;
82 : }
83 56 : *allowed_out = false;
84 :
85 : {
86 56 : const struct auth_session_info *session_info = ldb_get_opaque(
87 : ldb, DSDB_SESSION_INFO);
88 0 : const enum security_user_level level =
89 56 : security_session_user_level(session_info, NULL);
90 :
91 56 : if (level == SECURITY_SYSTEM) {
92 16 : *allowed_out = true;
93 16 : ret = LDB_SUCCESS;
94 16 : goto out;
95 : }
96 :
97 40 : if (session_info == NULL) {
98 0 : ret = dsdb_werror(ldb,
99 : LDB_ERR_OPERATIONS_ERROR,
100 : WERR_DS_CANT_RETRIEVE_ATTS,
101 : "no right to view attribute");
102 0 : goto out;
103 : }
104 :
105 40 : user_token = session_info->security_token;
106 : }
107 :
108 40 : tmp_ctx = talloc_new(msg);
109 40 : if (tmp_ctx == NULL) {
110 0 : ret = ldb_oom(ldb);
111 0 : goto out;
112 : }
113 :
114 : {
115 40 : const struct ldb_val *group_msa_membership = NULL;
116 0 : enum ndr_err_code err;
117 :
118 : /* [MS-ADTS] 3.1.1.4.4: Extended Access Checks. */
119 40 : group_msa_membership = ldb_msg_find_ldb_val(
120 : msg, "msDS-GroupMSAMembership");
121 40 : if (group_msa_membership == NULL) {
122 0 : ret = dsdb_werror(ldb,
123 : LDB_ERR_OPERATIONS_ERROR,
124 : WERR_DS_CANT_RETRIEVE_ATTS,
125 : "no right to view attribute");
126 0 : goto out;
127 : }
128 :
129 40 : 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 40 : if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
135 0 : status = ndr_map_error2ntstatus(err);
136 0 : DBG_WARNING("msDS-GroupMSAMembership pull failed: %s\n",
137 : nt_errstr(status));
138 0 : ret = ldb_operr(ldb);
139 0 : goto out;
140 : }
141 : }
142 :
143 : {
144 40 : const uint32_t access_desired = SEC_ADS_READ_PROP;
145 40 : uint32_t access_granted = 0;
146 :
147 40 : status = sec_access_check_ds(&group_msa_membership_sd,
148 : user_token,
149 : access_desired,
150 : &access_granted,
151 : NULL,
152 : account_sid);
153 40 : if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
154 : /*
155 : * The principal is not allowed to view the managed
156 : * password.
157 : */
158 33 : } else if (!NT_STATUS_IS_OK(status)) {
159 0 : 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));
165 :
166 0 : 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 0 : goto out;
172 : } else {
173 : /* Cool, the principal may view the password. */
174 33 : *allowed_out = true;
175 : }
176 : }
177 :
178 56 : out:
179 56 : TALLOC_FREE(tmp_ctx);
180 56 : return ret;
181 : }
182 :
183 129 : 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)
188 : {
189 129 : if (root_key == NULL) {
190 0 : return NT_STATUS_INVALID_PARAMETER;
191 : }
192 :
193 129 : if (pwd_id_blob != NULL) {
194 31 : return gkdi_pull_KeyEnvelope(mem_ctx, pwd_id_blob, pwd_id_out);
195 : }
196 :
197 : {
198 98 : const char *domain_name = NULL;
199 98 : const char *forest_name = NULL;
200 :
201 98 : domain_name = samdb_default_domain_name(ldb, mem_ctx);
202 98 : if (domain_name == NULL) {
203 0 : return NT_STATUS_NO_MEMORY;
204 : }
205 :
206 98 : forest_name = samdb_forest_name(ldb, mem_ctx);
207 98 : if (forest_name == NULL) {
208 : /* We leak ‘domain_name’, but that can’t be helped. */
209 0 : return NT_STATUS_NO_MEMORY;
210 : }
211 :
212 98 : *pwd_id_out = (struct KeyEnvelope){
213 98 : .version = root_key->version,
214 : .flags = ENVELOPE_FLAG_KEY_MAY_ENCRYPT_NEW_DATA,
215 : .domain_name = domain_name,
216 : .forest_name = forest_name,
217 : };
218 : }
219 :
220 98 : return NT_STATUS_OK;
221 : }
222 :
223 143 : void gmsa_update_managed_pwd_id(struct KeyEnvelope *pwd_id,
224 : const struct gmsa_update_pwd_part *new_pwd)
225 : {
226 143 : pwd_id->l0_index = new_pwd->gkid.l0_idx;
227 143 : pwd_id->l1_index = new_pwd->gkid.l1_idx;
228 143 : pwd_id->l2_index = new_pwd->gkid.l2_idx;
229 143 : pwd_id->root_key_id = new_pwd->root_key->id;
230 143 : }
231 :
232 143 : NTSTATUS gmsa_pack_managed_pwd_id(TALLOC_CTX *mem_ctx,
233 : const struct KeyEnvelope *pwd_id,
234 : DATA_BLOB *pwd_id_out)
235 : {
236 143 : NTSTATUS status = NT_STATUS_OK;
237 0 : enum ndr_err_code err;
238 :
239 143 : err = ndr_push_struct_blob(pwd_id_out,
240 : mem_ctx,
241 : pwd_id,
242 : (ndr_push_flags_fn_t)ndr_push_KeyEnvelope);
243 143 : status = ndr_map_error2ntstatus(err);
244 143 : if (!NT_STATUS_IS_OK(status)) {
245 0 : DBG_WARNING("KeyEnvelope push failed: %s\n", nt_errstr(status));
246 : }
247 :
248 143 : return status;
249 : }
250 :
251 39 : 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)
255 : {
256 39 : TALLOC_CTX *tmp_ctx = NULL;
257 39 : NTSTATUS status = NT_STATUS_OK;
258 39 : int ret = LDB_SUCCESS;
259 :
260 39 : tmp_ctx = talloc_new(mem_ctx);
261 39 : if (tmp_ctx == NULL) {
262 0 : ret = ldb_oom(ldb);
263 0 : goto out;
264 : }
265 :
266 : {
267 39 : const struct ldb_message *root_key_msg = NULL;
268 :
269 39 : ret = gkdi_root_key_from_id(tmp_ctx,
270 : ldb,
271 : &pwd_id.root_key_id,
272 : &root_key_msg);
273 39 : if (ret) {
274 0 : goto out;
275 : }
276 :
277 39 : 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 39 : if (!NT_STATUS_IS_OK(status)) {
282 0 : ret = ldb_operr(ldb);
283 0 : goto out;
284 : }
285 : }
286 :
287 39 : new_pwd_out->gkid = pwd_id.gkid;
288 :
289 39 : out:
290 39 : talloc_free(tmp_ctx);
291 39 : return ret;
292 : }
293 :
294 53 : 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)
299 : {
300 53 : TALLOC_CTX *tmp_ctx = NULL;
301 53 : int ret = LDB_SUCCESS;
302 :
303 53 : tmp_ctx = talloc_new(mem_ctx);
304 53 : if (tmp_ctx == NULL) {
305 0 : ret = ldb_oom(ldb);
306 0 : goto out;
307 : }
308 :
309 : {
310 53 : const struct ldb_message *root_key_msg = NULL;
311 0 : struct GUID root_key_id;
312 53 : NTSTATUS status = NT_STATUS_OK;
313 :
314 53 : 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 53 : if (ret) {
321 0 : goto out;
322 : }
323 :
324 53 : status = gkdi_root_key_from_msg(mem_ctx,
325 : root_key_id,
326 : root_key_msg,
327 : &new_pwd_out->root_key);
328 53 : if (!NT_STATUS_IS_OK(status)) {
329 0 : ret = ldb_operr(ldb);
330 0 : goto out;
331 : }
332 : }
333 :
334 53 : new_pwd_out->gkid = gkdi_get_interval_id(key_start_time);
335 :
336 53 : out:
337 53 : talloc_free(tmp_ctx);
338 53 : return ret;
339 : }
340 :
341 39 : static int gmsa_specifc_root_key(TALLOC_CTX *mem_ctx,
342 : const struct KeyEnvelopeId pwd_id,
343 : struct RootKey *root_key_out)
344 : {
345 39 : if (root_key_out == NULL) {
346 0 : return LDB_ERR_OPERATIONS_ERROR;
347 : }
348 :
349 39 : *root_key_out = (struct RootKey){.mem_ctx = mem_ctx,
350 : .type = ROOT_KEY_SPECIFIC,
351 : .u.specific = pwd_id};
352 39 : return LDB_SUCCESS;
353 : }
354 :
355 69 : static int gmsa_nonspecifc_root_key(TALLOC_CTX *mem_ctx,
356 : const NTTIME key_start_time,
357 : struct RootKey *root_key_out)
358 : {
359 69 : if (root_key_out == NULL) {
360 0 : return LDB_ERR_OPERATIONS_ERROR;
361 : }
362 :
363 69 : *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 69 : return LDB_SUCCESS;
368 : }
369 :
370 92 : 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)
375 : {
376 92 : if (root_key_out == NULL) {
377 0 : return LDB_ERR_OPERATIONS_ERROR;
378 : }
379 :
380 : /* Steal the data on to the appropriate memory context. */
381 92 : talloc_steal(mem_ctx, key.root_key);
382 92 : talloc_steal(mem_ctx, password);
383 :
384 92 : *root_key_out = (struct RootKey){.mem_ctx = mem_ctx,
385 : .type = ROOT_KEY_OBTAINED,
386 : .u.obtained = {.key = key,
387 : .password = password}};
388 92 : return LDB_SUCCESS;
389 : }
390 :
391 146 : 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)
395 : {
396 146 : TALLOC_CTX *tmp_ctx = NULL;
397 146 : NTSTATUS status = NT_STATUS_OK;
398 146 : int ret = LDB_SUCCESS;
399 :
400 146 : if (root_key == NULL) {
401 0 : ret = ldb_operr(ldb);
402 0 : goto out;
403 : }
404 :
405 146 : switch (root_key->type) {
406 92 : case ROOT_KEY_SPECIFIC:
407 : case ROOT_KEY_NONSPECIFIC: {
408 92 : struct gmsa_null_terminated_password *password = NULL;
409 0 : struct gmsa_update_pwd_part key;
410 :
411 92 : tmp_ctx = talloc_new(NULL);
412 92 : if (tmp_ctx == NULL) {
413 0 : ret = ldb_oom(ldb);
414 0 : goto out;
415 : }
416 :
417 92 : if (root_key->type == ROOT_KEY_SPECIFIC) {
418 : /* Search for a specific root key. */
419 39 : ret = gmsa_specific_password(tmp_ctx,
420 : ldb,
421 : root_key->u.specific,
422 : &key);
423 39 : if (ret) {
424 : /*
425 : * We couldn’t find a specific key — treat this
426 : * as an error.
427 : */
428 0 : goto out;
429 : }
430 : } else {
431 : /*
432 : * Search for the most recent root key meeting the start
433 : * time requirement.
434 : */
435 53 : 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. */
442 : }
443 92 : if (ret == LDB_ERR_NO_SUCH_OBJECT) {
444 : /*
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.
449 : */
450 0 : ret = LDB_SUCCESS;
451 0 : *root_key = empty_root_key;
452 92 : } else if (ret) {
453 0 : goto out;
454 : } else {
455 : /* Derive the password. */
456 92 : 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 92 : if (!NT_STATUS_IS_OK(status)) {
464 0 : ret = ldb_operr(ldb);
465 0 : goto out;
466 : }
467 :
468 : /*
469 : * Initialize the obtained structure, and give it the
470 : * appropriate memory context.
471 : */
472 92 : ret = gmsa_obtained_root_key_steal(root_key->mem_ctx,
473 : key,
474 : password,
475 : root_key);
476 92 : if (ret) {
477 0 : goto out;
478 : }
479 : }
480 92 : } break;
481 40 : case ROOT_KEY_NONE:
482 : /* No key is available. */
483 40 : break;
484 14 : case ROOT_KEY_OBTAINED:
485 : /* The key has already been obtained. */
486 14 : break;
487 0 : default:
488 0 : ret = ldb_operr(ldb);
489 0 : goto out;
490 : }
491 :
492 146 : out:
493 146 : TALLOC_FREE(tmp_ctx);
494 146 : return ret;
495 : }
496 :
497 : /*
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.
501 : */
502 146 : 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)
509 : {
510 146 : int ret = LDB_SUCCESS;
511 :
512 146 : if (password_out == NULL) {
513 0 : ret = ldb_operr(ldb);
514 0 : goto out;
515 : }
516 146 : *password_out = NULL;
517 :
518 146 : if (update_out != NULL) {
519 48 : *update_out = (struct gmsa_update_pwd_part){};
520 : }
521 :
522 : /* Fetch the root key from the database and obtain the password. */
523 146 : ret = gmsa_fetch_root_key(ldb, current_time, root_key, account_sid);
524 146 : if (ret) {
525 0 : goto out;
526 : }
527 :
528 146 : switch (root_key->type) {
529 40 : case ROOT_KEY_NONE:
530 : /* No key is available. */
531 40 : break;
532 106 : case ROOT_KEY_OBTAINED:
533 106 : *password_out = root_key->u.obtained.password;
534 106 : if (update_out != NULL) {
535 45 : *update_out = root_key->u.obtained.key;
536 : }
537 106 : break;
538 0 : default:
539 : /* Unexpected. */
540 0 : ret = ldb_operr(ldb);
541 0 : goto out;
542 : }
543 :
544 146 : out:
545 146 : return ret;
546 : }
547 :
548 31 : 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 : const bool current_key_becomes_previous,
554 : struct ldb_request **req_out)
555 : {
556 31 : TALLOC_CTX *tmp_ctx = NULL;
557 31 : const struct ldb_val *pwd_id_blob = ldb_msg_find_ldb_val(
558 : msg, "msDS-ManagedPasswordId");
559 0 : struct KeyEnvelope pwd_id;
560 31 : struct ldb_message *mod_msg = NULL;
561 31 : NTSTATUS status = NT_STATUS_OK;
562 31 : int ret = LDB_SUCCESS;
563 :
564 31 : tmp_ctx = talloc_new(mem_ctx);
565 31 : if (tmp_ctx == NULL) {
566 0 : ret = ldb_oom(ldb);
567 0 : goto out;
568 : }
569 :
570 : /* Create a new ldb message. */
571 31 : mod_msg = ldb_msg_new(tmp_ctx);
572 31 : if (mod_msg == NULL) {
573 0 : ret = ldb_oom(ldb);
574 0 : goto out;
575 : }
576 : {
577 31 : struct ldb_dn *dn = ldb_dn_copy(mod_msg, msg->dn);
578 31 : if (dn == NULL) {
579 0 : ret = ldb_oom(ldb);
580 0 : goto out;
581 : }
582 31 : mod_msg->dn = dn;
583 : }
584 :
585 : /* Get the Managed Password ID. */
586 31 : status = gmsa_managed_pwd_id(
587 31 : ldb, tmp_ctx, pwd_id_blob, new_pwd->new_id.root_key, &pwd_id);
588 31 : if (!NT_STATUS_IS_OK(status)) {
589 0 : ret = ldb_operr(ldb);
590 0 : goto out;
591 : }
592 :
593 : /* Update the password ID to contain the new GKID and root key ID. */
594 31 : gmsa_update_managed_pwd_id(&pwd_id, &new_pwd->new_id);
595 :
596 : {
597 31 : DATA_BLOB new_pwd_id_blob = {};
598 :
599 : /* Pack the current password ID. */
600 31 : status = gmsa_pack_managed_pwd_id(tmp_ctx,
601 : &pwd_id,
602 : &new_pwd_id_blob);
603 31 : if (!NT_STATUS_IS_OK(status)) {
604 0 : ret = ldb_operr(ldb);
605 0 : goto out;
606 : }
607 :
608 : /* Update the msDS-ManagedPasswordId attribute. */
609 31 : ret = ldb_msg_append_steal_value(mod_msg,
610 : "msDS-ManagedPasswordId",
611 : &new_pwd_id_blob,
612 : LDB_FLAG_MOD_REPLACE);
613 31 : if (ret) {
614 0 : goto out;
615 : }
616 : }
617 :
618 : {
619 31 : DATA_BLOB *prev_pwd_id_blob = NULL;
620 0 : DATA_BLOB _prev_pwd_id_blob;
621 31 : DATA_BLOB prev_pwd_id = {};
622 :
623 31 : if (new_pwd->prev_id.root_key != NULL) {
624 : /*
625 : * Update the password ID to contain the previous GKID
626 : * and root key ID.
627 : */
628 14 : gmsa_update_managed_pwd_id(&pwd_id, &new_pwd->prev_id);
629 :
630 : /* Pack the previous password ID. */
631 14 : status = gmsa_pack_managed_pwd_id(tmp_ctx,
632 : &pwd_id,
633 : &prev_pwd_id);
634 14 : if (!NT_STATUS_IS_OK(status)) {
635 0 : ret = ldb_operr(ldb);
636 0 : goto out;
637 : }
638 :
639 14 : prev_pwd_id_blob = &prev_pwd_id;
640 17 : } else if (current_key_becomes_previous && pwd_id_blob != NULL)
641 : {
642 : /* Copy the current password ID to the previous ID. */
643 14 : _prev_pwd_id_blob = ldb_val_dup(tmp_ctx, pwd_id_blob);
644 14 : if (_prev_pwd_id_blob.length != pwd_id_blob->length) {
645 0 : ret = ldb_oom(ldb);
646 0 : goto out;
647 : }
648 :
649 14 : prev_pwd_id_blob = &_prev_pwd_id_blob;
650 : }
651 :
652 31 : if (prev_pwd_id_blob != NULL) {
653 : /*
654 : * Update the msDS-ManagedPasswordPreviousId attribute.
655 : */
656 28 : ret = ldb_msg_append_steal_value(
657 : mod_msg,
658 : "msDS-ManagedPasswordPreviousId",
659 : prev_pwd_id_blob,
660 : LDB_FLAG_MOD_REPLACE);
661 28 : if (ret) {
662 0 : goto out;
663 : }
664 : }
665 : }
666 :
667 : {
668 31 : struct ldb_request *req = NULL;
669 :
670 : /* Build the ldb request to return. */
671 31 : ret = ldb_build_mod_req(&req,
672 : ldb,
673 : tmp_ctx,
674 : mod_msg,
675 : NULL,
676 : NULL,
677 : ldb_op_default_callback,
678 : NULL);
679 31 : if (ret) {
680 0 : goto out;
681 : }
682 :
683 : /* Tie the lifetime of the message to that of the request. */
684 31 : talloc_steal(req, mod_msg);
685 :
686 : /* Make sure the password ID update happens as System. */
687 31 : ret = dsdb_request_add_controls(req, DSDB_FLAG_AS_SYSTEM);
688 31 : if (ret) {
689 0 : goto out;
690 : }
691 :
692 31 : *req_out = talloc_steal(mem_ctx, req);
693 : }
694 :
695 31 : out:
696 31 : talloc_free(tmp_ctx);
697 31 : return ret;
698 : }
699 :
700 98 : int gmsa_generate_blobs(struct ldb_context *ldb,
701 : TALLOC_CTX *mem_ctx,
702 : const NTTIME current_time,
703 : const struct dom_sid *const account_sid,
704 : DATA_BLOB *pwd_id_blob_out,
705 : struct gmsa_null_terminated_password **password_out)
706 : {
707 98 : TALLOC_CTX *tmp_ctx = NULL;
708 0 : struct KeyEnvelope pwd_id;
709 98 : const struct ProvRootKey *root_key = NULL;
710 98 : NTSTATUS status = NT_STATUS_OK;
711 98 : int ret = LDB_SUCCESS;
712 :
713 98 : tmp_ctx = talloc_new(mem_ctx);
714 98 : if (tmp_ctx == NULL) {
715 0 : ret = ldb_oom(ldb);
716 0 : goto out;
717 : }
718 :
719 : {
720 98 : const struct ldb_message *root_key_msg = NULL;
721 0 : struct GUID root_key_id;
722 98 : const NTTIME one_interval = gkdi_key_cycle_duration +
723 : gkdi_max_clock_skew;
724 98 : const NTTIME one_interval_ago = current_time -
725 98 : MIN(one_interval, current_time);
726 :
727 98 : ret = gkdi_most_recently_created_root_key(tmp_ctx,
728 : ldb,
729 : current_time,
730 : one_interval_ago,
731 : &root_key_id,
732 : &root_key_msg);
733 98 : if (ret) {
734 0 : goto out;
735 : }
736 :
737 98 : status = gkdi_root_key_from_msg(tmp_ctx,
738 : root_key_id,
739 : root_key_msg,
740 : &root_key);
741 98 : if (!NT_STATUS_IS_OK(status)) {
742 0 : ret = ldb_operr(ldb);
743 0 : goto out;
744 : }
745 : }
746 :
747 : /* Get the Managed Password ID. */
748 98 : status = gmsa_managed_pwd_id(ldb, tmp_ctx, NULL, root_key, &pwd_id);
749 98 : if (!NT_STATUS_IS_OK(status)) {
750 0 : ret = ldb_operr(ldb);
751 0 : goto out;
752 : }
753 :
754 : {
755 98 : const struct Gkid current_gkid = gkdi_get_interval_id(
756 : current_time);
757 :
758 : /* Derive the password. */
759 98 : status = gmsa_talloc_password_based_on_key_id(tmp_ctx,
760 : current_gkid,
761 : current_time,
762 : root_key,
763 : account_sid,
764 : password_out);
765 98 : if (!NT_STATUS_IS_OK(status)) {
766 0 : ret = ldb_operr(ldb);
767 0 : goto out;
768 : }
769 :
770 : {
771 98 : const struct gmsa_update_pwd_part new_id = {
772 : .root_key = root_key,
773 : .gkid = current_gkid,
774 : };
775 :
776 : /*
777 : * Update the password ID to contain the new GKID and
778 : * root key ID.
779 : */
780 98 : gmsa_update_managed_pwd_id(&pwd_id, &new_id);
781 : }
782 : }
783 :
784 : /* Pack the current password ID. */
785 98 : status = gmsa_pack_managed_pwd_id(mem_ctx, &pwd_id, pwd_id_blob_out);
786 98 : if (!NT_STATUS_IS_OK(status)) {
787 0 : ret = ldb_operr(ldb);
788 0 : goto out;
789 : }
790 :
791 : /* Transfer ownership of the password to the caller’s memory context. */
792 98 : talloc_steal(mem_ctx, *password_out);
793 :
794 98 : out:
795 98 : talloc_free(tmp_ctx);
796 98 : return ret;
797 : }
798 :
799 31 : static int gmsa_create_update(TALLOC_CTX *mem_ctx,
800 : struct ldb_context *ldb,
801 : const struct ldb_message *msg,
802 : const NTTIME current_time,
803 : const struct dom_sid *account_sid,
804 : const bool current_key_becomes_previous,
805 : struct RootKey *current_key,
806 : struct RootKey *previous_key,
807 : struct gmsa_update **update_out)
808 : {
809 31 : TALLOC_CTX *tmp_ctx = NULL;
810 31 : const DATA_BLOB *found_pwd_id = NULL;
811 31 : struct ldb_request *old_pw_req = NULL;
812 31 : struct ldb_request *new_pw_req = NULL;
813 31 : struct ldb_request *pwd_id_req = NULL;
814 31 : struct ldb_dn *account_dn = NULL;
815 31 : struct gmsa_update_pwd new_pwd = {};
816 31 : struct gmsa_update *update = NULL;
817 31 : NTSTATUS status = NT_STATUS_OK;
818 31 : int ret = LDB_SUCCESS;
819 :
820 31 : if (update_out == NULL) {
821 0 : ret = ldb_operr(ldb);
822 0 : goto out;
823 : }
824 31 : *update_out = NULL;
825 :
826 31 : if (current_key == NULL) {
827 0 : ret = ldb_operr(ldb);
828 0 : goto out;
829 : }
830 :
831 31 : tmp_ctx = talloc_new(mem_ctx);
832 31 : if (tmp_ctx == NULL) {
833 0 : ret = ldb_oom(ldb);
834 0 : goto out;
835 : }
836 :
837 : {
838 : /*
839 : * The password_hash module expects these passwords to be
840 : * null‐terminated.
841 : */
842 31 : struct gmsa_null_terminated_password *new_password = NULL;
843 :
844 31 : ret = gmsa_get_root_key(ldb,
845 : current_time,
846 : account_sid,
847 : current_key,
848 : &new_password,
849 : &new_pwd.new_id);
850 31 : if (ret) {
851 0 : goto out;
852 : }
853 :
854 31 : if (new_password == NULL) {
855 0 : ret = ldb_operr(ldb);
856 0 : goto out;
857 : }
858 :
859 31 : status = gmsa_system_password_update_request(
860 31 : ldb, tmp_ctx, msg->dn, new_password->buf, &new_pw_req);
861 31 : if (!NT_STATUS_IS_OK(status)) {
862 0 : ret = ldb_operr(ldb);
863 0 : goto out;
864 : }
865 : }
866 :
867 : /* Does the previous password need to be updated? */
868 31 : if (current_key_becomes_previous) {
869 : /*
870 : * When we perform the password set, the now‐current password
871 : * will become the previous password automatically. We don’t
872 : * have to manage that ourselves.
873 : */
874 : } else {
875 17 : struct gmsa_null_terminated_password *old_password = NULL;
876 :
877 : /* The current key cannot be reused as the previous key. */
878 17 : ret = gmsa_get_root_key(ldb,
879 : current_time,
880 : account_sid,
881 : previous_key,
882 : &old_password,
883 : &new_pwd.prev_id);
884 17 : if (ret) {
885 0 : goto out;
886 : }
887 :
888 17 : if (old_password != NULL) {
889 14 : status = gmsa_system_password_update_request(
890 : ldb,
891 : tmp_ctx,
892 14 : msg->dn,
893 14 : old_password->buf,
894 : &old_pw_req);
895 14 : if (!NT_STATUS_IS_OK(status)) {
896 0 : ret = ldb_operr(ldb);
897 0 : goto out;
898 : }
899 : }
900 : }
901 :
902 : /* Ready the update of the msDS-ManagedPasswordId attribute. */
903 31 : ret = gmsa_system_update_password_id_req(ldb,
904 : tmp_ctx,
905 : msg,
906 : &new_pwd,
907 : current_key_becomes_previous,
908 : &pwd_id_req);
909 31 : if (ret) {
910 0 : goto out;
911 : }
912 :
913 : {
914 : /*
915 : * Remember the original managed password ID so that we can
916 : * confirm it hasn’t changed when we perform the update.
917 : */
918 :
919 31 : const struct ldb_val *pwd_id_blob = ldb_msg_find_ldb_val(
920 : msg, "msDS-ManagedPasswordId");
921 :
922 31 : if (pwd_id_blob != NULL) {
923 31 : DATA_BLOB found_pwd_id_data = {};
924 31 : DATA_BLOB *found_pwd_id_blob = NULL;
925 :
926 31 : found_pwd_id_blob = talloc(tmp_ctx, DATA_BLOB);
927 31 : if (found_pwd_id_blob == NULL) {
928 0 : ret = ldb_oom(ldb);
929 0 : goto out;
930 : }
931 :
932 31 : found_pwd_id_data = data_blob_dup_talloc(
933 : found_pwd_id_blob, *pwd_id_blob);
934 31 : if (found_pwd_id_data.length != pwd_id_blob->length) {
935 0 : ret = ldb_oom(ldb);
936 0 : goto out;
937 : }
938 :
939 31 : *found_pwd_id_blob = found_pwd_id_data;
940 31 : found_pwd_id = found_pwd_id_blob;
941 : }
942 : }
943 :
944 31 : account_dn = ldb_dn_copy(tmp_ctx, msg->dn);
945 31 : if (account_dn == NULL) {
946 0 : ret = ldb_oom(ldb);
947 0 : goto out;
948 : }
949 :
950 31 : update = talloc(tmp_ctx, struct gmsa_update);
951 31 : if (update == NULL) {
952 0 : ret = ldb_oom(ldb);
953 0 : goto out;
954 : }
955 :
956 31 : *update = (struct gmsa_update){
957 31 : .dn = talloc_steal(update, account_dn),
958 31 : .found_pwd_id = talloc_steal(update, found_pwd_id),
959 31 : .old_pw_req = talloc_steal(update, old_pw_req),
960 31 : .new_pw_req = talloc_steal(update, new_pw_req),
961 31 : .pwd_id_req = talloc_steal(update, pwd_id_req)};
962 :
963 31 : *update_out = talloc_steal(mem_ctx, update);
964 :
965 31 : out:
966 31 : TALLOC_FREE(tmp_ctx);
967 31 : return ret;
968 : }
969 :
970 49 : NTSTATUS gmsa_pack_managed_pwd(TALLOC_CTX *mem_ctx,
971 : const uint8_t *new_password,
972 : const uint8_t *old_password,
973 : uint64_t query_interval,
974 : uint64_t unchanged_interval,
975 : DATA_BLOB *managed_pwd_out)
976 : {
977 49 : const struct MANAGEDPASSWORD_BLOB managed_pwd = {
978 : .passwords = {.current = new_password,
979 : .previous = old_password,
980 : .query_interval = &query_interval,
981 : .unchanged_interval = &unchanged_interval}};
982 49 : NTSTATUS status = NT_STATUS_OK;
983 0 : enum ndr_err_code err;
984 :
985 49 : err = ndr_push_struct_blob(managed_pwd_out,
986 : mem_ctx,
987 : &managed_pwd,
988 : (ndr_push_flags_fn_t)
989 : ndr_push_MANAGEDPASSWORD_BLOB);
990 49 : status = ndr_map_error2ntstatus(err);
991 49 : if (!NT_STATUS_IS_OK(status)) {
992 0 : DBG_WARNING("MANAGEDPASSWORD_BLOB push failed: %s\n",
993 : nt_errstr(status));
994 : }
995 :
996 49 : return status;
997 : }
998 :
999 384772 : bool dsdb_account_is_gmsa(struct ldb_context *ldb,
1000 : const struct ldb_message *msg)
1001 : {
1002 : /*
1003 : * Check if the account has objectClass
1004 : * ‘msDS-GroupManagedServiceAccount’.
1005 : */
1006 384772 : return samdb_find_attribute(ldb,
1007 : msg,
1008 : "objectclass",
1009 384772 : "msDS-GroupManagedServiceAccount") != NULL;
1010 : }
1011 :
1012 : static struct new_key {
1013 : NTTIME start_time;
1014 : bool immediately_follows_previous;
1015 31 : } calculate_new_key(const NTTIME current_time,
1016 : const NTTIME current_key_expiration_time,
1017 : const NTTIME rollover_interval)
1018 : {
1019 31 : NTTIME new_key_start_time = current_key_expiration_time;
1020 31 : bool immediately_follows_previous = false;
1021 :
1022 31 : if (new_key_start_time < current_time && rollover_interval) {
1023 : /*
1024 : * Advance the key start time by the rollover interval until it
1025 : * would be greater than the current time.
1026 : */
1027 27 : const NTTIME time_to_advance_by = current_time + 1 -
1028 : new_key_start_time;
1029 27 : const uint64_t stale_count = time_to_advance_by /
1030 : rollover_interval;
1031 27 : new_key_start_time += stale_count * rollover_interval;
1032 :
1033 27 : SMB_ASSERT(new_key_start_time <= current_time);
1034 :
1035 27 : immediately_follows_previous = stale_count == 0;
1036 : } else {
1037 : /*
1038 : * It is possible that new_key_start_time ≥ current_time;
1039 : * specifically, if there is no password ID, and the creation
1040 : * time of the gMSA is in the future (perhaps due to replication
1041 : * weirdness).
1042 : */
1043 0 : }
1044 :
1045 31 : return (struct new_key){
1046 : .start_time = new_key_start_time,
1047 : .immediately_follows_previous = immediately_follows_previous};
1048 : }
1049 :
1050 163 : static bool gmsa_creation_time(const struct ldb_message *msg,
1051 : const NTTIME current_time,
1052 : NTTIME *creation_time_out)
1053 : {
1054 163 : const struct ldb_val *when_created = NULL;
1055 0 : time_t creation_unix_time;
1056 0 : int ret;
1057 :
1058 163 : when_created = ldb_msg_find_ldb_val(msg, "whenCreated");
1059 163 : ret = ldb_val_to_time(when_created, &creation_unix_time);
1060 163 : if (ret) {
1061 : /* Fail if we can’t read the attribute or it isn’t present. */
1062 0 : return false;
1063 : }
1064 :
1065 163 : unix_to_nt_time(creation_time_out, creation_unix_time);
1066 163 : return true;
1067 : }
1068 :
1069 172 : static const struct KeyEnvelopeId *gmsa_get_managed_pwd_id_attr_name(
1070 : const struct ldb_message *msg,
1071 : const char *attr_name,
1072 : struct KeyEnvelopeId *key_env_out)
1073 : {
1074 172 : const struct ldb_val *pwd_id_blob = ldb_msg_find_ldb_val(msg,
1075 : attr_name);
1076 172 : if (pwd_id_blob == NULL) {
1077 4 : return NULL;
1078 : }
1079 :
1080 168 : return gkdi_pull_KeyEnvelopeId(*pwd_id_blob, key_env_out);
1081 : }
1082 :
1083 168 : const struct KeyEnvelopeId *gmsa_get_managed_pwd_id(
1084 : const struct ldb_message *msg,
1085 : struct KeyEnvelopeId *key_env_out)
1086 : {
1087 168 : return gmsa_get_managed_pwd_id_attr_name(msg,
1088 : "msDS-ManagedPasswordId",
1089 : key_env_out);
1090 : }
1091 :
1092 4 : static const struct KeyEnvelopeId *gmsa_get_managed_pwd_prev_id(
1093 : const struct ldb_message *msg,
1094 : struct KeyEnvelopeId *key_env_out)
1095 : {
1096 4 : return gmsa_get_managed_pwd_id_attr_name(
1097 : msg, "msDS-ManagedPasswordPreviousId", key_env_out);
1098 : }
1099 :
1100 163 : static bool samdb_result_gkdi_rollover_interval(const struct ldb_message *msg,
1101 : NTTIME *rollover_interval_out)
1102 : {
1103 0 : int64_t managed_password_interval;
1104 :
1105 163 : managed_password_interval = ldb_msg_find_attr_as_int64(
1106 : msg, "msDS-ManagedPasswordInterval", 30);
1107 163 : return gkdi_rollover_interval(managed_password_interval,
1108 : rollover_interval_out);
1109 : }
1110 :
1111 5 : bool samdb_gmsa_key_is_recent(const struct ldb_message *msg,
1112 : const NTTIME current_time)
1113 : {
1114 5 : const struct KeyEnvelopeId *pwd_id = NULL;
1115 0 : struct KeyEnvelopeId pwd_id_buf;
1116 0 : NTTIME key_start_time;
1117 0 : bool ok;
1118 :
1119 5 : pwd_id = gmsa_get_managed_pwd_id(msg, &pwd_id_buf);
1120 5 : if (pwd_id == NULL) {
1121 0 : return false;
1122 : }
1123 :
1124 5 : ok = gkdi_get_key_start_time(pwd_id->gkid, &key_start_time);
1125 5 : if (!ok) {
1126 0 : return false;
1127 : }
1128 :
1129 5 : if (current_time < key_start_time) {
1130 0 : return false;
1131 : }
1132 :
1133 5 : return current_time - key_start_time < gkdi_max_clock_skew;
1134 : }
1135 :
1136 : /*
1137 : * Recalculate the managed password of an account. The account referred to by
1138 : * ‘msg’ should be a Group Managed Service Account.
1139 : *
1140 : * Updated passwords are returned in ‘update_out’.
1141 : *
1142 : * Pass in a non‐NULL pointer for ‘return_out’ if you want the passwords as
1143 : * reflected by the msDS-ManagedPassword operational attribute.
1144 : */
1145 163 : int gmsa_recalculate_managed_pwd(TALLOC_CTX *mem_ctx,
1146 : struct ldb_context *ldb,
1147 : const struct ldb_message *msg,
1148 : const NTTIME current_time,
1149 : struct gmsa_update **update_out,
1150 : struct gmsa_return_pwd *return_out)
1151 : {
1152 163 : TALLOC_CTX *tmp_ctx = NULL;
1153 163 : int ret = LDB_SUCCESS;
1154 0 : NTTIME rollover_interval;
1155 0 : NTTIME current_key_expiration_time;
1156 0 : NTTIME key_expiration_time;
1157 0 : struct dom_sid account_sid;
1158 0 : struct KeyEnvelopeId pwd_id_buf;
1159 163 : const struct KeyEnvelopeId *pwd_id = NULL;
1160 163 : struct RootKey previous_key = empty_root_key;
1161 163 : struct RootKey current_key = empty_root_key;
1162 163 : struct gmsa_update *update = NULL;
1163 163 : struct gmsa_null_terminated_password *previous_password = NULL;
1164 163 : struct gmsa_null_terminated_password *current_password = NULL;
1165 163 : NTTIME query_interval = 0;
1166 163 : NTTIME unchanged_interval = 0;
1167 163 : NTTIME creation_time = 0;
1168 163 : NTTIME account_age = 0;
1169 163 : NTTIME key_start_time = 0;
1170 163 : bool have_key_start_time = false;
1171 163 : bool ok = true;
1172 163 : bool current_key_is_valid = false;
1173 :
1174 163 : if (update_out == NULL) {
1175 0 : ret = ldb_operr(ldb);
1176 0 : goto out;
1177 : }
1178 163 : *update_out = NULL;
1179 :
1180 : /* Calculate the rollover interval. */
1181 163 : ok = samdb_result_gkdi_rollover_interval(msg, &rollover_interval);
1182 163 : if (!ok || rollover_interval == 0) {
1183 : /* We can’t do anything if the rollover interval is zero. */
1184 0 : ret = ldb_operr(ldb);
1185 0 : goto out;
1186 : }
1187 :
1188 163 : ok = gmsa_creation_time(msg, current_time, &creation_time);
1189 163 : if (!ok) {
1190 0 : return ldb_error(ldb,
1191 : LDB_ERR_OPERATIONS_ERROR,
1192 : "unable to determine creation time of Group "
1193 : "Managed Service Account");
1194 : }
1195 163 : account_age = current_time - MIN(creation_time, current_time);
1196 :
1197 : /* Calculate the expiration time of the current key. */
1198 163 : pwd_id = gmsa_get_managed_pwd_id(msg, &pwd_id_buf);
1199 326 : if (pwd_id != NULL &&
1200 163 : gkdi_get_key_start_time(pwd_id->gkid, &key_start_time))
1201 : {
1202 163 : have_key_start_time = true;
1203 :
1204 : /* Check for overflow. */
1205 163 : if (key_start_time > UINT64_MAX - rollover_interval) {
1206 0 : ret = ldb_operr(ldb);
1207 0 : goto out;
1208 : }
1209 163 : current_key_expiration_time = key_start_time +
1210 : rollover_interval;
1211 : } else {
1212 : /*
1213 : * [MS-ADTS] does not say to use gkdi_get_interval_start_time(),
1214 : * but surely it makes no sense to have keys starting or ending
1215 : * at random times.
1216 : */
1217 0 : current_key_expiration_time = gkdi_get_interval_start_time(
1218 : creation_time);
1219 : }
1220 :
1221 : /* Fetch the account’s SID, necessary for deriving passwords. */
1222 163 : ret = samdb_result_dom_sid_buf(msg, "objectSid", &account_sid);
1223 163 : if (ret) {
1224 0 : goto out;
1225 : }
1226 :
1227 163 : tmp_ctx = talloc_new(mem_ctx);
1228 163 : if (tmp_ctx == NULL) {
1229 0 : ret = ldb_oom(ldb);
1230 0 : goto out;
1231 : }
1232 :
1233 : /*
1234 : * In determining whether the account’s passwords should be updated, we
1235 : * do not validate that the unicodePwd attribute is up‐to‐date, or even
1236 : * that it exists. We rely entirely on the fact that the managed
1237 : * password ID should be updated *only* as part of a successful gMSA
1238 : * password update. In any case, unicodePwd is optional in Samba — save
1239 : * for machine accounts (which gMSAs are :)) — and we can’t always rely
1240 : * on its presence.
1241 : *
1242 : * All this means that an admin (or a DC that doesn’t support gMSAs)
1243 : * could reset a gMSA’s password outside of the normal procedure, and
1244 : * the account would then have the wrong password until the key was due
1245 : * to roll over again. There’s nothing much we can do about this if we
1246 : * don’t want to re‐derive and verify the password every time we look up
1247 : * the keys.
1248 : */
1249 :
1250 : /*
1251 : * Administrators should be careful not to set a DC’s clock too far in
1252 : * the future, or a gMSA’s keys may be stuck at that future time and
1253 : * stop updating until said time rolls around for real.
1254 : */
1255 :
1256 163 : current_key_is_valid = pwd_id != NULL &&
1257 0 : current_time < current_key_expiration_time;
1258 163 : if (current_key_is_valid) {
1259 132 : key_expiration_time = current_key_expiration_time;
1260 :
1261 132 : if (return_out != NULL) {
1262 0 : struct KeyEnvelopeId prev_pwd_id_buf;
1263 39 : const struct KeyEnvelopeId *prev_pwd_id = NULL;
1264 :
1265 39 : ret = gmsa_specifc_root_key(tmp_ctx,
1266 : *pwd_id,
1267 : ¤t_key);
1268 39 : if (ret) {
1269 0 : goto out;
1270 : }
1271 :
1272 39 : if (account_age >= rollover_interval) {
1273 4 : prev_pwd_id = gmsa_get_managed_pwd_prev_id(
1274 : msg, &prev_pwd_id_buf);
1275 4 : if (prev_pwd_id != NULL) {
1276 0 : ret = gmsa_specifc_root_key(
1277 : tmp_ctx,
1278 : *prev_pwd_id,
1279 : &previous_key);
1280 0 : if (ret) {
1281 0 : goto out;
1282 : }
1283 4 : } else if (have_key_start_time &&
1284 4 : key_start_time >= rollover_interval)
1285 : {
1286 : /*
1287 : * The account’s old enough to have a
1288 : * previous password, but it doesn’t
1289 : * have a previous password ID for some
1290 : * reason. This can happen in our tests
1291 : * (python/samba/krb5/gmsa_tests.py)
1292 : * when we’re mucking about with times.
1293 : * Just produce what would have been the
1294 : * previous key.
1295 : */
1296 4 : ret = gmsa_nonspecifc_root_key(
1297 : tmp_ctx,
1298 : key_start_time -
1299 : rollover_interval,
1300 : &previous_key);
1301 4 : if (ret) {
1302 0 : goto out;
1303 : }
1304 : }
1305 : } else {
1306 : /*
1307 : * The account is not old enough to have a
1308 : * previous password. The old password will not
1309 : * be returned.
1310 : */
1311 0 : }
1312 : }
1313 : } else {
1314 : /* Calculate the start time of the new key. */
1315 31 : const struct new_key new_key = calculate_new_key(
1316 : current_time,
1317 : current_key_expiration_time,
1318 : rollover_interval);
1319 31 : const bool current_key_becomes_previous =
1320 31 : pwd_id != NULL && new_key.immediately_follows_previous;
1321 :
1322 : /* Check for overflow. */
1323 31 : if (new_key.start_time > UINT64_MAX - rollover_interval) {
1324 0 : ret = ldb_operr(ldb);
1325 0 : goto out;
1326 : }
1327 31 : key_expiration_time = new_key.start_time + rollover_interval;
1328 :
1329 31 : ret = gmsa_nonspecifc_root_key(tmp_ctx,
1330 31 : new_key.start_time,
1331 : ¤t_key);
1332 31 : if (ret) {
1333 0 : goto out;
1334 : }
1335 :
1336 31 : if (account_age >= rollover_interval) {
1337 : /* Check for underflow. */
1338 28 : if (new_key.start_time < rollover_interval) {
1339 0 : ret = ldb_operr(ldb);
1340 0 : goto out;
1341 : }
1342 28 : ret = gmsa_nonspecifc_root_key(
1343 : tmp_ctx,
1344 28 : new_key.start_time - rollover_interval,
1345 : &previous_key);
1346 28 : if (ret) {
1347 0 : goto out;
1348 : }
1349 : } else {
1350 : /*
1351 : * The account is not old enough to have a previous
1352 : * password. The old password will not be returned.
1353 : */
1354 0 : }
1355 :
1356 : /*
1357 : * The current GMSA key, according to the Managed Password ID,
1358 : * is no longer valid. We should update the account’s Managed
1359 : * Password ID and keys in anticipation of their being needed in
1360 : * the near future.
1361 : */
1362 :
1363 31 : ret = gmsa_create_update(tmp_ctx,
1364 : ldb,
1365 : msg,
1366 : current_time,
1367 : &account_sid,
1368 : current_key_becomes_previous,
1369 : ¤t_key,
1370 : &previous_key,
1371 : &update);
1372 31 : if (ret) {
1373 0 : goto out;
1374 : }
1375 : }
1376 :
1377 163 : if (return_out != NULL) {
1378 0 : bool return_future_key;
1379 :
1380 49 : unchanged_interval = query_interval = key_expiration_time -
1381 49 : MIN(current_time,
1382 : key_expiration_time);
1383 :
1384 : /* Derive the current and previous passwords. */
1385 49 : return_future_key = query_interval <= gkdi_max_clock_skew;
1386 49 : if (return_future_key) {
1387 6 : struct RootKey future_key = empty_root_key;
1388 :
1389 : /*
1390 : * The current key hasn’t expired yet, but it
1391 : * soon will. Return a new key that will be valid in the
1392 : * next epoch.
1393 : */
1394 :
1395 6 : ret = gmsa_nonspecifc_root_key(tmp_ctx,
1396 : key_expiration_time,
1397 : &future_key);
1398 6 : if (ret) {
1399 0 : goto out;
1400 : }
1401 :
1402 6 : ret = gmsa_get_root_key(ldb,
1403 : current_time,
1404 : &account_sid,
1405 : &future_key,
1406 : ¤t_password,
1407 : NULL);
1408 6 : if (ret) {
1409 0 : goto out;
1410 : }
1411 :
1412 6 : ret = gmsa_get_root_key(ldb,
1413 : current_time,
1414 : &account_sid,
1415 : ¤t_key,
1416 : &previous_password,
1417 : NULL);
1418 6 : if (ret) {
1419 0 : goto out;
1420 : }
1421 :
1422 : /* Check for overflow. */
1423 6 : if (unchanged_interval > UINT64_MAX - rollover_interval)
1424 : {
1425 0 : ret = ldb_operr(ldb);
1426 0 : goto out;
1427 : }
1428 6 : unchanged_interval += rollover_interval;
1429 : } else {
1430 : /*
1431 : * Note that a gMSA will become unusable (at least until
1432 : * the next rollover) if its associated root key is ever
1433 : * deleted.
1434 : */
1435 :
1436 43 : ret = gmsa_get_root_key(ldb,
1437 : current_time,
1438 : &account_sid,
1439 : ¤t_key,
1440 : ¤t_password,
1441 : NULL);
1442 43 : if (ret) {
1443 0 : goto out;
1444 : }
1445 :
1446 43 : ret = gmsa_get_root_key(ldb,
1447 : current_time,
1448 : &account_sid,
1449 : &previous_key,
1450 : &previous_password,
1451 : NULL);
1452 43 : if (ret) {
1453 0 : goto out;
1454 : }
1455 : }
1456 :
1457 49 : unchanged_interval -= MIN(gkdi_max_clock_skew,
1458 : unchanged_interval);
1459 : }
1460 :
1461 163 : *update_out = talloc_steal(mem_ctx, update);
1462 163 : if (return_out != NULL) {
1463 49 : *return_out = (struct gmsa_return_pwd){
1464 49 : .prev_pwd = talloc_steal(mem_ctx, previous_password),
1465 49 : .new_pwd = talloc_steal(mem_ctx, current_password),
1466 : .query_interval = query_interval,
1467 : .unchanged_interval = unchanged_interval,
1468 : };
1469 : }
1470 :
1471 114 : out:
1472 163 : TALLOC_FREE(tmp_ctx);
1473 163 : return ret;
1474 : }
1475 :
1476 23 : static void gmsa_update_debug(const struct gmsa_update *gmsa_update)
1477 : {
1478 23 : struct ldb_dn *dn = NULL;
1479 23 : const char *account_dn = "<unknown>";
1480 :
1481 23 : if (!CHECK_DEBUGLVL(DBGLVL_NOTICE)) {
1482 23 : return;
1483 : }
1484 :
1485 0 : dn = gmsa_update->dn;
1486 0 : if (dn != NULL) {
1487 0 : const char *dn_str = NULL;
1488 :
1489 0 : dn_str = ldb_dn_get_linearized(dn);
1490 0 : if (dn_str != NULL) {
1491 0 : account_dn = dn_str;
1492 : }
1493 : }
1494 :
1495 0 : DBG_NOTICE("Updating keys for Group Managed Service Account %s\n",
1496 : account_dn);
1497 : }
1498 :
1499 69 : static int gmsa_perform_request(struct ldb_context *ldb,
1500 : struct ldb_request *req)
1501 : {
1502 69 : int ret = LDB_SUCCESS;
1503 :
1504 69 : if (req == NULL) {
1505 15 : return LDB_SUCCESS;
1506 : }
1507 :
1508 54 : ret = ldb_request(ldb, req);
1509 54 : if (ret) {
1510 0 : return ret;
1511 : }
1512 :
1513 54 : return ldb_wait(req->handle, LDB_WAIT_ALL);
1514 : }
1515 :
1516 23 : static bool dsdb_data_blobs_equal(const DATA_BLOB *d1, const DATA_BLOB *d2)
1517 : {
1518 23 : if (d1 == NULL && d2 == NULL) {
1519 0 : return true;
1520 : }
1521 :
1522 23 : if (d1 == NULL || d2 == NULL) {
1523 0 : return false;
1524 : }
1525 :
1526 : {
1527 23 : const int cmp = data_blob_cmp(d1, d2);
1528 23 : return cmp == 0;
1529 : }
1530 : }
1531 :
1532 23 : int dsdb_update_gmsa_entry_keys(struct ldb_context *ldb,
1533 : TALLOC_CTX *mem_ctx,
1534 : const struct gmsa_update *gmsa_update)
1535 : {
1536 23 : TALLOC_CTX *tmp_ctx = NULL;
1537 23 : int ret = LDB_SUCCESS;
1538 23 : bool in_transaction = false;
1539 :
1540 23 : if (gmsa_update == NULL) {
1541 0 : ret = ldb_operr(ldb);
1542 0 : goto out;
1543 : }
1544 :
1545 23 : tmp_ctx = talloc_new(mem_ctx);
1546 23 : if (tmp_ctx == NULL) {
1547 0 : ret = ldb_oom(ldb);
1548 0 : goto out;
1549 : }
1550 :
1551 23 : gmsa_update_debug(gmsa_update);
1552 :
1553 : /* The following must take place in a single transaction. */
1554 23 : ret = ldb_transaction_start(ldb);
1555 23 : if (ret) {
1556 0 : goto out;
1557 : }
1558 23 : in_transaction = true;
1559 :
1560 : {
1561 : /*
1562 : * Before performing the update, ensure that the managed
1563 : * password ID in the database has the value we expect.
1564 : */
1565 :
1566 23 : struct ldb_result *res = NULL;
1567 23 : const struct ldb_val *pwd_id_blob = NULL;
1568 0 : static const char *const managed_pwd_id_attr[] = {
1569 : "msDS-ManagedPasswordId",
1570 : NULL,
1571 : };
1572 :
1573 23 : if (gmsa_update->dn == NULL) {
1574 0 : ret = ldb_operr(ldb);
1575 0 : goto out;
1576 : }
1577 :
1578 23 : ret = dsdb_search_dn(ldb,
1579 : tmp_ctx,
1580 : &res,
1581 23 : gmsa_update->dn,
1582 : managed_pwd_id_attr,
1583 : 0);
1584 23 : if (ret) {
1585 0 : goto out;
1586 : }
1587 :
1588 23 : if (res->count != 1) {
1589 0 : ret = ldb_error(
1590 : ldb,
1591 : LDB_ERR_NO_SUCH_OBJECT,
1592 : "failed to find Group Managed Service Account "
1593 : "to verify managed password ID");
1594 0 : goto out;
1595 : }
1596 :
1597 23 : pwd_id_blob = ldb_msg_find_ldb_val(res->msgs[0],
1598 : "msDS-ManagedPasswordId");
1599 23 : if (!dsdb_data_blobs_equal(pwd_id_blob,
1600 23 : gmsa_update->found_pwd_id))
1601 : {
1602 : /*
1603 : * The account’s managed password ID doesn’t match what
1604 : * we thought it was — cancel the update. If the caller
1605 : * needs the latest values, it will retry the search,
1606 : * performing the update again if necessary.
1607 : */
1608 0 : ret = LDB_SUCCESS;
1609 0 : goto out;
1610 : }
1611 : }
1612 :
1613 : /*
1614 : * First update the previous password (if the request is not NULL,
1615 : * indicating that the previous password already matches the password of
1616 : * the account).
1617 : */
1618 23 : ret = gmsa_perform_request(ldb, gmsa_update->old_pw_req);
1619 23 : if (ret) {
1620 0 : goto out;
1621 : }
1622 :
1623 : /* Then update the current password. */
1624 23 : ret = gmsa_perform_request(ldb, gmsa_update->new_pw_req);
1625 23 : if (ret) {
1626 0 : goto out;
1627 : }
1628 :
1629 : /* Finally, update the msDS-ManagedPasswordId attribute. */
1630 23 : ret = gmsa_perform_request(ldb, gmsa_update->pwd_id_req);
1631 23 : if (ret) {
1632 0 : goto out;
1633 : }
1634 :
1635 : /* Commit the transaction. */
1636 23 : ret = ldb_transaction_commit(ldb);
1637 23 : in_transaction = false;
1638 23 : if (ret) {
1639 0 : goto out;
1640 : }
1641 :
1642 23 : out:
1643 23 : if (in_transaction) {
1644 0 : int ret2 = ldb_transaction_cancel(ldb);
1645 0 : if (ret2) {
1646 0 : ret = ret2;
1647 : }
1648 : }
1649 23 : talloc_free(tmp_ctx);
1650 23 : return ret;
1651 : }
1652 :
1653 342390 : int dsdb_update_gmsa_keys(struct ldb_context *ldb,
1654 : TALLOC_CTX *mem_ctx,
1655 : const struct ldb_result *res,
1656 : bool *retry_out)
1657 : {
1658 342390 : TALLOC_CTX *tmp_ctx = NULL;
1659 342390 : int ret = LDB_SUCCESS;
1660 342390 : bool retry = false;
1661 12204 : unsigned i;
1662 12204 : NTTIME current_time;
1663 342390 : bool am_rodc = true;
1664 :
1665 : /*
1666 : * This is non-zero if we are local to the sam.ldb, this is an
1667 : * opaque set by the samba_dsdb module
1668 : */
1669 342390 : void *samba_dsdb_opaque = ldb_get_opaque(
1670 : ldb, DSDB_OPAQUE_PARTITION_MODULE_MSG_OPAQUE_NAME);
1671 :
1672 342390 : if (samba_dsdb_opaque == NULL) {
1673 : /*
1674 : * We are not connected locally, so no point trying to
1675 : * set passwords
1676 : */
1677 178 : *retry_out = false;
1678 178 : return LDB_SUCCESS;
1679 : }
1680 :
1681 : {
1682 : /* Calculate the current time, as reckoned for gMSAs. */
1683 342212 : bool ok = dsdb_gmsa_current_time(ldb, ¤t_time);
1684 342212 : if (!ok) {
1685 0 : ret = ldb_operr(ldb);
1686 0 : goto out;
1687 : }
1688 : }
1689 :
1690 342212 : tmp_ctx = talloc_new(mem_ctx);
1691 342212 : if (tmp_ctx == NULL) {
1692 0 : ret = ldb_oom(ldb);
1693 0 : goto out;
1694 : }
1695 :
1696 : /* Are we operating as an RODC? */
1697 342212 : ret = samdb_rodc(ldb, &am_rodc);
1698 342212 : if (ret != LDB_SUCCESS) {
1699 0 : DBG_WARNING("unable to tell if we are an RODC\n");
1700 0 : goto out;
1701 : }
1702 :
1703 : /* Loop through each entry in the results. */
1704 684540 : for (i = 0; i < res->count; ++i) {
1705 342328 : struct ldb_message *msg = res->msgs[i];
1706 342328 : struct gmsa_update *gmsa_update = NULL;
1707 342328 : const bool is_gmsa = dsdb_account_is_gmsa(ldb, msg);
1708 :
1709 : /* Is the account a Group Managed Service Account? */
1710 342328 : if (!is_gmsa) {
1711 : /*
1712 : * It’s not a gMSA, and there’s nothing more to do for
1713 : * this result.
1714 : */
1715 342307 : continue;
1716 : }
1717 :
1718 114 : if (am_rodc) {
1719 : static const char *const secret_attributes[] = {
1720 : DSDB_SECRET_ATTRIBUTES};
1721 : size_t j;
1722 :
1723 : /*
1724 : * If we’re an RODC, we won’t be able to update the
1725 : * database entry with the new gMSA keys. The simplest
1726 : * thing to do is redact all the password attributes in
1727 : * the message. If our caller is the KDC, it will
1728 : * recognize the missing keys and dispatch a referral to
1729 : * a writable DC. */
1730 0 : for (j = 0; j < ARRAY_SIZE(secret_attributes); ++j) {
1731 0 : ldb_msg_remove_attr(msg, secret_attributes[j]);
1732 : }
1733 :
1734 : /* Proceed to the next search result. */
1735 0 : continue;
1736 : }
1737 :
1738 : /* Update any old gMSA state. */
1739 114 : ret = gmsa_recalculate_managed_pwd(
1740 : tmp_ctx, ldb, msg, current_time, &gmsa_update, NULL);
1741 114 : if (ret) {
1742 0 : goto out;
1743 : }
1744 :
1745 114 : if (gmsa_update == NULL) {
1746 : /*
1747 : * The usual case; the keys are up‐to‐date, and there’s
1748 : * nothing more to do for this result.
1749 : */
1750 93 : continue;
1751 : }
1752 :
1753 21 : ret = dsdb_update_gmsa_entry_keys(ldb, tmp_ctx, gmsa_update);
1754 21 : if (ret) {
1755 0 : goto out;
1756 : }
1757 :
1758 : /*
1759 : * Since the database entry has been updated, the caller will
1760 : * need to perform the search again.
1761 : */
1762 21 : retry = true;
1763 : }
1764 :
1765 342212 : *retry_out = retry;
1766 :
1767 342212 : out:
1768 342212 : talloc_free(tmp_ctx);
1769 342212 : return ret;
1770 : }
1771 :
1772 364684 : bool dsdb_gmsa_current_time(struct ldb_context *ldb, NTTIME *current_time_out)
1773 : {
1774 364684 : const unsigned long long *gmsa_time = talloc_get_type(
1775 : ldb_get_opaque(ldb, DSDB_GMSA_TIME_OPAQUE), unsigned long long);
1776 :
1777 364684 : if (gmsa_time != NULL) {
1778 186 : *current_time_out = *gmsa_time;
1779 186 : return true;
1780 : }
1781 :
1782 364498 : return gmsa_current_time(current_time_out);
1783 : }
|