Line data Source code
1 : /*
2 : Unix SMB/CIFS implementation.
3 :
4 : Kerberos utility functions
5 :
6 : Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
7 :
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.
12 :
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.
17 :
18 :
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/>.
21 : */
22 :
23 : /**
24 : * @file srv_keytab.c
25 : *
26 : * @brief Kerberos keytab utility functions
27 : *
28 : */
29 :
30 : #include "includes.h"
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"
39 :
40 348 : static void keytab_principals_free(krb5_context context,
41 : uint32_t num_principals,
42 : krb5_principal *set)
43 : {
44 26 : uint32_t i;
45 :
46 1178 : for (i = 0; i < num_principals; i++) {
47 830 : krb5_free_principal(context, set[i]);
48 : }
49 322 : }
50 :
51 347 : 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,
55 : int kvno,
56 : const char *password_s,
57 : krb5_context context,
58 : krb5_enctype *enctypes,
59 : krb5_keytab keytab,
60 : const char **error_string)
61 : {
62 26 : unsigned int i, p;
63 26 : krb5_error_code ret;
64 26 : krb5_data password;
65 26 : char *unparsed;
66 :
67 347 : password.data = discard_const_p(char, password_s);
68 347 : password.length = strlen(password_s);
69 :
70 1386 : for (i = 0; enctypes[i]; i++) {
71 78 : krb5_keytab_entry entry;
72 :
73 1039 : ZERO_STRUCT(entry);
74 :
75 1039 : ret = smb_krb5_create_key_from_string(context,
76 : salt_princ,
77 : NULL,
78 : &password,
79 961 : enctypes[i],
80 : KRB5_KT_KEY(&entry));
81 1039 : if (ret != 0) {
82 0 : *error_string = talloc_strdup(parent_ctx,
83 : "Failed to create key from string");
84 0 : return ret;
85 : }
86 :
87 1039 : entry.vno = kvno;
88 :
89 3515 : for (p = 0; p < num_principals; p++) {
90 2476 : bool found = false;
91 :
92 2476 : unparsed = NULL;
93 2476 : entry.principal = principals[p];
94 :
95 2476 : ret = smb_krb5_is_exact_entry_in_keytab(parent_ctx,
96 : context,
97 : keytab,
98 : &entry,
99 : &found,
100 : error_string);
101 2476 : if (ret != 0) {
102 0 : krb5_free_keyblock_contents(context,
103 : KRB5_KT_KEY(&entry));
104 0 : return ret;
105 : }
106 :
107 : /*
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
111 : * extend it
112 : */
113 2476 : if (found) {
114 0 : continue;
115 : }
116 :
117 2476 : ret = krb5_kt_add_entry(context, keytab, &entry);
118 2476 : if (ret != 0) {
119 0 : char *k5_error_string =
120 0 : smb_get_krb5_error_message(context,
121 : ret, NULL);
122 0 : krb5_unparse_name(context,
123 0 : principals[p], &unparsed);
124 0 : *error_string = talloc_asprintf(parent_ctx,
125 : "Failed to add enctype %d entry for "
126 : "%s(kvno %d) to keytab: %s\n",
127 0 : (int)enctypes[i], unparsed,
128 : kvno, k5_error_string);
129 :
130 0 : free(unparsed);
131 0 : talloc_free(k5_error_string);
132 0 : krb5_free_keyblock_contents(context,
133 : KRB5_KT_KEY(&entry));
134 0 : return ret;
135 : }
136 :
137 2476 : DEBUG(5, ("Added key (kvno %d) to keytab (enctype %d)\n",
138 : kvno, (int)enctypes[i]));
139 : }
140 1039 : krb5_free_keyblock_contents(context, KRB5_KT_KEY(&entry));
141 : }
142 321 : return 0;
143 : }
144 :
145 : /*
146 : * This is the inner part of smb_krb5_update_keytab on an open keytab
147 : * and without the deletion
148 : */
149 348 : static krb5_error_code smb_krb5_fill_keytab(TALLOC_CTX *parent_ctx,
150 : const char *saltPrincipal,
151 : int kvno,
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,
158 : krb5_keytab keytab,
159 : bool add_old,
160 : const char **perror_string)
161 : {
162 26 : krb5_error_code ret;
163 348 : krb5_principal salt_princ = NULL;
164 26 : krb5_enctype *enctypes;
165 26 : TALLOC_CTX *mem_ctx;
166 348 : const char *error_string = NULL;
167 :
168 348 : if (!new_secret) {
169 : /* There is no password here, so nothing to do */
170 1 : return 0;
171 : }
172 :
173 347 : mem_ctx = talloc_new(parent_ctx);
174 347 : if (!mem_ctx) {
175 0 : *perror_string = talloc_strdup(parent_ctx,
176 : "unable to allocate tmp_ctx for smb_krb5_fill_keytab");
177 0 : return ENOMEM;
178 : }
179 :
180 : /* The salt used to generate these entries may be different however,
181 : * fetch that */
182 347 : ret = krb5_parse_name(context, saltPrincipal, &salt_princ);
183 347 : if (ret) {
184 0 : *perror_string = smb_get_krb5_error_message(context,
185 : ret,
186 : parent_ctx);
187 0 : talloc_free(mem_ctx);
188 0 : return ret;
189 : }
190 :
191 347 : ret = ms_suptypes_to_ietf_enctypes(mem_ctx, supp_enctypes, &enctypes);
192 347 : if (ret) {
193 0 : *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,
197 : ret, mem_ctx));
198 0 : goto done;
199 : }
200 :
201 347 : ret = keytab_add_keys(mem_ctx,
202 : num_principals,
203 : principals,
204 : salt_princ, kvno, new_secret,
205 : context, enctypes, keytab, &error_string);
206 347 : if (ret) {
207 0 : *perror_string = talloc_steal(parent_ctx, error_string);
208 0 : goto done;
209 : }
210 :
211 347 : if (old_secret && add_old && kvno != 0) {
212 0 : ret = keytab_add_keys(mem_ctx,
213 : num_principals,
214 : principals,
215 : salt_princ, kvno - 1, old_secret,
216 : context, enctypes, keytab, &error_string);
217 0 : if (ret) {
218 0 : *perror_string = talloc_steal(parent_ctx, error_string);
219 : }
220 : }
221 :
222 347 : done:
223 347 : krb5_free_principal(context, salt_princ);
224 347 : talloc_free(mem_ctx);
225 347 : return ret;
226 : }
227 :
228 2 : NTSTATUS smb_krb5_fill_keytab_gmsa_keys(TALLOC_CTX *mem_ctx,
229 : struct smb_krb5_context *smb_krb5_context,
230 : krb5_keytab keytab,
231 : krb5_principal principal,
232 : struct ldb_context *samdb,
233 : struct ldb_dn *dn,
234 : bool include_historic_keys,
235 : const char **error_string)
236 : {
237 2 : const char *gmsa_attrs[] = {
238 : "msDS-ManagedPassword",
239 : "msDS-KeyVersionNumber",
240 : "sAMAccountName",
241 : "msDS-SupportedEncryptionTypes",
242 : NULL
243 : };
244 :
245 0 : NTSTATUS status;
246 0 : struct ldb_message *msg;
247 0 : const struct ldb_val *managed_password_blob;
248 0 : const char *managed_pw_utf8;
249 0 : const char *previous_managed_pw_utf8;
250 0 : const char *username;
251 0 : const char *salt_principal;
252 2 : uint32_t kvno = 0;
253 2 : uint32_t supported_enctypes = 0;
254 2 : krb5_context context = smb_krb5_context->krb5_context;
255 2 : struct cli_credentials *cred = NULL;
256 2 : const char *realm = NULL;
257 :
258 : /*
259 : * Search for msDS-ManagedPassword (and other attributes to
260 : * avoid a race) as this was not in the original search.
261 : */
262 0 : int ret;
263 :
264 2 : TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
265 2 : if (tmp_ctx == NULL) {
266 0 : return NT_STATUS_NO_MEMORY;
267 : }
268 :
269 2 : ret = dsdb_search_one(samdb,
270 : tmp_ctx,
271 : &msg,
272 : dn,
273 : LDB_SCOPE_BASE,
274 : gmsa_attrs, 0,
275 : "(objectClass=msDS-GroupManagedServiceAccount)");
276 :
277 2 : if (ret == LDB_ERR_NO_SUCH_OBJECT) {
278 : /*
279 : * Race condition, object has gone, or just wasn't a
280 : * gMSA
281 : */
282 0 : *error_string = talloc_asprintf(mem_ctx,
283 : "Did not find gMSA at %s",
284 : ldb_dn_get_linearized(dn));
285 0 : TALLOC_FREE(tmp_ctx);
286 0 : return NT_STATUS_NO_SUCH_USER;
287 : }
288 :
289 2 : if (ret != LDB_SUCCESS) {
290 0 : *error_string = talloc_asprintf(mem_ctx,
291 : "Error looking for gMSA at %s: %s",
292 : ldb_dn_get_linearized(dn), ldb_errstring(samdb));
293 0 : TALLOC_FREE(tmp_ctx);
294 0 : return NT_STATUS_UNSUCCESSFUL;
295 : }
296 :
297 : /* Extract out passwords */
298 2 : managed_password_blob = ldb_msg_find_ldb_val(msg, "msDS-ManagedPassword");
299 :
300 2 : if (managed_password_blob == NULL) {
301 : /*
302 : * No password set on this yet or not readable by this user
303 : */
304 0 : *error_string = talloc_asprintf(mem_ctx,
305 : "Did not find msDS-ManagedPassword at %s",
306 0 : ldb_dn_get_extended_linearized(mem_ctx, msg->dn, 1));
307 0 : TALLOC_FREE(tmp_ctx);
308 0 : return NT_STATUS_NO_USER_KEYS;
309 : }
310 :
311 2 : cred = cli_credentials_init(tmp_ctx);
312 2 : if (cred == NULL) {
313 0 : *error_string = talloc_asprintf(mem_ctx,
314 : "Could not allocate cli_credentials for %s",
315 0 : ldb_dn_get_linearized(msg->dn));
316 0 : TALLOC_FREE(tmp_ctx);
317 0 : return NT_STATUS_NO_MEMORY;
318 : }
319 :
320 2 : realm = smb_krb5_principal_get_realm(tmp_ctx,
321 : context,
322 : principal);
323 2 : if (realm == NULL) {
324 0 : *error_string = talloc_asprintf(mem_ctx,
325 : "Could not allocate copy of realm for %s",
326 0 : ldb_dn_get_linearized(msg->dn));
327 0 : TALLOC_FREE(tmp_ctx);
328 0 : return NT_STATUS_NO_MEMORY;
329 : }
330 :
331 2 : cli_credentials_set_realm(cred, realm, CRED_SPECIFIED);
332 :
333 2 : username = ldb_msg_find_attr_as_string(msg, "sAMAccountName", NULL);
334 2 : if (username == NULL) {
335 0 : *error_string = talloc_asprintf(mem_ctx,
336 : "No sAMAccountName on %s",
337 0 : ldb_dn_get_linearized(msg->dn));
338 0 : TALLOC_FREE(tmp_ctx);
339 0 : return NT_STATUS_INVALID_ACCOUNT_NAME;
340 : }
341 :
342 2 : cli_credentials_set_username(cred, username, CRED_SPECIFIED);
343 :
344 : /*
345 : * Note that this value may not be correct, it is updated
346 : * after the query that gives us the passwords
347 : */
348 2 : kvno = ldb_msg_find_attr_as_uint(msg, "msDS-KeyVersionNumber", 0);
349 :
350 2 : cli_credentials_set_kvno(cred, kvno);
351 :
352 2 : supported_enctypes = ldb_msg_find_attr_as_uint(msg,
353 : "msDS-SupportedEncryptionTypes",
354 : ENC_STRONG_SALTED_TYPES);
355 : /*
356 : * We trim this down to just the salted AES types, as the
357 : * passwords are now wrong for rc4-hmac due to the mapping of
358 : * invalid sequences in UTF16_MUNGED -> UTF8 string conversion
359 : * within cli_credentials_get_password(). Users using this new
360 : * feature won't be using such weak crypto anyway. If
361 : * required we could also set the NT Hash as a key directly,
362 : * this is just a limitation of smb_krb5_fill_keytab() taking
363 : * a simple string as input.
364 : */
365 2 : supported_enctypes &= ENC_STRONG_SALTED_TYPES;
366 :
367 : /* Update the keytab */
368 :
369 2 : status = cli_credentials_set_gmsa_passwords(cred,
370 : managed_password_blob,
371 : true /* for keytab */,
372 : error_string);
373 :
374 2 : if (!NT_STATUS_IS_OK(status)) {
375 0 : *error_string = talloc_asprintf(mem_ctx,
376 : "Could not parse gMSA passwords on %s: %s",
377 0 : ldb_dn_get_linearized(msg->dn),
378 : *error_string);
379 0 : TALLOC_FREE(tmp_ctx);
380 0 : return status;
381 : }
382 :
383 2 : managed_pw_utf8 = cli_credentials_get_password(cred);
384 :
385 2 : previous_managed_pw_utf8 = cli_credentials_get_old_password(cred);
386 :
387 2 : salt_principal = cli_credentials_get_salt_principal(cred, tmp_ctx);
388 2 : if (salt_principal == NULL) {
389 0 : *error_string = talloc_asprintf(mem_ctx,
390 : "Failed to generate salt principal for %s",
391 0 : ldb_dn_get_linearized(msg->dn));
392 0 : TALLOC_FREE(tmp_ctx);
393 0 : return NT_STATUS_NO_MEMORY;
394 : }
395 :
396 2 : ret = smb_krb5_fill_keytab(tmp_ctx,
397 : salt_principal,
398 : kvno,
399 : managed_pw_utf8,
400 : previous_managed_pw_utf8,
401 : supported_enctypes,
402 : 1,
403 : &principal,
404 : context,
405 : keytab,
406 : include_historic_keys,
407 : error_string);
408 2 : if (ret) {
409 0 : *error_string = talloc_asprintf(mem_ctx,
410 : "Failed to add keys from %s to keytab: %s",
411 0 : ldb_dn_get_linearized(msg->dn),
412 : *error_string);
413 0 : TALLOC_FREE(tmp_ctx);
414 0 : return NT_STATUS_UNSUCCESSFUL;
415 : }
416 :
417 2 : TALLOC_FREE(tmp_ctx);
418 2 : return NT_STATUS_OK;
419 : }
420 :
421 : /**
422 : * @brief Update a Kerberos keytab and removes any obsolete keytab entries.
423 : *
424 : * If the keytab does not exist, this function will create one.
425 : *
426 : * @param[in] parent_ctx Talloc memory context
427 : * @param[in] context Kerberos context
428 : * @param[in] keytab_name Keytab to open
429 : * @param[in] samAccountName User account to update
430 : * @param[in] realm Kerberos realm
431 : * @param[in] SPNs Service principal names to update
432 : * @param[in] num_SPNs Length of SPNs
433 : * @param[in] saltPrincipal Salt used for AES encryption.
434 : * Required, unless delete_all_kvno is set.
435 : * @param[in] old_secret Old password
436 : * @param[in] new_secret New password
437 : * @param[in] kvno Current key version number
438 : * @param[in] supp_enctypes msDS-SupportedEncryptionTypes bit-field
439 : * @param[in] delete_all_kvno Removes all obsolete entries, without
440 : * recreating the keytab.
441 : * @param[out] _keytab If supplied, returns the keytab
442 : * @param[out] perror_string Error string on failure
443 : *
444 : * @return 0 on success, errno on failure
445 : */
446 348 : krb5_error_code smb_krb5_update_keytab(TALLOC_CTX *parent_ctx,
447 : krb5_context context,
448 : const char *keytab_name,
449 : const char *samAccountName,
450 : const char *realm,
451 : const char **SPNs,
452 : int num_SPNs,
453 : const char *saltPrincipal,
454 : const char *new_secret,
455 : const char *old_secret,
456 : int kvno,
457 : uint32_t supp_enctypes,
458 : bool delete_all_kvno,
459 : krb5_keytab *_keytab,
460 : const char **perror_string)
461 : {
462 348 : krb5_keytab keytab = NULL;
463 26 : krb5_error_code ret;
464 348 : bool found_previous = false;
465 348 : TALLOC_CTX *tmp_ctx = NULL;
466 348 : krb5_principal *principals = NULL;
467 348 : uint32_t num_principals = 0;
468 26 : char *upper_realm;
469 348 : const char *error_string = NULL;
470 :
471 348 : if (keytab_name == NULL) {
472 0 : return ENOENT;
473 : }
474 :
475 348 : ret = krb5_kt_resolve(context, keytab_name, &keytab);
476 348 : if (ret) {
477 0 : *perror_string = smb_get_krb5_error_message(context,
478 : ret, parent_ctx);
479 0 : return ret;
480 : }
481 :
482 348 : DEBUG(5, ("Opened keytab %s\n", keytab_name));
483 :
484 348 : tmp_ctx = talloc_new(parent_ctx);
485 348 : if (!tmp_ctx) {
486 0 : *perror_string = talloc_strdup(parent_ctx,
487 : "Failed to allocate memory context");
488 0 : ret = ENOMEM;
489 0 : goto done;
490 : }
491 :
492 348 : upper_realm = strupper_talloc(tmp_ctx, realm);
493 348 : if (upper_realm == NULL) {
494 0 : *perror_string = talloc_strdup(parent_ctx,
495 : "Cannot allocate memory to upper case realm");
496 0 : ret = ENOMEM;
497 0 : goto done;
498 : }
499 :
500 348 : ret = smb_krb5_create_principals_array(tmp_ctx,
501 : context,
502 : samAccountName,
503 : upper_realm,
504 : num_SPNs,
505 : SPNs,
506 : &num_principals,
507 : &principals,
508 : &error_string);
509 348 : if (ret != 0) {
510 0 : *perror_string = talloc_asprintf(parent_ctx,
511 : "Failed to load principals from ldb message: %s\n",
512 : error_string);
513 0 : goto done;
514 : }
515 :
516 348 : ret = smb_krb5_remove_obsolete_keytab_entries(tmp_ctx,
517 : context,
518 : keytab,
519 : num_principals,
520 : principals,
521 : kvno,
522 : &found_previous,
523 : &error_string);
524 348 : if (ret != 0) {
525 0 : *perror_string = talloc_asprintf(parent_ctx,
526 : "Failed to remove old principals from keytab: %s\n",
527 : error_string);
528 0 : goto done;
529 : }
530 :
531 348 : if (!delete_all_kvno) {
532 : /* Create a new keytab. If during the cleanout we found
533 : * entries for kvno -1, then don't try and duplicate them.
534 : * Otherwise, add kvno, and kvno -1 */
535 346 : if (saltPrincipal == NULL) {
536 0 : *perror_string = talloc_strdup(parent_ctx,
537 : "No saltPrincipal provided");
538 0 : ret = EINVAL;
539 0 : goto done;
540 : }
541 :
542 372 : ret = smb_krb5_fill_keytab(tmp_ctx,
543 : saltPrincipal,
544 : kvno, new_secret, old_secret,
545 : supp_enctypes,
546 : num_principals,
547 : principals,
548 : context, keytab,
549 346 : found_previous ? false : true,
550 : &error_string);
551 346 : if (ret) {
552 0 : *perror_string = talloc_steal(parent_ctx, error_string);
553 : }
554 : }
555 :
556 348 : if (ret == 0 && _keytab != NULL) {
557 : /* caller wants the keytab handle back */
558 97 : *_keytab = keytab;
559 : }
560 :
561 251 : done:
562 348 : keytab_principals_free(context, num_principals, principals);
563 348 : if (ret != 0 || _keytab == NULL) {
564 251 : krb5_kt_close(context, keytab);
565 : }
566 348 : talloc_free(tmp_ctx);
567 348 : return ret;
568 : }
569 :
570 : /**
571 : * @brief Wrapper around smb_krb5_update_keytab() for creating an in-memory keytab
572 : *
573 : * @param[in] parent_ctx Talloc memory context
574 : * @param[in] context Kerberos context
575 : * @param[in] new_secret New password
576 : * @param[in] samAccountName User account to update
577 : * @param[in] realm Kerberos realm
578 : * @param[in] salt_principal Salt used for AES encryption.
579 : * Required, unless delete_all_kvno is set.
580 : * @param[in] kvno Current key version number
581 : * @param[out] keytab If supplied, returns the keytab
582 : * @param[out] keytab_name Returns the created keytab name
583 : *
584 : * @return 0 on success, errno on failure
585 : */
586 97 : krb5_error_code smb_krb5_create_memory_keytab(TALLOC_CTX *parent_ctx,
587 : krb5_context context,
588 : const char *new_secret,
589 : const char *samAccountName,
590 : const char *realm,
591 : const char *salt_principal,
592 : int kvno,
593 : krb5_keytab *keytab,
594 : const char **keytab_name)
595 : {
596 0 : krb5_error_code ret;
597 97 : TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
598 0 : const char *rand_string;
599 97 : const char *error_string = NULL;
600 97 : if (!mem_ctx) {
601 0 : return ENOMEM;
602 : }
603 :
604 97 : rand_string = generate_random_str(mem_ctx, 16);
605 97 : if (!rand_string) {
606 0 : talloc_free(mem_ctx);
607 0 : return ENOMEM;
608 : }
609 :
610 97 : *keytab_name = talloc_asprintf(mem_ctx, "MEMORY:%s", rand_string);
611 97 : if (*keytab_name == NULL) {
612 0 : talloc_free(mem_ctx);
613 0 : return ENOMEM;
614 : }
615 :
616 97 : ret = smb_krb5_update_keytab(mem_ctx, context,
617 : *keytab_name, samAccountName, realm,
618 : NULL, 0, salt_principal, new_secret, NULL,
619 : kvno, ENC_ALL_TYPES,
620 : false, keytab, &error_string);
621 97 : if (ret == 0) {
622 97 : talloc_steal(parent_ctx, *keytab_name);
623 : } else {
624 0 : DEBUG(0, ("Failed to create in-memory keytab: %s\n",
625 : error_string));
626 0 : *keytab_name = NULL;
627 : }
628 97 : talloc_free(mem_ctx);
629 97 : return ret;
630 : }
|