]> 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)
committerSimon Wilkinson <sxw@your-file-system.com>
Sat, 13 Jul 2013 10:45:16 +0000 (11:45 +0100)
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
src/rxkad/Makefile.in
src/rxkad/rxkad_prototypes.h
src/rxkad/ticket5_keytab.c [new file with mode: 0644]
src/shlibafsrpc/libafsrpc.map

index 42dec336cec15292f184d65c0955ba6016b8f8be..c7c19659e9d0adf5774fe353e6f7ccb164bc3e03 100644 (file)
@@ -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 <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>])
      RRA_LIB_KRB5_RESTORE])
 AC_SUBST([BUILD_KRB5])
 AC_SUBST([MAKE_KRB5])
index d458fdf4174b96d380d13bc7703be376d5b5edaf..bddd6f7fed12d85be9548f6edafa8ba04bf06bb2 100644 (file)
@@ -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 
index ddee6d54237d320b7c857c10bf834926177901a5..7fb22800f41f543e6cd3171e1e2d7412b6d79c00 100644 (file)
@@ -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 (file)
index 0000000..a969e02
--- /dev/null
@@ -0,0 +1,353 @@
+/*
+ * 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 <errno.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 = 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);
+}
index 78bb9817dc59b5c16d42f0092f9bb7db508612e1..98016d7dbcde4a960a33a28f8d495ae44ee0c1cd 100755 (executable)
@@ -48,6 +48,7 @@
        rxkad_GetServerInfo;
        rxkad_NewClientSecurityObject;
        rxkad_NewServerSecurityObject;
+       rxkad_SetAltDecryptProc;
        rxnull_NewClientSecurityObject;
        rxnull_NewServerSecurityObject;
        rxs_Release;