]> git.michaelhowe.org Git - packages/o/openafs.git/commitdiff
New optional rxkad functionality for decypting krb5 tokens
authorChaskiel Grundman <cg2v@andrew.cmu.edu>
Sat, 9 Feb 2013 17:42:20 +0000 (12:42 -0500)
committerRuss Allbery <rra@debian.org>
Mon, 22 Jul 2013 22:25:25 +0000 (15:25 -0700)
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
src/rxkad/Makefile.in
src/rxkad/rxkad_prototypes.h
src/rxkad/ticket5_keytab.c [new file with mode: 0644]
src/shlibafsrpc/mapfile

index 8ddffe0eab5e0f8b48f1b502b768db2c81fbe98a..6decb385b61ff0b67ae2adaa20b5f6ce64aa55a8 100644 (file)
@@ -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.h>],
-[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.h>],
-[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.h>],
-[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 <krb5.h>])
+       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 <krb5.h>])
+       AC_CHECK_DECLS([krb5_free_keytab_entry_contents, krb5_kt_free_entry,
+                      KRB5_KU_TICKET], [], [], [#include <krb5.h>])
        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
index 928bca71d2b778844f634f57521c3c2465653ba3..cdb4ce3b3b3137cb2fdd9153d463ca8574a0e857 100644 (file)
@@ -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
 
index a297d3bee623b7caf0d283bdd767ae6ea314531e..070ba13ecc4941522f4a23e8955ec887dcbeac08 100644 (file)
@@ -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 (file)
index 0000000..99c1aa9
--- /dev/null
@@ -0,0 +1,333 @@
+/*
+ * Copyright (c) 2013 Chaskiel Grundman <cg2v@andrew.cmu.edu>
+ * 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 <afsconfig.h>
+#include <afs/param.h>
+#include <afs/stds.h>
+#include <rx/rx.h>
+#include <rx/rxkad.h>
+
+#include <string.h>
+
+#include <krb5.h>
+
+#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);
+}
index b29b98f2d318279eca17ab9a9c1594bfb0c1e5dc..bbe301427d689897ab27770642c3c270df272353 100644 (file)
@@ -45,6 +45,7 @@
        rxkad_GetServerInfo;
        rxkad_NewClientSecurityObject;
        rxkad_NewServerSecurityObject;
+       rxkad_SetAltDecryptProc;
        rxnull_NewClientSecurityObject;
        rxnull_NewServerSecurityObject;
        rxs_Release;