From 85fbdcd048fefc5c7edca709855e7510f3085a9a Mon Sep 17 00:00:00 2001 From: Jo Sutton Date: Tue, 13 Feb 2024 16:09:57 +1300 Subject: [PATCH] s4:dsdb: Add functions for Group Managed Service Accounts implementation Signed-off-by: Jo Sutton Reviewed-by: Andrew Bartlett --- source4/dsdb/gmsa/gkdi.c | 248 ++++++++ source4/dsdb/gmsa/gkdi.h | 25 + source4/dsdb/gmsa/util.c | 1414 ++++++++++++++++++++++++++++++++++++++++++++ source4/dsdb/gmsa/util.h | 106 ++++ source4/dsdb/wscript_build | 2 +- 5 files changed, 1794 insertions(+), 1 deletion(-) create mode 100644 source4/dsdb/gmsa/util.c create mode 100644 source4/dsdb/gmsa/util.h diff --git a/source4/dsdb/gmsa/gkdi.c b/source4/dsdb/gmsa/gkdi.c index 3a79a5eb5fc..917b1355958 100644 --- a/source4/dsdb/gmsa/gkdi.c +++ b/source4/dsdb/gmsa/gkdi.c @@ -34,6 +34,77 @@ #include "librpc/gen_ndr/gkdi.h" #include "librpc/gen_ndr/ndr_gkdi.h" +NTSTATUS gkdi_root_key_from_msg(TALLOC_CTX *mem_ctx, + const struct GUID root_key_id, + const struct ldb_message *const msg, + const struct ProvRootKey **const root_key_out) +{ + NTSTATUS status = NT_STATUS_OK; + struct ldb_val root_key_data = {}; + struct KdfAlgorithm kdf_algorithm = {}; + + const int version = ldb_msg_find_attr_as_int(msg, "msKds-Version", 0); + const NTTIME create_time = samdb_result_nttime(msg, + "msKds-CreateTime", + 0); + const NTTIME use_start_time = samdb_result_nttime(msg, + "msKds-UseStartTime", + 0); + const char *domain_id = ldb_msg_find_attr_as_string(msg, + "msKds-DomainID", + NULL); + + { + const struct ldb_val *root_key_val = ldb_msg_find_ldb_val( + msg, "msKds-RootKeyData"); + if (root_key_val != NULL) { + root_key_data = *root_key_val; + } + } + + { + const char *algorithm_id = ldb_msg_find_attr_as_string( + msg, "msKds-KDFAlgorithmID", NULL); + const struct ldb_val *kdf_param_val = ldb_msg_find_ldb_val( + msg, "msKds-KDFParam"); + status = kdf_algorithm_from_params(algorithm_id, + kdf_param_val, + &kdf_algorithm); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + } + + status = ProvRootKey(mem_ctx, + root_key_id, + version, + root_key_data, + create_time, + use_start_time, + domain_id, + kdf_algorithm, + root_key_out); + if (!NT_STATUS_IS_OK(status)) { + goto out; + } + +out: + return status; +} + +/* + * Calculate an appropriate useStartTime for a root key created at + * ‘current_time’. + * + * This function goes unused. + */ +NTTIME gkdi_root_key_use_start_time(const NTTIME current_time) +{ + const NTTIME start_time = gkdi_get_interval_start_time(current_time); + + return start_time + gkdi_key_cycle_duration + gkdi_max_clock_skew; +} + static int gkdi_create_root_key(TALLOC_CTX *mem_ctx, struct ldb_context *const ldb, const NTTIME current_time, @@ -504,3 +575,180 @@ out: talloc_free(tmp_ctx); return ret; } + +int gkdi_root_key_from_id(TALLOC_CTX *mem_ctx, + struct ldb_context *const ldb, + const struct GUID *const root_key_id, + const struct ldb_message **const root_key_out) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_dn *root_key_dn = NULL; + struct ldb_result *res = NULL; + int ret = LDB_SUCCESS; + + *root_key_out = NULL; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + ret = ldb_oom(ldb); + goto out; + } + + root_key_dn = samdb_gkdi_root_key_dn(ldb, tmp_ctx, root_key_id); + if (root_key_dn == NULL) { + ret = ldb_operr(ldb); + goto out; + } + + ret = dsdb_search_dn( + ldb, tmp_ctx, &res, root_key_dn, root_key_attrs, 0); + if (ret) { + goto out; + } + + if (res->count != 1) { + ret = dsdb_werror(ldb, + LDB_ERR_NO_SUCH_OBJECT, + W_ERROR(HRES_ERROR_V(HRES_NTE_NO_KEY)), + "failed to find root key"); + goto out; + } + + *root_key_out = talloc_steal(mem_ctx, res->msgs[0]); + +out: + talloc_free(tmp_ctx); + return ret; +} + +int gkdi_most_recently_created_root_key( + TALLOC_CTX *mem_ctx, + struct ldb_context *const ldb, + _UNUSED_ const NTTIME current_time, + const NTTIME not_after, + struct GUID *const root_key_id_out, + const struct ldb_message **const root_key_out) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_result *res = NULL; + int ret = LDB_SUCCESS; + + *root_key_out = NULL; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + ret = ldb_oom(ldb); + goto out; + } + + { + struct ldb_dn *root_key_container_dn = NULL; + + root_key_container_dn = samdb_gkdi_root_key_container_dn( + ldb, tmp_ctx); + if (root_key_container_dn == NULL) { + ret = ldb_operr(ldb); + goto out; + } + + ret = dsdb_search(ldb, + tmp_ctx, + &res, + root_key_container_dn, + LDB_SCOPE_ONELEVEL, + root_key_attrs, + 0, + "(msKds-UseStartTime<=%" PRIu64 ")", + not_after); + if (ret) { + goto out; + } + } + + /* + * Windows just gives up if there are more than 1000 root keys in the + * container. + */ + + { + struct root_key_candidate { + struct GUID id; + const struct ldb_message *key; + NTTIME create_time; + } most_recent_key = { + .key = NULL, + }; + unsigned i; + + for (i = 0; i < res->count; ++i) { + struct root_key_candidate key = { + .key = res->msgs[i], + }; + const struct ldb_val *rdn_val = NULL; + bool ok; + + key.create_time = samdb_result_nttime( + key.key, "msKds-CreateTime", 0); + if (key.create_time < most_recent_key.create_time) { + /* We already have a more recent key. */ + continue; + } + + rdn_val = ldb_dn_get_rdn_val(key.key->dn); + if (rdn_val == NULL) { + continue; + } + + if (rdn_val->length != 36) { + /* + * Check the RDN is the right length — 36 is the + * length of a UUID. + */ + continue; + } + + ok = parse_guid_string((const char *)rdn_val->data, + &key.id); + if (!ok) { + /* The RDN is not a correctly formatted GUID. */ + continue; + } + + /* + * We’ve found a new candidate for the most recent root + * key. + */ + most_recent_key = key; + } + + if (most_recent_key.key == NULL) { + /* + * We were not able to find a suitable root key, but + * there is a possibility that a key we create now will + * do: if gkdi_root_key_use_start_time(current_time) ≤ + * not_after, then a newly‐created key will satisfy our + * caller’s requirements. + * + * Unfortunately, with gMSAs this (I believe) will never + * be the case. It’s too late to call + * gkdi_new_root_key() — the new key will be a bit *too* + * new to be usable for a gMSA. + */ + + ret = dsdb_werror(ldb, + LDB_ERR_NO_SUCH_OBJECT, + W_ERROR(HRES_ERROR_V( + HRES_NTE_NO_KEY)), + "failed to find a suitable root key"); + goto out; + } + + /* Return the root key that we found. */ + *root_key_id_out = most_recent_key.id; + *root_key_out = talloc_steal(mem_ctx, most_recent_key.key); + } + +out: + talloc_free(tmp_ctx); + return ret; +} diff --git a/source4/dsdb/gmsa/gkdi.h b/source4/dsdb/gmsa/gkdi.h index e6e36064445..4c5394167fd 100644 --- a/source4/dsdb/gmsa/gkdi.h +++ b/source4/dsdb/gmsa/gkdi.h @@ -28,6 +28,19 @@ #include "librpc/gen_ndr/misc.h" struct ldb_message; +struct ProvRootKey; +NTSTATUS gkdi_root_key_from_msg(TALLOC_CTX *mem_ctx, + const struct GUID root_key_id, + const struct ldb_message *const msg, + const struct ProvRootKey **const root_key_out); + +/* + * Calculate an appropriate useStartTime for a root key created at + * ‘current_time’. + * + * This function goes unused. + */ +NTTIME gkdi_root_key_use_start_time(const NTTIME current_time); /* * Create and return a new GKDI root key. @@ -42,5 +55,17 @@ int gkdi_new_root_key(TALLOC_CTX *mem_ctx, struct GUID *const root_key_id_out, const struct ldb_message **const root_key_out); +int gkdi_root_key_from_id(TALLOC_CTX *mem_ctx, + struct ldb_context *const ldb, + const struct GUID *const root_key_id, + const struct ldb_message **const root_key_out); + +int gkdi_most_recently_created_root_key( + TALLOC_CTX *mem_ctx, + struct ldb_context *const ldb, + const NTTIME current_time, + const NTTIME not_after, + struct GUID *const root_key_id_out, + const struct ldb_message **const root_key_out); #endif /* DSDB_GMSA_GKDI_H */ diff --git a/source4/dsdb/gmsa/util.c b/source4/dsdb/gmsa/util.c new file mode 100644 index 00000000000..30ea532f709 --- /dev/null +++ b/source4/dsdb/gmsa/util.c @@ -0,0 +1,1414 @@ +/* + Unix SMB/CIFS implementation. + msDS-ManagedPassword attribute for Group Managed Service Accounts + + Copyright (C) Catalyst.Net Ltd 2024 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "includes.h" +#include "ldb.h" +#include "ldb_module.h" +#include "ldb_errors.h" +#include "ldb_private.h" +#include "lib/crypto/gkdi.h" +#include "lib/crypto/gmsa.h" +#include "lib/util/data_blob.h" +#include "lib/util/fault.h" +#include "lib/util/time.h" +#include "libcli/security/access_check.h" +#include "librpc/gen_ndr/auth.h" +#include "librpc/gen_ndr/ndr_gkdi.h" +#include "librpc/gen_ndr/ndr_gmsa.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "dsdb/common/util.h" +#include "dsdb/gmsa/gkdi.h" +#include "dsdb/gmsa/util.h" +#include "dsdb/samdb/samdb.h" + +#undef strcasecmp + +enum RootKeyType { + ROOT_KEY_NONE, + ROOT_KEY_SPECIFIC, + ROOT_KEY_NONSPECIFIC, + ROOT_KEY_OBTAINED, +}; + +struct RootKey { + TALLOC_CTX *mem_ctx; + enum RootKeyType type; + union { + struct KeyEnvelopeId specific; + struct { + NTTIME key_start_time; + } nonspecific; + struct { + struct gmsa_update_pwd_part key; + struct gmsa_null_terminated_password *password; + } obtained; + } u; +}; + +static const struct RootKey empty_root_key = {.type = ROOT_KEY_NONE}; + +int gmsa_allowed_to_view_managed_password(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const struct ldb_message *msg, + const struct dom_sid *account_sid, + bool *allowed_out) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct security_descriptor group_msa_membership_sd = {}; + const struct security_token *user_token = NULL; + NTSTATUS status = NT_STATUS_OK; + int ret = LDB_SUCCESS; + + if (allowed_out == NULL) { + ret = ldb_operr(ldb); + goto out; + } + *allowed_out = false; + + { + const struct auth_session_info *session_info = ldb_get_opaque( + ldb, DSDB_SESSION_INFO); + const enum security_user_level level = + security_session_user_level(session_info, NULL); + + if (level == SECURITY_SYSTEM) { + *allowed_out = true; + ret = LDB_SUCCESS; + goto out; + } + + if (session_info == NULL) { + ret = dsdb_werror(ldb, + LDB_ERR_OPERATIONS_ERROR, + WERR_DS_CANT_RETRIEVE_ATTS, + "no right to view attribute"); + goto out; + } + + user_token = session_info->security_token; + } + + tmp_ctx = talloc_new(msg); + if (tmp_ctx == NULL) { + ret = ldb_oom(ldb); + goto out; + } + + { + const struct ldb_val *group_msa_membership = NULL; + enum ndr_err_code err; + + /* [MS-ADTS] 3.1.1.4.4: Extended Access Checks. */ + group_msa_membership = ldb_msg_find_ldb_val( + msg, "msDS-GroupMSAMembership"); + if (group_msa_membership == NULL) { + ret = dsdb_werror(ldb, + LDB_ERR_OPERATIONS_ERROR, + WERR_DS_CANT_RETRIEVE_ATTS, + "no right to view attribute"); + goto out; + } + + err = ndr_pull_struct_blob_all( + group_msa_membership, + tmp_ctx, + &group_msa_membership_sd, + (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); + if (!NDR_ERR_CODE_IS_SUCCESS(err)) { + status = ndr_map_error2ntstatus(err); + DBG_WARNING("msDS-GroupMSAMembership pull failed: %s\n", + nt_errstr(status)); + ret = ldb_operr(ldb); + goto out; + } + } + + { + const uint32_t access_desired = SEC_ADS_READ_PROP; + uint32_t access_granted = 0; + + status = sec_access_check_ds(&group_msa_membership_sd, + user_token, + access_desired, + &access_granted, + NULL, + account_sid); + if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) { + /* + * The principal is not allowed to view the managed + * password. + */ + } else if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("msDS-GroupMSAMembership: " + "sec_access_check_ds(access_desired=%#08x, " + "access_granted:%#08x) failed with: %s\n", + access_desired, + access_granted, + nt_errstr(status)); + + ret = dsdb_werror( + ldb, + LDB_ERR_OPERATIONS_ERROR, + WERR_DS_CANT_RETRIEVE_ATTS, + "access check to view managed password failed"); + goto out; + } else { + /* Cool, the principal may view the password. */ + *allowed_out = true; + } + } + +out: + TALLOC_FREE(tmp_ctx); + return ret; +} + +static NTSTATUS gmsa_managed_pwd_id(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const struct ldb_val *pwd_id_blob, + const struct ProvRootKey *root_key, + struct KeyEnvelope *pwd_id_out) +{ + if (root_key == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (pwd_id_blob != NULL) { + return gkdi_pull_KeyEnvelope(mem_ctx, pwd_id_blob, pwd_id_out); + } + + { + const char *domain_name = NULL; + const char *forest_name = NULL; + + domain_name = samdb_default_domain_name(ldb, mem_ctx); + if (domain_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + forest_name = samdb_forest_name(ldb, mem_ctx); + if (forest_name == NULL) { + /* We leak ‘domain_name’, but that can’t be helped. */ + return NT_STATUS_NO_MEMORY; + } + + *pwd_id_out = (struct KeyEnvelope){ + .version = root_key->version, + .flags = ENVELOPE_FLAG_KEY_MAY_ENCRYPT_NEW_DATA, + .domain_name = domain_name, + .forest_name = forest_name, + }; + } + + return NT_STATUS_OK; +} + +void gmsa_update_managed_pwd_id(struct KeyEnvelope *pwd_id, + const struct gmsa_update_pwd_part *new_pwd) +{ + pwd_id->l0_index = new_pwd->gkid.l0_idx; + pwd_id->l1_index = new_pwd->gkid.l1_idx; + pwd_id->l2_index = new_pwd->gkid.l2_idx; + pwd_id->root_key_id = new_pwd->root_key->id; +} + +NTSTATUS gmsa_pack_managed_pwd_id(TALLOC_CTX *mem_ctx, + const struct KeyEnvelope *pwd_id, + DATA_BLOB *pwd_id_out) +{ + NTSTATUS status = NT_STATUS_OK; + enum ndr_err_code err; + + err = ndr_push_struct_blob(pwd_id_out, + mem_ctx, + pwd_id, + (ndr_push_flags_fn_t)ndr_push_KeyEnvelope); + status = ndr_map_error2ntstatus(err); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("KeyEnvelope push failed: %s\n", nt_errstr(status)); + } + + return status; +} + +static int gmsa_specific_password(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const struct KeyEnvelopeId pwd_id, + struct gmsa_update_pwd_part *new_pwd_out) +{ + TALLOC_CTX *tmp_ctx = NULL; + NTSTATUS status = NT_STATUS_OK; + int ret = LDB_SUCCESS; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + ret = ldb_oom(ldb); + goto out; + } + + { + const struct ldb_message *root_key_msg = NULL; + + ret = gkdi_root_key_from_id(tmp_ctx, + ldb, + &pwd_id.root_key_id, + &root_key_msg); + if (ret) { + goto out; + } + + status = gkdi_root_key_from_msg(mem_ctx, + pwd_id.root_key_id, + root_key_msg, + &new_pwd_out->root_key); + if (!NT_STATUS_IS_OK(status)) { + ret = ldb_operr(ldb); + goto out; + } + } + + new_pwd_out->gkid = pwd_id.gkid; + +out: + talloc_free(tmp_ctx); + return ret; +} + +static int gmsa_nonspecific_password(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const NTTIME key_start_time, + const NTTIME current_time, + struct gmsa_update_pwd_part *new_pwd_out) +{ + TALLOC_CTX *tmp_ctx = NULL; + int ret = LDB_SUCCESS; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + ret = ldb_oom(ldb); + goto out; + } + + { + const struct ldb_message *root_key_msg = NULL; + struct GUID root_key_id; + NTSTATUS status = NT_STATUS_OK; + + ret = gkdi_most_recently_created_root_key(tmp_ctx, + ldb, + current_time, + key_start_time, + &root_key_id, + &root_key_msg); + if (ret) { + goto out; + } + + status = gkdi_root_key_from_msg(mem_ctx, + root_key_id, + root_key_msg, + &new_pwd_out->root_key); + if (!NT_STATUS_IS_OK(status)) { + ret = ldb_operr(ldb); + goto out; + } + } + + new_pwd_out->gkid = gkdi_get_interval_id(key_start_time); + +out: + talloc_free(tmp_ctx); + return ret; +} + +static int gmsa_specifc_root_key(TALLOC_CTX *mem_ctx, + const struct KeyEnvelopeId pwd_id, + struct RootKey *root_key_out) +{ + if (root_key_out == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + *root_key_out = (struct RootKey){.mem_ctx = mem_ctx, + .type = ROOT_KEY_SPECIFIC, + .u.specific = pwd_id}; + return LDB_SUCCESS; +} + +static int gmsa_nonspecifc_root_key(TALLOC_CTX *mem_ctx, + const NTTIME key_start_time, + struct RootKey *root_key_out) +{ + if (root_key_out == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + *root_key_out = (struct RootKey){ + .mem_ctx = mem_ctx, + .type = ROOT_KEY_NONSPECIFIC, + .u.nonspecific.key_start_time = key_start_time}; + return LDB_SUCCESS; +} + +static int gmsa_obtained_root_key_steal( + TALLOC_CTX *mem_ctx, + const struct gmsa_update_pwd_part key, + struct gmsa_null_terminated_password *password, + struct RootKey *root_key_out) +{ + if (root_key_out == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + + /* Steal the data on to the appropriate memory context. */ + talloc_steal(mem_ctx, key.root_key); + talloc_steal(mem_ctx, password); + + *root_key_out = (struct RootKey){.mem_ctx = mem_ctx, + .type = ROOT_KEY_OBTAINED, + .u.obtained = {.key = key, + .password = password}}; + return LDB_SUCCESS; +} + +static int gmsa_fetch_root_key(struct ldb_context *ldb, + const NTTIME current_time, + struct RootKey *root_key, + const struct dom_sid *const account_sid) +{ + TALLOC_CTX *tmp_ctx = NULL; + NTSTATUS status = NT_STATUS_OK; + int ret = LDB_SUCCESS; + + if (root_key == NULL) { + ret = ldb_operr(ldb); + goto out; + } + + switch (root_key->type) { + case ROOT_KEY_SPECIFIC: + case ROOT_KEY_NONSPECIFIC: { + struct gmsa_null_terminated_password *password = NULL; + struct gmsa_update_pwd_part key; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ldb_oom(ldb); + goto out; + } + + if (root_key->type == ROOT_KEY_SPECIFIC) { + /* Search for a specific root key. */ + ret = gmsa_specific_password(tmp_ctx, + ldb, + root_key->u.specific, + &key); + if (ret) { + /* + * We couldn’t find a specific key — treat this + * as an error. + */ + goto out; + } + } else { + /* + * Search for the most recent root key meeting the start + * time requirement. + */ + ret = gmsa_nonspecific_password( + tmp_ctx, + ldb, + root_key->u.nonspecific.key_start_time, + current_time, + &key); + /* Handle errors below. */ + } + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + /* + * We couldn’t find a key meeting the requirements — + * that’s OK, presumably. It’s not critical if we can’t + * find a key for deriving a previous gMSA password, for + * example. + */ + ret = LDB_SUCCESS; + *root_key = empty_root_key; + } else if (ret) { + goto out; + } else { + /* Derive the password. */ + status = gmsa_talloc_password_based_on_key_id( + tmp_ctx, + key.gkid, + current_time, + key.root_key, + account_sid, + &password); + if (!NT_STATUS_IS_OK(status)) { + ret = ldb_operr(ldb); + goto out; + } + + /* + * Initialize the obtained structure, and give it the + * appropriate memory context. + */ + ret = gmsa_obtained_root_key_steal(root_key->mem_ctx, + key, + password, + root_key); + if (ret) { + goto out; + } + } + } break; + case ROOT_KEY_NONE: + /* No key is available. */ + break; + case ROOT_KEY_OBTAINED: + /* The key has already been obtained. */ + break; + default: + ret = ldb_operr(ldb); + goto out; + } + +out: + TALLOC_FREE(tmp_ctx); + return ret; +} + +/* + * Get the password and update information associated with a root key. The + * caller *does not* own these structures; the root key object retains + * ownership. + */ +static int gmsa_get_root_key( + struct ldb_context *ldb, + const NTTIME current_time, + const struct dom_sid *const account_sid, + struct RootKey *root_key, + struct gmsa_null_terminated_password **password_out, + struct gmsa_update_pwd_part *update_out) +{ + int ret = LDB_SUCCESS; + + if (password_out == NULL) { + ret = ldb_operr(ldb); + goto out; + } + *password_out = NULL; + + if (update_out != NULL) { + *update_out = (struct gmsa_update_pwd_part){}; + } + + /* Fetch the root key from the database and obtain the password. */ + ret = gmsa_fetch_root_key(ldb, current_time, root_key, account_sid); + if (ret) { + goto out; + } + + switch (root_key->type) { + case ROOT_KEY_NONE: + /* No key is available. */ + break; + case ROOT_KEY_OBTAINED: + *password_out = root_key->u.obtained.password; + if (update_out != NULL) { + *update_out = root_key->u.obtained.key; + } + break; + default: + /* Unexpected. */ + ret = ldb_operr(ldb); + goto out; + } + +out: + return ret; +} + +static int gmsa_system_update_password_id_req( + struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const struct ldb_message *msg, + const struct gmsa_update_pwd *new_pwd, + struct ldb_request **req_out) +{ + TALLOC_CTX *tmp_ctx = NULL; + const struct ldb_val *pwd_id_blob = ldb_msg_find_ldb_val( + msg, "msDS-ManagedPasswordId"); + struct KeyEnvelope pwd_id; + struct ldb_message *mod_msg = NULL; + NTSTATUS status = NT_STATUS_OK; + int ret = LDB_SUCCESS; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + ret = ldb_oom(ldb); + goto out; + } + + /* Create a new ldb message. */ + mod_msg = ldb_msg_new(tmp_ctx); + if (mod_msg == NULL) { + ret = ldb_oom(ldb); + goto out; + } + { + struct ldb_dn *dn = ldb_dn_copy(mod_msg, msg->dn); + if (dn == NULL) { + ret = ldb_oom(ldb); + goto out; + } + mod_msg->dn = dn; + } + + /* Get the Managed Password ID. */ + status = gmsa_managed_pwd_id( + ldb, tmp_ctx, pwd_id_blob, new_pwd->new_id.root_key, &pwd_id); + if (!NT_STATUS_IS_OK(status)) { + ret = ldb_operr(ldb); + goto out; + } + + /* Update the password ID to contain the new GKID and root key ID. */ + gmsa_update_managed_pwd_id(&pwd_id, &new_pwd->new_id); + + { + DATA_BLOB new_pwd_id_blob = {}; + + /* Pack the current password ID. */ + status = gmsa_pack_managed_pwd_id(tmp_ctx, + &pwd_id, + &new_pwd_id_blob); + if (!NT_STATUS_IS_OK(status)) { + ret = ldb_operr(ldb); + goto out; + } + + /* Update the msDS-ManagedPasswordId attribute. */ + ret = ldb_msg_append_steal_value(mod_msg, + "msDS-ManagedPasswordId", + &new_pwd_id_blob, + LDB_FLAG_MOD_REPLACE); + if (ret) { + goto out; + } + } + + { + DATA_BLOB *prev_pwd_id_blob = NULL; + DATA_BLOB _prev_pwd_id_blob; + DATA_BLOB prev_pwd_id = {}; + + if (new_pwd->prev_id.root_key != NULL) { + /* + * Update the password ID to contain the previous GKID + * and root key ID. + */ + gmsa_update_managed_pwd_id(&pwd_id, &new_pwd->prev_id); + + /* Pack the previous password ID. */ + status = gmsa_pack_managed_pwd_id(tmp_ctx, + &pwd_id, + &prev_pwd_id); + if (!NT_STATUS_IS_OK(status)) { + ret = ldb_operr(ldb); + goto out; + } + + prev_pwd_id_blob = &prev_pwd_id; + } else if (pwd_id_blob != NULL) { + /* Copy the current password ID to the previous ID. */ + _prev_pwd_id_blob = ldb_val_dup(tmp_ctx, pwd_id_blob); + if (_prev_pwd_id_blob.length != pwd_id_blob->length) { + ret = ldb_oom(ldb); + goto out; + } + + prev_pwd_id_blob = &_prev_pwd_id_blob; + } + + if (prev_pwd_id_blob != NULL) { + /* + * Update the msDS-ManagedPasswordPreviousId attribute. + */ + ret = ldb_msg_append_steal_value( + mod_msg, + "msDS-ManagedPasswordPreviousId", + prev_pwd_id_blob, + LDB_FLAG_MOD_REPLACE); + if (ret) { + goto out; + } + } + } + + { + struct ldb_request *req = NULL; + + /* Build the ldb request to return. */ + ret = ldb_build_mod_req(&req, + ldb, + tmp_ctx, + mod_msg, + NULL, + NULL, + ldb_op_default_callback, + NULL); + if (ret) { + goto out; + } + + /* Tie the lifetime of the message to that of the request. */ + talloc_steal(req, mod_msg); + + /* Make sure the password ID update happens as System. */ + ret = dsdb_request_add_controls(req, DSDB_FLAG_AS_SYSTEM); + if (ret) { + goto out; + } + + *req_out = talloc_steal(mem_ctx, req); + } + +out: + talloc_free(tmp_ctx); + return ret; +} + +int gmsa_generate_blobs(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const NTTIME current_time, + const struct dom_sid *const account_sid, + DATA_BLOB *pwd_id_blob_out, + struct gmsa_null_terminated_password **password_out) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct KeyEnvelope pwd_id; + const struct ProvRootKey *root_key = NULL; + NTSTATUS status = NT_STATUS_OK; + int ret = LDB_SUCCESS; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + ret = ldb_oom(ldb); + goto out; + } + + { + const struct ldb_message *root_key_msg = NULL; + struct GUID root_key_id; + const NTTIME one_interval = gkdi_key_cycle_duration + + gkdi_max_clock_skew; + const NTTIME one_interval_ago = current_time - + MIN(one_interval, current_time); + + ret = gkdi_most_recently_created_root_key(tmp_ctx, + ldb, + current_time, + one_interval_ago, + &root_key_id, + &root_key_msg); + if (ret) { + goto out; + } + + status = gkdi_root_key_from_msg(tmp_ctx, + root_key_id, + root_key_msg, + &root_key); + if (!NT_STATUS_IS_OK(status)) { + ret = ldb_operr(ldb); + goto out; + } + } + + /* Get the Managed Password ID. */ + status = gmsa_managed_pwd_id(ldb, tmp_ctx, NULL, root_key, &pwd_id); + if (!NT_STATUS_IS_OK(status)) { + ret = ldb_operr(ldb); + goto out; + } + + { + const struct Gkid current_gkid = gkdi_get_interval_id( + current_time); + + /* Derive the password. */ + status = gmsa_talloc_password_based_on_key_id(tmp_ctx, + current_gkid, + current_time, + root_key, + account_sid, + password_out); + if (!NT_STATUS_IS_OK(status)) { + ret = ldb_operr(ldb); + goto out; + } + + { + const struct gmsa_update_pwd_part new_id = { + .root_key = root_key, + .gkid = current_gkid, + }; + + /* + * Update the password ID to contain the new GKID and + * root key ID. + */ + gmsa_update_managed_pwd_id(&pwd_id, &new_id); + } + } + + /* Pack the current password ID. */ + status = gmsa_pack_managed_pwd_id(mem_ctx, &pwd_id, pwd_id_blob_out); + if (!NT_STATUS_IS_OK(status)) { + ret = ldb_operr(ldb); + goto out; + } + + /* Transfer ownership of the password to the caller’s memory context. */ + talloc_steal(mem_ctx, *password_out); + +out: + talloc_free(tmp_ctx); + return ret; +} + +static int gmsa_create_update(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const struct ldb_message *msg, + const NTTIME current_time, + const struct dom_sid *account_sid, + const bool current_key_becomes_previous, + struct RootKey *current_key, + struct RootKey *previous_key, + struct gmsa_update **update_out) +{ + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_request *old_pw_req = NULL; + struct ldb_request *new_pw_req = NULL; + struct ldb_request *pwd_id_req = NULL; + struct gmsa_update_pwd new_pwd = {}; + struct gmsa_update *update = NULL; + NTSTATUS status = NT_STATUS_OK; + int ret = LDB_SUCCESS; + + if (update_out == NULL) { + ret = ldb_operr(ldb); + goto out; + } + *update_out = NULL; + + if (current_key == NULL) { + ret = ldb_operr(ldb); + goto out; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + ret = ldb_oom(ldb); + goto out; + } + + { + /* + * The password_hash module expects these passwords to be + * null‐terminated. + */ + struct gmsa_null_terminated_password *new_password = NULL; + + ret = gmsa_get_root_key(ldb, + current_time, + account_sid, + current_key, + &new_password, + &new_pwd.new_id); + if (ret) { + goto out; + } + + if (new_password == NULL) { + ret = ldb_operr(ldb); + goto out; + } + + status = gmsa_system_password_update_request( + ldb, tmp_ctx, msg->dn, new_password->buf, &new_pw_req); + if (!NT_STATUS_IS_OK(status)) { + ret = ldb_operr(ldb); + goto out; + } + } + + /* Does the previous password need to be updated? */ + if (current_key_becomes_previous) { + /* + * When we perform the password set, the now‐current password + * will become the previous password automatically. We don’t + * have to manage that ourselves. + */ + } else { + struct gmsa_null_terminated_password *old_password = NULL; + + /* The current key cannot be reused as the previous key. */ + ret = gmsa_get_root_key(ldb, + current_time, + account_sid, + previous_key, + &old_password, + &new_pwd.prev_id); + if (ret) { + goto out; + } + + if (old_password != NULL) { + status = gmsa_system_password_update_request( + ldb, + tmp_ctx, + msg->dn, + old_password->buf, + &old_pw_req); + if (!NT_STATUS_IS_OK(status)) { + ret = ldb_operr(ldb); + goto out; + } + } + } + + /* Ready the update of the msDS-ManagedPasswordId attribute. */ + ret = gmsa_system_update_password_id_req( + ldb, tmp_ctx, msg, &new_pwd, &pwd_id_req); + if (ret) { + goto out; + } + + update = talloc(tmp_ctx, struct gmsa_update); + if (update == NULL) { + ret = ldb_oom(ldb); + goto out; + } + + *update = (struct gmsa_update){ + .old_pw_req = talloc_steal(update, old_pw_req), + .new_pw_req = talloc_steal(update, new_pw_req), + .pwd_id_req = talloc_steal(update, pwd_id_req)}; + + *update_out = talloc_steal(mem_ctx, update); + +out: + TALLOC_FREE(tmp_ctx); + return ret; +} + +NTSTATUS gmsa_pack_managed_pwd(TALLOC_CTX *mem_ctx, + const uint8_t *new_password, + const uint8_t *old_password, + uint64_t query_interval, + uint64_t unchanged_interval, + DATA_BLOB *managed_pwd_out) +{ + const struct MANAGEDPASSWORD_BLOB managed_pwd = { + .passwords = {.current = new_password, + .previous = old_password, + .query_interval = &query_interval, + .unchanged_interval = &unchanged_interval}}; + NTSTATUS status = NT_STATUS_OK; + enum ndr_err_code err; + + err = ndr_push_struct_blob(managed_pwd_out, + mem_ctx, + &managed_pwd, + (ndr_push_flags_fn_t) + ndr_push_MANAGEDPASSWORD_BLOB); + status = ndr_map_error2ntstatus(err); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("MANAGEDPASSWORD_BLOB push failed: %s\n", + nt_errstr(status)); + } + + return status; +} + +bool dsdb_account_is_gmsa(struct ldb_context *ldb, + const struct ldb_message *msg) +{ + /* + * Check if the account has objectClass + * ‘msDS-GroupManagedServiceAccount’. + */ + return samdb_find_attribute(ldb, + msg, + "objectclass", + "msDS-GroupManagedServiceAccount") != NULL; +} + +static struct new_key { + NTTIME start_time; + bool immediately_follows_previous; +} calculate_new_key(const NTTIME current_time, + const NTTIME current_key_expiration_time, + const NTTIME rollover_interval) +{ + NTTIME new_key_start_time = current_key_expiration_time; + bool immediately_follows_previous = false; + + if (new_key_start_time < current_time && rollover_interval) { + /* + * Advance the key start time by the rollover interval until it + * would be greater than the current time. + */ + const NTTIME time_to_advance_by = current_time + 1 - + new_key_start_time; + const uint64_t stale_count = time_to_advance_by / + rollover_interval; + new_key_start_time += stale_count * rollover_interval; + + SMB_ASSERT(new_key_start_time <= current_time); + + immediately_follows_previous = stale_count == 0; + } else { + /* + * It is possible that new_key_start_time ≥ current_time; + * specifically, if there is no password ID, and the creation + * time of the gMSA is in the future (perhaps due to replication + * weirdness). + */ + } + + return (struct new_key){ + .start_time = new_key_start_time, + .immediately_follows_previous = immediately_follows_previous}; +} + +static bool gmsa_creation_time(const struct ldb_message *msg, + const NTTIME current_time, + NTTIME *creation_time_out) +{ + const struct ldb_val *when_created = NULL; + time_t creation_unix_time; + int ret; + + when_created = ldb_msg_find_ldb_val(msg, "whenCreated"); + ret = ldb_val_to_time(when_created, &creation_unix_time); + if (ret) { + /* Fail if we can’t read the attribute or it isn’t present. */ + return false; + } + + unix_to_nt_time(creation_time_out, creation_unix_time); + return true; +} + +static const struct KeyEnvelopeId *gmsa_get_managed_pwd_id_attr_name( + const struct ldb_message *msg, + const char *attr_name, + struct KeyEnvelopeId *key_env_out) +{ + const struct ldb_val *pwd_id_blob = ldb_msg_find_ldb_val(msg, + attr_name); + if (pwd_id_blob == NULL) { + return NULL; + } + + return gkdi_pull_KeyEnvelopeId(*pwd_id_blob, key_env_out); +} + +const struct KeyEnvelopeId *gmsa_get_managed_pwd_id( + const struct ldb_message *msg, + struct KeyEnvelopeId *key_env_out) +{ + return gmsa_get_managed_pwd_id_attr_name(msg, + "msDS-ManagedPasswordId", + key_env_out); +} + +static const struct KeyEnvelopeId *gmsa_get_managed_pwd_prev_id( + const struct ldb_message *msg, + struct KeyEnvelopeId *key_env_out) +{ + return gmsa_get_managed_pwd_id_attr_name( + msg, "msDS-ManagedPasswordPreviousId", key_env_out); +} + +static bool samdb_result_gkdi_rollover_interval(const struct ldb_message *msg, + NTTIME *rollover_interval_out) +{ + int64_t managed_password_interval; + + managed_password_interval = ldb_msg_find_attr_as_int64( + msg, "msDS-ManagedPasswordInterval", 30); + return gkdi_rollover_interval(managed_password_interval, + rollover_interval_out); +} + +int gmsa_recalculate_managed_pwd(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const struct ldb_message *msg, + const NTTIME current_time, + struct gmsa_update **update_out, + struct gmsa_return_pwd *return_out) +{ + TALLOC_CTX *tmp_ctx = NULL; + int ret = LDB_SUCCESS; + NTTIME rollover_interval; + NTTIME current_key_expiration_time; + NTTIME key_expiration_time; + struct dom_sid account_sid; + struct KeyEnvelopeId pwd_id_buf; + const struct KeyEnvelopeId *pwd_id = NULL; + struct RootKey previous_key = empty_root_key; + struct RootKey current_key = empty_root_key; + struct gmsa_update *update = NULL; + struct gmsa_null_terminated_password *previous_password = NULL; + struct gmsa_null_terminated_password *current_password = NULL; + NTTIME query_interval = 0; + NTTIME unchanged_interval = 0; + NTTIME creation_time = 0; + NTTIME account_age = 0; + NTTIME key_start_time = 0; + bool have_key_start_time = false; + bool ok = true; + bool current_key_is_valid = false; + + if (update_out == NULL) { + ret = ldb_operr(ldb); + goto out; + } + *update_out = NULL; + + { + /* Is the account a Group Managed Service Account? */ + const bool is_gmsa = dsdb_account_is_gmsa(ldb, msg); + if (!is_gmsa) { + /* It’s not a GMSA — we’re done here. */ + *update_out = NULL; + if (return_out != NULL) { + *return_out = (struct gmsa_return_pwd){}; + } + ret = LDB_SUCCESS; + goto out; + } + } + + /* Calculate the rollover interval. */ + ok = samdb_result_gkdi_rollover_interval(msg, &rollover_interval); + if (!ok || rollover_interval == 0) { + /* We can’t do anything if the rollover interval is zero. */ + ret = ldb_operr(ldb); + goto out; + } + + ok = gmsa_creation_time(msg, current_time, &creation_time); + if (!ok) { + return ldb_error(ldb, + LDB_ERR_OPERATIONS_ERROR, + "unable to determine creation time of Group " + "Managed Service Account"); + } + account_age = current_time - MIN(creation_time, current_time); + + /* Calculate the expiration time of the current key. */ + pwd_id = gmsa_get_managed_pwd_id(msg, &pwd_id_buf); + if (pwd_id != NULL && + gkdi_get_key_start_time(pwd_id->gkid, &key_start_time)) + { + have_key_start_time = true; + + /* Check for overflow. */ + if (key_start_time > UINT64_MAX - rollover_interval) { + ret = ldb_operr(ldb); + goto out; + } + current_key_expiration_time = key_start_time + + rollover_interval; + } else { + /* + * [MS-ADTS] does not say to use gkdi_get_interval_start_time(), + * but surely it makes no sense to have keys starting or ending + * at random times. + */ + current_key_expiration_time = gkdi_get_interval_start_time( + creation_time); + } + + /* Fetch the account’s SID, necessary for deriving passwords. */ + ret = samdb_result_dom_sid_buf(msg, "objectSid", &account_sid); + if (ret) { + goto out; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + ret = ldb_oom(ldb); + goto out; + } + + /* + * In determining whether the account’s passwords should be updated, we + * do not validate that the unicodePwd attribute is up‐to‐date, or even + * that it exists. We rely entirely on the fact that the managed + * password ID should be updated *only* as part of a successful gMSA + * password update. In any case, unicodePwd is optional in Samba — save + * for machine accounts (which gMSAs are :)) — and we can’t always rely + * on its presence. + * + * All this means that an admin (or a DC that doesn’t support gMSAs) + * could reset a gMSA’s password outside of the normal procedure, and + * the account would then have the wrong password until the key was due + * to roll over again. There’s nothing much we can do about this if we + * don’t want to re‐derive and verify the password every time we look up + * the keys. + */ + + current_key_is_valid = pwd_id != NULL && + current_time < current_key_expiration_time; + if (current_key_is_valid) { + key_expiration_time = current_key_expiration_time; + + if (return_out != NULL) { + struct KeyEnvelopeId prev_pwd_id_buf; + const struct KeyEnvelopeId *prev_pwd_id = NULL; + + ret = gmsa_specifc_root_key(tmp_ctx, + *pwd_id, + ¤t_key); + if (ret) { + goto out; + } + + if (account_age >= rollover_interval) { + prev_pwd_id = gmsa_get_managed_pwd_prev_id( + msg, &prev_pwd_id_buf); + if (prev_pwd_id != NULL) { + ret = gmsa_specifc_root_key( + tmp_ctx, + *prev_pwd_id, + &previous_key); + if (ret) { + goto out; + } + } else if (have_key_start_time && + key_start_time >= rollover_interval) + { + /* + * The account’s old enough to have a + * previous password, but it doesn’t + * have a previous password ID for some + * reason. This can happen in our tests + * (python/samba/krb5/gmsa_tests.py) + * when we’re mucking about with times. + * Just produce what would have been the + * previous key. + */ + ret = gmsa_nonspecifc_root_key( + tmp_ctx, + key_start_time - + rollover_interval, + &previous_key); + if (ret) { + goto out; + } + } + } else { + /* + * The account is not old enough to have a + * previous password. The old password will not + * be returned. + */ + } + } + } else { + /* Calculate the start time of the new key. */ + const struct new_key new_key = calculate_new_key( + current_time, + current_key_expiration_time, + rollover_interval); + const bool current_key_becomes_previous = + pwd_id != NULL && new_key.immediately_follows_previous; + + /* Check for overflow. */ + if (new_key.start_time > UINT64_MAX - rollover_interval) { + ret = ldb_operr(ldb); + goto out; + } + key_expiration_time = new_key.start_time + rollover_interval; + + ret = gmsa_nonspecifc_root_key(tmp_ctx, + new_key.start_time, + ¤t_key); + if (ret) { + goto out; + } + + if (account_age >= rollover_interval) { + /* Check for underflow. */ + if (new_key.start_time < rollover_interval) { + ret = ldb_operr(ldb); + goto out; + } + ret = gmsa_nonspecifc_root_key( + tmp_ctx, + new_key.start_time - rollover_interval, + &previous_key); + if (ret) { + goto out; + } + } else { + /* + * The account is not old enough to have a previous + * password. The old password will not be returned. + */ + } + + /* + * The current GMSA key, according to the Managed Password ID, + * is no longer valid. We should update the account’s Managed + * Password ID and keys in anticipation of their being needed in + * the near future. + */ + + ret = gmsa_create_update(tmp_ctx, + ldb, + msg, + current_time, + &account_sid, + current_key_becomes_previous, + ¤t_key, + &previous_key, + &update); + if (ret) { + goto out; + } + } + + if (return_out != NULL) { + bool return_future_key; + + unchanged_interval = query_interval = key_expiration_time - + MIN(current_time, + key_expiration_time); + + /* Derive the current and previous passwords. */ + return_future_key = query_interval <= gkdi_max_clock_skew; + if (return_future_key) { + struct RootKey future_key = empty_root_key; + + /* + * The current key hasn’t expired yet, but it + * soon will. Return a new key that will be valid in the + * next epoch. + */ + + ret = gmsa_nonspecifc_root_key(tmp_ctx, + key_expiration_time, + &future_key); + if (ret) { + goto out; + } + + ret = gmsa_get_root_key(ldb, + current_time, + &account_sid, + &future_key, + ¤t_password, + NULL); + if (ret) { + goto out; + } + + ret = gmsa_get_root_key(ldb, + current_time, + &account_sid, + ¤t_key, + &previous_password, + NULL); + if (ret) { + goto out; + } + + /* Check for overflow. */ + if (unchanged_interval > UINT64_MAX - rollover_interval) + { + ret = ldb_operr(ldb); + goto out; + } + unchanged_interval += rollover_interval; + } else { + /* + * Note that a gMSA will become unusable (at least until + * the next rollover) if its associated root key is ever + * deleted. + */ + + ret = gmsa_get_root_key(ldb, + current_time, + &account_sid, + ¤t_key, + ¤t_password, + NULL); + if (ret) { + goto out; + } + + ret = gmsa_get_root_key(ldb, + current_time, + &account_sid, + &previous_key, + &previous_password, + NULL); + if (ret) { + goto out; + } + } + + unchanged_interval -= MIN(gkdi_max_clock_skew, + unchanged_interval); + } + + *update_out = talloc_steal(mem_ctx, update); + if (return_out != NULL) { + *return_out = (struct gmsa_return_pwd){ + .prev_pwd = talloc_steal(mem_ctx, previous_password), + .new_pwd = talloc_steal(mem_ctx, current_password), + .query_interval = query_interval, + .unchanged_interval = unchanged_interval, + }; + } + +out: + TALLOC_FREE(tmp_ctx); + return ret; +} + +bool dsdb_gmsa_current_time(struct ldb_context *ldb, NTTIME *current_time_out) +{ + const unsigned long long *gmsa_time = talloc_get_type( + ldb_get_opaque(ldb, DSDB_GMSA_TIME_OPAQUE), unsigned long long); + + if (gmsa_time != NULL) { + *current_time_out = *gmsa_time; + return true; + } + + return gmsa_current_time(current_time_out); +} diff --git a/source4/dsdb/gmsa/util.h b/source4/dsdb/gmsa/util.h new file mode 100644 index 00000000000..7d5430eae28 --- /dev/null +++ b/source4/dsdb/gmsa/util.h @@ -0,0 +1,106 @@ +/* + Unix SMB/CIFS implementation. + msDS-ManagedPassword attribute for Group Managed Service Accounts + + Copyright (C) Catalyst.Net Ltd 2024 + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef DSDB_GMSA_UTIL_H +#define DSDB_GMSA_UTIL_H + +#include "ldb.h" +#include "ldb_module.h" +#include + +#include "lib/crypto/gkdi.h" +#include "lib/crypto/gmsa.h" +#include "lib/util/data_blob.h" +#include "lib/util/time.h" + +struct gmsa_update { + /* An optional request to set the previous password. */ + struct ldb_request *old_pw_req; + /* A request to set the current password. */ + struct ldb_request *new_pw_req; + /* An request to set the managed password ID. */ + struct ldb_request *pwd_id_req; +}; + +struct gmsa_update_pwd_part { + const struct ProvRootKey *root_key; + struct Gkid gkid; +}; + +struct gmsa_update_pwd { + struct gmsa_update_pwd_part prev_id; + struct gmsa_update_pwd_part new_id; +}; + +struct dom_sid; +int gmsa_allowed_to_view_managed_password(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const struct ldb_message *msg, + const struct dom_sid *account_sid, + bool *allowed_out); + +struct KeyEnvelope; +void gmsa_update_managed_pwd_id(struct KeyEnvelope *pwd_id, + const struct gmsa_update_pwd_part *new_pwd); + +NTSTATUS gmsa_pack_managed_pwd_id(TALLOC_CTX *mem_ctx, + const struct KeyEnvelope *pwd_id, + DATA_BLOB *pwd_id_out); + +int gmsa_generate_blobs(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const NTTIME current_time, + const struct dom_sid *const account_sid, + DATA_BLOB *pwd_id_blob_out, + struct gmsa_null_terminated_password **password_out); + +NTSTATUS gmsa_pack_managed_pwd(TALLOC_CTX *mem_ctx, + const uint8_t *new_password, + const uint8_t *old_password, + uint64_t query_interval, + uint64_t unchanged_interval, + DATA_BLOB *managed_pwd_out); + +bool dsdb_account_is_gmsa(struct ldb_context *ldb, + const struct ldb_message *msg); + +const struct KeyEnvelopeId *gmsa_get_managed_pwd_id( + const struct ldb_message *msg, + struct KeyEnvelopeId *key_env_out); + +struct gmsa_return_pwd { + struct gmsa_null_terminated_password *prev_pwd; + struct gmsa_null_terminated_password *new_pwd; + NTTIME query_interval; + NTTIME unchanged_interval; +}; + +int gmsa_recalculate_managed_pwd(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const struct ldb_message *msg, + const NTTIME current_time, + struct gmsa_update **update_out, + struct gmsa_return_pwd *return_out); + +#define DSDB_GMSA_TIME_OPAQUE ("dsdb_gmsa_time_opaque") + +bool dsdb_gmsa_current_time(struct ldb_context *ldb, NTTIME *current_time_out); + +#endif /* DSDB_GMSA_UTIL_H */ diff --git a/source4/dsdb/wscript_build b/source4/dsdb/wscript_build index b263c72cc30..196cf66dfab 100644 --- a/source4/dsdb/wscript_build +++ b/source4/dsdb/wscript_build @@ -13,7 +13,7 @@ bld.SAMBA_LIBRARY('samdb', ) bld.SAMBA_LIBRARY('samdb-common', - source='common/util.c common/util_trusts.c common/util_groups.c common/util_samr.c common/dsdb_dn.c common/dsdb_access.c common/util_links.c common/rodc_helper.c gmsa/gkdi.c', + source='common/util.c common/util_trusts.c common/util_groups.c common/util_samr.c common/dsdb_dn.c common/dsdb_access.c common/util_links.c common/rodc_helper.c gmsa/gkdi.c gmsa/util.c', autoproto='common/proto.h', private_library=True, deps='ldb NDR_DRSBLOBS util_ldb LIBCLI_AUTH samba-hostconfig samba_socket cli-ldap-common flag_mapping UTIL_RUNCMD SAMBA_VERSION samba-security gkdi gmsa' -- 2.11.4.GIT