xref: /freebsd/crypto/krb5/src/lib/kdb/kdb_cpw.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/kdb/kdb_cpw.c */
3 /*
4  * Copyright 1995, 2009, 2014 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 (C) 1998 by the FundsXpress, INC.
28  *
29  * All rights reserved.
30  *
31  * Export of this software from the United States of America may require
32  * a specific license from the United States Government.  It is the
33  * responsibility of any person or organization contemplating export to
34  * obtain such a license before exporting.
35  *
36  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
37  * distribute this software and its documentation for any purpose and
38  * without fee is hereby granted, provided that the above copyright
39  * notice appear in all copies and that both that copyright notice and
40  * this permission notice appear in supporting documentation, and that
41  * the name of FundsXpress. not be used in advertising or publicity pertaining
42  * to distribution of the software without specific, written prior
43  * permission.  FundsXpress makes no representations about the suitability of
44  * this software for any purpose.  It is provided "as is" without express
45  * or implied warranty.
46  *
47  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
48  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
49  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
50  */
51 
52 #include "k5-int.h"
53 #include "kdb.h"
54 #include <stdio.h>
55 #include <errno.h>
56 
57 enum save { DISCARD_ALL, KEEP_LAST_KVNO, KEEP_ALL };
58 
59 int
krb5_db_get_key_data_kvno(context,count,data)60 krb5_db_get_key_data_kvno(context, count, data)
61     krb5_context          context;
62     int                   count;
63     krb5_key_data       * data;
64 {
65     int i, kvno;
66     /* Find last key version number */
67     for (kvno = i = 0; i < count; i++) {
68         if (kvno < data[i].key_data_kvno) {
69             kvno = data[i].key_data_kvno;
70         }
71     }
72     return(kvno);
73 }
74 
75 static void
cleanup_key_data(context,count,data)76 cleanup_key_data(context, count, data)
77     krb5_context          context;
78     int                   count;
79     krb5_key_data       * data;
80 {
81     int i;
82 
83     /* If data is NULL, count is always 0 */
84     if (data == NULL) return;
85 
86     for (i = 0; i < count; i++)
87         krb5_dbe_free_key_data_contents(context, &data[i]);
88     free(data);
89 }
90 
91 /* Transfer key data from old_kd to new_kd, making sure that new_kd is
92  * encrypted with mkey.  May steal from old_kd and zero it out. */
93 static krb5_error_code
preserve_one_old_key(krb5_context context,krb5_keyblock * mkey,krb5_db_entry * dbent,krb5_key_data * old_kd,krb5_key_data * new_kd)94 preserve_one_old_key(krb5_context context, krb5_keyblock *mkey,
95                      krb5_db_entry *dbent, krb5_key_data *old_kd,
96                      krb5_key_data *new_kd)
97 {
98     krb5_error_code ret;
99     krb5_keyblock kb;
100     krb5_keysalt salt;
101 
102     memset(new_kd, 0, sizeof(*new_kd));
103 
104     ret = krb5_dbe_decrypt_key_data(context, mkey, old_kd, &kb, NULL);
105     if (ret == 0) {
106         /* old_kd is already encrypted in mkey, so just move it. */
107         *new_kd = *old_kd;
108         memset(old_kd, 0, sizeof(*old_kd));
109         krb5_free_keyblock_contents(context, &kb);
110         return 0;
111     }
112 
113     /* Decrypt and re-encrypt old_kd using mkey. */
114     ret = krb5_dbe_decrypt_key_data(context, NULL, old_kd, &kb, &salt);
115     if (ret)
116         return ret;
117     ret = krb5_dbe_encrypt_key_data(context, mkey, &kb, &salt,
118                                     old_kd->key_data_kvno, new_kd);
119     krb5_free_keyblock_contents(context, &kb);
120     krb5_free_data_contents(context, &salt.data);
121     return ret;
122 }
123 
124 /*
125  * Add key_data to dbent, making sure that each entry is encrypted in mkey.  If
126  * kvno is non-zero, preserve only keys of that kvno.  May steal some elements
127  * from key_data and zero them out.
128  */
129 static krb5_error_code
preserve_old_keys(krb5_context context,krb5_keyblock * mkey,krb5_db_entry * dbent,int kvno,int n_key_data,krb5_key_data * key_data)130 preserve_old_keys(krb5_context context, krb5_keyblock *mkey,
131                   krb5_db_entry *dbent, int kvno, int n_key_data,
132                   krb5_key_data *key_data)
133 {
134     krb5_error_code ret;
135     int i;
136 
137     for (i = 0; i < n_key_data; i++) {
138         if (kvno != 0 && key_data[i].key_data_kvno != kvno)
139             continue;
140         ret = krb5_dbe_create_key_data(context, dbent);
141         if (ret)
142             return ret;
143         ret = preserve_one_old_key(context, mkey, dbent, &key_data[i],
144                                    &dbent->key_data[dbent->n_key_data - 1]);
145         if (ret)
146             return ret;
147     }
148     return 0;
149 }
150 
151 static krb5_error_code
add_key_rnd(context,master_key,ks_tuple,ks_tuple_count,db_entry,kvno)152 add_key_rnd(context, master_key, ks_tuple, ks_tuple_count, db_entry, kvno)
153     krb5_context          context;
154     krb5_keyblock       * master_key;
155     krb5_key_salt_tuple * ks_tuple;
156     int                   ks_tuple_count;
157     krb5_db_entry       * db_entry;
158     int                   kvno;
159 {
160     krb5_keyblock         key;
161     int                   i, j;
162     krb5_error_code       retval;
163     krb5_key_data        *kd_slot;
164 
165     for (i = 0; i < ks_tuple_count; i++) {
166         krb5_boolean similar;
167 
168         similar = 0;
169 
170         /*
171          * We could use krb5_keysalt_iterate to replace this loop, or use
172          * krb5_keysalt_is_present for the loop below, but we want to avoid
173          * circular library dependencies.
174          */
175         for (j = 0; j < i; j++) {
176             if ((retval = krb5_c_enctype_compare(context,
177                                                  ks_tuple[i].ks_enctype,
178                                                  ks_tuple[j].ks_enctype,
179                                                  &similar)))
180                 return(retval);
181 
182             if (similar)
183                 break;
184         }
185 
186         if (similar)
187             continue;
188 
189         if ((retval = krb5_dbe_create_key_data(context, db_entry)))
190             return retval;
191         kd_slot = &db_entry->key_data[db_entry->n_key_data - 1];
192 
193         /* there used to be code here to extract the old key, and derive
194            a new key from it.  Now that there's a unified prng, that isn't
195            necessary. */
196 
197         /* make new key */
198         if ((retval = krb5_c_make_random_key(context, ks_tuple[i].ks_enctype,
199                                              &key)))
200             return retval;
201 
202         retval = krb5_dbe_encrypt_key_data(context, master_key, &key, NULL,
203                                            kvno, kd_slot);
204 
205         krb5_free_keyblock_contents(context, &key);
206         if( retval )
207             return retval;
208     }
209 
210     return 0;
211 }
212 
213 /* Construct a random explicit salt. */
214 static krb5_error_code
make_random_salt(krb5_context context,krb5_keysalt * salt_out)215 make_random_salt(krb5_context context, krb5_keysalt *salt_out)
216 {
217     krb5_error_code retval;
218     unsigned char rndbuf[8];
219     krb5_data salt, rnd = make_data(rndbuf, sizeof(rndbuf));
220     unsigned int i;
221 
222     /*
223      * Salts are limited by RFC 4120 to 7-bit ASCII.  For ease of examination
224      * and to avoid certain folding issues for older enctypes, we use printable
225      * characters with four fixed bits and four random bits, encoding 64
226      * psuedo-random bits into 16 bytes.
227      */
228     retval = krb5_c_random_make_octets(context, &rnd);
229     if (retval)
230         return retval;
231     retval = alloc_data(&salt, sizeof(rndbuf) * 2);
232     if (retval)
233         return retval;
234     for (i = 0; i < sizeof(rndbuf); i++) {
235         salt.data[i * 2] = 0x40 | (rndbuf[i] >> 4);
236         salt.data[i * 2 + 1] = 0x40 | (rndbuf[i] & 0xf);
237     }
238 
239     salt_out->type = KRB5_KDB_SALTTYPE_SPECIAL;
240     salt_out->data = salt;
241     return 0;
242 }
243 
244 /*
245  * Add key_data for a krb5_db_entry
246  * If passwd is NULL the assumes that the caller wants a random password.
247  */
248 static krb5_error_code
add_key_pwd(context,master_key,ks_tuple,ks_tuple_count,passwd,db_entry,kvno)249 add_key_pwd(context, master_key, ks_tuple, ks_tuple_count, passwd,
250             db_entry, kvno)
251     krb5_context          context;
252     krb5_keyblock       * master_key;
253     krb5_key_salt_tuple * ks_tuple;
254     int                   ks_tuple_count;
255     const char          * passwd;
256     krb5_db_entry       * db_entry;
257     int                   kvno;
258 {
259     krb5_error_code       retval;
260     krb5_keysalt          key_salt;
261     krb5_keyblock         key;
262     krb5_data             pwd;
263     int                   i, j;
264     krb5_key_data        *kd_slot;
265 
266     for (i = 0; i < ks_tuple_count; i++) {
267         krb5_boolean similar;
268 
269         similar = 0;
270 
271         /*
272          * We could use krb5_keysalt_iterate to replace this loop, or use
273          * krb5_keysalt_is_present for the loop below, but we want to avoid
274          * circular library dependencies.
275          */
276         for (j = 0; j < i; j++) {
277             if ((retval = krb5_c_enctype_compare(context,
278                                                  ks_tuple[i].ks_enctype,
279                                                  ks_tuple[j].ks_enctype,
280                                                  &similar)))
281                 return(retval);
282 
283             if (similar &&
284                 (ks_tuple[j].ks_salttype == ks_tuple[i].ks_salttype))
285                 break;
286         }
287 
288         if (j < i)
289             continue;
290 
291         if ((retval = krb5_dbe_create_key_data(context, db_entry)))
292             return(retval);
293         kd_slot = &db_entry->key_data[db_entry->n_key_data - 1];
294 
295         /* Convert password string to key using appropriate salt */
296         switch (key_salt.type = ks_tuple[i].ks_salttype) {
297         case KRB5_KDB_SALTTYPE_ONLYREALM: {
298             krb5_data * saltdata;
299             if ((retval = krb5_copy_data(context, krb5_princ_realm(context,
300                                                                    db_entry->princ), &saltdata)))
301                 return(retval);
302 
303             key_salt.data = *saltdata;
304             free(saltdata);
305         }
306             break;
307         case KRB5_KDB_SALTTYPE_NOREALM:
308             if ((retval=krb5_principal2salt_norealm(context, db_entry->princ,
309                                                     &key_salt.data)))
310                 return(retval);
311             break;
312         case KRB5_KDB_SALTTYPE_NORMAL:
313             if ((retval = krb5_principal2salt(context, db_entry->princ,
314                                               &key_salt.data)))
315                 return(retval);
316             break;
317         case KRB5_KDB_SALTTYPE_SPECIAL:
318             retval = make_random_salt(context, &key_salt);
319             if (retval)
320                 return retval;
321             break;
322         default:
323             return(KRB5_KDB_BAD_SALTTYPE);
324         }
325 
326         pwd = string2data((char *)passwd);
327 
328         retval = krb5_c_string_to_key_with_params(context,
329                                                   ks_tuple[i].ks_enctype,
330                                                   &pwd, &key_salt.data,
331                                                   NULL, &key);
332         if (retval) {
333             free(key_salt.data.data);
334             return retval;
335         }
336 
337         retval = krb5_dbe_encrypt_key_data(context, master_key, &key,
338                                            (const krb5_keysalt *)&key_salt,
339                                            kvno, kd_slot);
340         if (key_salt.data.data)
341             free(key_salt.data.data);
342         free(key.contents);
343 
344         if( retval )
345             return retval;
346     }
347 
348     return 0;
349 }
350 
351 static krb5_error_code
rekey(krb5_context context,krb5_keyblock * mkey,krb5_key_salt_tuple * ks_tuple,int ks_tuple_count,const char * password,int new_kvno,enum save savekeys,krb5_db_entry * db_entry)352 rekey(krb5_context context, krb5_keyblock *mkey, krb5_key_salt_tuple *ks_tuple,
353       int ks_tuple_count, const char *password, int new_kvno,
354       enum save savekeys, krb5_db_entry *db_entry)
355 {
356     krb5_error_code ret;
357     krb5_key_data *key_data;
358     int n_key_data, old_kvno, save_kvno;
359 
360     /* Save aside the old key data. */
361     n_key_data = db_entry->n_key_data;
362     key_data = db_entry->key_data;
363     db_entry->n_key_data = 0;
364     db_entry->key_data = NULL;
365 
366     /* Make sure the new kvno is greater than the old largest kvno. */
367     old_kvno = krb5_db_get_key_data_kvno(context, n_key_data, key_data);
368     if (new_kvno < old_kvno + 1)
369         new_kvno = old_kvno + 1;
370     /* Wrap from 65535 to 1; we can only store 16-bit kvno values in key_data,
371      * and we assign special meaning to kvno 0. */
372     if (new_kvno == (1 << 16))
373         new_kvno = 1;
374 
375     /* Add new keys to the front of the list. */
376     if (password != NULL) {
377         ret = add_key_pwd(context, mkey, ks_tuple, ks_tuple_count, password,
378                           db_entry, new_kvno);
379     } else {
380         ret = add_key_rnd(context, mkey, ks_tuple, ks_tuple_count, db_entry,
381                           new_kvno);
382     }
383     if (ret) {
384         cleanup_key_data(context, db_entry->n_key_data, db_entry->key_data);
385         db_entry->n_key_data = n_key_data;
386         db_entry->key_data = key_data;
387         return ret;
388     }
389 
390     /* Possibly add some or all of the old keys to the back of the list.  May
391      * steal from and zero out some of the old key data entries. */
392     if (savekeys != DISCARD_ALL) {
393         save_kvno = (savekeys == KEEP_LAST_KVNO) ? old_kvno : 0;
394         ret = preserve_old_keys(context, mkey, db_entry, save_kvno, n_key_data,
395                                 key_data);
396     }
397 
398     /* Free any old key data entries not stolen and zeroed out above. */
399     cleanup_key_data(context, n_key_data, key_data);
400     return ret;
401 }
402 
403 /*
404  * Change random key for a krb5_db_entry
405  * Assumes the max kvno
406  *
407  * As a side effect all old keys are nuked if keepold is false.
408  */
409 krb5_error_code
krb5_dbe_crk(krb5_context context,krb5_keyblock * mkey,krb5_key_salt_tuple * ks_tuple,int ks_tuple_count,krb5_boolean keepold,krb5_db_entry * dbent)410 krb5_dbe_crk(krb5_context context, krb5_keyblock *mkey,
411              krb5_key_salt_tuple *ks_tuple, int ks_tuple_count,
412              krb5_boolean keepold, krb5_db_entry *dbent)
413 {
414     return rekey(context, mkey, ks_tuple, ks_tuple_count, NULL, 0,
415                  keepold ? KEEP_ALL : DISCARD_ALL, dbent);
416 }
417 
418 /*
419  * Add random key for a krb5_db_entry
420  * Assumes the max kvno
421  *
422  * As a side effect all old keys older than the max kvno are nuked.
423  */
424 krb5_error_code
krb5_dbe_ark(krb5_context context,krb5_keyblock * mkey,krb5_key_salt_tuple * ks_tuple,int ks_tuple_count,krb5_db_entry * dbent)425 krb5_dbe_ark(krb5_context context, krb5_keyblock *mkey,
426              krb5_key_salt_tuple *ks_tuple, int ks_tuple_count,
427              krb5_db_entry *dbent)
428 {
429     return rekey(context, mkey, ks_tuple, ks_tuple_count, NULL, 0,
430                  KEEP_LAST_KVNO, dbent);
431 }
432 
433 /*
434  * Change password for a krb5_db_entry
435  * Assumes the max kvno
436  *
437  * As a side effect all old keys are nuked if keepold is false.
438  */
439 krb5_error_code
krb5_dbe_def_cpw(krb5_context context,krb5_keyblock * mkey,krb5_key_salt_tuple * ks_tuple,int ks_tuple_count,char * password,int new_kvno,krb5_boolean keepold,krb5_db_entry * dbent)440 krb5_dbe_def_cpw(krb5_context context, krb5_keyblock *mkey,
441                  krb5_key_salt_tuple *ks_tuple, int ks_tuple_count,
442                  char *password, int new_kvno, krb5_boolean keepold,
443                  krb5_db_entry *dbent)
444 {
445     return rekey(context, mkey, ks_tuple, ks_tuple_count, password, new_kvno,
446                  keepold ? KEEP_ALL : DISCARD_ALL, dbent);
447 }
448 
449 /*
450  * Add password for a krb5_db_entry
451  * Assumes the max kvno
452  *
453  * As a side effect all old keys older than the max kvno are nuked.
454  */
455 krb5_error_code
krb5_dbe_apw(krb5_context context,krb5_keyblock * mkey,krb5_key_salt_tuple * ks_tuple,int ks_tuple_count,char * password,krb5_db_entry * dbent)456 krb5_dbe_apw(krb5_context context, krb5_keyblock *mkey,
457              krb5_key_salt_tuple *ks_tuple, int ks_tuple_count, char *password,
458              krb5_db_entry *dbent)
459 {
460     return rekey(context, mkey, ks_tuple, ks_tuple_count, password, 0,
461                  KEEP_LAST_KVNO, dbent);
462 }
463