Import Upstream version 1.8.5
[hcoop/debian/openafs.git] / src / aklog / akeyconvert.c
CommitLineData
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
101enum 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 */
111static afs_int32
112ktent_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
138static int
139princ_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
161out:
162 krb5_free_unparsed_name(ctx, name1);
163 krb5_free_unparsed_name(ctx, name2);
164 krb5_free_context(ctx);
165 return ret;
166}
167
168static int
169kvno_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
184static int
185etype_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
200static int
201ke_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
211static int
212full_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
222static afs_int32
223slurp_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;
275out:
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 */
289static afs_int32
290check_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 */
322static afs_int32
323convert_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);
371out:
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 */
388static void
389free_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
400static int
401CommandProc(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
450out:
451 free_ents(ctx, ents, nents);
452 free(kt_path);
453 krb5_free_context(ctx);
454 afsconf_Close(dir);
455 return code;
456}
457
458int
459main(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}