Add akeyconvert, for rxkad.keytab to KeyFileExt conversion
[openafs.git] / src / aklog / akeyconvert.c
1 /* aklog/akeyconvert.c - migrate keys from rxkad.keytab to KeyFileExt */
2 /*
3  * Copyright (C) 2015 by the Massachusetts Institute of Technology.
4  * Copyright (C) 2016 Benjamin Kaduk.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * * Redistributions of source code must retain the above copyright
12  *   notice, this list of conditions and the following disclaimer.
13  *
14  * * Redistributions in binary form must reproduce the above copyright
15  *   notice, this list of conditions and the following disclaimer in
16  *   the documentation and/or other materials provided with the
17  *   distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30  * OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 /*
34  * Helper for migrations from OpenAFS 1.6.x to OpenAFS 1.8.x when
35  * the rxkad-k5 extension is in use.
36  *
37  * Read keys from the current rxkad.keytab and add them to the
38  * KeyFileExt, creating it if necessary.  Detect duplicated
39  * kvno/enctype keys, which are possible when attached to different
40  * principals in the rxkad.keytab, but are not possible in the
41  * KeyFileExt.
42  *
43  * The implementation reads the entire keytab contents into memory,
44  * then sorts by principal (most significant), kvno, and enctype (least
45  * significant) to facilitate selecting the newest kvno for each principal
46  * and avoiding duplicate kvno/enctype values.  The direction of sort is
47  * chosen so as to hopefully put the more often used keys at the beginning
48  * of the file.
49  *
50  * By default, only copy the latest key for each principal, but provide an
51  * option to copy all keys.
52  */
53
54 #include <afsconfig.h>
55 #include <afs/param.h>
56 #include <sys/errno.h>
57 #include <string.h>
58
59 #include <afs/cellconfig.h>
60 #include <afs/dirpath.h>
61 #include <afs/keys.h>
62 #include <afs/opr.h>
63 #include <afs/cmd.h>
64
65 #include <roken.h>
66
67 #include <stdio.h>
68
69 #define KERBEROS_APPLE_DEPRECATED(x)
70 /* krb5_free_unparsed_name() is deprecated; it's unclear why. */
71 #define KRB5_DEPRECATED_FUNCTION(x)
72 #include <krb5.h>
73 #ifdef HAVE_COM_ERR_H
74 # include <com_err.h>
75 #elif HAVE_ET_COM_ERR_H
76 # include <et/com_err.h>
77 #elif HAVE_KRB5_COM_ERR_H
78 # include <krb5/com_err.h>
79 #else
80 # error com_err is required for akeyconvert
81 #endif
82
83 #if HAVE_KRB5_KEYTAB_ENTRY_KEY
84 # define deref_entry_keylen(x)  ((x).key.length)
85 # define deref_entry_keyval(x)  ((x).key.contents)
86 # define deref_entry_enctype(x) ((x).key.enctype)
87 #elif HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK
88 # define deref_entry_keylen(x)  ((x).keyblock.keyvalue.length)
89 # define deref_entry_keyval(x)  ((x).keyblock.keyvalue.data)
90 # define deref_entry_enctype(x) ((x).keyblock.keytype)
91 #else
92 # error krb5_keytab_entry structure unknown
93 #endif
94 #ifndef HAVE_KRB5_FREE_KEYTAB_ENTRY_CONTENTS
95 # define krb5_free_keytab_entry_contents        krb5_kt_free_entry
96 #endif
97 #ifndef HAVE_KRB5_FREE_UNPARSED_NAME
98 # define krb5_free_unparsed_name(x, y)  free((y))
99 #endif
100
101 enum optionsList {
102     OPT_all,
103 };
104
105 /*
106  * Convert keytab entry to the OpenAFS typedKey format, allocating
107  * storage for the output.
108  *
109  * Returns 0 on success.
110  */
111 static afs_int32
112 ktent_to_typedKey(krb5_keytab_entry entry, struct afsconf_typedKey **out)
113 {
114     struct rx_opaque key;
115     afs_int32 enctype;
116
117     key.len = deref_entry_keylen(entry);
118     key.val = deref_entry_keyval(entry);
119     enctype = deref_entry_enctype(entry);
120     if (enctype == 1 /* ETYPE_DES_CBC_CRC */ ||
121         enctype == 2 /* ETYPE_DES_CBC_MD4 */ ||
122         enctype == 3 /* ETYPE_DES_CBC_MD5 */) {
123         *out = afsconf_typedKey_new(afsconf_rxkad, entry.vno, 0, &key);
124         if (*out == NULL)
125             return ENOMEM;
126         else
127             return 0;
128     }
129     /* else, an rxkad_krb5 key */
130     *out = afsconf_typedKey_new(afsconf_rxkad_krb5, entry.vno,
131                                 deref_entry_enctype(entry), &key);
132     if (*out == NULL)
133         return ENOMEM;
134     else
135         return 0;
136 }
137
138 static int
139 princ_sort(const void *aa, const void *bb)
140 {
141     const krb5_keytab_entry *a, *b;
142     char *name1 = NULL, *name2 = NULL;
143     krb5_boolean equal;
144     krb5_context ctx;
145     int ret;
146
147     a = aa;
148     b = bb;
149
150     opr_Verify(krb5_init_context(&ctx) == 0);
151     equal = krb5_principal_compare(ctx, a->principal, b->principal);
152     if (equal) {
153         ret = 0;
154         goto out;
155     }
156     opr_Verify(krb5_unparse_name(ctx, a->principal, &name1) == 0);
157     opr_Verify(krb5_unparse_name(ctx, b->principal, &name2) == 0);
158     ret = strcmp(name1, name2);
159     opr_Assert(ret != 0);
160
161 out:
162     krb5_free_unparsed_name(ctx, name1);
163     krb5_free_unparsed_name(ctx, name2);
164     krb5_free_context(ctx);
165     return ret;
166 }
167
168 static int
169 kvno_sort(const void *aa, const void *bb)
170 {
171     const krb5_keytab_entry *a, *b;
172
173     a = aa;
174     b = bb;
175
176     if (a->vno == b->vno)
177         return 0;
178     else if (a->vno > b->vno)
179         return -1;
180     else
181         return 1;
182 }
183
184 static int
185 etype_sort(const void *aa, const void *bb)
186 {
187     const krb5_keytab_entry *a, *b;
188
189     a = aa;
190     b = bb;
191
192     if (deref_entry_enctype(*a) == deref_entry_enctype(*b))
193         return 0;
194     else if (deref_entry_enctype(*a) > deref_entry_enctype(*b))
195         return -1;
196     else
197         return 1;
198 }
199
200 static int
201 ke_sort(const void *a, const void *b)
202 {
203     int ret;
204
205     ret = kvno_sort(a, b);
206     if (ret != 0)
207         return ret;
208     return etype_sort(a, b);
209 }
210
211 static int
212 full_sort(const void *a, const void *b)
213 {
214     int ret;
215
216     ret = princ_sort(a, b);
217     if (ret != 0)
218         return ret;
219     return ke_sort(a, b);
220 }
221
222 static afs_int32
223 slurp_keytab(krb5_context ctx, char *kt_path, krb5_keytab_entry **ents_out,
224              int *nents)
225 {
226     krb5_keytab kt = NULL;
227     krb5_keytab_entry entry, *ents = NULL;
228     krb5_kt_cursor cursor;
229     afs_int32 code;
230     int n = 0, i;
231
232     *ents_out = NULL;
233     *nents = 0;
234     memset(&cursor, 0, sizeof(cursor));
235
236     code = krb5_kt_resolve(ctx, kt_path, &kt);
237     if (code)
238         return code;
239
240     code = krb5_kt_start_seq_get(ctx, kt, &cursor);
241     if (code != 0)
242         goto out;
243     while ((code = krb5_kt_next_entry(ctx, kt, &entry, &cursor)) == 0) {
244         ++n;
245         krb5_free_keytab_entry_contents(ctx, &entry);
246     }
247     krb5_kt_end_seq_get(ctx, kt, &cursor);
248     if (code != 0 && code != KRB5_KT_END)
249         goto out;
250
251     ents = calloc(n, sizeof(*ents));
252     if (ents == NULL) {
253         code = ENOMEM;
254         goto out;
255     }
256     code = krb5_kt_start_seq_get(ctx, kt, &cursor);
257     if (code != 0)
258         goto out;
259     i = 0;
260     while ((code = krb5_kt_next_entry(ctx, kt, ents + i, &cursor)) == 0) {
261         if (i++ == n) {
262             /* Out of space; bail early */
263             fprintf(stderr, "Warning: keytab size changed during processing\n");
264             break;
265         }
266     }
267     krb5_kt_end_seq_get(ctx, kt, &cursor);
268     if (code != 0 && code != KRB5_KT_END)
269         goto out;
270
271     code = 0;
272     *nents = n;
273     *ents_out = ents;
274     ents = NULL;
275 out:
276     free(ents);
277     krb5_kt_close(ctx, kt);
278     return code;
279 }
280
281 /*
282  * Check for duplicate kvno/enctype pairs (across different principals).
283  *
284  * This is a fatal error, but emit a diagnostic for all instances before
285  * exiting.
286  *
287  * Requires the input array (ents) to be sorted by kvno and enctype.
288  */
289 static afs_int32
290 check_dups(struct afsconf_dir *dir, krb5_keytab_entry *ents, int nents)
291 {
292     int i, old_kvno = 0, old_etype = 0;
293     afs_int32 code = 0;
294
295     for (i = 0; i < nents; ++i) {
296         if (old_kvno == ents[i].vno &&
297             old_etype == deref_entry_enctype(ents[i])) {
298             fprintf(stderr, "Duplicate kvno/enctype %i/%i\n", old_kvno,
299                     old_etype);
300             code = AFSCONF_KEYINUSE;
301         }
302         old_kvno = ents[i].vno;
303         old_etype = deref_entry_enctype(ents[i]);
304     }
305     if (code)
306         fprintf(stderr, "FATAL: duplicate key identifiers found.\n");
307     return code;
308 }
309
310 /*
311  * Go through the list of keytab entries and write them to the KeyFileExt.
312  *
313  * If do_all is set, write all entries; otherwise, only write the highest
314  * kvno for each principal.
315  *
316  * Emit a diagnostic for kvno/enctype pairs which are already in the
317  * KeyFileExt (and thus cannot be added), but continue on.
318  *
319  * Requires the input array (ents) to be fully sorted, by principal, kvno,
320  * and enctype.
321  */
322 static afs_int32
323 convert_kt(struct afsconf_dir *dir, krb5_context ctx, krb5_keytab_entry *ents,
324            int nents, int do_all)
325 {
326     int i, n;
327     krb5_principal old_princ, wellknown_princ;
328     struct afsconf_typedKey *key = NULL;
329     afsconf_keyType type;
330     afs_int32 best_kvno = 0, code;
331
332     code = krb5_parse_name(ctx, "WELLKNOWN/ANONYMOUS@WELLKNOWN:ANONYMOUS",
333                            &wellknown_princ);
334     old_princ = wellknown_princ;
335     if (code)
336         goto out;
337     n = 0;
338     for (i = 0; i < nents; ++i) {
339         if (!krb5_principal_compare(ctx, old_princ, ents[i].principal)) {
340             best_kvno = ents[i].vno;
341         }
342         if (krb5_principal_compare(ctx, old_princ, ents[i].principal) &&
343             best_kvno != ents[i].vno && !do_all)
344             continue;
345         old_princ = ents[i].principal;
346         code = ktent_to_typedKey(ents[i], &key);
347         if (code)
348             goto out;
349         afsconf_typedKey_values(key, &type, NULL, NULL, NULL);
350         if (type == afsconf_rxkad) {
351             fprintf(stderr,
352                     "Cannot add single-DES keys to KeyFileExt, continuing\n");
353             afsconf_typedKey_put(&key);
354             continue;
355         }
356         code = afsconf_AddTypedKey(dir, key, 0);
357         if (code == AFSCONF_KEYINUSE) {
358             fprintf(stderr,
359                     "Key already exists for kvno %i enctype %i, continuing\n",
360                     ents[i].vno, deref_entry_enctype(ents[i]));
361             afsconf_typedKey_put(&key);
362             continue;
363         } else if (code) {
364             goto out;
365         }
366         n++;
367         afsconf_typedKey_put(&key);
368     }
369     code = 0;
370     printf("Wrote %i keys\n", n);
371 out:
372     if (key != NULL)
373         afsconf_typedKey_put(&key);
374     krb5_free_principal(ctx, wellknown_princ);
375     return code;
376 }
377
378 /*
379  * Liberate the Shepherds of the Trees from the forest that they might
380  * seek out the Entwives.
381  *
382  * Deallocate the storage for the keytab entries stored in the
383  * array ents (of length nents), and also deallocate the storage
384  * for the array itself.
385  *
386  * Safe to call with a NULL ents parameter.
387  */
388 static void
389 free_ents(krb5_context ctx, krb5_keytab_entry *ents, int nents)
390 {
391     int i;
392
393     if (ents == NULL)
394         return;
395     for(i = 0; i < nents; ++i)
396         krb5_free_keytab_entry_contents(ctx, ents + i);
397     free(ents);
398 }
399
400 static int
401 CommandProc(struct cmd_syndesc *as, void *arock)
402 {
403     char *kt_path = NULL;
404     krb5_context ctx = NULL;
405     krb5_keytab_entry *ents = NULL;
406     struct afsconf_dir *dir;
407     afs_int32 code;
408     int do_all, nents = -1;
409
410     code = krb5_init_context(&ctx);
411     if (code)
412         return -1;
413
414     dir = afsconf_Open(AFSDIR_SERVER_ETC_DIR);
415     if (dir == NULL) {
416         fprintf(stderr, "Failed to open server config directory\n");
417         code = -1;
418         goto out;
419     }
420
421     code = asprintf(&kt_path, "%s/%s", dir->name, AFSDIR_RXKAD_KEYTAB_FILE);
422     if (code < 0) {
423         kt_path = NULL;
424         code = ENOMEM;
425         goto out;
426     }
427     code = slurp_keytab(ctx, kt_path, &ents, &nents);
428     if (code) {
429         fprintf(stderr, "failed to read keytab\n");
430         goto out;
431     }
432
433     /* Sort the keytab by kvno and enctype. */
434     qsort(ents, nents, sizeof(*ents), &ke_sort);
435
436     /* Check for duplicates before sorting by principal. */
437     code = check_dups(dir, ents, nents);
438     if (code)
439         goto out;
440
441     qsort(ents, nents, sizeof(*ents), &full_sort);
442
443     do_all = cmd_OptionPresent(as, OPT_all);
444     code = convert_kt(dir, ctx, ents, nents, do_all);
445     if (code) {
446         fprintf(stderr, "Failed to convert keys, errno %i\n", errno);
447         goto out;
448     }
449
450 out:
451     free_ents(ctx, ents, nents);
452     free(kt_path);
453     krb5_free_context(ctx);
454     afsconf_Close(dir);
455     return code;
456 }
457
458 int
459 main(int argc, char *argv[])
460 {
461     struct cmd_syndesc *ts;
462     afs_int32 code;
463
464     ts = cmd_CreateSyntax(NULL, CommandProc, NULL, 0,
465                           "Convert cell keys for the 1.6->1.8 OpenAFS upgrade");
466     cmd_AddParmAtOffset(ts, OPT_all, "-all", CMD_FLAG, CMD_OPTIONAL,
467                 "convert old keys as well as the current keys");
468     code = cmd_Dispatch(argc, argv);
469     return (code != 0);
470 }