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 1384 : for (i = 0; enctypes[i]; i++) {
71 78 : krb5_keytab_entry entry;
72 :
73 1037 : ZERO_STRUCT(entry);
74 :
75 1037 : ret = smb_krb5_create_key_from_string(context,
76 : salt_princ,
77 : NULL,
78 : &password,
79 959 : enctypes[i],
80 : KRB5_KT_KEY(&entry));
81 1037 : 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 1037 : entry.vno = kvno;
88 :
89 3511 : for (p = 0; p < num_principals; p++) {
90 2474 : bool found = false;
91 :
92 2474 : unparsed = NULL;
93 2474 : entry.principal = principals[p];
94 :
95 2474 : ret = smb_krb5_is_exact_entry_in_keytab(parent_ctx,
96 : context,
97 : keytab,
98 : &entry,
99 : &found,
100 : error_string);
101 2474 : 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 2474 : if (found) {
114 0 : continue;
115 : }
116 :
117 2474 : ret = krb5_kt_add_entry(context, keytab, &entry);
118 2474 : 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 2474 : DEBUG(5, ("Added key (kvno %d) to keytab (enctype %d)\n",
138 : kvno, (int)enctypes[i]));
139 : }
140 1037 : 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 : const char **error_string)
235 : {
236 2 : const char *gmsa_attrs[] = {
237 : "msDS-ManagedPassword",
238 : "msDS-KeyVersionNumber",
239 : "sAMAccountName",
240 : "msDS-SupportedEncryptionTypes",
241 : NULL
242 : };
243 :
244 0 : NTSTATUS status;
245 0 : struct ldb_message *msg;
246 0 : const struct ldb_val *managed_password_blob;
247 0 : const char *managed_pw_utf8;
248 0 : const char *previous_managed_pw_utf8;
249 0 : const char *username;
250 0 : const char *salt_principal;
251 2 : uint32_t kvno = 0;
252 2 : uint32_t supported_enctypes = 0;
253 2 : krb5_context context = smb_krb5_context->krb5_context;
254 2 : struct cli_credentials *cred = NULL;
255 2 : const char *realm = NULL;
256 :
257 : /*
258 : * Search for msDS-ManagedPassword (and other attributes to
259 : * avoid a race) as this was not in the original search.
260 : */
261 0 : int ret;
262 :
263 2 : TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
264 2 : if (tmp_ctx == NULL) {
265 0 : return NT_STATUS_NO_MEMORY;
266 : }
267 :
268 2 : ret = dsdb_search_one(samdb,
269 : tmp_ctx,
270 : &msg,
271 : dn,
272 : LDB_SCOPE_BASE,
273 : gmsa_attrs, 0,
274 : "(objectClass=msDS-GroupManagedServiceAccount)");
275 :
276 2 : if (ret == LDB_ERR_NO_SUCH_OBJECT) {
277 : /*
278 : * Race condition, object has gone, or just wasn't a
279 : * gMSA
280 : */
281 0 : *error_string = talloc_asprintf(mem_ctx,
282 : "Did not find gMSA at %s",
283 : ldb_dn_get_linearized(dn));
284 0 : TALLOC_FREE(tmp_ctx);
285 0 : return NT_STATUS_NO_SUCH_USER;
286 : }
287 :
288 2 : if (ret != LDB_SUCCESS) {
289 0 : *error_string = talloc_asprintf(mem_ctx,
290 : "Error looking for gMSA at %s: %s",
291 : ldb_dn_get_linearized(dn), ldb_errstring(samdb));
292 0 : TALLOC_FREE(tmp_ctx);
293 0 : return NT_STATUS_UNSUCCESSFUL;
294 : }
295 :
296 : /* Extract out passwords */
297 2 : managed_password_blob = ldb_msg_find_ldb_val(msg, "msDS-ManagedPassword");
298 :
299 2 : if (managed_password_blob == NULL) {
300 : /*
301 : * No password set on this yet or not readable by this user
302 : */
303 0 : *error_string = talloc_asprintf(mem_ctx,
304 : "Did not find msDS-ManagedPassword at %s",
305 0 : ldb_dn_get_extended_linearized(mem_ctx, msg->dn, 1));
306 0 : TALLOC_FREE(tmp_ctx);
307 0 : return NT_STATUS_NO_USER_KEYS;
308 : }
309 :
310 2 : cred = cli_credentials_init(tmp_ctx);
311 2 : if (cred == NULL) {
312 0 : *error_string = talloc_asprintf(mem_ctx,
313 : "Could not allocate cli_credentials for %s",
314 0 : ldb_dn_get_linearized(msg->dn));
315 0 : TALLOC_FREE(tmp_ctx);
316 0 : return NT_STATUS_NO_MEMORY;
317 : }
318 :
319 2 : realm = smb_krb5_principal_get_realm(tmp_ctx,
320 : context,
321 : principal);
322 2 : if (realm == NULL) {
323 0 : *error_string = talloc_asprintf(mem_ctx,
324 : "Could not allocate copy of realm for %s",
325 0 : ldb_dn_get_linearized(msg->dn));
326 0 : TALLOC_FREE(tmp_ctx);
327 0 : return NT_STATUS_NO_MEMORY;
328 : }
329 :
330 2 : cli_credentials_set_realm(cred, realm, CRED_SPECIFIED);
331 :
332 2 : username = ldb_msg_find_attr_as_string(msg, "sAMAccountName", NULL);
333 2 : if (username == NULL) {
334 0 : *error_string = talloc_asprintf(mem_ctx,
335 : "No sAMAccountName on %s",
336 0 : ldb_dn_get_linearized(msg->dn));
337 0 : TALLOC_FREE(tmp_ctx);
338 0 : return NT_STATUS_INVALID_ACCOUNT_NAME;
339 : }
340 :
341 2 : cli_credentials_set_username(cred, username, CRED_SPECIFIED);
342 :
343 : /*
344 : * Note that this value may not be correct, it is updated
345 : * after the query that gives us the passwords
346 : */
347 2 : kvno = ldb_msg_find_attr_as_uint(msg, "msDS-KeyVersionNumber", 0);
348 :
349 2 : cli_credentials_set_kvno(cred, kvno);
350 :
351 2 : supported_enctypes = ldb_msg_find_attr_as_uint(msg,
352 : "msDS-SupportedEncryptionTypes",
353 : ENC_HMAC_SHA1_96_AES256);
354 : /*
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.
363 : */
364 2 : supported_enctypes &= ENC_STRONG_SALTED_TYPES;
365 :
366 : /* Update the keytab */
367 :
368 2 : status = cli_credentials_set_gmsa_passwords(cred,
369 : managed_password_blob,
370 : true /* for keytab */,
371 : error_string);
372 :
373 2 : if (!NT_STATUS_IS_OK(status)) {
374 0 : *error_string = talloc_asprintf(mem_ctx,
375 : "Could not parse gMSA passwords on %s: %s",
376 0 : ldb_dn_get_linearized(msg->dn),
377 : *error_string);
378 0 : TALLOC_FREE(tmp_ctx);
379 0 : return status;
380 : }
381 :
382 2 : managed_pw_utf8 = cli_credentials_get_password(cred);
383 :
384 2 : previous_managed_pw_utf8 = cli_credentials_get_old_password(cred);
385 :
386 2 : salt_principal = cli_credentials_get_salt_principal(cred, tmp_ctx);
387 2 : if (salt_principal == NULL) {
388 0 : *error_string = talloc_asprintf(mem_ctx,
389 : "Failed to generate salt principal for %s",
390 0 : ldb_dn_get_linearized(msg->dn));
391 0 : TALLOC_FREE(tmp_ctx);
392 0 : return NT_STATUS_NO_MEMORY;
393 : }
394 :
395 2 : ret = smb_krb5_fill_keytab(tmp_ctx,
396 : salt_principal,
397 : kvno,
398 : managed_pw_utf8,
399 : previous_managed_pw_utf8,
400 : supported_enctypes,
401 : 1,
402 : &principal,
403 : context,
404 : keytab,
405 : true,
406 : error_string);
407 2 : if (ret) {
408 0 : *error_string = talloc_asprintf(mem_ctx,
409 : "Failed to add keys from %s to keytab: %s",
410 0 : ldb_dn_get_linearized(msg->dn),
411 : *error_string);
412 0 : TALLOC_FREE(tmp_ctx);
413 0 : return NT_STATUS_UNSUCCESSFUL;
414 : }
415 :
416 2 : TALLOC_FREE(tmp_ctx);
417 2 : return NT_STATUS_OK;
418 : }
419 :
420 : /**
421 : * @brief Update a Kerberos keytab and removes any obsolete keytab entries.
422 : *
423 : * If the keytab does not exist, this function will create one.
424 : *
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
442 : *
443 : * @return 0 on success, errno on failure
444 : */
445 348 : krb5_error_code smb_krb5_update_keytab(TALLOC_CTX *parent_ctx,
446 : krb5_context context,
447 : const char *keytab_name,
448 : const char *samAccountName,
449 : const char *realm,
450 : const char **SPNs,
451 : int num_SPNs,
452 : const char *saltPrincipal,
453 : const char *new_secret,
454 : const char *old_secret,
455 : int kvno,
456 : uint32_t supp_enctypes,
457 : bool delete_all_kvno,
458 : krb5_keytab *_keytab,
459 : const char **perror_string)
460 : {
461 348 : krb5_keytab keytab = NULL;
462 26 : krb5_error_code ret;
463 348 : bool found_previous = false;
464 348 : TALLOC_CTX *tmp_ctx = NULL;
465 348 : krb5_principal *principals = NULL;
466 348 : uint32_t num_principals = 0;
467 26 : char *upper_realm;
468 348 : const char *error_string = NULL;
469 :
470 348 : if (keytab_name == NULL) {
471 0 : return ENOENT;
472 : }
473 :
474 348 : ret = krb5_kt_resolve(context, keytab_name, &keytab);
475 348 : if (ret) {
476 0 : *perror_string = smb_get_krb5_error_message(context,
477 : ret, parent_ctx);
478 0 : return ret;
479 : }
480 :
481 348 : DEBUG(5, ("Opened keytab %s\n", keytab_name));
482 :
483 348 : tmp_ctx = talloc_new(parent_ctx);
484 348 : if (!tmp_ctx) {
485 0 : *perror_string = talloc_strdup(parent_ctx,
486 : "Failed to allocate memory context");
487 0 : ret = ENOMEM;
488 0 : goto done;
489 : }
490 :
491 348 : upper_realm = strupper_talloc(tmp_ctx, realm);
492 348 : if (upper_realm == NULL) {
493 0 : *perror_string = talloc_strdup(parent_ctx,
494 : "Cannot allocate memory to upper case realm");
495 0 : ret = ENOMEM;
496 0 : goto done;
497 : }
498 :
499 348 : ret = smb_krb5_create_principals_array(tmp_ctx,
500 : context,
501 : samAccountName,
502 : upper_realm,
503 : num_SPNs,
504 : SPNs,
505 : &num_principals,
506 : &principals,
507 : &error_string);
508 348 : if (ret != 0) {
509 0 : *perror_string = talloc_asprintf(parent_ctx,
510 : "Failed to load principals from ldb message: %s\n",
511 : error_string);
512 0 : goto done;
513 : }
514 :
515 348 : ret = smb_krb5_remove_obsolete_keytab_entries(tmp_ctx,
516 : context,
517 : keytab,
518 : num_principals,
519 : principals,
520 : kvno,
521 : &found_previous,
522 : &error_string);
523 348 : if (ret != 0) {
524 0 : *perror_string = talloc_asprintf(parent_ctx,
525 : "Failed to remove old principals from keytab: %s\n",
526 : error_string);
527 0 : goto done;
528 : }
529 :
530 348 : 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 346 : if (saltPrincipal == NULL) {
535 0 : *perror_string = talloc_strdup(parent_ctx,
536 : "No saltPrincipal provided");
537 0 : ret = EINVAL;
538 0 : goto done;
539 : }
540 :
541 372 : ret = smb_krb5_fill_keytab(tmp_ctx,
542 : saltPrincipal,
543 : kvno, new_secret, old_secret,
544 : supp_enctypes,
545 : num_principals,
546 : principals,
547 : context, keytab,
548 346 : found_previous ? false : true,
549 : &error_string);
550 346 : if (ret) {
551 0 : *perror_string = talloc_steal(parent_ctx, error_string);
552 : }
553 : }
554 :
555 348 : if (ret == 0 && _keytab != NULL) {
556 : /* caller wants the keytab handle back */
557 97 : *_keytab = keytab;
558 : }
559 :
560 251 : done:
561 348 : keytab_principals_free(context, num_principals, principals);
562 348 : if (ret != 0 || _keytab == NULL) {
563 251 : krb5_kt_close(context, keytab);
564 : }
565 348 : talloc_free(tmp_ctx);
566 348 : return ret;
567 : }
568 :
569 : /**
570 : * @brief Wrapper around smb_krb5_update_keytab() for creating an in-memory keytab
571 : *
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
582 : *
583 : * @return 0 on success, errno on failure
584 : */
585 97 : 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,
589 : const char *realm,
590 : const char *salt_principal,
591 : int kvno,
592 : krb5_keytab *keytab,
593 : const char **keytab_name)
594 : {
595 0 : krb5_error_code ret;
596 97 : TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
597 0 : const char *rand_string;
598 97 : const char *error_string = NULL;
599 97 : if (!mem_ctx) {
600 0 : return ENOMEM;
601 : }
602 :
603 97 : rand_string = generate_random_str(mem_ctx, 16);
604 97 : if (!rand_string) {
605 0 : talloc_free(mem_ctx);
606 0 : return ENOMEM;
607 : }
608 :
609 97 : *keytab_name = talloc_asprintf(mem_ctx, "MEMORY:%s", rand_string);
610 97 : if (*keytab_name == NULL) {
611 0 : talloc_free(mem_ctx);
612 0 : return ENOMEM;
613 : }
614 :
615 97 : ret = smb_krb5_update_keytab(mem_ctx, context,
616 : *keytab_name, samAccountName, realm,
617 : NULL, 0, salt_principal, new_secret, NULL,
618 : kvno, ENC_ALL_TYPES,
619 : false, keytab, &error_string);
620 97 : if (ret == 0) {
621 97 : talloc_steal(parent_ctx, *keytab_name);
622 : } else {
623 0 : DEBUG(0, ("Failed to create in-memory keytab: %s\n",
624 : error_string));
625 0 : *keytab_name = NULL;
626 : }
627 97 : talloc_free(mem_ctx);
628 97 : return ret;
629 : }
|