From 00bc84235a5c27671e49e3c0303e59df0ea3df1a Mon Sep 17 00:00:00 2001 From: Chaskiel Grundman Date: Sat, 9 Feb 2013 12:42:20 -0500 Subject: [PATCH] New optional rxkad functionality for decypting krb5 tokens An additional, optional mechanism for decrypting krb5-format tokens is provided that uses the krb5 api with a key from a keytab instead of using libdes and the AFS KeyFile The AIX compat stub for krb5_c_decrypt is contributed by Andrew Deason. Change-Id: I97c08122c60482b84d602d6fa6482f1d5deef142 --- src/cf/kerberos.m4 | 53 +----- src/rxkad/Makefile.in | 5 +- src/rxkad/rxkad_prototypes.h | 3 + src/rxkad/ticket5_keytab.c | 333 +++++++++++++++++++++++++++++++++++ src/shlibafsrpc/mapfile | 1 + 5 files changed, 349 insertions(+), 46 deletions(-) create mode 100644 src/rxkad/ticket5_keytab.c diff --git a/src/cf/kerberos.m4 b/src/cf/kerberos.m4 index 8ddffe0ea..6decb385b 100644 --- a/src/cf/kerberos.m4 +++ b/src/cf/kerberos.m4 @@ -60,7 +60,7 @@ if test X$conf_krb5 = XYES; then CPPFLAGS="$CPPFLAGS $KRB5CFLAGS" save_LIBS="$LIBS" LIBS="$LIBS $KRB5LIBS" - AC_CHECK_FUNCS([add_to_error_table add_error_table krb5_princ_size krb5_principal_get_comp_string encode_krb5_enc_tkt_part encode_krb5_ticket krb5_c_encrypt krb5_c_encrypt_length krb5_cc_register krb5_decode_ticket krb5_get_prompt_types krb5_allow_weak_crypto krb5_enctype_enable]) + AC_CHECK_FUNCS([add_to_error_table add_error_table krb5_princ_size krb5_principal_get_comp_string encode_krb5_enc_tkt_part encode_krb5_ticket krb5_c_encrypt krb5_c_encrypt_length krb5_c_decrypt krb5_cc_register krb5_decode_ticket krb5_get_prompt_types krb5_allow_weak_crypto krb5_enctype_enable krb5_crypto_init krb5_encrypt_tkt_part krb5_decrypt_tkt_part]) AC_CHECK_FUNCS([krb5_524_convert_creds], , [AC_CHECK_FUNCS([krb524_convert_creds_kdc], , [AC_CHECK_LIB([krb524], [krb524_convert_creds_kdc], @@ -71,54 +71,17 @@ if test X$conf_krb5 = XYES; then AC_CHECK_HEADERS([kerberosIV/krb.h]) AC_CHECK_HEADERS([kerberosV/heim_err.h]) -AC_MSG_CHECKING(for krb5_creds.keyblock existence) -AC_CACHE_VAL(ac_cv_krb5_creds_keyblock_exists, -[ -AC_TRY_COMPILE( -[#include ], -[krb5_creds _c; -printf("%x\n", _c.keyblock);], -ac_cv_krb5_creds_keyblock_exists=yes, -ac_cv_krb5_creds_keyblock_exists=no)]) -AC_MSG_RESULT($ac_cv_krb5_creds_keyblock_exists) - -AC_MSG_CHECKING(for krb5_creds.session existence) -AC_CACHE_VAL(ac_cv_krb5_creds_session_exists, -[ -AC_TRY_COMPILE( -[#include ], -[krb5_creds _c; -printf("%x\n", _c.session);], -ac_cv_krb5_creds_session_exists=yes, -ac_cv_krb5_creds_session_exists=no)]) -AC_MSG_RESULT($ac_cv_krb5_creds_session_exists) -AC_MSG_CHECKING(for krb5_prompt.type existence) -AC_CACHE_VAL(ac_cv_krb5_prompt_type_exists, -[ -AC_TRY_COMPILE( -[#include ], -[krb5_prompt _p; -printf("%x\n", _p.type);], -ac_cv_krb5_prompt_type_exists=yes, -ac_cv_krb5_prompt_type_exists=no)]) -AC_MSG_RESULT($ac_cv_krb5_prompt_type_exists) - -if test "x$ac_cv_krb5_creds_keyblock_exists" = "xyes"; then - AC_DEFINE(HAVE_KRB5_CREDS_KEYBLOCK, 1, [define if krb5_creds has keyblock]) -fi -if test "x$ac_cv_krb5_creds_session_exists" = "xyes"; then - AC_DEFINE(HAVE_KRB5_CREDS_SESSION, 1, [define if krb5_creds has session]) -fi -if test "x$ac_cv_krb5_prompt_type_exists" = "xyes"; then - AC_DEFINE(HAVE_KRB5_PROMPT_TYPE, 1, [define if krb5_prompt has type]) -fi - -dnl AC_CHECK_MEMBERS([krb5_creds.keyblock, krb5_creds.session],,, [#include ]) + AC_CHECK_MEMBERS([krb5_creds.keyblock, krb5_creds.keyblock.enctype, + krb5_creds.session, krb5_keytab_entry.key, + krb5_keytab_entry.keyblock, krb5_keyblock.enctype, + krb5_keyblock.keytype, krb5_prompt.type], , , + [#include ]) + AC_CHECK_DECLS([krb5_free_keytab_entry_contents, krb5_kt_free_entry, + KRB5_KU_TICKET], [], [], [#include ]) CPPFLAGS="$save_CPPFLAGS" LIBS="$save_LIBS" fi - if test "x$ac_cv_krb5_cc_register_exists" = "xyes"; then AC_DEFINE(HAVE_KRB5_CC_REGISTER, 1, [define if krb5_cc_register exists]) fi diff --git a/src/rxkad/Makefile.in b/src/rxkad/Makefile.in index 928bca71d..cdb4ce3b3 100644 --- a/src/rxkad/Makefile.in +++ b/src/rxkad/Makefile.in @@ -17,7 +17,7 @@ INCLS=${TOP_INCDIR}/rx/rx.h ${TOP_INCDIR}/rx/xdr.h \ OBJS=rxkad_client.o rxkad_server.o rxkad_common.o rxkad_errs.o \ fcrypt.o crypt_conn.o ticket.o ticket5.o crc.o \ - md4.o md5.o + md4.o md5.o @MAKE_KRB5@ ticket5_keytab.o fc_test_OBJS=fc_test.o @@ -94,6 +94,9 @@ fcrypt.o: ${srcdir}/domestic/fcrypt.c fcrypt.h sboxes.h rxkad.h rxkad_prototypes crypt_conn.o: ${srcdir}/domestic/crypt_conn.c fcrypt.h private_data.h ${INCLS} ${CCOBJ} ${CFLAGS} -c ${srcdir}/domestic/crypt_conn.c +ticket5_keytab.o: ticket5_keytab.c ${INCLS} + ${CCOBJ} ${CFLAGS} -c ${srcdir}/ticket5_keytab.c @KRB5CFLAGS@ + tcrypt.o: ${srcdir}/domestic/tcrypt.c AFS_component_version_number.o ${CCOBJ} ${CFLAGS} -c ${srcdir}/domestic/fcrypt.c diff --git a/src/rxkad/rxkad_prototypes.h b/src/rxkad/rxkad_prototypes.h index a297d3bee..070ba13ec 100644 --- a/src/rxkad/rxkad_prototypes.h +++ b/src/rxkad/rxkad_prototypes.h @@ -153,5 +153,8 @@ extern int tkt_DecodeTicket5(char *ticket, afs_int32 ticket_len, afs_int32 * host, afs_int32 * start, afs_int32 * end, afs_int32 disableDotCheck, rxkad_alt_decrypt_func alt_decrypt); +/* ticket5_keytab.c */ +extern int rxkad_InitKeytabDecrypt(const char *); +extern int rxkad_BindKeytabDecrypt(struct rx_securityClass *); #endif diff --git a/src/rxkad/ticket5_keytab.c b/src/rxkad/ticket5_keytab.c new file mode 100644 index 000000000..99c1aa9d9 --- /dev/null +++ b/src/rxkad/ticket5_keytab.c @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2013 Chaskiel Grundman + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include + +#include + +#ifdef RX_ENABLE_LOCKS +static afs_kmutex_t krb5_lock; +#endif + +/* these globals are expected to be set only once, so locking is not needed */ +static char *keytab_name; +static int have_keytab_keys; + +/* + * krb5_lock must be held to use/set these globals, including any + * krb5 api use with k5ctx + */ +static krb5_context k5ctx; +static int nkeys; +static krb5_keytab_entry *ktent; +static time_t last_reload; + +#ifdef HAVE_KRB5_KEYBLOCK_ENCTYPE +# define kb_enctype(keyblock) ((keyblock)->enctype) +#elif defined(HAVE_KRB5_KEYBLOCK_KEYTYPE) +# define kb_enctype(keyblock) ((keyblock)->keytype) +#else +# error Cannot figure out how keyblocks work +#endif +#ifdef HAVE_KRB5_KEYTAB_ENTRY_KEYBLOCK +# define kte_keyblock(kte) (&(kte).keyblock) +#elif defined(HAVE_KRB5_KEYTAB_ENTRY_KEY) +# define kte_keyblock(kte) (&(kte).key) +#else +# error Cannot figure out how keytab entries work +#endif +#if HAVE_DECL_KRB5_FREE_KEYTAB_ENTRY_CONTENTS +/* nothing */ +#elif HAVE_DECL_KRB5_KT_FREE_ENTRY +# define krb5_free_keytab_entry_contents krb5_kt_free_entry +#else +static_inline int +krb5_free_keytab_entry_contents(krb5_context ctx, krb5_keytab_entry *ent) +{ + krb5_free_principal(ctx, ent->principal); + krb5_free_keyblock_contents(ctx, kte_keyblock(ent)); + return 0; +} +#endif +#ifndef KRB5_KEYUSAGE_KDC_REP_TICKET +# ifdef HAVE_DECL_KRB5_KU_TICKET +# define KRB5_KEYUSAGE_KDC_REP_TICKET KRB5_KU_TICKET +# else +# define KRB5_KEYUSAGE_KDC_REP_TICKET 2 +# endif +#endif + +static krb5_error_code +reload_keys(void) +{ + krb5_error_code ret; + krb5_keytab fkeytab = NULL; + krb5_kt_cursor c; + krb5_keytab_entry kte; + int i, n_nkeys, o_nkeys; + krb5_keytab_entry *n_ktent = NULL, *o_ktent; + + time(&last_reload); + if (keytab_name != NULL) + ret = krb5_kt_resolve(k5ctx, keytab_name, &fkeytab); + else + ret = krb5_kt_default(k5ctx, &fkeytab); + if (ret != 0) + goto cleanup; + ret = krb5_kt_start_seq_get(k5ctx, fkeytab, &c); + if (ret != 0) + goto cleanup; + n_nkeys = 0; + while (krb5_kt_next_entry(k5ctx, fkeytab, &kte, &c) == 0) { + krb5_free_keytab_entry_contents(k5ctx, &kte); + n_nkeys++; + } + krb5_kt_end_seq_get(k5ctx, fkeytab, &c); + if (n_nkeys == 0) { + ret = KRB5_KT_NOTFOUND; + goto cleanup; + } + n_ktent = calloc(n_nkeys, sizeof(krb5_keytab_entry)); + if (n_ktent == NULL) { + ret = KRB5_KT_NOTFOUND; + goto cleanup; + } + ret = krb5_kt_start_seq_get(k5ctx, fkeytab, &c); + if (ret != 0) + goto cleanup; + for (i = 0; i < n_nkeys; i++) + if (krb5_kt_next_entry(k5ctx, fkeytab, &n_ktent[i], &c) != 0) + break; + krb5_kt_end_seq_get(k5ctx, fkeytab, &c); + if (i < n_nkeys) + goto cleanup; + have_keytab_keys = 1; + o_ktent = ktent; + ktent = n_ktent; + + o_nkeys = nkeys; + nkeys = n_nkeys; + + /* for cleanup */ + n_ktent = o_ktent; + n_nkeys = o_nkeys; +cleanup: + if (n_ktent != NULL) { + for (i = 0; i < n_nkeys; i++) + krb5_free_keytab_entry_contents(k5ctx, &n_ktent[i]); + free(n_ktent); + } + if (fkeytab != NULL) { + krb5_kt_close(k5ctx, fkeytab); + } + return ret; +} + +#if defined(HAVE_KRB5_DECRYPT_TKT_PART) && !defined(HAVE_KRB5_C_DECRYPT) +extern krb5_error_code +encode_krb5_enc_tkt_part(krb5_enc_tkt_part *encpart, krb5_data **a_out); +/* + * AIX krb5 has krb5_decrypt_tkt_part, but no krb5_c_decrypt. So, implement our + * own krb5_c_decrypt. Note that this krb5_c_decrypt is only suitable for + * decrypting an encrypted krb5_enc_tkt_part. But since that's all we ever use + * it for, that should be fine. + */ +static krb5_error_code +krb5_c_decrypt(krb5_context context, const krb5_keyblock *key, + krb5_keyusage usage, const krb5_data *cipher_state, + const krb5_enc_data *input, krb5_data *output) +{ + krb5_ticket tkt; + krb5_error_code code; + krb5_data *tout; + + osi_Assert(cipher_state == NULL); + osi_Assert(usage == KRB5_KEYUSAGE_KDC_REP_TICKET); + + memset(&tkt, 0, sizeof(tkt)); + + tkt.enc_part = *input; + + code = krb5_decrypt_tkt_part(context, key, &tkt); + if (code != 0) + return code; + + code = encode_krb5_enc_tkt_part(tkt.enc_part2, &tout); + if (code != 0) + return code; + + osi_Assert(tout->length <= output->length); + + memcpy(output->data, tout->data, tout->length); + output->length = tout->length; + + krb5_free_data(context, tout); + return 0; +} +#endif /* HAVE_KRB5_DECRYPT_TKT_PART && !HAVE_KRB5_C_DECRYPT */ + +static int +rxkad_keytab_decrypt(int kvno, int et, void *in, size_t inlen, + void *out, size_t *outlen) +{ + krb5_error_code code; + /* use heimdal api if available, since heimdal's interface to + krb5_c_decrypt is non-standard and annoying to use */ +#ifdef HAVE_KRB5_CRYPTO_INIT + krb5_crypto kcrypto; +#else + krb5_enc_data ind; +#endif + krb5_data outd; + int retried, i, foundkey; + MUTEX_ENTER(&krb5_lock); + if (have_keytab_keys == 0) { + if (time(NULL) - last_reload > 600) { + reload_keys(); + } + if (have_keytab_keys == 0) { + MUTEX_EXIT(&krb5_lock); + return RXKADUNKNOWNKEY; + } + } + foundkey = 0; + code = -1; + retried = 0; +retry: + for (i = 0; i < nkeys; i++) { + /* foundkey determines what error code we return for failure */ + if (ktent[i].vno == kvno) + foundkey = 1; + /* but check against all keys if the enctype matches, for robustness */ + if (kb_enctype(kte_keyblock(ktent[i])) == et) { +#ifdef HAVE_KRB5_CRYPTO_INIT + code = krb5_crypto_init(k5ctx, kte_keyblock(ktent[i]), et, + &kcrypto); + if (code == 0) { + code = krb5_decrypt(k5ctx, kcrypto, + KRB5_KEYUSAGE_KDC_REP_TICKET, in, inlen, + &outd); + krb5_crypto_destroy(k5ctx, kcrypto); + } + if (code == 0) { + /* heimdal allocates new memory for the decrypted data; put + * the data back into the requested 'out' buffer */ + osi_Assert(outd.length <= *outlen); + *outlen = outd.length; + memcpy(out, outd.data, outd.length); + krb5_data_free(&outd); + break; + } +#else + outd.length = *outlen; + outd.data = out; + ind.ciphertext.length = inlen; + ind.ciphertext.data = in; + ind.enctype = et; + ind.kvno = kvno; + code = krb5_c_decrypt(k5ctx, kte_keyblock(ktent[i]), + KRB5_KEYUSAGE_KDC_REP_TICKET, NULL, &ind, + &outd); + if (code == 0) { + *outlen = outd.length; + break; + } +#endif + } + } + if (code != 0 && time(NULL) - last_reload > 600 && !retried) { + reload_keys(); + retried = 1; + goto retry; + } + MUTEX_EXIT(&krb5_lock); + if (code == 0) + return 0; + if (foundkey != 0) + return RXKADBADTICKET; + return RXKADUNKNOWNKEY; +} + +#ifdef RX_ENABLE_LOCKS +static int +init_krb5_lock(void) +{ + MUTEX_INIT(&krb5_lock, "krb5 api", MUTEX_DEFAULT, 0); +} + +static pthread_once_t rxkad_keytab_once_init = PTHREAD_ONCE_INIT; +#define INIT_PTHREAD_LOCKS osi_Assert(pthread_once(&rxkad_keytab_once_init, init_krb5_lock)==0) +#else +#define INIT_PTHREAD_LOCKS +#endif +int +rxkad_InitKeytabDecrypt(const char *ktname) +{ + int code; + static int keytab_init; + INIT_PTHREAD_LOCKS; + MUTEX_ENTER(&krb5_lock); + if (keytab_init) { + MUTEX_EXIT(&krb5_lock); + return 0; + } + k5ctx = NULL; + keytab_name = NULL; + code = krb5_init_context(&k5ctx); + if (code != 0) + goto cleanup; + if (ktname != NULL) { + keytab_name = strdup(ktname); + if (keytab_name == NULL) { + code = KRB5_KT_BADNAME; + goto cleanup; + } + } + keytab_init=1; + reload_keys(); + MUTEX_EXIT(&krb5_lock); + return 0; +cleanup: + if (keytab_name != NULL) { + free(keytab_name); + } + if (k5ctx != NULL) { + krb5_free_context(k5ctx); + } + MUTEX_EXIT(&krb5_lock); + return code; +} + +int +rxkad_BindKeytabDecrypt(struct rx_securityClass *aclass) +{ + return rxkad_SetAltDecryptProc(aclass, rxkad_keytab_decrypt); +} diff --git a/src/shlibafsrpc/mapfile b/src/shlibafsrpc/mapfile index b29b98f2d..bbe301427 100644 --- a/src/shlibafsrpc/mapfile +++ b/src/shlibafsrpc/mapfile @@ -45,6 +45,7 @@ rxkad_GetServerInfo; rxkad_NewClientSecurityObject; rxkad_NewServerSecurityObject; + rxkad_SetAltDecryptProc; rxnull_NewClientSecurityObject; rxnull_NewServerSecurityObject; rxs_Release; -- 2.39.5