modules/aaa/mod_autht_jwt.c (858 lines of code) (raw):

/* 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. */ /* apr_jose support requires >= 1.7 */ #if APU_MAJOR_VERSION > 1 || \ (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION > 6) #define HAVE_APU_JOSE 1 #endif #include "httpd.h" #include "http_config.h" #ifdef HAVE_APU_JOSE #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 "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; unsigned int signs_set :1, verifies_set :1; } auth_bearer_config_rec; typedef struct { const char *library; const char *params; apr_crypto_t **crypto; unsigned int library_set :1; } 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(10432) "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(10433) "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(10434) "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(10435) "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(10436) "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(10437) "The crypto library '%s' could not be initialised", conf->library); return rv; } ap_log_error(APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(10438) "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(10439) "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(10440) "AuthtJwtClaim: could not encode a JWT token for URI '%s': no claims", r->uri); return "error:no-claims"; } /* sign with the first key, if present */ if (conf->signs_set) { srec = (auth_bearer_signature_rec *)conf->signs->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(10441) "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(10442) "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(10443) "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(10444) "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(10445) "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(10446) "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(10447) "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 */ }; #else static const command_rec auth_bearer_cmds[] = { {NULL} }; static void register_hooks(apr_pool_t *p) { } AP_DECLARE_MODULE(autht_jwt) = { STANDARD20_MODULE_STUFF, NULL, /* dir config creater */ NULL, /* dir merger --- default is to override */ NULL, /* server config */ NULL, /* merge server config */ auth_bearer_cmds, /* command apr_table_t */ register_hooks /* register hooks */ }; #endif /* HAVE_APU_JOSE */