Coverage Report

Created: 2026-02-14 07:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/samba/source4/auth/kerberos/srv_keytab.c
Line
Count
Source
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
static void keytab_principals_free(krb5_context context,
41
           uint32_t num_principals,
42
           krb5_principal *set)
43
0
{
44
0
  uint32_t i;
45
46
0
  for (i = 0; i < num_principals; i++) {
47
0
    krb5_free_principal(context, set[i]);
48
0
  }
49
0
}
50
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,
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
0
{
62
0
  unsigned int i, p;
63
0
  krb5_error_code ret;
64
0
  krb5_data password;
65
0
  char *unparsed;
66
67
0
  password.data = discard_const_p(char, password_s);
68
0
  password.length = strlen(password_s);
69
70
0
  for (i = 0; enctypes[i]; i++) {
71
0
    krb5_keytab_entry entry;
72
73
0
    ZERO_STRUCT(entry);
74
75
0
    ret = smb_krb5_create_key_from_string(context,
76
0
                  salt_princ,
77
0
                  NULL,
78
0
                  &password,
79
0
                  enctypes[i],
80
0
                  KRB5_KT_KEY(&entry));
81
0
    if (ret != 0) {
82
0
      *error_string = talloc_strdup(parent_ctx,
83
0
                  "Failed to create key from string");
84
0
      return ret;
85
0
    }
86
87
0
    entry.vno = kvno;
88
89
0
    for (p = 0; p < num_principals; p++) {
90
0
      bool found = false;
91
92
0
      unparsed = NULL;
93
0
      entry.principal = principals[p];
94
95
0
      ret = smb_krb5_is_exact_entry_in_keytab(parent_ctx,
96
0
                context,
97
0
                keytab,
98
0
                &entry,
99
0
                &found,
100
0
                error_string);
101
0
      if (ret != 0) {
102
0
        krb5_free_keyblock_contents(context,
103
0
                  KRB5_KT_KEY(&entry));
104
0
        return ret;
105
0
      }
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
0
      if (found) {
114
0
        continue;
115
0
      }
116
117
0
      ret = krb5_kt_add_entry(context, keytab, &entry);
118
0
      if (ret != 0) {
119
0
        char *k5_error_string =
120
0
          smb_get_krb5_error_message(context,
121
0
                   ret, NULL);
122
0
        krb5_unparse_name(context,
123
0
            principals[p], &unparsed);
124
0
        *error_string = talloc_asprintf(parent_ctx,
125
0
          "Failed to add enctype %d entry for "
126
0
          "%s(kvno %d) to keytab: %s\n",
127
0
          (int)enctypes[i], unparsed,
128
0
          kvno, k5_error_string);
129
130
0
        free(unparsed);
131
0
        talloc_free(k5_error_string);
132
0
        krb5_free_keyblock_contents(context,
133
0
                  KRB5_KT_KEY(&entry));
134
0
        return ret;
135
0
      }
136
137
0
      DEBUG(5, ("Added key (kvno %d) to keytab (enctype %d)\n",
138
0
          kvno, (int)enctypes[i]));
139
0
    }
140
0
    krb5_free_keyblock_contents(context, KRB5_KT_KEY(&entry));
141
0
  }
142
0
  return 0;
143
0
}
144
145
/*
146
 * This is the inner part of smb_krb5_update_keytab on an open keytab
147
 * and without the deletion
148
 */
149
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
0
{
162
0
  krb5_error_code ret;
163
0
  krb5_principal salt_princ = NULL;
164
0
  krb5_enctype *enctypes;
165
0
  TALLOC_CTX *mem_ctx;
166
0
  const char *error_string = NULL;
167
168
0
  if (!new_secret) {
169
    /* There is no password here, so nothing to do */
170
0
    return 0;
171
0
  }
172
173
0
  mem_ctx = talloc_new(parent_ctx);
174
0
  if (!mem_ctx) {
175
0
    *perror_string = talloc_strdup(parent_ctx,
176
0
      "unable to allocate tmp_ctx for smb_krb5_fill_keytab");
177
0
    return ENOMEM;
178
0
  }
179
180
  /* The salt used to generate these entries may be different however,
181
   * fetch that */
182
0
  ret = krb5_parse_name(context, saltPrincipal, &salt_princ);
183
0
  if (ret) {
184
0
    *perror_string = smb_get_krb5_error_message(context,
185
0
                 ret,
186
0
                 parent_ctx);
187
0
    talloc_free(mem_ctx);
188
0
    return ret;
189
0
  }
190
191
0
  ret = ms_suptypes_to_ietf_enctypes(mem_ctx, supp_enctypes, &enctypes);
192
0
  if (ret) {
193
0
    *perror_string = talloc_asprintf(parent_ctx,
194
0
          "smb_krb5_fill_keytab: generating list of "
195
0
          "encryption types failed (%s)\n",
196
0
          smb_get_krb5_error_message(context,
197
0
                ret, mem_ctx));
198
0
    goto done;
199
0
  }
200
201
0
  ret = keytab_add_keys(mem_ctx,
202
0
            num_principals,
203
0
            principals,
204
0
            salt_princ, kvno, new_secret,
205
0
            context, enctypes, keytab, &error_string);
206
0
  if (ret) {
207
0
    *perror_string = talloc_steal(parent_ctx, error_string);
208
0
    goto done;
209
0
  }
210
211
0
  if (old_secret && add_old && kvno != 0) {
212
0
    ret = keytab_add_keys(mem_ctx,
213
0
              num_principals,
214
0
              principals,
215
0
              salt_princ, kvno - 1, old_secret,
216
0
              context, enctypes, keytab, &error_string);
217
0
    if (ret) {
218
0
      *perror_string = talloc_steal(parent_ctx, error_string);
219
0
    }
220
0
  }
221
222
0
done:
223
0
  krb5_free_principal(context, salt_princ);
224
0
  talloc_free(mem_ctx);
225
0
  return ret;
226
0
}
227
228
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
0
{
237
0
  const char *gmsa_attrs[] = {
238
0
    "msDS-ManagedPassword",
239
0
    "msDS-KeyVersionNumber",
240
0
    "sAMAccountName",
241
0
    "msDS-SupportedEncryptionTypes",
242
0
    NULL
243
0
  };
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
0
  uint32_t kvno = 0;
253
0
  uint32_t supported_enctypes = 0;
254
0
  krb5_context context = smb_krb5_context->krb5_context;
255
0
  struct cli_credentials *cred = NULL;
256
0
  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
0
  TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
265
0
  if (tmp_ctx == NULL) {
266
0
    return NT_STATUS_NO_MEMORY;
267
0
  }
268
269
0
  ret = dsdb_search_one(samdb,
270
0
            tmp_ctx,
271
0
            &msg,
272
0
            dn,
273
0
            LDB_SCOPE_BASE,
274
0
            gmsa_attrs, 0,
275
0
            "(objectClass=msDS-GroupManagedServiceAccount)");
276
277
0
  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
0
            "Did not find gMSA at %s",
284
0
            ldb_dn_get_linearized(dn));
285
0
    TALLOC_FREE(tmp_ctx);
286
0
    return NT_STATUS_NO_SUCH_USER;
287
0
  }
288
289
0
  if (ret != LDB_SUCCESS) {
290
0
    *error_string = talloc_asprintf(mem_ctx,
291
0
            "Error looking for gMSA at %s: %s",
292
0
            ldb_dn_get_linearized(dn), ldb_errstring(samdb));
293
0
    TALLOC_FREE(tmp_ctx);
294
0
    return NT_STATUS_UNSUCCESSFUL;
295
0
  }
296
297
  /* Extract out passwords */
298
0
  managed_password_blob = ldb_msg_find_ldb_val(msg, "msDS-ManagedPassword");
299
300
0
  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
0
            "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
0
  }
310
311
0
  cred = cli_credentials_init(tmp_ctx);
312
0
  if (cred == NULL) {
313
0
    *error_string = talloc_asprintf(mem_ctx,
314
0
            "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
0
  }
319
320
0
  realm = smb_krb5_principal_get_realm(tmp_ctx,
321
0
               context,
322
0
               principal);
323
0
  if (realm == NULL) {
324
0
    *error_string = talloc_asprintf(mem_ctx,
325
0
            "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
0
  }
330
331
0
  cli_credentials_set_realm(cred, realm, CRED_SPECIFIED);
332
333
0
  username = ldb_msg_find_attr_as_string(msg, "sAMAccountName", NULL);
334
0
  if (username == NULL) {
335
0
    *error_string = talloc_asprintf(mem_ctx,
336
0
            "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
0
  }
341
342
0
  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
0
  kvno = ldb_msg_find_attr_as_uint(msg, "msDS-KeyVersionNumber", 0);
349
350
0
  cli_credentials_set_kvno(cred, kvno);
351
352
0
  supported_enctypes = ldb_msg_find_attr_as_uint(msg,
353
0
                   "msDS-SupportedEncryptionTypes",
354
0
                   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
0
  supported_enctypes &= ENC_STRONG_SALTED_TYPES;
366
367
  /* Update the keytab */
368
369
0
  status = cli_credentials_set_gmsa_passwords(cred,
370
0
                managed_password_blob,
371
0
                true /* for keytab */,
372
0
                error_string);
373
374
0
  if (!NT_STATUS_IS_OK(status)) {
375
0
    *error_string = talloc_asprintf(mem_ctx,
376
0
            "Could not parse gMSA passwords on %s: %s",
377
0
            ldb_dn_get_linearized(msg->dn),
378
0
            *error_string);
379
0
    TALLOC_FREE(tmp_ctx);
380
0
    return status;
381
0
  }
382
383
0
  managed_pw_utf8 = cli_credentials_get_password(cred);
384
385
0
  previous_managed_pw_utf8 = cli_credentials_get_old_password(cred);
386
387
0
  salt_principal = cli_credentials_get_salt_principal(cred, tmp_ctx);
388
0
  if (salt_principal == NULL) {
389
0
    *error_string = talloc_asprintf(mem_ctx,
390
0
            "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
0
  }
395
396
0
  ret = smb_krb5_fill_keytab(tmp_ctx,
397
0
           salt_principal,
398
0
           kvno,
399
0
           managed_pw_utf8,
400
0
           previous_managed_pw_utf8,
401
0
           supported_enctypes,
402
0
           1,
403
0
           &principal,
404
0
           context,
405
0
           keytab,
406
0
           include_historic_keys,
407
0
           error_string);
408
0
  if (ret) {
409
0
    *error_string = talloc_asprintf(mem_ctx,
410
0
            "Failed to add keys from %s to keytab: %s",
411
0
            ldb_dn_get_linearized(msg->dn),
412
0
            *error_string);
413
0
    TALLOC_FREE(tmp_ctx);
414
0
    return NT_STATUS_UNSUCCESSFUL;
415
0
  }
416
417
0
  TALLOC_FREE(tmp_ctx);
418
0
  return NT_STATUS_OK;
419
0
}
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] new_secret  New password
436
 * @param[in] old_secret  Old 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
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
0
{
462
0
  krb5_keytab keytab = NULL;
463
0
  krb5_error_code ret;
464
0
  bool found_previous = false;
465
0
  TALLOC_CTX *tmp_ctx = NULL;
466
0
  krb5_principal *principals = NULL;
467
0
  uint32_t num_principals = 0;
468
0
  char *upper_realm;
469
0
  const char *error_string = NULL;
470
471
0
  if (keytab_name == NULL) {
472
0
    return ENOENT;
473
0
  }
474
475
0
  ret = krb5_kt_resolve(context, keytab_name, &keytab);
476
0
  if (ret) {
477
0
    *perror_string = smb_get_krb5_error_message(context,
478
0
                 ret, parent_ctx);
479
0
    return ret;
480
0
  }
481
482
0
  DEBUG(5, ("Opened keytab %s\n", keytab_name));
483
484
0
  tmp_ctx = talloc_new(parent_ctx);
485
0
  if (!tmp_ctx) {
486
0
    *perror_string = talloc_strdup(parent_ctx,
487
0
                "Failed to allocate memory context");
488
0
    ret = ENOMEM;
489
0
    goto done;
490
0
  }
491
492
0
  upper_realm = strupper_talloc(tmp_ctx, realm);
493
0
  if (upper_realm == NULL) {
494
0
    *perror_string = talloc_strdup(parent_ctx,
495
0
                "Cannot allocate memory to upper case realm");
496
0
    ret = ENOMEM;
497
0
    goto done;
498
0
  }
499
500
0
  ret = smb_krb5_create_principals_array(tmp_ctx,
501
0
                 context,
502
0
                 samAccountName,
503
0
                 upper_realm,
504
0
                 num_SPNs,
505
0
                 SPNs,
506
0
                 &num_principals,
507
0
                 &principals,
508
0
                 &error_string);
509
0
  if (ret != 0) {
510
0
    *perror_string = talloc_asprintf(parent_ctx,
511
0
      "Failed to load principals from ldb message: %s\n",
512
0
      error_string);
513
0
    goto done;
514
0
  }
515
516
0
  ret = smb_krb5_remove_obsolete_keytab_entries(tmp_ctx,
517
0
                  context,
518
0
                  keytab,
519
0
                  num_principals,
520
0
                  principals,
521
0
                  kvno,
522
0
                  &found_previous,
523
0
                  &error_string);
524
0
  if (ret != 0) {
525
0
    *perror_string = talloc_asprintf(parent_ctx,
526
0
      "Failed to remove old principals from keytab: %s\n",
527
0
      error_string);
528
0
    goto done;
529
0
  }
530
531
0
  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
0
    if (saltPrincipal == NULL) {
536
0
      *perror_string = talloc_strdup(parent_ctx,
537
0
                   "No saltPrincipal provided");
538
0
      ret = EINVAL;
539
0
      goto done;
540
0
    }
541
542
0
    ret = smb_krb5_fill_keytab(tmp_ctx,
543
0
            saltPrincipal,
544
0
            kvno, new_secret, old_secret,
545
0
            supp_enctypes,
546
0
            num_principals,
547
0
            principals,
548
0
            context, keytab,
549
0
            found_previous ? false : true,
550
0
            &error_string);
551
0
    if (ret) {
552
0
      *perror_string = talloc_steal(parent_ctx, error_string);
553
0
    }
554
0
  }
555
556
0
  if (ret == 0 && _keytab != NULL) {
557
    /* caller wants the keytab handle back */
558
0
    *_keytab = keytab;
559
0
  }
560
561
0
done:
562
0
  keytab_principals_free(context, num_principals, principals);
563
0
  if (ret != 0 || _keytab == NULL) {
564
0
    krb5_kt_close(context, keytab);
565
0
  }
566
0
  talloc_free(tmp_ctx);
567
0
  return ret;
568
0
}
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
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
0
{
596
0
  krb5_error_code ret;
597
0
  TALLOC_CTX *mem_ctx = talloc_new(parent_ctx);
598
0
  const char *rand_string;
599
0
  const char *error_string = NULL;
600
0
  if (!mem_ctx) {
601
0
    return ENOMEM;
602
0
  }
603
604
0
  rand_string = generate_random_str(mem_ctx, 16);
605
0
  if (!rand_string) {
606
0
    talloc_free(mem_ctx);
607
0
    return ENOMEM;
608
0
  }
609
610
0
  *keytab_name = talloc_asprintf(mem_ctx, "MEMORY:%s", rand_string);
611
0
  if (*keytab_name == NULL) {
612
0
    talloc_free(mem_ctx);
613
0
    return ENOMEM;
614
0
  }
615
616
0
  ret = smb_krb5_update_keytab(mem_ctx, context,
617
0
             *keytab_name, samAccountName, realm,
618
0
             NULL, 0, salt_principal, new_secret, NULL,
619
0
             kvno, ENC_ALL_TYPES,
620
0
             false, keytab, &error_string);
621
0
  if (ret == 0) {
622
0
    talloc_steal(parent_ctx, *keytab_name);
623
0
  } else {
624
0
    DEBUG(0, ("Failed to create in-memory keytab: %s\n",
625
0
        error_string));
626
0
    *keytab_name = NULL;
627
0
  }
628
  talloc_free(mem_ctx);
629
0
  return ret;
630
0
}