blob: f1754cfb5c5e44f91db70d55633166c8dcd011a2 [file] [log] [blame]
/* 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;
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(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 */