summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
authorGraham Leggett <minfrin@apache.org>2023-04-25 19:52:18 +0200
committerGraham Leggett <minfrin@apache.org>2023-04-25 19:52:18 +0200
commitd8246a15d78d3f51acc19ef8a826c7e34f1f8406 (patch)
treeffed84ec318046ae84c8018d0d9f742fa2e7028c /modules
parentcore: Add the token_checker hook, that allows authentication to take (diff)
downloadapache2-d8246a15d78d3f51acc19ef8a826c7e34f1f8406.tar.xz
apache2-d8246a15d78d3f51acc19ef8a826c7e34f1f8406.zip
*) mod_autht_jwt: New module to handle RFC 7519 JWT tokens within
bearer tokens, both as part of the aaa framework, and as a way to generate tokens and pass them to backend servers and services. *) mod_auth_bearer: New module to handle RFC 6750 Bearer tokens, using the token_checker hook. *) mod_autht_core: New module to handle provider aliases for token authentication. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1909411 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'modules')
-rw-r--r--modules/aaa/config.m411
-rw-r--r--modules/aaa/mod_auth_bearer.c422
-rw-r--r--modules/aaa/mod_autht_core.c227
-rw-r--r--modules/aaa/mod_autht_jwt.c1089
4 files changed, 1749 insertions, 0 deletions
diff --git a/modules/aaa/config.m4 b/modules/aaa/config.m4
index bf3391ffe8..1b59f99f49 100644
--- a/modules/aaa/config.m4
+++ b/modules/aaa/config.m4
@@ -6,6 +6,16 @@ dnl APACHE_MODULE(name, helptext[, objects[, structname[, default[, config]]]])
APACHE_MODPATH_INIT(aaa)
+dnl Token modules: modules that parse or reference a token, that may
+dnl contain or reference further data like usernames, or IP addresses.
+dnl
+APACHE_MODULE(autht_jwt, RFC7519 JSON Web Token based authentication control, , , most)
+
+dnl General Authentication modules; module which implements the
+dnl non-autht module specific directives.
+dnl
+APACHE_MODULE(autht_core, core token authentication module, , , yes)
+
dnl Authentication modules; modules checking a username and password against a
dnl file, database, or other similar magic.
dnl
@@ -67,6 +77,7 @@ APACHE_MODULE(access_compat, mod_access compatibility, , , yes)
dnl these are the front-end authentication modules
APACHE_MODULE(auth_basic, basic authentication, , , yes)
+APACHE_MODULE(auth_bearer, bearer authentication, , , yes)
APACHE_MODULE(auth_form, form authentication, , , most)
APACHE_MODULE(auth_digest, RFC2617 Digest authentication, , , most, [
APR_CHECK_APR_DEFINE(APR_HAS_RANDOM)
diff --git a/modules/aaa/mod_auth_bearer.c b/modules/aaa/mod_auth_bearer.c
new file mode 100644
index 0000000000..7772b21a21
--- /dev/null
+++ b/modules/aaa/mod_auth_bearer.c
@@ -0,0 +1,422 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This module adds support for https://tools.ietf.org/html/rfc6750 Bearer
+ * tokens, both as a generator of bearer tokens, and as an acceptor of
+ * Bearer tokens for authentication.
+ */
+
+#include "apr_strings.h"
+#include "apr_hash.h"
+#include "apr_lib.h" /* for apr_isspace */
+#define APR_WANT_STRFUNC /* for strcasecmp */
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "ap_provider.h"
+#include "ap_expr.h"
+
+#include "mod_auth.h"
+
+module AP_MODULE_DECLARE_DATA auth_bearer_module;
+
+typedef struct {
+ autht_provider_list *providers;
+ int authoritative;
+ ap_expr_info_t *proxy;
+ int authoritative_set:1;
+ int proxy_set:1;
+} auth_bearer_config_rec;
+
+static void *create_auth_bearer_dir_config(apr_pool_t *p, char *d)
+{
+ auth_bearer_config_rec *conf = apr_pcalloc(p, sizeof(*conf));
+
+ /* Any failures are fatal. */
+ conf->authoritative = 1;
+
+ return conf;
+}
+
+static void *merge_auth_bearer_dir_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ auth_bearer_config_rec *newconf = apr_pcalloc(p, sizeof(*newconf));
+ auth_bearer_config_rec *base = basev;
+ auth_bearer_config_rec *overrides = overridesv;
+
+ newconf->authoritative =
+ overrides->authoritative_set ? overrides->authoritative :
+ base->authoritative;
+ newconf->authoritative_set = overrides->authoritative_set
+ || base->authoritative_set;
+
+ newconf->providers = overrides->providers ? overrides->providers : base->providers;
+
+ newconf->proxy =
+ overrides->proxy_set ? overrides->proxy : base->proxy;
+ newconf->proxy_set = overrides->proxy_set || base->proxy_set;
+
+ return newconf;
+}
+
+static const char *add_autht_provider(cmd_parms *cmd, void *config,
+ const char *arg)
+{
+ auth_bearer_config_rec *conf = (auth_bearer_config_rec*)config;
+ autht_provider_list *newp;
+
+ newp = apr_pcalloc(cmd->pool, sizeof(autht_provider_list));
+ newp->provider_name = arg;
+
+ /* lookup and cache the actual provider now */
+ newp->provider = ap_lookup_provider(AUTHT_PROVIDER_GROUP,
+ newp->provider_name,
+ AUTHT_PROVIDER_VERSION);
+
+ if (newp->provider == NULL) {
+ /* by the time they use it, the provider should be loaded and
+ registered with us. */
+ return apr_psprintf(cmd->pool,
+ "Unknown Autht provider: %s",
+ newp->provider_name);
+ }
+
+ if (!newp->provider->check_token) {
+ /* if it doesn't provide the appropriate function, reject it */
+ return apr_psprintf(cmd->pool,
+ "The '%s' Autht provider doesn't support "
+ "Bearer Authentication", newp->provider_name);
+ }
+
+ /* Add it to the list now. */
+ if (!conf->providers) {
+ conf->providers = newp;
+ }
+ else {
+ autht_provider_list *last = conf->providers;
+
+ while (last->next) {
+ last = last->next;
+ }
+ last->next = newp;
+ }
+
+ return NULL;
+}
+
+static const char *set_authoritative(cmd_parms * cmd, void *config, int flag)
+{
+ auth_bearer_config_rec *conf = (auth_bearer_config_rec *) config;
+
+ conf->authoritative = flag;
+ conf->authoritative_set = 1;
+
+ return NULL;
+}
+
+static const char *set_bearer_proxy(cmd_parms * cmd, void *config,
+ const char *user)
+{
+ auth_bearer_config_rec *conf = (auth_bearer_config_rec *) config;
+ const char *err;
+
+ if (!strcasecmp(user, "off")) {
+ conf->proxy = NULL;
+ conf->proxy_set = 1;
+ }
+ else {
+
+ conf->proxy =
+ ap_expr_parse_cmd(cmd, user, AP_EXPR_FLAG_STRING_RESULT,
+ &err, NULL);
+ if (err) {
+ return apr_psprintf(cmd->pool,
+ "Could not parse proxy expression '%s': %s", user,
+ err);
+ }
+ conf->proxy_set = 1;
+ }
+
+ return NULL;
+}
+
+static const command_rec auth_bearer_cmds[] =
+{
+ AP_INIT_ITERATE("AuthBearerProvider", add_autht_provider, NULL, OR_AUTHCFG,
+ "specify the auth providers for a directory or location"),
+ AP_INIT_FLAG("AuthBearerAuthoritative", set_authoritative, NULL, OR_AUTHCFG,
+ "Set to 'Off' to allow access control to be passed along to "
+ "lower modules if the token is not known to this module"),
+ AP_INIT_TAKE1("AuthBearerProxy", set_bearer_proxy, NULL, OR_AUTHCFG,
+ "Pass a bearer authentication token over a proxy connection "
+ "generated using the given expression, 'off' to disable."),
+ {NULL}
+};
+
+/* These functions return 0 if client is OK, and proper error status
+ * if not... either HTTP_UNAUTHORIZED, if we made a check, and it failed, or
+ * HTTP_INTERNAL_SERVER_ERROR, if things are so totally confused that we
+ * couldn't figure out how to tell if the client is authorized or not.
+ *
+ * If they return DECLINED, and all other modules also decline, that's
+ * treated by the server core as a configuration error, logged and
+ * reported as such.
+ */
+
+static void note_bearer_auth_failure(request_rec *r)
+{
+ apr_table_setn(r->err_headers_out,
+ (PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authenticate"
+ : "WWW-Authenticate",
+ apr_pstrcat(r->pool, "Bearer realm=\"", ap_auth_name(r),
+ "\"", NULL));
+}
+
+static int hook_note_bearer_auth_failure(request_rec *r, const char *auth_type)
+{
+ if (strcasecmp(auth_type, "Bearer"))
+ return DECLINED;
+
+ note_bearer_auth_failure(r);
+ return OK;
+}
+
+static int get_bearer_auth(request_rec *r, const char **token)
+{
+ const char *auth_line;
+
+ /* Get the appropriate header */
+ auth_line = apr_table_get(r->headers_in, (PROXYREQ_PROXY == r->proxyreq)
+ ? "Proxy-Authorization"
+ : "Authorization");
+
+ if (!auth_line) {
+ note_bearer_auth_failure(r);
+ return HTTP_UNAUTHORIZED;
+ }
+
+ if (strcasecmp(ap_getword(r->pool, &auth_line, ' '), "Bearer")) {
+ /* Client tried to authenticate using wrong auth scheme */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01614)
+ "client used wrong authentication scheme: %s", r->uri);
+ note_bearer_auth_failure(r);
+ return HTTP_UNAUTHORIZED;
+ }
+
+ /* Skip leading spaces. */
+ while (apr_isspace(*auth_line)) {
+ auth_line++;
+ }
+
+ *token = auth_line;
+
+ return OK;
+}
+
+/* Determine the token, and check if we can process the token, for HTTP
+ * bearer authentication...
+ */
+static int authenticate_bearer_token(request_rec *r)
+{
+ auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &auth_bearer_module);
+ const char *sent_token, *current_auth;
+ int res;
+ autht_status auth_result;
+ autht_provider_list *current_provider;
+
+ /* Are we configured to be Bearer auth? */
+ current_auth = ap_auth_type(r);
+ if (!current_auth || strcasecmp(current_auth, "Bearer")) {
+ return DECLINED;
+ }
+
+ /* We need an authentication realm. */
+ if (!ap_auth_name(r)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01615)
+ "need AuthName: %s", r->uri);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ r->ap_auth_type = (char*)current_auth;
+
+ res = get_bearer_auth(r, &sent_token);
+ if (res) {
+ return res;
+ }
+
+ current_provider = conf->providers;
+ do {
+ const autht_provider *provider;
+
+ /* For now, if a provider isn't set, we'll be nice and use the jwt
+ * provider.
+ */
+ if (!current_provider) {
+ provider = ap_lookup_provider(AUTHT_PROVIDER_GROUP,
+ AUTHT_DEFAULT_PROVIDER,
+ AUTHT_PROVIDER_VERSION);
+
+ if (!provider || !provider->check_token) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01616)
+ "No Autht provider configured");
+ auth_result = AUTHT_GENERAL_ERROR;
+ break;
+ }
+ apr_table_setn(r->notes, AUTHT_PROVIDER_NAME_NOTE, AUTHT_DEFAULT_PROVIDER);
+ }
+ else {
+ provider = current_provider->provider;
+ apr_table_setn(r->notes, AUTHT_PROVIDER_NAME_NOTE, current_provider->provider_name);
+ }
+
+ auth_result = provider->check_token(r, "bearer", sent_token);
+
+ apr_table_unset(r->notes, AUTHT_PROVIDER_NAME_NOTE);
+
+ /* Something occurred. Stop checking. */
+ if (auth_result != AUTHT_MISMATCH) {
+ break;
+ }
+
+ /* If we're not really configured for providers, stop now. */
+ if (!conf->providers) {
+ break;
+ }
+
+ current_provider = current_provider->next;
+ } while (current_provider);
+
+ if (auth_result != AUTHT_GRANTED) {
+ int return_code;
+
+ /* If we're not authoritative, then any error is ignored. */
+ if (!(conf->authoritative) && auth_result != AUTHT_DENIED) {
+ return DECLINED;
+ }
+
+ switch (auth_result) {
+ case AUTHT_DENIED:
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
+ "bearer token %s: authentication failure for \"%s\": "
+ "Token Rejected",
+ sent_token, r->uri);
+ return_code = HTTP_UNAUTHORIZED;
+ break;
+ case AUTHT_EXPIRED:
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
+ "bearer token %s: authentication failure for \"%s\": "
+ "Token has expired",
+ sent_token, r->uri);
+ return_code = HTTP_UNAUTHORIZED;
+ break;
+ case AUTHT_INVALID:
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
+ "bearer token %s: authentication failure for \"%s\": "
+ "Token is not yet valid",
+ sent_token, r->uri);
+ return_code = HTTP_UNAUTHORIZED;
+ break;
+ case AUTHT_MISMATCH:
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
+ "bearer token %s: did not match '%s': %s", sent_token,
+ ap_auth_name(r), r->uri);
+ return_code = HTTP_UNAUTHORIZED;
+ break;
+ case AUTH_GENERAL_ERROR:
+ default:
+ /* We'll assume that the module has already said what its error
+ * was in the logs.
+ */
+ return_code = HTTP_INTERNAL_SERVER_ERROR;
+ break;
+ }
+
+ /* If we're returning 401, tell them to try again. */
+ if (return_code == HTTP_UNAUTHORIZED) {
+ note_bearer_auth_failure(r);
+ }
+ return return_code;
+ }
+
+ return OK;
+}
+
+/* If we have set claims to be made, create a bearer authentication header
+ * for the benefit of a proxy or application running behind this server.
+ */
+static int authenticate_bearer_fixup(request_rec *r)
+{
+ const char *auth_line, *token, *err;
+ auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &auth_bearer_module);
+
+ if (!conf->proxy) {
+ return DECLINED;
+ }
+
+ token = ap_expr_str_exec(r, conf->proxy, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02455)
+ "AuthBearerProxy: could not evaluate token expression for URI '%s': %s", r->uri, err);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if (!token || !*token) {
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02458)
+ "AuthBearerProxy: empty token expression for URI '%s', ignoring", r->uri);
+
+ apr_table_unset(r->headers_in, "Authorization");
+
+ return DECLINED;
+ }
+
+ auth_line = apr_pstrcat(r->pool, "Bearer ", token,
+ NULL);
+ apr_table_setn(r->headers_in, "Authorization", auth_line);
+
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02457)
+ "AuthBearerProxy: \"Authorization: %s\"",
+ auth_line);
+
+ return OK;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_hook_check_autht(authenticate_bearer_token, NULL, NULL, APR_HOOK_MIDDLE,
+ AP_AUTH_INTERNAL_PER_CONF);
+ ap_hook_fixups(authenticate_bearer_fixup, NULL, NULL, APR_HOOK_LAST);
+ ap_hook_note_auth_failure(hook_note_bearer_auth_failure, NULL, NULL,
+ APR_HOOK_MIDDLE);
+}
+
+AP_DECLARE_MODULE(auth_bearer) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_auth_bearer_dir_config, /* dir config creater */
+ merge_auth_bearer_dir_config, /* dir merger --- default is to override */
+ NULL, /* server config */
+ NULL, /* merge server config */
+ auth_bearer_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
diff --git a/modules/aaa/mod_autht_core.c b/modules/aaa/mod_autht_core.c
new file mode 100644
index 0000000000..357f5ae241
--- /dev/null
+++ b/modules/aaa/mod_autht_core.c
@@ -0,0 +1,227 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Security options etc.
+ *
+ * Module derived from code originally written by Rob McCool
+ *
+ */
+
+#include "apr_strings.h"
+#include "apr_network_io.h"
+#define APR_WANT_STRFUNC
+#define APR_WANT_BYTEFUNC
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_request.h"
+#include "http_protocol.h"
+#include "ap_provider.h"
+
+#include "mod_auth.h"
+
+#if APR_HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
+typedef struct provider_alias_rec {
+ char *provider_name;
+ char *provider_alias;
+ ap_conf_vector_t *sec_auth;
+ const autht_provider *provider;
+} provider_alias_rec;
+
+typedef struct autht_alias_srv_conf {
+ apr_hash_t *alias_rec;
+} autht_alias_srv_conf;
+
+
+module AP_MODULE_DECLARE_DATA autht_core_module;
+
+static autht_status authn_alias_check_token(request_rec *r, const char *type,
+ const char *token)
+{
+ /* Look up the provider alias in the alias list */
+ /* Get the dir_config and call ap_Merge_per_dir_configs() */
+ /* Call the real provider->check_password() function */
+ /* return the result of the above function call */
+
+ const char *provider_name = apr_table_get(r->notes, AUTHT_PROVIDER_NAME_NOTE);
+ autht_status ret = AUTHT_MISMATCH;
+ autht_alias_srv_conf *authcfg =
+ (autht_alias_srv_conf *)ap_get_module_config(r->server->module_config,
+ &autht_core_module);
+
+ if (provider_name) {
+ provider_alias_rec *prvdraliasrec = apr_hash_get(authcfg->alias_rec,
+ provider_name, APR_HASH_KEY_STRING);
+ ap_conf_vector_t *orig_dir_config = r->per_dir_config;
+
+ /* If we found the alias provider in the list, then merge the directory
+ configurations and call the real provider */
+ if (prvdraliasrec) {
+ r->per_dir_config = ap_merge_per_dir_configs(r->pool, orig_dir_config,
+ prvdraliasrec->sec_auth);
+ ret = prvdraliasrec->provider->check_token(r, type, token);
+ r->per_dir_config = orig_dir_config;
+ }
+ }
+
+ return ret;
+}
+
+static void *create_autht_alias_svr_config(apr_pool_t *p, server_rec *s)
+{
+
+ autht_alias_srv_conf *authcfg;
+
+ authcfg = (autht_alias_srv_conf *) apr_pcalloc(p, sizeof(autht_alias_srv_conf));
+ authcfg->alias_rec = apr_hash_make(p);
+
+ return (void *) authcfg;
+}
+
+/* Only per-server directive we have is GLOBAL_ONLY */
+static void *merge_autht_alias_svr_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ return basev;
+}
+
+static const autht_provider autht_alias_provider =
+{
+ &authn_alias_check_token
+};
+
+static const char *authaliassection(cmd_parms *cmd, void *mconfig, const char *arg)
+{
+ const char *endp = ap_strrchr_c(arg, '>');
+ const char *args;
+ char *provider_alias;
+ char *provider_name;
+ int old_overrides = cmd->override;
+ const char *errmsg;
+ const autht_provider *provider = NULL;
+ ap_conf_vector_t *new_auth_config = ap_create_per_dir_config(cmd->pool);
+ autht_alias_srv_conf *authcfg =
+ (autht_alias_srv_conf *)ap_get_module_config(cmd->server->module_config,
+ &autht_core_module);
+
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+ if (err != NULL) {
+ return err;
+ }
+
+ if (endp == NULL) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ "> directive missing closing '>'", NULL);
+ }
+
+ args = apr_pstrndup(cmd->temp_pool, arg, endp - arg);
+
+ if (!args[0]) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ "> directive requires additional arguments", NULL);
+ }
+
+ /* Pull the real provider name and the alias name from the block header */
+ provider_name = ap_getword_conf(cmd->pool, &args);
+ provider_alias = ap_getword_conf(cmd->pool, &args);
+
+ if (!provider_name[0] || !provider_alias[0]) {
+ return apr_pstrcat(cmd->pool, cmd->cmd->name,
+ "> directive requires additional arguments", NULL);
+ }
+
+ if (strcasecmp(provider_name, provider_alias) == 0) {
+ return apr_pstrcat(cmd->pool,
+ "The alias provider name must be different from the base provider name.", NULL);
+ }
+
+ /* Look up the alias provider to make sure that it hasn't already been registered. */
+ provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP, provider_alias,
+ AUTHN_PROVIDER_VERSION);
+ if (provider) {
+ return apr_pstrcat(cmd->pool, "The alias provider ", provider_alias,
+ " has already be registered previously as either a base provider or an alias provider.",
+ NULL);
+ }
+
+ /* walk the subsection configuration to get the per_dir config that we will
+ merge just before the real provider is called. */
+ cmd->override = OR_AUTHCFG | ACCESS_CONF;
+ errmsg = ap_walk_config(cmd->directive->first_child, cmd, new_auth_config);
+ cmd->override = old_overrides;
+
+ if (!errmsg) {
+ provider_alias_rec *prvdraliasrec = apr_pcalloc(cmd->pool, sizeof(provider_alias_rec));
+ provider = ap_lookup_provider(AUTHN_PROVIDER_GROUP, provider_name,
+ AUTHN_PROVIDER_VERSION);
+
+ if (!provider) {
+ /* by the time they use it, the provider should be loaded and
+ registered with us. */
+ return apr_psprintf(cmd->pool,
+ "Unknown Authn provider: %s",
+ provider_name);
+ }
+
+ /* Save off the new directory config along with the original provider name
+ and function pointer data */
+ prvdraliasrec->sec_auth = new_auth_config;
+ prvdraliasrec->provider_name = provider_name;
+ prvdraliasrec->provider_alias = provider_alias;
+ prvdraliasrec->provider = provider;
+ apr_hash_set(authcfg->alias_rec, provider_alias, APR_HASH_KEY_STRING, prvdraliasrec);
+
+ /* Register the fake provider so that we get called first */
+ ap_register_auth_provider(cmd->pool, AUTHT_PROVIDER_GROUP,
+ provider_alias, AUTHT_PROVIDER_VERSION,
+ &autht_alias_provider,
+ AP_AUTH_INTERNAL_PER_CONF);
+ }
+
+ return errmsg;
+}
+
+static const command_rec autht_cmds[] =
+{
+ AP_INIT_RAW_ARGS("<AuthtProviderAlias", authaliassection, NULL, RSRC_CONF,
+ "container for grouping an authentication provider's "
+ "directives under a provider alias"),
+ {NULL}
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+
+}
+
+AP_DECLARE_MODULE(autht_core) =
+{
+ STANDARD20_MODULE_STUFF,
+ NULL, /* dir config creater */
+ NULL, /* dir merger --- default is to override */
+ create_autht_alias_svr_config, /* server config */
+ merge_autht_alias_svr_config, /* merge server config */
+ autht_cmds,
+ register_hooks /* register hooks */
+};
+
diff --git a/modules/aaa/mod_autht_jwt.c b/modules/aaa/mod_autht_jwt.c
new file mode 100644
index 0000000000..ba271a535f
--- /dev/null
+++ b/modules/aaa/mod_autht_jwt.c
@@ -0,0 +1,1089 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This module adds support for https://tools.ietf.org/html/rfc7519 JWT tokens
+ * as https://tools.ietf.org/html/rfc6750 Bearer tokens, both as a generator
+ * of JWT bearer tokens, and as an acceptor of JWT Bearer tokens for authentication.
+ */
+
+#include "apr_strings.h"
+#include "apr_hash.h"
+#include "apr_crypto.h"
+#include "apr_jose.h"
+#include "apr_lib.h" /* for apr_isspace */
+#include "apr_base64.h" /* for apr_base64_decode et al */
+#define APR_WANT_STRFUNC /* for strcasecmp */
+#include "apr_want.h"
+
+#include "ap_config.h"
+#include "httpd.h"
+#include "http_config.h"
+#include "http_core.h"
+#include "http_log.h"
+#include "http_protocol.h"
+#include "http_request.h"
+#include "util_md5.h"
+#include "ap_provider.h"
+#include "ap_expr.h"
+
+#include "mod_auth.h"
+
+#define CRYPTO_KEY "auth_bearer_context"
+
+module AP_MODULE_DECLARE_DATA autht_jwt_module;
+
+typedef enum jws_alg_type_e {
+ /** No specific type. */
+ JWS_ALG_TYPE_NONE = 0,
+ /** HMAC SHA256 */
+ JWS_ALG_TYPE_HS256 = 1,
+} jws_alg_type_e;
+
+typedef struct {
+ unsigned char *secret;
+ apr_size_t secret_len;
+ jws_alg_type_e jws_alg;
+} auth_bearer_signature_rec;
+
+typedef struct {
+ apr_hash_t *claims;
+ apr_array_header_t *signs;
+ apr_array_header_t *verifies;
+ int signs_set:1;
+ int verifies_set:1;
+ int fake_set:1;
+} auth_bearer_config_rec;
+
+typedef struct {
+ const char *library;
+ const char *params;
+ apr_crypto_t **crypto;
+ int library_set;
+} auth_bearer_conf;
+
+static int auth_bearer_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp,
+ server_rec *s) {
+ const apr_crypto_driver_t *driver = NULL;
+
+ /* auth_bearer_init() will be called twice. Don't bother
+ * going through all of the initialization on the first call
+ * because it will just be thrown away.*/
+ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) {
+ return OK;
+ }
+
+ while (s) {
+
+ auth_bearer_conf *conf = ap_get_module_config(s->module_config,
+ &autht_jwt_module);
+
+ if (conf->library_set && !*conf->crypto) {
+
+ const apu_err_t *err = NULL;
+ apr_status_t rv;
+
+ rv = apr_crypto_init(p);
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ APLOGNO() "APR crypto could not be initialised");
+ return rv;
+ }
+
+ rv = apr_crypto_get_driver(&driver, conf->library, conf->params,
+ &err, p);
+ if (APR_EREINIT == rv) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s,
+ APLOGNO() "warning: crypto for '%s' was already initialised, " "using existing configuration",
+ conf->library);
+ rv = APR_SUCCESS;
+ }
+ if (APR_SUCCESS != rv && err) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ APLOGNO() "The crypto library '%s' could not be loaded: %s (%s: %d)",
+ conf->library, err->msg, err->reason, err->rc);
+ return rv;
+ }
+ if (APR_ENOTIMPL == rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ APLOGNO() "The crypto library '%s' could not be found",
+ conf->library);
+ return rv;
+ }
+ if (APR_SUCCESS != rv || !driver) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ APLOGNO() "The crypto library '%s' could not be loaded",
+ conf->library);
+ return rv;
+ }
+
+ rv = apr_crypto_make(conf->crypto, driver, conf->params, p);
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+ APLOGNO() "The crypto library '%s' could not be initialised",
+ conf->library);
+ return rv;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_INFO, rv, s,
+ APLOGNO() "The crypto library '%s' was loaded successfully",
+ conf->library);
+
+ }
+
+ s = s->next;
+ }
+
+ return OK;
+}
+
+static void *create_auth_bearer_config(apr_pool_t * p, server_rec *s)
+{
+ auth_bearer_conf *new =
+ (auth_bearer_conf *) apr_pcalloc(p, sizeof(auth_bearer_conf));
+
+ /* if no library has been configured, set the recommended library
+ * as a sensible default.
+ */
+#ifdef APU_CRYPTO_RECOMMENDED_DRIVER
+ new->library = APU_CRYPTO_RECOMMENDED_DRIVER;
+#endif
+ new->crypto = apr_pcalloc(p, sizeof(apr_crypto_t *));
+
+ return (void *) new;
+}
+
+static void *merge_auth_bearer_config(apr_pool_t * p, void *basev, void *addv)
+{
+ auth_bearer_conf *new = (auth_bearer_conf *) apr_pcalloc(p, sizeof(auth_bearer_conf));
+ auth_bearer_conf *add = (auth_bearer_conf *) addv;
+ auth_bearer_conf *base = (auth_bearer_conf *) basev;
+
+ new->library = (add->library_set == 0) ? base->library : add->library;
+ new->params = (add->library_set == 0) ? base->params : add->params;
+ new->library_set = add->library_set || base->library_set;
+
+ new->crypto = base->crypto;
+
+ return (void *) new;
+}
+
+static void *create_auth_bearer_dir_config(apr_pool_t *p, char *d)
+{
+ auth_bearer_config_rec *conf = apr_pcalloc(p, sizeof(*conf));
+
+ conf->claims = apr_hash_make(p);
+ conf->signs = apr_array_make(p, 1, sizeof(auth_bearer_signature_rec));
+ conf->verifies = apr_array_make(p, 1, sizeof(auth_bearer_signature_rec));
+
+ return conf;
+}
+
+static void *merge_auth_bearer_dir_config(apr_pool_t *p, void *basev, void *overridesv)
+{
+ auth_bearer_config_rec *newconf = apr_pcalloc(p, sizeof(*newconf));
+ auth_bearer_config_rec *base = basev;
+ auth_bearer_config_rec *overrides = overridesv;
+
+ newconf->claims = apr_hash_overlay(p, overrides->claims,
+ base->claims);
+
+ newconf->signs =
+ overrides->signs_set ? overrides->signs : base->signs;
+ newconf->signs_set = overrides->signs_set || base->signs_set;
+
+ newconf->verifies =
+ overrides->verifies_set ? overrides->verifies : base->verifies;
+ newconf->verifies_set = overrides->verifies_set || base->verifies_set;
+
+ return newconf;
+}
+
+static const char *set_jwt_claim(cmd_parms *cmd, void *config,
+ const char *op, const char *key, const char *expression)
+{
+ auth_bearer_config_rec *conf = (auth_bearer_config_rec *) config;
+ const char *err;
+
+ if (!strcasecmp(op, "set")) {
+ ap_expr_info_t *expr;
+
+ expr = ap_expr_parse_cmd(cmd, expression, AP_EXPR_FLAG_STRING_RESULT,
+ &err, NULL);
+ if (err) {
+ return apr_psprintf(cmd->pool,
+ "Could not parse claim '%s' expression '%s': %s", key,
+ expression, err);
+ }
+
+ apr_hash_set(conf->claims, key, APR_HASH_KEY_STRING, expr);
+
+ } else if (!strcasecmp(op, "unset")) {
+
+ apr_hash_set(conf->claims, key, APR_HASH_KEY_STRING, NULL);
+
+ } else {
+
+ return apr_psprintf(cmd->pool,
+ "Could not parse claim operation '%s', "
+ "values should be 'set' or 'unset'", op);
+
+ }
+
+ return NULL;
+}
+
+static const char *set_jwt_sign(cmd_parms * cmd, void *config,
+ const char *alg, const char *type, const char *sig)
+{
+ auth_bearer_config_rec *dconf = (auth_bearer_config_rec *) config;
+
+ auth_bearer_signature_rec *srec = apr_array_push(dconf->signs);
+
+ /* handle the algorithm */
+ if (!strcasecmp(alg, "none")) {
+ srec->jws_alg = JWS_ALG_TYPE_NONE;
+ if (type || sig) {
+ return "AuthtJwtSign: algorithm 'none' has extra parameters";
+ }
+ }
+ else if (!strcasecmp(alg, "HS256")) {
+ srec->jws_alg = JWS_ALG_TYPE_HS256;
+ }
+ else {
+ return apr_psprintf(cmd->pool, "AuthtJwtSign: algorithm not supported: %s", alg);
+ }
+
+ /* handle the file */
+ if (type) {
+ if (!strcasecmp(type, "file")) {
+ apr_file_t *file;
+ apr_finfo_t finfo;
+ apr_status_t status;
+
+ sig = ap_server_root_relative(cmd->temp_pool, sig);
+
+ status = apr_file_open(&file, sig, APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, cmd->pool);
+ if (status != APR_SUCCESS) {
+ char buf[1024];
+ apr_strerror(status, buf, sizeof(buf));
+ return apr_psprintf(cmd->pool,
+ "AuthtJwtSign: file '%s' could not be opened: %s", sig,
+ buf);
+ }
+
+ status = apr_file_info_get(&finfo, APR_FINFO_TYPE | APR_FINFO_SIZE,
+ file);
+ if (status != APR_SUCCESS) {
+ char buf[1024];
+ apr_strerror(status, buf, sizeof(buf));
+ return apr_psprintf(cmd->pool,
+ "AuthtJwtSign: info could not be obtained for '%s': %s",
+ sig, buf);
+ }
+
+ srec->secret = apr_palloc(cmd->pool, finfo.size);
+ srec->secret_len = finfo.size;
+
+ status = apr_file_read_full(file, srec->secret,
+ srec->secret_len, NULL);
+ if (status != APR_SUCCESS) {
+ char buf[1024];
+ apr_strerror(status, buf, sizeof(buf));
+ return apr_psprintf(cmd->pool,
+ "AuthtJwtSign: file '%s' could not be read: %s", sig,
+ buf);
+ }
+
+ apr_file_close(file);
+
+ }
+ else {
+ return apr_psprintf(cmd->pool,
+ "AuthtJwtSign: parameter '%s' is not 'file'", type);
+ }
+ }
+
+ dconf->signs_set = 1;
+
+ return NULL;
+}
+
+static const char *set_jwt_verify(cmd_parms * cmd, void *config,
+ const char *alg, const char *type, const char *sig)
+{
+ auth_bearer_config_rec *dconf = (auth_bearer_config_rec *) config;
+
+ auth_bearer_signature_rec *srec = apr_array_push(dconf->verifies);
+
+ /* handle the algorithm */
+ if (!strcasecmp(alg, "none")) {
+ srec->jws_alg = JWS_ALG_TYPE_NONE;
+ if (type || sig) {
+ return "AuthtJwtVerify: algorithm 'none' has extra parameters";
+ }
+ }
+ else if (!strcasecmp(alg, "HS256")) {
+ srec->jws_alg = JWS_ALG_TYPE_HS256;
+ }
+ else {
+ return apr_psprintf(cmd->pool, "AuthtJwtVerify: algorithm not supported: %s", alg);
+ }
+
+ /* handle the file */
+ if (type) {
+ if (!strcasecmp(type, "file")) {
+ apr_file_t *file;
+ apr_finfo_t finfo;
+ apr_status_t status;
+
+ sig = ap_server_root_relative(cmd->temp_pool, sig);
+
+ status = apr_file_open(&file, sig, APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, cmd->pool);
+ if (status != APR_SUCCESS) {
+ char buf[1024];
+ apr_strerror(status, buf, sizeof(buf));
+ return apr_psprintf(cmd->pool,
+ "AuthtJwtVerify: file '%s' could not be opened: %s", sig,
+ buf);
+ }
+
+ status = apr_file_info_get(&finfo, APR_FINFO_TYPE | APR_FINFO_SIZE,
+ file);
+ if (status != APR_SUCCESS) {
+ char buf[1024];
+ apr_strerror(status, buf, sizeof(buf));
+ return apr_psprintf(cmd->pool,
+ "AuthtJwtVerify: info could not be obtained for '%s': %s",
+ sig, buf);
+ }
+
+ srec->secret = apr_palloc(cmd->pool, finfo.size);
+ srec->secret_len = finfo.size;
+
+ status = apr_file_read_full(file, srec->secret,
+ srec->secret_len, NULL);
+ if (status != APR_SUCCESS) {
+ char buf[1024];
+ apr_strerror(status, buf, sizeof(buf));
+ return apr_psprintf(cmd->pool,
+ "AuthtJwtVerify: file '%s' could not be read: %s", sig,
+ buf);
+ }
+
+ apr_file_close(file);
+
+ }
+ else {
+ return apr_psprintf(cmd->pool,
+ "AuthtJwtVerify: parameter '%s' is not 'file'", type);
+ }
+ }
+
+ dconf->verifies_set = 1;
+
+ return NULL;
+}
+
+static const char *set_jwt_driver(cmd_parms * cmd, void *config, const char *arg)
+{
+ auth_bearer_conf *conf =
+ (auth_bearer_conf *)ap_get_module_config(cmd->server->module_config,
+ &autht_jwt_module);
+
+ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
+
+ if (err != NULL) {
+ return err;
+ }
+
+ conf->library = ap_getword_conf(cmd->pool, &arg);
+ conf->params = arg;
+ conf->library_set = 1;
+
+ return NULL;
+}
+
+static const command_rec auth_bearer_cmds[] =
+{
+ AP_INIT_TAKE13("AuthtJwtVerify", set_jwt_verify, NULL, RSRC_CONF|OR_AUTHCFG,
+ "The JWS signing algorithm and passphrase/key to verify an incoming JWT token"),
+ AP_INIT_TAKE13("AuthtJwtSign", set_jwt_sign, NULL, RSRC_CONF|OR_AUTHCFG,
+ "The JWS signing algorithm and passphrase/key to sign an outgoing JWT token"),
+
+ AP_INIT_TAKE23("AuthtJwtClaim", set_jwt_claim, NULL, OR_AUTHCFG,
+ "Set a claim with the given name and expression, or "
+ "unset the claim with the given name."),
+
+ AP_INIT_RAW_ARGS("AuthtJwtDriver", set_jwt_driver, NULL, RSRC_CONF,
+ "The underlying crypto library driver to use"),
+
+ {NULL}
+};
+
+typedef struct claim_iter_t {
+ request_rec *r;
+ apr_json_value_t *object;
+} claim_iter_t;
+
+static int claim_iter(void *ctx, const void *key, apr_ssize_t klen,
+ const void *val)
+{
+ const char *err, *value;
+ claim_iter_t *iter = ctx;
+ request_rec *r = iter->r;
+
+ value = ap_expr_str_exec(r, val, &err);
+ if (err) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO()
+ "AuthtJwtClaim: could not evaluate '%s' expression "
+ "'%s' for URI '%s': %s",
+ (char * )key, (char * )val, r->uri, err);
+ return FALSE;
+ }
+
+
+ apr_json_object_set(iter->object, key, klen,
+ apr_json_string_create(r->pool, value, strlen(value)), r->pool);
+
+ return TRUE;
+}
+
+static apr_status_t sign_cb(apr_bucket_brigade *bb, apr_jose_t *jose,
+ apr_jose_signature_t *signature, void *ctx, apr_pool_t *pool) {
+ auth_bearer_signature_rec *srec = NULL;
+ request_rec *r = ctx;
+
+ auth_bearer_conf *sconf = ap_get_module_config(r->server->module_config,
+ &autht_jwt_module);
+
+ auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &autht_jwt_module);
+
+ if (conf->signs_set) {
+ srec = (auth_bearer_signature_rec *) conf->signs->elts;
+ }
+
+ if (srec) {
+ switch (srec->jws_alg) {
+ case JWS_ALG_TYPE_NONE: {
+
+ return APR_SUCCESS;
+ }
+ case JWS_ALG_TYPE_HS256: {
+ apr_bucket *e;
+ apr_crypto_key_rec_t *krec;
+ apr_crypto_key_t *key = NULL;
+ apr_crypto_digest_t *digest = NULL;
+ apr_crypto_digest_rec_t *rec;
+ char * buf;
+ apr_status_t status;
+
+ if (!*sconf->crypto) {
+ jose->result.msg = "token could not be signed";
+ jose->result.reason = "no crypto driver configured (set AuthtJwtDriver)";
+ return APR_EGENERAL;
+ }
+
+ krec = apr_crypto_key_rec_make(APR_CRYPTO_KTYPE_HMAC, pool);
+
+ krec->k.hmac.digest = APR_CRYPTO_DIGEST_SHA256;
+ krec->k.hmac.secret = srec->secret;
+ krec->k.hmac.secretLen = srec->secret_len;
+
+ status = apr_crypto_key(&key, krec, *sconf->crypto, pool);
+ if (status != APR_SUCCESS) {
+ jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+ apr_strerror(status, buf, HUGE_STRING_LEN);
+ jose->result.msg = "token could not be signed";
+ return status;
+ }
+
+ rec = apr_crypto_digest_rec_make(APR_CRYPTO_DTYPE_SIGN, pool);
+
+ status = apr_crypto_digest_init(&digest, key, rec, pool);
+ if (status != APR_SUCCESS) {
+ jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+ apr_strerror(status, buf, HUGE_STRING_LEN);
+ jose->result.msg = "token could not be signed";
+ return status;
+ }
+
+ for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e =
+ APR_BUCKET_NEXT(e)) {
+ const char *str;
+ apr_size_t len;
+
+ /* If we see an EOS, don't bother doing anything more. */
+ if (APR_BUCKET_IS_EOS(e)) {
+ break;
+ }
+
+ status = apr_bucket_read(e, &str, &len, APR_BLOCK_READ);
+ if (status != APR_SUCCESS) {
+ jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+ apr_strerror(status, buf, HUGE_STRING_LEN);
+ jose->result.msg = "token could not be signed";
+ return status;
+ }
+
+ status = apr_crypto_digest_update(digest,
+ (const unsigned char *) str, len);
+ if (status != APR_SUCCESS) {
+ jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+ apr_strerror(status, buf, HUGE_STRING_LEN);
+ jose->result.msg = "token could not be signed";
+ return status;
+ }
+ }
+
+ status = apr_crypto_digest_final(digest);
+ if (status != APR_SUCCESS) {
+ jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+ apr_strerror(status, buf, HUGE_STRING_LEN);
+ jose->result.msg = "token could not be signed";
+ return status;
+ }
+
+ signature->sig.data = rec->d.sign.s;
+ signature->sig.len = rec->d.sign.slen;
+
+ return APR_SUCCESS;
+ }
+ }
+ }
+ else {
+ /* algorithm is none */
+ return APR_SUCCESS;
+ }
+
+ return APR_ENOTIMPL;
+}
+
+/* If we have set claims to be made, create a JWT token.
+ */
+static const char *jwt_get_token(request_rec *r)
+{
+ claim_iter_t iter = { 0 };
+ apr_json_value_t *claims;
+ apr_json_value_t *protect;
+ apr_jose_t jwt = { 0 };
+ apr_jose_t jws = { 0 };
+ apr_jose_signature_t signature = { 0 };
+ auth_bearer_signature_rec *srec = NULL;
+ apr_bucket_brigade *bb;
+ char *auth_line;
+ apr_size_t len;
+ apr_off_t offset;
+ apr_status_t status;
+
+ auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &autht_jwt_module);
+
+ apr_jose_cb_t cb = { 0 };
+
+ cb.sign = sign_cb;
+ cb.ctx = r;
+
+ if (!conf->claims || !apr_hash_count(conf->claims)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r,
+ APLOGNO() "AuthtJwtClaim: could not encode a JWT token for URI '%s': no claims",
+ r->uri);
+ return "error:no-claims";
+ }
+
+ if (conf->verifies_set) {
+ srec = (auth_bearer_signature_rec *)conf->verifies->elts;
+ }
+
+ /* create a JWT containing the claims */
+ claims = apr_json_object_create(r->pool);
+ iter.object = claims;
+ iter.r = r;
+
+ /* iterate through our claims */
+ if (!apr_hash_do(claim_iter, &iter, conf->claims)) {
+ return "error:claim-failed";
+ }
+
+ apr_jose_jwt_make(&jwt, claims, r->pool);
+ protect = apr_json_object_create(r->pool);
+
+ apr_json_object_set(protect, APR_JOSE_JWSE_TYPE,
+ APR_JSON_VALUE_STRING,
+ apr_json_string_create(r->pool, APR_JOSE_JWSE_TYPE_JWT,
+ APR_JSON_VALUE_STRING), r->pool);
+
+ if (srec) {
+ /* which signature type do we have? */
+ switch (srec->jws_alg) {
+ case JWS_ALG_TYPE_NONE: {
+ apr_json_object_set(protect, APR_JOSE_JWKSE_ALGORITHM,
+ APR_JSON_VALUE_STRING,
+ apr_json_string_create(r->pool, APR_JOSE_JWA_NONE,
+ APR_JSON_VALUE_STRING), r->pool);
+
+ break;
+ }
+ case JWS_ALG_TYPE_HS256: {
+ apr_json_object_set(protect, APR_JOSE_JWKSE_ALGORITHM,
+ APR_JSON_VALUE_STRING,
+ apr_json_string_create(r->pool, APR_JOSE_JWA_HS256,
+ APR_JSON_VALUE_STRING), r->pool);
+
+ break;
+ }
+ }
+
+ }
+ else {
+ /* no srec defaults to none */
+ apr_json_object_set(protect, APR_JOSE_JWKSE_ALGORITHM,
+ APR_JSON_VALUE_STRING,
+ apr_json_string_create(r->pool, APR_JOSE_JWA_NONE,
+ APR_JSON_VALUE_STRING), r->pool);
+ }
+
+ apr_jose_signature_make(&signature, NULL, protect, r->pool);
+ apr_jose_jws_make(&jws, &signature, NULL, &jwt, r->pool);
+
+ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+ status = apr_jose_encode(bb, NULL, NULL, &jws, &cb, r->pool);
+ if (APR_SUCCESS != status) {
+ const apu_err_t *err = apr_jose_error(&jws);
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
+ APLOGNO() "AuthtJwtClaim: could not encode a JWT token for URI '%s': %s: %s",
+ r->uri, err->msg, err->reason);
+ return "error:could-not-encode";
+ }
+
+ apr_brigade_length(bb, 1, &offset);
+ auth_line = apr_pcalloc(r->pool, offset + 1);
+ len = offset;
+ apr_brigade_flatten(bb, auth_line, &len);
+ auth_line[offset] = 0;
+
+ return auth_line;
+}
+
+static const char *jwt_expr_var_fn(ap_expr_eval_ctx_t *ctx, const void *data)
+{
+ char *var = (char *)data;
+
+ if (var && *var && ctx->r && ap_cstr_casecmp(var, "TOKEN") == 0) {
+ return jwt_get_token(ctx->r);
+ }
+ return NULL;
+}
+
+static int jwt_expr_lookup(ap_expr_lookup_parms *parms)
+{
+ switch (parms->type) {
+ case AP_EXPR_FUNC_VAR:
+ /* for now, we just handle everything that starts with JWT_.
+ */
+ if (strncasecmp(parms->name, "JWT_", 4) == 0) {
+ *parms->func = jwt_expr_var_fn;
+ *parms->data = parms->name + 4;
+ return OK;
+ }
+ break;
+ }
+ return DECLINED;
+}
+
+static apr_status_t verify_cb(apr_bucket_brigade *bb,
+ apr_jose_t *jose, apr_jose_signature_t *signature, void *ctx,
+ int *vflags, apr_pool_t *pool)
+{
+ request_rec *r = ctx;
+ apr_json_kv_t *alg = NULL;
+
+ auth_bearer_conf *sconf = ap_get_module_config(r->server->module_config,
+ &autht_jwt_module);
+
+ auth_bearer_config_rec *conf = ap_get_module_config(r->per_dir_config,
+ &autht_jwt_module);
+
+ int alg_supported = 0;
+
+ if (signature) {
+ apr_json_value_t *ph = signature->protected_header;
+
+ if (ph && ph->type == APR_JSON_OBJECT) {
+
+ alg = apr_json_object_get(ph, APR_JOSE_JWKSE_ALGORITHM,
+ APR_JSON_VALUE_STRING);
+
+ }
+ }
+
+ if (!alg) {
+ apr_errprintf(&jose->result, r->pool, "", APR_EGENERAL,
+ "JWT token protected header has no '"
+ APR_JOSE_JWKSE_ALGORITHM
+ "' for URI '%s'",
+ r->uri);
+ return APR_EGENERAL;
+ }
+
+ if (alg->v->type != APR_JSON_STRING) {
+ apr_errprintf(&jose->result, r->pool, "", APR_EGENERAL,
+ "JWT token protected header '"
+ APR_JOSE_JWKSE_ALGORITHM
+ "' is not a string for URI '%s'",
+ r->uri);
+ return APR_EGENERAL;
+ }
+
+ /* first pass, is our algorithm supported? */
+ for (int i = 0; i < conf->verifies->nelts; i++) {
+ auth_bearer_signature_rec *srec = &APR_ARRAY_IDX(conf->verifies,
+ i, auth_bearer_signature_rec);
+
+ /* which signature type do we have? */
+ switch (srec->jws_alg) {
+ case JWS_ALG_TYPE_NONE: {
+ if (!strncmp(alg->v->value.string.p, "none",
+ alg->v->value.string.len)) {
+ alg_supported = 1;
+ }
+ break;
+ }
+ case JWS_ALG_TYPE_HS256: {
+ if (!strncmp(alg->v->value.string.p, "HS256",
+ alg->v->value.string.len)) {
+ alg_supported = 1;
+ }
+ break;
+ }
+ }
+
+ }
+
+ /* we don't support the algorithm */
+ if (!alg_supported) {
+ apr_errprintf(&jose->result, r->pool, "", APR_ENODIGEST,
+ "JWT token protected header '"
+ APR_JOSE_JWKSE_ALGORITHM
+ "' %s is not supported for URI '%s'",
+ alg->v->value.string.p, r->uri);
+ return APR_ENODIGEST;
+ }
+
+ /* second pass, does the signature match? */
+ for (int i = 0; i < conf->verifies->nelts; i++) {
+ auth_bearer_signature_rec *srec = &APR_ARRAY_IDX(conf->verifies,
+ i, auth_bearer_signature_rec);
+
+ /* which signature type do we have? */
+ switch (srec->jws_alg) {
+ case JWS_ALG_TYPE_NONE: {
+ if (!strncmp(alg->v->value.string.p, "none",
+ alg->v->value.string.len)) {
+ return APR_SUCCESS;
+ }
+ break;
+ }
+ case JWS_ALG_TYPE_HS256: {
+ if (!strncmp(alg->v->value.string.p, "HS256",
+ alg->v->value.string.len)) {
+
+ apr_bucket *e;
+ apr_crypto_key_rec_t *krec;
+ apr_crypto_key_t *key = NULL;
+ apr_crypto_digest_t *digest = NULL;
+ apr_crypto_digest_rec_t *rec;
+ char * buf;
+ apr_status_t status;
+
+ if (!*sconf->crypto) {
+ jose->result.msg = "token could not be verified";
+ jose->result.reason = "no crypto driver configured (set AuthtJwtDriver)";
+ return APR_EGENERAL;
+ }
+
+ krec = apr_crypto_key_rec_make(APR_CRYPTO_KTYPE_HMAC, pool);
+
+ krec->k.hmac.digest = APR_CRYPTO_DIGEST_SHA256;
+ krec->k.hmac.secret = srec->secret;
+ krec->k.hmac.secretLen = srec->secret_len;
+
+ status = apr_crypto_key(&key, krec, *sconf->crypto, pool);
+ if (status != APR_SUCCESS) {
+ jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+ apr_strerror(status, buf, HUGE_STRING_LEN);
+ jose->result.msg = "token could not be verified";
+ return status;
+ }
+
+ rec = apr_crypto_digest_rec_make(APR_CRYPTO_DTYPE_SIGN, pool);
+
+ status = apr_crypto_digest_init(&digest, key, rec, pool);
+ if (status != APR_SUCCESS) {
+ jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+ apr_strerror(status, buf, HUGE_STRING_LEN);
+ jose->result.msg = "token could not be verified";
+ return status;
+ }
+
+ for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e =
+ APR_BUCKET_NEXT(e)) {
+ const char *str;
+ apr_size_t len;
+
+ /* If we see an EOS, don't bother doing anything more. */
+ if (APR_BUCKET_IS_EOS(e)) {
+ break;
+ }
+
+ status = apr_bucket_read(e, &str, &len, APR_BLOCK_READ);
+ if (status != APR_SUCCESS) {
+ jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+ apr_strerror(status, buf, HUGE_STRING_LEN);
+ jose->result.msg = "token could not be verified";
+ return status;
+ }
+
+ status = apr_crypto_digest_update(digest,
+ (const unsigned char *) str, len);
+ if (status != APR_SUCCESS) {
+ jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+ apr_strerror(status, buf, HUGE_STRING_LEN);
+ jose->result.msg = "token could not be verified";
+ return status;
+ }
+ }
+
+ status = apr_crypto_digest_final(digest);
+ if (status != APR_SUCCESS) {
+ jose->result.reason = buf = apr_pcalloc(pool, HUGE_STRING_LEN);
+ apr_strerror(status, buf, HUGE_STRING_LEN);
+ jose->result.msg = "token could not be verified";
+ return status;
+ }
+
+ if (signature->sig.len == rec->d.sign.slen &&
+ !memcmp(signature->sig.data, rec->d.sign.s, rec->d.sign.slen)) {
+ return APR_SUCCESS;
+ }
+
+ }
+ break;
+ }
+ }
+
+ }
+
+ /* no match, oh well */
+ apr_errprintf(&jose->result, r->pool, "", APR_ENODIGEST,
+ "JWT token protected header '"
+ APR_JOSE_JWKSE_ALGORITHM
+ "' %s is not supported for URI '%s'",
+ alg->v->value.string.p, r->uri);
+ return APR_ENOVERIFY;
+}
+
+static autht_status check_token(request_rec *r, const char *type,
+ const char *token)
+{
+ apr_bucket_brigade *bb;
+ apr_jose_t *jose = NULL;
+ apr_json_kv_t *kv;
+ apr_status_t status;
+
+ apr_jose_cb_t cb;
+
+ apr_table_t *e = r->subprocess_env;
+
+ const char *aud = NULL;
+ const char *sub = NULL;
+
+ apr_int64_t exp;
+ apr_int64_t nbf;
+
+ int exp_set = 0;
+ int nbf_set = 0;
+
+ cb.verify = verify_cb;
+ cb.decrypt = NULL;
+ cb.ctx = r;
+
+ bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+
+ if (token) {
+ apr_brigade_write(bb, NULL, NULL, token, strlen(token));
+ }
+
+ status = apr_jose_decode(&jose, "JWT", bb, &cb, 10, APR_JOSE_FLAG_NONE, r->pool);
+
+ if (APR_SUCCESS != status) {
+ const apu_err_t *err = apr_jose_error(jose);
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+ APLOGNO() "AuthtJwt: could not decode a JWT token for URI '%s': %s: %s",
+ r->uri, err->msg, err->reason);
+ return AUTHT_DENIED;
+ }
+
+ if (jose->type != APR_JOSE_TYPE_JWT) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+ APLOGNO() "AuthtJwt: JOSE token was not a JWT token for URI '%s'",
+ r->uri);
+ return AUTHT_DENIED;
+ }
+
+ /* first pass - identity sub and aud */
+ kv = apr_json_object_first(jose->jose.jwt->claims);
+ do {
+
+ /* ignore any key that isn't a string */
+ if (kv->k->type != APR_JSON_STRING) {
+ continue;
+ }
+
+ if (!strncmp("aud", kv->k->value.string.p, kv->k->value.string.len)) {
+ if (kv->v->type == APR_JSON_STRING) {
+ aud = apr_pstrndup(r->pool, kv->v->value.string.p,
+ kv->v->value.string.len);
+ }
+ }
+
+ if (!strncmp("sub", kv->k->value.string.p, kv->k->value.string.len)) {
+ if (kv->v->type == APR_JSON_STRING) {
+ sub = apr_pstrndup(r->pool, kv->v->value.string.p,
+ kv->v->value.string.len);
+ }
+ }
+
+ if (!strncmp("exp", kv->k->value.string.p, kv->k->value.string.len)) {
+ if (kv->v->type == APR_JSON_LONG) {
+ exp = kv->v->value.lnumber;
+ exp_set = 1;
+ }
+ }
+
+ if (!strncmp("nbf", kv->k->value.string.p, kv->k->value.string.len)) {
+ if (kv->v->type == APR_JSON_LONG) {
+ nbf = kv->v->value.lnumber;
+ nbf_set = 1;
+ }
+ }
+
+
+ } while ((kv = apr_json_object_next(jose->jose.jwt->claims, kv)));
+
+ if (!aud) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+ APLOGNO() "AuthtJwt: JWT token 'aud' value was missing and did not match AuthName '%s' for URI '%s'",
+ ap_auth_name(r), r->uri);
+ return AUTHT_MISMATCH;
+ }
+
+ if (strcmp(aud, ap_auth_name(r))) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+ APLOGNO() "AuthtJwt: JWT token 'aud' value '%s' did not match AuthName '%s' for URI '%s'",
+ aud, ap_auth_name(r), r->uri);
+ return AUTHT_MISMATCH;
+ }
+
+ if (exp_set || nbf_set) {
+ apr_time_t now = apr_time_now();
+
+ if (exp_set &&
+ exp < apr_time_sec(now)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+ APLOGNO() "AuthtJwt: JWT token is expired (%"
+ APR_INT64_T_FMT " < %" APR_TIME_T_FMT ") for URI '%s'",
+ exp, apr_time_sec(now), r->uri);
+ return AUTHT_EXPIRED;
+ }
+
+ if (nbf_set &&
+ nbf > apr_time_sec(now)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+ APLOGNO() "AuthtJwt: JWT token is not yet valid (%"
+ APR_INT64_T_FMT " > %" APR_TIME_T_FMT ") for URI '%s'",
+ nbf, apr_time_sec(now), r->uri);
+ return AUTHT_INVALID;
+ }
+ }
+
+ /* we are good at this point - accept the token */
+
+ if (sub) {
+ r->user = apr_pstrdup(r->pool, sub);
+ }
+
+ /* second pass - add all string claims to the environment, prefixed by TOKEN_ */
+ kv = apr_json_object_first(jose->jose.jwt->claims);
+ do {
+ char *key, *val;
+ int j;
+
+ /* ignore anything that isn't a string */
+ if (kv->k->type != APR_JSON_STRING || kv->v->type != APR_JSON_STRING) {
+ continue;
+ }
+
+ key = apr_psprintf(r->pool, AUTHT_PREFIX "%.*s", (int)kv->k->value.string.len, kv->k->value.string.p);
+ j = sizeof(AUTHT_PREFIX);
+ while (key[j]) {
+ if (apr_isalnum(key[j])) {
+ key[j] = apr_toupper(key[j]);
+ }
+ else {
+ key[j] = '_';
+ }
+ j++;
+ }
+
+ val = apr_pstrndup(r->pool, kv->v->value.string.p,
+ kv->v->value.string.len);
+
+ apr_table_setn(e, key, val);
+
+ } while ((kv = apr_json_object_next(jose->jose.jwt->claims, kv)));
+
+ return AUTHT_GRANTED;
+}
+
+static const autht_provider autht_jwt_provider =
+{
+ &check_token
+};
+
+static void register_hooks(apr_pool_t *p)
+{
+ ap_register_auth_provider(p, AUTHT_PROVIDER_GROUP, "jwt",
+ AUTHT_PROVIDER_VERSION,
+ &autht_jwt_provider, AP_AUTH_INTERNAL_PER_CONF);
+ ap_hook_expr_lookup(jwt_expr_lookup, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_post_config(auth_bearer_init, NULL, NULL, APR_HOOK_LAST);
+}
+
+AP_DECLARE_MODULE(autht_jwt) =
+{
+ STANDARD20_MODULE_STUFF,
+ create_auth_bearer_dir_config, /* dir config creater */
+ merge_auth_bearer_dir_config, /* dir merger --- default is to override */
+ create_auth_bearer_config, /* server config */
+ merge_auth_bearer_config, /* merge server config */
+ auth_bearer_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};