From 14db1a40e5be3b7325951d002885bbf288d570c1 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 --- configure.ac | 13 +- src/rxkad/Makefile.in | 5 +- src/rxkad/rxkad_prototypes.h | 3 + src/rxkad/ticket5_keytab.c | 353 ++++++++++++++++++++++++++++++++++ src/shlibafsrpc/libafsrpc.map | 1 + 5 files changed, 372 insertions(+), 3 deletions(-) create mode 100644 src/rxkad/ticket5_keytab.c diff --git a/configure.ac b/configure.ac index 42dec336c..c7c19659e 100644 --- a/configure.ac +++ b/configure.ac @@ -57,8 +57,12 @@ AS_IF([test x"$KRB5_LIBS" != x], encode_krb5_ticket \ krb5_524_conv_principal \ krb5_allow_weak_crypto \ + krb5_c_decrypt \ krb5_c_encrypt \ + krb5_crypto_init \ krb5_decode_ticket \ + krb5_decrypt_tkt_part \ + krb5_encrypt_tkt_part \ krb5_enctype_enable \ krb5_get_init_creds_opt_alloc \ krb5_get_prompt_types \ @@ -76,8 +80,13 @@ AS_IF([test x"$KRB5_LIBS" != x], [Define to 1 if you have the `krb524_convert_creds_kdc' function.])])])]) AC_CHECK_HEADERS([kerberosIV/krb.h]) AC_CHECK_HEADERS([kerberosV/heim_err.h]) - AC_CHECK_MEMBERS([krb5_creds.keyblock, krb5_creds.keyblock.enctype, krb5_creds.session, - krb5_prompt.type], , , [#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 ]) RRA_LIB_KRB5_RESTORE]) AC_SUBST([BUILD_KRB5]) AC_SUBST([MAKE_KRB5]) diff --git a/src/rxkad/Makefile.in b/src/rxkad/Makefile.in index d458fdf41..bddd6f7fe 100644 --- a/src/rxkad/Makefile.in +++ b/src/rxkad/Makefile.in @@ -21,7 +21,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 @@ -92,6 +92,9 @@ fcrypt.o: fcrypt.c fcrypt.h sboxes.h rxkad.h rxkad_prototypes.h crypt_conn.o: crypt_conn.c fcrypt.h private_data.h ${INCLS} +ticket5_keytab.o: ticket5_keytab.c ${INCLS} + ${CCOBJ} ${CFLAGS} -c ${srcdir}/ticket5_keytab.c @KRB5_CPPFLAGS@ + tcrypt.o: tcrypt.c AFS_component_version_number.o tcrypt: tcrypt.o librxkad.a diff --git a/src/rxkad/rxkad_prototypes.h b/src/rxkad/rxkad_prototypes.h index ddee6d542..7fb22800f 100644 --- a/src/rxkad/rxkad_prototypes.h +++ b/src/rxkad/rxkad_prototypes.h @@ -163,6 +163,9 @@ extern int tkt_DecodeTicket5(char *ticket, afs_int32 ticket_len, afs_int32 * host, afs_uint32 * start, afs_uint32 * 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 *); #if !defined(NO_DES_H_INCLUDE) static_inline unsigned char * diff --git a/src/rxkad/ticket5_keytab.c b/src/rxkad/ticket5_keytab.c new file mode 100644 index 000000000..a969e023a --- /dev/null +++ b/src/rxkad/ticket5_keytab.c @@ -0,0 +1,353 @@ +/* + * 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 + +#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 = NULL; + + /* We only handle a subset of possible arguments; if we somehow get passed + * something else, bail out with EINVAL. */ + if (cipher_state != NULL) + return EINVAL; + if (usage != KRB5_KEYUSAGE_KDC_REP_TICKET) + return EINVAL; + + 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; + + if (tout->length > output->length) { + /* This should never happen, but don't assert since we may be dealing + * with untrusted user data. */ + code = EINVAL; + goto error; + } + + memcpy(output->data, tout->data, tout->length); + output->length = tout->length; + + error: + if (tout) + krb5_free_data(context, tout); + return code; +} +#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) { + if (outd.length > *outlen) { + /* This should never happen, but don't assert since we may + * be dealing with untrusted user data. */ + code = EINVAL; + krb5_data_free(&outd); + outd.data = NULL; + } + } + if (code == 0) { + /* heimdal allocates new memory for the decrypted data; put + * the data back into the requested 'out' buffer */ + *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 void +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/libafsrpc.map b/src/shlibafsrpc/libafsrpc.map index 78bb9817d..98016d7db 100755 --- a/src/shlibafsrpc/libafsrpc.map +++ b/src/shlibafsrpc/libafsrpc.map @@ -48,6 +48,7 @@ rxkad_GetServerInfo; rxkad_NewClientSecurityObject; rxkad_NewServerSecurityObject; + rxkad_SetAltDecryptProc; rxnull_NewClientSecurityObject; rxnull_NewServerSecurityObject; rxs_Release; -- 2.39.5