diff --git a/admin/add.c b/admin/add.c index 13580b9bb..5f1920ff8 100644 --- a/admin/add.c +++ b/admin/add.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997-2005 Kungliga Tekniska Högskolan + * Copyright (c) 1997-2022 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * @@ -32,6 +32,8 @@ */ #include "ktutil_locl.h" +#include <heimbase.h> +#include <base64.h> RCSID("$Id$"); @@ -153,6 +155,178 @@ kt_add(struct add_options *opt, int argc, char **argv) krb5_warn(context, ret, "add"); out: krb5_kt_free_entry(context, &entry); - krb5_kt_close(context, keytab); + if (ret == 0) { + ret = krb5_kt_close(context, keytab); + if (ret) + krb5_warn(context, ret, "Could not write the keytab"); + } else { + krb5_kt_close(context, keytab); + } + return ret != 0; +} + +/* We might be reading from a pipe, so we can't use rk_undumpdata() */ +static char * +read_file(FILE *f) +{ + size_t alloced; + size_t len = 0; + size_t bytes; + char *res, *end, *p; + + if ((res = malloc(1024)) == NULL) + err(1, "Out of memory"); + alloced = 1024; + + end = res + alloced; + p = res; + do { + if (p == end) { + char *tmp; + + if ((tmp = realloc(res, alloced + (alloced > 1))) == NULL) + err(1, "Out of memory"); + alloced += alloced > 1; + p = tmp + (p - res); + res = tmp; + end = res + alloced; + } + bytes = fread(p, 1, end - p, f); + len += bytes; + p += bytes; + } while (bytes && !feof(f) && !ferror(f)); + + if (ferror(f)) + errx(1, "Could not read all input"); + if (p == end) { + char *tmp; + + if ((tmp = strndup(res, len)) == NULL) + err(1, "Out of memory"); + free(res); + res = tmp; + } + if (strlen(res) != len) + err(1, "Embedded NULs in input!"); + return res; +} + +static void +json2keytab_entry(heim_dict_t d, krb5_keytab kt, size_t idx) +{ + krb5_keytab_entry e; + krb5_error_code ret; + heim_object_t v; + uint64_t u; + int64_t i; + char *buf = NULL; + + memset(&e, 0, sizeof(e)); + + v = heim_dict_get_value(d, HSTR("timestamp")); + if (heim_get_tid(v) != HEIM_TID_NUMBER) + goto bad; + u = heim_number_get_long(v); + e.timestamp = u; + if (u != (uint64_t)e.timestamp) + goto bad; + + v = heim_dict_get_value(d, HSTR("kvno")); + if (heim_get_tid(v) != HEIM_TID_NUMBER) + goto bad; + i = heim_number_get_long(v); + e.vno = i; + if (i != (int64_t)e.vno) + goto bad; + + v = heim_dict_get_value(d, HSTR("enctype_number")); + if (heim_get_tid(v) != HEIM_TID_NUMBER) + goto bad; + i = heim_number_get_long(v); + e.keyblock.keytype = i; + if (i != (int64_t)e.keyblock.keytype) + goto bad; + + v = heim_dict_get_value(d, HSTR("key")); + if (heim_get_tid(v) != HEIM_TID_STRING) + goto bad; + { + const char *s = heim_string_get_utf8(v); + int declen; + + if ((buf = malloc(strlen(s))) == NULL) + err(1, "Out of memory"); + declen = rk_base64_decode(s, buf); + if (declen < 0) + goto bad; + e.keyblock.keyvalue.data = buf; + e.keyblock.keyvalue.length = declen; + } + + v = heim_dict_get_value(d, HSTR("principal")); + if (heim_get_tid(v) != HEIM_TID_STRING) + goto bad; + ret = krb5_parse_name(context, heim_string_get_utf8(v), &e.principal); + if (ret == 0) + ret = krb5_kt_add_entry(context, kt, &e); + + /* For now, ignore aliases; besides, they're never set anywhere in-tree */ + + if (ret) + krb5_warn(context, ret, + "Could not parse or write keytab entry %lu", + (unsigned long)idx); +bad: + krb5_free_principal(context, e.principal); +} + +int +kt_import(void *opt, int argc, char **argv) +{ + krb5_error_code ret; + krb5_keytab kt; + heim_object_t o; + heim_error_t json_err = NULL; + heim_json_flags_t flags = HEIM_JSON_F_STRICT; + FILE *f = argc == 0 ? stdin : fopen(argv[0], "r"); + size_t alen, i; + char *json; + + if (f == NULL) + err(1, "Could not open file %s", argv[0]); + + json = read_file(f); + o = heim_json_create(json, 10, flags, &json_err); + free(json); + if (o == NULL) { + if (json_err != NULL) { + o = heim_error_copy_string(json_err); + if (o) + errx(1, "Could not parse JSON: %s", heim_string_get_utf8(o)); + } + errx(1, "Could not parse JSON"); + } + + if (heim_get_tid(o) != HEIM_TID_ARRAY) + errx(1, "JSON text must be an array"); + + alen = heim_array_get_length(o); + if (alen == 0) + errx(1, "Empty JSON array; not overwriting keytab"); + + if ((kt = ktutil_open_keytab()) == NULL) + err(1, "Could not open keytab"); + + for (i = 0; i < alen; i++) { + heim_object_t e = heim_array_get_value(o, i); + + if (heim_get_tid(e) != HEIM_TID_DICT) + warnx("Element %ld of JSON text array is not an object", (long)i); + else + json2keytab_entry(heim_array_get_value(o, i), kt, i); + } + ret = krb5_kt_close(context, kt); + if (ret) + krb5_warn(context, ret, "Could not write the keytab"); return ret != 0; } diff --git a/admin/copy.c b/admin/copy.c index 7b50de1c3..8acd6e48e 100644 --- a/admin/copy.c +++ b/admin/copy.c @@ -47,7 +47,7 @@ compare_keyblock(const krb5_keyblock *a, const krb5_keyblock *b) } int -kt_copy (void *opt, int argc, char **argv) +kt_copy (struct copy_options *opt, int argc, char **argv) { krb5_error_code ret; krb5_keytab src_keytab, dst_keytab; @@ -106,11 +106,18 @@ kt_copy (void *opt, int argc, char **argv) "already exists for %s, keytype %s, kvno %d", name_str, etype_str, entry.vno); } - krb5_kt_free_entry(context, &dummy); - krb5_kt_free_entry (context, &entry); - free(name_str); - free(etype_str); - continue; + if (!opt->copy_duplicates_flag) { + krb5_kt_free_entry(context, &dummy); + krb5_kt_free_entry (context, &entry); + free(name_str); + free(etype_str); + continue; + } + /* + * Because we can end up trying all keys that match the enctype, + * copying entries with duplicate principal, vno, and enctype, but + * different keys, can be useful. + */ } else if(ret != KRB5_KT_NOTFOUND) { krb5_warn (context, ret, "%s: fetching %s/%s/%u", to, name_str, etype_str, entry.vno); diff --git a/admin/get.c b/admin/get.c index f56e50f43..ecd6f6a16 100644 --- a/admin/get.c +++ b/admin/get.c @@ -197,23 +197,27 @@ kt_get(struct get_options *opt, int argc, char **argv) break; } - ret = kadm5_create_principal(kadm_handle, &princ, mask, "thisIs_aUseless.password123"); - if(ret == 0) - created = 1; - else if(ret != KADM5_DUP) { - krb5_warn(context, ret, "kadm5_create_principal(%s)", argv[a]); - krb5_free_principal(context, princ_ent); - failed++; - continue; - } - ret = kadm5_randkey_principal_3(kadm_handle, princ_ent, keep, nks, ks, - &keys, &n_keys); - if (ret) { - krb5_warn(context, ret, "kadm5_randkey_principal(%s)", argv[a]); - krb5_free_principal(context, princ_ent); - failed++; - continue; - } + if (opt->create_flag) { + ret = kadm5_create_principal(kadm_handle, &princ, mask, "thisIs_aUseless.password123"); + if(ret == 0) + created = 1; + else if(ret != KADM5_DUP) { + krb5_warn(context, ret, "kadm5_create_principal(%s)", argv[a]); + krb5_free_principal(context, princ_ent); + failed++; + continue; + } + } + if (opt->change_keys_flag) { + ret = kadm5_randkey_principal_3(kadm_handle, princ_ent, keep, nks, ks, + &keys, &n_keys); + if (ret) { + krb5_warn(context, ret, "kadm5_randkey_principal(%s)", argv[a]); + krb5_free_principal(context, princ_ent); + failed++; + continue; + } + } ret = kadm5_get_principal(kadm_handle, princ_ent, &princ, KADM5_PRINCIPAL | KADM5_KVNO | KADM5_ATTRIBUTES); diff --git a/admin/ktutil-commands.in b/admin/ktutil-commands.in index 51c8df0be..a85eb5c57 100644 --- a/admin/ktutil-commands.in +++ b/admin/ktutil-commands.in @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004 Kungliga Tekniska Högskolan + * Copyright (c) 2004-2022 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * @@ -151,11 +151,17 @@ command = { } command = { name = "copy" + name = "merge" function = "kt_copy" + option = { + long = "copy-duplicates" + type = "flag" + help = "copy entries for the same principal and kvno, but different keys" + } argument = "source destination" min_args = "2" max_args = "2" - help = "Copies one keytab to another." + help = "Merges one keytab into another." } command = { name = "get" @@ -166,6 +172,16 @@ command = { help = "admin principal" argument = "principal" } + option = { + long = "create" + type = "-flag" + help = "do not create the principal" + } + option = { + long = "change-keys" + type = "-flag" + help = "do not change the principal's keys" + } option = { long = "enctypes" short = "e" @@ -214,6 +230,14 @@ command = { argument = "principal..." help = "Change keys for specified principals, and add them to the keytab." } +command = { + name = "import" + function = "kt_import" + help = "Imports a keytab from JSON output of ktutil list --json --keys." + min_args = "0" + max_args = "1" + argument = "JSON-FILE" +} command = { name = "list" option = { diff --git a/admin/ktutil.1 b/admin/ktutil.1 index 1a78cc868..0036edcbd 100644 --- a/admin/ktutil.1 +++ b/admin/ktutil.1 @@ -82,29 +82,67 @@ server for the realm of a keytab entry. Otherwise it will use the values specified by the options. .Pp If no principals are given, all the ones in the keytab are updated. -.It Nm copy Ar keytab-src Ar keytab-dest +.It Nm copy Oo Fl Fl copy-duplicates Oc Ar keytab-src Ar keytab-dest Copies all the entries from .Ar keytab-src to .Ar keytab-dest . +Because entries already in +.Ar keytab-dest +are kept, this command functions to merge keytabs. +Entries for the same principal, key version number, and +encryption type in the +.Ar keytab-src +that are also in the +.Ar keytab-dest +will not be copied to the +.Ar keytab-dest +unless the +.Fl Fl copy-duplicates +option is given. .It Nm get Oo Fl p Ar admin principal Oc \ Oo Fl Fl principal= Ns Ar admin principal Oc Oo Fl e Ar enctype Oc \ +Oo Fl Fl no-create Oc \ +Oo Fl Fl no-change-keys Oc \ Oo Fl Fl keepold | Fl Fl keepallold | Fl Fl pruneall Oc \ Oo Fl Fl enctypes= Ns Ar enctype Oc Oo Fl r Ar realm Oc \ Oo Fl Fl realm= Ns Ar realm Oc Oo Fl a Ar admin server Oc \ Oo Fl Fl admin-server= Ns Ar admin server Oc Oo Fl s Ar server port Oc \ Oo Fl Fl server-port= Ns Ar server port Oc Ar principal ... +.Pp For each .Ar principal , -generate a new key for it (creating it if it doesn't already exist), -and put that key in the keytab. +get a the principal's keys from the KDC via the kadmin protocol, +creating the principal if it doesn't exist (unless +.Fl Fl no-create +is given), and changing its keys to new random keys (unless +.Fl Fl no-change-keys +is given). .Pp If no .Ar realm is specified, the realm to operate on is taken from the first principal. +.It Nm import Oo JSON-FILE Oc +Read an array of keytab entries in a JSON file and copy them to +the keytab. +Use the +.Nm list +command with its +.Fl Fl json +option +and +.Fl Fl keys +option to export a keytab. .It Nm list Oo Fl Fl keys Oc Op Fl Fl timestamp Oo Op Fl Fl json Oc List the keys stored in the keytab. +Use the +.Fl Fl json +and +.Fl Fl keys +options to export a keytab as JSON for importing with the +.Nm import +command. .It Nm remove Oo Fl p Ar principal Oc Oo Fl Fl principal= Ns Ar principal Oc \ Oo Fl V kvno Oc Oo Fl Fl kvno= Ns Ar kvno Oc Oo Fl e enctype Oc \ Oo Fl Fl enctype= Ns Ar enctype Oc @@ -113,8 +151,14 @@ Removes the specified key or keys. Not specifying a removes keys with any version number. Not specifying an .Ar enctype removes keys of any type. +.It Nm merge Oo Fl Fl copy-duplicates Oc Ar keytab-src Ar keytab-dest +An alias for the +.Nm copy +command. .It Nm rename Ar from-principal Ar to-principal -Renames all entries in the keytab that match the +Renames all entries for the +.Ar from-principal +in the keytab .Ar from-principal to .Ar to-principal . @@ -123,6 +167,12 @@ Removes all old versions of a key for which there is a newer version that is at least .Ar age (default one week) old. +Note that this does not update the KDC database. +The +.Xr kadmin 1 +command has a +.Nm prune +command that can do this on the KDC side. .El .Sh SEE ALSO .Xr kadmin 1