xref: /freebsd/crypto/krb5/src/kadmin/dbutil/kdb5_mkey.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
4  * Use is subject to license terms.
5  */
6 
7 #include <k5-int.h>
8 #include <kdb.h>
9 #include <kadm5/server_internal.h>
10 #include <kadm5/admin.h>
11 #include <adm_proto.h>
12 #include "kdb5_util.h"
13 #include <time.h>
14 
15 #if defined(HAVE_COMPILE) && defined(HAVE_STEP)
16 #define SOLARIS_REGEXPS
17 #elif defined(HAVE_REGCOMP) && defined(HAVE_REGEXEC)
18 #define POSIX_REGEXPS
19 #elif defined(HAVE_RE_COMP) && defined(HAVE_RE_EXEC)
20 #define BSD_REGEXPS
21 #else
22 #error I cannot find any regexp functions
23 #endif
24 #ifdef SOLARIS_REGEXPS
25 #include        <regexpr.h>
26 #endif
27 #ifdef POSIX_REGEXPS
28 #include        <regex.h>
29 #endif
30 
31 extern krb5_keyblock master_keyblock; /* current mkey */
32 extern krb5_kvno   master_kvno;
33 extern krb5_principal master_princ;
34 extern krb5_data master_salt;
35 extern char *mkey_fullname;
36 extern char *mkey_password;
37 extern char *progname;
38 extern int exit_status;
39 extern kadm5_config_params global_params;
40 extern krb5_context util_context;
41 extern time_t get_date(char *);
42 
43 static const char *
strdate(krb5_timestamp when)44 strdate(krb5_timestamp when)
45 {
46     struct tm *tm;
47     static char out[40];
48     time_t lcltim = ts2tt(when);
49 
50     tm = localtime(&lcltim);
51     if (tm == NULL ||
52         strftime(out, sizeof(out), "%a %b %d %H:%M:%S %Z %Y", tm) == 0)
53         strlcpy(out, "(error)", sizeof(out));
54     return out;
55 }
56 
57 krb5_kvno
get_next_kvno(krb5_context context,krb5_db_entry * entry)58 get_next_kvno(krb5_context context, krb5_db_entry *entry)
59 {
60     krb5_kvno new_kvno;
61 
62     new_kvno = krb5_db_get_key_data_kvno(context, entry->n_key_data,
63                                          entry->key_data);
64     new_kvno++;
65     /* deal with wrapping */
66     if (new_kvno == 0)
67         new_kvno = 1; /* knvo must not be 0 as this is special value (IGNORE_VNO) */
68 
69     return (new_kvno);
70 }
71 
72 krb5_error_code
add_new_mkey(krb5_context context,krb5_db_entry * master_entry,krb5_keyblock * new_mkey,krb5_kvno use_mkvno)73 add_new_mkey(krb5_context context, krb5_db_entry *master_entry,
74              krb5_keyblock *new_mkey, krb5_kvno use_mkvno)
75 {
76     krb5_error_code retval = 0;
77     int old_key_data_count, i;
78     krb5_kvno new_mkey_kvno;
79     krb5_key_data tmp_key_data;
80     krb5_mkey_aux_node  *mkey_aux_data_head = NULL, **mkey_aux_data;
81     krb5_keylist_node  *keylist_node;
82     krb5_keylist_node *master_keylist = krb5_db_mkey_list_alias(context);
83 
84     /* do this before modifying master_entry key_data */
85     new_mkey_kvno = get_next_kvno(context, master_entry);
86     /* verify the requested mkvno if not 0 is the one that would be used here. */
87     if (use_mkvno != 0 && new_mkey_kvno != use_mkvno)
88         return (KRB5_KDB_KVNONOMATCH);
89 
90     old_key_data_count = master_entry->n_key_data;
91 
92     /* alloc enough space to hold new and existing key_data */
93     /*
94      * The encrypted key is malloc'ed by krb5_dbe_encrypt_key_data and
95      * krb5_key_data key_data_contents is a pointer to this key.  Using some
96      * logic from master_key_convert().
97      */
98     for (i = 0; i < master_entry->n_key_data; i++)
99         krb5_free_key_data_contents(context, &master_entry->key_data[i]);
100     free(master_entry->key_data);
101     master_entry->key_data = (krb5_key_data *) malloc(sizeof(krb5_key_data) *
102                                                       (old_key_data_count + 1));
103     if (master_entry->key_data == NULL)
104         return (ENOMEM);
105 
106     memset(master_entry->key_data, 0,
107            sizeof(krb5_key_data) * (old_key_data_count + 1));
108     master_entry->n_key_data = old_key_data_count + 1;
109 
110     /* Note, mkey does not have salt */
111     /* add new mkey encrypted with itself to mkey princ entry */
112     if ((retval = krb5_dbe_encrypt_key_data(context, new_mkey, new_mkey, NULL,
113                                             (int) new_mkey_kvno,
114                                             &master_entry->key_data[0]))) {
115         return (retval);
116     }
117     /* the mvkno should be that of the newest mkey */
118     if ((retval = krb5_dbe_update_mkvno(context, master_entry, new_mkey_kvno))) {
119         krb5_free_key_data_contents(context, &master_entry->key_data[0]);
120         return (retval);
121     }
122     /*
123      * Need to decrypt old keys with the current mkey which is in the global
124      * master_keyblock and encrypt those keys with the latest mkey.  And while
125      * the old keys are being decrypted, use those to create the
126      * KRB5_TL_MKEY_AUX entries which store the latest mkey encrypted by one of
127      * the older mkeys.
128      *
129      * The new mkey is followed by existing keys.
130      *
131      * First, set up for creating a krb5_mkey_aux_node list which will be used
132      * to update the mkey aux data for the mkey princ entry.
133      */
134     mkey_aux_data_head = (krb5_mkey_aux_node *) malloc(sizeof(krb5_mkey_aux_node));
135     if (mkey_aux_data_head == NULL) {
136         retval = ENOMEM;
137         goto clean_n_exit;
138     }
139     memset(mkey_aux_data_head, 0, sizeof(krb5_mkey_aux_node));
140     mkey_aux_data = &mkey_aux_data_head;
141 
142     for (keylist_node = master_keylist, i = 1; keylist_node != NULL;
143          keylist_node = keylist_node->next, i++) {
144 
145         /*
146          * Create a list of krb5_mkey_aux_node nodes.  One node contains the new
147          * mkey encrypted by an old mkey and the old mkey's kvno (one node per
148          * old mkey).
149          */
150         if (*mkey_aux_data == NULL) {
151             /* *mkey_aux_data points to next field of previous node */
152             *mkey_aux_data = (krb5_mkey_aux_node *) malloc(sizeof(krb5_mkey_aux_node));
153             if (*mkey_aux_data == NULL) {
154                 retval = ENOMEM;
155                 goto clean_n_exit;
156             }
157             memset(*mkey_aux_data, 0, sizeof(krb5_mkey_aux_node));
158         }
159 
160         memset(&tmp_key_data, 0, sizeof(tmp_key_data));
161         /* encrypt the new mkey with the older mkey */
162         retval = krb5_dbe_encrypt_key_data(context, &keylist_node->keyblock,
163                                            new_mkey, NULL, (int) new_mkey_kvno,
164                                            &tmp_key_data);
165         if (retval)
166             goto clean_n_exit;
167 
168         (*mkey_aux_data)->latest_mkey = tmp_key_data;
169         (*mkey_aux_data)->mkey_kvno = keylist_node->kvno;
170         mkey_aux_data = &((*mkey_aux_data)->next);
171 
172         /*
173          * Store old key in master_entry keydata past the new mkey
174          */
175         retval = krb5_dbe_encrypt_key_data(context, new_mkey,
176                                            &keylist_node->keyblock,
177                                            NULL, (int) keylist_node->kvno,
178                                            &master_entry->key_data[i]);
179         if (retval)
180             goto clean_n_exit;
181     }
182     assert(i == old_key_data_count + 1);
183 
184     if ((retval = krb5_dbe_update_mkey_aux(context, master_entry,
185                                            mkey_aux_data_head))) {
186         goto clean_n_exit;
187     }
188     master_entry->mask |= KADM5_KEY_DATA | KADM5_TL_DATA;
189 
190 clean_n_exit:
191     krb5_dbe_free_mkey_aux_list(context, mkey_aux_data_head);
192     return (retval);
193 }
194 
195 void
kdb5_add_mkey(int argc,char * argv[])196 kdb5_add_mkey(int argc, char *argv[])
197 {
198     int optchar;
199     krb5_error_code retval;
200     char *pw_str = 0;
201     unsigned int pw_size = 0;
202     int do_stash = 0;
203     krb5_data pwd;
204     krb5_kvno new_mkey_kvno;
205     krb5_keyblock new_mkeyblock;
206     krb5_enctype new_master_enctype = ENCTYPE_UNKNOWN;
207     char *new_mkey_password;
208     krb5_db_entry *master_entry = NULL;
209     krb5_timestamp now;
210 
211     /*
212      * The command table entry for this command causes open_db_and_mkey() to be
213      * called first to open the KDB and get the current mkey.
214      */
215 
216     memset(&new_mkeyblock, 0, sizeof(new_mkeyblock));
217     master_salt.data = NULL;
218 
219     while ((optchar = getopt(argc, argv, "e:s")) != -1) {
220         switch(optchar) {
221         case 'e':
222             if (krb5_string_to_enctype(optarg, &new_master_enctype)) {
223                 com_err(progname, EINVAL, _("%s is an invalid enctype"),
224                         optarg);
225                 exit_status++;
226                 return;
227             }
228             break;
229         case 's':
230             do_stash++;
231             break;
232         case '?':
233         default:
234             usage();
235             return;
236         }
237     }
238 
239     if (new_master_enctype == ENCTYPE_UNKNOWN)
240         new_master_enctype = global_params.enctype;
241 
242     retval = krb5_db_get_principal(util_context, master_princ, 0,
243                                    &master_entry);
244     if (retval != 0) {
245         com_err(progname, retval, _("while getting master key principal %s"),
246                 mkey_fullname);
247         exit_status++;
248         goto cleanup_return;
249     }
250 
251     printf(_("Creating new master key for master key principal '%s'\n"),
252            mkey_fullname);
253 
254     printf(_("You will be prompted for a new database Master Password.\n"));
255     printf(_("It is important that you NOT FORGET this password.\n"));
256     fflush(stdout);
257 
258     pw_size = 1024;
259     pw_str = malloc(pw_size);
260     if (pw_str == NULL) {
261         com_err(progname, ENOMEM, _("while creating new master key"));
262         exit_status++;
263         goto cleanup_return;
264     }
265 
266     retval = krb5_read_password(util_context, KRB5_KDC_MKEY_1, KRB5_KDC_MKEY_2,
267                                 pw_str, &pw_size);
268     if (retval) {
269         com_err(progname, retval,
270                 _("while reading new master key from keyboard"));
271         exit_status++;
272         goto cleanup_return;
273     }
274     new_mkey_password = pw_str;
275 
276     pwd.data = new_mkey_password;
277     pwd.length = strlen(new_mkey_password);
278     retval = krb5_principal2salt(util_context, master_princ, &master_salt);
279     if (retval) {
280         com_err(progname, retval, _("while calculating master key salt"));
281         exit_status++;
282         goto cleanup_return;
283     }
284 
285     retval = krb5_c_string_to_key(util_context, new_master_enctype,
286                                   &pwd, &master_salt, &new_mkeyblock);
287     if (retval) {
288         com_err(progname, retval,
289                 _("while transforming master key from password"));
290         exit_status++;
291         goto cleanup_return;
292     }
293 
294     new_mkey_kvno = get_next_kvno(util_context, master_entry);
295     retval = add_new_mkey(util_context, master_entry, &new_mkeyblock,
296                           new_mkey_kvno);
297     if (retval) {
298         com_err(progname, retval,
299                 _("adding new master key to master principal"));
300         exit_status++;
301         goto cleanup_return;
302     }
303 
304     if ((retval = krb5_timeofday(util_context, &now))) {
305         com_err(progname, retval, _("while getting current time"));
306         exit_status++;
307         goto cleanup_return;
308     }
309 
310     if ((retval = krb5_dbe_update_mod_princ_data(util_context, master_entry,
311                                                  now, master_princ))) {
312         com_err(progname, retval, _("while updating the master key principal "
313                                     "modification time"));
314         exit_status++;
315         goto cleanup_return;
316     }
317 
318     if ((retval = krb5_db_put_principal(util_context, master_entry))) {
319         com_err(progname, retval, _("while adding master key entry to the "
320                                     "database"));
321         exit_status++;
322         goto cleanup_return;
323     }
324 
325     if (do_stash) {
326         retval = krb5_db_store_master_key(util_context,
327                                           global_params.stash_file,
328                                           master_princ,
329                                           new_mkey_kvno,
330                                           &new_mkeyblock,
331                                           mkey_password);
332         if (retval) {
333             com_err(progname, retval, _("while storing key"));
334             printf(_("Warning: couldn't stash master key.\n"));
335         }
336     }
337 
338 cleanup_return:
339     /* clean up */
340     krb5_db_free_principal(util_context, master_entry);
341     zap((char *)new_mkeyblock.contents, new_mkeyblock.length);
342     free(new_mkeyblock.contents);
343     if (pw_str) {
344         zap(pw_str, pw_size);
345         free(pw_str);
346     }
347     free(master_salt.data);
348     return;
349 }
350 
351 void
kdb5_use_mkey(int argc,char * argv[])352 kdb5_use_mkey(int argc, char *argv[])
353 {
354     krb5_error_code retval;
355     krb5_kvno  use_kvno;
356     krb5_timestamp now, start_time;
357     krb5_actkvno_node *actkvno_list = NULL, *new_actkvno = NULL,
358         *prev_actkvno, *cur_actkvno;
359     krb5_db_entry *master_entry = NULL;
360     krb5_keylist_node *keylist_node;
361     krb5_boolean inserted = FALSE;
362     krb5_keylist_node *master_keylist = krb5_db_mkey_list_alias(util_context);
363 
364     if (argc < 2 || argc > 3) {
365         /* usage calls exit */
366         usage();
367     }
368 
369     use_kvno = atoi(argv[1]);
370     if (use_kvno == 0) {
371         com_err(progname, EINVAL, _("0 is an invalid KVNO value"));
372         exit_status++;
373         return;
374     } else {
375         /* verify use_kvno is valid */
376         for (keylist_node = master_keylist; keylist_node != NULL;
377              keylist_node = keylist_node->next) {
378             if (use_kvno == keylist_node->kvno)
379                 break;
380         }
381         if (!keylist_node) {
382             com_err(progname, EINVAL, _("%d is an invalid KVNO value"),
383                     use_kvno);
384             exit_status++;
385             return;
386         }
387     }
388 
389     if ((retval = krb5_timeofday(util_context, &now))) {
390         com_err(progname, retval, _("while getting current time"));
391         exit_status++;
392         return;
393     }
394 
395     if (argc == 3) {
396         time_t t = get_date(argv[2]);
397         if (t == -1) {
398             com_err(progname, 0, _("could not parse date-time string '%s'"),
399                     argv[2]);
400             exit_status++;
401             return;
402         } else
403             start_time = (krb5_timestamp) t;
404     } else {
405         start_time = now;
406     }
407 
408     /*
409      * Need to:
410      *
411      * 1. get mkey princ
412      * 2. get krb5_actkvno_node list
413      * 3. add use_kvno to actkvno list (sorted in right spot)
414      * 4. update mkey princ's tl data
415      * 5. put mkey princ.
416      */
417 
418     retval = krb5_db_get_principal(util_context, master_princ, 0,
419                                    &master_entry);
420     if (retval != 0) {
421         com_err(progname, retval, _("while getting master key principal %s"),
422                 mkey_fullname);
423         exit_status++;
424         goto cleanup_return;
425     }
426 
427     retval = krb5_dbe_lookup_actkvno(util_context, master_entry, &actkvno_list);
428     if (retval != 0) {
429         com_err(progname, retval,
430                 _("while looking up active version of master key"));
431         exit_status++;
432         goto cleanup_return;
433     }
434 
435     /*
436      * If an entry already exists with the same kvno either delete it or if it's
437      * the only entry, just set its active time.
438      */
439     for (prev_actkvno = NULL, cur_actkvno = actkvno_list;
440          cur_actkvno != NULL;
441          prev_actkvno = cur_actkvno, cur_actkvno = cur_actkvno->next) {
442 
443         if (cur_actkvno->act_kvno == use_kvno) {
444             /* delete it */
445             if (prev_actkvno) {
446                 prev_actkvno->next = cur_actkvno->next;
447                 cur_actkvno->next = NULL;
448                 krb5_dbe_free_actkvno_list(util_context, cur_actkvno);
449             } else {
450                 if (cur_actkvno->next) {
451                     /* delete it from front of list */
452                     actkvno_list = cur_actkvno->next;
453                     cur_actkvno->next = NULL;
454                     krb5_dbe_free_actkvno_list(util_context, cur_actkvno);
455                 } else {
456                     /* There's only one entry, go ahead and change the time */
457                     cur_actkvno->act_time = start_time;
458                     inserted = TRUE;
459                 }
460             }
461             break;
462         }
463     }
464 
465     if (!inserted) {
466         /* alloc enough space to hold new and existing key_data */
467         new_actkvno = (krb5_actkvno_node *) malloc(sizeof(krb5_actkvno_node));
468         if (new_actkvno == NULL) {
469             com_err(progname, ENOMEM, _("while adding new master key"));
470             exit_status++;
471             goto cleanup_return;
472         }
473         memset(new_actkvno, 0, sizeof(krb5_actkvno_node));
474         new_actkvno->act_kvno = use_kvno;
475         new_actkvno->act_time = start_time;
476 
477         /* insert new act kvno node */
478 
479         if (actkvno_list == NULL) {
480             /* new actkvno is the list */
481             actkvno_list = new_actkvno;
482         } else {
483             for (prev_actkvno = NULL, cur_actkvno = actkvno_list;
484                  cur_actkvno != NULL;
485                  prev_actkvno = cur_actkvno, cur_actkvno = cur_actkvno->next) {
486 
487                 if (ts_after(cur_actkvno->act_time, new_actkvno->act_time)) {
488                     if (prev_actkvno) {
489                         prev_actkvno->next = new_actkvno;
490                         new_actkvno->next = cur_actkvno;
491                     } else {
492                         new_actkvno->next = actkvno_list;
493                         actkvno_list = new_actkvno;
494                     }
495                     break;
496                 } else if (cur_actkvno->next == NULL) {
497                     /* end of line, just add new node to end of list */
498                     cur_actkvno->next = new_actkvno;
499                     break;
500                 }
501             }
502         }
503     }
504 
505     if (ts_after(actkvno_list->act_time, now)) {
506         com_err(progname, EINVAL,
507                 _("there must be one master key currently active"));
508         exit_status++;
509         goto cleanup_return;
510     }
511 
512     if ((retval = krb5_dbe_update_actkvno(util_context, master_entry,
513                                           actkvno_list))) {
514         com_err(progname, retval,
515                 _("while updating actkvno data for master principal entry"));
516         exit_status++;
517         goto cleanup_return;
518     }
519 
520     if ((retval = krb5_dbe_update_mod_princ_data(util_context, master_entry,
521                                                  now, master_princ))) {
522         com_err(progname, retval, _("while updating the master key principal "
523                                     "modification time"));
524         exit_status++;
525         goto cleanup_return;
526     }
527 
528     if ((retval = krb5_db_put_principal(util_context, master_entry))) {
529         com_err(progname, retval,
530                 _("while adding master key entry to the database"));
531         exit_status++;
532         goto cleanup_return;
533     }
534 
535 cleanup_return:
536     /* clean up */
537     krb5_db_free_principal(util_context, master_entry);
538     krb5_dbe_free_actkvno_list(util_context, actkvno_list);
539     return;
540 }
541 
542 void
kdb5_list_mkeys(int argc,char * argv[])543 kdb5_list_mkeys(int argc, char *argv[])
544 {
545     krb5_error_code retval;
546     char *output_str = NULL, enctype[BUFSIZ];
547     krb5_kvno  act_kvno;
548     krb5_timestamp act_time;
549     krb5_actkvno_node *actkvno_list = NULL, *cur_actkvno;
550     krb5_db_entry *master_entry = NULL;
551     krb5_keylist_node  *cur_kb_node;
552     krb5_keyblock *act_mkey;
553     krb5_keylist_node *master_keylist = krb5_db_mkey_list_alias(util_context);
554 
555     if (master_keylist == NULL) {
556         com_err(progname, 0, _("master keylist not initialized"));
557         exit_status++;
558         return;
559     }
560 
561     retval = krb5_db_get_principal(util_context, master_princ, 0,
562                                    &master_entry);
563     if (retval != 0) {
564         com_err(progname, retval, _("while getting master key principal %s"),
565                 mkey_fullname);
566         exit_status++;
567         goto cleanup_return;
568     }
569 
570     retval = krb5_dbe_lookup_actkvno(util_context, master_entry, &actkvno_list);
571     if (retval != 0) {
572         com_err(progname, retval, _("while looking up active kvno list"));
573         exit_status++;
574         goto cleanup_return;
575     }
576 
577     retval = krb5_dbe_find_act_mkey(util_context, actkvno_list, &act_kvno,
578                                     &act_mkey);
579     if (retval != 0) {
580         com_err(progname, retval, _("while looking up active master key"));
581         exit_status++;
582         goto cleanup_return;
583     }
584 
585     printf("Master keys for Principal: %s\n", mkey_fullname);
586 
587     for (cur_kb_node = master_keylist; cur_kb_node != NULL;
588          cur_kb_node = cur_kb_node->next) {
589 
590         if ((retval = krb5_enctype_to_name(cur_kb_node->keyblock.enctype,
591                                            FALSE, enctype, sizeof(enctype)))) {
592             com_err(progname, retval, _("while getting enctype description"));
593             exit_status++;
594             goto cleanup_return;
595         }
596 
597         act_time = -1; /* assume actkvno entry not found */
598         for (cur_actkvno = actkvno_list; cur_actkvno != NULL;
599              cur_actkvno = cur_actkvno->next) {
600             if (cur_actkvno->act_kvno == cur_kb_node->kvno) {
601                 act_time = cur_actkvno->act_time;
602                 break;
603             }
604         }
605 
606         if (cur_kb_node->kvno == act_kvno) {
607             /* * indicates kvno is currently active */
608             retval = asprintf(&output_str,
609                               _("KVNO: %d, Enctype: %s, Active on: %s *\n"),
610                               cur_kb_node->kvno, enctype, strdate(act_time));
611         } else {
612             if (act_time != -1) {
613                 retval = asprintf(&output_str,
614                                   _("KVNO: %d, Enctype: %s, Active on: %s\n"),
615                                   cur_kb_node->kvno, enctype, strdate(act_time));
616             } else {
617                 retval = asprintf(&output_str,
618                                   _("KVNO: %d, Enctype: %s, No activate time "
619                                     "set\n"), cur_kb_node->kvno, enctype);
620             }
621         }
622         if (retval == -1) {
623             com_err(progname, ENOMEM, _("asprintf could not allocate enough "
624                                         "memory to hold output"));
625             exit_status++;
626             goto cleanup_return;
627         }
628         printf("%s", output_str);
629         free(output_str);
630         output_str = NULL;
631     }
632 
633 cleanup_return:
634     /* clean up */
635     krb5_db_free_principal(util_context, master_entry);
636     free(output_str);
637     krb5_dbe_free_actkvno_list(util_context, actkvno_list);
638     return;
639 }
640 
641 struct update_enc_mkvno {
642     unsigned int re_match_count;
643     unsigned int already_current;
644     unsigned int updated;
645     unsigned int dry_run : 1;
646     unsigned int verbose : 1;
647 #ifdef SOLARIS_REGEXPS
648     char *expbuf;
649 #endif
650 #ifdef POSIX_REGEXPS
651     regex_t preg;
652 #endif
653 #if !defined(SOLARIS_REGEXPS) && !defined(POSIX_REGEXPS)
654     unsigned char placeholder;
655 #endif
656 };
657 
658 /* XXX Duplicated in libkadm5srv! */
659 /*
660  * Function: glob_to_regexp
661  *
662  * Arguments:
663  *
664  *      glob    (r) the shell-style glob (?*[]) to convert
665  *      realm   (r) the default realm to append, or NULL
666  *      regexp  (w) the ed-style regexp created from glob
667  *
668  * Effects:
669  *
670  * regexp is filled in with allocated memory contained a regular
671  * expression to be used with re_comp/compile that matches what the
672  * shell-style glob would match.  If glob does not contain an "@"
673  * character and realm is not NULL, "@*" is appended to the regexp.
674  *
675  * Conversion algorithm:
676  *
677  *      quoted characters are copied quoted
678  *      ? is converted to .
679  *      * is converted to .*
680  *      active characters are quoted: ^, $, .
681  *      [ and ] are active but supported and have the same meaning, so
682  *              they are copied
683  *      other characters are copied
684  *      regexp is anchored with ^ and $
685  */
glob_to_regexp(char * glob,char * realm,char ** regexp)686 static int glob_to_regexp(char *glob, char *realm, char **regexp)
687 {
688     int append_realm;
689     char *p;
690 
691     /* validate the glob */
692     if (glob[strlen(glob)-1] == '\\')
693         return EINVAL;
694 
695     /* A character of glob can turn into two in regexp, plus ^ and $ */
696     /* and trailing null.  If glob has no @, also allocate space for */
697     /* the realm. */
698     append_realm = (realm != NULL) && (strchr(glob, '@') == NULL);
699     p = (char *) malloc(strlen(glob)*2+ 3 + (append_realm ? 3 : 0));
700     if (p == NULL)
701         return ENOMEM;
702     *regexp = p;
703 
704     *p++ = '^';
705     while (*glob) {
706         switch (*glob) {
707         case '?':
708             *p++ = '.';
709             break;
710         case '*':
711             *p++ = '.';
712             *p++ = '*';
713             break;
714         case '.':
715         case '^':
716         case '$':
717             *p++ = '\\';
718             *p++ = *glob;
719             break;
720         case '\\':
721             *p++ = '\\';
722             *p++ = *++glob;
723             break;
724         default:
725             *p++ = *glob;
726             break;
727         }
728         glob++;
729     }
730 
731     if (append_realm) {
732         *p++ = '@';
733         *p++ = '.';
734         *p++ = '*';
735     }
736 
737     *p++ = '$';
738     *p++ = '\0';
739     return 0;
740 }
741 
742 static int
update_princ_encryption_1(void * cb,krb5_db_entry * ent)743 update_princ_encryption_1(void *cb, krb5_db_entry *ent)
744 {
745     struct update_enc_mkvno *p = cb;
746     char *pname = 0;
747     krb5_error_code retval;
748     int match;
749     krb5_timestamp now;
750     int result;
751     krb5_kvno old_mkvno;
752 
753     retval = krb5_unparse_name(util_context, ent->princ, &pname);
754     if (retval) {
755         com_err(progname, retval,
756                 _("getting string representation of principal name"));
757         goto fail;
758     }
759 
760     if (krb5_principal_compare(util_context, ent->princ, master_princ)) {
761         goto skip;
762     }
763 
764 #ifdef SOLARIS_REGEXPS
765     match = (step(pname, p->expbuf) != 0);
766 #endif
767 #ifdef POSIX_REGEXPS
768     match = (regexec(&p->preg, pname, 0, NULL, 0) == 0);
769 #endif
770 #ifdef BSD_REGEXPS
771     match = (re_exec(pname) != 0);
772 #endif
773     if (!match) {
774         goto skip;
775     }
776     p->re_match_count++;
777     retval = krb5_dbe_get_mkvno(util_context, ent, &old_mkvno);
778     if (retval) {
779         com_err(progname, retval,
780                 _("determining master key used for principal '%s'"), pname);
781         goto fail;
782     }
783     /* Line up "skip" and "update" messages for viewing.  */
784     if (old_mkvno == new_mkvno) {
785         if (p->dry_run && p->verbose)
786             printf(_("would skip:   %s\n"), pname);
787         else if (p->verbose)
788             printf(_("skipping: %s\n"), pname);
789         p->already_current++;
790         goto skip;
791     }
792     if (p->dry_run) {
793         if (p->verbose)
794             printf(_("would update: %s\n"), pname);
795         p->updated++;
796         goto skip;
797     } else if (p->verbose)
798         printf(_("updating: %s\n"), pname);
799     retval = master_key_convert (util_context, ent);
800     if (retval) {
801         com_err(progname, retval,
802                 _("error re-encrypting key for principal '%s'"), pname);
803         goto fail;
804     }
805     if ((retval = krb5_timeofday(util_context, &now))) {
806         com_err(progname, retval, _("while getting current time"));
807         goto fail;
808     }
809 
810     if ((retval = krb5_dbe_update_mod_princ_data(util_context, ent,
811                                                  now, master_princ))) {
812         com_err(progname, retval,
813                 _("while updating principal '%s' modification time"), pname);
814         goto fail;
815     }
816 
817     ent->mask |= KADM5_KEY_DATA;
818 
819     if ((retval = krb5_db_put_principal(util_context, ent))) {
820         com_err(progname, retval, _("while updating principal '%s' key data "
821                                     "in the database"), pname);
822         goto fail;
823     }
824     p->updated++;
825 skip:
826     result = 0;
827     goto egress;
828 fail:
829     exit_status++;
830     result = 1;
831 egress:
832     if (pname)
833         krb5_free_unparsed_name(util_context, pname);
834     return result;
835 }
836 
837 extern int are_you_sure (const char *, ...)
838 #if !defined(__cplusplus) && (__GNUC__ > 2)
839     __attribute__((__format__(__printf__, 1, 2)))
840 #endif
841     ;
842 
843 int
are_you_sure(const char * format,...)844 are_you_sure (const char *format, ...)
845 {
846     va_list va;
847     char ansbuf[100];
848 
849     va_start(va, format);
850     vprintf(format, va);
851     va_end(va);
852     printf(_("\n(type 'yes' to confirm)? "));
853     fflush(stdout);
854     if (fgets(ansbuf, sizeof(ansbuf), stdin) == NULL)
855         return 0;
856     if (strcmp(ansbuf, "yes\n"))
857         return 0;
858     return 1;
859 }
860 
861 void
kdb5_update_princ_encryption(int argc,char * argv[])862 kdb5_update_princ_encryption(int argc, char *argv[])
863 {
864     struct update_enc_mkvno data = { 0 };
865     char *name_pattern = NULL;
866     int force = 0;
867     int optchar;
868     krb5_error_code retval;
869     krb5_actkvno_node *actkvno_list = 0;
870     krb5_db_entry *master_entry = NULL;
871 #ifdef BSD_REGEXPS
872     char *msg;
873 #endif
874     char *regexp = NULL;
875     krb5_keyblock *act_mkey;
876     krb5_keylist_node *master_keylist = krb5_db_mkey_list_alias(util_context);
877     krb5_flags iterflags = 0;
878 
879     while ((optchar = getopt(argc, argv, "fnv")) != -1) {
880         switch (optchar) {
881         case 'f':
882             force = 1;
883             break;
884         case 'n':
885             data.dry_run = 1;
886             break;
887         case 'v':
888             data.verbose = 1;
889             break;
890         case '?':
891         case ':':
892         default:
893             usage();
894         }
895     }
896     if (argv[optind] != NULL) {
897         name_pattern = argv[optind];
898         if (argv[optind+1] != NULL)
899             usage();
900     }
901 
902     if (master_keylist == NULL) {
903         com_err(progname, 0, _("master keylist not initialized"));
904         exit_status++;
905         goto cleanup;
906     }
907 
908     /* The glob_to_regexp code only cares if the "realm" parameter is
909        NULL or not; the string data is irrelevant.  */
910     if (name_pattern == NULL)
911         name_pattern = "*";
912     if (glob_to_regexp(name_pattern, "hi", &regexp) != 0) {
913         com_err(progname, ENOMEM,
914                 _("converting glob pattern '%s' to regular expression"),
915                 name_pattern);
916         exit_status++;
917         goto cleanup;
918     }
919 
920     if (
921 #ifdef SOLARIS_REGEXPS
922         ((data.expbuf = compile(regexp, NULL, NULL)) == NULL)
923 #endif
924 #ifdef POSIX_REGEXPS
925         ((regcomp(&data.preg, regexp, REG_NOSUB)) != 0)
926 #endif
927 #ifdef BSD_REGEXPS
928         ((msg = (char *) re_comp(regexp)) != NULL)
929 #endif
930     ) {
931         /* XXX syslog msg or regerr(regerrno) */
932         com_err(progname, 0, _("error compiling converted regexp '%s'"),
933                 regexp);
934         exit_status++;
935         goto cleanup;
936     }
937 
938     retval = krb5_db_get_principal(util_context, master_princ, 0,
939                                    &master_entry);
940     if (retval != 0) {
941         com_err(progname, retval, _("while getting master key principal %s"),
942                 mkey_fullname);
943         exit_status++;
944         goto cleanup;
945     }
946 
947     retval = krb5_dbe_lookup_actkvno(util_context, master_entry, &actkvno_list);
948     if (retval != 0) {
949         com_err(progname, retval, _("while looking up active kvno list"));
950         exit_status++;
951         goto cleanup;
952     }
953 
954     retval = krb5_dbe_find_act_mkey(util_context, actkvno_list, &new_mkvno,
955                                     &act_mkey);
956     if (retval) {
957         com_err(progname, retval, _("while looking up active master key"));
958         exit_status++;
959         goto cleanup;
960     }
961     new_master_keyblock = *act_mkey;
962 
963     if (!force &&
964         !data.dry_run &&
965         !are_you_sure(_("Re-encrypt all keys not using master key vno %u?"),
966                       new_mkvno)) {
967         printf(_("OK, doing nothing.\n"));
968         exit_status++;
969         goto cleanup;
970     }
971     if (data.verbose) {
972         if (data.dry_run) {
973             printf(_("Principals whose keys WOULD BE re-encrypted to master "
974                      "key vno %u:\n"), new_mkvno);
975         } else {
976             printf(_("Principals whose keys are being re-encrypted to master "
977                      "key vno %u if necessary:\n"), new_mkvno);
978         }
979     }
980 
981     if (!data.dry_run) {
982         /* Grab a write lock so we don't have to upgrade to a write lock and
983          * reopen the DB while iterating. */
984         iterflags = KRB5_DB_ITER_WRITE;
985     }
986 
987     retval = krb5_db_iterate(util_context, name_pattern,
988                              update_princ_encryption_1, &data, iterflags);
989     /* If exit_status is set, then update_princ_encryption_1 already
990        printed a message.  */
991     if (retval != 0 && exit_status == 0) {
992         com_err(progname, retval, _("trying to process principal database"));
993         exit_status++;
994     }
995     if (data.dry_run) {
996         printf(_("%u principals processed: %u would be updated, %u already "
997                  "current\n"),
998                data.re_match_count, data.updated, data.already_current);
999     } else {
1000         printf(_("%u principals processed: %u updated, %u already current\n"),
1001                data.re_match_count, data.updated, data.already_current);
1002     }
1003 
1004 cleanup:
1005     krb5_db_free_principal(util_context, master_entry);
1006     free(regexp);
1007 #ifdef POSIX_REGEXPS
1008     regfree(&data.preg);
1009 #endif
1010     memset(&new_master_keyblock, 0, sizeof(new_master_keyblock));
1011     krb5_dbe_free_actkvno_list(util_context, actkvno_list);
1012 }
1013 
1014 struct kvnos_in_use {
1015     krb5_kvno               kvno;
1016     unsigned int            use_count;
1017 };
1018 
1019 struct purge_args {
1020     krb5_context         kcontext;
1021     struct kvnos_in_use  *kvnos;
1022     unsigned int         num_kvnos;
1023 };
1024 
1025 static krb5_error_code
find_mkvnos_in_use(krb5_pointer ptr,krb5_db_entry * entry)1026 find_mkvnos_in_use(krb5_pointer   ptr,
1027                    krb5_db_entry *entry)
1028 {
1029     krb5_error_code retval;
1030     struct purge_args * args;
1031     unsigned int i;
1032     krb5_kvno mkvno;
1033 
1034     args = (struct purge_args *) ptr;
1035 
1036     retval = krb5_dbe_get_mkvno(args->kcontext, entry, &mkvno);
1037     if (retval)
1038         return (retval);
1039 
1040     for (i = 0; i < args->num_kvnos; i++) {
1041         if (args->kvnos[i].kvno == mkvno) {
1042             /* XXX do I need to worry about use_count wrapping? */
1043             args->kvnos[i].use_count++;
1044             break;
1045         }
1046     }
1047     return 0;
1048 }
1049 
1050 void
kdb5_purge_mkeys(int argc,char * argv[])1051 kdb5_purge_mkeys(int argc, char *argv[])
1052 {
1053     int optchar;
1054     krb5_error_code retval;
1055     krb5_timestamp now;
1056     krb5_db_entry *master_entry = NULL;
1057     krb5_boolean force = FALSE, dry_run = FALSE, verbose = FALSE;
1058     struct purge_args args;
1059     char buf[5];
1060     unsigned int i, j, k, num_kvnos_inuse, num_kvnos_purged;
1061     unsigned int old_key_data_count;
1062     krb5_actkvno_node *actkvno_list = NULL, *actkvno_entry, *prev_actkvno_entry;
1063     krb5_mkey_aux_node *mkey_aux_list = NULL, *mkey_aux_entry, *prev_mkey_aux_entry;
1064     krb5_key_data *old_key_data;
1065 
1066     /*
1067      * Verify that the master key list has been initialized before doing
1068      * anything else.
1069      */
1070     if (krb5_db_mkey_list_alias(util_context) == NULL) {
1071         com_err(progname, KRB5_KDB_DBNOTINITED,
1072                 _("master keylist not initialized"));
1073         exit_status++;
1074         return;
1075     }
1076 
1077     memset(&args, 0, sizeof(args));
1078 
1079     optind = 1;
1080     while ((optchar = getopt(argc, argv, "fnv")) != -1) {
1081         switch(optchar) {
1082         case 'f':
1083             force = TRUE;
1084             break;
1085         case 'n':
1086             dry_run = TRUE; /* mkey princ will not be modified */
1087             force = TRUE; /* implied */
1088             break;
1089         case 'v':
1090             verbose = TRUE;
1091             break;
1092         case '?':
1093         default:
1094             usage();
1095             return;
1096         }
1097     }
1098 
1099     retval = krb5_db_get_principal(util_context, master_princ, 0,
1100                                    &master_entry);
1101     if (retval != 0) {
1102         com_err(progname, retval, _("while getting master key principal %s"),
1103                 mkey_fullname);
1104         exit_status++;
1105         goto cleanup_return;
1106     }
1107 
1108     if (!force) {
1109         printf(_("Will purge all unused master keys stored in the '%s' "
1110                  "principal, are you sure?\n"), mkey_fullname);
1111         printf(_("(type 'yes' to confirm)? "));
1112         if (fgets(buf, sizeof(buf), stdin) == NULL) {
1113             exit_status++;
1114             goto cleanup_return;
1115         }
1116         if (strcmp(buf, "yes\n")) {
1117             exit_status++;
1118             goto cleanup_return;
1119         }
1120         printf(_("OK, purging unused master keys from '%s'...\n"),
1121                mkey_fullname);
1122     }
1123 
1124     /* save the old keydata */
1125     old_key_data_count = master_entry->n_key_data;
1126     if (old_key_data_count == 1) {
1127         if (verbose)
1128             printf(_("There is only one master key which can not be "
1129                      "purged.\n"));
1130         goto cleanup_return;
1131     }
1132     old_key_data = master_entry->key_data;
1133 
1134     args.kvnos = (struct kvnos_in_use *) malloc(sizeof(struct kvnos_in_use) * old_key_data_count);
1135     if (args.kvnos == NULL) {
1136         retval = ENOMEM;
1137         com_err(progname, ENOMEM, _("while allocating args.kvnos"));
1138         exit_status++;
1139         goto cleanup_return;
1140     }
1141     memset(args.kvnos, 0, sizeof(struct kvnos_in_use) * old_key_data_count);
1142     args.num_kvnos = old_key_data_count;
1143     args.kcontext = util_context;
1144 
1145     /* populate the kvnos array with all the current mkvnos */
1146     for (i = 0; i < old_key_data_count; i++)
1147         args.kvnos[i].kvno =  master_entry->key_data[i].key_data_kvno;
1148 
1149     if ((retval = krb5_db_iterate(util_context,
1150                                   NULL,
1151                                   find_mkvnos_in_use,
1152                                   (krb5_pointer) &args, 0))) {
1153         com_err(progname, retval, _("while finding master keys in use"));
1154         exit_status++;
1155         goto cleanup_return;
1156     }
1157     /*
1158      * args.kvnos has been marked with the mkvno's that are currently protecting
1159      * princ entries
1160      */
1161     if (dry_run) {
1162         printf(_("Would purge the following master key(s) from %s:\n"),
1163                mkey_fullname);
1164     } else {
1165         printf(_("Purging the following master key(s) from %s:\n"),
1166                mkey_fullname);
1167     }
1168 
1169     /* find # of keys still in use or print out verbose info */
1170     for (i = num_kvnos_inuse = num_kvnos_purged = 0; i < args.num_kvnos; i++) {
1171         if (args.kvnos[i].use_count > 0) {
1172             num_kvnos_inuse++;
1173         } else {
1174             /* this key would be deleted */
1175             if (args.kvnos[i].kvno == master_kvno) {
1176                 com_err(progname, KRB5_KDB_STORED_MKEY_NOTCURRENT,
1177                         _("master key stash file needs updating, command "
1178                           "aborting"));
1179                 exit_status++;
1180                 goto cleanup_return;
1181             }
1182             num_kvnos_purged++;
1183             printf(_("KVNO: %d\n"), args.kvnos[i].kvno);
1184         }
1185     }
1186     /* didn't find any keys to purge */
1187     if (num_kvnos_inuse == args.num_kvnos) {
1188         printf(_("All keys in use, nothing purged.\n"));
1189         goto cleanup_return;
1190     }
1191     if (dry_run) {
1192         /* bail before doing anything else */
1193         printf(_("%d key(s) would be purged.\n"), num_kvnos_purged);
1194         goto cleanup_return;
1195     }
1196 
1197     retval = krb5_dbe_lookup_actkvno(util_context, master_entry, &actkvno_list);
1198     if (retval != 0) {
1199         com_err(progname, retval, _("while looking up active kvno list"));
1200         exit_status++;
1201         goto cleanup_return;
1202     }
1203 
1204     retval = krb5_dbe_lookup_mkey_aux(util_context, master_entry, &mkey_aux_list);
1205     if (retval != 0) {
1206         com_err(progname, retval, _("while looking up mkey aux data list"));
1207         exit_status++;
1208         goto cleanup_return;
1209     }
1210 
1211     master_entry->key_data = (krb5_key_data *) malloc(sizeof(krb5_key_data) * num_kvnos_inuse);
1212     if (master_entry->key_data == NULL) {
1213         retval = ENOMEM;
1214         com_err(progname, ENOMEM, _("while allocating key_data"));
1215         exit_status++;
1216         goto cleanup_return;
1217     }
1218     memset(master_entry->key_data, 0, sizeof(krb5_key_data) * num_kvnos_inuse);
1219     master_entry->n_key_data = num_kvnos_inuse; /* there's only 1 mkey per kvno */
1220 
1221     /*
1222      * Assuming that the latest mkey will not be purged because it will always
1223      * be "in use" so this code will not bother with encrypting keys again.
1224      */
1225     for (i = k = 0; i < old_key_data_count; i++) {
1226         for (j = 0; j < args.num_kvnos; j++) {
1227             if (args.kvnos[j].kvno == (krb5_kvno) old_key_data[i].key_data_kvno) {
1228                 if (args.kvnos[j].use_count != 0) {
1229                     master_entry->key_data[k++] = old_key_data[i];
1230                     memset(&old_key_data[i], 0, sizeof(old_key_data[i]));
1231                     break;
1232                 } else {
1233                     /* remove unused mkey */
1234                     /* adjust the actkno data */
1235                     for (prev_actkvno_entry = actkvno_entry = actkvno_list;
1236                          actkvno_entry != NULL;
1237                          actkvno_entry = actkvno_entry->next) {
1238 
1239                         if (actkvno_entry->act_kvno == args.kvnos[j].kvno) {
1240                             if (actkvno_entry == actkvno_list) {
1241                                 /* remove from head */
1242                                 actkvno_list = actkvno_entry->next;
1243                             } else if (actkvno_entry->next == NULL) {
1244                                 /* remove from tail */
1245                                 prev_actkvno_entry->next = NULL;
1246                             } else {
1247                                 /* remove in between */
1248                                 prev_actkvno_entry->next = actkvno_entry->next;
1249                             }
1250                             actkvno_entry->next = NULL;
1251                             krb5_dbe_free_actkvno_list(util_context, actkvno_entry);
1252                             break; /* deleted entry, no need to loop further */
1253                         } else {
1254                             prev_actkvno_entry = actkvno_entry;
1255                         }
1256                     }
1257                     /* adjust the mkey aux data */
1258                     for (prev_mkey_aux_entry = mkey_aux_entry = mkey_aux_list;
1259                          mkey_aux_entry != NULL;
1260                          mkey_aux_entry = mkey_aux_entry->next) {
1261 
1262                         if (mkey_aux_entry->mkey_kvno == args.kvnos[j].kvno) {
1263                             if (mkey_aux_entry == mkey_aux_list) {
1264                                 mkey_aux_list = mkey_aux_entry->next;
1265                             } else if (mkey_aux_entry->next == NULL) {
1266                                 prev_mkey_aux_entry->next = NULL;
1267                             } else {
1268                                 prev_mkey_aux_entry->next = mkey_aux_entry->next;
1269                             }
1270                             mkey_aux_entry->next = NULL;
1271                             krb5_dbe_free_mkey_aux_list(util_context, mkey_aux_entry);
1272                             break; /* deleted entry, no need to loop further */
1273                         } else {
1274                             prev_mkey_aux_entry = mkey_aux_entry;
1275                         }
1276                     }
1277                 }
1278             }
1279         }
1280     }
1281     assert(k == num_kvnos_inuse);
1282 
1283     /* Free any key data entries we did not consume in the loop above. */
1284     for (i = 0; i < old_key_data_count; i++)
1285         krb5_dbe_free_key_data_contents(util_context, &old_key_data[i]);
1286     free(old_key_data);
1287 
1288     if ((retval = krb5_dbe_update_actkvno(util_context, master_entry,
1289                                           actkvno_list))) {
1290         com_err(progname, retval,
1291                 _("while updating actkvno data for master principal entry"));
1292         exit_status++;
1293         goto cleanup_return;
1294     }
1295 
1296     if ((retval = krb5_dbe_update_mkey_aux(util_context, master_entry,
1297                                            mkey_aux_list))) {
1298         com_err(progname, retval,
1299                 _("while updating mkey_aux data for master principal entry"));
1300         exit_status++;
1301         goto cleanup_return;
1302     }
1303 
1304     if ((retval = krb5_timeofday(util_context, &now))) {
1305         com_err(progname, retval, _("while getting current time"));
1306         exit_status++;
1307         goto cleanup_return;
1308     }
1309 
1310     if ((retval = krb5_dbe_update_mod_princ_data(util_context, master_entry,
1311                                                  now, master_princ))) {
1312         com_err(progname, retval, _("while updating the master key principal "
1313                                     "modification time"));
1314         exit_status++;
1315         goto cleanup_return;
1316     }
1317 
1318     master_entry->mask |= KADM5_KEY_DATA | KADM5_TL_DATA;
1319 
1320     if ((retval = krb5_db_put_principal(util_context, master_entry))) {
1321         com_err(progname, retval,
1322                 _("while adding master key entry to the database"));
1323         exit_status++;
1324         goto cleanup_return;
1325     }
1326     printf(_("%d key(s) purged.\n"), num_kvnos_purged);
1327 
1328 cleanup_return:
1329     krb5_db_free_principal(util_context, master_entry);
1330     free(args.kvnos);
1331     krb5_dbe_free_actkvno_list(util_context, actkvno_list);
1332     krb5_dbe_free_mkey_aux_list(util_context, mkey_aux_list);
1333     return;
1334 }
1335