MIT Kerberos defines the keyring ccache type which can protect
credentials from sibling processes. On systems with libkeyutils,
override WebAuthCredCacheDir to enable protected session-linked
keyring credential caches.
---
config-mod.h.in | 3 +
configure.ac | 2 +
include/webauth.h | 10 +++
lib/krb5.c | 42 ++++++++++++
lib/libwebauth.map | 1 +
lib/libwebauth.sym | 1 +
modules/webauth/config.c | 6 ++
modules/webauth/krb5.c | 158 +++++++++++++++++++++++++++++++++++++++-------
8 files changed, 200 insertions(+), 23 deletions(-)
diff --git a/config-mod.h.in b/config-mod.h.in
index e46aade..36e5f7c 100644
--- a/config-mod.h.in
+++ b/config-mod.h.in
@@ -59,6 +59,9 @@
/* Define to 1 if you have the `krb5_xfree' function. */
#undef HAVE_KRB5_XFREE
+/* Define to 1 if you have the `keyutils' library (-lkeyutils). */
+#undef HAVE_LIBKEYUTILS
+
/* Define to 1 if `useragent_ip' is a member of `request_rec'. */
#undef HAVE_REQUEST_REC_USERAGENT_IP
diff --git a/configure.ac b/configure.ac
index 9bfd20c..8a711fc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -137,6 +137,8 @@ AS_IF([test x"$rra_reduced_depends" = xtrue],
DEPEND_LIBS="$DEPEND_LIBS $CRYPTO_LDFLAGS $CRYPTO_LIBS"])
AC_SUBST([DEPEND_LIBS])
+AC_CHECK_LIB([keyutils], [add_key, keyctl_search, keyctl_setperm])
+
AC_CONFIG_FILES([Makefile tests/data/conf-webkdc])
AC_CONFIG_HEADERS([config.h config-mod.h])
AS_IF([test x"$build_perl" = xtrue], [AC_CONFIG_FILES([perl/Makefile.PL])])
diff --git a/include/webauth.h b/include/webauth.h
index 4275b13..10857cd 100644
--- a/include/webauth.h
+++ b/include/webauth.h
@@ -699,6 +699,16 @@ int webauth_krb5_init_via_cred(WEBAUTH_KRB5_CTXT *, const
void *cred,
size_t cred_len, const char *cache_name);
/*
+ * Initialize a context from a credential that was created via
+ * webauth_krb5_export_tgt or webauth_krb5_export_ticket, but do not import
+ * the credential.
+ *
+ * Returns WA_ERR_NONE or WA_ERR_KRB5.
+ */
+int webauth_krb5_prepare_via_cred(WEBAUTH_KRB5_CTXT *, const void *cred,
+ size_t cred_len, const char *cache_name);
+
+/*
* Export the TGT from the context and also store the expiration time. This
* is used to construct a proxy-token after a call to
* webauth_krb5_init_via_password or webauth_krb5_init_via_tgt. Memory
diff --git a/lib/krb5.c b/lib/krb5.c
index 89b6124..d8c3ada 100644
--- a/lib/krb5.c
+++ b/lib/krb5.c
@@ -807,6 +807,48 @@ webauth_krb5_init_via_cred(WEBAUTH_KRB5_CTXT *context,
const void *cred,
return WA_ERR_NONE;
}
+/*
+ * Initialize a context from a passed, delegated credential, but do
+ * not import the credential.
+ */
+
+int
+webauth_krb5_prepare_via_cred(WEBAUTH_KRB5_CTXT *context, const void *cred,
+ size_t cred_len, const char *cache_name)
+{
+ WEBAUTH_KRB5_CTXTP *c = (WEBAUTH_KRB5_CTXTP *) context;
+ krb5_creds creds;
+ char ccname[128];
+ int s;
+
+ assert(c != NULL);
+ assert(cred != NULL);
+
+ if (cache_name == NULL) {
+ snprintf(ccname, sizeof(ccname), "MEMORY:%p", c);
+ cache_name = ccname;
+ }
+
+ s = cred_from_attr_encoding(c, cred, cred_len, &creds);
+
+ if (s != WA_ERR_NONE)
+ return s;
+
+ c->code = krb5_cc_resolve(c->ctx, cache_name, &c->cc);
+ if (c->code != 0)
+ return WA_ERR_KRB5;
+
+ c->code = krb5_copy_principal(c->ctx, creds.client, &c->princ);
+ if (c->code != 0)
+ return WA_ERR_KRB5;
+
+ c->code = krb5_cc_initialize(c->ctx, c->cc, c->princ);
+ if (c->code != 0)
+ return WA_ERR_KRB5;
+
+ return WA_ERR_NONE;
+}
+
/*
* Import a credential into an existing ticket cache.
diff --git a/lib/libwebauth.map b/lib/libwebauth.map
index c1ed4ec..83afe18 100644
--- a/lib/libwebauth.map
+++ b/lib/libwebauth.map
@@ -47,6 +47,7 @@ WEBAUTH_3 {
webauth_krb5_import_cred;
webauth_krb5_init_via_cache;
webauth_krb5_init_via_cred;
+ webauth_krb5_prepare_via_cred;
webauth_krb5_init_via_keytab;
webauth_krb5_init_via_password;
webauth_krb5_keep_cred_cache;
diff --git a/lib/libwebauth.sym b/lib/libwebauth.sym
index 58600b9..76d95db 100644
--- a/lib/libwebauth.sym
+++ b/lib/libwebauth.sym
@@ -56,6 +56,7 @@ webauth_krb5_get_realm
webauth_krb5_import_cred
webauth_krb5_init_via_cache
webauth_krb5_init_via_cred
+webauth_krb5_prepare_via_cred
webauth_krb5_init_via_keytab
webauth_krb5_init_via_password
webauth_krb5_keep_cred_cache
diff --git a/modules/webauth/config.c b/modules/webauth/config.c
index fc288c5..23d1910 100644
--- a/modules/webauth/config.c
+++ b/modules/webauth/config.c
@@ -463,6 +463,12 @@ cfg_str(cmd_parms *cmd, void *mconf, const char *arg)
sconf->auth_type = apr_pstrdup(cmd->pool, arg);
break;
case E_CredCacheDir:
+#ifdef HAVE_LIBKEYUTILS
+ if (strncmp(arg, "KEYRING:", 8) == 0) {
+ sconf->cred_cache_dir = apr_pstrdup(cmd->pool, arg);
+ break;
+ }
+#endif
sconf->cred_cache_dir = ap_server_root_relative(cmd->pool, arg);
break;
case E_Keyring:
diff --git a/modules/webauth/krb5.c b/modules/webauth/krb5.c
index a79694d..d69c07b 100644
--- a/modules/webauth/krb5.c
+++ b/modules/webauth/krb5.c
@@ -20,6 +20,10 @@
#include <webauth/basic.h>
#include <webauth/tokens.h>
+#ifdef HAVE_LIBKEYUTILS
+#include <keyutils.h>
+#endif
+
static void
log_webauth_error(server_rec *s, int status, WEBAUTH_KRB5_CTXT *ctxt,
@@ -106,29 +110,21 @@ krb5_validate_sad(MWA_REQ_CTXT *rc, const void *sad,
size_t sad_len)
* called when the request pool gets cleaned up
*/
static apr_status_t
-cred_cache_destroy(void *data)
+krb5_cleanup_context(void *data)
{
- char *path = (char*)data;
+ WEBAUTH_KRB5_CTXT *ctxt = (WEBAUTH_KRB5_CTXT *)data;
/*
ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
- "mod_webauth: cleanup cred: %s", path);
+ "mod_webauth: cleanup ctxt: %p", ctxt);
*/
- if (unlink(path) == -1) {
- ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL,
- "mod_webauth: cleanup cred: unlink(%s) errno(%d)",
- path, errno);
- }
+ webauth_krb5_free(ctxt);
return APR_SUCCESS;
}
-
-/*
- * prepare any krb5 creds
- */
static int
-krb5_prepare_creds(MWA_REQ_CTXT *rc, apr_array_header_t *creds)
+krb5_prepare_file_creds(MWA_REQ_CTXT *rc, apr_array_header_t *creds)
{
- const char *mwa_func="krb5_prepare_creds";
+ const char *mwa_func="krb5_prepare_file_creds";
WEBAUTH_KRB5_CTXT *ctxt;
size_t i;
int status;
@@ -136,12 +132,6 @@ krb5_prepare_creds(MWA_REQ_CTXT *rc, apr_array_header_t
*creds)
apr_file_t *fp;
apr_status_t astatus;
- if (rc->sconf->cred_cache_dir == NULL) {
- ap_log_error(APLOG_MARK, APLOG_ERR, 0, rc->r->server,
- "mod_webauth: WebAuthCredCacheDir is not set");
- return 0;
- }
-
astatus = apr_filepath_merge(&temp_cred_file,
rc->sconf->cred_cache_dir,
"temp.krb5.XXXXXX",
@@ -166,7 +156,7 @@ krb5_prepare_creds(MWA_REQ_CTXT *rc, apr_array_header_t
*creds)
}
apr_pool_cleanup_register(rc->r->pool, temp_cred_file,
- cred_cache_destroy,
+ krb5_cleanup_context,
apr_pool_cleanup_null);
if (rc->sconf->debug)
@@ -178,8 +168,6 @@ krb5_prepare_creds(MWA_REQ_CTXT *rc, apr_array_header_t
*creds)
if (ctxt == NULL)
return 0;
- webauth_krb5_keep_cred_cache(ctxt);
-
for (i = 0; i < (size_t) creds->nelts; i++) {
struct webauth_token_cred *cred;
@@ -213,6 +201,130 @@ krb5_prepare_creds(MWA_REQ_CTXT *rc, apr_array_header_t
*creds)
return 1;
}
+#ifdef HAVE_LIBKEYUTILS
+static int
+krb5_prepare_keyring_creds(MWA_REQ_CTXT *rc, apr_array_header_t *creds)
+{
+ const char *mwa_func="krb5_prepare_keyring_creds";
+ WEBAUTH_KRB5_CTXT *ctxt;
+ size_t i;
+ int status;
+ const char *kr_ccache_name;
+ key_serial_t kr_ccache = 0;
+ key_serial_t key;
+
+ kr_ccache_name = rc->sconf->cred_cache_dir;
+
+ if (rc->sconf->debug)
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, rc->r->server,
+ "mod_webauth: %s: krb5 keyring cache %s)",
+ mwa_func, kr_ccache_name);
+
+ ctxt = get_webauth_krb5_ctxt(rc->r->server, mwa_func);
+ if (ctxt == NULL)
+ return 0;
+
+ /* register a pool cleanup handler */
+ apr_pool_cleanup_register(rc->r->pool, ctxt,
+ krb5_cleanup_context,
+ apr_pool_cleanup_null);
+
+ for (i = 0; i < (size_t) creds->nelts; i++) {
+ struct webauth_token_cred *cred;
+
+ cred = APR_ARRAY_IDX(creds, i, struct webauth_token_cred *);
+
+ // sanity
+ if (strcmp(cred->type, "krb5") != 0)
+ continue;
+
+ /* In order to enforce possessor only permissions on our keyring
+ * ccache, create the keys and set permissions before krb5 fills the
+ * keys. There is still a window between add_key and setperm where
+ * other procs can link the key to become a "possessor"..
+ */
+
+ if (kr_ccache == 0) {
+ if (rc->sconf->debug)
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, rc->r->server,
+ "mod_webauth: %s: prepare (%s) for (%s)",
+ mwa_func, cred->service, cred->subject);
+
+ kr_ccache = add_key("keyring", (const char *)kr_ccache_name + 8,
+ NULL, 0, KEY_SPEC_SESSION_KEYRING);
+ if (kr_ccache < 0) {
+ status = errno;
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, rc->r->server,
+ "mod_webauth: %s: failed to create ccache keyring %s:
%s",
+ mwa_func, kr_ccache_name, strerror(status));
+ return 0;
+ }
+
+ keyctl_setperm(kr_ccache, KEY_POS_ALL);
+
+ status = webauth_krb5_prepare_via_cred(ctxt,
+ (void *) cred->data,
+ cred->data_len,
+ kr_ccache_name);
+ if (status != WA_ERR_NONE) {
+ log_webauth_error(rc->r->server, status, ctxt, mwa_func,
+ "webauth_krb5_prepare_via_cred", NULL);
+ return 0;
+ }
+
+ key = keyctl_search(kr_ccache, "user", "__krb5_princ__", 0);
+ if (key < 0) {
+ status = errno;
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, rc->r->server,
+ "mod_webauth: %s: failed to find princ in keyring %d:
%s",
+ mwa_func, kr_ccache, strerror(status));
+ return 0;
+ }
+
+ keyctl_setperm(key, KEY_POS_ALL);
+ }
+
+ key = add_key("user", cred->service, "null", 4, kr_ccache);
+ if (key < 0) {
+ status = errno;
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, rc->r->server,
+ "mod_webauth: %s: add_key failed for %s, keyring %d: %s",
+ mwa_func, cred->service, kr_ccache, strerror(status));
+ continue;
+ }
+
+ keyctl_setperm(key, KEY_POS_ALL);
+
+ status = webauth_krb5_import_cred(ctxt, (void *) cred->data,
+ cred->data_len);
+ if (status != WA_ERR_NONE)
+ log_webauth_error(rc->r->server, status, ctxt, mwa_func,
+ "webauth_krb5_import_cred", NULL);
+ }
+
+ /* set environment variable */
+ apr_table_setn(rc->r->subprocess_env, ENV_KRB5CCNAME, kr_ccache_name);
+ return 1;
+}
+#endif
+
+/*
+ * prepare any krb5 creds
+ */
+static int
+krb5_prepare_creds(MWA_REQ_CTXT *rc, apr_array_header_t *creds)
+{
+ if (rc->sconf->cred_cache_dir == NULL) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, rc->r->server,
+ "mod_webauth: WebAuthCredCacheDir is not set");
+ return 0;
+ }
+#ifdef HAVE_LIBKEYUTILS
+ if (strncmp(rc->sconf->cred_cache_dir, "KEYRING:", 8) == 0)
+ return krb5_prepare_keyring_creds(rc, creds);
+#endif
+ return krb5_prepare_file_creds(rc, creds);
+}
static const char *
krb5_webkdc_credential(server_rec *server,
--
1.7.5.4