xref: /freebsd/crypto/krb5/src/lib/kdb/kdb_default.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/kdb/kdb_default.c */
3 /*
4  * Copyright 1995, 2009 by the Massachusetts Institute of Technology.
5  * All Rights Reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  *
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  Furthermore if you modify this software you must label
20  * your software as modified software and not distribute it in such a
21  * fashion that it might be confused with the original M.I.T. software.
22  * M.I.T. makes no representations about the suitability of
23  * this software for any purpose.  It is provided "as is" without express
24  * or implied warranty.
25  */
26 /*
27  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
28  * Use is subject to license terms.
29  */
30 
31 #include "k5-int.h"
32 #include "kdb.h"
33 #include <string.h>
34 #include <stdio.h>
35 #include <errno.h>
36 #include <arpa/inet.h>
37 
38 
39 /*
40  * Set *kd_out to the key data entry matching kvno, enctype, and salttype.  If
41  * any of those three parameters are -1, ignore them.  If kvno is 0, match only
42  * the highest kvno.  Begin searching at the index *start and set *start to the
43  * index after the match.  Do not return keys of non-permitted enctypes; return
44  * KRB5_KDB_NO_PERMITTED_KEY if the whole list was searched and only
45  * non-permitted matches were found.
46  */
47 krb5_error_code
krb5_dbe_def_search_enctype(krb5_context context,krb5_db_entry * ent,krb5_int32 * start,krb5_int32 enctype,krb5_int32 salttype,krb5_int32 kvno,krb5_key_data ** kd_out)48 krb5_dbe_def_search_enctype(krb5_context context, krb5_db_entry *ent,
49                             krb5_int32 *start, krb5_int32 enctype,
50                             krb5_int32 salttype, krb5_int32 kvno,
51                             krb5_key_data **kd_out)
52 {
53     krb5_key_data *kd;
54     krb5_int32 db_salttype;
55     krb5_boolean saw_non_permitted = FALSE;
56     int i;
57 
58     *kd_out = NULL;
59 
60     if (enctype != -1 && !krb5_is_permitted_enctype(context, enctype))
61         return KRB5_KDB_NO_PERMITTED_KEY;
62     if (ent->n_key_data == 0)
63         return KRB5_KDB_NO_MATCHING_KEY;
64 
65     /* Match the highest kvno if kvno is 0.  Key data is sorted in descending
66      * order of kvno. */
67     if (kvno == 0)
68         kvno = ent->key_data[0].key_data_kvno;
69 
70     for (i = *start; i < ent->n_key_data; i++) {
71         kd = &ent->key_data[i];
72         db_salttype = (kd->key_data_ver > 1) ? kd->key_data_type[1] :
73             KRB5_KDB_SALTTYPE_NORMAL;
74 
75         /* Match this entry against the arguments.  Stop searching if we have
76          * passed the entries for the requested kvno. */
77         if (enctype != -1 && kd->key_data_type[0] != enctype)
78             continue;
79         if (salttype >= 0 && db_salttype != salttype)
80             continue;
81         if (kvno >= 0 && kd->key_data_kvno < kvno)
82             break;
83         if (kvno >= 0 && kd->key_data_kvno != kvno)
84             continue;
85 
86         /* Filter out non-permitted enctypes. */
87         if (!krb5_is_permitted_enctype(context, kd->key_data_type[0])) {
88             saw_non_permitted = TRUE;
89             continue;
90         }
91 
92         *start = i + 1;
93         *kd_out = kd;
94         return 0;
95     }
96 
97     /* If we scanned the whole set of keys and matched only non-permitted
98      * enctypes, indicate that. */
99     return (*start == 0 && saw_non_permitted) ? KRB5_KDB_NO_PERMITTED_KEY :
100         KRB5_KDB_NO_MATCHING_KEY;
101 }
102 
103 /*
104  *  kdb default functions. Ideally, some other file should have this functions. For now, TBD.
105  */
106 #ifndef min
107 #define min(a,b) (((a) < (b)) ? (a) : (b))
108 #endif
109 
110 krb5_error_code
krb5_def_store_mkey_list(krb5_context context,char * keyfile,krb5_principal mname,krb5_keylist_node * keylist,char * master_pwd)111 krb5_def_store_mkey_list(krb5_context       context,
112                          char               *keyfile,
113                          krb5_principal     mname,
114                          krb5_keylist_node  *keylist,
115                          char               *master_pwd)
116 {
117     krb5_error_code retval = 0;
118     char defkeyfile[MAXPATHLEN+1];
119     char *tmp_ktname = NULL, *tmp_ktpath;
120     krb5_data *realm = krb5_princ_realm(context, mname);
121     krb5_keytab kt = NULL;
122     krb5_keytab_entry new_entry;
123     struct stat stb;
124     int statrc;
125 
126     if (!keyfile) {
127         (void) snprintf(defkeyfile, sizeof(defkeyfile), "%s%s",
128                         DEFAULT_KEYFILE_STUB, realm->data);
129         keyfile = defkeyfile;
130     }
131 
132     if ((statrc = stat(keyfile, &stb)) >= 0) {
133         /* if keyfile exists it better be a regular file */
134         if (!S_ISREG(stb.st_mode)) {
135             retval = EINVAL;
136             k5_setmsg(context, retval,
137                       _("keyfile (%s) is not a regular file: %s"),
138                       keyfile, error_message(retval));
139             goto out;
140         }
141     }
142 
143     /*
144      * We assume the stash file is in a directory writable only by root.
145      * As such, don't worry about collisions, just do an atomic rename.
146      */
147     retval = asprintf(&tmp_ktname, "FILE:%s_tmp", keyfile);
148     if (retval < 0) {
149         k5_setmsg(context, retval,
150                   _("Could not create temp keytab file name."));
151         goto out;
152     }
153 
154     /*
155      * Set tmp_ktpath to point to the keyfile path (skip FILE:).  Subtracting
156      * 1 to account for NULL terminator in sizeof calculation of a string
157      * constant.  Used further down.
158      */
159     tmp_ktpath = tmp_ktname + (sizeof("FILE:") - 1);
160 
161     /*
162      * This time-of-check-to-time-of-access race is fine; we care only
163      * about an administrator running the command twice, not an attacker
164      * trying to beat us to creating the file.  Per the above comment, we
165      * assume the stash file is in a directory writable only by root.
166      */
167     statrc = stat(tmp_ktpath, &stb);
168     if (statrc == -1 && errno != ENOENT) {
169         /* ENOENT is the expected case */
170         retval = errno;
171         goto out;
172     } else if (statrc == 0) {
173         retval = EEXIST;
174         k5_setmsg(context, retval,
175                   _("Temporary stash file already exists: %s."), tmp_ktpath);
176         goto out;
177     }
178 
179     /* create new stash keytab using temp file name */
180     retval = krb5_kt_resolve(context, tmp_ktname, &kt);
181     if (retval != 0)
182         goto out;
183 
184     while (keylist && !retval) {
185         memset(&new_entry, 0, sizeof(new_entry));
186         new_entry.principal = mname;
187         new_entry.key = keylist->keyblock;
188         new_entry.vno = keylist->kvno;
189 
190         retval = krb5_kt_add_entry(context, kt, &new_entry);
191         keylist = keylist->next;
192     }
193     krb5_kt_close(context, kt);
194 
195     if (retval != 0) {
196         /* Clean up by deleting the tmp keyfile if it exists. */
197         (void)unlink(tmp_ktpath);
198     } else {
199         /* Atomically rename temp keyfile to original filename. */
200         if (rename(tmp_ktpath, keyfile) < 0) {
201             retval = errno;
202             k5_setmsg(context, retval,
203                       _("rename of temporary keyfile (%s) to (%s) failed: %s"),
204                       tmp_ktpath, keyfile, error_message(errno));
205         }
206     }
207 
208 out:
209     if (tmp_ktname != NULL)
210         free(tmp_ktname);
211 
212     return retval;
213 }
214 
215 static krb5_error_code
krb5_db_def_fetch_mkey_stash(krb5_context context,const char * keyfile,krb5_keyblock * key,krb5_kvno * kvno)216 krb5_db_def_fetch_mkey_stash(krb5_context   context,
217                              const char *keyfile,
218                              krb5_keyblock *key,
219                              krb5_kvno     *kvno)
220 {
221     krb5_error_code retval = 0;
222     krb5_ui_2 enctype;
223     krb5_ui_4 keylength;
224     FILE *kf = NULL;
225 
226     if (!(kf = fopen(keyfile, "rb")))
227         return KRB5_KDB_CANTREAD_STORED;
228     set_cloexec_file(kf);
229 
230     if (fread((krb5_pointer) &enctype, 2, 1, kf) != 1) {
231         retval = KRB5_KDB_CANTREAD_STORED;
232         goto errout;
233     }
234 
235 #if BIG_ENDIAN_MASTER_KEY
236     enctype = ntohs((uint16_t) enctype);
237 #endif
238 
239     if (key->enctype == ENCTYPE_UNKNOWN)
240         key->enctype = enctype;
241     else if (enctype != key->enctype) {
242         retval = KRB5_KDB_BADSTORED_MKEY;
243         goto errout;
244     }
245 
246     if (fread((krb5_pointer) &keylength,
247               sizeof(keylength), 1, kf) != 1) {
248         retval = KRB5_KDB_CANTREAD_STORED;
249         goto errout;
250     }
251 
252 #if BIG_ENDIAN_MASTER_KEY
253     key->length = ntohl((uint32_t) keylength);
254 #else
255     key->length = keylength;
256 #endif
257 
258     if (!key->length || key->length > 1024) {
259         retval = KRB5_KDB_BADSTORED_MKEY;
260         goto errout;
261     }
262 
263     if (!(key->contents = (krb5_octet *)malloc(key->length))) {
264         retval = ENOMEM;
265         goto errout;
266     }
267 
268     if (fread((krb5_pointer) key->contents, sizeof(key->contents[0]),
269               key->length, kf) != key->length) {
270         retval = KRB5_KDB_CANTREAD_STORED;
271         zap(key->contents, key->length);
272         free(key->contents);
273         key->contents = 0;
274     } else
275         retval = 0;
276 
277     /*
278      * Note, the old stash format did not store the kvno and at this point it
279      * can be assumed to be 1 as is the case for the mkey princ.  If the kvno is
280      * passed in and isn't ignore_vno just leave it alone as this could cause
281      * verifcation trouble if the mkey princ is using a kvno other than 1.
282      */
283     if (kvno && *kvno == IGNORE_VNO)
284         *kvno = 1;
285 
286 errout:
287     (void) fclose(kf);
288     return retval;
289 }
290 
291 static krb5_error_code
krb5_db_def_fetch_mkey_keytab(krb5_context context,const char * keyfile,krb5_principal mname,krb5_keyblock * key,krb5_kvno * kvno)292 krb5_db_def_fetch_mkey_keytab(krb5_context   context,
293                               const char     *keyfile,
294                               krb5_principal mname,
295                               krb5_keyblock  *key,
296                               krb5_kvno      *kvno)
297 {
298     krb5_error_code retval = 0;
299     krb5_keytab kt = NULL;
300     krb5_keytab_entry kt_ent;
301     krb5_enctype enctype = IGNORE_ENCTYPE;
302 
303     if ((retval = krb5_kt_resolve(context, keyfile, &kt)) != 0)
304         goto errout;
305 
306     /* override default */
307     if (key->enctype != ENCTYPE_UNKNOWN)
308         enctype = key->enctype;
309 
310     if ((retval = krb5_kt_get_entry(context, kt, mname,
311                                     kvno ? *kvno : IGNORE_VNO,
312                                     enctype,
313                                     &kt_ent)) == 0) {
314 
315         if (key->enctype == ENCTYPE_UNKNOWN)
316             key->enctype = kt_ent.key.enctype;
317 
318         if (((int) kt_ent.key.length) < 0) {
319             retval = KRB5_KDB_BADSTORED_MKEY;
320             krb5_kt_free_entry(context, &kt_ent);
321             goto errout;
322         }
323 
324         key->length = kt_ent.key.length;
325 
326         /*
327          * If a kvno pointer was passed in and it dereferences the
328          * IGNORE_VNO value then it should be assigned the value of the kvno
329          * found in the keytab otherwise the KNVO specified should be the
330          * same as the one returned from the keytab.
331          */
332         if (kvno != NULL && *kvno == IGNORE_VNO)
333             *kvno = kt_ent.vno;
334 
335         /*
336          * kt_ent will be free'd so need to allocate and copy key contents for
337          * output to caller.
338          */
339         key->contents = k5memdup(kt_ent.key.contents, kt_ent.key.length,
340                                  &retval);
341         if (key->contents == NULL) {
342             krb5_kt_free_entry(context, &kt_ent);
343             goto errout;
344         }
345         krb5_kt_free_entry(context, &kt_ent);
346     }
347 
348 errout:
349     if (kt)
350         krb5_kt_close(context, kt);
351 
352     return retval;
353 }
354 
355 krb5_error_code
krb5_db_def_fetch_mkey(krb5_context context,krb5_principal mname,krb5_keyblock * key,krb5_kvno * kvno,char * db_args)356 krb5_db_def_fetch_mkey(krb5_context   context,
357                        krb5_principal mname,
358                        krb5_keyblock *key,
359                        krb5_kvno     *kvno,
360                        char          *db_args)
361 {
362     krb5_error_code retval;
363     char keyfile[MAXPATHLEN+1];
364     krb5_data *realm = krb5_princ_realm(context, mname);
365 
366     key->magic = KV5M_KEYBLOCK;
367 
368     if (db_args != NULL) {
369         (void) strncpy(keyfile, db_args, sizeof(keyfile));
370     } else {
371         (void) snprintf(keyfile, sizeof(keyfile), "%s%s",
372                         DEFAULT_KEYFILE_STUB, realm->data);
373     }
374     /* null terminate no matter what */
375     keyfile[sizeof(keyfile) - 1] = '\0';
376 
377     /* Try the keytab and old stash file formats. */
378     retval = krb5_db_def_fetch_mkey_keytab(context, keyfile, mname, key, kvno);
379     if (retval == KRB5_KEYTAB_BADVNO)
380         retval = krb5_db_def_fetch_mkey_stash(context, keyfile, key, kvno);
381 
382     /*
383      * Use a generic error code for failure to retrieve the master
384      * key, but set a message indicating the actual error.
385      */
386     if (retval != 0) {
387         k5_setmsg(context, KRB5_KDB_CANTREAD_STORED,
388                   _("Can not fetch master key (error: %s)."),
389                   error_message(retval));
390         return KRB5_KDB_CANTREAD_STORED;
391     } else
392         return 0;
393 }
394 
395 krb5_error_code
krb5_def_fetch_mkey_list(krb5_context context,krb5_principal mprinc,const krb5_keyblock * mkey,krb5_keylist_node ** mkeys_list)396 krb5_def_fetch_mkey_list(krb5_context        context,
397                          krb5_principal        mprinc,
398                          const krb5_keyblock  *mkey,
399                          krb5_keylist_node  **mkeys_list)
400 {
401     krb5_error_code retval;
402     krb5_db_entry *master_entry;
403     krb5_boolean found_key = FALSE;
404     krb5_keyblock cur_mkey;
405     krb5_keylist_node *mkey_list_head = NULL, **mkey_list_node;
406     krb5_key_data *key_data;
407     krb5_mkey_aux_node  *mkey_aux_data_list = NULL, *aux_data_entry;
408     int i;
409 
410     if (mkeys_list == NULL)
411         return (EINVAL);
412 
413     memset(&cur_mkey, 0, sizeof(cur_mkey));
414 
415     retval = krb5_db_get_principal(context, mprinc, 0, &master_entry);
416     if (retval == KRB5_KDB_NOENTRY)
417         return (KRB5_KDB_NOMASTERKEY);
418     if (retval)
419         return (retval);
420 
421     if (master_entry->n_key_data == 0) {
422         retval = KRB5_KDB_NOMASTERKEY;
423         goto clean_n_exit;
424     }
425 
426     /*
427      * Check if the input mkey is the latest key and if it isn't then find the
428      * latest mkey.
429      */
430 
431     if (mkey->enctype == master_entry->key_data[0].key_data_type[0]) {
432         if (krb5_dbe_decrypt_key_data(context, mkey,
433                                       &master_entry->key_data[0],
434                                       &cur_mkey, NULL) == 0) {
435             found_key = TRUE;
436         }
437     }
438 
439     if (!found_key) {
440         if ((retval = krb5_dbe_lookup_mkey_aux(context, master_entry,
441                                                &mkey_aux_data_list)))
442             goto clean_n_exit;
443 
444         for (aux_data_entry = mkey_aux_data_list; aux_data_entry != NULL;
445              aux_data_entry = aux_data_entry->next) {
446 
447             if (krb5_dbe_decrypt_key_data(context, mkey,
448                                           &aux_data_entry->latest_mkey,
449                                           &cur_mkey, NULL) == 0) {
450                 found_key = TRUE;
451                 break;
452             }
453         }
454         if (found_key != TRUE) {
455             k5_setmsg(context, KRB5_KDB_BADMASTERKEY,
456                       _("Unable to decrypt latest master key with the "
457                         "provided master key\n"));
458             retval = KRB5_KDB_BADMASTERKEY;
459             goto clean_n_exit;
460         }
461     }
462 
463     /*
464      * Extract all the mkeys from master_entry using the most current mkey and
465      * create a mkey list for the mkeys field in kdc_realm_t.
466      */
467 
468     mkey_list_head = (krb5_keylist_node *) malloc(sizeof(krb5_keylist_node));
469     if (mkey_list_head == NULL) {
470         retval = ENOMEM;
471         goto clean_n_exit;
472     }
473 
474     memset(mkey_list_head, 0, sizeof(krb5_keylist_node));
475 
476     /* Set mkey_list_head to the current mkey as an optimization. */
477     /* mkvno may not be latest so ... */
478     mkey_list_head->kvno = master_entry->key_data[0].key_data_kvno;
479     /* this is the latest clear mkey (avoids a redundant decrypt) */
480     mkey_list_head->keyblock = cur_mkey;
481 
482     /* loop through any other master keys creating a list of krb5_keylist_nodes */
483     mkey_list_node = &mkey_list_head->next;
484     for (i = 1; i < master_entry->n_key_data; i++) {
485         if (*mkey_list_node == NULL) {
486             /* *mkey_list_node points to next field of previous node */
487             *mkey_list_node = (krb5_keylist_node *) malloc(sizeof(krb5_keylist_node));
488             if (*mkey_list_node == NULL) {
489                 retval = ENOMEM;
490                 goto clean_n_exit;
491             }
492             memset(*mkey_list_node, 0, sizeof(krb5_keylist_node));
493         }
494         key_data = &master_entry->key_data[i];
495         retval = krb5_dbe_decrypt_key_data(context, &cur_mkey, key_data,
496                                            &((*mkey_list_node)->keyblock),
497                                            NULL);
498         if (retval)
499             goto clean_n_exit;
500 
501         (*mkey_list_node)->kvno = key_data->key_data_kvno;
502         mkey_list_node = &((*mkey_list_node)->next);
503     }
504 
505     *mkeys_list = mkey_list_head;
506 
507 clean_n_exit:
508     krb5_db_free_principal(context, master_entry);
509     krb5_dbe_free_mkey_aux_list(context, mkey_aux_data_list);
510     if (retval != 0)
511         krb5_dbe_free_key_list(context, mkey_list_head);
512     return retval;
513 }
514 
515 krb5_error_code
krb5_db_def_rename_principal(krb5_context kcontext,krb5_const_principal source,krb5_const_principal target)516 krb5_db_def_rename_principal(krb5_context kcontext,
517                              krb5_const_principal source,
518                              krb5_const_principal target)
519 {
520     krb5_db_entry *kdb = NULL;
521     krb5_principal oldprinc;
522     krb5_error_code ret;
523 
524     if (source == NULL || target == NULL)
525         return EINVAL;
526 
527     ret = krb5_db_get_principal(kcontext, source, 0, &kdb);
528     if (ret)
529         goto cleanup;
530 
531     /* Store salt values explicitly so that they don't depend on the principal
532      * name. */
533     ret = krb5_dbe_specialize_salt(kcontext, kdb);
534     if (ret)
535         goto cleanup;
536 
537     /* Temporarily alias kdb->princ to target and put the principal entry. */
538     oldprinc = kdb->princ;
539     kdb->princ = (krb5_principal)target;
540     ret = krb5_db_put_principal(kcontext, kdb);
541     kdb->princ = oldprinc;
542     if (ret)
543         goto cleanup;
544 
545     ret = krb5_db_delete_principal(kcontext, (krb5_principal)source);
546 
547 
548 cleanup:
549     krb5_db_free_principal(kcontext, kdb);
550     return ret;
551 }
552