summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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;
+}