Commit | Line | Data |
---|---|---|
805e021f CE |
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 | } |