2 Unix SMB/CIFS implementation.
4 Kerberos utility functions
6 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 3 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
26 * @brief Kerberos keytab utility functions
31 #include "system/kerberos.h"
32 #include "auth/credentials/credentials.h"
33 #include "auth/credentials/credentials_krb5.h"
34 #include "auth/kerberos/kerberos.h"
35 #include "auth/kerberos/kerberos_util.h"
36 #include "auth/kerberos/kerberos_srv_keytab.h"
37 #include "librpc/gen_ndr/ndr_gmsa.h"
38 #include "dsdb/samdb/samdb.h"
40 static void keytab_principals_free(krb5_context context,
41 uint32_t num_principals,
46 for (i = 0; i < num_principals; i++) {
47 krb5_free_principal(context, set[i]);
51 static krb5_error_code keytab_add_keys(TALLOC_CTX *parent_ctx,
52 uint32_t num_principals,
53 krb5_principal *principals,
54 krb5_principal salt_princ,
56 const char *password_s,
58 krb5_enctype *enctypes,
60 const char **error_string)
67 password.data = discard_const_p(char, password_s);
68 password.length = strlen(password_s);
70 for (i = 0; enctypes[i]; i++) {
71 krb5_keytab_entry entry;
75 ret = smb_krb5_create_key_from_string(context,
82 *error_string = talloc_strdup(parent_ctx,
83 "Failed to create key from string");
89 for (p = 0; p < num_principals; p++) {
93 entry.principal = principals[p];
95 ret = smb_krb5_is_exact_entry_in_keytab(parent_ctx,
102 krb5_free_keyblock_contents(context,
103 KRB5_KT_KEY(&entry));
108 * Do not add the exact same key twice, this
109 * will allow "samba-tool domain exportkeytab"
110 * to refresh a keytab rather than infinitely
117 ret = krb5_kt_add_entry(context, keytab, &entry);
119 char *k5_error_string =
120 smb_get_krb5_error_message(context,
122 krb5_unparse_name(context,
123 principals[p], &unparsed);
124 *error_string = talloc_asprintf(parent_ctx,
125 "Failed to add enctype %d entry for "
126 "%s(kvno %d) to keytab: %s\n",
127 (int)enctypes[i], unparsed,
128 kvno, k5_error_string);
131 talloc_free(k5_error_string);
132 krb5_free_keyblock_contents(context,
133 KRB5_KT_KEY(&entry));
137 DEBUG(5, ("Added key (kvno %d) to keytab (enctype %d)\n",
138 kvno, (int)enctypes[i]));
140 krb5_free_keyblock_contents(context, KRB5_KT_KEY(&entry));
146 * This is the inner part of smb_krb5_update_keytab on an open keytab
147 * and without the deletion
149 static krb5_error_code smb_krb5_fill_keytab(TALLOC_CTX *parent_ctx,
150 const char *saltPrincipal,
152 const char *new_secret,
153 const char *old_secret,
154 uint32_t supp_enctypes,
155 uint32_t num_principals,
156 krb5_principal *principals,
157 krb5_context context,
160 const char **perror_string)
163 krb5_principal salt_princ = NULL;
164 krb5_enctype *enctypes;
166 const char *error_string = NULL;
169 /* There is no password here, so nothing to do */
173 mem_ctx = talloc_new(parent_ctx);
175 *perror_string = talloc_strdup(parent_ctx,
176 "unable to allocate tmp_ctx for smb_krb5_fill_keytab");
180 /* The salt used to generate these entries may be different however,
182 ret = krb5_parse_name(context, saltPrincipal, &salt_princ);
184 *perror_string = smb_get_krb5_error_message(context,
187 talloc_free(mem_ctx);
191 ret = ms_suptypes_to_ietf_enctypes(mem_ctx, supp_enctypes, &enctypes);
193 *perror_string = talloc_asprintf(parent_ctx,
194 "smb_krb5_fill_keytab: generating list of "
195 "encryption types failed (%s)\n",
196 smb_get_krb5_error_message(context,
201 ret = keytab_add_keys(mem_ctx,
204 salt_princ, kvno, new_secret,
205 context, enctypes, keytab, &error_string);
207 *perror_string = talloc_steal(parent_ctx, error_string);
211 if (old_secret && add_old && kvno != 0) {
212 ret = keytab_add_keys(mem_ctx,
215 salt_princ, kvno - 1, old_secret,
216 context, enctypes, keytab, &error_string);
218 *perror_string = talloc_steal(parent_ctx, error_string);
223 krb5_free_principal(context, salt_princ);
224 talloc_free(mem_ctx);
228 NTSTATUS smb_krb5_fill_keytab_gmsa_keys(TALLOC_CTX *mem_ctx,
229 struct smb_krb5_context *smb_krb5_context,
231 krb5_principal principal,
232 struct ldb_context *samdb,
234 const char **error_string)
236 const char *gmsa_attrs[] = {
237 "msDS-ManagedPassword",
238 "msDS-KeyVersionNumber",
240 "msDS-SupportedEncryptionTypes",
245 struct ldb_message *msg;
246 const struct ldb_val *managed_password_blob;
247 const char *managed_pw_utf8;
248 const char *previous_managed_pw_utf8;
249 const char *username;
250 const char *salt_principal;
252 uint32_t supported_enctypes = 0;
253 krb5_context context = smb_krb5_context->krb5_context;
254 struct cli_credentials *cred = NULL;
255 const char *realm = NULL;
258 * Search for msDS-ManagedPassword (and other attributes to
259 * avoid a race) as this was not in the original search.
263 TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
264 if (tmp_ctx == NULL) {
265 return NT_STATUS_NO_MEMORY;
268 ret = dsdb_search_one(samdb,
274 "(objectClass=msDS-GroupManagedServiceAccount)");
276 if (ret == LDB_ERR_NO_SUCH_OBJECT) {
278 * Race condition, object has gone, or just wasn't a
281 *error_string = talloc_asprintf(mem_ctx,
282 "Did not find gMSA at %s",
283 ldb_dn_get_linearized(dn));
284 TALLOC_FREE(tmp_ctx);
285 return NT_STATUS_NO_SUCH_USER;
288 if (ret != LDB_SUCCESS) {
289 *error_string = talloc_asprintf(mem_ctx,
290 "Error looking for gMSA at %s: %s",
291 ldb_dn_get_linearized(dn), ldb_errstring(samdb));
292 TALLOC_FREE(tmp_ctx);
293 return NT_STATUS_UNSUCCESSFUL;
296 /* Extract out passwords */
297 managed_password_blob = ldb_msg_find_ldb_val(msg, "msDS-ManagedPassword");
299 if (managed_password_blob == NULL) {
301 * No password set on this yet or not readable by this user
303 *error_string = talloc_asprintf(mem_ctx,
304 "Did not find msDS-ManagedPassword at %s",
305 ldb_dn_get_extended_linearized(mem_ctx, msg->dn, 1));
306 TALLOC_FREE(tmp_ctx);
307 return NT_STATUS_NO_USER_KEYS;
310 cred = cli_credentials_init(tmp_ctx);
312 *error_string = talloc_asprintf(mem_ctx,
313 "Could not allocate cli_credentials for %s",
314 ldb_dn_get_linearized(msg->dn));
315 TALLOC_FREE(tmp_ctx);
316 return NT_STATUS_NO_MEMORY;
319 realm = smb_krb5_principal_get_realm(tmp_ctx,
323 *error_string = talloc_asprintf(mem_ctx,
324 "Could not allocate copy of realm for %s",
325 ldb_dn_get_linearized(msg->dn));
326 TALLOC_FREE(tmp_ctx);
327 return NT_STATUS_NO_MEMORY;
330 cli_credentials_set_realm(cred, realm, CRED_SPECIFIED);
332 username = ldb_msg_find_attr_as_string(msg, "sAMAccountName", NULL);
333 if (username == NULL) {
334 *error_string = talloc_asprintf(mem_ctx,
335 "No sAMAccountName on %s",
336 ldb_dn_get_linearized(msg->dn));
337 TALLOC_FREE(tmp_ctx);
338 return NT_STATUS_INVALID_ACCOUNT_NAME;
341 cli_credentials_set_username(cred, username, CRED_SPECIFIED);
344 * Note that this value may not be correct, it is updated
345 * after the query that gives us the passwords
347 kvno = ldb_msg_find_attr_as_uint(msg, "msDS-KeyVersionNumber", 0);
349 cli_credentials_set_kvno(cred, kvno);
351 supported_enctypes = ldb_msg_find_attr_as_uint(msg,
352 "msDS-SupportedEncryptionTypes",
353 ENC_STRONG_SALTED_TYPES);
355 * We trim this down to just the salted AES types, as the
356 * passwords are now wrong for rc4-hmac due to the mapping of
357 * invalid sequences in UTF16_MUNGED -> UTF8 string conversion
358 * within cli_credentials_get_password(). Users using this new
359 * feature won't be using such weak crypto anyway. If
360 * required we could also set the NT Hash as a key directly,
361 * this is just a limitation of smb_krb5_fill_keytab() taking
362 * a simple string as input.
364 supported_enctypes &= ENC_STRONG_SALTED_TYPES;
366 /* Update the keytab */
368 status = cli_credentials_set_gmsa_passwords(cred,
369 managed_password_blob,
370 true /* for keytab */,
373 if (!NT_STATUS_IS_OK(status)) {
374 *error_string = talloc_asprintf(mem_ctx,
375 "Could not parse gMSA passwords on %s: %s",
376 ldb_dn_get_linearized(msg->dn),
378 TALLOC_FREE(tmp_ctx);
382 managed_pw_utf8 = cli_credentials_get_password(cred);
384 previous_managed_pw_utf8 = cli_credentials_get_old_password(cred);
386 salt_principal = cli_credentials_get_salt_principal(cred, tmp_ctx);
387 if (salt_principal == NULL) {
388 *error_string = talloc_asprintf(mem_ctx,
389 "Failed to generate salt principal for %s",
390 ldb_dn_get_linearized(msg->dn));
391 TALLOC_FREE(tmp_ctx);
392 return NT_STATUS_NO_MEMORY;
395 ret = smb_krb5_fill_keytab(tmp_ctx,
399 previous_managed_pw_utf8,
408 *error_string = talloc_asprintf(mem_ctx,
409 "Failed to add keys from %s to keytab: %s",
410 ldb_dn_get_linearized(msg->dn),
412 TALLOC_FREE(tmp_ctx);
413 return NT_STATUS_UNSUCCESSFUL;
416 TALLOC_FREE(tmp_ctx);
421 * @brief Update a Kerberos keytab and removes any obsolete keytab entries.
423 * If the keytab does not exist, this function will create one.
425 * @param[in] parent_ctx Talloc memory context
426 * @param[in] context Kerberos context
427 * @param[in] keytab_name Keytab to open
428 * @param[in] samAccountName User account to update
429 * @param[in] realm Kerberos realm
430 * @param[in] SPNs Service principal names to update
431 * @param[in] num_SPNs Length of SPNs
432 * @param[in] saltPrincipal Salt used for AES encryption.
433 * Required, unless delete_all_kvno is set.
434 * @param[in] old_secret Old password
435 * @param[in] new_secret New password
436 * @param[in] kvno Current key version number
437 * @param[in] supp_enctypes msDS-SupportedEncryptionTypes bit-field
438 * @param[in] delete_all_kvno Removes all obsolete entries, without
439 * recreating the keytab.
440 * @param[out] _keytab If supplied, returns the keytab
441 * @param[out] perror_string Error string on failure
443 * @return 0 on success, errno on failure
445 krb5_error_code smb_krb5_update_keytab(TALLOC_CTX *parent_ctx,
446 krb5_context context,
447 const char *keytab_name,
448 const char *samAccountName,
452 const char *saltPrincipal,
453 const char *new_secret,
454 const char *old_secret,
456 uint32_t supp_enctypes,
457 bool delete_all_kvno,
458 krb5_keytab *_keytab,
459 const char **perror_string)
461 krb5_keytab keytab = NULL;
463 bool found_previous = false;
464 TALLOC_CTX *tmp_ctx = NULL;
465 krb5_principal *principals = NULL;
466 uint32_t num_principals = 0;
468 const char *error_string = NULL;
470 if (keytab_name == NULL) {
474 ret = krb5_kt_resolve(context, keytab_name, &keytab);
476 *perror_string = smb_get_krb5_error_message(context,
481 DEBUG(5, ("Opened keytab %s\n", keytab_name));
483 tmp_ctx = talloc_new(parent_ctx);
485 *perror_string = talloc_strdup(parent_ctx,
486 "Failed to allocate memory context");
491 upper_realm = strupper_talloc(tmp_ctx, realm);
492 if (upper_realm == NULL) {
493 *perror_string = talloc_strdup(parent_ctx,
494 "Cannot allocate memory to upper case realm");
499 ret = smb_krb5_create_principals_array(tmp_ctx,
509 *perror_string = talloc_asprintf(parent_ctx,
510 "Failed to load principals from ldb message: %s\n",
515 ret = smb_krb5_remove_obsolete_keytab_entries(tmp_ctx,
524 *perror_string = talloc_asprintf(parent_ctx,
525 "Failed to remove old principals from keytab: %s\n",
530 if (!delete_all_kvno) {
531 /* Create a new keytab. If during the cleanout we found
532 * entries for kvno -1, then don't try and duplicate them.
533 * Otherwise, add kvno, and kvno -1 */
534 if (saltPrincipal == NULL) {
535 *perror_string = talloc_strdup(parent_ctx,
536 "No saltPrincipal provided");
541 ret = smb_krb5_fill_keytab(tmp_ctx,
543 kvno, new_secret, old_secret,
548 found_previous ? false : true,
551 *perror_string = talloc_steal(parent_ctx, error_string);
555 if (ret == 0 && _keytab != NULL) {
556 /* caller wants the keytab handle back */
561 keytab_principals_free(context, num_principals, principals);
562 if (ret != 0 || _keytab == NULL) {
563 krb5_kt_close(context, keytab);
565 talloc_free(tmp_ctx);
570 * @brief Wrapper around smb_krb5_update_keytab() for creating an in-memory keytab
572 * @param[in] parent_ctx Talloc memory context
573 * @param[in] context Kerberos context
574 * @param[in] new_secret New password
575 * @param[in] samAccountName User account to update
576 * @param[in] realm Kerberos realm
577 * @param[in] salt_principal Salt used for AES encryption.
578 * Required, unless delete_all_kvno is set.
579 * @param[in] kvno Current key version number
580 * @param[out] keytab If supplied, returns the keytab
581 * @param[out] keytab_name Returns the created keytab name
583 * @return 0 on success, errno on failure
585 krb5_error_code smb_krb5_create_memory_keytab(TALLOC_CTX *parent_ctx,
586 krb5_context context,
587 const char *new_secret,
588 const char *samAccountName,
590 const char *salt_principal,
593 const char **keytab_name)
596 TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
597 const char *rand_string;
598 const char *error_string = NULL;
603 rand_string = generate_random_str(mem_ctx, 16);
605 talloc_free(mem_ctx);
609 *keytab_name = talloc_asprintf(mem_ctx, "MEMORY:%s", rand_string);
610 if (*keytab_name == NULL) {
611 talloc_free(mem_ctx);
615 ret = smb_krb5_update_keytab(mem_ctx, context,
616 *keytab_name, samAccountName, realm,
617 NULL, 0, salt_principal, new_secret, NULL,
619 false, keytab, &error_string);
621 talloc_steal(parent_ctx, *keytab_name);
623 DEBUG(0, ("Failed to create in-memory keytab: %s\n",
627 talloc_free(mem_ctx);