From a1f38973435b60c7f147abfca12b95c6a0a64406 Mon Sep 17 00:00:00 2001 From: Greg Hudson Date: Wed, 17 Jun 2020 20:48:38 -0400 Subject: [PATCH] Add three kvno options from Heimdal kgetcred Add the flags --cached-only and --no-store, which pass the corresponding options to krb5_get_credentials(). Add the option --out-cache to write the retrieved credentials to a specified output cache. Add a Python test script for kvno command-line options, including tests for the new options. ticket: 8917 (new) --- doc/user/user_commands/kvno.rst | 13 ++++ src/clients/kvno/Makefile.in | 3 + src/clients/kvno/kvno.c | 115 +++++++++++++++++++++++--------- src/clients/kvno/t_kvno.py | 75 +++++++++++++++++++++ src/man/kvno.man | 13 ++++ 5 files changed, 187 insertions(+), 32 deletions(-) create mode 100644 src/clients/kvno/t_kvno.py diff --git a/doc/user/user_commands/kvno.rst b/doc/user/user_commands/kvno.rst index 3892f0ca5..718313576 100644 --- a/doc/user/user_commands/kvno.rst +++ b/doc/user/user_commands/kvno.rst @@ -74,6 +74,19 @@ OPTIONS client principal with the X.509 certificate in *cert_file*. The certificate file must be in PEM format. +**--cached-only** + Only retrieve credentials already present in the cache, not from + the KDC. + +**--no-store** + Do not store retrieved credentials in the cache. If + **--out-cache** is also specified, credentials will still be + stored into the output credential cache. + +**--out-cache** *ccache* + Initialize *ccache* and store all retrieved credentials into it. + Do not store acquired credentials in the input cache. + **--u2u** *ccache* Requests a user-to-user ticket. *ccache* must contain a local krbtgt ticket for the server principal. The reported version diff --git a/src/clients/kvno/Makefile.in b/src/clients/kvno/Makefile.in index 1c3f79392..5ba877271 100644 --- a/src/clients/kvno/Makefile.in +++ b/src/clients/kvno/Makefile.in @@ -26,6 +26,9 @@ kvno: kvno.o $(KRB5_BASE_DEPLIBS) ##WIN32## link $(EXE_LINKOPTS) /out:$@ $** ##WIN32## $(_VC_MANIFEST_EMBED_EXE) +check-pytests: kvno + $(RUNPYTEST) $(srcdir)/t_kvno.py $(PYTESTFLAGS) + clean-unix:: $(RM) kvno.o kvno diff --git a/src/clients/kvno/kvno.c b/src/clients/kvno/kvno.c index 2472c0cfe..9d85864f6 100644 --- a/src/clients/kvno/kvno.c +++ b/src/clients/kvno/kvno.c @@ -44,14 +44,17 @@ xusage() fprintf(stderr, _("usage: %s [-C] [-u] [-c ccache] [-e etype]\n"), prog); fprintf(stderr, _("\t[-k keytab] [-S sname] [{-I | -U} for_user | " "[-F cert_file] [-P]]\n")); - fprintf(stderr, _("\t[--u2u ccache] service1 service2 ...\n")); + fprintf(stderr, _("\t[--cached-only] [--no-store] [--out-cache ccache] " + "[--u2u ccache]\n")); + fprintf(stderr, _("\tservice1 service2 ...\n")); exit(1); } static void do_v5_kvno(int argc, char *argv[], char *ccachestr, char *etypestr, - char *keytab_name, char *sname, int canon, int unknown, - char *for_user, int for_user_enterprise, - char *for_user_cert_file, int proxy, + char *keytab_name, char *sname, int cached_only, + int canon, int no_store, int unknown, char *for_user, + int for_user_enterprise, char *for_user_cert_file, + int proxy, const char *out_ccname, const char *u2u_ccname); #include @@ -61,18 +64,21 @@ static void extended_com_err_fn(const char *myprog, errcode_t code, int main(int argc, char *argv[]) { - enum { OPTION_U2U = 256 }; - struct option lopts[] = { - { "u2u", 1, NULL, OPTION_U2U }, - { NULL, 0, NULL, 0 } - }; + enum { OPTION_U2U = 256, OPTION_OUT_CACHE = 257 }; const char *shopts = "uCc:e:hk:qPS:I:U:F:"; int option; char *etypestr = NULL, *ccachestr = NULL, *keytab_name = NULL; char *sname = NULL, *for_user = NULL, *u2u_ccname = NULL; - char *for_user_cert_file = NULL; + char *for_user_cert_file = NULL, *out_ccname = NULL; int canon = 0, unknown = 0, proxy = 0, for_user_enterprise = 0; - int impersonate = 0; + int impersonate = 0, cached_only = 0, no_store = 0; + struct option lopts[] = { + { "cached-only", 0, &cached_only, 1 }, + { "no-store", 0, &no_store, 1 }, + { "out-cache", 1, NULL, OPTION_OUT_CACHE }, + { "u2u", 1, NULL, OPTION_U2U }, + { NULL, 0, NULL, 0 } + }; setlocale(LC_ALL, ""); set_com_err_hook(extended_com_err_fn); @@ -135,6 +141,12 @@ main(int argc, char *argv[]) case OPTION_U2U: u2u_ccname = optarg; break; + case OPTION_OUT_CACHE: + out_ccname = optarg; + break; + case 0: + /* If this option set a flag, do nothing else now. */ + break; default: xusage(); break; @@ -159,8 +171,9 @@ main(int argc, char *argv[]) xusage(); do_v5_kvno(argc - optind, argv + optind, ccachestr, etypestr, keytab_name, - sname, canon, unknown, for_user, for_user_enterprise, - for_user_cert_file, proxy, u2u_ccname); + sname, cached_only, canon, no_store, unknown, for_user, + for_user_enterprise, for_user_cert_file, proxy, out_ccname, + u2u_ccname); return 0; } @@ -274,14 +287,16 @@ static krb5_error_code kvno(const char *name, krb5_ccache ccache, krb5_principal me, krb5_enctype etype, krb5_keytab keytab, const char *sname, krb5_flags options, int unknown, krb5_principal for_user_princ, - krb5_data *for_user_cert, int proxy, krb5_data *u2u_ticket) + krb5_data *for_user_cert, int proxy, krb5_data *u2u_ticket, + krb5_creds **creds_out) { krb5_error_code ret; krb5_principal server = NULL; krb5_ticket *ticket = NULL; - krb5_creds in_creds, *out_creds = NULL; + krb5_creds in_creds, *creds = NULL; char *princ = NULL; + *creds_out = NULL; memset(&in_creds, 0, sizeof(in_creds)); if (sname != NULL) { @@ -321,13 +336,12 @@ kvno(const char *name, krb5_ccache ccache, krb5_principal me, in_creds.client = for_user_princ; in_creds.server = me; ret = krb5_get_credentials_for_user(context, options, ccache, - &in_creds, for_user_cert, - &out_creds); + &in_creds, for_user_cert, &creds); } else { in_creds.client = me; in_creds.server = server; ret = krb5_get_credentials(context, options, ccache, &in_creds, - &out_creds); + &creds); } if (ret) { @@ -336,7 +350,7 @@ kvno(const char *name, krb5_ccache ccache, krb5_principal me, } /* We need a native ticket. */ - ret = krb5_decode_ticket(&out_creds->ticket, &ticket); + ret = krb5_decode_ticket(&creds->ticket, &ticket); if (ret) { com_err(prog, ret, _("while decoding ticket for %s"), princ); goto cleanup; @@ -362,15 +376,15 @@ kvno(const char *name, krb5_ccache ccache, krb5_principal me, } if (proxy) { - in_creds.client = out_creds->client; - out_creds->client = NULL; - krb5_free_creds(context, out_creds); - out_creds = NULL; + in_creds.client = creds->client; + creds->client = NULL; + krb5_free_creds(context, creds); + creds = NULL; in_creds.server = server; ret = krb5_get_credentials_for_proxy(context, KRB5_GC_CANONICALIZE, ccache, &in_creds, ticket, - &out_creds); + &creds); krb5_free_principal(context, in_creds.client); if (ret) { com_err(prog, ret, _("%s: constrained delegation failed"), @@ -379,10 +393,13 @@ kvno(const char *name, krb5_ccache ccache, krb5_principal me, } } + *creds_out = creds; + creds = NULL; + cleanup: krb5_free_principal(context, server); krb5_free_ticket(context, ticket); - krb5_free_creds(context, out_creds); + krb5_free_creds(context, creds); krb5_free_unparsed_name(context, princ); return ret; } @@ -428,19 +445,28 @@ cleanup: static void do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr, - char *keytab_name, char *sname, int canon, int unknown, - char *for_user, int for_user_enterprise, - char *for_user_cert_file, int proxy, const char *u2u_ccname) + char *keytab_name, char *sname, int cached_only, int canon, + int no_store, int unknown, char *for_user, int for_user_enterprise, + char *for_user_cert_file, int proxy, const char *out_ccname, + const char *u2u_ccname) { krb5_error_code ret; - int i, errors, flags; + int i, errors, flags, initialized = 0; krb5_enctype etype; - krb5_ccache ccache; + krb5_ccache ccache, out_ccache = NULL; krb5_principal me; krb5_keytab keytab = NULL; krb5_principal for_user_princ = NULL; - krb5_flags options = canon ? KRB5_GC_CANONICALIZE : 0; + krb5_flags options = 0; krb5_data cert_data = empty_data(), *user_cert = NULL, *u2u_ticket = NULL; + krb5_creds *creds; + + if (canon) + options |= KRB5_GC_CANONICALIZE; + if (cached_only) + options |= KRB5_GC_CACHED; + if (no_store || out_ccname != NULL) + options |= KRB5_GC_NO_STORE; ret = krb5_init_context(&context); if (ret) { @@ -467,6 +493,14 @@ do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr, exit(1); } + if (out_ccname != NULL) { + ret = krb5_cc_resolve(context, out_ccname, &out_ccache); + if (ret) { + com_err(prog, ret, _("while resolving output ccache")); + exit(1); + } + } + if (keytab_name != NULL) { ret = krb5_kt_resolve(context, keytab_name, &keytab); if (ret) { @@ -513,8 +547,25 @@ do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr, errors = 0; for (i = 0; i < count; i++) { if (kvno(names[i], ccache, me, etype, keytab, sname, options, unknown, - for_user_princ, user_cert, proxy, u2u_ticket) != 0) + for_user_princ, user_cert, proxy, u2u_ticket, &creds) != 0) { errors++; + } else if (out_ccache != NULL) { + if (!initialized) { + ret = krb5_cc_initialize(context, out_ccache, creds->client); + if (ret) { + com_err(prog, ret, _("while initializing output ccache")); + exit(1); + } + initialized = 1; + } + ret = krb5_cc_store_cred(context, out_ccache, creds); + if (ret) { + com_err(prog, ret, _("while storing creds in output ccache")); + exit(1); + } + } + + krb5_free_creds(context, creds); } if (keytab != NULL) diff --git a/src/clients/kvno/t_kvno.py b/src/clients/kvno/t_kvno.py new file mode 100644 index 000000000..e98b90e8a --- /dev/null +++ b/src/clients/kvno/t_kvno.py @@ -0,0 +1,75 @@ +from k5test import * + +realm = K5Realm() + +def check_cache(ccache, expected_services): + # Fetch the klist output and skip past the header. + lines = realm.run([klist, '-c', ccache]).splitlines() + lines = lines[4:] + + # For each line not beginning with an indent, match against the + # expected service principals. + svcs = {x: True for x in expected_services} + for l in lines: + if not l.startswith('\t'): + svcprinc = l.split()[4] + if svcprinc in svcs: + del svcs[svcprinc] + else: + fail('unexpected service princ ' + svcprinc) + + if svcs: + fail('services not found in klist output: ' + ' '.join(svcs.keys())) + + +mark('no options') +realm.run([kvno, realm.user_princ], expected_msg='user@KRBTEST.COM: kvno = 1') +check_cache(realm.ccache, [realm.krbtgt_princ, realm.user_princ]) + +mark('-e') +msgs = ('etypes requested in TGS request: camellia128-cts', + '/KDC has no support for encryption type') +realm.run([kvno, '-e', 'camellia128-cts', realm.host_princ], + expected_code=1, expected_trace=msgs) + +mark('--cached-only') +realm.run([kvno, '--cached-only', realm.user_princ], expected_msg='kvno = 1') +realm.run([kvno, '--cached-only', realm.host_princ], + expected_code=1, expected_msg='Matching credential not found') +check_cache(realm.ccache, [realm.krbtgt_princ, realm.user_princ]) + +mark('--no-store') +realm.run([kvno, '--no-store', realm.host_princ], expected_msg='kvno = 1') +check_cache(realm.ccache, [realm.krbtgt_princ, realm.user_princ]) + +mark('--out-cache') # and multiple services +out_ccache = os.path.join(realm.testdir, 'ccache.out') +realm.run([kvno, '--out-cache', out_ccache, + realm.host_princ, realm.admin_princ]) +check_cache(realm.ccache, [realm.krbtgt_princ, realm.user_princ]) +check_cache(out_ccache, [realm.host_princ, realm.admin_princ]) + +mark('--out-cache --cached-only') # tests out-cache overwriting, and -q +realm.run([kvno, '--out-cache', out_ccache, '--cached-only', realm.host_princ], + expected_code=1, expected_msg='Matching credential not found') +out = realm.run([kvno, '-q', '--out-cache', out_ccache, '--cached-only', + realm.user_princ]) +if out: + fail('unexpected kvno output with -q') +check_cache(out_ccache, [realm.user_princ]) + +mark('-U') # and -c +svc_ccache = os.path.join(realm.testdir, 'ccache.svc') +realm.run([kinit, '-k', '-c', svc_ccache, realm.host_princ]) +realm.run([kvno, '-c', svc_ccache, '-U', 'user', realm.host_princ]) +realm.run([klist, '-c', svc_ccache], expected_msg='for client user@') +realm.run([kvno, '-c', svc_ccache, '-U', 'user', '--out-cache', out_ccache, + realm.host_princ]) +out = realm.run([klist, '-c', out_ccache]) +if ('Default principal: user@KRBTEST.COM' not in out): + fail('wrong default principal in klist output') + +# More S4U options are tested in tests/gssapi/t_s4u.py. +# --u2u is tested in tests/t_u2u.py. + +success('kvno tests') diff --git a/src/man/kvno.man b/src/man/kvno.man index 005a2ec97..b9f6739eb 100644 --- a/src/man/kvno.man +++ b/src/man/kvno.man @@ -95,6 +95,19 @@ Specifies that protocol transition is to be used, identifying the client principal with the X.509 certificate in \fIcert_file\fP\&. The certificate file must be in PEM format. .TP +\fB\-\-cached\-only\fP +Only retrieve credentials already present in the cache, not from +the KDC. +.TP +\fB\-\-no\-store\fP +Do not store retrieved credentials in the cache. If +\fB\-\-out\-cache\fP is also specified, credentials will still be +stored into the output credential cache. +.TP +\fB\-\-out\-cache\fP \fIccache\fP +Initialize \fIccache\fP and store all retrieved credentials into it. +Do not store acquired credentials in the input cache. +.TP \fB\-\-u2u\fP \fIccache\fP Requests a user\-to\-user ticket. \fIccache\fP must contain a local krbtgt ticket for the server principal. The reported version