summaryrefslogtreecommitdiffstats
path: root/modules/aaa/mod_autht_jwt.c
diff options
context:
space:
mode:
Diffstat (limited to 'modules/aaa/mod_autht_jwt.c')
-rw-r--r--modules/aaa/mod_autht_jwt.c1089
1 files changed, 1089 insertions, 0 deletions
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 */
+};