summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuss Allbery <rra@stanford.edu>2011-08-03 11:25:58 -0700
committerRuss Allbery <rra@stanford.edu>2011-08-03 11:31:35 -0700
commit0eef6a850df7fb865506dcf99f3be72452a419a7 (patch)
tree00a02c133fe6ea1c1d9fa3bd61d251ea2a57be55
parentbea500204c21ff8f530693209cc649ee6e377b50 (diff)
Add mod_webkdc support for user metadata and multifactor
Add support for querying for user metadata if so configured. Add support for fulfilling multifactor requirements and tracking the initial factors through multiple webkdc-proxy tokens into the tokens generated for the WAS. Add support for rejecting logins with multifactor required or multifactor unavailable based on the user's configuration and the requests from the WAS. This provides the core of basic multifactor support except that it provides no way of getting webkdc-proxy tokens with the appropriate factors. That will be coming in a subsequent change. Most of the login code is moved into the libwebauth library, but there are a lot of remnants still left behind in mod_webkdc that will need to move later, and therefore a lot of duplicate code. Change-Id: I4d10a121472d93caa8e00e5b755949f862e71f82 Reviewed-on: https://gerrit.stanford.edu/118 Tested-by: Russ Allbery <rra@stanford.edu> Reviewed-by: Russ Allbery <rra@stanford.edu>
-rw-r--r--.gitignore1
-rw-r--r--Makefile.am22
-rw-r--r--include/webauth/webkdc.h126
-rw-r--r--lib/internal.h3
-rw-r--r--lib/libwebauth.map2
-rw-r--r--lib/libwebauth.sym2
-rw-r--r--lib/webkdc-config.c46
-rw-r--r--lib/webkdc-login.c879
-rw-r--r--modules/webkdc/config.c49
-rw-r--r--modules/webkdc/mod_webkdc.c676
-rw-r--r--modules/webkdc/mod_webkdc.h2
-rw-r--r--tests/TESTS1
-rw-r--r--tests/data/xml/info/normal.xml15
-rw-r--r--tests/data/xml/info/random.xml11
-rw-r--r--tests/lib/webkdc-login-t.c593
15 files changed, 2054 insertions, 374 deletions
diff --git a/.gitignore b/.gitignore
index cc88786e..7708443e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,6 +51,7 @@
/tests/lib/token-encode-t
/tests/lib/token-t
/tests/lib/webkdc-info-t
+/tests/lib/webkdc-login-t
/tests/portable/asprintf-t
/tests/portable/snprintf-t
/tests/portable/mkstemp-t
diff --git a/Makefile.am b/Makefile.am
index 85c1267f..4ec8305a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -77,10 +77,11 @@ webauthincludedir = $(includedir)/webauth
webauthinclude_HEADERS = include/webauth/basic.h include/webauth/keys.h \
include/webauth/tokens.h include/webauth/util.h
nodist_webauthinclude_HEADERS = include/webauth/defines.h
-lib_libwebauth_la_SOURCES = lib/apr-buffer.c lib/attrs.c lib/base64.c \
- lib/context.c lib/factors.c lib/key.c lib/keys.c lib/krb5.c \
- lib/internal.h lib/misc.c lib/random.c lib/token-decode.c \
- lib/token-encode.c lib/token.c lib/userinfo.c lib/util.c lib/xml.c
+lib_libwebauth_la_SOURCES = lib/apr-buffer.c lib/attrs.c lib/base64.c \
+ lib/context.c lib/factors.c lib/key.c lib/keys.c lib/krb5.c \
+ lib/internal.h lib/misc.c lib/random.c lib/token-decode.c \
+ lib/token-encode.c lib/token.c lib/userinfo.c lib/util.c \
+ lib/webkdc-config.c lib/webkdc-login.c lib/xml.c
EXTRA_lib_libwebauth_la_SOURCES = lib/krb5-heimdal.c lib/krb5-mit.c
lib_libwebauth_la_CPPFLAGS = $(AM_CPPFLAGS) $(APR_CPPFLAGS) \
$(APRUTIL_CPPFLAGS) $(REMCTL_CPPFLAGS) $(KRB5_CPPFLAGS) \
@@ -257,10 +258,10 @@ check_PROGRAMS = tests/runtests tests/lib/attrs-t tests/lib/base64-t \
tests/lib/key-t tests/lib/keys-t tests/lib/krb5-t \
tests/lib/krb5-tgt-t tests/lib/random-t tests/lib/token-decode-t \
tests/lib/token-encode-t tests/lib/token-t tests/lib/webkdc-info-t \
- tests/portable/asprintf-t tests/portable/mkstemp-t \
- tests/portable/snprintf-t tests/portable/strlcat-t \
- tests/portable/strlcpy-t tests/util/concat-t tests/util/messages-t \
- tests/util/xmalloc
+ tests/lib/webkdc-login-t tests/portable/asprintf-t \
+ tests/portable/mkstemp-t tests/portable/snprintf-t \
+ tests/portable/strlcat-t tests/portable/strlcpy-t \
+ tests/util/concat-t tests/util/messages-t tests/util/xmalloc
tests_runtests_CPPFLAGS = -DSOURCE='"$(abs_top_srcdir)/tests"' \
-DBUILD='"$(abs_top_builddir)/tests"'
check_LIBRARIES = tests/tap/libtap.a
@@ -294,6 +295,11 @@ tests_lib_webkdc_info_t_CPPFLAGS = -DPATH_REMCTLD='"$(PATH_REMCTLD)"' \
tests_lib_webkdc_info_t_LDFLAGS = $(APR_LDFLAGS) $(KRB5_LDFLAGS)
tests_lib_webkdc_info_t_LDADD = tests/tap/libtap.a lib/libwebauth.la \
util/libutil.a portable/libportable.la $(APR_LIBS) $(KRB5_LIBS)
+tests_lib_webkdc_login_t_CPPFLAGS = -DPATH_REMCTLD='"$(PATH_REMCTLD)"' \
+ $(APR_CPPFLAGS) $(AM_CPPFLAGS)
+tests_lib_webkdc_login_t_LDFLAGS = $(APR_LDFLAGS) $(KRB5_LDFLAGS)
+tests_lib_webkdc_login_t_LDADD = tests/tap/libtap.a lib/libwebauth.la \
+ util/libutil.a portable/libportable.la $(APR_LIBS) $(KRB5_LIBS)
tests_portable_asprintf_t_SOURCES = tests/portable/asprintf-t.c \
tests/portable/asprintf.c
tests_portable_asprintf_t_LDADD = tests/tap/libtap.a portable/libportable.la
diff --git a/include/webauth/webkdc.h b/include/webauth/webkdc.h
index 1de86c6a..3bb81588 100644
--- a/include/webauth/webkdc.h
+++ b/include/webauth/webkdc.h
@@ -35,9 +35,74 @@
#include <sys/types.h>
+#include <webauth.h>
+
struct webauth_context;
/*
+ * General configuration information for the WebKDC functions. The WebKDC
+ * Apache module gets this information from the Apache configuration and then
+ * passes it into the library via webauth_webkdc_config.
+ */
+struct webauth_webkdc_config {
+ const char *keytab_path; /* Path to WebKDC's Kerberos keytab. */
+ const char *principal; /* WebKDC's Kerberos principal. */
+ time_t proxy_lifetime; /* Maximum webkdc-proxy token lifetime (s). */
+ WA_APR_ARRAY_HEADER_T *permitted_realms; /* Array of char * realms. */
+ WA_APR_ARRAY_HEADER_T *local_realms; /* Array of char * realms. */
+};
+
+/*
+ * Holds an encoded webkdc-proxy token along with some additional metadata
+ * about it that may be needed by consumers who can't decode the token (or
+ * don't want to).
+ */
+struct webauth_webkdc_proxy_data {
+ const char *type;
+ const char *token;
+};
+
+/*
+ * Input for a <requestTokenRequest>, which is sent from the WebLogin server
+ * to the WebKDC and represents a request by a user to authenticate to a WAS.
+ * This request may contain webkdc-proxy tokens, representing existing single
+ * sign-on credentials, and a login token, representing a username and
+ * authentication credential provided by the user in this session.
+ */
+struct webauth_webkdc_login_request {
+ struct webauth_token_webkdc_service *service;
+ WA_APR_ARRAY_HEADER_T *creds; /* Array of webauth_token pointers. */
+ struct webauth_token_request *request;
+ const char *remote_user;
+ const char *local_ip;
+ const char *local_port;
+ const char *remote_ip;
+ const char *remote_port;
+};
+
+/*
+ * Result from a <requestTokenResponse>, which is sent by the WebKDC back to
+ * the WebLogin server containing the results of an authentication request.
+ * It was successful if the login_error is 0.
+ */
+struct webauth_webkdc_login_response {
+ int login_error;
+ const char *login_message;
+ WA_APR_ARRAY_HEADER_T *factors_wanted; /* Array of char * factors. */
+ WA_APR_ARRAY_HEADER_T *factors_configured; /* Array of char * factors. */
+ WA_APR_ARRAY_HEADER_T *proxies; /* Array of webkdc_proxy_data structs. */
+ const char *return_url;
+ const char *requester;
+ const char *subject;
+ const char *result; /* Encrypted id or cred token. */
+ const char *result_type; /* Type of result token as a string. */
+ const char *login_cancel; /* Encrypted error token. */
+ const void *app_state;
+ size_t app_state_len;
+ WA_APR_ARRAY_HEADER_T *logins; /* Array of struct webauth_login. */
+};
+
+/*
* Supported protocols for contacting the user metadata and multifactor
* authentication services. Currently, only remctl is supported.
*/
@@ -64,6 +129,17 @@ struct webauth_user_config {
};
/*
+ * Stores a single suspicious or questionable login, or a login that for some
+ * other reason the user should be notified about. Returned in an APR array
+ * in the webauth_userinfo struct.
+ */
+struct webauth_login {
+ const char *ip;
+ const char *hostname;
+ time_t timestamp;
+};
+
+/*
* The webauth_userinfo struct and its supporting data structures stores
* metadata about a user, returned from the site-local user management
* middleware.
@@ -76,17 +152,6 @@ struct webauth_user_info {
WA_APR_ARRAY_HEADER_T *logins; /* Array of struct webauth_login. */
};
-/*
- * Stores a single suspicious or questionable login, or a login that for some
- * other reason the user should be notified about. Returned in an APR array
- * in the webauth_userinfo struct.
- */
-struct webauth_login {
- const char *ip;
- const char *hostname;
- time_t timestamp;
-};
-
BEGIN_DECLS
/*
@@ -96,9 +161,8 @@ BEGIN_DECLS
* Returns a status code, which will be WA_ERR_NONE unless invalid parameters
* were passed.
*/
-int
-webauth_user_config(struct webauth_context *ctx, struct webauth_user_config *)
- __attribute__((__nonnull__(1, 2)));
+int webauth_user_config(struct webauth_context *, struct webauth_user_config *)
+ __attribute__((__nonnull__));
/*
* Obtain user information for a given user. The IP address of the user (as a
@@ -115,9 +179,37 @@ webauth_user_config(struct webauth_context *ctx, struct webauth_user_config *)
* allocated from pool memory and returns WA_ERR_NONE. On failure, returns an
* error code and sets the info parameter to NULL.
*/
-int
-webauth_user_info(struct webauth_context *, const char *user, const char *ip,
- int, struct webauth_user_info **info)
+int webauth_user_info(struct webauth_context *, const char *user,
+ const char *ip, int, struct webauth_user_info **info)
+ __attribute__((__nonnull__));
+
+/*
+ * Configure the WebKDC services. Takes the context and the configuration
+ * information. The configuration information is stored in the WebAuth
+ * context and is used for all subsequent webauth_webkdc functions. Returns a
+ * status code, which will be WA_ERR_NONE unless invalid parameters were
+ * passed.
+ */
+int webauth_webkdc_config(struct webauth_context *,
+ struct webauth_webkdc_config *)
+ __attribute__((__nonnull__));
+
+/*
+ * Given the data from a <requestTokenRequest> login attempt, process that
+ * attempted login and return the information for a <requestTokenResponse> in
+ * a newly-allocated struct from pool memory. All of the tokens included in
+ * the input and output are the unencrypted struct representations; the caller
+ * does the encryption or decryption and base64 conversion.
+ *
+ * Returns WA_ERR_NONE if the request was successfully processed, which
+ * doesn't mean it succeeded; see the login_code attribute of the struct for
+ * that. Returns an error code if we were unable to process the struct even
+ * to generate an error response.
+ */
+int webauth_webkdc_login(struct webauth_context *,
+ struct webauth_webkdc_login_request *,
+ struct webauth_webkdc_login_response **,
+ WEBAUTH_KEYRING *keyring)
__attribute__((__nonnull__));
END_DECLS
diff --git a/lib/internal.h b/lib/internal.h
index 55d5a9a5..773e01fb 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -27,6 +27,9 @@ struct webauth_context {
/* The below are used only for the WebKDC functions. */
+ /* General WebKDC configuration. */
+ struct webauth_webkdc_config *webkdc;
+
/* Configuration for contacting the user metadata service. */
struct webauth_user_config *user;
};
diff --git a/lib/libwebauth.map b/lib/libwebauth.map
index ec2d5146..4462d69b 100644
--- a/lib/libwebauth.map
+++ b/lib/libwebauth.map
@@ -83,6 +83,8 @@ WEBAUTH_3 {
webauth_token_type_string;
webauth_user_config;
webauth_user_info;
+ webauth_webkdc_config;
+ webauth_webkdc_login;
local:
*;
diff --git a/lib/libwebauth.sym b/lib/libwebauth.sym
index 7ddb39c4..d68f3db1 100644
--- a/lib/libwebauth.sym
+++ b/lib/libwebauth.sym
@@ -81,3 +81,5 @@ webauth_token_type_code
webauth_token_type_string
webauth_user_config
webauth_user_info
+webauth_webkdc_config
+webauth_webkdc_login
diff --git a/lib/webkdc-config.c b/lib/webkdc-config.c
new file mode 100644
index 00000000..75dba8b0
--- /dev/null
+++ b/lib/webkdc-config.c
@@ -0,0 +1,46 @@
+/*
+ * Interface for configuring the WebKDC portion of the library.
+ *
+ * Written by Russ Allbery <rra@stanford.edu>
+ * Copyright 2011
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * See LICENSE for licensing terms.
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <lib/internal.h>
+#include <webauth/basic.h>
+#include <webauth/webkdc.h>
+
+
+/*
+ * Configure the WebKDC services. Takes the context and the configuration
+ * information. The configuration information is stored in the WebAuth
+ * context and is used for all subsequent webauth_webkdc functions. Returns a
+ * status code, which will be WA_ERR_NONE unless invalid parameters were
+ * passed.
+ */
+int
+webauth_webkdc_config(struct webauth_context *ctx,
+ struct webauth_webkdc_config *config)
+{
+ int status;
+
+ if (config->local_realms == NULL) {
+ status = WA_ERR_INVALID;
+ webauth_error_set(ctx, status, "local realms must be present");
+ return status;
+ }
+ if (config->permitted_realms == NULL) {
+ status = WA_ERR_INVALID;
+ webauth_error_set(ctx, status, "permitted realms must be present");
+ return status;
+ }
+ ctx->webkdc = config;
+
+ /* FIXME: Add more error checking for consistency of configuration. */
+ return WA_ERR_NONE;
+}
diff --git a/lib/webkdc-login.c b/lib/webkdc-login.c
new file mode 100644
index 00000000..c687f681
--- /dev/null
+++ b/lib/webkdc-login.c
@@ -0,0 +1,879 @@
+/*
+ * WebKDC interface for processing a <requestTokenRequest>.
+ *
+ * These interfaces are used by the WebKDC implementation to process a
+ * <requestTokenRequest> from the WebLogin server, representing a user's
+ * attempt to authenticate to a WAS, either with proxy tokens or with a
+ * username and authentication credential, or both.
+ *
+ * Written by Russ Allbery <rra@stanford.edu>
+ * Copyright 2011
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * See LICENSE for licensing terms.
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <apr_pools.h>
+#include <apr_strings.h>
+#include <apr_tables.h>
+
+#include <lib/internal.h>
+#include <webauth/basic.h>
+#include <webauth/keys.h>
+#include <webauth/tokens.h>
+#include <webauth/webkdc.h>
+
+
+/*
+ * Given a Kerberos principal for an authenticated user, derive the WebAuth
+ * authenticated subject based on the local_realms parameter of the WebKDC
+ * configuration. The subject may be identical to the Kerberos principal, but
+ * often means stripping off the realm or applying Kerberos local name
+ * conversion. Returns the subject in newly allocated pool memory. Returns a
+ * status code on failure.
+ *
+ * The local_realms array in the WebKDC configuration may either be a single
+ * keyword or may be a list of realms. If it is a keyword, it's one of
+ * "local" or "none". "local" means to apply Kerberos local name conversion.
+ * "none" means to use the principal name without modification. Otherwise,
+ * it's taken to be a list of realms, and any of those realms are stripped
+ * from the principal. Any principal not in one of those realms is retained
+ * as a fully-qualified principal name. If local_realms is not set, assume
+ * "local", which is the default.
+ */
+static int
+canonicalize_user(struct webauth_context *ctx, WEBAUTH_KRB5_CTXT *kctx,
+ const char **result)
+{
+ char *subject;
+ int status, i;
+ enum webauth_krb5_canon canonicalize = WA_KRB5_CANON_LOCAL;
+
+ *result = NULL;
+ if (ctx->webkdc->local_realms->nelts > 0) {
+ const char *local;
+ char *realm;
+
+ local = APR_ARRAY_IDX(ctx->webkdc->local_realms, 0, const char *);
+ if (strcmp(local, "none") == 0)
+ canonicalize = WA_KRB5_CANON_NONE;
+ else if (strcmp(local, "local") == 0)
+ canonicalize = WA_KRB5_CANON_LOCAL;
+ else {
+ canonicalize = WA_KRB5_CANON_NONE;
+ status = webauth_krb5_get_realm(kctx, &realm);
+ if (status != WA_ERR_NONE)
+ return status;
+ for (i = 0; i < ctx->webkdc->local_realms->nelts; i++) {
+ local = APR_ARRAY_IDX(ctx->webkdc->local_realms, i,
+ const char *);
+ if (strcmp(local, realm) == 0)
+ canonicalize = WA_KRB5_CANON_STRIP;
+ }
+ free(realm);
+ }
+ }
+
+ /*
+ * We now know the canonicalization method we're using, so we can retrieve
+ * the principal from the context.
+ */
+ status = webauth_krb5_get_principal(kctx, &subject, canonicalize);
+ if (status != WA_ERR_NONE)
+ return status;
+ *result = apr_pstrdup(ctx->pool, subject);
+ free(subject);
+ return WA_ERR_NONE;
+}
+
+
+/*
+ * Check that the realm of the authenticated principal is in the list of
+ * permitted realms, or that the list of realms is empty. Returns MWK_OK if
+ * the realm is permitted, MWK_ERROR otherwise. Sets the error on a failure,
+ * so the caller doesn't need to do so.
+ */
+static int
+realm_permitted(struct webauth_context *ctx, WEBAUTH_KRB5_CTXT *kctx,
+ struct webauth_webkdc_login_response *response)
+{
+ int status, i;
+ char *realm;
+ const char *allowed;
+ bool okay = false;
+
+ /* If we aren't restricting the realms, always return true. */
+ if (ctx->webkdc->permitted_realms->nelts == 0)
+ return WA_ERR_NONE;
+
+ /* Get the realm. */
+ status = webauth_krb5_get_realm(kctx, &realm);
+ if (status != WA_ERR_NONE)
+ goto done;
+
+ /*
+ * We assume that all realms listed in the configuration are already
+ * escaped, as is the realm parameter.
+ */
+ for (i = 0; i < ctx->webkdc->permitted_realms->nelts; i++) {
+ allowed = APR_ARRAY_IDX(ctx->webkdc->permitted_realms, i, const char *);
+ if (strcmp(allowed, realm) == 0) {
+ okay = true;
+ break;
+ }
+ }
+ if (!okay) {
+ response->login_error = WA_PEC_USER_REJECTED;
+ response->login_message
+ = apr_psprintf(ctx->pool, "realm %s is not permitted", realm);
+ }
+ status = WA_ERR_NONE;
+
+done:
+ free(realm);
+ return status;
+}
+
+
+/*
+ * Attempt a username and password login. On success, generate a new
+ * webkdc-proxy token based on that information and store it in the token
+ * argument. On login failure, store the error code and message in the
+ * response. On a more fundamental failure, return an error code.
+ */
+static int
+do_login(struct webauth_context *ctx,
+ struct webauth_webkdc_login_response *response,
+ struct webauth_token_login *login,
+ struct webauth_token **wkproxy)
+{
+ int status;
+ WEBAUTH_KRB5_CTXT *kctx;
+ const char *subject;
+ char *webkdc, *tmp;
+ char *tgt, *tmp_tgt;
+ size_t tgt_len;
+ time_t expires;
+ struct webauth_token_webkdc_proxy *pt;
+
+ status = webauth_krb5_new(&kctx);
+ if (status != WA_ERR_NONE) {
+ if (status == WA_ERR_KRB5)
+ webauth_krb5_free(kctx);
+ return status;
+ }
+ status = webauth_krb5_init_via_password(kctx,
+ login->username,
+ login->password,
+ NULL,
+ ctx->webkdc->keytab_path,
+ ctx->webkdc->principal,
+ NULL,
+ &webkdc);
+ switch (status) {
+ case 0:
+ break;
+ case WA_ERR_LOGIN_FAILED:
+ response->login_error = WA_PEC_LOGIN_FAILED;
+ response->login_message = webauth_error_message(ctx, status);
+ status = WA_ERR_NONE;
+ goto cleanup;
+ case WA_ERR_CREDS_EXPIRED:
+ response->login_error = WA_PEC_CREDS_EXPIRED;
+ response->login_message = webauth_error_message(ctx, status);
+ status = WA_ERR_NONE;
+ goto cleanup;
+ case WA_ERR_USER_REJECTED:
+ response->login_error = WA_PEC_USER_REJECTED;
+ response->login_message = webauth_error_message(ctx, status);
+ status = WA_ERR_NONE;
+ goto cleanup;
+ case WA_ERR_KRB5:
+ webauth_error_set(ctx, status, "%s", webauth_krb5_error_message(kctx));
+ /* fall through */
+ default:
+ return status;
+ }
+
+ /*
+ * webauth_krb5_init_via_password determined the principal of the WebKDC
+ * service to which we just authenticated and stored that information in
+ * webkdc, but it's not yet poolified, so make a copy in our memory pool
+ * so that we can free it.
+ */
+ tmp = apr_pstrcat(ctx->pool, "krb5:", webkdc, NULL);
+ free(webkdc);
+ webkdc = tmp;
+
+ /*
+ * Check if the realm of the authenticated principal is permitted and
+ * then canonicalize the user's identity.
+ */
+ status = realm_permitted(ctx, kctx, response);
+ if (status != WA_ERR_NONE || response->login_error != 0)
+ goto cleanup;
+ status = canonicalize_user(ctx, kctx, &subject);
+ if (status != WA_ERR_NONE)
+ goto cleanup;
+
+ /* Export the ticket-granting ticket for the webkdc-proxy token. */
+ status = webauth_krb5_export_tgt(kctx, &tgt, &tgt_len, &expires);
+ if (status != WA_ERR_NONE)
+ goto cleanup;
+ tmp_tgt = apr_palloc(ctx->pool, tgt_len);
+ memcpy(tmp_tgt, tgt, tgt_len);
+ free(tgt);
+ tgt = tmp_tgt;
+
+ /*
+ * We now have everything we need to create the webkdc-proxy token. We've
+ * already copied all this stuff into a pool, so there is no need to copy
+ * again.
+ */
+ *wkproxy = apr_pcalloc(ctx->pool, sizeof(struct webauth_token));
+ (*wkproxy)->type = WA_TOKEN_WEBKDC_PROXY;
+ pt = &(*wkproxy)->token.webkdc_proxy;
+ pt->subject = subject;
+ pt->proxy_type = "krb5";
+ pt->proxy_subject = apr_pstrcat(ctx->pool, "WEBKDC:", webkdc, NULL);
+ pt->data = tgt;
+ pt->data_len = tgt_len;
+ pt->initial_factors = WA_FA_PASSWORD;
+ if (ctx->webkdc->proxy_lifetime == 0)
+ pt->expiration = expires;
+ else {
+ pt->expiration = time(NULL) + ctx->webkdc->proxy_lifetime;
+ if (pt->expiration > expires)
+ pt->expiration = expires;
+ }
+
+cleanup:
+ webauth_krb5_free(kctx);
+ return status;
+}
+
+
+/*
+ * Merge an array of webkdc-proxy tokens into a single token, which we'll then
+ * use for subsequent operations. Takes the context, the array of
+ * credentials, and a place to store the newly created webkdc-proxy token (or
+ * return a pointer to one of the ones passed in if there is only one.
+ *
+ * We use the following logic to merge webkdc-proxy tokens:
+ *
+ * 1. Expired tokens are discarded.
+ * 2. Tokens whose initial factors are a subset of the accumulated factors
+ * and which do not add krb5 capability are discarded.
+ * 3. The krb5 data is added if not already present, and the expiration is
+ * set to the token with the krb5 data.
+ * 4. Initial factors are merged between all webkdc-proxy tokens, with the
+ * expiration set to the nearest expiration of all contributing tokens.
+ * 5. Creation time is set to the current time if we pull from multiple
+ * tokens.
+ */
+static int
+merge_webkdc_proxy(struct webauth_context *ctx, apr_array_header_t *creds,
+ struct webauth_token **result)
+{
+ bool created = false;
+ struct webauth_token *token, *tmp;
+ struct webauth_token *genbest = NULL;
+ struct webauth_token_webkdc_proxy *wkproxy;
+ struct webauth_token_webkdc_proxy *best = NULL;
+ struct webauth_factors *current;
+ struct webauth_factors *factors = NULL;
+ time_t now;
+ int i, status;
+
+ *result = NULL;
+ if (creds->nelts == 0)
+ return WA_ERR_NONE;
+ now = time(NULL);
+ for (i = 0; i < creds->nelts; i++) {
+ token = APR_ARRAY_IDX(creds, i, struct webauth_token *);
+ if (token->type != WA_TOKEN_WEBKDC_PROXY)
+ continue;
+ wkproxy = &token->token.webkdc_proxy;
+ if (wkproxy->expiration <= now)
+ continue;
+ if (best == NULL) {
+ best = wkproxy;
+ genbest = token;
+ continue;
+ }
+ if (factors == NULL) {
+ status = webauth_factors_parse(ctx, best->initial_factors,
+ &factors);
+ if (status != WA_ERR_NONE)
+ return status;
+ }
+ current = NULL;
+ status = webauth_factors_parse(ctx, wkproxy->initial_factors,
+ &current);
+ if (status != WA_ERR_NONE)
+ return status;
+ if (webauth_factors_subset(ctx, current, factors)
+ && (strcmp(best->proxy_type, "krb5") == 0
+ || strcmp(wkproxy->proxy_type, "krb5") != 0))
+ continue;
+ if (!created) {
+ tmp = apr_palloc(ctx->pool, sizeof(struct webauth_token));
+ *tmp = *genbest;
+ genbest = tmp;
+ best = &tmp->token.webkdc_proxy;
+ created = true;
+ }
+ if (strcmp(best->proxy_type, "krb5") != 0
+ && strcmp(wkproxy->proxy_type, "krb5") == 0) {
+ best->data = wkproxy->data;
+ best->data_len = wkproxy->data_len;
+ }
+ status = webauth_factors_parse(ctx, wkproxy->initial_factors,
+ &factors);
+ if (status != WA_ERR_NONE)
+ return status;
+ if (wkproxy->expiration < best->expiration)
+ best->expiration = wkproxy->expiration;
+ if (wkproxy->loa > best->loa)
+ best->loa = wkproxy->loa;
+ }
+ if (created) {
+ best->initial_factors = webauth_factors_string(ctx, factors);
+ best->creation = now;
+ }
+ *result = genbest;
+ return WA_ERR_NONE;
+}
+
+
+/*
+ * Given the desired factors expressed by the WebAuth Application Server, the
+ * current set of factors the user has authenticated with, and the user
+ * metadata information (which may be NULL if there's no metadata configured),
+ * check whether multifactor authentication is already satisfied or
+ * unnecessary, required, or impossible.
+ *
+ * Returns WA_ERR_NONE and leaves request->login_error unchanged if any
+ * multifactor requirements are satisfied. Sets request->login_error if
+ * multifactor is required or unavailable. Returns an error code on errors in
+ * processing.
+ */
+static int
+check_multifactor(struct webauth_context *ctx,
+ struct webauth_webkdc_login_request *request,
+ struct webauth_webkdc_login_response *response,
+ struct webauth_user_info *info)
+{
+ int i, status;
+ apr_array_header_t *factors = NULL;
+ struct webauth_factors *wanted;
+ struct webauth_factors configured;
+ struct webauth_factors *have = NULL;
+ struct webauth_token *cred;
+ struct webauth_token_request *req;
+ struct webauth_token_webkdc_proxy *wkproxy;
+
+ /* If the WAS doesn't care, neither do we. */
+ req = request->request;
+
+ /* Figure out what factors we want and have. */
+ if (req->initial_factors == NULL || req->initial_factors[0] == '\0') {
+ wanted = apr_pcalloc(ctx->pool, sizeof(struct webauth_factors));
+ wanted->factors = apr_array_make(ctx->pool, 1, sizeof(char *));
+ } else {
+ status = webauth_factors_parse(ctx, req->initial_factors, &wanted);
+ if (status != WA_ERR_NONE)
+ return status;
+ }
+ for (i = 0; i < request->creds->nelts; i++) {
+ cred = APR_ARRAY_IDX(request->creds, i, struct webauth_token *);
+ if (cred->type != WA_TOKEN_WEBKDC_PROXY)
+ continue;
+ wkproxy = &cred->token.webkdc_proxy;
+ status = webauth_factors_parse(ctx, wkproxy->initial_factors, &have);
+ if (status != WA_ERR_NONE)
+ return status;
+ }
+
+ /*
+ * Check if multifactor is forced by user configuration. If so, and if
+ * the user has not already authenticated with multifactor, we need to
+ * return one or the other error code. Also set factors to the user's
+ * configured factors if they have any.
+ */
+ if (info != NULL && info->factors != NULL && info->factors->nelts > 0)
+ factors = info->factors;
+ if (info != NULL && info->multifactor_required) {
+ status = webauth_factors_parse(ctx, WA_FA_MULTIFACTOR, &wanted);
+ if (status != WA_ERR_NONE)
+ return status;
+ }
+
+ /*
+ * Second, see if the WAS-requested factors are already satisfied by the
+ * factors that we have.
+ */
+ if (webauth_factors_subset(ctx, wanted, have))
+ return WA_ERR_NONE;
+
+ /*
+ * Finally, check if the WAS-requested factors can be satisfied by the
+ * factors configured by the user. We have to do a bit of work here to
+ * turn the user's configured factors into a webauth_factors struct.
+ *
+ * Assume we can always do password authentication.
+ */
+ memset(&configured, 0, sizeof(configured));
+ if (factors != NULL) {
+ configured.factors = apr_array_copy(ctx->pool, factors);
+ for (i = 0; i < factors->nelts; i++)
+ if (strcmp(APR_ARRAY_IDX(factors, i, const char *), "m") == 0)
+ configured.multifactor = true;
+ } else {
+ configured.factors = apr_array_make(ctx->pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(configured.factors, const char *) = "p";
+ }
+ response->factors_wanted = wanted->factors;
+ response->factors_configured = configured.factors;
+ if (webauth_factors_subset(ctx, wanted, &configured)) {
+ response->login_error = WA_PEC_MULTIFACTOR_REQUIRED;
+ response->login_message = "multifactor login required";
+ } else {
+ response->login_error = WA_PEC_MULTIFACTOR_UNAVAILABLE;
+ response->login_message = "multifactor required but not configured";
+ }
+ return WA_ERR_NONE;
+}
+
+
+/*
+ * Given the identity of a WAS and a webkdc-proxy token identifying the user,
+ * obtain a Kerberos authenticator identifying that user to that WAS. Store
+ * it in the provided buffer. Returns either WA_ERR_NONE on success or a
+ * WebAuth error code. On error, also set the WebAuth error message.
+ */
+static int
+get_krb5_authenticator(struct webauth_context *ctx, const char *server,
+ struct webauth_token_webkdc_proxy *wkproxy,
+ void **krb5_auth, size_t *krb5_auth_len)
+{
+ int status;
+ WEBAUTH_KRB5_CTXT *kctx;
+ char *tmp_auth;
+
+ *krb5_auth = NULL;
+ status = webauth_krb5_new(&kctx);
+ if (status != WA_ERR_NONE) {
+ if (status == WA_ERR_KRB5)
+ webauth_krb5_free(kctx);
+ return status;
+ }
+
+ /*
+ * FIXME: Probably need to examine errors a little more closely to
+ * determine if we should return a proxy-token error or a server-failure.
+ */
+ status = webauth_krb5_init_via_cred(kctx, wkproxy->data,
+ wkproxy->data_len, NULL);
+ if (status != WA_ERR_NONE) {
+ if (status == WA_ERR_KRB5)
+ webauth_error_set(ctx, status, "%s",
+ webauth_krb5_error_message(kctx));
+ webauth_krb5_free(kctx);
+ return status;
+ }
+
+ /*
+ * Generate the Kerberos authenticator.
+ *
+ * FIXME: Probably need to examine errors a little more closely to
+ * determine if we should return a proxy-token error or a server-failure.
+ */
+ if (strncmp(server, "krb5:", 5) == 0)
+ server += 5;
+ status = webauth_krb5_mk_req(kctx, server, &tmp_auth, krb5_auth_len);
+ if (status != WA_ERR_NONE) {
+ if (status == WA_ERR_KRB5)
+ webauth_error_set(ctx, status, "%s",
+ webauth_krb5_error_message(kctx));
+ webauth_krb5_free(kctx);
+ return status;
+ } else {
+ *krb5_auth = apr_palloc(ctx->pool, *krb5_auth_len);
+ memcpy(*krb5_auth, tmp_auth, *krb5_auth_len);
+ free(tmp_auth);
+ }
+ webauth_krb5_free(kctx);
+ return WA_ERR_NONE;
+}
+
+
+/*
+ * Given a WebKDC proxy token and a request token, create the id token
+ * requested by the WAS and store it in the response. At this point, we've
+ * already done all required checks and ensured we have a WebKDC proxy token,
+ * so this just involves setting the correct fields. Returns a status code on
+ * any sort of internal WebAuth error.
+ */
+static int
+create_id_token(struct webauth_context *ctx,
+ struct webauth_webkdc_login_request *request,
+ struct webauth_token_webkdc_proxy *wkproxy,
+ struct webauth_webkdc_login_response *response,
+ WEBAUTH_KEYRING *keyring)
+{
+ int status;
+ void *krb5_auth;
+ size_t krb5_auth_len;
+ struct webauth_token token;
+ struct webauth_token_id *id;
+ struct webauth_token_request *req;
+
+ req = request->request;
+ memset(&token, 0, sizeof(token));
+ token.type = WA_TOKEN_ID;
+ id = &token.token.id;
+ id->subject = wkproxy->subject;
+ id->auth = req->auth;
+ if (strcmp(req->auth, "krb5") == 0) {
+ status = get_krb5_authenticator(ctx, request->service->subject,
+ wkproxy, &krb5_auth, &krb5_auth_len);
+ if (status == WA_ERR_KRB5) {
+ response->login_error = WA_PEC_PROXY_TOKEN_INVALID;
+ response->login_message = webauth_error_message(ctx, status);
+ return WA_ERR_NONE;
+ } else if (status != WA_ERR_NONE)
+ return status;
+ id->auth_data = krb5_auth;
+ id->auth_data_len = krb5_auth_len;
+ }
+ id->expiration = wkproxy->expiration;
+ id->initial_factors = wkproxy->initial_factors;
+ id->loa = wkproxy->loa;
+
+ /* FIXME: No idea what the session factors are. */
+ id->session_factors = "u";
+
+ /* Encode the token and store the resulting string. */
+ response->result_type = "id";
+ return webauth_token_encode(ctx, &token, keyring, &response->result);
+}
+
+
+/*
+ * Given a WebKDC proxy token and a request token, create the proxy token
+ * requested by the WAS and store it in the response. At this point, we've
+ * already done all required checks and ensured we have a WebKDC proxy token,
+ * so this just involves setting the correct fields. Returns a status code on
+ * any sort of internal WebAuth error.
+ *
+ * This function needs the WebKDC keyring, since it has to encode the
+ * embedded webkdc-proxy token in the WebKDC's private key. The first keyring
+ * is the session keyring for the enclosing proxy token, and the second is the
+ * WebKDC's private keyring.
+ */
+static int
+create_proxy_token(struct webauth_context *ctx,
+ struct webauth_webkdc_login_request *request,
+ struct webauth_token_webkdc_proxy *wkproxy,
+ struct webauth_webkdc_login_response *response,
+ WEBAUTH_KEYRING *session, WEBAUTH_KEYRING *keyring)
+{
+ int status;
+ struct webauth_token token, subtoken;
+ struct webauth_token_proxy *proxy;
+ struct webauth_token_request *req;
+
+ /* Create the easy portions of the proxy token. */
+ req = request->request;
+ memset(&token, 0, sizeof(token));
+ token.type = WA_TOKEN_PROXY;
+ proxy = &token.token.proxy;
+ proxy->subject = wkproxy->subject;
+ proxy->type = req->proxy_type;
+ proxy->initial_factors = wkproxy->initial_factors;
+ proxy->loa = wkproxy->loa;
+ proxy->expiration = wkproxy->expiration;
+
+ /* FIXME: No idea what the session factors are. */
+ proxy->session_factors = "u";
+
+ /* Create the embedded webkdc-proxy token and limit its scope. */
+ memset(&subtoken, 0, sizeof(subtoken));
+ subtoken.type = WA_TOKEN_WEBKDC_PROXY;
+ subtoken.token.webkdc_proxy = *wkproxy;
+ subtoken.token.webkdc_proxy.proxy_subject = request->service->subject;
+ subtoken.token.webkdc_proxy.creation = 0;
+ status = webauth_token_encode_raw(ctx, &subtoken, keyring,
+ &proxy->webkdc_proxy,
+ &proxy->webkdc_proxy_len);
+ if (status != WA_ERR_NONE)
+ return status;
+
+ /* Encode the token and store the resulting string. */
+ response->result_type = "proxy";
+ return webauth_token_encode(ctx, &token, session, &response->result);
+}
+
+
+/*
+ * Given the data from a <requestTokenRequest> login attempt, process that
+ * attempted login and return the information for a <requestTokenResponse> in
+ * a newly-allocated struct from pool memory. All of the tokens included in
+ * the input and output are the unencrypted struct representations; the caller
+ * does the encryption or decryption and base64 conversion.
+ *
+ * Returns WA_ERR_NONE if the request was successfully processed, which
+ * doesn't mean it succeeded; see the login_code attribute of the struct for
+ * that. Returns an error code if we were unable to process the struct even
+ * to generate an error response.
+ */
+int
+webauth_webkdc_login(struct webauth_context *ctx,
+ struct webauth_webkdc_login_request *request,
+ struct webauth_webkdc_login_response **response,
+ WEBAUTH_KEYRING *keyring)
+{
+ struct webauth_token *cred, *newproxy;
+ struct webauth_token **token;
+ struct webauth_token cancel;
+ struct webauth_token_request *req;
+ struct webauth_token_webkdc_proxy *wkproxy = NULL;
+ int i, status;
+ struct webauth_user_info *info = NULL;
+ const char *ip;
+ const char *etoken;
+ bool did_login = false;
+ size_t size;
+ WEBAUTH_KEY key;
+ WEBAUTH_KEYRING *session;
+
+ /* Basic sanity checking. */
+ if (request->service == NULL || request->creds == NULL
+ || request->request == NULL) {
+ status = WA_ERR_CORRUPT;
+ webauth_error_set(ctx, status, "incomplete login request data");
+ return status;
+ }
+
+ /* Shorter names for things we'll be referring to often. */
+ ip = request->remote_ip;
+ req = request->request;
+
+ /* Fill in the basics of our response. */
+ *response = apr_pcalloc(ctx->pool, sizeof(**response));
+ (*response)->return_url = req->return_url;
+ (*response)->requester = request->service->subject;
+ (*response)->app_state = req->state;
+ (*response)->app_state_len = req->state_len;
+
+ /*
+ * Several tokens, such as the login cancel token and the result token,
+ * have to be encrypted in the session key rather than in the WebKDC
+ * private key, since they're meant to be readable by the WAS. Create a
+ * keyring containing the session key we can use for those.
+ *
+ * FIXME: The conversion from the webkdc-service token to a key is an ugly
+ * hack.
+ */
+ key.type = WA_AES_KEY;
+ key.length = request->service->session_key_len;
+ key.data = (void *) request->service->session_key;
+ status = webauth_keyring_from_key(ctx, &key, &session);
+ if (status != WA_ERR_NONE)
+ return status;
+
+ /*
+ * If the WAS requested login cancel support, generate an error token
+ * representing a canceled login and store it in the response. We will
+ * return that token to WebLogin, which in turn will pass it (in the URL)
+ * back to the WAS if the user clicks on the cancel login link.
+ *
+ * FIXME: Use something less lame than strstr to see if the option is set.
+ */
+ if (req->options != NULL && strstr(req->options, "lc") != NULL) {
+ cancel.type = WA_TOKEN_ERROR;
+ cancel.token.error.code = WA_PEC_LOGIN_CANCELED;
+ cancel.token.error.message = "user canceled login";
+ status = webauth_token_encode(ctx, &cancel, session, &etoken);
+ if (status != WA_ERR_NONE)
+ return status;
+ (*response)->login_cancel = etoken;
+ }
+
+ /*
+ * Check for a login token in the supplied creds. If there is one, use it
+ * to authenticate and transform it into a webkdc-proxy token.
+ *
+ * FIXME: Stop modifying the array in place. This is surprising to the
+ * caller and makes the test suite more annoying.
+ *
+ * FIXME: How do we get the LoA set in the resulting webkdc-proxy token?
+ */
+ for (i = 0; i < request->creds->nelts; i++) {
+ cred = APR_ARRAY_IDX(request->creds, i, struct webauth_token *);
+ if (cred->type != WA_TOKEN_LOGIN)
+ continue;
+ token = apr_array_push(request->creds);
+ status = do_login(ctx, *response, &cred->token.login, token);
+ if (status != WA_ERR_NONE)
+ return status;
+ if (*token == NULL)
+ apr_array_pop(request->creds);
+ else
+ did_login = true;
+
+ /* If the login failed, return what we have so far. */
+ if ((*response)->login_error != 0)
+ return WA_ERR_NONE;
+ }
+
+ /*
+ * If there was a login token, all webkdc-proxy tokens also supplied must
+ * be WEBKDC tokens (in other words, global single-sign-on tokens). A WAS
+ * can't send a WAS-scoped webkdc-proxy token from a proxy token combined
+ * with a login token.
+ */
+ if (did_login)
+ for (i = 0; i < request->creds->nelts; i++) {
+ cred = APR_ARRAY_IDX(request->creds, i, struct webauth_token *);
+ if (cred->type != WA_TOKEN_WEBKDC_PROXY)
+ continue;
+ wkproxy = &cred->token.webkdc_proxy;
+ if (strncmp(wkproxy->proxy_subject, "WEBKDC:", 7) != 0) {
+ (*response)->login_error = WA_PEC_PROXY_TOKEN_INVALID;
+ (*response)->login_message
+ = apr_psprintf(ctx->pool, "proxy subject %s not allowed"
+ " with login token", wkproxy->proxy_subject);
+ return WA_ERR_NONE;
+ }
+ }
+
+ /*
+ * We have condensed all the user authentication information at this point
+ * to a set of webkdc-proxy tokens. However, we want one and only one
+ * webkdc-proxy token that has our combined factor information. If we did
+ * a login (meaning that we generated new webkdc-proxy information), we
+ * want to copy that new webkdc-proxy token into our output.
+ */
+ status = merge_webkdc_proxy(ctx, request->creds, &newproxy);
+ if (status != WA_ERR_NONE)
+ return status;
+ if (newproxy != NULL) {
+ struct webauth_webkdc_proxy_data *data;
+
+ wkproxy = &newproxy->token.webkdc_proxy;
+ size = sizeof(struct webauth_webkdc_proxy_data);
+ (*response)->proxies = apr_array_make(ctx->pool, 1, size);
+ data = apr_array_push((*response)->proxies);
+ data->type = wkproxy->proxy_type;
+ status = webauth_token_encode(ctx, newproxy, keyring, &data->token);
+ if (status != WA_ERR_NONE)
+ return status;
+ }
+
+ /*
+ * Determine the authenticated user.
+ *
+ * If we have configuration for a user metadata service, we now know as
+ * much as we're going to know about who the user is and should retrieve
+ * that information if possible. If we did a login, we should return
+ * login history if we have any.
+ */
+ if (wkproxy != NULL)
+ (*response)->subject = wkproxy->subject;
+ if (ctx->user != NULL && wkproxy != NULL) {
+ status = webauth_user_info(ctx, wkproxy->subject, ip, 0, &info);
+ if (status != WA_ERR_NONE)
+ return status;
+ if (did_login)
+ (*response)->logins = info->logins;
+ if (wkproxy->loa > info->max_loa)
+ wkproxy->loa = info->max_loa;
+ }
+
+ /*
+ * If we have no webkdc-proxy token, we're done; we can't authenticate the
+ * user, so bounce them back to the WebLogin screen with what information
+ * we do have.
+ */
+ if (wkproxy == NULL) {
+ (*response)->login_error = WA_PEC_PROXY_TOKEN_REQUIRED;
+ (*response)->login_message = "need a proxy token";
+ return WA_ERR_NONE;
+ }
+
+ /*
+ * If forced login is set and we didn't just process a login token, error
+ * out with the error code for forced login, instructing WebLogin to put
+ * up the login screen.
+ *
+ * FIXME: strstr is still lame.
+ */
+ if (!did_login)
+ if (req->options != NULL && strstr(req->options, "fa") != NULL) {
+ (*response)->login_error = WA_PEC_LOGIN_FORCED;
+ (*response)->login_message = "forced authentication, need to login";
+ return WA_ERR_NONE;
+ }
+
+ /*
+ * If the user metadata service says that multifactor is required, reject
+ * the login with either multifactor required or with multifactor
+ * unavailable, depending on whether the user has multifactor configured.
+ */
+ status = check_multifactor(ctx, request, *response, info);
+ if (status != WA_ERR_NONE)
+ return status;
+ if ((*response)->login_error != 0)
+ return WA_ERR_NONE;
+
+ /*
+ * We have to ensure that the webkdc-proxy token we have available is
+ * capable of satisfying the request from the WAS. This is always the
+ * case if the WAS just wants an id token of type webkdc (a simple
+ * identity assertion), but if the WAS asked for a krb5 id or proxy token,
+ * we have to have a krb5 webkdc-proxy token.
+ */
+ if ((strcmp(req->type, "id") == 0 && strcmp(req->auth, "krb5") == 0)
+ || (strcmp(req->type, "proxy") == 0
+ && strcmp(req->proxy_type, "krb5") == 0))
+ if (strcmp(wkproxy->proxy_type, "krb5") != 0) {
+ (*response)->login_error = WA_PEC_PROXY_TOKEN_REQUIRED;
+ (*response)->login_message = "need a proxy token";
+ return WA_ERR_NONE;
+ }
+
+ /*
+ * Protect against an attacker using the WebLogin XML interface and
+ * sending, as the webkdc-proxy token, a webkdc-proxy token obtained by a
+ * WAS to use to get delegated credentials. That's only allowed to
+ * generate an id token if it's for the WAS that we're talking to.
+ */
+ if (wkproxy != NULL
+ && strncmp(wkproxy->proxy_subject, "WEBKDC:", 7) != 0
+ && strcmp(wkproxy->proxy_subject, request->service->subject) != 0) {
+ (*response)->login_error = WA_PEC_UNAUTHORIZED;
+ (*response)->login_message = "not authorized to use proxy token";
+ return WA_ERR_NONE;
+ }
+
+ /*
+ * We have a single (or no) webkdc-proxy token that contains everything we
+ * know about the user. Attempt to satisfy their request.
+ */
+ if (strcmp(req->type, "id") == 0)
+ status = create_id_token(ctx, request, wkproxy, *response, session);
+ else if (strcmp(req->type, "proxy") == 0)
+ status = create_proxy_token(ctx, request, wkproxy, *response, session,
+ keyring);
+ else {
+ status = WA_ERR_CORRUPT;
+ webauth_error_set(ctx, status, "unsupported requested token type %s",
+ req->type);
+ }
+ return status;
+}
diff --git a/modules/webkdc/config.c b/modules/webkdc/config.c
index c6ebfac5..10d28477 100644
--- a/modules/webkdc/config.c
+++ b/modules/webkdc/config.c
@@ -26,6 +26,7 @@
#include <modules/webkdc/mod_webkdc.h>
#include <util/macros.h>
#include <webauth/util.h>
+#include <webauth/webkdc.h>
/*
* For each directive, we have the directive name (CD_), a usage string (CU_),
@@ -57,6 +58,8 @@ DIRN(ProxyTokenLifetime, "lifetime of webkdc-proxy tokens")
DIRN(ServiceTokenLifetime,"lifetime of webkdc-service tokens")
DIRN(TokenAcl, "path to the token ACL file")
DIRD(TokenMaxTTL, "max lifetime of recent tokens", int, 60 * 5)
+DIRN(UserInfoURL, "URL to user metadata service")
+DIRN(UserInfoPrincipal, "authentication identity of the metadata service")
enum {
E_Debug,
@@ -70,6 +73,8 @@ enum {
E_ServiceTokenLifetime,
E_TokenAcl,
E_TokenMaxTTL,
+ E_UserInfoURL,
+ E_UserInfoPrincipal,
};
/*
@@ -150,6 +155,8 @@ webkdc_config_merge(apr_pool_t *pool, void *basev, void *overv)
MERGE_PTR(keytab_path);
MERGE_PTR_OTHER(keytab_principal, keytab_path);
MERGE_PTR(token_acl_path);
+ MERGE_PTR(userinfo_config);
+ MERGE_PTR(userinfo_principal);
MERGE_SET(debug);
MERGE_SET(keyring_auto_update);
MERGE_SET(key_lifetime);
@@ -245,6 +252,38 @@ parse_interval(cmd_parms *cmd, const char *arg, unsigned long *value)
/*
+ * Utility function for parsing a user metadata service URL. This also does
+ * validation of the URL and the protocol to ensure that it represents a
+ * supported user metadata service. Returns an error string or NULL on
+ * success. The URL will be of the form:
+ *
+ * remctl://hostname.example.com:4373/oath
+ *
+ * where the path portion is the remctl command name.
+ */
+static const char *
+parse_userinfo_url(cmd_parms *cmd, const char *arg,
+ struct webauth_user_config *config)
+{
+ apr_uri_t uri;
+ int status;
+
+ status = apr_uri_parse(cmd->pool, arg, &uri);
+ if (status != APR_SUCCESS)
+ return apr_psprintf(cmd->pool, "Invalid user metadata service URL"
+ " \"%s\" for %s", arg, cmd->directive->directive);
+ if (strcmp(uri.scheme, "remctl") != 0)
+ return apr_psprintf(cmd->pool, "Unknown user metadata protocol \"%s\""
+ " for %s", uri.scheme, cmd->directive->directive);
+ config->protocol = WA_PROTOCOL_REMCTL;
+ config->host = uri.hostname;
+ config->port = uri.port;
+ config->command = uri.path;
+ return NULL;
+}
+
+
+/*
* Return the error message for an internal error parsing a configuration
* directive. This happens when the wrong configuration handling routine is
* called for a directive and indicates a coding error in the configuration
@@ -310,6 +349,14 @@ cfg_str(cmd_parms *cmd, void *mconf UNUSED, const char *arg)
if (err == NULL)
sconf->token_max_ttl_set = true;
break;
+ case E_UserInfoURL:
+ sconf->userinfo_config
+ = apr_palloc(cmd->pool, sizeof(struct webauth_user_config));
+ err = parse_userinfo_url(cmd, arg, sconf->userinfo_config);
+ break;
+ case E_UserInfoPrincipal:
+ sconf->userinfo_principal = arg;
+ break;
default:
err = unknown_error(cmd, directive, "cfg_str");
break;
@@ -396,5 +443,7 @@ const command_rec webkdc_cmds[] = {
DIRECTIVE(AP_INIT_TAKE1, cfg_str, ServiceTokenLifetime),
DIRECTIVE(AP_INIT_TAKE1, cfg_str, TokenAcl),
DIRECTIVE(AP_INIT_TAKE1, cfg_str, TokenMaxTTL),
+ DIRECTIVE(AP_INIT_TAKE1, cfg_str, UserInfoURL),
+ DIRECTIVE(AP_INIT_TAKE1, cfg_str, UserInfoPrincipal),
{ NULL, { NULL }, NULL, OR_NONE, RAW_ARGS, NULL }
};
diff --git a/modules/webkdc/mod_webkdc.c b/modules/webkdc/mod_webkdc.c
index ef898442..1860e199 100644
--- a/modules/webkdc/mod_webkdc.c
+++ b/modules/webkdc/mod_webkdc.c
@@ -26,6 +26,8 @@
#include <util/macros.h>
#include <webauth/basic.h>
#include <webauth/keys.h>
+#include <webauth/tokens.h>
+#include <webauth/webkdc.h>
/*
@@ -701,30 +703,6 @@ get_krb5_sad(MWK_REQ_CTXT *rc,
/*
*/
static enum mwk_status
-create_error_token_from_req(MWK_REQ_CTXT *rc,
- int error_code,
- const char *error_message,
- MWK_REQUESTER_CREDENTIAL *req_cred,
- MWK_RETURNED_TOKEN *rtoken)
-{
- static const char *mwk_func = "create_error_token_from_req";
- enum mwk_status ms;
- struct webauth_token token;
-
- memset(&token, 0, sizeof(token));
- token.type = WA_TOKEN_ERROR;
- token.token.error.code = error_code;
- token.token.error.message = error_message;
-
- ms = make_token_with_key(rc, req_cred->u.st.session_key,
- req_cred->u.st.session_key_len, &token,
- &rtoken->token_data, mwk_func);
- return ms;
-}
-
-/*
- */
-static enum mwk_status
create_id_token_from_req(MWK_REQ_CTXT *rc,
const char *auth_type,
MWK_REQUESTER_CREDENTIAL *req_cred,
@@ -889,7 +867,7 @@ create_proxy_token_from_req(MWK_REQ_CTXT *rc,
mwk_func, true);
}
- /* make sure we are creating a proxy-tyoken that has
+ /* make sure we are creating a proxy-token that has
the same type as the proxy-token we are using to create it */
sub_pt = find_proxy_token(rc, sub_cred, proxy_type, mwk_func, 1);
if (sub_pt == NULL)
@@ -1481,199 +1459,160 @@ get_subject(MWK_REQ_CTXT *rc, WEBAUTH_KRB5_CTXT *ctxt,
return MWK_OK;
}
+
/*
- * attempt to login. If successful, fill in both sub_cred and rtokens and
- * return MWK_OK. If unsuccessful, generate an errorResponse/log and return
- * MWK_ERROR.
- *
- * This is the point at which different types of authentication could be
- * plugged in, and the point at which we should create all the different types
- * of proxy-tokens we'll be needing at login time.
+ * Parses a <requesterCredential> XML element containing a service token and
+ * stores it in the provided webauth_webkdc_login_request struct after
+ * decoding it and confirming that it's the right type. Use
+ * parse_requesterCredential to support either service or krb5 credentials.
*/
static enum mwk_status
-mwk_do_login(MWK_REQ_CTXT *rc,
- struct webauth_token_login *lt,
- MWK_SUBJECT_CREDENTIAL *sub_cred,
- MWK_RETURNED_PROXY_TOKEN rtokens[],
- size_t *num_rtokens,
- const char **subject_out)
+parse_service_token(MWK_REQ_CTXT *rc, apr_xml_elem *e,
+ struct webauth_webkdc_login_request *request)
{
- static const char*mwk_func = "mwk_do_login";
- WEBAUTH_KRB5_CTXT *ctxt;
- const char *subject;
- char *server_principal, *temp;
+ static const char *mwk_func = "parse_service_token";
int status;
- size_t tgt_len;
- enum mwk_status ms;
- time_t tgt_expiration;
- char *tgt;
- struct webauth_token_webkdc_proxy *pt;
- struct webauth_token data;
-
- *subject_out = NULL;
+ struct webauth_token *data;
+ const char *at = get_attr_value(rc, e, "type", 1, mwk_func);
+ char *msg, *token;
- ms = MWK_ERROR;
+ if (rc->sconf->ring == NULL)
+ return set_errorResponse(rc, WA_PEC_SERVER_FAILURE, "no keyring",
+ mwk_func, true);
- ctxt = mwk_get_webauth_krb5_ctxt(rc->r, mwk_func);
- if (ctxt == NULL) {
- /* mwk_get_webauth_krb5_ctxt already logged error */
- return set_errorResponse(rc, WA_PEC_SERVER_FAILURE,
- "server failure", mwk_func, false);
+ /* Make sure that the provided token type is service. */
+ if (at == NULL)
+ return MWK_ERROR;
+ if (strcmp(at, "service") != 0) {
+ msg = apr_psprintf(rc->r->pool, "unknown <requesterCredential> type:"
+ " %s", at);
+ return set_errorResponse(rc, WA_PEC_INVALID_REQUEST, msg, mwk_func,
+ true);
}
- status = webauth_krb5_init_via_password(ctxt,
- lt->username,
- lt->password,
- NULL,
- rc->sconf->keytab_path,
- rc->sconf->keytab_principal,
- NULL,
- &server_principal);
-
- if (status == WA_ERR_LOGIN_FAILED) {
- char *msg = mwk_webauth_error_message(rc->r, status, ctxt,
- "mwk_do_login", NULL);
- set_errorResponse(rc, WA_PEC_LOGIN_FAILED, msg, mwk_func, true);
- goto cleanup;
-
- } else if (status == WA_ERR_CREDS_EXPIRED) {
- char *msg = mwk_webauth_error_message(rc->r, status, ctxt,
- "mwk_do_login", NULL);
- set_errorResponse(rc, WA_PEC_CREDS_EXPIRED, msg, mwk_func, true);
- goto cleanup;
-
- } else if (status == WA_ERR_USER_REJECTED) {
- char *msg = mwk_webauth_error_message(rc->r, status, ctxt,
- "mwk_do_login", NULL);
- set_errorResponse(rc, WA_PEC_USER_REJECTED, msg, mwk_func, true);
- goto cleanup;
-
- } else if (status != WA_ERR_NONE) {
- char *msg = mwk_webauth_error_message(rc->r, status, ctxt,
- "webauth_krb5_init_via_password",
- NULL);
- set_errorResponse(rc, WA_PEC_SERVER_FAILURE, msg, mwk_func, true);
- goto cleanup;
+ token = get_elem_text(rc, e, mwk_func);
+ if (token == NULL)
+ return MWK_ERROR;
+ status = webauth_token_decode(rc->ctx, WA_TOKEN_WEBKDC_SERVICE, token,
+ rc->sconf->ring, &data);
+ if (status != WA_ERR_NONE) {
+ mwk_log_webauth_error(rc->r->server, status, NULL, mwk_func,
+ "webauth_token_decode", NULL);
+ if (status == WA_ERR_TOKEN_EXPIRED) {
+ set_errorResponse(rc, WA_PEC_SERVICE_TOKEN_EXPIRED,
+ "service token was expired",
+ mwk_func, false);
+ } else if (status == WA_ERR_BAD_HMAC) {
+ set_errorResponse(rc, WA_PEC_SERVICE_TOKEN_INVALID,
+ "can't decrypt service token", mwk_func,
+ false);
+ } else {
+ set_errorResponse(rc, WA_PEC_SERVICE_TOKEN_INVALID,
+ "error parsing token", mwk_func, false);
+ }
+ return MWK_ERROR;
}
+ request->service = &data->token.webkdc_service;
+ return MWK_OK;
+}
- /* copy server_principal to request pool */
- temp = apr_pstrcat(rc->r->pool, "krb5:", server_principal, NULL);
- free(server_principal);
- server_principal = temp;
-
- /* Check if the realm of the authenticated principal is permitted. */
- if (realm_permitted(rc, ctxt, mwk_func) != MWK_OK)
- goto cleanup;
-
- /* get subject, attempt local-name conversion */
- if (get_subject(rc, ctxt, &subject, mwk_func) != MWK_OK)
- goto cleanup;
- *subject_out = subject;
- /* export TGT for webkdc-proxy-token */
- status = webauth_krb5_export_tgt(ctxt, &tgt, &tgt_len, &tgt_expiration);
- if (status != WA_ERR_NONE) {
- char *msg = mwk_webauth_error_message(rc->r, status, ctxt,
- "webauth_krb5_export_tgt",
- NULL);
- set_errorResponse(rc, WA_PEC_SERVER_FAILURE, msg, mwk_func, true);
- goto cleanup;
- } else {
- char *new_tgt = apr_palloc(rc->r->pool, tgt_len);
- memcpy(new_tgt, tgt, tgt_len);
- free(tgt);
- tgt = new_tgt;
- }
+/*
+ * Parse a <subjectCredential> XML element passed in to <requestTokenRequest>
+ * into a webauth_webkdc_login_request struct. Use parse_subjectCredential
+ * for <getTokens> for right now.
+ */
+static enum mwk_status
+parse_subject_credentials(MWK_REQ_CTXT *rc, apr_xml_elem *e,
+ struct webauth_webkdc_login_request *request)
+{
+ static const char *mwk_func = "parse_subject_credentials";
+ apr_xml_elem *child;
+ char *data;
+ struct webauth_token *token;
+ struct webauth_token_webkdc_proxy *wkproxy;
- /* we now have everything we need to create the webkdc-proy-token
- * lets load up data in the sub_cred proxy token and use it
- * to create a token we'll return.
- *
- * we've already copied all this stuff into a pool, so there is no
- * need to copy again...
- */
- pt = &sub_cred->u.proxy.pt[0];
- pt->subject = subject;
- pt->proxy_type = "krb5";
- pt->proxy_subject = apr_pstrcat(rc->r->pool, "WEBKDC:",
- server_principal, NULL);
- pt->data = tgt;
- pt->data_len = tgt_len;
+ if (request->creds == NULL)
+ request->creds = apr_array_make(rc->r->pool, 2,
+ sizeof(struct webauth_token *));
/*
- * FIXME: We only support password authentication right now, so we
- * hard-code the factors. This will change once we add multifactor
- * support.
+ * Just quietly ignore invalid webkdc-proxy tokens for right now.
+ *
+ * FIXME: Restore a facility for telling WebLogin to discard uninteresting
+ * webkdc-proxy tokens.
+ *
+ * FIXME: We're calling functions that get generic tokens and collapse
+ * them into the appropriate type, and then allocating a generic token and
+ * copying it back. This is silly. Restructure all this code.
*/
- pt->initial_factors = apr_pstrdup(rc->r->pool, "p");
- pt->loa = 0;
-
- /* if ProxyTokenLifetime is non-zero, use the min of it
- and the tgt, else just use the tgt */
- if (rc->sconf->proxy_lifetime) {
- time_t pmax = time(NULL) + rc->sconf->proxy_lifetime;
-
- pt->expiration = (tgt_expiration < pmax) ? tgt_expiration : pmax;
- } else {
- pt->expiration = tgt_expiration;
- }
-
- /* Make the base64-encoded token. */
- memset(&data, 0, sizeof(data));
- data.type = WA_TOKEN_WEBKDC_PROXY;
- data.token.webkdc_proxy = *pt;
- ms = make_token(rc, &data, &rtokens[0].token_data, mwk_func);
- if (ms == MWK_OK) {
- rtokens[0].type = pt->proxy_type;
- *num_rtokens = 1;
- sub_cred->u.proxy.num_proxy_tokens = 1;
- /* make sure we fill in type! */
- sub_cred->type = "proxy";
+ for (child = e->first_child; child != NULL; child = child->next) {
+ if (strcmp(child->name, "proxyToken") == 0) {
+ data = get_elem_text(rc, child, mwk_func);
+ if (data == NULL)
+ return MWK_ERROR;
+ token = apr_pcalloc(rc->r->pool, sizeof(struct webauth_token));
+ token->type = WA_TOKEN_WEBKDC_PROXY;
+ wkproxy = &token->token.webkdc_proxy;
+ if (!parse_webkdc_proxy_token(rc, data, wkproxy))
+ continue;
+ APR_ARRAY_PUSH(request->creds, struct webauth_token *) = token;
+ } else if (strcmp(child->name, "loginToken") == 0) {
+ data = get_elem_text(rc, child, mwk_func);
+ if (data == NULL)
+ return MWK_ERROR;
+ token = apr_pcalloc(rc->r->pool, sizeof(struct webauth_token));
+ token->type = WA_TOKEN_LOGIN;
+ if (!parse_login_token(rc, data, &token->token.login))
+ return MWK_ERROR;
+ APR_ARRAY_PUSH(request->creds, struct webauth_token *) = token;
+ } else {
+ unknown_element(rc, mwk_func, e->name, child->name);
+ return MWK_ERROR;
+ }
}
-
- cleanup:
- webauth_krb5_free(ctxt);
- return ms;
+ return MWK_OK;
}
+
static enum mwk_status
-parse_requestInfo(MWK_REQ_CTXT *rc,
- apr_xml_elem *e, MWK_REQUEST_INFO *req_info)
+parse_requestInfo(MWK_REQ_CTXT *rc, apr_xml_elem *e,
+ struct webauth_webkdc_login_request *request)
{
+ static const char *mwk_func = "parse_requestInfo";
apr_xml_elem *ie;
- static const char *mwk_func="parse_identInfo";
for (ie = e->first_child; ie; ie = ie->next) {
if (strcmp(ie->name, "localIpAddr") == 0) {
- req_info->local_addr = get_elem_text(rc, ie, mwk_func);
- if (req_info->local_addr == NULL)
+ request->local_ip = get_elem_text(rc, ie, mwk_func);
+ if (request->local_ip == NULL)
return MWK_ERROR;
} else if (strcmp(ie->name, "localIpPort") == 0) {
- req_info->local_port = get_elem_text(rc, ie, mwk_func);
- if (req_info->local_port == NULL)
+ request->local_port = get_elem_text(rc, ie, mwk_func);
+ if (request->local_port == NULL)
return MWK_ERROR;
} else if (strcmp(ie->name, "remoteIpAddr") == 0) {
- req_info->remote_addr = get_elem_text(rc, ie, mwk_func);
- if (req_info->remote_addr == NULL)
+ request->remote_ip = get_elem_text(rc, ie, mwk_func);
+ if (request->remote_ip == NULL)
return MWK_ERROR;
} else if (strcmp(ie->name, "remoteIpPort") == 0) {
- req_info->remote_port = get_elem_text(rc, ie, mwk_func);
- if (req_info->remote_port == NULL)
+ request->remote_port = get_elem_text(rc, ie, mwk_func);
+ if (request->remote_port == NULL)
return MWK_ERROR;
} else if (strcmp(ie->name, "remoteUser") == 0) {
- req_info->remote_user = get_elem_text(rc, ie, mwk_func);
- if (req_info->remote_user == NULL)
+ request->remote_user = get_elem_text(rc, ie, mwk_func);
+ if (request->remote_user == NULL)
return MWK_ERROR;
} else {
unknown_element(rc, mwk_func, e->name, ie->name);
return MWK_ERROR;
}
}
- if (req_info->remote_user == NULL
- && (req_info->local_addr == NULL ||
- req_info->local_port == NULL ||
- req_info->remote_addr == NULL ||
- req_info->remote_port == NULL)) {
+ if (request->remote_user == NULL
+ && (request->local_ip == NULL ||
+ request->local_port == NULL ||
+ request->remote_ip == NULL ||
+ request->remote_port == NULL)) {
return set_errorResponse(rc, WA_PEC_INVALID_REQUEST,
"<requestInfo> missing data",
mwk_func, true);
@@ -1681,6 +1620,29 @@ parse_requestInfo(MWK_REQ_CTXT *rc,
return MWK_OK;
}
+
+/*
+ * Given the request context, an XML tag, and an array of const char *, print
+ * out the contents of the array as a series of that XML element. If the
+ * array is NULL, prints out nothing.
+ */
+static void
+print_xml_array(MWK_REQ_CTXT *rc, const char *tag, apr_array_header_t *array)
+{
+ int i;
+ const char *string;
+
+ if (array == NULL)
+ return;
+
+ for (i = 0; i < array->nelts; i++) {
+ string = APR_ARRAY_IDX(array, i, const char *);
+ string = apr_xml_quote_string(rc->r->pool, string, false);
+ ap_rvputs(rc->r, "<%s>%s</%s>", tag, string, tag, NULL);
+ }
+}
+
+
static enum mwk_status
handle_requestTokenRequest(MWK_REQ_CTXT *rc, apr_xml_elem *e,
const char **req_subject_out,
@@ -1688,55 +1650,37 @@ handle_requestTokenRequest(MWK_REQ_CTXT *rc, apr_xml_elem *e,
{
apr_xml_elem *child;
static const char *mwk_func="handle_requestTokenRequest";
- char *request_token;
- MWK_REQUEST_INFO req_info;
- MWK_REQUESTER_CREDENTIAL req_cred;
- MWK_SUBJECT_CREDENTIAL parsed_sub_cred, login_sub_cred, *sub_cred;
- enum mwk_status ms;
- struct webauth_token_request *req_token;
- int req_cred_parsed = 0;
- int sub_cred_parsed = 0;
- size_t i;
- int did_login;
- int login_ec;
- const char *login_em = NULL;
+ char *request_token = NULL;
+ int i, status;
const char *req_token_info;
- MWK_RETURNED_TOKEN rtoken;
- MWK_RETURNED_PROXY_TOKEN rptokens[MAX_PROXY_TOKENS_RETURNED];
- size_t num_proxy_tokens = 0;
+ struct webauth_user_info *info = NULL;
+ struct webauth_webkdc_login_request request;
+ struct webauth_webkdc_login_response *response;
+ /*
+ * FIXME: These should be set to NULL, not <unknown>. Chase down all the
+ * places we assume they aren't NULL.
+ */
*subject_out = "<unknown>";
*req_subject_out = "<unkknown>";
- did_login = 0;
- num_proxy_tokens = 0;
- login_ec = 0;
- request_token = NULL;
- sub_cred = NULL;
req_token_info = "";
-
- memset(&req_cred, 0, sizeof(req_cred));
- memset(&rtoken, 0, sizeof(rtoken));
- memset(&req_info, 0, sizeof(req_info));
+ memset(&request, 0, sizeof(request));
/* walk through each child element in <requestTokenRequest> */
for (child = e->first_child; child; child = child->next) {
if (strcmp(child->name, "requesterCredential") == 0) {
- if (!parse_requesterCredential(rc, child, &req_cred,
- req_subject_out))
+ if (!parse_service_token(rc, child, &request))
return MWK_ERROR;
- req_cred_parsed = 1;
} else if (strcmp(child->name, "subjectCredential") == 0) {
- if (!parse_subjectCredential(rc, child, &parsed_sub_cred,
- &num_proxy_tokens, rptokens))
+ if (!parse_subject_credentials(rc, child, &request))
return MWK_ERROR;
- sub_cred_parsed = 1;
} else if (strcmp(child->name, "requestToken") == 0) {
request_token = get_elem_text(rc, child, mwk_func);
if (request_token == NULL)
return MWK_ERROR;
} else if (strcmp(child->name, "requestInfo") == 0) {
- if (!parse_requestInfo(rc, child, &req_info))
+ if (!parse_requestInfo(rc, child, &request))
return MWK_ERROR;
} else {
unknown_element(rc, mwk_func, e->name, child->name);
@@ -1745,26 +1689,23 @@ handle_requestTokenRequest(MWK_REQ_CTXT *rc, apr_xml_elem *e,
}
/* make sure we found requesterCredential */
- if (!req_cred_parsed) {
+ if (request.service == NULL) {
return set_errorResponse(rc, WA_PEC_INVALID_REQUEST,
"missing <requesterCredential>",
mwk_func, true);
}
+ if (request.service != NULL)
+ *req_subject_out = request.service->subject;
- /* make sure we found subjectCredentials */
- if (!sub_cred_parsed) {
+ /*
+ * Make sure we found <subjectCredential>. Note that the array may be
+ * legitimately empty if the user has no proxy credentials and it's their
+ * first visit to WebLogin.
+ */
+ if (request.creds == NULL)
return set_errorResponse(rc, WA_PEC_INVALID_REQUEST,
"missing <subjectCredential>",
mwk_func, true);
- }
-
- /* make sure req_cred is of type "service" */
- if (strcmp(req_cred.type, "service") != 0) {
- return set_errorResponse(rc, WA_PEC_INVALID_REQUEST,
- "must use <requesterCredential> "
- "of type 'service'",
- mwk_func, true);
- }
/* make sure we found requestToken */
if (request_token == NULL) {
@@ -1772,197 +1713,212 @@ handle_requestTokenRequest(MWK_REQ_CTXT *rc, apr_xml_elem *e,
"missing <requestToken>",
mwk_func, true);
}
-
- if (!parse_request_token(rc, request_token, &req_cred.u.st, &req_token)) {
+ if (!parse_request_token(rc, request_token, request.service,
+ &request.request))
return MWK_ERROR;
+
+ /*
+ * Based on the type of token requested, check that the requesting WAS is
+ * permitted to get that type of token.
+ */
+ if (strcmp(request.request->type, "id") == 0) {
+ if (!mwk_has_id_access(rc, request.service->subject)) {
+ return set_errorResponse(rc, WA_PEC_UNAUTHORIZED,
+ "not authorized to get an id token",
+ mwk_func, true);
+ }
+ } else if (strcmp(request.request->type, "proxy") == 0) {
+ if (!mwk_has_proxy_access(rc, request.service->subject,
+ request.request->proxy_type)) {
+ return set_errorResponse(rc, WA_PEC_UNAUTHORIZED,
+ "not authorized to get a proxy token",
+ mwk_func, true);
+ }
}
/*
- * if we have a login-token, attempt to login with it,
- * and if that succeeds, we'll get a new MWK_SUBJECT_CREDENTIAL
- * to pass around, and new proxy-tokens to set.
- *
+ * All of the supplied webkdc-proxy credentials, if any, must be for the
+ * same authenticated user (the same subject) and must be usable by the
+ * same entity (the same proxy_subject). We don't have to check login
+ * tokens here, since we'll do that later in webauth_webkdc_login when we
+ * turn those login tokens into webkdc-proxy tokens.
*/
- if (strcmp(parsed_sub_cred.type, "login") == 0) {
- if (!mwk_do_login(rc, &parsed_sub_cred.u.lt, &login_sub_cred,
- rptokens, &num_proxy_tokens, subject_out)) {
- *subject_out = parsed_sub_cred.u.lt.username;
- if (rc->error_code == WA_PEC_LOGIN_FAILED) {
- login_ec = rc->error_code;
- login_em = rc->error_message;
- rc->error_code = 0;
- rc->error_message = NULL;
- goto send_response;
+ if (request.creds != NULL && request.creds->nelts > 0) {
+ struct webauth_token *token;
+ struct webauth_token_webkdc_proxy *wkproxy;
+ const char *subject = NULL;
+ const char *proxy_subject = NULL;
+
+ for (i = 0; i < request.creds->nelts; i++) {
+ token = APR_ARRAY_IDX(request.creds, 0, struct webauth_token *);
+ if (token->type == WA_TOKEN_LOGIN)
+ continue;
+ wkproxy = &token->token.webkdc_proxy;
+ if (subject == NULL) {
+ subject = wkproxy->subject;
+ proxy_subject = wkproxy->proxy_subject;
} else {
- return MWK_ERROR;
- }
- }
- sub_cred = &login_sub_cred;
- did_login = 1;
- } else {
- sub_cred = &parsed_sub_cred;
- /* grab first subject from passed in proxy tokens as subject_out */
- if (strcmp(parsed_sub_cred.type, "proxy") == 0) {
- for (i=0; i < parsed_sub_cred.u.proxy.num_proxy_tokens; i++) {
- if (parsed_sub_cred.u.proxy.pt[i].subject) {
- *subject_out = parsed_sub_cred.u.proxy.pt[i].subject;
- break;
+ if (strcmp(subject, wkproxy->subject) != 0
+ || strcmp(proxy_subject, wkproxy->proxy_subject) != 0) {
+ return set_errorResponse(rc, WA_PEC_UNAUTHORIZED,
+ "not authorized to get a proxy"
+ " token", mwk_func, true);
}
}
}
}
+
+ /*
+ * Call into libwebauth to process the login information. This will take
+ * the accumulated data in the request and attempt to fulfill it. On an
+ * internal error, this function will return a status other than
+ * WA_ERR_NONE. Otherwise, it may set the login_error and login_message
+ * in the response. We handle that below when we generate the XML
+ * response.
+ */
+ status = webauth_webkdc_login(rc->ctx, &request, &response,
+ rc->sconf->ring);
+ if (status != WA_ERR_NONE)
+ return set_errorResponse(rc, WA_PEC_SERVER_FAILURE,
+ webauth_error_message(rc->ctx, status),
+ mwk_func, true);
- /* lets see if they requested forced-authentication, if so
- and we didn't just login, then we need
- to return an error that will cause the web front-end to
- prompt for a username/password */
- if (req_token->options != NULL
- && ap_strstr(req_token->options, "fa") != 0
- && !did_login) {
- login_ec = WA_PEC_LOGIN_FORCED;
- login_em = "forced authentication, need to login";
- goto send_response;
- }
-
- /* now examine req_token to see what they asked for */
-
- if (strcmp(req_token->type, "id") == 0) {
- ms = create_id_token_from_req(rc, req_token->auth,
- &req_cred, sub_cred, &rtoken,
- subject_out);
- req_token_info = apr_pstrcat(rc->r->pool,
- " sa=",
- req_token->auth,
+ /* Accumulate logging information about what we were asked to do. */
+ if (strcmp(request.request->type, "id") == 0)
+ req_token_info = apr_pstrcat(rc->r->pool, " sa=", request.request->auth,
NULL);
- } else if (strcmp(req_token->type, "proxy") == 0) {
- ms = create_proxy_token_from_req(rc, req_token->proxy_type,
- &req_cred, sub_cred, &rtoken);
- req_token_info = apr_pstrcat(rc->r->pool,
- " pt=",
- req_token->proxy_type,
- NULL);
- } else {
- char *msg = apr_psprintf(rc->r->pool,
- "unsupported requested-token-type: %s",
- req_token->type);
- set_errorResponse(rc, WA_PEC_INVALID_REQUEST, msg,
- mwk_func, true);
- return MWK_ERROR;
- }
-
- if (ms != MWK_OK) {
- switch (rc->error_code) {
- case WA_PEC_PROXY_TOKEN_REQUIRED:
- login_ec = rc->error_code;
- login_em = rc->error_message;
- goto send_response;
- default:
- return MWK_ERROR;
- }
- }
-
- send_response:
+ else if (strcmp(request.request->type, "proxy") == 0)
+ req_token_info = apr_pstrcat(rc->r->pool, " pt=",
+ request.request->proxy_type, NULL);
+ if (response->subject != NULL)
+ *subject_out = response->subject;
+ /*
+ * If we saw an error other than proxy token required, abort and send the
+ * error message.
+ */
+ if (response->login_error != 0
+ && response->login_error != WA_PEC_PROXY_TOKEN_REQUIRED
+ && response->login_error != WA_PEC_MULTIFACTOR_REQUIRED
+ && response->login_error != WA_PEC_MULTIFACTOR_UNAVAILABLE
+ && response->login_error != WA_PEC_LOA_UNAVAILABLE)
+ return set_errorResponse(rc, response->login_error,
+ response->login_message, mwk_func, true);
+
+ /* Send the XML response. */
ap_rvputs(rc->r, "<requestTokenResponse>", NULL);
- if (login_ec) {
- ap_rprintf(rc->r, "<loginErrorCode>%d</loginErrorCode>", login_ec);
+ if (response->login_error != 0) {
+ ap_rprintf(rc->r, "<loginErrorCode>%d</loginErrorCode>",
+ response->login_error);
ap_rprintf(rc->r, "<loginErrorMessage>%s</loginErrorMessage>",
- apr_xml_quote_string(rc->r->pool, login_em, 0));
+ apr_xml_quote_string(rc->r->pool, response->login_message,
+ false));
}
- if (num_proxy_tokens) {
+ if (response->login_error == WA_PEC_MULTIFACTOR_REQUIRED) {
+ ap_rvputs(rc->r, "<multifactorRequired>", NULL);
+ print_xml_array(rc, "factor", response->factors_wanted);
+ print_xml_array(rc, "configuredFactor", response->factors_configured);
+ ap_rvputs(rc->r, "</multifactorRequired>", NULL);
+ }
+
+ if (response->proxies != NULL) {
+ struct webauth_webkdc_proxy_data *data;
+
ap_rvputs(rc->r, "<proxyTokens>", NULL);
- for (i = 0; i < num_proxy_tokens; i++) {
- ap_rvputs(rc->r, "<proxyToken type='", rptokens[i].type,"'>",
- /* don't have to quote since base64'd data */
- rptokens[i].token_data,
- "</proxyToken>",
- NULL);
+ for (i = 0; i < response->proxies->nelts; i++) {
+ data = &APR_ARRAY_IDX(response->proxies, i,
+ struct webauth_webkdc_proxy_data);
+ ap_rvputs(rc->r, "<proxyToken type='", data->type, "'>",
+ data->token, "</proxyToken>", NULL);
}
ap_rvputs(rc->r, "</proxyTokens>", NULL);
}
/* put out return-url */
ap_rvputs(rc->r,"<returnUrl>",
- apr_xml_quote_string(rc->r->pool, req_token->return_url, 1),
+ apr_xml_quote_string(rc->r->pool, response->return_url, 1),
"</returnUrl>", NULL);
/* requesterSubject */
ap_rvputs(rc->r,
"<requesterSubject>",
- apr_xml_quote_string(rc->r->pool, req_cred.subject, 1),
+ apr_xml_quote_string(rc->r->pool, response->requester, 1),
"</requesterSubject>", NULL);
/* subject */
- if (*subject_out != NULL) {
+ if (response->subject != NULL) {
ap_rvputs(rc->r,
"<subject>",
- apr_xml_quote_string(rc->r->pool, *subject_out, 1),
+ apr_xml_quote_string(rc->r->pool, response->subject, 1),
"</subject>", NULL);
}
/* requestedToken, don't need to quote */
- if (rtoken.token_data) {
+ if (response->result != NULL) {
ap_rvputs(rc->r,
"<requestedToken>",
- rtoken.token_data,
+ response->result,
"</requestedToken>",
NULL);
ap_rvputs(rc->r,
"<requestedTokenType>",
- apr_xml_quote_string(rc->r->pool, req_token->type, 1),
+ apr_xml_quote_string(rc->r->pool, response->result_type, 1),
"</requestedTokenType>", NULL);
}
- if (req_token->options != NULL
- && ap_strstr(req_token->options, "lc") != NULL) {
- MWK_RETURNED_TOKEN lc_token;
-
- memset(&lc_token, 0, sizeof(lc_token));
- ms = create_error_token_from_req(rc,
- WA_PEC_LOGIN_CANCELED,
- "user canceled login",
- &req_cred,
- &lc_token);
- if (ms == MWK_OK)
- ap_rvputs(rc->r,
- "<loginCanceledToken>",
- lc_token.token_data,
- "</loginCanceledToken>",
- NULL);
+ if (response->login_cancel != NULL) {
+ ap_rvputs(rc->r, "<loginCanceledToken>", response->login_cancel,
+ "</loginCanceledToken>", NULL);
}
/* appState, need to base64-encode */
- if (req_token->state != NULL) {
+ if (request.request->state != NULL) {
char *out_state = (char*)
apr_palloc(rc->r->pool,
- apr_base64_encode_len(req_token->state_len));
- apr_base64_encode(out_state, req_token->state,
- req_token->state_len);
+ apr_base64_encode_len(request.request->state_len));
+ apr_base64_encode(out_state, request.request->state,
+ request.request->state_len);
/* don't need to quote */
ap_rvputs(rc->r,
"<appState>", out_state , "</appState>",
NULL);
}
+
+ /* loginHistory (if present) */
+ if (info != NULL && info->logins != NULL) {
+ struct webauth_login *login;
+
+ ap_rvputs(rc->r, "<loginHistory>", NULL);
+ for (i = 0; i < info->logins->nelts; i++) {
+ login = &APR_ARRAY_IDX(info->logins, i, struct webauth_login);
+ ap_rvputs(rc->r, "<loginLocation ip=\"", login->ip, "\"", NULL);
+ if (login->timestamp != 0)
+ ap_rprintf(rc->r, "time=\"%lu\"",
+ (unsigned long) login->timestamp);
+ ap_rvputs(rc->r, ">", login->hostname, "</loginLocation>", NULL);
+ }
+ ap_rvputs(rc->r, "</loginHistory>", NULL);
+ }
+
ap_rvputs(rc->r, "</requestTokenResponse>", NULL);
ap_rflush(rc->r);
-
ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, rc->r->server,
"mod_webkdc: event=requestToken from=%s clientIp=%s "
"server=%s user=%s rtt=%s%s%s%s%s",
rc->r->connection->remote_ip,
- (req_info.remote_addr == NULL ? "" : req_info.remote_addr),
- *req_subject_out,
- *subject_out,
- req_token->type,
+ (request.remote_ip == NULL ? "" : request.remote_ip),
+ response->requester,
+ (response->subject == NULL ? "<unknown>" : response->subject),
+ request.request->type,
req_token_info,
- (req_token->options == NULL
- || *req_token->options == '\0') ? "" :
- apr_psprintf(rc->r->pool, " ro=%s", req_token->options),
- (login_ec == 0 && !did_login) ? "" :
- apr_psprintf(rc->r->pool, " lec=%d", login_ec),
- login_em == NULL ? "" :
- apr_psprintf(rc->r->pool, " lem=%s", log_escape(rc, login_em))
+ (request.request->options == NULL
+ || *request.request->options == '\0') ? "" :
+ apr_psprintf(rc->r->pool, " ro=%s", request.request->options),
+ apr_psprintf(rc->r->pool, " lec=%d", response->login_error),
+ response->login_message == NULL ? "" :
+ apr_psprintf(rc->r->pool, " lem=%s",
+ log_escape(rc, response->login_message))
);
return MWK_OK;
@@ -2431,6 +2387,7 @@ handler_hook(request_rec *r)
MWK_REQ_CTXT rc;
int status;
const char *req_content_type;
+ struct webauth_webkdc_config config;
memset(&rc, 0, sizeof(rc));
status = webauth_context_init_apr(&rc.ctx, r->pool);
@@ -2443,6 +2400,27 @@ handler_hook(request_rec *r)
rc.r = r;
rc.sconf = ap_get_module_config(r->server->module_config, &webkdc_module);
+ config.keytab_path = rc.sconf->keytab_path;
+ config.principal = rc.sconf->keytab_principal;
+ config.proxy_lifetime = rc.sconf->proxy_lifetime;
+ config.permitted_realms = rc.sconf->permitted_realms;
+ config.local_realms = rc.sconf->local_realms;
+ status = webauth_webkdc_config(rc.ctx, &config);
+ if (status != WA_ERR_NONE) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, r->server,
+ "mod_webkdc: webauth_webkdc_config failed: %s",
+ webauth_error_message(rc.ctx, status));
+ return DECLINED;
+ }
+ if (rc.sconf->userinfo_config != NULL) {
+ status = webauth_user_config(rc.ctx, rc.sconf->userinfo_config);
+ if (status != WA_ERR_NONE) {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, 0, r->server,
+ "mod_webkdc: webauth_user_config failed: %s",
+ webauth_error_message(rc.ctx, status));
+ return DECLINED;
+ }
+ }
if (strcmp(r->handler, "webkdc")) {
return DECLINED;
diff --git a/modules/webkdc/mod_webkdc.h b/modules/webkdc/mod_webkdc.h
index 1c83e2ef..45572f17 100644
--- a/modules/webkdc/mod_webkdc.h
+++ b/modules/webkdc/mod_webkdc.h
@@ -60,6 +60,8 @@ struct config {
const char *keytab_path;
const char *keytab_principal;
const char *token_acl_path;
+ struct webauth_user_config *userinfo_config;
+ const char *userinfo_principal;
bool debug;
bool keyring_auto_update;
unsigned long key_lifetime;
diff --git a/tests/TESTS b/tests/TESTS
index c4b63c80..fa6caa19 100644
--- a/tests/TESTS
+++ b/tests/TESTS
@@ -11,6 +11,7 @@ lib/random
lib/token
lib/token-decode
lib/token-encode
+lib/webkdc-login
portable/asprintf
portable/snprintf
portable/mkstemp
diff --git a/tests/data/xml/info/normal.xml b/tests/data/xml/info/normal.xml
new file mode 100644
index 00000000..5dfc3e49
--- /dev/null
+++ b/tests/data/xml/info/normal.xml
@@ -0,0 +1,15 @@
+<authdata user="normal">
+ <multifactor>
+ <type>p</type>
+ <type>m</type>
+ <type>r</type>
+ <type>o</type>
+ <type>o2</type>
+ </multifactor>
+ <login-history>
+ <host ip="127.0.0.2">example.com</host>
+ <host ip="127.0.0.3">www.example.com</host>
+ </login-history>
+ <max-loa>3</max-loa>
+ <password-expires>1310675733</password-expires>
+</authdata>
diff --git a/tests/data/xml/info/random.xml b/tests/data/xml/info/random.xml
new file mode 100644
index 00000000..f05cdee2
--- /dev/null
+++ b/tests/data/xml/info/random.xml
@@ -0,0 +1,11 @@
+<authdata user="random">
+ <multifactor>
+ <type>p</type>
+ <type>m</type>
+ <type>r</type>
+ <type>o</type>
+ <type>o2</type>
+ <required />
+ </multifactor>
+ <max-loa>2</max-loa>
+</authdata>
diff --git a/tests/lib/webkdc-login-t.c b/tests/lib/webkdc-login-t.c
new file mode 100644
index 00000000..b82d5bdf
--- /dev/null
+++ b/tests/lib/webkdc-login-t.c
@@ -0,0 +1,593 @@
+/*
+ * Test WebKDC login support.
+ *
+ * Written by Russ Allbery <rra@stanford.edu>
+ * Copyright 2011
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * See LICENSE for licensing terms.
+ */
+
+#include <config.h>
+#include <portable/system.h>
+
+#include <apr_pools.h>
+#include <apr_strings.h>
+#include <apr_tables.h>
+#include <time.h>
+
+#include <tests/tap/basic.h>
+#include <tests/tap/kerberos.h>
+#include <tests/tap/remctl.h>
+#include <util/concat.h>
+#include <webauth/basic.h>
+#include <webauth/keys.h>
+#include <webauth/tokens.h>
+#include <webauth/webkdc.h>
+
+
+int
+main(void)
+{
+ apr_pool_t *pool = NULL;
+ WEBAUTH_KEYRING *ring, *session;
+ WEBAUTH_KEY *session_key;
+ char key_data[WA_AES_128], username[BUFSIZ], password[BUFSIZ];
+ int status;
+ char *realm, *path, *keyring, *conf;
+ time_t now;
+ pid_t remctld;
+ FILE *file;
+ struct webauth_context *ctx;
+ struct webauth_webkdc_config config;
+ struct webauth_user_config user_config;
+ struct webauth_webkdc_login_request request;
+ struct webauth_webkdc_login_response *response;
+ struct webauth_token *token, login, wkproxy, wkproxy2;
+ struct webauth_token_request req;
+ struct webauth_token_webkdc_proxy *pt;
+ struct webauth_token_webkdc_service service;
+ struct webauth_webkdc_proxy_data *pd;
+ time_t expiration = 0;
+
+ if (apr_initialize() != APR_SUCCESS)
+ bail("cannot initialize APR");
+ if (apr_pool_create(&pool, NULL) != APR_SUCCESS)
+ bail("cannot create memory pool");
+ if (webauth_context_init_apr(&ctx, pool) != WA_ERR_NONE)
+ bail("cannot initialize WebAuth context");
+
+ /* Load the precreated keyring that we'll use for token encryption. */
+ keyring = test_file_path("data/keyring");
+ status = webauth_keyring_read_file(keyring, &ring);
+ if (status != WA_ERR_NONE)
+ bail("cannot read %s: %s", keyring,
+ webauth_error_message(NULL, status));
+ test_file_path_free(keyring);
+
+ /* Ensure we have a basic configuration available. */
+ memset(&config, 0, sizeof(config));
+ config.local_realms = apr_array_make(pool, 1, sizeof(const char *));
+ config.permitted_realms = apr_array_make(pool, 1, sizeof(const char *));
+ memset(&user_config, 0, sizeof(user_config));
+ if (chdir(getenv("SOURCE")) < 0)
+ bail("can't chdir to SOURCE");
+ config.principal = kerberos_setup();
+ if (config.principal == NULL)
+ skip_all("Kerberos tests not configured");
+ realm = strchr(config.principal, '@');
+ if (realm == NULL)
+ skip_all("Kerberos principal has no realm");
+ realm++;
+ config.keytab_path = test_file_path("data/test.keytab");
+ if (config.keytab_path == NULL)
+ skip_all("Kerberos tests not configured");
+
+ /*
+ * Ensure we have a username and password.
+ *
+ * FIXME: Ideally, we would skip the tests that don't require this and
+ * still do the rest.
+ */
+ path = test_file_path("data/test.password");
+ if (path == NULL)
+ skip_all("Kerberos tests not configured");
+ file = fopen(path, "r");
+ if (file == NULL)
+ sysbail("cannot open %s", path);
+ if (fgets(username, sizeof(username), file) == NULL)
+ bail("cannot read %s", path);
+ if (fgets(password, sizeof(password), file) == NULL)
+ bail("cannot read password from %s", path);
+ fclose(file);
+ if (username[strlen(username) - 1] != '\n')
+ bail("no newline in %s", path);
+ username[strlen(username) - 1] = '\0';
+ if (password[strlen(password) - 1] != '\n')
+ bail("username or password too long in %s", path);
+ password[strlen(password) - 1] = '\0';
+ test_file_path_free(path);
+
+ /*
+ * FIXME: Similarly, if we don't have remctl, we should skip tests instead
+ * of skipping the whole thing.
+ */
+#ifndef PATH_REMCTLD
+ skip_all("remctld not found");
+#endif
+ if (chdir(getenv("SOURCE")) < 0)
+ bail("can't chdir to SOURCE");
+ conf = concatpath(getenv("SOURCE"), "data/conf-webkdc");
+ remctld = remctld_start(PATH_REMCTLD, config.principal, conf, NULL);
+
+ plan(158);
+
+ /* Provide basic configuration to the WebKDC code. */
+ status = webauth_webkdc_config(ctx, &config);
+ is_int(WA_ERR_NONE, status, "WebKDC configuration succeeded");
+
+ /* Flesh out the absolute minimum required in the request. */
+ now = time(NULL);
+ memset(&request, 0, sizeof(request));
+ memset(&service, 0, sizeof(service));
+ service.subject = "krb5:webauth/example.com@EXAMPLE.COM";
+ if (webauth_random_key(key_data, sizeof(key_data)) != WA_ERR_NONE)
+ bail("cannot create random key");
+ session_key = webauth_key_create(WA_AES_KEY, key_data, sizeof(key_data));
+ status = webauth_keyring_from_key(ctx, session_key, &session);
+ if (status != WA_ERR_NONE)
+ bail("cannot create keyring from session key");
+ service.session_key = session_key->data;
+ service.session_key_len = session_key->length;
+ service.creation = now;
+ service.expiration = now + 60;
+ request.service = &service;
+ memset(&req, 0, sizeof(req));
+ req.type = "id";
+ req.auth = "webkdc";
+ req.return_url = "https://example.com/";
+ req.creation = now;
+ request.request = &req;
+ request.creds = apr_array_make(pool, 1, sizeof(struct token *));
+
+ /*
+ * Attempted login with now proxy or login tokens. Should return an error
+ * indicating that a proxy token is required.
+ */
+ status = webauth_webkdc_login(ctx, &request, &response, ring);
+ is_int(WA_ERR_NONE, status, "Minimal login returns success");
+ is_int(WA_PEC_PROXY_TOKEN_REQUIRED, response->login_error,
+ "...with correct error code");
+ is_string("need a proxy token", response->login_message,
+ "...and correct error message");
+ ok(response->factors_wanted == NULL, "...no factors wanted");
+ ok(response->factors_configured == NULL, "...no factors configured");
+ ok(response->proxies == NULL, "...no new webkdc-proxy tokens");
+ is_string("https://example.com/", response->return_url,
+ "...return URL is correct");
+ is_string("krb5:webauth/example.com@EXAMPLE.COM", response->requester,
+ "...requester is correct");
+ is_string(NULL, response->subject, "...no subject");
+ is_string(NULL, response->result, "...no result token");
+ is_string(NULL, response->result_type, "...no result type");
+ is_string(NULL, response->login_cancel, "...no login cancel token");
+ ok(response->app_state == NULL, "...no app state");
+ is_int(0, response->app_state_len, "...no app state length");
+ ok(response->logins == NULL, "...no login information");
+
+ /* Try again, but with a login cancel token requested. */
+ req.options = "lc";
+ status = webauth_webkdc_login(ctx, &request, &response, ring);
+ is_int(WA_ERR_NONE, status, "Login w/cancel returns success");
+ is_int(WA_PEC_PROXY_TOKEN_REQUIRED, response->login_error,
+ "...with correct error code");
+ is_string("need a proxy token", response->login_message,
+ "...and correct error message");
+ ok(response->login_cancel != NULL, "...and now a cancel token");
+ status = webauth_token_decode(ctx, WA_TOKEN_ERROR, response->login_cancel,
+ session, &token);
+ is_int(WA_ERR_NONE, status, "...which decodes properly");
+ if (status != WA_ERR_NONE)
+ ok_block(3, 0, "...invalid error token");
+ else {
+ is_int(WA_PEC_LOGIN_CANCELED, token->token.error.code,
+ "...with correct code");
+ is_string("user canceled login", token->token.error.message,
+ "...and message");
+ ok(token->token.error.creation - now < 3, "...and creation time");
+ }
+
+ /* Now add a login token and see if we can get an id token in response. */
+ config.local_realms = apr_array_make(pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(config.local_realms, const char *) = "none";
+ memset(&login, 0, sizeof(login));
+ login.type = WA_TOKEN_LOGIN;
+ login.token.login.username = username;
+ login.token.login.password = password;
+ login.token.login.creation = now;
+ APR_ARRAY_PUSH(request.creds, struct webauth_token *) = &login;
+ status = webauth_webkdc_login(ctx, &request, &response, ring);
+ is_int(WA_ERR_NONE, status, "Login w/password returns success");
+ is_int(0, response->login_error, "...with no error");
+ is_string(NULL, response->login_message, "...and no message");
+ ok(response->proxies != NULL, "...and now we have proxy tokens");
+ pt = NULL;
+ if (response->proxies == NULL)
+ ok_block(11, 0, "...no proxy tokens");
+ else {
+ is_int(1, response->proxies->nelts, "...one proxy token");
+ pd = &APR_ARRAY_IDX(response->proxies, 0,
+ struct webauth_webkdc_proxy_data);
+ is_string("krb5", pd->type, "...of type krb5");
+ status = webauth_token_decode(ctx, WA_TOKEN_WEBKDC_PROXY, pd->token,
+ ring, &token);
+ is_int(WA_ERR_NONE, status, "...which decodes properly");
+ pt = &token->token.webkdc_proxy;
+ is_string(username, pt->subject, "...with correct subject");
+ is_string("krb5", pt->proxy_type, "...and correct type");
+ ok(strncmp("WEBKDC:krb5:", pt->proxy_subject, 12) == 0,
+ "...and correct proxy subject prefix");
+ ok(strcmp(config.principal, pt->proxy_subject + 12) == 0,
+ "...and correct proxy subject identity");
+ ok(pt->data != NULL, "...and data is not NULL");
+ is_string("p", pt->initial_factors, "...and factors is password");
+ ok(pt->creation - now < 3, "...and creation is okay");
+ ok(pt->expiration > now, "...and expiration is sane");
+ }
+ is_string(username, response->subject, "...subject is correct");
+ ok(response->result != NULL, "...there is a result token");
+ is_string("id", response->result_type, "...which is an id token");
+ status = webauth_token_decode(ctx, WA_TOKEN_ID, response->result,
+ session, &token);
+ is_int(WA_ERR_NONE, status, "...result token decodes properly");
+ if (status != WA_ERR_NONE)
+ ok_block(7, 0, "...no result token: %s",
+ webauth_error_message(ctx, status));
+ else {
+ is_string(username, token->token.id.subject,
+ "...result subject is right");
+ is_string("webkdc", token->token.id.auth,
+ "...result auth type is right");
+ ok(token->token.id.auth_data == NULL, "...and there is no auth data");
+ is_string("p", token->token.id.initial_factors,
+ "...result initial factors is right");
+ is_int(0, token->token.id.loa, "...and no LoA");
+ ok(token->token.id.creation - now < 3, "...and creation is sane");
+ is_int(pt->expiration, token->token.id.expiration,
+ "...and expiration matches the expiration of the proxy token");
+ }
+
+ /* Get an id token with a Kerberos authenticator and test forced auth. */
+ req.options = "lc,fa";
+ request.creds = apr_array_make(pool, 2, sizeof(struct webauth_token *));
+ APR_ARRAY_PUSH(request.creds, struct webauth_token *) = &login;
+ service.subject = apr_pstrcat(pool, "krb5:", config.principal, NULL);
+ req.auth = "krb5";
+ status = webauth_webkdc_login(ctx, &request, &response, ring);
+ is_int(WA_ERR_NONE, status, "Login for krb5 auth returns success");
+ is_int(0, response->login_error, "...with no error");
+ is_string(NULL, response->login_message, "...and no message");
+ ok(response->login_cancel != NULL, "...and there is a cancel token");
+ ok(response->result != NULL, "...there is a result token");
+ is_string("id", response->result_type, "...which is an id token");
+ status = webauth_token_decode(ctx, WA_TOKEN_ID, response->result,
+ session, &token);
+ is_int(WA_ERR_NONE, status, "...result token decodes properly");
+ if (status != WA_ERR_NONE)
+ ok_block(3, 0, "...no result token: %s",
+ webauth_error_message(ctx, status));
+ else {
+ is_string(username, token->token.id.subject,
+ "...result subject is right");
+ is_string("krb5", token->token.id.auth,
+ "...result auth type is right");
+ ok(token->token.id.auth_data != NULL, "...and there is auth data");
+ }
+
+ /* Get a proxy token instead. */
+ req.options = NULL;
+ request.creds = apr_array_make(pool, 2, sizeof(struct webauth_token *));
+ APR_ARRAY_PUSH(request.creds, struct webauth_token *) = &login;
+ req.type = "proxy";
+ req.auth = NULL;
+ req.proxy_type = "krb5";
+ status = webauth_webkdc_login(ctx, &request, &response, ring);
+ is_int(WA_ERR_NONE, status, "Login for proxy token returns success");
+ is_int(0, response->login_error, "...with no error");
+ is_string(NULL, response->login_message, "...and no message");
+ ok(response->login_cancel == NULL, "...and there is no cancel token");
+ is_string(username, response->subject, "...subject is correct");
+ pt = NULL;
+ if (response->proxies == NULL)
+ ok_block(3, 0, "...no proxy tokens");
+ else {
+ is_int(1, response->proxies->nelts, "...one proxy token");
+ pd = &APR_ARRAY_IDX(response->proxies, 0,
+ struct webauth_webkdc_proxy_data);
+ is_string("krb5", pd->type, "...of type krb5");
+ status = webauth_token_decode(ctx, WA_TOKEN_WEBKDC_PROXY, pd->token,
+ ring, &token);
+ is_int(WA_ERR_NONE, status, "...which decodes properly");
+ expiration = token->token.webkdc_proxy.expiration;
+ }
+ ok(response->result != NULL, "...there is a result token");
+ is_string("proxy", response->result_type, "...which is a proxy token");
+ status = webauth_token_decode(ctx, WA_TOKEN_PROXY, response->result,
+ session, &token);
+ is_int(WA_ERR_NONE, status, "...result token decodes properly");
+ if (status != WA_ERR_NONE)
+ ok_block(6, 0, "...no result token: %s",
+ webauth_error_message(ctx, status));
+ else {
+ is_string(username, token->token.proxy.subject,
+ "...result subject is right");
+ is_string("krb5", token->token.proxy.type,
+ "...result proxy type is right");
+ is_string("p", token->token.proxy.initial_factors,
+ "...result initial factors is right");
+ is_int(0, token->token.proxy.loa, "...and no LoA");
+ ok(token->token.proxy.creation - now < 3, "...and creation is sane");
+ is_int(expiration, token->token.proxy.expiration,
+ "...and expiration matches the expiration of the proxy token");
+ status = webauth_token_decode_raw(ctx, WA_TOKEN_WEBKDC_PROXY,
+ token->token.proxy.webkdc_proxy,
+ token->token.proxy.webkdc_proxy_len,
+ ring, &token);
+ is_int(WA_ERR_NONE, status, "...embedded webkdc-proxy token decodes");
+ if (status != WA_ERR_NONE)
+ ok_block(7, 0, "...no webkdc-proxy token: %s",
+ webauth_error_message(ctx, status));
+ else {
+ pt = &token->token.webkdc_proxy;
+ is_string(username, pt->subject, "...with correct subject");
+ is_string("krb5", pt->proxy_type, "...and correct type");
+ ok(strcmp(request.service->subject, pt->proxy_subject) == 0,
+ "...and correct proxy subject identity");
+ ok(pt->data != NULL, "...and data is not NULL");
+ is_string("p", pt->initial_factors, "...and factors is password");
+ ok(pt->creation - now < 3, "...and creation is okay");
+ ok(pt->expiration > now, "...and expiration is sane");
+ }
+ }
+
+ /* Get an id token with a single sign-on webkdc-proxy token. */
+ memset(&wkproxy, 0, sizeof(wkproxy));
+ wkproxy.type = WA_TOKEN_WEBKDC_PROXY;
+ wkproxy.token.webkdc_proxy.subject = "testuser";
+ wkproxy.token.webkdc_proxy.proxy_type = "remuser";
+ wkproxy.token.webkdc_proxy.proxy_subject = "WEBKDC:remuser";
+ wkproxy.token.webkdc_proxy.data = "testuser";
+ wkproxy.token.webkdc_proxy.data_len = strlen("testuser");
+ wkproxy.token.webkdc_proxy.initial_factors = "x,x1";
+ wkproxy.token.webkdc_proxy.loa = 3;
+ wkproxy.token.webkdc_proxy.creation = now;
+ wkproxy.token.webkdc_proxy.expiration = now + 60 * 60;
+ request.creds = apr_array_make(pool, 2, sizeof(struct webauth_token *));
+ APR_ARRAY_PUSH(request.creds, struct webauth_token *) = &wkproxy;
+ req.type = "id";
+ req.auth = "webkdc";
+ req.proxy_type = NULL;
+ status = webauth_webkdc_login(ctx, &request, &response, ring);
+ if (status != WA_ERR_NONE)
+ diag("error status: %s", webauth_error_message(ctx, status));
+ is_int(WA_ERR_NONE, status, "Proxy auth for webkdc returns success");
+ is_int(0, response->login_error, "...with no error");
+ is_string(NULL, response->login_message, "...and no message");
+ ok(response->proxies != NULL, "...and we have proxy tokens");
+ if (response->proxies == NULL)
+ ok_block(5, 0, "...no proxy tokens");
+ else {
+ is_int(1, response->proxies->nelts, "...one proxy token");
+ pd = &APR_ARRAY_IDX(response->proxies, 0,
+ struct webauth_webkdc_proxy_data);
+ is_string("remuser", pd->type, "...of type webkdc");
+ status = webauth_token_decode(ctx, WA_TOKEN_WEBKDC_PROXY, pd->token,
+ ring, &token);
+ is_int(WA_ERR_NONE, status, "...which decodes properly");
+ pt = &token->token.webkdc_proxy;
+ is_string("testuser", pt->subject, "...with correct subject");
+ is_string("x,x1", pt->initial_factors, "...and initial factors");
+ }
+ ok(response->result != NULL, "...there is a result token");
+ is_string("id", response->result_type, "...which is an id token");
+ status = webauth_token_decode(ctx, WA_TOKEN_ID, response->result,
+ session, &token);
+ is_int(WA_ERR_NONE, status, "...result token decodes properly");
+ if (status != WA_ERR_NONE)
+ ok_block(6, 0, "...no result token: %s",
+ webauth_error_message(ctx, status));
+ else {
+ is_string("testuser", token->token.id.subject,
+ "...result subject is right");
+ is_string("webkdc", token->token.id.auth,
+ "...result auth type is right");
+ is_string("x,x1", token->token.proxy.initial_factors,
+ "...result initial factors is right");
+ is_int(3, token->token.id.loa, "...result LoA is right");
+ ok(token->token.id.creation - now < 3, "...and creation is sane");
+ is_int(now + 60 * 60, token->token.id.expiration,
+ "...and expiration matches the expiration of the proxy token");
+ }
+
+ /* Set forced authentication and try again. */
+ req.options = "fa";
+ status = webauth_webkdc_login(ctx, &request, &response, ring);
+ is_int(WA_ERR_NONE, status, "Proxy auth w/forced login returns success");
+ is_int(WA_PEC_LOGIN_FORCED, response->login_error,
+ "...with the right error");
+ is_string("forced authentication, need to login", response->login_message,
+ "...and the right message");
+ is_string("testuser", response->subject, "...but we do know the subject");
+
+ /* Remove forced authentication but ask for a proxy token. */
+ req.options = NULL;
+ req.type = "proxy";
+ req.auth = NULL;
+ req.proxy_type = "krb5";
+ status = webauth_webkdc_login(ctx, &request, &response, ring);
+ is_int(WA_ERR_NONE, status, "Proxy auth for proxy returns success");
+ is_int(WA_PEC_PROXY_TOKEN_REQUIRED, response->login_error,
+ "...with the right error");
+ is_string("need a proxy token", response->login_message,
+ "...and the right message");
+ is_string("testuser", response->subject, "...but we do know the subject");
+
+ /* Now, add configuration for user information, and try this again. */
+ user_config.protocol = WA_PROTOCOL_REMCTL;
+ user_config.host = "localhost";
+ user_config.port = 14373;
+ user_config.identity = config.principal;
+ user_config.command = "test";
+ status = webauth_user_config(ctx, &user_config);
+ is_int(WA_ERR_NONE, status, "User information config accepted");
+ req.type = "id";
+ req.auth = "webkdc";
+ req.proxy_type = NULL;
+ wkproxy.token.webkdc_proxy.subject = "mini";
+ wkproxy.token.webkdc_proxy.data = "mini";
+ wkproxy.token.webkdc_proxy.data_len = strlen("mini");
+ status = webauth_webkdc_login(ctx, &request, &response, ring);
+ if (status != WA_ERR_NONE)
+ diag("error status: %s", webauth_error_message(ctx, status));
+ is_int(WA_ERR_NONE, status, "Proxy auth w/user config returns success");
+ is_int(0, response->login_error, "...with no error");
+ is_string(NULL, response->login_message, "...and no message");
+ ok(response->result != NULL, "...there is a result token");
+ is_string("id", response->result_type, "...which is an id token");
+ status = webauth_token_decode(ctx, WA_TOKEN_ID, response->result,
+ session, &token);
+ is_int(WA_ERR_NONE, status, "...result token decodes properly");
+ if (status != WA_ERR_NONE)
+ ok_block(4, 0, "...no result token: %s",
+ webauth_error_message(ctx, status));
+ else {
+ is_string("mini", token->token.id.subject,
+ "...result subject is right");
+ is_string("webkdc", token->token.id.auth,
+ "...result auth type is right");
+ is_string("x,x1", token->token.proxy.initial_factors,
+ "...result initial factors is right");
+ is_int(1, token->token.id.loa, "...result LoA is right");
+ }
+
+ /*
+ * Request an X.509 factor and try again. This should still work even
+ * though this user doesn't have password listed as a supported factor in
+ * the metadata.
+ */
+ req.initial_factors = "x";
+ status = webauth_webkdc_login(ctx, &request, &response, ring);
+ if (status != WA_ERR_NONE)
+ diag("error status: %s", webauth_error_message(ctx, status));
+ is_int(WA_ERR_NONE, status, "Multifactor with proxy returns success");
+ is_int(0, response->login_error, "...with no error");
+ is_string(NULL, response->login_message, "...and no message");
+ ok(response->result != NULL, "...there is a result token");
+ is_string("id", response->result_type, "...which is an id token");
+
+ /*
+ * Change the WebKDC proxy token to assert just a password factor and ask
+ * for an OTP factor, and try again. This should be rejected with
+ * multifactor required.
+ */
+ wkproxy.token.webkdc_proxy.initial_factors = "p";
+ req.initial_factors = "o";
+ status = webauth_webkdc_login(ctx, &request, &response, ring);
+ is_int(WA_ERR_NONE, status, "Multifactor without config returns success");
+ is_int(WA_PEC_MULTIFACTOR_UNAVAILABLE, response->login_error,
+ "...with the right error");
+ is_string("multifactor required but not configured",
+ response->login_message, "...and the right message");
+ ok(response->result == NULL, "...and there is no result token");
+ is_int(1, response->factors_wanted->nelts, "...and one factor is wanted");
+ is_string("o", APR_ARRAY_IDX(response->factors_wanted, 0, const char *),
+ "...which is the OTP factor");
+ is_int(1, response->factors_configured->nelts,
+ "...and one factor is configured");
+ is_string("p",
+ APR_ARRAY_IDX(response->factors_configured, 0, const char *),
+ "...which is the password factor");
+
+ /*
+ * Try with a user who has multifactor configuration and forced
+ * multifactor.
+ */
+ wkproxy.token.webkdc_proxy.subject = "full";
+ wkproxy.token.webkdc_proxy.data = "full";
+ wkproxy.token.webkdc_proxy.data_len = strlen("full");
+ status = webauth_webkdc_login(ctx, &request, &response, ring);
+ is_int(WA_ERR_NONE, status, "Multifactor with config returns success");
+ is_int(WA_PEC_MULTIFACTOR_REQUIRED, response->login_error,
+ "...with the right error");
+ is_string("multifactor login required", response->login_message,
+ "...and the right message");
+ ok(response->result == NULL, "...and there is no result token");
+ is_int(2, response->factors_wanted->nelts,
+ "...and two factors are wanted");
+ is_string("o", APR_ARRAY_IDX(response->factors_wanted, 0, const char *),
+ "...which is the OTP factor");
+ is_int(5, response->factors_configured->nelts,
+ "...and five factors are configured");
+ is_string("p",
+ APR_ARRAY_IDX(response->factors_configured, 0, const char *),
+ "...which is the password factor");
+ is_string("r",
+ APR_ARRAY_IDX(response->factors_configured, 1, const char *),
+ "...the random factor");
+ is_string("m",
+ APR_ARRAY_IDX(response->factors_configured, 2, const char *),
+ "...the generic multifactor factor");
+ is_string("o",
+ APR_ARRAY_IDX(response->factors_configured, 3, const char *),
+ "...the OTP factor");
+ is_string("o3",
+ APR_ARRAY_IDX(response->factors_configured, 4, const char *),
+ "...and the OTP-3 factor");
+
+ /*
+ * Add a second webkdc-proxy token that repesents an OTP login. This
+ * login should then work.
+ */
+ wkproxy.token.webkdc_proxy.loa = 3;
+ wkproxy2.type = WA_TOKEN_WEBKDC_PROXY;
+ wkproxy2.token.webkdc_proxy.subject = "full";
+ wkproxy2.token.webkdc_proxy.proxy_type = "remuser";
+ wkproxy2.token.webkdc_proxy.proxy_subject = "WEBKDC:remuser";
+ wkproxy2.token.webkdc_proxy.data = "full";
+ wkproxy2.token.webkdc_proxy.data_len = strlen("full");
+ wkproxy2.token.webkdc_proxy.initial_factors = "o,o3";
+ wkproxy2.token.webkdc_proxy.loa = 2;
+ wkproxy2.token.webkdc_proxy.creation = now;
+ wkproxy2.token.webkdc_proxy.expiration = now + 30 * 60;
+ APR_ARRAY_PUSH(request.creds, struct webauth_token *) = &wkproxy2;
+ status = webauth_webkdc_login(ctx, &request, &response, ring);
+ is_int(WA_ERR_NONE, status,
+ "Multifactor with two proxies returns success");
+ is_int(0, response->login_error, "...with no error");
+ is_string(NULL, response->login_message, "...and no error message");
+ ok(response->result != NULL, "...there is a result token");
+ is_string("id", response->result_type, "...which is an id token");
+ status = webauth_token_decode(ctx, WA_TOKEN_ID, response->result,
+ session, &token);
+ is_int(WA_ERR_NONE, status, "...result token decodes properly");
+ if (status != WA_ERR_NONE)
+ ok_block(5, 0, "...no result token: %s",
+ webauth_error_message(ctx, status));
+ else {
+ is_string("full", token->token.id.subject,
+ "...result subject is right");
+ is_string("webkdc", token->token.id.auth,
+ "...result auth type is right");
+ is_string("p,o,o3,m", token->token.proxy.initial_factors,
+ "...result initial factors is right");
+ is_int(3, token->token.id.loa, "...result LoA is right");
+ is_int(now + 30 * 60, token->token.id.expiration,
+ "...and expiration matches the shorter expiration");
+ }
+
+ /* Clean up. */
+ remctld_stop(remctld);
+ kerberos_cleanup();
+ apr_terminate();
+ return 0;
+}