mod_auth_openidc-2.3.3/src/mod_auth_openidc.c 0000644 0000765 0000024 00000362511 13203320522 021112 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Initially based on mod_auth_cas.c:
* https://github.com/Jasig/mod_auth_cas
*
* Other code copied/borrowed/adapted:
* shared memory caching: mod_auth_mellon
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*
**************************************************************************/
#include "apr_hash.h"
#include "apr_strings.h"
#include "ap_config.h"
#include "ap_provider.h"
#include "apr_lib.h"
#include "apr_file_io.h"
#include "apr_sha1.h"
#include "apr_base64.h"
#include "httpd.h"
#include "http_core.h"
#include "http_config.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"
#include "mod_auth_openidc.h"
// TODO:
// - sort out oidc_cfg vs. oidc_dir_cfg stuff
// - rigid input checking on discovery responses
// - check self-issued support
// - README.quickstart
// - refresh metadata once-per too? (for non-signing key changes)
extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
/*
* clean any suspicious headers in the HTTP request sent by the user agent
*/
static void oidc_scrub_request_headers(request_rec *r, const char *claim_prefix,
apr_hash_t *scrub) {
const int prefix_len = claim_prefix ? strlen(claim_prefix) : 0;
/* get an array representation of the incoming HTTP headers */
const apr_array_header_t * const h = apr_table_elts(r->headers_in);
/* table to keep the non-suspicious headers */
apr_table_t *clean_headers = apr_table_make(r->pool, h->nelts);
/* loop over the incoming HTTP headers */
const apr_table_entry_t * const e = (const apr_table_entry_t *) h->elts;
int i;
for (i = 0; i < h->nelts; i++) {
const char * const k = e[i].key;
/* is this header's name equivalent to a header that needs scrubbing? */
const char *hdr =
(k != NULL) && (scrub != NULL) ?
apr_hash_get(scrub, k, APR_HASH_KEY_STRING) : NULL;
const int header_matches = (hdr != NULL)
&& (oidc_strnenvcmp(k, hdr, -1) == 0);
/*
* would this header be interpreted as a mod_auth_openidc attribute? Note
* that prefix_len will be zero if no attr_prefix is defined,
* so this will always be false. Also note that we do not
* scrub headers if the prefix is empty because every header
* would match.
*/
const int prefix_matches = (k != NULL) && prefix_len
&& (oidc_strnenvcmp(k, claim_prefix, prefix_len) == 0);
/* add to the clean_headers if non-suspicious, skip and report otherwise */
if (!prefix_matches && !header_matches) {
apr_table_addn(clean_headers, k, e[i].val);
} else {
oidc_warn(r, "scrubbed suspicious request header (%s: %.32s)", k,
e[i].val);
}
}
/* overwrite the incoming headers with the cleaned result */
r->headers_in = clean_headers;
}
/*
* scrub all mod_auth_openidc related headers
*/
void oidc_scrub_headers(request_rec *r) {
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
if (cfg->scrub_request_headers != 0) {
const char *prefix = oidc_cfg_claim_prefix(r);
apr_hash_t *hdrs = apr_hash_make(r->pool);
if (apr_strnatcmp(prefix, "") == 0) {
if ((cfg->white_listed_claims != NULL)
&& (apr_hash_count(cfg->white_listed_claims) > 0))
hdrs = apr_hash_overlay(r->pool, cfg->white_listed_claims,
hdrs);
else
oidc_warn(r,
"both " OIDCClaimPrefix " and " OIDCWhiteListedClaims " are empty: this renders an insecure setup!");
}
char *authn_hdr = oidc_cfg_dir_authn_header(r);
if (authn_hdr != NULL)
apr_hash_set(hdrs, authn_hdr, APR_HASH_KEY_STRING, authn_hdr);
/*
* scrub all headers starting with OIDC_ first
*/
oidc_scrub_request_headers(r, OIDC_DEFAULT_HEADER_PREFIX, hdrs);
/*
* then see if the claim headers need to be removed on top of that
* (i.e. the prefix does not start with the default OIDC_)
*/
if ((strstr(prefix, OIDC_DEFAULT_HEADER_PREFIX) != prefix)) {
oidc_scrub_request_headers(r, prefix, NULL);
}
}
}
/*
* strip the session cookie from the headers sent to the application/backend
*/
void oidc_strip_cookies(request_rec *r) {
char *cookie, *ctx, *result = NULL;
const char *name = NULL;
int i;
apr_array_header_t *strip = oidc_dir_cfg_strip_cookies(r);
char *cookies = apr_pstrdup(r->pool, oidc_util_hdr_in_cookie_get(r));
if ((cookies != NULL) && (strip != NULL)) {
oidc_debug(r,
"looking for the following cookies to strip from cookie header: %s",
apr_array_pstrcat(r->pool, strip, OIDC_CHAR_COMMA));
cookie = apr_strtok(cookies, OIDC_STR_SEMI_COLON, &ctx);
do {
while (cookie != NULL && *cookie == OIDC_CHAR_SPACE)
cookie++;
for (i = 0; i < strip->nelts; i++) {
name = ((const char**) strip->elts)[i];
if ((strncmp(cookie, name, strlen(name)) == 0)
&& (cookie[strlen(name)] == OIDC_CHAR_EQUAL)) {
oidc_debug(r, "stripping: %s", name);
break;
}
}
if (i == strip->nelts) {
result = result ? apr_psprintf(r->pool, "%s%s%s", result,
OIDC_STR_SEMI_COLON, cookie) :
cookie;
}
cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &ctx);
} while (cookie != NULL);
oidc_util_hdr_in_cookie_set(r, result);
}
}
#define OIDC_SHA1_LEN 20
/*
* calculates a hash value based on request fingerprint plus a provided nonce string.
*/
static char *oidc_get_browser_state_hash(request_rec *r, const char *nonce) {
oidc_debug(r, "enter");
/* helper to hold to header values */
const char *value = NULL;
/* the hash context */
apr_sha1_ctx_t sha1;
/* Initialize the hash context */
apr_sha1_init(&sha1);
/* get the X-FORWARDED-FOR header value */
value = oidc_util_hdr_in_x_forwarded_for_get(r);
/* if we have a value for this header, concat it to the hash input */
if (value != NULL)
apr_sha1_update(&sha1, value, strlen(value));
/* get the USER-AGENT header value */
value = oidc_util_hdr_in_user_agent_get(r);
/* if we have a value for this header, concat it to the hash input */
if (value != NULL)
apr_sha1_update(&sha1, value, strlen(value));
/* get the remote client IP address or host name */
/*
int remotehost_is_ip;
value = ap_get_remote_host(r->connection, r->per_dir_config,
REMOTE_NOLOOKUP, &remotehost_is_ip);
apr_sha1_update(&sha1, value, strlen(value));
*/
/* concat the nonce parameter to the hash input */
apr_sha1_update(&sha1, nonce, strlen(nonce));
/* concat the token binding ID if present */
value = oidc_util_get_provided_token_binding_id(r);
if (value != NULL) {
oidc_debug(r,
"Provided Token Binding ID environment variable found; adding its value to the state");
apr_sha1_update(&sha1, value, strlen(value));
}
/* finalize the hash input and calculate the resulting hash output */
unsigned char hash[OIDC_SHA1_LEN];
apr_sha1_final(hash, &sha1);
/* base64url-encode the resulting hash and return it */
char *result = NULL;
oidc_base64url_encode(r, &result, (const char *) hash, OIDC_SHA1_LEN, TRUE);
return result;
}
/*
* return the name for the state cookie
*/
static char *oidc_get_state_cookie_name(request_rec *r, const char *state) {
return apr_psprintf(r->pool, "%s%s", OIDC_STATE_COOKIE_PREFIX, state);
}
/*
* return the static provider configuration, i.e. from a metadata URL or configuration primitives
*/
static apr_byte_t oidc_provider_static_config(request_rec *r, oidc_cfg *c,
oidc_provider_t **provider) {
json_t *j_provider = NULL;
char *s_json = NULL;
/* see if we should configure a static provider based on external (cached) metadata */
if ((c->metadata_dir != NULL) || (c->provider.metadata_url == NULL)) {
*provider = &c->provider;
return TRUE;
}
oidc_cache_get_provider(r, c->provider.metadata_url, &s_json);
if (s_json == NULL) {
if (oidc_metadata_provider_retrieve(r, c, NULL,
c->provider.metadata_url, &j_provider, &s_json) == FALSE) {
oidc_error(r, "could not retrieve metadata from url: %s",
c->provider.metadata_url);
return FALSE;
}
oidc_cache_set_provider(r, c->provider.metadata_url, s_json,
apr_time_now() + (c->provider_metadata_refresh_interval <= 0 ? apr_time_from_sec( OIDC_CACHE_PROVIDER_METADATA_EXPIRY_DEFAULT) : c->provider_metadata_refresh_interval));
} else {
/* correct parsing and validation was already done when it was put in the cache */
oidc_util_decode_json_object(r, s_json, &j_provider);
}
*provider = apr_pcalloc(r->pool, sizeof(oidc_provider_t));
memcpy(*provider, &c->provider, sizeof(oidc_provider_t));
if (oidc_metadata_provider_parse(r, c, j_provider, *provider) == FALSE) {
oidc_error(r, "could not parse metadata from url: %s",
c->provider.metadata_url);
if (j_provider)
json_decref(j_provider);
return FALSE;
}
json_decref(j_provider);
return TRUE;
}
/*
* return the oidc_provider_t struct for the specified issuer
*/
static oidc_provider_t *oidc_get_provider_for_issuer(request_rec *r,
oidc_cfg *c, const char *issuer, apr_byte_t allow_discovery) {
/* by default we'll assume that we're dealing with a single statically configured OP */
oidc_provider_t *provider = NULL;
if (oidc_provider_static_config(r, c, &provider) == FALSE)
return NULL;
/* unless a metadata directory was configured, so we'll try and get the provider settings from there */
if (c->metadata_dir != NULL) {
/* try and get metadata from the metadata directory for the OP that sent this response */
if ((oidc_metadata_get(r, c, issuer, &provider, allow_discovery)
== FALSE) || (provider == NULL)) {
/* don't know nothing about this OP/issuer */
oidc_error(r, "no provider metadata found for issuer \"%s\"",
issuer);
return NULL;
}
}
return provider;
}
/*
* find out whether the request is a response from an IDP discovery page
*/
static apr_byte_t oidc_is_discovery_response(request_rec *r, oidc_cfg *cfg) {
/*
* prereq: this is a call to the configured redirect_uri, now see if:
* the OIDC_DISC_OP_PARAM is present
*/
return oidc_util_request_has_parameter(r, OIDC_DISC_OP_PARAM)
|| oidc_util_request_has_parameter(r, OIDC_DISC_USER_PARAM);
}
/*
* return the HTTP method being called: only for POST data persistence purposes
*/
static const char *oidc_original_request_method(request_rec *r, oidc_cfg *cfg,
apr_byte_t handle_discovery_response) {
const char *method = OIDC_METHOD_GET;
char *m = NULL;
if ((handle_discovery_response == TRUE)
&& (oidc_util_request_matches_url(r, oidc_get_redirect_uri(r, cfg)))
&& (oidc_is_discovery_response(r, cfg))) {
oidc_util_get_request_parameter(r, OIDC_DISC_RM_PARAM, &m);
if (m != NULL)
method = apr_pstrdup(r->pool, m);
} else {
/*
* if POST preserve is not enabled for this location, there's no point in preserving
* the method either which would result in POSTing empty data on return;
* so we revert to legacy behavior
*/
if (oidc_cfg_dir_preserve_post(r) == 0)
return OIDC_METHOD_GET;
const char *content_type = oidc_util_hdr_in_content_type_get(r);
if ((r->method_number == M_POST) && (apr_strnatcmp(content_type,
OIDC_CONTENT_TYPE_FORM_ENCODED) == 0))
method = OIDC_METHOD_FORM_POST;
}
oidc_debug(r, "return: %s", method);
return method;
}
/*
* send an OpenID Connect authorization request to the specified provider preserving POST parameters using HTML5 storage
*/
apr_byte_t oidc_post_preserve_javascript(request_rec *r, const char *location,
char **javascript, char **javascript_method) {
if (oidc_cfg_dir_preserve_post(r) == 0)
return FALSE;
oidc_debug(r, "enter");
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
const char *method = oidc_original_request_method(r, cfg, FALSE);
if (apr_strnatcmp(method, OIDC_METHOD_FORM_POST) != 0)
return FALSE;
/* read the parameters that are POST-ed to us */
apr_table_t *params = apr_table_make(r->pool, 8);
if (oidc_util_read_post_params(r, params) == FALSE) {
oidc_error(r, "something went wrong when reading the POST parameters");
return FALSE;
}
const apr_array_header_t *arr = apr_table_elts(params);
const apr_table_entry_t *elts = (const apr_table_entry_t*) arr->elts;
int i;
char *json = "";
for (i = 0; i < arr->nelts; i++) {
json = apr_psprintf(r->pool, "%s'%s': '%s'%s", json,
oidc_util_escape_string(r, elts[i].key),
oidc_util_escape_string(r, elts[i].val),
i < arr->nelts - 1 ? "," : "");
}
json = apr_psprintf(r->pool, "{ %s }", json);
const char *jmethod = "preserveOnLoad";
const char *jscript =
apr_psprintf(r->pool,
" \n", jmethod, json,
location ?
apr_psprintf(r->pool, "window.location='%s';\n",
location) :
"");
if (location == NULL) {
if (javascript_method)
*javascript_method = apr_pstrdup(r->pool, jmethod);
if (javascript)
*javascript = apr_pstrdup(r->pool, jscript);
} else {
oidc_util_html_send(r, "Preserving...", jscript, jmethod,
"
Preserving...
", DONE);
}
return TRUE;
}
/*
* restore POST parameters on original_url from HTML5 local storage
*/
static int oidc_request_post_preserved_restore(request_rec *r,
const char *original_url) {
oidc_debug(r, "enter: original_url=%s", original_url);
const char *method = "postOnLoad";
const char *script =
apr_psprintf(r->pool,
" \n", method, original_url);
const char *body = "
Restoring...
\n"
" \n";
return oidc_util_html_send(r, "Restoring...", script, method, body,
DONE);
}
/*
* parse state that was sent to us by the issuer
*/
static apr_byte_t oidc_unsolicited_proto_state(request_rec *r, oidc_cfg *c,
const char *state, oidc_proto_state_t **proto_state) {
char *alg = NULL;
oidc_debug(r, "enter: state header=%s",
oidc_proto_peek_jwt_header(r, state, &alg));
oidc_jose_error_t err;
oidc_jwk_t *jwk = NULL;
if (oidc_util_create_symmetric_key(r, c->provider.client_secret,
oidc_alg2keysize(alg), OIDC_JOSE_ALG_SHA256,
TRUE, &jwk) == FALSE)
return FALSE;
oidc_jwt_t *jwt = NULL;
if (oidc_jwt_parse(r->pool, state, &jwt,
oidc_util_merge_symmetric_key(r->pool, c->private_keys, jwk),
&err) == FALSE) {
oidc_error(r,
"could not parse JWT from state: invalid unsolicited response: %s",
oidc_jose_e2s(r->pool, err));
return FALSE;
}
oidc_jwk_destroy(jwk);
oidc_debug(r, "successfully parsed JWT from state");
if (jwt->payload.iss == NULL) {
oidc_error(r, "no \"%s\" could be retrieved from JWT state, aborting",
OIDC_CLAIM_ISS);
oidc_jwt_destroy(jwt);
return FALSE;
}
oidc_provider_t *provider = oidc_get_provider_for_issuer(r, c,
jwt->payload.iss, FALSE);
if (provider == NULL) {
oidc_jwt_destroy(jwt);
return FALSE;
}
/* validate the state JWT, validating optional exp + iat */
if (oidc_proto_validate_jwt(r, jwt, provider->issuer, FALSE, FALSE,
provider->idtoken_iat_slack) == FALSE) {
oidc_jwt_destroy(jwt);
return FALSE;
}
char *rfp = NULL;
if (oidc_jose_get_string(r->pool, jwt->payload.value.json, OIDC_CLAIM_RFP,
TRUE, &rfp, &err) == FALSE) {
oidc_error(r,
"no \"%s\" claim could be retrieved from JWT state, aborting: %s",
OIDC_CLAIM_RFP, oidc_jose_e2s(r->pool, err));
oidc_jwt_destroy(jwt);
return FALSE;
}
if (apr_strnatcmp(rfp, OIDC_PROTO_ISS) != 0) {
oidc_error(r, "\"%s\" (%s) does not match \"%s\", aborting",
OIDC_CLAIM_RFP, rfp, OIDC_PROTO_ISS);
oidc_jwt_destroy(jwt);
return FALSE;
}
char *target_link_uri = NULL;
oidc_jose_get_string(r->pool, jwt->payload.value.json,
OIDC_CLAIM_TARGET_LINK_URI,
FALSE, &target_link_uri, NULL);
if (target_link_uri == NULL) {
if (c->default_sso_url == NULL) {
oidc_error(r,
"no \"%s\" claim could be retrieved from JWT state and no " OIDCDefaultURL " is set, aborting",
OIDC_CLAIM_TARGET_LINK_URI);
oidc_jwt_destroy(jwt);
return FALSE;
}
target_link_uri = c->default_sso_url;
}
if (c->metadata_dir != NULL) {
if ((oidc_metadata_get(r, c, jwt->payload.iss, &provider, FALSE)
== FALSE) || (provider == NULL)) {
oidc_error(r, "no provider metadata found for provider \"%s\"",
jwt->payload.iss);
oidc_jwt_destroy(jwt);
return FALSE;
}
}
char *jti = NULL;
oidc_jose_get_string(r->pool, jwt->payload.value.json, OIDC_CLAIM_JTI,
FALSE, &jti,
NULL);
if (jti == NULL) {
char *cser = oidc_jwt_serialize(r->pool, jwt, &err);
if (cser == NULL)
return FALSE;
if (oidc_util_hash_string_and_base64url_encode(r, OIDC_JOSE_ALG_SHA256,
cser, &jti) == FALSE) {
oidc_error(r,
"oidc_util_hash_string_and_base64url_encode returned an error");
return FALSE;
}
}
char *replay = NULL;
oidc_cache_get_jti(r, jti, &replay);
if (replay != NULL) {
oidc_error(r,
"the \"%s\" value (%s) passed in the browser state was found in the cache already; possible replay attack!?",
OIDC_CLAIM_JTI, jti);
oidc_jwt_destroy(jwt);
return FALSE;
}
/* jti cache duration is the configured replay prevention window for token issuance plus 10 seconds for safety */
apr_time_t jti_cache_duration = apr_time_from_sec(
provider->idtoken_iat_slack * 2 + 10);
/* store it in the cache for the calculated duration */
oidc_cache_set_jti(r, jti, jti, apr_time_now() + jti_cache_duration);
oidc_debug(r,
"jti \"%s\" validated successfully and is now cached for %" APR_TIME_T_FMT " seconds",
jti, apr_time_sec(jti_cache_duration));
jwk = NULL;
if (oidc_util_create_symmetric_key(r, c->provider.client_secret, 0,
NULL, TRUE, &jwk) == FALSE)
return FALSE;
oidc_jwks_uri_t jwks_uri = { provider->jwks_uri,
provider->jwks_refresh_interval, provider->ssl_validate_server };
if (oidc_proto_jwt_verify(r, c, jwt, &jwks_uri,
oidc_util_merge_symmetric_key(r->pool, NULL, jwk)) == FALSE) {
oidc_error(r, "state JWT could not be validated, aborting");
oidc_jwt_destroy(jwt);
return FALSE;
}
oidc_jwk_destroy(jwk);
oidc_debug(r, "successfully verified state JWT");
*proto_state = oidc_proto_state_new();
oidc_proto_state_set_issuer(*proto_state, jwt->payload.iss);
oidc_proto_state_set_original_url(*proto_state, target_link_uri);
oidc_proto_state_set_original_method(*proto_state, OIDC_METHOD_GET);
oidc_proto_state_set_response_mode(*proto_state, provider->response_mode);
oidc_proto_state_set_response_type(*proto_state, provider->response_type);
oidc_proto_state_set_timestamp_now(*proto_state);
oidc_jwt_destroy(jwt);
return TRUE;
}
static void oidc_clean_expired_state_cookies(request_rec *r, oidc_cfg *c,
const char *currentCookieName) {
char *cookie, *tokenizerCtx;
char *cookies = apr_pstrdup(r->pool, oidc_util_hdr_in_cookie_get(r));
if (cookies != NULL) {
cookie = apr_strtok(cookies, OIDC_STR_SEMI_COLON, &tokenizerCtx);
while (cookie != NULL) {
while (*cookie == OIDC_CHAR_SPACE)
cookie++;
if (strstr(cookie, OIDC_STATE_COOKIE_PREFIX) == cookie) {
char *cookieName = cookie;
while (cookie != NULL && *cookie != OIDC_CHAR_EQUAL)
cookie++;
if (*cookie == OIDC_CHAR_EQUAL) {
*cookie = '\0';
cookie++;
if ((currentCookieName == NULL)
|| (apr_strnatcmp(cookieName, currentCookieName)
!= 0)) {
oidc_proto_state_t *proto_state =
oidc_proto_state_from_cookie(r, c, cookie);
if (proto_state != NULL) {
json_int_t ts = oidc_proto_state_get_timestamp(
proto_state);
if (apr_time_now() > ts + apr_time_from_sec(c->state_timeout)) {
oidc_error(r, "state (%s) has expired",
cookieName);
oidc_util_set_cookie(r, cookieName, "", 0,
NULL);
}
oidc_proto_state_destroy(proto_state);
}
}
}
}
cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &tokenizerCtx);
}
}
}
/*
* restore the state that was maintained between authorization request and response in an encrypted cookie
*/
static apr_byte_t oidc_restore_proto_state(request_rec *r, oidc_cfg *c,
const char *state, oidc_proto_state_t **proto_state) {
oidc_debug(r, "enter");
const char *cookieName = oidc_get_state_cookie_name(r, state);
/* clean expired state cookies to avoid pollution */
oidc_clean_expired_state_cookies(r, c, cookieName);
/* get the state cookie value first */
char *cookieValue = oidc_util_get_cookie(r, cookieName);
if (cookieValue == NULL) {
oidc_error(r, "no \"%s\" state cookie found", cookieName);
return oidc_unsolicited_proto_state(r, c, state, proto_state);
}
/* clear state cookie because we don't need it anymore */
oidc_util_set_cookie(r, cookieName, "", 0, NULL);
*proto_state = oidc_proto_state_from_cookie(r, c, cookieValue);
if (*proto_state == NULL)
return FALSE;
const char *nonce = oidc_proto_state_get_nonce(*proto_state);
/* calculate the hash of the browser fingerprint concatenated with the nonce */
char *calc = oidc_get_browser_state_hash(r, nonce);
/* compare the calculated hash with the value provided in the authorization response */
if (apr_strnatcmp(calc, state) != 0) {
oidc_error(r,
"calculated state from cookie does not match state parameter passed back in URL: \"%s\" != \"%s\"",
state, calc);
oidc_proto_state_destroy(*proto_state);
return FALSE;
}
apr_time_t ts = oidc_proto_state_get_timestamp(*proto_state);
/* check that the timestamp is not beyond the valid interval */
if (apr_time_now() > ts + apr_time_from_sec(c->state_timeout)) {
oidc_error(r, "state has expired");
oidc_util_html_send_error(r, c->error_template,
"Invalid Authentication Response",
apr_psprintf(r->pool,
"This is due to a timeout; please restart your authentication session by re-entering the URL/bookmark you originally wanted to access: %s",
oidc_proto_state_get_original_url(*proto_state)),
DONE);
oidc_proto_state_destroy(*proto_state);
return FALSE;
}
/* add the state */
oidc_proto_state_set_state(*proto_state, state);
/* log the restored state object */
oidc_debug(r, "restored state: %s",
oidc_proto_state_to_string(r, *proto_state));
/* we've made it */
return TRUE;
}
/*
* set the state that is maintained between an authorization request and an authorization response
* in a cookie in the browser that is cryptographically bound to that state
*/
static apr_byte_t oidc_authorization_request_set_cookie(request_rec *r,
oidc_cfg *c, const char *state, oidc_proto_state_t *proto_state) {
/*
* create a cookie consisting of 8 elements:
* random value, original URL, original method, issuer, response_type, response_mod, prompt and timestamp
* encoded as JSON, encrypting the resulting JSON value
*/
char *cookieValue = oidc_proto_state_to_cookie(r, c, proto_state);
if (cookieValue == NULL)
return FALSE;
/* clean expired state cookies to avoid pollution */
oidc_clean_expired_state_cookies(r, c, NULL);
/* assemble the cookie name for the state cookie */
const char *cookieName = oidc_get_state_cookie_name(r, state);
/* set it as a cookie */
oidc_util_set_cookie(r, cookieName, cookieValue, -1,
c->cookie_same_site ? OIDC_COOKIE_EXT_SAME_SITE_LAX : NULL);
//free(s_value);
return TRUE;
}
/*
* get the mod_auth_openidc related context from the (userdata in the) request
* (used for passing state between various Apache request processing stages and hook callbacks)
*/
static apr_table_t *oidc_request_state(request_rec *rr) {
/* our state is always stored in the main request */
request_rec *r = (rr->main != NULL) ? rr->main : rr;
/* our state is a table, get it */
apr_table_t *state = NULL;
apr_pool_userdata_get((void **) &state, OIDC_USERDATA_KEY, r->pool);
/* if it does not exist, we'll create a new table */
if (state == NULL) {
state = apr_table_make(r->pool, 5);
apr_pool_userdata_set(state, OIDC_USERDATA_KEY, NULL, r->pool);
}
/* return the resulting table, always non-null now */
return state;
}
/*
* set a name/value pair in the mod_auth_openidc-specific request context
* (used for passing state between various Apache request processing stages and hook callbacks)
*/
void oidc_request_state_set(request_rec *r, const char *key, const char *value) {
/* get a handle to the global state, which is a table */
apr_table_t *state = oidc_request_state(r);
/* put the name/value pair in that table */
apr_table_set(state, key, value);
}
/*
* get a name/value pair from the mod_auth_openidc-specific request context
* (used for passing state between various Apache request processing stages and hook callbacks)
*/
const char*oidc_request_state_get(request_rec *r, const char *key) {
/* get a handle to the global state, which is a table */
apr_table_t *state = oidc_request_state(r);
/* return the value from the table */
return apr_table_get(state, key);
}
/*
* set the claims from a JSON object (c.q. id_token or user_info response) stored
* in the session in to HTTP headers passed on to the application
*/
static apr_byte_t oidc_set_app_claims(request_rec *r,
const oidc_cfg * const cfg, oidc_session_t *session,
const char *s_claims) {
json_t *j_claims = NULL;
/* decode the string-encoded attributes in to a JSON structure */
if (s_claims != NULL) {
if (oidc_util_decode_json_object(r, s_claims, &j_claims) == FALSE)
return FALSE;
}
/* set the resolved claims a HTTP headers for the application */
if (j_claims != NULL) {
oidc_util_set_app_infos(r, j_claims, oidc_cfg_claim_prefix(r),
cfg->claim_delimiter, oidc_cfg_dir_pass_info_in_headers(r),
oidc_cfg_dir_pass_info_in_envvars(r));
/* release resources */
json_decref(j_claims);
}
return TRUE;
}
static int oidc_authenticate_user(request_rec *r, oidc_cfg *c,
oidc_provider_t *provider, const char *original_url,
const char *login_hint, const char *id_token_hint, const char *prompt,
const char *auth_request_params, const char *path_scope);
/*
* log message about max session duration
*/
static void oidc_log_session_expires(request_rec *r, const char *msg,
apr_time_t session_expires) {
char buf[APR_RFC822_DATE_LEN + 1];
apr_rfc822_date(buf, session_expires);
oidc_debug(r, "%s: %s (in %" APR_TIME_T_FMT " secs from now)", msg, buf,
apr_time_sec(session_expires - apr_time_now()));
}
/*
* find out which action we need to take when encountering an unauthenticated request
*/
static int oidc_handle_unauthenticated_user(request_rec *r, oidc_cfg *c) {
/* see if we've configured OIDCUnAuthAction for this path */
switch (oidc_dir_cfg_unauth_action(r)) {
case OIDC_UNAUTH_RETURN410:
return HTTP_GONE;
case OIDC_UNAUTH_RETURN401:
return HTTP_UNAUTHORIZED;
case OIDC_UNAUTH_PASS:
r->user = "";
/*
* we're not going to pass information about an authenticated user to the application,
* but we do need to scrub the headers that mod_auth_openidc would set for security reasons
*/
oidc_scrub_headers(r);
return OK;
case OIDC_UNAUTH_AUTHENTICATE:
/*
* exception handling: if this looks like a XMLHttpRequest call we
* won't redirect the user and thus avoid creating a state cookie
* for a non-browser (= Javascript) call that will never return from the OP
*/
if ((oidc_util_hdr_in_x_requested_with_get(r) != NULL)
&& (apr_strnatcasecmp(oidc_util_hdr_in_x_requested_with_get(r),
OIDC_HTTP_HDR_VAL_XML_HTTP_REQUEST) == 0))
return HTTP_UNAUTHORIZED;
}
/*
* else: no session (regardless of whether it is main or sub-request),
* and we need to authenticate the user
*/
return oidc_authenticate_user(r, c, NULL, oidc_get_current_url(r), NULL,
NULL, NULL, oidc_dir_cfg_path_auth_request_params(r),
oidc_dir_cfg_path_scope(r));
}
/*
* see if this is a non-browser request
*/
static apr_byte_t oidc_is_xml_http_request(request_rec *r) {
if ((oidc_util_hdr_in_x_requested_with_get(r) != NULL)
&& (apr_strnatcasecmp(oidc_util_hdr_in_x_requested_with_get(r),
OIDC_HTTP_HDR_VAL_XML_HTTP_REQUEST) == 0))
return TRUE;
return FALSE;
}
/*
* check if maximum session duration was exceeded
*/
static int oidc_check_max_session_duration(request_rec *r, oidc_cfg *cfg,
oidc_session_t *session) {
/* get the session expiry from the session data */
apr_time_t session_expires = oidc_session_get_session_expires(r, session);
/* check the expire timestamp against the current time */
if (apr_time_now() > session_expires) {
oidc_warn(r, "maximum session duration exceeded for user: %s",
session->remote_user);
oidc_session_kill(r, session);
return oidc_handle_unauthenticated_user(r, cfg);
}
/* log message about max session duration */
oidc_log_session_expires(r, "session max lifetime", session_expires);
return OK;
}
/*
* validate received session cookie against the domain it was issued for:
*
* this handles the case where the cache configured is a the same single memcache, Redis, or file
* backend for different (virtual) hosts, or a client-side cookie protected with the same secret
*
* it also handles the case that a cookie is unexpectedly shared across multiple hosts in
* name-based virtual hosting even though the OP(s) would be the same
*/
static apr_byte_t oidc_check_cookie_domain(request_rec *r, oidc_cfg *cfg,
oidc_session_t *session) {
const char *c_cookie_domain =
cfg->cookie_domain ?
cfg->cookie_domain : oidc_get_current_url_host(r);
const char *s_cookie_domain = oidc_session_get_cookie_domain(r, session);
if ((s_cookie_domain == NULL)
|| (apr_strnatcmp(c_cookie_domain, s_cookie_domain) != 0)) {
oidc_warn(r,
"aborting: detected attempt to play cookie against a different domain/host than issued for! (issued=%s, current=%s)",
s_cookie_domain, c_cookie_domain);
return FALSE;
}
return TRUE;
}
/*
* get a handle to the provider configuration via the "issuer" stored in the session
*/
apr_byte_t oidc_get_provider_from_session(request_rec *r, oidc_cfg *c,
oidc_session_t *session, oidc_provider_t **provider) {
oidc_debug(r, "enter");
/* get the issuer value from the session state */
const char *issuer = oidc_session_get_issuer(r, session);
if (issuer == NULL) {
oidc_error(r, "session corrupted: no issuer found in session");
return FALSE;
}
/* get the provider info associated with the issuer value */
oidc_provider_t *p = oidc_get_provider_for_issuer(r, c, issuer, FALSE);
if (p == NULL) {
oidc_error(r, "session corrupted: no provider found for issuer: %s",
issuer);
return FALSE;
}
*provider = p;
return TRUE;
}
/*
* store claims resolved from the userinfo endpoint in the session
*/
static void oidc_store_userinfo_claims(request_rec *r, oidc_cfg *c,
oidc_session_t *session, oidc_provider_t *provider, const char *claims,
const char *userinfo_jwt) {
oidc_debug(r, "enter");
/* see if we've resolved any claims */
if (claims != NULL) {
/*
* Successfully decoded a set claims from the response so we can store them
* (well actually the stringified representation in the response)
* in the session context safely now
*/
oidc_session_set_userinfo_claims(r, session, claims);
if (c->session_type != OIDC_SESSION_TYPE_CLIENT_COOKIE) {
/* this will also clear the entry if a JWT was not returned at this point */
oidc_session_set_userinfo_jwt(r, session, userinfo_jwt);
}
} else {
/*
* clear the existing claims because we could not refresh them
*/
oidc_session_set_userinfo_claims(r, session, NULL);
oidc_session_set_userinfo_jwt(r, session, NULL);
}
/* store the last refresh time if we've configured a userinfo refresh interval */
if (provider->userinfo_refresh_interval > 0)
oidc_session_reset_userinfo_last_refresh(r, session);
}
/*
* execute refresh token grant to refresh the existing access token
*/
static apr_byte_t oidc_refresh_access_token(request_rec *r, oidc_cfg *c,
oidc_session_t *session, oidc_provider_t *provider,
char **new_access_token) {
oidc_debug(r, "enter");
/* get the refresh token that was stored in the session */
const char *refresh_token = oidc_session_get_refresh_token(r, session);
if (refresh_token == NULL) {
oidc_warn(r,
"refresh token routine called but no refresh_token found in the session");
return FALSE;
}
/* elements returned in the refresh response */
char *s_id_token = NULL;
int expires_in = -1;
char *s_token_type = NULL;
char *s_access_token = NULL;
char *s_refresh_token = NULL;
/* refresh the tokens by calling the token endpoint */
if (oidc_proto_refresh_request(r, c, provider, refresh_token, &s_id_token,
&s_access_token, &s_token_type, &expires_in,
&s_refresh_token) == FALSE) {
oidc_error(r, "access_token could not be refreshed");
return FALSE;
}
/* store the new access_token in the session and discard the old one */
oidc_session_set_access_token(r, session, s_access_token);
oidc_session_set_access_token_expires(r, session, expires_in);
/* reset the access token refresh timestamp */
oidc_session_reset_access_token_last_refresh(r, session);
/* see if we need to return it as a parameter */
if (new_access_token != NULL)
*new_access_token = s_access_token;
/* if we have a new refresh token (rolling refresh), store it in the session and overwrite the old one */
if (s_refresh_token != NULL)
oidc_session_set_refresh_token(r, session, s_refresh_token);
return TRUE;
}
/*
* retrieve claims from the userinfo endpoint and return the stringified response
*/
static const char *oidc_retrieve_claims_from_userinfo_endpoint(request_rec *r,
oidc_cfg *c, oidc_provider_t *provider, const char *access_token,
oidc_session_t *session, char *id_token_sub, char **userinfo_jwt) {
oidc_debug(r, "enter");
/* see if a userinfo endpoint is set, otherwise there's nothing to do for us */
if (provider->userinfo_endpoint_url == NULL) {
oidc_debug(r,
"not retrieving userinfo claims because userinfo_endpoint is not set");
return NULL;
}
/* see if there's an access token, otherwise we can't call the userinfo endpoint at all */
if (access_token == NULL) {
oidc_debug(r,
"not retrieving userinfo claims because access_token is not provided");
return NULL;
}
if ((id_token_sub == NULL) && (session != NULL)) {
// when refreshing claims from the userinfo endpoint
json_t *id_token_claims = oidc_session_get_idtoken_claims_json(r,
session);
if (id_token_claims == NULL) {
oidc_error(r, "no id_token_claims found in session");
return NULL;
}
oidc_jose_get_string(r->pool, id_token_claims, OIDC_CLAIM_SUB, FALSE,
&id_token_sub, NULL);
}
// TODO: return code should indicate whether the token expired or some other error occurred
// TODO: long-term: session storage should be JSON (with explicit types and less conversion, using standard routines)
/* try to get claims from the userinfo endpoint using the provided access token */
char *result = NULL;
if (oidc_proto_resolve_userinfo(r, c, provider, id_token_sub, access_token,
&result, userinfo_jwt) == FALSE) {
/* see if we have an existing session and we are refreshing the user info claims */
if (session != NULL) {
/* first call to user info endpoint failed, but the access token may have just expired, so refresh it */
char *access_token = NULL;
if (oidc_refresh_access_token(r, c, session, provider,
&access_token) == TRUE) {
/* try again with the new access token */
if (oidc_proto_resolve_userinfo(r, c, provider, id_token_sub,
access_token, &result, userinfo_jwt) == FALSE) {
oidc_error(r,
"resolving user info claims with the refreshed access token failed, nothing will be stored in the session");
result = NULL;
}
} else {
oidc_warn(r,
"refreshing access token failed, claims will not be retrieved/refreshed from the userinfo endpoint");
result = NULL;
}
} else {
oidc_error(r,
"resolving user info claims with the existing/provided access token failed, nothing will be stored in the session");
result = NULL;
}
}
return result;
}
/*
* get (new) claims from the userinfo endpoint
*/
static apr_byte_t oidc_refresh_claims_from_userinfo_endpoint(request_rec *r,
oidc_cfg *cfg, oidc_session_t *session) {
oidc_provider_t *provider = NULL;
const char *claims = NULL;
const char *access_token = NULL;
char *userinfo_jwt = NULL;
/* get the current provider info */
if (oidc_get_provider_from_session(r, cfg, session, &provider) == FALSE)
return FALSE;
/* see if we can do anything here, i.e. we have a userinfo endpoint and a refresh interval is configured */
apr_time_t interval = apr_time_from_sec(
provider->userinfo_refresh_interval);
oidc_debug(r, "userinfo_endpoint=%s, interval=%d",
provider->userinfo_endpoint_url,
provider->userinfo_refresh_interval);
if ((provider->userinfo_endpoint_url != NULL) && (interval > 0)) {
/* get the last refresh timestamp from the session info */
apr_time_t last_refresh = oidc_session_get_userinfo_last_refresh(r,
session);
oidc_debug(r, "refresh needed in: %" APR_TIME_T_FMT " seconds",
apr_time_sec(last_refresh + interval - apr_time_now()));
/* see if we need to refresh again */
if (last_refresh + interval < apr_time_now()) {
/* get the current access token */
access_token = oidc_session_get_access_token(r, session);
/* retrieve the current claims */
claims = oidc_retrieve_claims_from_userinfo_endpoint(r, cfg,
provider, access_token, session, NULL, &userinfo_jwt);
/* store claims resolved from userinfo endpoint */
oidc_store_userinfo_claims(r, cfg, session, provider, claims,
userinfo_jwt);
/* indicated something changed */
return TRUE;
}
}
return FALSE;
}
/*
* copy the claims and id_token from the session to the request state and optionally return them
*/
static void oidc_copy_tokens_to_request_state(request_rec *r,
oidc_session_t *session, const char **s_id_token, const char **s_claims) {
const char *id_token = oidc_session_get_idtoken_claims(r, session);
const char *claims = oidc_session_get_userinfo_claims(r, session);
oidc_debug(r, "id_token=%s claims=%s", id_token, claims);
if (id_token != NULL) {
oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_IDTOKEN, id_token);
if (s_id_token != NULL)
*s_id_token = id_token;
}
if (claims != NULL) {
oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_CLAIMS, claims);
if (s_claims != NULL)
*s_claims = claims;
}
}
/*
* pass refresh_token, access_token and access_token_expires as headers/environment variables to the application
*/
static apr_byte_t oidc_session_pass_tokens_and_save(request_rec *r,
oidc_cfg *cfg, oidc_session_t *session, apr_byte_t needs_save) {
apr_byte_t pass_headers = oidc_cfg_dir_pass_info_in_headers(r);
apr_byte_t pass_envvars = oidc_cfg_dir_pass_info_in_envvars(r);
/* set the refresh_token in the app headers/variables, if enabled for this location/directory */
const char *refresh_token = oidc_session_get_refresh_token(r, session);
if ((oidc_cfg_dir_pass_refresh_token(r) != 0) && (refresh_token != NULL)) {
/* pass it to the app in a header or environment variable */
oidc_util_set_app_info(r, OIDC_APP_INFO_REFRESH_TOKEN, refresh_token,
OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars);
}
/* set the access_token in the app headers/variables */
const char *access_token = oidc_session_get_access_token(r, session);
if (access_token != NULL) {
/* pass it to the app in a header or environment variable */
oidc_util_set_app_info(r, OIDC_APP_INFO_ACCESS_TOKEN, access_token,
OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars);
}
/* set the expiry timestamp in the app headers/variables */
const char *access_token_expires = oidc_session_get_access_token_expires(r,
session);
if (access_token_expires != NULL) {
/* pass it to the app in a header or environment variable */
oidc_util_set_app_info(r, OIDC_APP_INFO_ACCESS_TOKEN_EXP,
access_token_expires,
OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars);
}
/*
* reset the session inactivity timer
* but only do this once per 10% of the inactivity timeout interval (with a max to 60 seconds)
* for performance reasons
*
* now there's a small chance that the session ends 10% (or a minute) earlier than configured/expected
* cq. when there's a request after a recent save (so no update) and then no activity happens until
* a request comes in just before the session should expire
* ("recent" and "just before" refer to 10%-with-a-max-of-60-seconds of the inactivity interval after
* the start/last-update and before the expiry of the session respectively)
*
* this is be deemed acceptable here because of performance gain
*/
apr_time_t interval = apr_time_from_sec(cfg->session_inactivity_timeout);
apr_time_t now = apr_time_now();
apr_time_t slack = interval / 10;
if (slack > apr_time_from_sec(60))
slack = apr_time_from_sec(60);
if (session->expiry - now < interval - slack) {
session->expiry = now + interval;
needs_save = TRUE;
}
/* log message about session expiry */
oidc_log_session_expires(r, "session inactivity timeout", session->expiry);
/* check if something was updated in the session and we need to save it again */
if (needs_save)
if (oidc_session_save(r, session, FALSE) == FALSE)
return FALSE;
return TRUE;
}
/*
* handle the case where we have identified an existing authentication session for a user
*/
static int oidc_handle_existing_session(request_rec *r, oidc_cfg *cfg,
oidc_session_t *session) {
oidc_debug(r, "enter");
/* set the user in the main request for further (incl. sub-request) processing */
r->user = (char *) session->remote_user;
oidc_debug(r, "set remote_user to \"%s\"", r->user);
/* get the header name in which the remote user name needs to be passed */
char *authn_header = oidc_cfg_dir_authn_header(r);
apr_byte_t pass_headers = oidc_cfg_dir_pass_info_in_headers(r);
apr_byte_t pass_envvars = oidc_cfg_dir_pass_info_in_envvars(r);
/* verify current cookie domain against issued cookie domain */
if (oidc_check_cookie_domain(r, cfg, session) == FALSE)
return HTTP_UNAUTHORIZED;
/* check if the maximum session duration was exceeded */
int rc = oidc_check_max_session_duration(r, cfg, session);
if (rc != OK)
return rc;
/* if needed, refresh claims from the user info endpoint */
apr_byte_t needs_save = oidc_refresh_claims_from_userinfo_endpoint(r, cfg,
session);
/*
* we're going to pass the information that we have to the application,
* but first we need to scrub the headers that we're going to use for security reasons
*/
oidc_scrub_headers(r);
/* set the user authentication HTTP header if set and required */
if ((r->user != NULL) && (authn_header != NULL))
oidc_util_hdr_in_set(r, authn_header, r->user);
const char *s_claims = NULL;
const char *s_id_token = NULL;
/* copy id_token and claims from session to request state and obtain their values */
oidc_copy_tokens_to_request_state(r, session, &s_id_token, &s_claims);
if ((cfg->pass_userinfo_as & OIDC_PASS_USERINFO_AS_CLAIMS)) {
/* set the userinfo claims in the app headers */
if (oidc_set_app_claims(r, cfg, session, s_claims) == FALSE)
return HTTP_INTERNAL_SERVER_ERROR;
}
if ((cfg->pass_userinfo_as & OIDC_PASS_USERINFO_AS_JSON_OBJECT)) {
/* pass the userinfo JSON object to the app in a header or environment variable */
oidc_util_set_app_info(r, OIDC_APP_INFO_USERINFO_JSON, s_claims,
OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars);
}
if ((cfg->pass_userinfo_as & OIDC_PASS_USERINFO_AS_JWT)) {
if (cfg->session_type != OIDC_SESSION_TYPE_CLIENT_COOKIE) {
/* get the compact serialized JWT from the session */
const char *s_userinfo_jwt = oidc_session_get_userinfo_jwt(r,
session);
if (s_userinfo_jwt != NULL) {
/* pass the compact serialized JWT to the app in a header or environment variable */
oidc_util_set_app_info(r, OIDC_APP_INFO_USERINFO_JWT,
s_userinfo_jwt,
OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars);
} else {
oidc_debug(r,
"configured to pass userinfo in a JWT, but no such JWT was found in the session (probably no such JWT was returned from the userinfo endpoint)");
}
} else {
oidc_error(r,
"session type \"client-cookie\" does not allow storing/passing a userinfo JWT; use \"" OIDCSessionType " server-cache\" for that");
}
}
if ((cfg->pass_idtoken_as & OIDC_PASS_IDTOKEN_AS_CLAIMS)) {
/* set the id_token in the app headers */
if (oidc_set_app_claims(r, cfg, session, s_id_token) == FALSE)
return HTTP_INTERNAL_SERVER_ERROR;
}
if ((cfg->pass_idtoken_as & OIDC_PASS_IDTOKEN_AS_PAYLOAD)) {
/* pass the id_token JSON object to the app in a header or environment variable */
oidc_util_set_app_info(r, OIDC_APP_INFO_ID_TOKEN_PAYLOAD, s_id_token,
OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars);
}
if ((cfg->pass_idtoken_as & OIDC_PASS_IDTOKEN_AS_SERIALIZED)) {
if (cfg->session_type != OIDC_SESSION_TYPE_CLIENT_COOKIE) {
/* get the compact serialized JWT from the session */
const char *s_id_token = oidc_session_get_idtoken(r, session);
/* pass the compact serialized JWT to the app in a header or environment variable */
oidc_util_set_app_info(r, OIDC_APP_INFO_ID_TOKEN, s_id_token,
OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars);
} else {
oidc_error(r,
"session type \"client-cookie\" does not allow storing/passing the id_token; use \"" OIDCSessionType " server-cache\" for that");
}
}
/* pass the at, rt and at expiry to the application, possibly update the session expiry and save the session */
if (oidc_session_pass_tokens_and_save(r, cfg, session, needs_save) == FALSE)
return HTTP_INTERNAL_SERVER_ERROR;
/* return "user authenticated" status */
return OK;
}
/*
* helper function for basic/implicit client flows upon receiving an authorization response:
* check that it matches the state stored in the browser and return the variables associated
* with the state, such as original_url and OP oidc_provider_t pointer.
*/
static apr_byte_t oidc_authorization_response_match_state(request_rec *r,
oidc_cfg *c, const char *state, struct oidc_provider_t **provider,
oidc_proto_state_t **proto_state) {
oidc_debug(r, "enter (state=%s)", state);
if ((state == NULL) || (apr_strnatcmp(state, "") == 0)) {
oidc_error(r, "state parameter is not set");
return FALSE;
}
/* check the state parameter against what we stored in a cookie */
if (oidc_restore_proto_state(r, c, state, proto_state) == FALSE) {
oidc_error(r, "unable to restore state");
return FALSE;
}
*provider = oidc_get_provider_for_issuer(r, c,
oidc_proto_state_get_issuer(*proto_state), FALSE);
return (*provider != NULL);
}
/*
* redirect the browser to the session logout endpoint
*/
static int oidc_session_redirect_parent_window_to_logout(request_rec *r,
oidc_cfg *c) {
oidc_debug(r, "enter");
char *java_script = apr_psprintf(r->pool,
" \n", oidc_get_redirect_uri(r, c));
return oidc_util_html_send(r, "Redirecting...", java_script, NULL, NULL,
DONE);
}
/*
* handle an error returned by the OP
*/
static int oidc_authorization_response_error(request_rec *r, oidc_cfg *c,
oidc_proto_state_t *proto_state, const char *error,
const char *error_description) {
const char *prompt = oidc_proto_state_get_prompt(proto_state);
if (prompt != NULL)
prompt = apr_pstrdup(r->pool, prompt);
oidc_proto_state_destroy(proto_state);
if ((prompt != NULL)
&& (apr_strnatcmp(prompt, OIDC_PROTO_PROMPT_NONE) == 0)) {
return oidc_session_redirect_parent_window_to_logout(r, c);
}
return oidc_util_html_send_error(r, c->error_template,
apr_psprintf(r->pool, "OpenID Connect Provider error: %s", error),
error_description, DONE);
}
/*
* get the r->user for this request based on the configuration for OIDC/OAuth
*/
apr_byte_t oidc_get_remote_user(request_rec *r, const char *claim_name,
const char *reg_exp, const char *replace, json_t *json,
char **request_user) {
/* get the claim value from the JSON object */
json_t *username = json_object_get(json, claim_name);
if ((username == NULL) || (!json_is_string(username))) {
oidc_warn(r, "JSON object did not contain a \"%s\" string", claim_name);
return FALSE;
}
*request_user = apr_pstrdup(r->pool, json_string_value(username));
if (reg_exp != NULL) {
char *error_str = NULL;
if (replace == NULL) {
if (oidc_util_regexp_first_match(r->pool, *request_user, reg_exp,
request_user, &error_str) == FALSE) {
oidc_error(r, "oidc_util_regexp_first_match failed: %s",
error_str);
*request_user = NULL;
return FALSE;
}
} else if (oidc_util_regexp_substitute(r->pool, *request_user, reg_exp,
replace, request_user, &error_str) == FALSE) {
oidc_error(r, "oidc_util_regexp_substitute failed: %s", error_str);
*request_user = NULL;
return FALSE;
}
}
return TRUE;
}
/*
* set the unique user identifier that will be propagated in the Apache r->user and REMOTE_USER variables
*/
static apr_byte_t oidc_set_request_user(request_rec *r, oidc_cfg *c,
oidc_provider_t *provider, oidc_jwt_t *jwt, const char *s_claims) {
char *issuer = provider->issuer;
char *claim_name = apr_pstrdup(r->pool, c->remote_user_claim.claim_name);
int n = strlen(claim_name);
apr_byte_t post_fix_with_issuer = (claim_name[n - 1] == OIDC_CHAR_AT);
if (post_fix_with_issuer == TRUE) {
claim_name[n - 1] = '\0';
issuer =
(strstr(issuer, "https://") == NULL) ?
apr_pstrdup(r->pool, issuer) :
apr_pstrdup(r->pool, issuer + strlen("https://"));
}
/* extract the username claim (default: "sub") from the id_token payload or user claims */
apr_byte_t rc = FALSE;
char *remote_user = NULL;
json_t *claims = NULL;
oidc_util_decode_json_object(r, s_claims, &claims);
if (claims == NULL) {
rc = oidc_get_remote_user(r, claim_name, c->remote_user_claim.reg_exp,
c->remote_user_claim.replace, jwt->payload.value.json,
&remote_user);
} else {
oidc_util_json_merge(r, jwt->payload.value.json, claims);
rc = oidc_get_remote_user(r, claim_name, c->remote_user_claim.reg_exp,
c->remote_user_claim.replace, claims, &remote_user);
json_decref(claims);
}
if ((rc == FALSE) || (remote_user == NULL)) {
oidc_error(r,
"" OIDCRemoteUserClaim "is set to \"%s\", but could not set the remote user based on the requested claim \"%s\" and the available claims for the user",
c->remote_user_claim.claim_name, claim_name);
return FALSE;
}
if (post_fix_with_issuer == TRUE)
remote_user = apr_psprintf(r->pool, "%s%s%s", remote_user, OIDC_STR_AT,
issuer);
r->user = remote_user;
oidc_debug(r, "set remote_user to \"%s\" based on claim: \"%s\"%s", r->user,
c->remote_user_claim.claim_name,
c->remote_user_claim.reg_exp ?
apr_psprintf(r->pool,
" and expression: \"%s\" and replace string: \"%s\"",
c->remote_user_claim.reg_exp,
c->remote_user_claim.replace) :
"");
return TRUE;
}
/*
* store resolved information in the session
*/
static apr_byte_t oidc_save_in_session(request_rec *r, oidc_cfg *c,
oidc_session_t *session, oidc_provider_t *provider,
const char *remoteUser, const char *id_token, oidc_jwt_t *id_token_jwt,
const char *claims, const char *access_token, const int expires_in,
const char *refresh_token, const char *session_state, const char *state,
const char *original_url, const char *userinfo_jwt) {
/* store the user in the session */
session->remote_user = remoteUser;
/* set the session expiry to the inactivity timeout */
session->expiry =
apr_time_now() + apr_time_from_sec(c->session_inactivity_timeout);
/* store the claims payload in the id_token for later reference */
oidc_session_set_idtoken_claims(r, session,
id_token_jwt->payload.value.str);
if (c->session_type != OIDC_SESSION_TYPE_CLIENT_COOKIE) {
/* store the compact serialized representation of the id_token for later reference */
oidc_session_set_idtoken(r, session, id_token);
}
/* store the issuer in the session (at least needed for session mgmt and token refresh */
oidc_session_set_issuer(r, session, provider->issuer);
/* store the state and original URL in the session for handling browser-back more elegantly */
oidc_session_set_request_state(r, session, state);
oidc_session_set_original_url(r, session, original_url);
if ((session_state != NULL) && (provider->check_session_iframe != NULL)) {
/* store the session state and required parameters session management */
oidc_session_set_session_state(r, session, session_state);
oidc_session_set_check_session_iframe(r, session,
provider->check_session_iframe);
oidc_session_set_client_id(r, session, provider->client_id);
oidc_debug(r,
"session management enabled: stored session_state (%s), check_session_iframe (%s) and client_id (%s) in the session",
session_state, provider->check_session_iframe,
provider->client_id);
} else if (provider->check_session_iframe == NULL) {
oidc_debug(r,
"session management disabled: \"check_session_iframe\" is not set in provider configuration");
} else {
oidc_warn(r,
"session management disabled: no \"session_state\" value is provided in the authentication response even though \"check_session_iframe\" (%s) is set in the provider configuration",
provider->check_session_iframe);
}
if (provider->end_session_endpoint != NULL)
oidc_session_set_logout_endpoint(r, session,
provider->end_session_endpoint);
/* store claims resolved from userinfo endpoint */
oidc_store_userinfo_claims(r, c, session, provider, claims, userinfo_jwt);
/* see if we have an access_token */
if (access_token != NULL) {
/* store the access_token in the session context */
oidc_session_set_access_token(r, session, access_token);
/* store the associated expires_in value */
oidc_session_set_access_token_expires(r, session, expires_in);
/* reset the access token refresh timestamp */
oidc_session_reset_access_token_last_refresh(r, session);
}
/* see if we have a refresh_token */
if (refresh_token != NULL) {
/* store the refresh_token in the session context */
oidc_session_set_refresh_token(r, session, refresh_token);
}
/* store max session duration in the session as a hard cut-off expiry timestamp */
apr_time_t session_expires =
(provider->session_max_duration == 0) ?
apr_time_from_sec(id_token_jwt->payload.exp) :
(apr_time_now()
+ apr_time_from_sec(provider->session_max_duration));
oidc_session_set_session_expires(r, session, session_expires);
oidc_debug(r,
"provider->session_max_duration = %d, session_expires=%" APR_TIME_T_FMT,
provider->session_max_duration, session_expires);
/* log message about max session duration */
oidc_log_session_expires(r, "session max lifetime", session_expires);
/* store the domain for which this session is valid */
oidc_session_set_cookie_domain(r, session,
c->cookie_domain ? c->cookie_domain : oidc_get_current_url_host(r));
/* store the session */
return oidc_session_save(r, session, TRUE);
}
/*
* parse the expiry for the access token
*/
static int oidc_parse_expires_in(request_rec *r, const char *expires_in) {
if (expires_in != NULL) {
char *ptr = NULL;
long number = strtol(expires_in, &ptr, 10);
if (number <= 0) {
oidc_warn(r,
"could not convert \"expires_in\" value (%s) to a number",
expires_in);
return -1;
}
return number;
}
return -1;
}
/*
* handle the different flows (hybrid, implicit, Authorization Code)
*/
static apr_byte_t oidc_handle_flows(request_rec *r, oidc_cfg *c,
oidc_proto_state_t *proto_state, oidc_provider_t *provider,
apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) {
apr_byte_t rc = FALSE;
const char *requested_response_type = oidc_proto_state_get_response_type(
proto_state);
/* handle the requested response type/mode */
if (oidc_util_spaced_string_equals(r->pool, requested_response_type,
OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN)) {
rc = oidc_proto_authorization_response_code_idtoken_token(r, c,
proto_state, provider, params, response_mode, jwt);
} else if (oidc_util_spaced_string_equals(r->pool, requested_response_type,
OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN)) {
rc = oidc_proto_authorization_response_code_idtoken(r, c, proto_state,
provider, params, response_mode, jwt);
} else if (oidc_util_spaced_string_equals(r->pool, requested_response_type,
OIDC_PROTO_RESPONSE_TYPE_CODE_TOKEN)) {
rc = oidc_proto_handle_authorization_response_code_token(r, c,
proto_state, provider, params, response_mode, jwt);
} else if (oidc_util_spaced_string_equals(r->pool, requested_response_type,
OIDC_PROTO_RESPONSE_TYPE_CODE)) {
rc = oidc_proto_handle_authorization_response_code(r, c, proto_state,
provider, params, response_mode, jwt);
} else if (oidc_util_spaced_string_equals(r->pool, requested_response_type,
OIDC_PROTO_RESPONSE_TYPE_IDTOKEN_TOKEN)) {
rc = oidc_proto_handle_authorization_response_idtoken_token(r, c,
proto_state, provider, params, response_mode, jwt);
} else if (oidc_util_spaced_string_equals(r->pool, requested_response_type,
OIDC_PROTO_RESPONSE_TYPE_IDTOKEN)) {
rc = oidc_proto_handle_authorization_response_idtoken(r, c, proto_state,
provider, params, response_mode, jwt);
} else {
oidc_error(r, "unsupported response type: \"%s\"",
requested_response_type);
}
if ((rc == FALSE) && (*jwt != NULL)) {
oidc_jwt_destroy(*jwt);
*jwt = NULL;
}
return rc;
}
/* handle the browser back on an authorization response */
static apr_byte_t oidc_handle_browser_back(request_rec *r, const char *r_state,
oidc_session_t *session) {
/* see if we have an existing session and browser-back was used */
const char *s_state = NULL, *o_url = NULL;
if (session->remote_user != NULL) {
s_state = oidc_session_get_request_state(r, session);
o_url = oidc_session_get_original_url(r, session);
if ((r_state != NULL) && (s_state != NULL)
&& (apr_strnatcmp(r_state, s_state) == 0)) {
/* log the browser back event detection */
oidc_warn(r,
"browser back detected, redirecting to original URL: %s",
o_url);
/* go back to the URL that he originally tried to access */
oidc_util_hdr_out_location_set(r, o_url);
return TRUE;
}
}
return FALSE;
}
/*
* complete the handling of an authorization response by obtaining, parsing and verifying the
* id_token and storing the authenticated user state in the session
*/
static int oidc_handle_authorization_response(request_rec *r, oidc_cfg *c,
oidc_session_t *session, apr_table_t *params, const char *response_mode) {
oidc_debug(r, "enter, response_mode=%s", response_mode);
oidc_provider_t *provider = NULL;
oidc_proto_state_t *proto_state = NULL;
oidc_jwt_t *jwt = NULL;
/* see if this response came from a browser-back event */
if (oidc_handle_browser_back(r, apr_table_get(params, OIDC_PROTO_STATE),
session) == TRUE)
return HTTP_MOVED_TEMPORARILY;
/* match the returned state parameter against the state stored in the browser */
if (oidc_authorization_response_match_state(r, c,
apr_table_get(params, OIDC_PROTO_STATE), &provider,
&proto_state) == FALSE) {
if (c->default_sso_url != NULL) {
oidc_warn(r,
"invalid authorization response state; a default SSO URL is set, sending the user there: %s",
c->default_sso_url);
oidc_util_hdr_out_location_set(r, c->default_sso_url);
return HTTP_MOVED_TEMPORARILY;
}
oidc_error(r,
"invalid authorization response state and no default SSO URL is set, sending an error...");
return HTTP_INTERNAL_SERVER_ERROR;
}
/* see if the response is an error response */
if (apr_table_get(params, OIDC_PROTO_ERROR) != NULL)
return oidc_authorization_response_error(r, c, proto_state,
apr_table_get(params, OIDC_PROTO_ERROR),
apr_table_get(params, OIDC_PROTO_ERROR_DESCRIPTION));
/* handle the code, implicit or hybrid flow */
if (oidc_handle_flows(r, c, proto_state, provider, params, response_mode,
&jwt) == FALSE)
return oidc_authorization_response_error(r, c, proto_state,
"Error in handling response type.", NULL);
if (jwt == NULL) {
oidc_error(r, "no id_token was provided");
return oidc_authorization_response_error(r, c, proto_state,
"No id_token was provided.", NULL);
}
int expires_in = oidc_parse_expires_in(r,
apr_table_get(params, OIDC_PROTO_EXPIRES_IN));
char *userinfo_jwt = NULL;
/*
* optionally resolve additional claims against the userinfo endpoint
* parsed claims are not actually used here but need to be parsed anyway for error checking purposes
*/
const char *claims = oidc_retrieve_claims_from_userinfo_endpoint(r, c,
provider, apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN), NULL,
jwt->payload.sub, &userinfo_jwt);
/* restore the original protected URL that the user was trying to access */
const char *original_url = oidc_proto_state_get_original_url(proto_state);
if (original_url != NULL)
original_url = apr_pstrdup(r->pool, original_url);
const char *original_method = oidc_proto_state_get_original_method(
proto_state);
if (original_method != NULL)
original_method = apr_pstrdup(r->pool, original_method);
const char *prompt = oidc_proto_state_get_prompt(proto_state);
/* set the user */
if (oidc_set_request_user(r, c, provider, jwt, claims) == TRUE) {
/* session management: if the user in the new response is not equal to the old one, error out */
if ((prompt != NULL)
&& (apr_strnatcmp(prompt, OIDC_PROTO_PROMPT_NONE) == 0)) {
// TOOD: actually need to compare sub? (need to store it in the session separately then
//const char *sub = NULL;
//oidc_session_get(r, session, "sub", &sub);
//if (apr_strnatcmp(sub, jwt->payload.sub) != 0) {
if (apr_strnatcmp(session->remote_user, r->user) != 0) {
oidc_warn(r,
"user set from new id_token is different from current one");
oidc_jwt_destroy(jwt);
return oidc_authorization_response_error(r, c, proto_state,
"User changed!", NULL);
}
}
/* store resolved information in the session */
if (oidc_save_in_session(r, c, session, provider, r->user,
apr_table_get(params, OIDC_PROTO_ID_TOKEN), jwt, claims,
apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN), expires_in,
apr_table_get(params, OIDC_PROTO_REFRESH_TOKEN),
apr_table_get(params, OIDC_PROTO_SESSION_STATE),
apr_table_get(params, OIDC_PROTO_STATE), original_url,
userinfo_jwt) == FALSE)
return HTTP_INTERNAL_SERVER_ERROR;
} else {
oidc_error(r, "remote user could not be set");
return oidc_authorization_response_error(r, c, proto_state,
"Remote user could not be set: contact the website administrator",
NULL);
}
/* cleanup */
oidc_proto_state_destroy(proto_state);
oidc_jwt_destroy(jwt);
/* check that we've actually authenticated a user; functions as error handling for oidc_get_remote_user */
if (r->user == NULL)
return HTTP_UNAUTHORIZED;
/* log the successful response */
oidc_debug(r,
"session created and stored, returning to original URL: %s, original method: %s",
original_url, original_method);
/* check whether form post data was preserved; if so restore it */
if (apr_strnatcmp(original_method, OIDC_METHOD_FORM_POST) == 0) {
return oidc_request_post_preserved_restore(r, original_url);
}
/* now we've authenticated the user so go back to the URL that he originally tried to access */
oidc_util_hdr_out_location_set(r, original_url);
/* do the actual redirect to the original URL */
return HTTP_MOVED_TEMPORARILY;
}
/*
* handle an OpenID Connect Authorization Response using the POST (+fragment->POST) response_mode
*/
static int oidc_handle_post_authorization_response(request_rec *r, oidc_cfg *c,
oidc_session_t *session) {
oidc_debug(r, "enter");
/* initialize local variables */
char *response_mode = NULL;
/* read the parameters that are POST-ed to us */
apr_table_t *params = apr_table_make(r->pool, 8);
if (oidc_util_read_post_params(r, params) == FALSE) {
oidc_error(r, "something went wrong when reading the POST parameters");
return HTTP_INTERNAL_SERVER_ERROR;
}
/* see if we've got any POST-ed data at all */
if ((apr_table_elts(params)->nelts < 1)
|| ((apr_table_elts(params)->nelts == 1)
&& apr_table_get(params, OIDC_PROTO_RESPONSE_MODE)
&& (apr_strnatcmp(
apr_table_get(params, OIDC_PROTO_RESPONSE_MODE),
OIDC_PROTO_RESPONSE_MODE_FRAGMENT) == 0))) {
return oidc_util_html_send_error(r, c->error_template,
"Invalid Request",
"You've hit an OpenID Connect Redirect URI with no parameters, this is an invalid request; you should not open this URL in your browser directly, or have the server administrator use a different " OIDCRedirectURI " setting.",
HTTP_INTERNAL_SERVER_ERROR);
}
/* get the parameters */
response_mode = (char *) apr_table_get(params, OIDC_PROTO_RESPONSE_MODE);
/* do the actual implicit work */
return oidc_handle_authorization_response(r, c, session, params,
response_mode ? response_mode : OIDC_PROTO_RESPONSE_MODE_FORM_POST);
}
/*
* handle an OpenID Connect Authorization Response using the redirect response_mode
*/
static int oidc_handle_redirect_authorization_response(request_rec *r,
oidc_cfg *c, oidc_session_t *session) {
oidc_debug(r, "enter");
/* read the parameters from the query string */
apr_table_t *params = apr_table_make(r->pool, 8);
oidc_util_read_form_encoded_params(r, params, r->args);
/* do the actual work */
return oidc_handle_authorization_response(r, c, session, params,
OIDC_PROTO_RESPONSE_MODE_QUERY);
}
/*
* present the user with an OP selection screen
*/
static int oidc_discovery(request_rec *r, oidc_cfg *cfg) {
oidc_debug(r, "enter");
/* obtain the URL we're currently accessing, to be stored in the state/session */
char *current_url = oidc_get_current_url(r);
const char *method = oidc_original_request_method(r, cfg, FALSE);
/* generate CSRF token */
char *csrf = NULL;
if (oidc_proto_generate_nonce(r, &csrf, 8) == FALSE)
return HTTP_INTERNAL_SERVER_ERROR;
char *path_scopes = oidc_dir_cfg_path_scope(r);
char *path_auth_request_params = oidc_dir_cfg_path_auth_request_params(r);
char *discover_url = oidc_cfg_dir_discover_url(r);
/* see if there's an external discovery page configured */
if (discover_url != NULL) {
/* yes, assemble the parameters for external discovery */
char *url = apr_psprintf(r->pool, "%s%s%s=%s&%s=%s&%s=%s&%s=%s",
discover_url,
strchr(discover_url, OIDC_CHAR_QUERY) != NULL ?
OIDC_STR_AMP :
OIDC_STR_QUERY,
OIDC_DISC_RT_PARAM, oidc_util_escape_string(r, current_url),
OIDC_DISC_RM_PARAM, method,
OIDC_DISC_CB_PARAM,
oidc_util_escape_string(r, oidc_get_redirect_uri(r, cfg)),
OIDC_CSRF_NAME, oidc_util_escape_string(r, csrf));
if (path_scopes != NULL)
url = apr_psprintf(r->pool, "%s&%s=%s", url, OIDC_DISC_SC_PARAM,
oidc_util_escape_string(r, path_scopes));
if (path_auth_request_params != NULL)
url = apr_psprintf(r->pool, "%s&%s=%s", url, OIDC_DISC_AR_PARAM,
oidc_util_escape_string(r, path_auth_request_params));
/* log what we're about to do */
oidc_debug(r, "redirecting to external discovery page: %s", url);
/* set CSRF cookie */
oidc_util_set_cookie(r, OIDC_CSRF_NAME, csrf, -1,
cfg->cookie_same_site ?
OIDC_COOKIE_EXT_SAME_SITE_STRICT :
NULL);
/* see if we need to preserve POST parameters through Javascript/HTML5 storage */
if (oidc_post_preserve_javascript(r, url, NULL, NULL) == TRUE)
return DONE;
/* do the actual redirect to an external discovery page */
oidc_util_hdr_out_location_set(r, url);
return HTTP_MOVED_TEMPORARILY;
}
/* get a list of all providers configured in the metadata directory */
apr_array_header_t *arr = NULL;
if (oidc_metadata_list(r, cfg, &arr) == FALSE)
return oidc_util_html_send_error(r, cfg->error_template,
"Configuration Error",
"No configured providers found, contact your administrator",
HTTP_UNAUTHORIZED);
/* assemble a where-are-you-from IDP discovery HTML page */
const char *s = "
Select your OpenID Connect Identity Provider
\n";
/* list all configured providers in there */
int i;
for (i = 0; i < arr->nelts; i++) {
const char *issuer = ((const char**) arr->elts)[i];
// TODO: html escape (especially & character)
char *href = apr_psprintf(r->pool,
"%s?%s=%s&%s=%s&%s=%s&%s=%s",
oidc_get_redirect_uri(r, cfg), OIDC_DISC_OP_PARAM,
oidc_util_escape_string(r, issuer),
OIDC_DISC_RT_PARAM, oidc_util_escape_string(r, current_url),
OIDC_DISC_RM_PARAM, method,
OIDC_CSRF_NAME, csrf);
if (path_scopes != NULL)
href = apr_psprintf(r->pool, "%s&%s=%s", href,
OIDC_DISC_SC_PARAM, oidc_util_escape_string(r, path_scopes));
if (path_auth_request_params != NULL)
href = apr_psprintf(r->pool, "%s&%s=%s", href,
OIDC_DISC_AR_PARAM,
oidc_util_escape_string(r, path_auth_request_params));
char *display =
(strstr(issuer, "https://") == NULL) ?
apr_pstrdup(r->pool, issuer) :
apr_pstrdup(r->pool, issuer + strlen("https://"));
/* strip port number */
//char *p = strstr(display, ":");
//if (p != NULL) *p = '\0';
/* point back to the redirect_uri, where the selection is handled, with an IDP selection and return_to URL */
s = apr_psprintf(r->pool, "%s
\n", s, href,
display);
}
/* add an option to enter an account or issuer name for dynamic OP discovery */
s = apr_psprintf(r->pool, "%s\n", s);
oidc_util_set_cookie(r, OIDC_CSRF_NAME, csrf, -1,
cfg->cookie_same_site ? OIDC_COOKIE_EXT_SAME_SITE_STRICT : NULL);
char *javascript = NULL, *javascript_method = NULL;
char *html_head =
"";
if (oidc_post_preserve_javascript(r, NULL, &javascript,
&javascript_method) == TRUE)
html_head = apr_psprintf(r->pool, "%s%s", html_head, javascript);
/* now send the HTML contents to the user agent */
return oidc_util_html_send(r, "OpenID Connect Provider Discovery",
html_head, javascript_method, s, DONE);
}
/*
* authenticate the user to the selected OP, if the OP is not selected yet perform discovery first
*/
static int oidc_authenticate_user(request_rec *r, oidc_cfg *c,
oidc_provider_t *provider, const char *original_url,
const char *login_hint, const char *id_token_hint, const char *prompt,
const char *auth_request_params, const char *path_scope) {
oidc_debug(r, "enter");
if (provider == NULL) {
// TODO: should we use an explicit redirect to the discovery endpoint (maybe a "discovery" param to the redirect_uri)?
if (c->metadata_dir != NULL)
return oidc_discovery(r, c);
/* we're not using multiple OP's configured in a metadata directory, pick the statically configured OP */
if (oidc_provider_static_config(r, c, &provider) == FALSE)
return HTTP_INTERNAL_SERVER_ERROR;
}
/* generate the random nonce value that correlates requests and responses */
char *nonce = NULL;
if (oidc_proto_generate_nonce(r, &nonce, OIDC_PROTO_NONCE_LENGTH) == FALSE)
return HTTP_INTERNAL_SERVER_ERROR;
char *pkce_state = NULL;
char *code_challenge = NULL;
if ((oidc_util_spaced_string_contains(r->pool, provider->response_type,
OIDC_PROTO_CODE) == TRUE) && (provider->pkce != NULL)) {
/* generate the code verifier value that correlates authorization requests and code exchange requests */
if (provider->pkce->state(r, &pkce_state) == FALSE)
return HTTP_INTERNAL_SERVER_ERROR;
/* generate the PKCE code challenge */
if (provider->pkce->challenge(r, pkce_state, &code_challenge) == FALSE)
return HTTP_INTERNAL_SERVER_ERROR;
}
/* create the state between request/response */
oidc_proto_state_t *proto_state = oidc_proto_state_new();
oidc_proto_state_set_original_url(proto_state, original_url);
oidc_proto_state_set_original_method(proto_state,
oidc_original_request_method(r, c, TRUE));
oidc_proto_state_set_issuer(proto_state, provider->issuer);
oidc_proto_state_set_response_type(proto_state, provider->response_type);
oidc_proto_state_set_nonce(proto_state, nonce);
oidc_proto_state_set_timestamp_now(proto_state);
if (provider->response_mode)
oidc_proto_state_set_response_mode(proto_state,
provider->response_mode);
if (prompt)
oidc_proto_state_set_prompt(proto_state, prompt);
if (pkce_state)
oidc_proto_state_set_pkce_state(proto_state, pkce_state);
/* get a hash value that fingerprints the browser concatenated with the random input */
char *state = oidc_get_browser_state_hash(r, nonce);
/* create state that restores the context when the authorization response comes in; cryptographically bind it to the browser */
if (oidc_authorization_request_set_cookie(r, c, state, proto_state) == FALSE)
return HTTP_INTERNAL_SERVER_ERROR;
/*
* printout errors if Cookie settings are not going to work
*/
apr_uri_t o_uri;
memset(&o_uri, 0, sizeof(apr_uri_t));
apr_uri_t r_uri;
memset(&r_uri, 0, sizeof(apr_uri_t));
apr_uri_parse(r->pool, original_url, &o_uri);
apr_uri_parse(r->pool, oidc_get_redirect_uri(r, c), &r_uri);
if ((apr_strnatcmp(o_uri.scheme, r_uri.scheme) != 0)
&& (apr_strnatcmp(r_uri.scheme, "https") == 0)) {
oidc_error(r,
"the URL scheme (%s) of the configured " OIDCRedirectURI " does not match the URL scheme of the URL being accessed (%s): the \"state\" and \"session\" cookies will not be shared between the two!",
r_uri.scheme, o_uri.scheme);
return HTTP_INTERNAL_SERVER_ERROR;
}
if (c->cookie_domain == NULL) {
if (apr_strnatcmp(o_uri.hostname, r_uri.hostname) != 0) {
char *p = strstr(o_uri.hostname, r_uri.hostname);
if ((p == NULL) || (apr_strnatcmp(r_uri.hostname, p) != 0)) {
oidc_error(r,
"the URL hostname (%s) of the configured " OIDCRedirectURI " does not match the URL hostname of the URL being accessed (%s): the \"state\" and \"session\" cookies will not be shared between the two!",
r_uri.hostname, o_uri.hostname);
return HTTP_INTERNAL_SERVER_ERROR;
}
}
} else {
if (!oidc_util_cookie_domain_valid(r_uri.hostname, c->cookie_domain)) {
oidc_error(r,
"the domain (%s) configured in " OIDCCookieDomain " does not match the URL hostname (%s) of the URL being accessed (%s): setting \"state\" and \"session\" cookies will not work!!",
c->cookie_domain, o_uri.hostname, original_url);
return HTTP_INTERNAL_SERVER_ERROR;
}
}
/* send off to the OpenID Connect Provider */
// TODO: maybe show intermediate/progress screen "redirecting to"
return oidc_proto_authorization_request(r, provider, login_hint,
oidc_get_redirect_uri_iss(r, c, provider), state, proto_state,
id_token_hint, code_challenge, auth_request_params, path_scope);
}
/*
* check if the target_link_uri matches to configuration settings to prevent an open redirect
*/
static int oidc_target_link_uri_matches_configuration(request_rec *r,
oidc_cfg *cfg, const char *target_link_uri) {
apr_uri_t o_uri;
apr_uri_parse(r->pool, target_link_uri, &o_uri);
if (o_uri.hostname == NULL) {
oidc_error(r,
"could not parse the \"target_link_uri\" (%s) in to a valid URL: aborting.",
target_link_uri);
return FALSE;
}
apr_uri_t r_uri;
apr_uri_parse(r->pool, oidc_get_redirect_uri(r, cfg), &r_uri);
if (cfg->cookie_domain == NULL) {
/* cookie_domain set: see if the target_link_uri matches the redirect_uri host (because the session cookie will be set host-wide) */
if (apr_strnatcmp(o_uri.hostname, r_uri.hostname) != 0) {
char *p = strstr(o_uri.hostname, r_uri.hostname);
if ((p == NULL) || (apr_strnatcmp(r_uri.hostname, p) != 0)) {
oidc_error(r,
"the URL hostname (%s) of the configured " OIDCRedirectURI " does not match the URL hostname of the \"target_link_uri\" (%s): aborting to prevent an open redirect.",
r_uri.hostname, o_uri.hostname);
return FALSE;
}
}
} else {
/* cookie_domain set: see if the target_link_uri is within the cookie_domain */
char *p = strstr(o_uri.hostname, cfg->cookie_domain);
if ((p == NULL) || (apr_strnatcmp(cfg->cookie_domain, p) != 0)) {
oidc_error(r,
"the domain (%s) configured in " OIDCCookieDomain " does not match the URL hostname (%s) of the \"target_link_uri\" (%s): aborting to prevent an open redirect.",
cfg->cookie_domain, o_uri.hostname, target_link_uri);
return FALSE;
}
}
/* see if the cookie_path setting matches the target_link_uri path */
char *cookie_path = oidc_cfg_dir_cookie_path(r);
if (cookie_path != NULL) {
char *p = (o_uri.path != NULL) ? strstr(o_uri.path, cookie_path) : NULL;
if ((p == NULL) || (p != o_uri.path)) {
oidc_error(r,
"the path (%s) configured in " OIDCCookiePath " does not match the URL path (%s) of the \"target_link_uri\" (%s): aborting to prevent an open redirect.",
cfg->cookie_domain, o_uri.path, target_link_uri);
return FALSE;
} else if (strlen(o_uri.path) > strlen(cookie_path)) {
int n = strlen(cookie_path);
if (cookie_path[n - 1] == OIDC_CHAR_FORWARD_SLASH)
n--;
if (o_uri.path[n] != OIDC_CHAR_FORWARD_SLASH) {
oidc_error(r,
"the path (%s) configured in " OIDCCookiePath " does not match the URL path (%s) of the \"target_link_uri\" (%s): aborting to prevent an open redirect.",
cfg->cookie_domain, o_uri.path, target_link_uri);
return FALSE;
}
}
}
return TRUE;
}
/*
* handle a response from an IDP discovery page and/or handle 3rd-party initiated SSO
*/
static int oidc_handle_discovery_response(request_rec *r, oidc_cfg *c) {
/* variables to hold the values returned in the response */
char *issuer = NULL, *target_link_uri = NULL, *login_hint = NULL,
*auth_request_params = NULL, *csrf_cookie, *csrf_query = NULL,
*user = NULL, *path_scopes;
oidc_provider_t *provider = NULL;
oidc_util_get_request_parameter(r, OIDC_DISC_OP_PARAM, &issuer);
oidc_util_get_request_parameter(r, OIDC_DISC_USER_PARAM, &user);
oidc_util_get_request_parameter(r, OIDC_DISC_RT_PARAM, &target_link_uri);
oidc_util_get_request_parameter(r, OIDC_DISC_LH_PARAM, &login_hint);
oidc_util_get_request_parameter(r, OIDC_DISC_SC_PARAM, &path_scopes);
oidc_util_get_request_parameter(r, OIDC_DISC_AR_PARAM,
&auth_request_params);
oidc_util_get_request_parameter(r, OIDC_CSRF_NAME, &csrf_query);
csrf_cookie = oidc_util_get_cookie(r, OIDC_CSRF_NAME);
/* do CSRF protection if not 3rd party initiated SSO */
if (csrf_cookie) {
/* clean CSRF cookie */
oidc_util_set_cookie(r, OIDC_CSRF_NAME, "", 0, NULL);
/* compare CSRF cookie value with query parameter value */
if ((csrf_query == NULL)
|| apr_strnatcmp(csrf_query, csrf_cookie) != 0) {
oidc_warn(r,
"CSRF protection failed, no Discovery and dynamic client registration will be allowed");
csrf_cookie = NULL;
}
}
// TODO: trim issuer/accountname/domain input and do more input validation
oidc_debug(r,
"issuer=\"%s\", target_link_uri=\"%s\", login_hint=\"%s\", user=\"%s\"",
issuer, target_link_uri, login_hint, user);
if (target_link_uri == NULL) {
if (c->default_sso_url == NULL) {
return oidc_util_html_send_error(r, c->error_template,
"Invalid Request",
"SSO to this module without specifying a \"target_link_uri\" parameter is not possible because " OIDCDefaultURL " is not set.",
HTTP_INTERNAL_SERVER_ERROR);
}
target_link_uri = c->default_sso_url;
}
/* do open redirect prevention */
if (oidc_target_link_uri_matches_configuration(r, c,
target_link_uri) == FALSE) {
return oidc_util_html_send_error(r, c->error_template,
"Invalid Request",
"\"target_link_uri\" parameter does not match configuration settings, aborting to prevent an open redirect.",
HTTP_UNAUTHORIZED);
}
/* see if this is a static setup */
if (c->metadata_dir == NULL) {
if ((oidc_provider_static_config(r, c, &provider) == TRUE)
&& (issuer != NULL)) {
if (apr_strnatcmp(provider->issuer, issuer) != 0) {
return oidc_util_html_send_error(r, c->error_template,
"Invalid Request",
apr_psprintf(r->pool,
"The \"iss\" value must match the configured providers' one (%s != %s).",
issuer, c->provider.issuer),
HTTP_INTERNAL_SERVER_ERROR);
}
}
return oidc_authenticate_user(r, c, NULL, target_link_uri, login_hint,
NULL, NULL, auth_request_params, path_scopes);
}
/* find out if the user entered an account name or selected an OP manually */
if (user != NULL) {
if (login_hint == NULL)
login_hint = apr_pstrdup(r->pool, user);
/* normalize the user identifier */
if (strstr(user, "https://") != user)
user = apr_psprintf(r->pool, "https://%s", user);
/* got an user identifier as input, perform OP discovery with that */
if (oidc_proto_url_based_discovery(r, c, user, &issuer) == FALSE) {
/* something did not work out, show a user facing error */
return oidc_util_html_send_error(r, c->error_template,
"Invalid Request",
"Could not resolve the provided user identifier to an OpenID Connect provider; check your syntax.",
HTTP_NOT_FOUND);
}
/* issuer is set now, so let's continue as planned */
} else if (strstr(issuer, OIDC_STR_AT) != NULL) {
if (login_hint == NULL) {
login_hint = apr_pstrdup(r->pool, issuer);
//char *p = strstr(issuer, OIDC_STR_AT);
//*p = '\0';
}
/* got an account name as input, perform OP discovery with that */
if (oidc_proto_account_based_discovery(r, c, issuer, &issuer) == FALSE) {
/* something did not work out, show a user facing error */
return oidc_util_html_send_error(r, c->error_template,
"Invalid Request",
"Could not resolve the provided account name to an OpenID Connect provider; check your syntax.",
HTTP_NOT_FOUND);
}
/* issuer is set now, so let's continue as planned */
}
/* strip trailing '/' */
int n = strlen(issuer);
if (issuer[n - 1] == OIDC_CHAR_FORWARD_SLASH)
issuer[n - 1] = '\0';
/* try and get metadata from the metadata directories for the selected OP */
if ((oidc_metadata_get(r, c, issuer, &provider, csrf_cookie != NULL) == TRUE)
&& (provider != NULL)) {
/* now we've got a selected OP, send the user there to authenticate */
return oidc_authenticate_user(r, c, provider, target_link_uri,
login_hint, NULL, NULL, auth_request_params, path_scopes);
}
/* something went wrong */
return oidc_util_html_send_error(r, c->error_template, "Invalid Request",
"Could not find valid provider metadata for the selected OpenID Connect provider; contact the administrator",
HTTP_NOT_FOUND);
}
static apr_uint32_t oidc_transparent_pixel[17] = { 0x474e5089, 0x0a1a0a0d,
0x0d000000, 0x52444849, 0x01000000, 0x01000000, 0x00000408, 0x0c1cb500,
0x00000002, 0x4144490b, 0x639c7854, 0x0000cffa, 0x02010702, 0x71311c9a,
0x00000000, 0x444e4549, 0x826042ae };
static apr_byte_t oidc_is_front_channel_logout(const char *logout_param_value) {
return ((logout_param_value != NULL)
&& ((apr_strnatcmp(logout_param_value,
OIDC_GET_STYLE_LOGOUT_PARAM_VALUE) == 0)
|| (apr_strnatcmp(logout_param_value,
OIDC_IMG_STYLE_LOGOUT_PARAM_VALUE) == 0)));
}
/*
* handle a local logout
*/
static int oidc_handle_logout_request(request_rec *r, oidc_cfg *c,
oidc_session_t *session, const char *url) {
oidc_debug(r, "enter (url=%s)", url);
/* if there's no remote_user then there's no (stored) session to kill */
if (session->remote_user != NULL) {
/* remove session state (cq. cache entry and cookie) */
oidc_session_kill(r, session);
}
/* see if this is the OP calling us */
if (oidc_is_front_channel_logout(url)) {
/* set recommended cache control headers */
oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_CACHE_CONTROL,
"no-cache, no-store");
oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_PRAGMA, "no-cache");
oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_P3P, "CAO PSA OUR");
oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_EXPIRES, "0");
oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_X_FRAME_OPTIONS, "DENY");
/* see if this is PF-PA style logout in which case we return a transparent pixel */
const char *accept = oidc_util_hdr_in_accept_get(r);
if ((apr_strnatcmp(url, OIDC_IMG_STYLE_LOGOUT_PARAM_VALUE) == 0)
|| ((accept) && strstr(accept, OIDC_CONTENT_TYPE_IMAGE_PNG))) {
return oidc_util_http_send(r,
(const char *) &oidc_transparent_pixel,
sizeof(oidc_transparent_pixel), OIDC_CONTENT_TYPE_IMAGE_PNG,
DONE);
}
/* standard HTTP based logout: should be called in an iframe from the OP */
return oidc_util_html_send(r, "Logged Out", NULL, NULL,
"
Logged Out
", DONE);
}
/* see if we don't need to go somewhere special after killing the session locally */
if (url == NULL)
return oidc_util_html_send(r, "Logged Out", NULL, NULL,
"
Logged Out
", DONE);
/* send the user to the specified where-to-go-after-logout URL */
oidc_util_hdr_out_location_set(r, url);
return HTTP_MOVED_TEMPORARILY;
}
/*
* perform (single) logout
*/
static int oidc_handle_logout(request_rec *r, oidc_cfg *c,
oidc_session_t *session) {
/* pickup the command or URL where the user wants to go after logout */
char *url = NULL;
oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_LOGOUT, &url);
oidc_debug(r, "enter (url=%s)", url);
if (oidc_is_front_channel_logout(url)) {
return oidc_handle_logout_request(r, c, session, url);
}
if ((url == NULL) || (apr_strnatcmp(url, "") == 0)) {
url = c->default_slo_url;
} else {
/* do input validation on the logout parameter value */
const char *error_description = NULL;
apr_uri_t uri;
if (apr_uri_parse(r->pool, url, &uri) != APR_SUCCESS) {
const char *error_description = apr_psprintf(r->pool,
"Logout URL malformed: %s", url);
oidc_error(r, "%s", error_description);
return oidc_util_html_send_error(r, c->error_template,
"Malformed URL", error_description,
HTTP_INTERNAL_SERVER_ERROR);
}
if ((strstr(r->hostname, uri.hostname) == NULL)
|| (strstr(uri.hostname, r->hostname) == NULL)) {
error_description =
apr_psprintf(r->pool,
"logout value \"%s\" does not match the hostname of the current request \"%s\"",
apr_uri_unparse(r->pool, &uri, 0), r->hostname);
oidc_error(r, "%s", error_description);
return oidc_util_html_send_error(r, c->error_template,
"Invalid Request", error_description,
HTTP_INTERNAL_SERVER_ERROR);
}
/* validate the URL to prevent HTTP header splitting */
if (((strstr(url, "\n") != NULL) || strstr(url, "\r") != NULL)) {
error_description =
apr_psprintf(r->pool,
"logout value \"%s\" contains illegal \"\n\" or \"\r\" character(s)",
url);
oidc_error(r, "%s", error_description);
return oidc_util_html_send_error(r, c->error_template,
"Invalid Request", error_description,
HTTP_INTERNAL_SERVER_ERROR);
}
}
const char *end_session_endpoint = oidc_session_get_logout_endpoint(r,
session);
if (end_session_endpoint != NULL) {
const char *id_token_hint = oidc_session_get_idtoken(r, session);
char *logout_request = apr_pstrdup(r->pool, end_session_endpoint);
if (id_token_hint != NULL) {
logout_request = apr_psprintf(r->pool, "%s%sid_token_hint=%s",
logout_request,
strchr(logout_request ? logout_request : "",
OIDC_CHAR_QUERY) != NULL ?
OIDC_STR_AMP :
OIDC_STR_QUERY,
oidc_util_escape_string(r, id_token_hint));
}
if (url != NULL) {
logout_request = apr_psprintf(r->pool,
"%s%spost_logout_redirect_uri=%s", logout_request,
strchr(logout_request ? logout_request : "",
OIDC_CHAR_QUERY) != NULL ?
OIDC_STR_AMP :
OIDC_STR_QUERY,
oidc_util_escape_string(r, url));
}
url = logout_request;
}
return oidc_handle_logout_request(r, c, session, url);
}
/*
* handle request for JWKs
*/
int oidc_handle_jwks(request_rec *r, oidc_cfg *c) {
/* pickup requested JWKs type */
// char *jwks_type = NULL;
// oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_JWKS, &jwks_type);
char *jwks = apr_pstrdup(r->pool, "{ \"keys\" : [");
apr_hash_index_t *hi = NULL;
apr_byte_t first = TRUE;
oidc_jose_error_t err;
if (c->public_keys != NULL) {
/* loop over the RSA public keys */
for (hi = apr_hash_first(r->pool, c->public_keys); hi; hi =
apr_hash_next(hi)) {
const char *s_kid = NULL;
oidc_jwk_t *jwk = NULL;
char *s_json = NULL;
apr_hash_this(hi, (const void**) &s_kid, NULL, (void**) &jwk);
if (oidc_jwk_to_json(r->pool, jwk, &s_json, &err) == TRUE) {
jwks = apr_psprintf(r->pool, "%s%s %s ", jwks, first ? "" : ",",
s_json);
first = FALSE;
} else {
oidc_error(r,
"could not convert RSA JWK to JSON using oidc_jwk_to_json: %s",
oidc_jose_e2s(r->pool, err));
}
}
}
// TODO: send stuff if first == FALSE?
jwks = apr_psprintf(r->pool, "%s ] }", jwks);
return oidc_util_http_send(r, jwks, strlen(jwks), OIDC_CONTENT_TYPE_JSON,
DONE);
}
static int oidc_handle_session_management_iframe_op(request_rec *r, oidc_cfg *c,
oidc_session_t *session, const char *check_session_iframe) {
oidc_debug(r, "enter");
oidc_util_hdr_out_location_set(r, check_session_iframe);
return HTTP_MOVED_TEMPORARILY;
}
static int oidc_handle_session_management_iframe_rp(request_rec *r, oidc_cfg *c,
oidc_session_t *session, const char *client_id,
const char *check_session_iframe) {
oidc_debug(r, "enter");
const char *java_script =
" \n";
/* determine the origin for the check_session_iframe endpoint */
char *origin = apr_pstrdup(r->pool, check_session_iframe);
apr_uri_t uri;
apr_uri_parse(r->pool, check_session_iframe, &uri);
char *p = strstr(origin, uri.path);
*p = '\0';
/* the element identifier for the OP iframe */
const char *op_iframe_id = "openidc-op";
/* restore the OP session_state from the session */
const char *session_state = oidc_session_get_session_state(r, session);
if (session_state == NULL) {
oidc_warn(r,
"no session_state found in the session; the OP does probably not support session management!?");
return DONE;
}
char *s_poll_interval = NULL;
oidc_util_get_request_parameter(r, "poll", &s_poll_interval);
if (s_poll_interval == NULL)
s_poll_interval = "3000";
const char *redirect_uri = oidc_get_redirect_uri(r, c);
java_script = apr_psprintf(r->pool, java_script, origin, client_id,
session_state, op_iframe_id, s_poll_interval, redirect_uri,
redirect_uri);
return oidc_util_html_send(r, NULL, java_script, "setTimer", NULL, DONE);
}
/*
* handle session management request
*/
static int oidc_handle_session_management(request_rec *r, oidc_cfg *c,
oidc_session_t *session) {
char *cmd = NULL;
const char *id_token_hint = NULL, *client_id = NULL, *check_session_iframe =
NULL;
oidc_provider_t *provider = NULL;
/* get the command passed to the session management handler */
oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_SESSION, &cmd);
if (cmd == NULL) {
oidc_error(r, "session management handler called with no command");
return HTTP_INTERNAL_SERVER_ERROR;
}
/* see if this is a local logout during session management */
if (apr_strnatcmp("logout", cmd) == 0) {
oidc_debug(r,
"[session=logout] calling oidc_handle_logout_request because of session mgmt local logout call.");
return oidc_handle_logout_request(r, c, session, c->default_slo_url);
}
/* see if this is a request for the OP iframe */
if (apr_strnatcmp("iframe_op", cmd) == 0) {
check_session_iframe = oidc_session_get_check_session_iframe(r,
session);
if (check_session_iframe != NULL) {
return oidc_handle_session_management_iframe_op(r, c, session,
check_session_iframe);
}
return HTTP_NOT_FOUND;
}
/* see if this is a request for the RP iframe */
if (apr_strnatcmp("iframe_rp", cmd) == 0) {
client_id = oidc_session_get_client_id(r, session);
check_session_iframe = oidc_session_get_check_session_iframe(r,
session);
if ((client_id != NULL) && (check_session_iframe != NULL)) {
return oidc_handle_session_management_iframe_rp(r, c, session,
client_id, check_session_iframe);
}
oidc_debug(r,
"iframe_rp command issued but no client (%s) and/or no check_session_iframe (%s) set",
client_id, check_session_iframe);
return HTTP_NOT_FOUND;
}
/* see if this is a request check the login state with the OP */
if (apr_strnatcmp("check", cmd) == 0) {
id_token_hint = oidc_session_get_idtoken(r, session);
oidc_get_provider_from_session(r, c, session, &provider);
if ((session->remote_user != NULL) && (provider != NULL)) {
/*
* TODO: this doesn't work with per-path provided auth_request_params and scopes
* as oidc_dir_cfg_path_auth_request_params and oidc_dir_cfg_path_scope will pick
* those for the redirect_uri itself; do we need to store those as part of the
* session now?
*/
return oidc_authenticate_user(r, c, provider,
apr_psprintf(r->pool, "%s?session=iframe_rp",
oidc_get_redirect_uri_iss(r, c, provider)), NULL,
id_token_hint, "none",
oidc_dir_cfg_path_auth_request_params(r),
oidc_dir_cfg_path_scope(r));
}
oidc_debug(r,
"[session=check] calling oidc_handle_logout_request because no session found.");
return oidc_session_redirect_parent_window_to_logout(r, c);
}
/* handle failure in fallthrough */
oidc_error(r, "unknown command: %s", cmd);
return HTTP_INTERNAL_SERVER_ERROR;
}
/*
* handle refresh token request
*/
static int oidc_handle_refresh_token_request(request_rec *r, oidc_cfg *c,
oidc_session_t *session) {
char *return_to = NULL;
char *r_access_token = NULL;
char *error_code = NULL;
/* get the command passed to the session management handler */
oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_REFRESH,
&return_to);
oidc_util_get_request_parameter(r, OIDC_PROTO_ACCESS_TOKEN,
&r_access_token);
/* check the input parameters */
if (return_to == NULL) {
oidc_error(r,
"refresh token request handler called with no URL to return to");
return HTTP_INTERNAL_SERVER_ERROR;
}
if (r_access_token == NULL) {
oidc_error(r,
"refresh token request handler called with no access_token parameter");
error_code = "no_access_token";
goto end;
}
const char *s_access_token = oidc_session_get_access_token(r, session);
if (s_access_token == NULL) {
oidc_error(r,
"no existing access_token found in the session, nothing to refresh");
error_code = "no_access_token_exists";
goto end;
}
/* compare the access_token parameter used for XSRF protection */
if (apr_strnatcmp(s_access_token, r_access_token) != 0) {
oidc_error(r,
"access_token passed in refresh request does not match the one stored in the session");
error_code = "no_access_token_match";
goto end;
}
/* get a handle to the provider configuration */
oidc_provider_t *provider = NULL;
if (oidc_get_provider_from_session(r, c, session, &provider) == FALSE) {
error_code = "session_corruption";
goto end;
}
/* execute the actual refresh grant */
if (oidc_refresh_access_token(r, c, session, provider, NULL) == FALSE) {
oidc_error(r, "access_token could not be refreshed");
error_code = "refresh_failed";
goto end;
}
/* pass the tokens to the application and save the session, possibly updating the expiry */
if (oidc_session_pass_tokens_and_save(r, c, session, TRUE) == FALSE) {
error_code = "session_corruption";
goto end;
}
end:
/* pass optional error message to the return URL */
if (error_code != NULL)
return_to = apr_psprintf(r->pool, "%s%serror_code=%s", return_to,
strchr(return_to ? return_to : "", OIDC_CHAR_QUERY) ?
OIDC_STR_AMP :
OIDC_STR_QUERY, oidc_util_escape_string(r, error_code));
/* add the redirect location header */
oidc_util_hdr_out_location_set(r, return_to);
return HTTP_MOVED_TEMPORARILY;
}
/*
* handle request object by reference request
*/
static int oidc_handle_request_uri(request_rec *r, oidc_cfg *c) {
char *request_ref = NULL;
oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_REQUEST_URI,
&request_ref);
if (request_ref == NULL) {
oidc_error(r, "no \"%s\" parameter found",
OIDC_REDIRECT_URI_REQUEST_REQUEST_URI);
return HTTP_BAD_REQUEST;
}
char *jwt = NULL;
oidc_cache_get_request_uri(r, request_ref, &jwt);
if (jwt == NULL) {
oidc_error(r, "no cached JWT found for %s reference: %s",
OIDC_REDIRECT_URI_REQUEST_REQUEST_URI, request_ref);
return HTTP_NOT_FOUND;
}
oidc_cache_set_request_uri(r, request_ref, NULL, 0);
return oidc_util_http_send(r, jwt, strlen(jwt), OIDC_CONTENT_TYPE_JWT, DONE);
}
/*
* handle a request to invalidate a cached access token introspection result
*/
static int oidc_handle_remove_at_cache(request_rec *r, oidc_cfg *c) {
char *access_token = NULL;
oidc_util_get_request_parameter(r,
OIDC_REDIRECT_URI_REQUEST_REMOVE_AT_CACHE, &access_token);
char *cache_entry = NULL;
oidc_cache_get_access_token(r, access_token, &cache_entry);
if (cache_entry == NULL) {
oidc_error(r, "no cached access token found for value: %s",
access_token);
return HTTP_NOT_FOUND;
}
oidc_cache_set_access_token(r, access_token, NULL, 0);
return DONE;
}
#define OIDC_INFO_PARAM_ACCESS_TOKEN_REFRESH_INTERVAL "access_token_refresh_interval"
/*
* handle request for session info
*/
static int oidc_handle_info_request(request_rec *r, oidc_cfg *c,
oidc_session_t *session) {
apr_byte_t needs_save = FALSE;
char *s_format = NULL, *s_interval = NULL;
oidc_util_get_request_parameter(r, OIDC_REDIRECT_URI_REQUEST_INFO,
&s_format);
oidc_util_get_request_parameter(r,
OIDC_INFO_PARAM_ACCESS_TOKEN_REFRESH_INTERVAL, &s_interval);
/* see if this is a request for a format that is supported */
if (apr_strnatcmp(OIDC_HOOK_INFO_FORMAT_JSON, s_format) != 0) {
oidc_warn(r, "request for unknown format: %s", s_format);
return HTTP_UNSUPPORTED_MEDIA_TYPE;
}
/* check that we actually have a user session and this is someone calling with a proper session cookie */
if (session->remote_user == NULL) {
oidc_warn(r, "no user session found");
return HTTP_UNAUTHORIZED;
}
/* set the user in the main request for further (incl. sub-request and authz) processing */
r->user = (char *) session->remote_user;
if (c->info_hook_data == NULL) {
oidc_warn(r, "no data configured to return in " OIDCInfoHook);
return HTTP_NOT_FOUND;
}
/* see if we can and need to refresh the access token */
if ((s_interval != NULL)
&& (oidc_session_get_refresh_token(r, session) != NULL)) {
apr_time_t t_interval;
if (sscanf(s_interval, "%" APR_TIME_T_FMT, &t_interval) == 1) {
t_interval = apr_time_from_sec(t_interval);
/* get the last refresh timestamp from the session info */
apr_time_t last_refresh =
oidc_session_get_access_token_last_refresh(r, session);
oidc_debug(r, "refresh needed in: %" APR_TIME_T_FMT " seconds",
apr_time_sec(last_refresh + t_interval - apr_time_now()));
/* see if we need to refresh again */
if (last_refresh + t_interval < apr_time_now()) {
/* get the current provider info */
oidc_provider_t *provider = NULL;
if (oidc_get_provider_from_session(r, c, session,
&provider) == FALSE)
return HTTP_INTERNAL_SERVER_ERROR;
/* execute the actual refresh grant */
if (oidc_refresh_access_token(r, c, session, provider,
NULL) == FALSE)
oidc_warn(r, "access_token could not be refreshed");
else
needs_save = TRUE;
}
}
}
/* create the JSON object */
json_t *json = json_object();
/* add a timestamp of creation in there for the caller */
if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_TIMESTAMP,
APR_HASH_KEY_STRING)) {
json_object_set_new(json, OIDC_HOOK_INFO_TIMESTAMP,
json_integer(apr_time_sec(apr_time_now())));
}
/*
* refresh the claims from the userinfo endpoint
* side-effect is that this may refresh the access token if not already done
* note that OIDCUserInfoRefreshInterval should be set to control the refresh policy
*/
needs_save |= oidc_refresh_claims_from_userinfo_endpoint(r, c, session);
/* include the access token in the session info */
if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_ACCES_TOKEN,
APR_HASH_KEY_STRING)) {
const char *access_token = oidc_session_get_access_token(r, session);
if (access_token != NULL)
json_object_set_new(json, OIDC_HOOK_INFO_ACCES_TOKEN,
json_string(access_token));
}
/* include the access token expiry timestamp in the session info */
if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_ACCES_TOKEN_EXP,
APR_HASH_KEY_STRING)) {
const char *access_token_expires =
oidc_session_get_access_token_expires(r, session);
if (access_token_expires != NULL)
json_object_set_new(json, OIDC_HOOK_INFO_ACCES_TOKEN_EXP,
json_string(access_token_expires));
}
/* include the id_token claims in the session info */
if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_ID_TOKEN,
APR_HASH_KEY_STRING)) {
json_t *id_token = oidc_session_get_idtoken_claims_json(r, session);
if (id_token)
json_object_set_new(json, OIDC_HOOK_INFO_ID_TOKEN, id_token);
}
if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_USER_INFO,
APR_HASH_KEY_STRING)) {
/* include the claims from the userinfo endpoint the session info */
json_t *claims = oidc_session_get_userinfo_claims_json(r, session);
if (claims)
json_object_set_new(json, OIDC_HOOK_INFO_USER_INFO, claims);
}
if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_SESSION,
APR_HASH_KEY_STRING)) {
json_t *j_session = json_object();
json_object_set(j_session, OIDC_HOOK_INFO_SESSION_STATE,
session->state);
json_object_set_new(j_session, OIDC_HOOK_INFO_SESSION_UUID,
json_string(session->uuid));
json_object_set_new(j_session, OIDC_HOOK_INFO_SESSION_EXP,
json_integer(apr_time_sec(session->expiry)));
json_object_set_new(j_session, OIDC_HOOK_INFO_SESSION_REMOTE_USER,
json_string(session->remote_user));
json_object_set_new(json, OIDC_HOOK_INFO_SESSION, j_session);
}
if (apr_hash_get(c->info_hook_data, OIDC_HOOK_INFO_REFRESH_TOKEN,
APR_HASH_KEY_STRING)) {
/* include the refresh token in the session info */
const char *refresh_token = oidc_session_get_refresh_token(r, session);
if (refresh_token != NULL)
json_object_set_new(json, OIDC_HOOK_INFO_REFRESH_TOKEN,
json_string(refresh_token));
}
/* JSON-encode the result */
char *r_value = oidc_util_encode_json_object(r, json, 0);
/* free the allocated resources */
json_decref(json);
/* pass the tokens to the application and save the session, possibly updating the expiry */
if (oidc_session_pass_tokens_and_save(r, c, session, needs_save) == FALSE) {
oidc_warn(r, "error saving session");
return HTTP_INTERNAL_SERVER_ERROR;
}
/* return the stringified JSON result */
return oidc_util_http_send(r, r_value, strlen(r_value),
OIDC_CONTENT_TYPE_JSON,
DONE);
}
/*
* handle all requests to the redirect_uri
*/
int oidc_handle_redirect_uri_request(request_rec *r, oidc_cfg *c,
oidc_session_t *session) {
if (oidc_proto_is_redirect_authorization_response(r, c)) {
/* this is an authorization response from the OP using the Basic Client profile or a Hybrid flow*/
return oidc_handle_redirect_authorization_response(r, c, session);
} else if (oidc_proto_is_post_authorization_response(r, c)) {
/* this is an authorization response using the fragment(+POST) response_mode with the Implicit Client profile */
return oidc_handle_post_authorization_response(r, c, session);
} else if (oidc_is_discovery_response(r, c)) {
/* this is response from the OP discovery page */
return oidc_handle_discovery_response(r, c);
} else if (oidc_util_request_has_parameter(r,
OIDC_REDIRECT_URI_REQUEST_LOGOUT)) {
/* handle logout */
return oidc_handle_logout(r, c, session);
} else if (oidc_util_request_has_parameter(r,
OIDC_REDIRECT_URI_REQUEST_JWKS)) {
/* handle JWKs request */
return oidc_handle_jwks(r, c);
} else if (oidc_util_request_has_parameter(r,
OIDC_REDIRECT_URI_REQUEST_SESSION)) {
/* handle session management request */
return oidc_handle_session_management(r, c, session);
} else if (oidc_util_request_has_parameter(r,
OIDC_REDIRECT_URI_REQUEST_REFRESH)) {
/* handle refresh token request */
return oidc_handle_refresh_token_request(r, c, session);
} else if (oidc_util_request_has_parameter(r,
OIDC_REDIRECT_URI_REQUEST_REQUEST_URI)) {
/* handle request object by reference request */
return oidc_handle_request_uri(r, c);
} else if (oidc_util_request_has_parameter(r,
OIDC_REDIRECT_URI_REQUEST_REMOVE_AT_CACHE)) {
/* handle request to invalidate access token cache */
return oidc_handle_remove_at_cache(r, c);
} else if (oidc_util_request_has_parameter(r,
OIDC_REDIRECT_URI_REQUEST_INFO)) {
if (session->remote_user == NULL)
return HTTP_UNAUTHORIZED;
/* set remote user, set headers/env-vars, update expiry, update userinfo + AT */
return oidc_handle_existing_session(r, c, session);
} else if ((r->args == NULL) || (apr_strnatcmp(r->args, "") == 0)) {
/* this is a "bare" request to the redirect URI, indicating implicit flow using the fragment response_mode */
return oidc_proto_javascript_implicit(r, c);
}
/* this is not an authorization response or logout request */
/* check for "error" response */
if (oidc_util_request_has_parameter(r, OIDC_PROTO_ERROR)) {
// char *error = NULL, *descr = NULL;
// oidc_util_get_request_parameter(r, "error", &error);
// oidc_util_get_request_parameter(r, "error_description", &descr);
//
// /* send user facing error to browser */
// return oidc_util_html_send_error(r, error, descr, DONE);
oidc_handle_redirect_authorization_response(r, c, session);
}
oidc_error(r,
"The OpenID Connect callback URL received an invalid request: %s; returning HTTP_INTERNAL_SERVER_ERROR",
r->args);
/* something went wrong */
return oidc_util_html_send_error(r, c->error_template, "Invalid Request",
apr_psprintf(r->pool,
"The OpenID Connect callback URL received an invalid request"),
HTTP_INTERNAL_SERVER_ERROR);
}
#define OIDC_AUTH_TYPE_OPENID_CONNECT "openid-connect"
#define OIDC_AUTH_TYPE_OPENID_OAUTH20 "oauth20"
#define OIDC_AUTH_TYPE_OPENID_BOTH "auth-openidc"
/*
* main routine: handle OpenID Connect authentication
*/
static int oidc_check_userid_openidc(request_rec *r, oidc_cfg *c) {
if (oidc_get_redirect_uri(r, c) == NULL) {
oidc_error(r,
"configuration error: the authentication type is set to \"" OIDC_AUTH_TYPE_OPENID_CONNECT "\" but " OIDCRedirectURI " has not been set");
return HTTP_INTERNAL_SERVER_ERROR;
}
/* check if this is a sub-request or an initial request */
if (ap_is_initial_req(r)) {
int rc = OK;
/* load the session from the request state; this will be a new "empty" session if no state exists */
oidc_session_t *session = NULL;
oidc_session_load(r, &session);
/* see if the initial request is to the redirect URI; this handles potential logout too */
if (oidc_util_request_matches_url(r, oidc_get_redirect_uri(r, c))) {
/* handle request to the redirect_uri */
rc = oidc_handle_redirect_uri_request(r, c, session);
/* free resources allocated for the session */
oidc_session_free(r, session);
return rc;
/* initial request to non-redirect URI, check if we have an existing session */
} else if (session->remote_user != NULL) {
/* this is initial request and we already have a session */
rc = oidc_handle_existing_session(r, c, session);
/* free resources allocated for the session */
oidc_session_free(r, session);
/* strip any cookies that we need to */
oidc_strip_cookies(r);
return rc;
}
/* free resources allocated for the session */
oidc_session_free(r, session);
/*
* else: initial request, we have no session and it is not an authorization or
* discovery response: just hit the default flow for unauthenticated users
*/
} else {
/* not an initial request, try to recycle what we've already established in the main request */
if (r->main != NULL)
r->user = r->main->user;
else if (r->prev != NULL)
r->user = r->prev->user;
if (r->user != NULL) {
/* this is a sub-request and we have a session (headers will have been scrubbed and set already) */
oidc_debug(r,
"recycling user '%s' from initial request for sub-request",
r->user);
/*
* apparently request state can get lost in sub-requests, so let's see
* if we need to restore id_token and/or claims from the session cache
*/
const char *s_id_token = oidc_request_state_get(r,
OIDC_REQUEST_STATE_KEY_IDTOKEN);
if (s_id_token == NULL) {
oidc_session_t *session = NULL;
oidc_session_load(r, &session);
oidc_copy_tokens_to_request_state(r, session, NULL, NULL);
/* free resources allocated for the session */
oidc_session_free(r, session);
}
/* strip any cookies that we need to */
oidc_strip_cookies(r);
return OK;
}
/*
* else: not initial request, but we could not find a session, so:
* just hit the default flow for unauthenticated users
*/
}
return oidc_handle_unauthenticated_user(r, c);
}
/*
* main routine: handle "mixed" OIDC/OAuth authentication
*/
static int oidc_check_mixed_userid_oauth(request_rec *r, oidc_cfg *c) {
/* get the bearer access token from the Authorization header */
const char *access_token = NULL;
if (oidc_oauth_get_bearer_token(r, &access_token) == TRUE)
return oidc_oauth_check_userid(r, c);
/* no bearer token found: then treat this as a regular OIDC browser request */
return oidc_check_userid_openidc(r, c);
}
/*
* generic Apache authentication hook for this module: dispatches to OpenID Connect or OAuth 2.0 specific routines
*/
int oidc_check_user_id(request_rec *r) {
oidc_cfg *c = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
/* log some stuff about the incoming HTTP request */
oidc_debug(r, "incoming request: \"%s?%s\", ap_is_initial_req(r)=%d",
r->parsed_uri.path, r->args, ap_is_initial_req(r));
/* see if any authentication has been defined at all */
if (ap_auth_type(r) == NULL)
return DECLINED;
/* see if we've configured OpenID Connect user authentication for this request */
if (apr_strnatcasecmp((const char *) ap_auth_type(r),
OIDC_AUTH_TYPE_OPENID_CONNECT) == 0)
return oidc_check_userid_openidc(r, c);
/* see if we've configured OAuth 2.0 access control for this request */
if (apr_strnatcasecmp((const char *) ap_auth_type(r),
OIDC_AUTH_TYPE_OPENID_OAUTH20) == 0)
return oidc_oauth_check_userid(r, c);
/* see if we've configured "mixed mode" for this request */
if (apr_strnatcasecmp((const char *) ap_auth_type(r),
OIDC_AUTH_TYPE_OPENID_BOTH) == 0)
return oidc_check_mixed_userid_oauth(r, c);
/* this is not for us but for some other handler */
return DECLINED;
}
/*
* get the claims and id_token from request state
*/
static void oidc_authz_get_claims_and_idtoken(request_rec *r, json_t **claims,
json_t **id_token) {
const char *s_claims = oidc_request_state_get(r,
OIDC_REQUEST_STATE_KEY_CLAIMS);
if (s_claims != NULL)
oidc_util_decode_json_object(r, s_claims, claims);
const char *s_id_token = oidc_request_state_get(r,
OIDC_REQUEST_STATE_KEY_IDTOKEN);
if (s_id_token != NULL)
oidc_util_decode_json_object(r, s_id_token, id_token);
}
#if MODULE_MAGIC_NUMBER_MAJOR >= 20100714
/*
* find out which action we need to take when encountering an unauthorized request
*/
static authz_status oidc_handle_unauthorized_user24(request_rec *r) {
oidc_debug(r, "enter");
oidc_cfg *c = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
if (apr_strnatcasecmp((const char *) ap_auth_type(r),
OIDC_AUTH_TYPE_OPENID_OAUTH20) == 0) {
oidc_oauth_return_www_authenticate(r, "insufficient_scope",
"Different scope(s) or other claims required");
return AUTHZ_DENIED;
}
/* see if we've configured OIDCUnAutzAction for this path */
switch (oidc_dir_cfg_unautz_action(r)) {
// TODO: document that AuthzSendForbiddenOnFailure is required to return 403 FORBIDDEN
case OIDC_UNAUTZ_RETURN403:
case OIDC_UNAUTZ_RETURN401:
return AUTHZ_DENIED;
break;
case OIDC_UNAUTZ_AUTHENTICATE:
/*
* exception handling: if this looks like a XMLHttpRequest call we
* won't redirect the user and thus avoid creating a state cookie
* for a non-browser (= Javascript) call that will never return from the OP
*/
if (oidc_is_xml_http_request(r) == TRUE)
return AUTHZ_DENIED;
break;
}
oidc_authenticate_user(r, c, NULL, oidc_get_current_url(r), NULL,
NULL, NULL, oidc_dir_cfg_path_auth_request_params(r),
oidc_dir_cfg_path_scope(r));
const char *location = oidc_util_hdr_out_location_get(r);
if (location != NULL) {
oidc_debug(r, "send HTML refresh with authorization redirect: %s",
location);
char *html_head = apr_psprintf(r->pool,
"",
location);
oidc_util_html_send(r, "Stepup Authentication", html_head, NULL, NULL,
HTTP_UNAUTHORIZED);
}
return AUTHZ_DENIED;
}
/*
* generic Apache >=2.4 authorization hook for this module
* handles both OpenID Connect or OAuth 2.0 in the same way, based on the claims stored in the session
*/
authz_status oidc_authz_checker(request_rec *r, const char *require_args,
const void *parsed_require_args,
oidc_authz_match_claim_fn_type match_claim_fn) {
oidc_debug(r, "enter");
/* check for anonymous access and PASS mode */
if (r->user != NULL && strlen(r->user) == 0) {
r->user = NULL;
if (oidc_dir_cfg_unauth_action(r) == OIDC_UNAUTH_PASS)
return AUTHZ_GRANTED;
}
/* get the set of claims from the request state (they've been set in the authentication part earlier */
json_t *claims = NULL, *id_token = NULL;
oidc_authz_get_claims_and_idtoken(r, &claims, &id_token);
/* merge id_token claims (e.g. "iss") in to claims json object */
if (claims)
oidc_util_json_merge(r, id_token, claims);
/* dispatch to the >=2.4 specific authz routine */
authz_status rc = oidc_authz_worker24(r, claims ? claims : id_token,
require_args, match_claim_fn);
/* cleanup */
if (claims)
json_decref(claims);
if (id_token)
json_decref(id_token);
if ((rc == AUTHZ_DENIED) && ap_auth_type(r))
rc = oidc_handle_unauthorized_user24(r);
return rc;
}
authz_status oidc_authz_checker_claim(request_rec *r, const char *require_args,
const void *parsed_require_args) {
return oidc_authz_checker(r, require_args, parsed_require_args,
oidc_authz_match_claim);
}
#ifdef USE_LIBJQ
authz_status oidc_authz_checker_claims_expr(request_rec *r, const char *require_args, const void *parsed_require_args) {
return oidc_authz_checker(r, require_args, parsed_require_args, oidc_authz_match_claims_expr);
}
#endif
#else
/*
* find out which action we need to take when encountering an unauthorized request
*/
static int oidc_handle_unauthorized_user22(request_rec *r) {
oidc_cfg *c = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
if (apr_strnatcasecmp((const char *) ap_auth_type(r), OIDC_AUTH_TYPE_OPENID_OAUTH20) == 0) {
oidc_oauth_return_www_authenticate(r, "insufficient_scope", "Different scope(s) or other claims required");
return HTTP_UNAUTHORIZED;
}
/* see if we've configured OIDCUnAutzAction for this path */
switch (oidc_dir_cfg_unautz_action(r)) {
case OIDC_UNAUTZ_RETURN403:
return HTTP_FORBIDDEN;
case OIDC_UNAUTZ_RETURN401:
return HTTP_UNAUTHORIZED;
case OIDC_UNAUTZ_AUTHENTICATE:
/*
* exception handling: if this looks like a XMLHttpRequest call we
* won't redirect the user and thus avoid creating a state cookie
* for a non-browser (= Javascript) call that will never return from the OP
*/
if (oidc_is_xml_http_request(r) == TRUE)
return HTTP_UNAUTHORIZED;
}
return oidc_authenticate_user(r, c, NULL, oidc_get_current_url(r), NULL,
NULL, NULL, oidc_dir_cfg_path_auth_request_params(r), oidc_dir_cfg_path_scope(r));
}
/*
* generic Apache <2.4 authorization hook for this module
* handles both OpenID Connect and OAuth 2.0 in the same way, based on the claims stored in the request context
*/
int oidc_auth_checker(request_rec *r) {
/* check for anonymous access and PASS mode */
if (r->user != NULL && strlen(r->user) == 0) {
r->user = NULL;
if (oidc_dir_cfg_unauth_action(r) == OIDC_UNAUTH_PASS)
return OK;
}
/* get the set of claims from the request state (they've been set in the authentication part earlier */
json_t *claims = NULL, *id_token = NULL;
oidc_authz_get_claims_and_idtoken(r, &claims, &id_token);
/* get the Require statements */
const apr_array_header_t * const reqs_arr = ap_requires(r);
/* see if we have any */
const require_line * const reqs =
reqs_arr ? (require_line *) reqs_arr->elts : NULL;
if (!reqs_arr) {
oidc_debug(r,
"no require statements found, so declining to perform authorization.");
return DECLINED;
}
/* merge id_token claims (e.g. "iss") in to claims json object */
if (claims)
oidc_util_json_merge(r, id_token, claims);
/* dispatch to the <2.4 specific authz routine */
int rc = oidc_authz_worker22(r, claims ? claims : id_token, reqs,
reqs_arr->nelts);
/* cleanup */
if (claims)
json_decref(claims);
if (id_token)
json_decref(id_token);
if ((rc == HTTP_UNAUTHORIZED) && ap_auth_type(r))
rc = oidc_handle_unauthorized_user22(r);
return rc;
}
#endif
/*
* handle content generating requests
*/
int oidc_content_handler(request_rec *r) {
oidc_cfg *c = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
int rc = DECLINED;
if (oidc_util_request_matches_url(r, oidc_get_redirect_uri(r, c))) {
if (oidc_util_request_has_parameter(r,
OIDC_REDIRECT_URI_REQUEST_INFO)) {
oidc_session_t *session = NULL;
oidc_session_load(r, &session);
/* handle request for session info */
rc = oidc_handle_info_request(r, c, session);
/* free resources allocated for the session */
oidc_session_free(r, session);
}
}
return rc;
}
extern const command_rec oidc_config_cmds[];
module AP_MODULE_DECLARE_DATA auth_openidc_module = {
STANDARD20_MODULE_STUFF,
oidc_create_dir_config,
oidc_merge_dir_config,
oidc_create_server_config,
oidc_merge_server_config,
oidc_config_cmds,
oidc_register_hooks
};
mod_auth_openidc-2.3.3/src/cache/file.c 0000644 0000765 0000024 00000034006 13200625410 017567 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* caching using a file storage backend
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*/
#include
#include
#include
#include
#include
#include
#include "../mod_auth_openidc.h"
extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
/*
* header structure that holds the metadata info for a cache file entry
*/
typedef struct {
/* length of the cached data */
apr_size_t len;
/* cache expiry timestamp */
apr_time_t expire;
} oidc_cache_file_info_t;
/*
* prefix that distinguishes mod_auth_openidc cache files from other files in the same directory (/tmp)
*/
#define OIDC_CACHE_FILE_PREFIX "mod-auth-openidc-"
/* post config routine */
int oidc_cache_file_post_config(server_rec *s) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(s->module_config,
&auth_openidc_module);
if (cfg->cache_file_dir == NULL) {
/* by default we'll use the OS specified /tmp dir for cache files */
apr_temp_dir_get((const char **) &cfg->cache_file_dir,
s->process->pool);
}
return OK;
}
/*
* return the cache file name for a specified key
*/
static const char *oidc_cache_file_name(request_rec *r, const char *section,
const char *key) {
return apr_psprintf(r->pool, "%s%s-%s", OIDC_CACHE_FILE_PREFIX, section,
oidc_util_escape_string(r, key));
}
/*
* return the fully qualified path name to a cache file for a specified key
*/
static const char *oidc_cache_file_path(request_rec *r, const char *section,
const char *key) {
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
return apr_psprintf(r->pool, "%s/%s", cfg->cache_file_dir,
oidc_cache_file_name(r, section, key));
}
/*
* read a specified number of bytes from a cache file in to a preallocated buffer
*/
static apr_status_t oidc_cache_file_read(request_rec *r, const char *path,
apr_file_t *fd, void *buf, const apr_size_t len) {
apr_status_t rc = APR_SUCCESS;
apr_size_t bytes_read = 0;
char s_err[128];
/* (blocking) read the requested number of bytes */
rc = apr_file_read_full(fd, buf, len, &bytes_read);
/* test for system errors */
if (rc != APR_SUCCESS) {
oidc_error(r, "could not read from: %s (%s)", path,
apr_strerror(rc, s_err, sizeof(s_err)));
}
/* ensure that we've got the requested number of bytes */
if (bytes_read != len) {
oidc_error(r,
"could not read enough bytes from: \"%s\", bytes_read (%" APR_SIZE_T_FMT ") != len (%" APR_SIZE_T_FMT ")",
path, bytes_read, len);
rc = APR_EGENERAL;
}
return rc;
}
/*
* write a specified number of bytes from a buffer to a cache file
*/
static apr_status_t oidc_cache_file_write(request_rec *r, const char *path,
apr_file_t *fd, void *buf, const apr_size_t len) {
apr_status_t rc = APR_SUCCESS;
apr_size_t bytes_written = 0;
char s_err[128];
/* (blocking) write the number of bytes in the buffer */
rc = apr_file_write_full(fd, buf, len, &bytes_written);
/* check for a system error */
if (rc != APR_SUCCESS) {
oidc_error(r, "could not write to: \"%s\" (%s)", path,
apr_strerror(rc, s_err, sizeof(s_err)));
return rc;
}
/* check that all bytes from the header were written */
if (bytes_written != len) {
oidc_error(r,
"could not write enough bytes to: \"%s\", bytes_written (%" APR_SIZE_T_FMT ") != len (%" APR_SIZE_T_FMT ")",
path, bytes_written, len);
return APR_EGENERAL;
}
return rc;
}
/*
* get a value for the specified key from the cache
*/
static apr_byte_t oidc_cache_file_get(request_rec *r, const char *section,
const char *key, const char **value) {
apr_file_t *fd = NULL;
apr_status_t rc = APR_SUCCESS;
char s_err[128];
/* get the fully qualified path to the cache file based on the key name */
const char *path = oidc_cache_file_path(r, section, key);
/* open the cache file if it exists, otherwise we just have a "regular" cache miss */
if (apr_file_open(&fd, path, APR_FOPEN_READ | APR_FOPEN_BUFFERED,
APR_OS_DEFAULT, r->pool) != APR_SUCCESS) {
oidc_debug(r, "cache miss for key \"%s\"", key);
return TRUE;
}
/* the file exists, now lock it */
apr_file_lock(fd, APR_FLOCK_EXCLUSIVE);
/* move the read pointer to the very start of the cache file */
apr_off_t begin = 0;
apr_file_seek(fd, APR_SET, &begin);
/* read a header with metadata */
oidc_cache_file_info_t info;
if ((rc = oidc_cache_file_read(r, path, fd, &info,
sizeof(oidc_cache_file_info_t))) != APR_SUCCESS)
goto error_close;
/* check if this cache entry has already expired */
if (apr_time_now() >= info.expire) {
/* yep, expired: unlock and close before deleting the cache file */
apr_file_unlock(fd);
apr_file_close(fd);
/* log this event */
oidc_debug(r, "cache entry \"%s\" expired, removing file \"%s\"", key,
path);
/* and kill it */
if ((rc = apr_file_remove(path, r->pool)) != APR_SUCCESS) {
oidc_error(r, "could not delete cache file \"%s\" (%s)", path,
apr_strerror(rc, s_err, sizeof(s_err)));
}
/* nothing strange happened really */
return TRUE;
}
/* allocate space for the actual value based on the data size info in the header (+1 for \0 termination) */
*value = apr_palloc(r->pool, info.len);
/* (blocking) read the requested data in to the buffer */
rc = oidc_cache_file_read(r, path, fd, (void *) *value, info.len);
/* barf on failure */
if (rc != APR_SUCCESS) {
oidc_error(r, "could not read cache value from \"%s\"", path);
goto error_close;
}
/* we're done, unlock and close the file */
apr_file_unlock(fd);
apr_file_close(fd);
return TRUE;
error_close:
apr_file_unlock(fd);
apr_file_close(fd);
oidc_error(r, "return error status %d (%s)", rc,
apr_strerror(rc, s_err, sizeof(s_err)));
return FALSE;
}
// TODO: make these configurable?
#define OIDC_CACHE_FILE_LAST_CLEANED "last-cleaned"
/*
* delete all expired entries from the cache directory
*/
static apr_status_t oidc_cache_file_clean(request_rec *r) {
apr_status_t rc = APR_SUCCESS;
apr_dir_t *dir = NULL;
apr_file_t *fd = NULL;
apr_status_t i;
apr_finfo_t fi;
oidc_cache_file_info_t info;
char s_err[128];
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
/* get the path to the metadata file that holds "last cleaned" metadata info */
const char *metadata_path = oidc_cache_file_path(r, "cache-file",
OIDC_CACHE_FILE_LAST_CLEANED);
/* open the metadata file if it exists */
if ((rc = apr_stat(&fi, metadata_path, APR_FINFO_MTIME, r->pool))
== APR_SUCCESS) {
/* really only clean once per so much time, check that we haven not recently run */
if (apr_time_now() < fi.mtime + apr_time_from_sec(cfg->cache_file_clean_interval)) {
oidc_debug(r,
"last cleanup call was less than %d seconds ago (next one as early as in %" APR_TIME_T_FMT " secs)",
cfg->cache_file_clean_interval,
apr_time_sec( fi.mtime + apr_time_from_sec(cfg->cache_file_clean_interval) - apr_time_now()));
return APR_SUCCESS;
}
/* time to clean, reset the modification time of the metadata file to reflect the timestamp of this cleaning cycle */
apr_file_mtime_set(metadata_path, apr_time_now(), r->pool);
oidc_debug(r, "start cleaning cycle");
} else {
/* no metadata file exists yet, create one (and open it) */
if ((rc = apr_file_open(&fd, metadata_path,
(APR_FOPEN_WRITE | APR_FOPEN_CREATE), APR_OS_DEFAULT, r->pool))
!= APR_SUCCESS) {
oidc_error(r, "error creating cache timestamp file '%s' (%s)",
metadata_path, apr_strerror(rc, s_err, sizeof(s_err)));
return rc;
}
/* and cleanup... */
if ((rc = apr_file_close(fd)) != APR_SUCCESS) {
oidc_error(r, "error closing cache timestamp file '%s' (%s)",
metadata_path, apr_strerror(rc, s_err, sizeof(s_err)));
}
}
/* time to clean, open the cache directory */
if ((rc = apr_dir_open(&dir, cfg->cache_file_dir, r->pool)) != APR_SUCCESS) {
oidc_error(r, "error opening cache directory '%s' for cleaning (%s)",
cfg->cache_file_dir, apr_strerror(rc, s_err, sizeof(s_err)));
return rc;
}
/* loop trough the cache file entries */
do {
/* read the next entry from the directory */
i = apr_dir_read(&fi, APR_FINFO_NAME, dir);
if (i == APR_SUCCESS) {
/* skip non-cache entries, cq. the ".", ".." and the metadata file */
if ((fi.name[0] == OIDC_CHAR_DOT)
|| (strstr(fi.name, OIDC_CACHE_FILE_PREFIX) != fi.name)
|| ((apr_strnatcmp(fi.name,
oidc_cache_file_name(r, "cache-file",
OIDC_CACHE_FILE_LAST_CLEANED)) == 0)))
continue;
/* get the fully qualified path to the cache file and open it */
const char *path = apr_psprintf(r->pool, "%s/%s",
cfg->cache_file_dir, fi.name);
if ((rc = apr_file_open(&fd, path, APR_FOPEN_READ, APR_OS_DEFAULT,
r->pool)) != APR_SUCCESS) {
oidc_error(r, "unable to open cache entry \"%s\" (%s)", path,
apr_strerror(rc, s_err, sizeof(s_err)));
continue;
}
/* read the header with cache metadata info */
rc = oidc_cache_file_read(r, path, fd, &info,
sizeof(oidc_cache_file_info_t));
apr_file_close(fd);
if (rc == APR_SUCCESS) {
/* check if this entry expired, if not just continue to the next entry */
if (apr_time_now() < info.expire)
continue;
/* the cache entry expired, we're going to remove it so log that event */
oidc_debug(r, "cache entry (%s) expired, removing file \"%s\")",
fi.name, path);
} else {
/* file open returned an error, log that */
oidc_error(r,
"cache entry (%s) corrupted (%s), removing file \"%s\"",
fi.name, apr_strerror(rc, s_err, sizeof(s_err)), path);
}
/* delete the cache file */
if ((rc = apr_file_remove(path, r->pool)) != APR_SUCCESS) {
/* hrm, this will most probably happen again on the next run... */
oidc_error(r, "could not delete cache file \"%s\" (%s)", path,
apr_strerror(rc, s_err, sizeof(s_err)));
}
}
} while (i == APR_SUCCESS);
apr_dir_close(dir);
return APR_SUCCESS;
}
/*
* write a value for the specified key to the cache
*/
static apr_byte_t oidc_cache_file_set(request_rec *r, const char *section,
const char *key, const char *value, apr_time_t expiry) {
apr_file_t *fd = NULL;
apr_status_t rc = APR_SUCCESS;
char s_err[128];
/* get the fully qualified path to the cache file based on the key name */
const char *path = oidc_cache_file_path(r, section, key);
/* only on writes (not on reads) we clean the cache first (if not done recently) */
oidc_cache_file_clean(r);
/* just remove cache file if value is NULL */
if (value == NULL) {
if ((rc = apr_file_remove(path, r->pool)) != APR_SUCCESS) {
oidc_error(r, "could not delete cache file \"%s\" (%s)", path,
apr_strerror(rc, s_err, sizeof(s_err)));
}
return TRUE;
}
/* try to open the cache file for writing, creating it if it does not exist */
if ((rc = apr_file_open(&fd, path,
(APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE),
APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) {
oidc_error(r, "cache file \"%s\" could not be opened (%s)", path,
apr_strerror(rc, s_err, sizeof(s_err)));
return FALSE;
}
/* lock the file and move the write pointer to the start of it */
apr_file_lock(fd, APR_FLOCK_EXCLUSIVE);
apr_off_t begin = 0;
apr_file_seek(fd, APR_SET, &begin);
/* construct the metadata for this cache entry in the header info */
oidc_cache_file_info_t info;
info.expire = expiry;
info.len = strlen(value) + 1;
/* write the header */
if ((rc = oidc_cache_file_write(r, path, fd, &info,
sizeof(oidc_cache_file_info_t))) != APR_SUCCESS)
return FALSE;
/* next write the value */
rc = oidc_cache_file_write(r, path, fd, (void *) value, info.len);
/* unlock and close the written file */
apr_file_unlock(fd);
apr_file_close(fd);
/* log our success/failure */
oidc_debug(r,
"%s entry for key \"%s\" in file of %" APR_SIZE_T_FMT " bytes",
(rc == APR_SUCCESS) ? "successfully stored" : "could not store",
key, info.len);
return (rc == APR_SUCCESS);
}
oidc_cache_t oidc_cache_file = {
"file",
1,
oidc_cache_file_post_config,
NULL,
oidc_cache_file_get,
oidc_cache_file_set,
NULL
};
mod_auth_openidc-2.3.3/src/cache/memcache.c 0000644 0000765 0000024 00000022222 13200625410 020407 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* caching using a memcache backend
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*/
#include "apr_general.h"
#include "apr_strings.h"
#include "apr_hash.h"
#include "apr_memcache.h"
#include
#include
#include
#include "../mod_auth_openidc.h"
extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
typedef struct oidc_cache_cfg_memcache_t {
/* cache_type = memcache: memcache ptr */
apr_memcache_t *cache_memcache;
} oidc_cache_cfg_memcache_t;
/* create the cache context */
static void *oidc_cache_memcache_cfg_create(apr_pool_t *pool) {
oidc_cache_cfg_memcache_t *context = apr_pcalloc(pool,
sizeof(oidc_cache_cfg_memcache_t));
context->cache_memcache = NULL;
return context;
}
/*
* initialize the memcache struct to a number of memcache servers
*/
static int oidc_cache_memcache_post_config(server_rec *s) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(s->module_config,
&auth_openidc_module);
if (cfg->cache_cfg != NULL)
return APR_SUCCESS;
oidc_cache_cfg_memcache_t *context = oidc_cache_memcache_cfg_create(
s->process->pool);
cfg->cache_cfg = context;
apr_status_t rv = APR_SUCCESS;
int nservers = 0;
char* split;
char* tok;
apr_pool_t *p = s->process->pool;
if (cfg->cache_memcache_servers == NULL) {
oidc_serror(s,
"cache type is set to \"memcache\", but no valid " OIDCMemCacheServers " setting was found");
return HTTP_INTERNAL_SERVER_ERROR;
}
/* loop over the provided memcache servers to find out the number of servers configured */
char *cache_config = apr_pstrdup(p, cfg->cache_memcache_servers);
split = apr_strtok(cache_config, OIDC_STR_SPACE, &tok);
while (split) {
nservers++;
split = apr_strtok(NULL, OIDC_STR_SPACE, &tok);
}
/* allocated space for the number of servers */
rv = apr_memcache_create(p, nservers, 0, &context->cache_memcache);
if (rv != APR_SUCCESS) {
oidc_serror(s, "failed to create memcache object of '%d' size",
nservers);
return HTTP_INTERNAL_SERVER_ERROR;
}
/* loop again over the provided servers */
cache_config = apr_pstrdup(p, cfg->cache_memcache_servers);
split = apr_strtok(cache_config, OIDC_STR_SPACE, &tok);
while (split) {
apr_memcache_server_t* st;
char* host_str;
char* scope_id;
apr_port_t port;
/* parse out host and port */
rv = apr_parse_addr_port(&host_str, &scope_id, &port, split, p);
if (rv != APR_SUCCESS) {
oidc_serror(s, "failed to parse cache server: '%s'", split);
return HTTP_INTERNAL_SERVER_ERROR;
}
if (host_str == NULL) {
oidc_serror(s,
"failed to parse cache server, no hostname specified: '%s'",
split);
return HTTP_INTERNAL_SERVER_ERROR;
}
if (port == 0)
port = 11211;
/* create the memcache server struct */
// TODO: tune this
rv = apr_memcache_server_create(p, host_str, port, 0, 1, 1, 60, &st);
if (rv != APR_SUCCESS) {
oidc_serror(s, "failed to create cache server: %s:%d", host_str,
port);
return HTTP_INTERNAL_SERVER_ERROR;
}
/* add the memcache server struct to the list */
rv = apr_memcache_add_server(context->cache_memcache, st);
if (rv != APR_SUCCESS) {
oidc_serror(s, "failed to add cache server: %s:%d", host_str, port);
return HTTP_INTERNAL_SERVER_ERROR;
}
/* go to the next entry */
split = apr_strtok(NULL, OIDC_STR_SPACE, &tok);
}
return OK;
}
#define OIDC_CACHE_MEMCACHE_STATUS_ERR_SIZE 64
/*
* printout readable error messages about memcache failures
*/
static void oidc_cache_memcache_log_status_error(request_rec *r, const char *s,
apr_status_t rv) {
char s_err[OIDC_CACHE_MEMCACHE_STATUS_ERR_SIZE];
apr_strerror(rv, s_err, OIDC_CACHE_MEMCACHE_STATUS_ERR_SIZE);
oidc_error(r,
"%s returned an error: [%s]; check your that your memcache server is available/accessible.",
s, s_err);
}
/*
* assemble single key name based on section/key input
*/
static char *oidc_cache_memcache_get_key(apr_pool_t *pool, const char *section,
const char *key) {
return apr_psprintf(pool, "%s:%s", section, key);
}
/*
* check dead/alive status for all servers
*/
static apr_byte_t oidc_cache_memcache_status(request_rec *r,
oidc_cache_cfg_memcache_t *context) {
int rc = TRUE;
int i;
for (i = 0; rc && i < context->cache_memcache->ntotal; i++)
rc = rc
&& (context->cache_memcache->live_servers[0]->status
!= APR_MC_SERVER_DEAD);
return rc;
}
/*
* get a name/value pair from memcache
*/
static apr_byte_t oidc_cache_memcache_get(request_rec *r, const char *section,
const char *key, const char **value) {
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
oidc_cache_cfg_memcache_t *context =
(oidc_cache_cfg_memcache_t *) cfg->cache_cfg;
apr_size_t len = 0;
/* get it */
apr_status_t rv = apr_memcache_getp(context->cache_memcache, r->pool,
oidc_cache_memcache_get_key(r->pool, section, key), (char **) value,
&len, NULL);
if (rv == APR_NOTFOUND) {
/*
* NB: workaround the fact that the apr_memcache returns APR_NOTFOUND if a server has been marked dead
*/
if (oidc_cache_memcache_status(r, context) == FALSE) {
oidc_cache_memcache_log_status_error(r, "apr_memcache_getp", rv);
return FALSE;
}
oidc_debug(r, "apr_memcache_getp: key %s not found in cache",
oidc_cache_memcache_get_key(r->pool, section, key));
return TRUE;
} else if (rv != APR_SUCCESS) {
oidc_cache_memcache_log_status_error(r, "apr_memcache_getp", rv);
return FALSE;
}
/* do sanity checking on the string value */
if ((*value) && (strlen(*value) != len)) {
oidc_error(r,
"apr_memcache_getp returned less bytes than expected: strlen(value) [%zu] != len [%" APR_SIZE_T_FMT "]",
strlen(*value), len);
return FALSE;
}
return TRUE;
}
/*
* store a name/value pair in memcache
*/
static apr_byte_t oidc_cache_memcache_set(request_rec *r, const char *section,
const char *key, const char *value, apr_time_t expiry) {
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
oidc_cache_cfg_memcache_t *context =
(oidc_cache_cfg_memcache_t *) cfg->cache_cfg;
apr_status_t rv = APR_SUCCESS;
/* see if we should be clearing this entry */
if (value == NULL) {
rv = apr_memcache_delete(context->cache_memcache,
oidc_cache_memcache_get_key(r->pool, section, key), 0);
if (rv == APR_NOTFOUND) {
oidc_debug(r, "apr_memcache_delete: key %s not found in cache",
oidc_cache_memcache_get_key(r->pool, section, key));
rv = APR_SUCCESS;
} else if (rv != APR_SUCCESS) {
oidc_cache_memcache_log_status_error(r, "apr_memcache_delete", rv);
}
} else {
/* calculate the timeout from now */
apr_uint32_t timeout = apr_time_sec(expiry - apr_time_now());
/* store it */
rv = apr_memcache_set(context->cache_memcache,
oidc_cache_memcache_get_key(r->pool, section, key),
(char *) value, strlen(value), timeout, 0);
if (rv != APR_SUCCESS) {
oidc_cache_memcache_log_status_error(r, "apr_memcache_set", rv);
}
}
return (rv == APR_SUCCESS);
}
oidc_cache_t oidc_cache_memcache = {
"memcache",
1,
oidc_cache_memcache_post_config,
NULL,
oidc_cache_memcache_get,
oidc_cache_memcache_set,
NULL
};
mod_auth_openidc-2.3.3/src/cache/shm.c 0000644 0000765 0000024 00000024626 13200625410 017446 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* caching using a shared memory backend, FIFO-style
* based on mod_auth_mellon code
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*/
#include
#include
#include
#include "apr_shm.h"
#include "../mod_auth_openidc.h"
extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
typedef struct oidc_cache_cfg_shm_t {
apr_shm_t *shm;
oidc_cache_mutex_t *mutex;
} oidc_cache_cfg_shm_t;
/* size of key in cached key/value pairs */
#define OIDC_CACHE_SHM_KEY_MAX 512
/* represents one (fixed size) cache entry, cq. name/value string pair */
typedef struct oidc_cache_shm_entry_t {
/* name of the cache entry */
char section_key[OIDC_CACHE_SHM_KEY_MAX];
/* last (read) access timestamp */
apr_time_t access;
/* expiry timestamp */
apr_time_t expires;
/* value of the cache entry */
char value[];
} oidc_cache_shm_entry_t;
/* create the cache context */
static void *oidc_cache_shm_cfg_create(apr_pool_t *pool) {
oidc_cache_cfg_shm_t *context = apr_pcalloc(pool,
sizeof(oidc_cache_cfg_shm_t));
context->shm = NULL;
context->mutex = oidc_cache_mutex_create(pool);
return context;
}
#define OIDC_CACHE_SHM_ADD_OFFSET(t, size) t = (oidc_cache_shm_entry_t *)((uint8_t *)t + size)
/*
* initialized the shared memory block in the parent process
*/
int oidc_cache_shm_post_config(server_rec *s) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(s->module_config,
&auth_openidc_module);
if (cfg->cache_cfg != NULL)
return APR_SUCCESS;
oidc_cache_cfg_shm_t *context = oidc_cache_shm_cfg_create(s->process->pool);
cfg->cache_cfg = context;
/* create the shared memory segment */
apr_status_t rv = apr_shm_create(&context->shm,
cfg->cache_shm_entry_size_max * cfg->cache_shm_size_max,
NULL, s->process->pool);
if (rv != APR_SUCCESS) {
oidc_serror(s, "apr_shm_create failed to create shared memory segment");
return HTTP_INTERNAL_SERVER_ERROR;
}
/* initialize the whole segment to '/0' */
int i;
oidc_cache_shm_entry_t *t = apr_shm_baseaddr_get(context->shm);
for (i = 0; i < cfg->cache_shm_size_max;
i++, OIDC_CACHE_SHM_ADD_OFFSET(t, cfg->cache_shm_entry_size_max)) {
t->section_key[0] = '\0';
t->access = 0;
}
if (oidc_cache_mutex_post_config(s, context->mutex, "shm") == FALSE)
return HTTP_INTERNAL_SERVER_ERROR;
oidc_sdebug(s,
"initialized shared memory with a cache size (# entries) of: %d, and a max (single) entry size of: %d",
cfg->cache_shm_size_max, cfg->cache_shm_entry_size_max);
return OK;
}
/*
* initialize the shared memory segment in a child process
*/
int oidc_cache_shm_child_init(apr_pool_t *p, server_rec *s) {
oidc_cfg *cfg = ap_get_module_config(s->module_config,
&auth_openidc_module);
oidc_cache_cfg_shm_t *context = (oidc_cache_cfg_shm_t *) cfg->cache_cfg;
/* initialize the lock for the child process */
return oidc_cache_mutex_child_init(p, s, context->mutex);
}
/*
* assemble single key name based on section/key input
*/
static char *oidc_cache_shm_get_key(apr_pool_t *pool, const char *section,
const char *key) {
return apr_psprintf(pool, "%s:%s", section, key);
}
/*
* get a value from the shared memory cache
*/
static apr_byte_t oidc_cache_shm_get(request_rec *r, const char *section,
const char *key, const char **value) {
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
oidc_cache_cfg_shm_t *context = (oidc_cache_cfg_shm_t *) cfg->cache_cfg;
int i;
const char *section_key = oidc_cache_shm_get_key(r->pool, section, key);
*value = NULL;
/* grab the global lock */
if (oidc_cache_mutex_lock(r, context->mutex) == FALSE)
return FALSE;
/* get the pointer to the start of the shared memory block */
oidc_cache_shm_entry_t *t = apr_shm_baseaddr_get(context->shm);
/* loop over the block, looking for the key */
for (i = 0; i < cfg->cache_shm_size_max;
i++, OIDC_CACHE_SHM_ADD_OFFSET(t, cfg->cache_shm_entry_size_max)) {
const char *tablekey = t->section_key;
if ((tablekey != NULL) && (apr_strnatcmp(tablekey, section_key) == 0)) {
/* found a match, check if it has expired */
if (t->expires > apr_time_now()) {
/* update access timestamp */
t->access = apr_time_now();
*value = t->value;
} else {
/* clear the expired entry */
t->section_key[0] = '\0';
t->access = 0;
}
/* we safely can break now since we would not have found an expired match twice */
break;
}
}
/* release the global lock */
oidc_cache_mutex_unlock(r, context->mutex);
return TRUE;
}
/*
* store a value in the shared memory cache
*/
static apr_byte_t oidc_cache_shm_set(request_rec *r, const char *section,
const char *key, const char *value, apr_time_t expiry) {
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
oidc_cache_cfg_shm_t *context = (oidc_cache_cfg_shm_t *) cfg->cache_cfg;
oidc_cache_shm_entry_t *match, *free, *lru;
oidc_cache_shm_entry_t *t;
apr_time_t current_time;
int i;
apr_time_t age;
const char *section_key = oidc_cache_shm_get_key(r->pool, section, key);
/* check that the passed in key is valid */
if (strlen(section_key) > OIDC_CACHE_SHM_KEY_MAX) {
oidc_error(r, "could not store value since key size is too large (%s)",
section_key);
return FALSE;
}
/* check that the passed in value is valid */
if ((value != NULL)
&& (strlen(value)
> (cfg->cache_shm_entry_size_max
- sizeof(oidc_cache_shm_entry_t)))) {
oidc_error(r,
"could not store value since value size is too large (%llu > %lu); consider increasing " OIDCCacheShmEntrySizeMax "",
(unsigned long long )strlen(value),
(unsigned long )(cfg->cache_shm_entry_size_max
- sizeof(oidc_cache_shm_entry_t)));
return FALSE;
}
/* grab the global lock */
if (oidc_cache_mutex_lock(r, context->mutex) == FALSE)
return FALSE;
/* get a pointer to the shared memory block */
t = apr_shm_baseaddr_get(context->shm);
/* get the current time */
current_time = apr_time_now();
/* loop over the block, looking for the key */
match = NULL;
free = NULL;
lru = t;
for (i = 0; i < cfg->cache_shm_size_max;
i++, OIDC_CACHE_SHM_ADD_OFFSET(t, cfg->cache_shm_entry_size_max)) {
/* see if this slot is free */
if (t->section_key[0] == '\0') {
if (free == NULL)
free = t;
continue;
}
/* see if a value already exists for this key */
if (apr_strnatcmp(t->section_key, section_key) == 0) {
match = t;
break;
}
/* see if this slot has expired */
if (t->expires <= current_time) {
if (free == NULL)
free = t;
continue;
}
/* see if this slot was less recently used than the current pointer */
if (t->access < lru->access) {
lru = t;
}
}
/* if we have no free slots, issue a warning about the LRU entry */
if (match == NULL && free == NULL) {
age = (current_time - lru->access) / 1000000;
if (age < 3600) {
oidc_warn(r,
"dropping LRU entry with age = %" APR_TIME_T_FMT "s, which is less than one hour; consider increasing the shared memory caching space (which is %d now) with the (global) " OIDCCacheShmMax " setting.",
age, cfg->cache_shm_size_max);
}
}
/* pick the best slot: choose one with a matching key over a free slot, over a least-recently-used one */
t = match ? match : (free ? free : lru);
/* see if we need to clear or set the value */
if (value != NULL) {
/* fill out the entry with the provided data */
strcpy(t->section_key, section_key);
strcpy(t->value, value);
t->expires = expiry;
t->access = current_time;
} else {
t->section_key[0] = '\0';
t->access = 0;
}
/* release the global lock */
oidc_cache_mutex_unlock(r, context->mutex);
return TRUE;
}
static int oidc_cache_shm_destroy(server_rec *s) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(s->module_config,
&auth_openidc_module);
oidc_cache_cfg_shm_t *context = (oidc_cache_cfg_shm_t *) cfg->cache_cfg;
apr_status_t rv = APR_SUCCESS;
if (context->shm) {
apr_global_mutex_lock(context->mutex->mutex);
if (*context->mutex->sema == 1) {
rv = apr_shm_destroy(context->shm);
oidc_sdebug(s, "apr_shm_destroy returned: %d", rv);
}
context->shm = NULL;
apr_global_mutex_unlock(context->mutex->mutex);
}
oidc_cache_mutex_destroy(s, context->mutex);
return rv;
}
oidc_cache_t oidc_cache_shm = {
"shm",
0,
oidc_cache_shm_post_config,
oidc_cache_shm_child_init,
oidc_cache_shm_get,
oidc_cache_shm_set,
oidc_cache_shm_destroy
};
mod_auth_openidc-2.3.3/src/cache/common.c 0000644 0000765 0000024 00000044747 13200625410 020155 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* core cache functions: locking, crypto and utils
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*/
#ifndef WIN32
#include
#endif
#include "apr_general.h"
#include
#include
#include
#ifdef AP_NEED_SET_MUTEX_PERMS
#include "unixd.h"
#endif
#include
#include
#include
#include
#include "../mod_auth_openidc.h"
extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
/* create the cache lock context */
oidc_cache_mutex_t *oidc_cache_mutex_create(apr_pool_t *pool) {
oidc_cache_mutex_t *ctx = apr_pcalloc(pool, sizeof(oidc_cache_mutex_t));
ctx->mutex = NULL;
ctx->mutex_filename = NULL;
ctx->shm = NULL;
ctx->sema = NULL;
return ctx;
}
#define OIDC_CACHE_ERROR_STR_MAX 255
/*
* convert a apr status code to a string
*/
char *oidc_cache_status2str(apr_status_t statcode) {
char buf[OIDC_CACHE_ERROR_STR_MAX];
return apr_strerror(statcode, buf, OIDC_CACHE_ERROR_STR_MAX);
}
apr_byte_t oidc_cache_mutex_post_config(server_rec *s, oidc_cache_mutex_t *m,
const char *type) {
apr_status_t rv = APR_SUCCESS;
const char *dir;
/* construct the mutex filename */
apr_temp_dir_get(&dir, s->process->pool);
m->mutex_filename = apr_psprintf(s->process->pool,
"%s/mod_auth_openidc_%s_mutex.%ld.%pp", dir, type,
(long int) getpid(), s);
/* create the mutex lock */
rv = apr_global_mutex_create(&m->mutex, (const char *) m->mutex_filename,
APR_LOCK_DEFAULT, s->process->pool);
if (rv != APR_SUCCESS) {
oidc_serror(s,
"apr_global_mutex_create failed to create mutex on file %s: %s (%d)",
m->mutex_filename, oidc_cache_status2str(rv), rv);
return FALSE;
}
/* need this on Linux */
#ifdef AP_NEED_SET_MUTEX_PERMS
#if MODULE_MAGIC_NUMBER_MAJOR >= 20081201
rv = ap_unixd_set_global_mutex_perms(m->mutex);
#else
rv = unixd_set_global_mutex_perms(m->mutex);
#endif
if (rv != APR_SUCCESS) {
oidc_serror(s,
"unixd_set_global_mutex_perms failed; could not set permissions: %s (%d)",
oidc_cache_status2str(rv), rv);
return FALSE;
}
#endif
rv = apr_shm_create(&m->shm, sizeof(int), NULL, s->process->pool);
if (rv != APR_SUCCESS) {
oidc_serror(s, "apr_shm_create failed to create shared memory segment");
return FALSE;
}
m->sema = apr_shm_baseaddr_get(m->shm);
*m->sema = 1;
return TRUE;
}
/*
* initialize the cache lock in a child process
*/
apr_status_t oidc_cache_mutex_child_init(apr_pool_t *p, server_rec *s,
oidc_cache_mutex_t *m) {
/* initialize the lock for the child process */
apr_status_t rv = apr_global_mutex_child_init(&m->mutex,
(const char *) m->mutex_filename, p);
if (rv != APR_SUCCESS) {
oidc_serror(s,
"apr_global_mutex_child_init failed to reopen mutex on file %s: %s (%d)",
m->mutex_filename, oidc_cache_status2str(rv), rv);
} else {
apr_global_mutex_lock(m->mutex);
m->sema = apr_shm_baseaddr_get(m->shm);
(*m->sema)++;
apr_global_mutex_unlock(m->mutex);
}
//oidc_sdebug(s, "semaphore: %d (m=%pp,s=%pp)", *m->sema, m, s);
return rv;
}
/*
* global lock
*/
apr_byte_t oidc_cache_mutex_lock(request_rec *r, oidc_cache_mutex_t *m) {
apr_status_t rv = apr_global_mutex_lock(m->mutex);
if (rv != APR_SUCCESS)
oidc_error(r, "apr_global_mutex_lock() failed: %s (%d)",
oidc_cache_status2str(rv), rv);
return TRUE;
}
/*
* global unlock
*/
apr_byte_t oidc_cache_mutex_unlock(request_rec *r, oidc_cache_mutex_t *m) {
apr_status_t rv = apr_global_mutex_unlock(m->mutex);
if (rv != APR_SUCCESS)
oidc_error(r, "apr_global_mutex_unlock() failed: %s (%d)",
oidc_cache_status2str(rv), rv);
return TRUE;
}
/*
* destroy mutex
*/
apr_byte_t oidc_cache_mutex_destroy(server_rec *s, oidc_cache_mutex_t *m) {
apr_status_t rv = APR_SUCCESS;
if (m->mutex != NULL) {
apr_global_mutex_lock(m->mutex);
(*m->sema)--;
//oidc_sdebug(s, "semaphore: %d (m=%pp,s=%pp)", *m->sema, m->mutex, s);
apr_global_mutex_unlock(m->mutex);
if ((m->shm != NULL) && (*m->sema == 0)) {
rv = apr_global_mutex_destroy(m->mutex);
oidc_sdebug(s, "apr_global_mutex_destroy returned :%d", rv);
m->mutex = NULL;
rv = apr_shm_destroy(m->shm);
oidc_sdebug(s, "apr_shm_destroy for semaphore returned: %d", rv);
m->shm = NULL;
rv = APR_SUCCESS;
}
}
return rv;
}
#define oidc_cache_crypto_openssl_error(r, fmt, ...) \
oidc_error(r, "%s: %s", apr_psprintf(r->pool, fmt, ##__VA_ARGS__), ERR_error_string(ERR_get_error(), NULL))
#define OIDC_CACHE_CIPHER EVP_aes_256_gcm()
#define OIDC_CACHE_TAG_LEN 16
#if (OPENSSL_VERSION_NUMBER >= 0x10100005L)
#define OIDC_CACHE_CRYPTO_GET_TAG EVP_CTRL_AEAD_GET_TAG
#define OIDC_CACHE_CRYPTO_SET_TAG EVP_CTRL_AEAD_SET_TAG
#define OIDC_CACHE_CRYPTO_SET_IVLEN EVP_CTRL_AEAD_SET_IVLEN
#else
#define OIDC_CACHE_CRYPTO_GET_TAG EVP_CTRL_GCM_GET_TAG
#define OIDC_CACHE_CRYPTO_SET_TAG EVP_CTRL_GCM_SET_TAG
#define OIDC_CACHE_CRYPTO_SET_IVLEN EVP_CTRL_GCM_SET_IVLEN
#endif
/*
* AES GCM encrypt
*/
static int oidc_cache_crypto_encrypt_impl(request_rec *r,
unsigned char *plaintext, int plaintext_len, const unsigned char *aad,
int aad_len, unsigned char *key, const unsigned char *iv, int iv_len,
unsigned char *ciphertext, const unsigned char *tag, int tag_len) {
EVP_CIPHER_CTX *ctx;
int len;
int ciphertext_len;
/* create and initialize the context */
if (!(ctx = EVP_CIPHER_CTX_new())) {
oidc_cache_crypto_openssl_error(r, "EVP_CIPHER_CTX_new");
return -1;
}
/* initialize the encryption cipher */
if (!EVP_EncryptInit_ex(ctx, OIDC_CACHE_CIPHER, NULL, NULL, NULL)) {
oidc_cache_crypto_openssl_error(r, "EVP_EncryptInit_ex");
return -1;
}
/* set IV length */
if (!EVP_CIPHER_CTX_ctrl(ctx, OIDC_CACHE_CRYPTO_SET_IVLEN, iv_len, NULL)) {
oidc_cache_crypto_openssl_error(r, "EVP_CIPHER_CTX_ctrl");
return -1;
}
/* initialize key and IV */
if (!EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv)) {
oidc_cache_crypto_openssl_error(r, "EVP_EncryptInit_ex");
return -1;
}
/* provide AAD data */
if (!EVP_EncryptUpdate(ctx, NULL, &len, aad, aad_len)) {
oidc_cache_crypto_openssl_error(r, "EVP_DecryptUpdate aad: aad_len=%d",
aad_len);
return -1;
}
/* provide the message to be encrypted and obtain the encrypted output */
if (!EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)) {
oidc_cache_crypto_openssl_error(r, "EVP_EncryptUpdate ciphertext");
return -1;
}
ciphertext_len = len;
/*
* finalize the encryption; normally ciphertext bytes may be written at
* this stage, but this does not occur in GCM mode
*/
if (!EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) {
oidc_cache_crypto_openssl_error(r, "EVP_EncryptFinal_ex");
return -1;
}
ciphertext_len += len;
/* get the tag */
if (!EVP_CIPHER_CTX_ctrl(ctx, OIDC_CACHE_CRYPTO_GET_TAG, tag_len,
(void *) tag)) {
oidc_cache_crypto_openssl_error(r, "EVP_CIPHER_CTX_ctrl");
return -1;
}
/* clean up */
EVP_CIPHER_CTX_free(ctx);
return ciphertext_len;
}
/*
* AES GCM decrypt
*/
static int oidc_cache_crypto_decrypt_impl(request_rec *r,
unsigned char *ciphertext, int ciphertext_len, const unsigned char *aad,
int aad_len, const unsigned char *tag, int tag_len, unsigned char *key,
const unsigned char *iv, int iv_len, unsigned char *plaintext) {
EVP_CIPHER_CTX *ctx;
int len;
int plaintext_len;
int ret;
/* create and initialize the context */
if (!(ctx = EVP_CIPHER_CTX_new())) {
oidc_cache_crypto_openssl_error(r, "EVP_CIPHER_CTX_new");
return -1;
}
/* initialize the decryption cipher */
if (!EVP_DecryptInit_ex(ctx, OIDC_CACHE_CIPHER, NULL, NULL, NULL)) {
oidc_cache_crypto_openssl_error(r, "EVP_DecryptInit_ex");
return -1;
}
/* set IV length */
if (!EVP_CIPHER_CTX_ctrl(ctx, OIDC_CACHE_CRYPTO_SET_IVLEN, iv_len, NULL)) {
oidc_cache_crypto_openssl_error(r, "EVP_CIPHER_CTX_ctrl");
return -1;
}
/* initialize key and IV */
if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) {
oidc_cache_crypto_openssl_error(r, "EVP_DecryptInit_ex");
return -1;
}
/* provide AAD data */
if (!EVP_DecryptUpdate(ctx, NULL, &len, aad, aad_len)) {
oidc_cache_crypto_openssl_error(r, "EVP_DecryptUpdate aad: aad_len=%d",
aad_len);
return -1;
}
/* provide the message to be decrypted and obtain the plaintext output */
if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) {
oidc_cache_crypto_openssl_error(r, "EVP_DecryptUpdate ciphertext");
return -1;
}
plaintext_len = len;
/* set expected tag value; works in OpenSSL 1.0.1d and later */
if (!EVP_CIPHER_CTX_ctrl(ctx, OIDC_CACHE_CRYPTO_SET_TAG, tag_len,
(void *) tag)) {
oidc_cache_crypto_openssl_error(r, "EVP_CIPHER_CTX_ctrl");
return -1;
}
/*
* finalize the decryption; a positive return value indicates success,
* anything else is a failure - the plaintext is not trustworthy
*/
ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len);
/* clean up */
EVP_CIPHER_CTX_free(ctx);
if (ret > 0) {
/* success */
plaintext_len += len;
return plaintext_len;
} else {
/* verify failed */
oidc_cache_crypto_openssl_error(r, "EVP_DecryptFinal_ex");
return -1;
}
}
/*
* static AAD value for encryption/decryption
*/
static const unsigned char OIDC_CACHE_CRYPTO_GCM_AAD[] = { 0x4d, 0x23, 0xc3,
0xce, 0xc3, 0x34, 0xb4, 0x9b, 0xdb, 0x37, 0x0c, 0x43, 0x7f, 0xec, 0x78,
0xde };
/*
* static IV value for encryption/decryption
*/
static const unsigned char OIDC_CACHE_CRYPTO_GCM_IV[] = { 0x00, 0x01, 0x02,
0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f };
/*
* AES GCM encrypt using the static AAD and IV
*/
static int oidc_cache_crypto_encrypt(request_rec *r, const char *plaintext,
unsigned char *key, char **result) {
char *encoded = NULL, *p = NULL, *e_tag = NULL;
unsigned char *ciphertext = NULL;
int plaintext_len, ciphertext_len, encoded_len, e_tag_len;
unsigned char tag[OIDC_CACHE_TAG_LEN];
/* allocate space for the ciphertext */
plaintext_len = strlen(plaintext) + 1;
ciphertext = apr_pcalloc(r->pool,
(plaintext_len + EVP_CIPHER_block_size(OIDC_CACHE_CIPHER)));
ciphertext_len = oidc_cache_crypto_encrypt_impl(r,
(unsigned char *) plaintext, plaintext_len,
OIDC_CACHE_CRYPTO_GCM_AAD, sizeof(OIDC_CACHE_CRYPTO_GCM_AAD), key,
OIDC_CACHE_CRYPTO_GCM_IV, sizeof(OIDC_CACHE_CRYPTO_GCM_IV),
ciphertext, tag, sizeof(tag));
/* base64url encode the resulting ciphertext */
encoded_len = oidc_base64url_encode(r, &encoded, (const char *) ciphertext,
ciphertext_len, 1);
if (encoded_len > 0) {
p = encoded;
/* now allocated space for the concatenated base64url encoded ciphertext and tag */
encoded = apr_pcalloc(r->pool,
encoded_len + 1 + OIDC_CACHE_TAG_LEN + 1);
memcpy(encoded, p, encoded_len);
p = encoded + encoded_len;
*p = OIDC_CHAR_DOT;
p++;
/* base64url encode the tag and append it in the buffer */
e_tag_len = oidc_base64url_encode(r, &e_tag, (const char *) tag,
OIDC_CACHE_TAG_LEN, 1);
memcpy(p, e_tag, e_tag_len);
encoded_len += e_tag_len + 1;
/* make sure the result is \0 terminated */
encoded[encoded_len] = '\0';
*result = encoded;
}
return encoded_len;
}
/*
* AES GCM decrypt using the static AAD and IV
*/
static int oidc_cache_crypto_decrypt(request_rec *r, const char *cache_value,
unsigned char *key, unsigned char **plaintext) {
int len = -1;
/* grab the base64url-encoded tag after the "." */
char *encoded_tag = strstr(cache_value, ".");
if (encoded_tag == NULL) {
oidc_error(r,
"corrupted cache value: no tag separator found in encrypted value");
return FALSE;
}
/* make sure we don't modify the original string since it may be just a pointer into the cache (shm) */
cache_value = apr_pstrmemdup(r->pool, cache_value,
strlen(cache_value) - strlen(encoded_tag));
encoded_tag++;
/* base64url decode the ciphertext */
char *d_bytes = NULL;
int d_len = oidc_base64url_decode(r->pool, &d_bytes, cache_value);
/* base64url decode the tag */
char *t_bytes = NULL;
int t_len = oidc_base64url_decode(r->pool, &t_bytes, encoded_tag);
/* see if we're still good to go */
if ((d_len > 0) && (t_len > 0)) {
/* allocated space for the plaintext */
*plaintext = apr_pcalloc(r->pool,
(d_len + EVP_CIPHER_block_size(OIDC_CACHE_CIPHER) - 1));
/* decrypt the ciphertext providing the tag value */
len = oidc_cache_crypto_decrypt_impl(r, (unsigned char *) d_bytes,
d_len, OIDC_CACHE_CRYPTO_GCM_AAD,
sizeof(OIDC_CACHE_CRYPTO_GCM_AAD), (unsigned char *) t_bytes,
t_len, key, OIDC_CACHE_CRYPTO_GCM_IV,
sizeof(OIDC_CACHE_CRYPTO_GCM_IV), *plaintext);
/* check the result and make sure it is \0 terminated */
if (len > -1) {
(*plaintext)[len] = '\0';
} else {
*plaintext = NULL;
}
}
return len;
}
/*
* hash the crypto passhphrase so it has enough key length for AES GCM 256
*/
static unsigned char *oidc_cache_hash_passphrase(request_rec *r,
const char *passphrase) {
unsigned char *key = NULL;
unsigned int key_len = 0;
oidc_jose_error_t err;
if (oidc_jose_hash_bytes(r->pool, OIDC_JOSE_ALG_SHA256,
(const unsigned char *) passphrase, strlen(passphrase), &key,
&key_len, &err) == FALSE) {
oidc_error(r, "oidc_jose_hash_bytes returned an error: %s", err.text);
return NULL;
}
return key;
}
/*
* hash a cache key and a crypto passphrase so the result is suitable as an randomized cache key
*/
static char *oidc_cache_get_hashed_key(request_rec *r, const char *passphrase,
const char *key) {
char *input = apr_psprintf(r->pool, "%s:%s", passphrase, key);
char *output = NULL;
if (oidc_util_hash_string_and_base64url_encode(r, OIDC_JOSE_ALG_SHA256,
input, &output) == FALSE) {
oidc_error(r,
"oidc_util_hash_string_and_base64url_encode returned an error");
return NULL;
}
return output;
}
/*
* get a key/value string pair from the cache, possibly decrypting it
*/
apr_byte_t oidc_cache_get(request_rec *r, const char *section, const char *key,
char **value) {
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
int encrypted = oidc_cfg_cache_encrypt(r);
apr_byte_t rc = TRUE;
char *msg = NULL;
oidc_debug(r, "enter: %s (section=%s, decrypt=%d, type=%s)", key, section,
encrypted, cfg->cache->name);
/* see if encryption is turned on */
if (encrypted == 1)
key = oidc_cache_get_hashed_key(r, cfg->crypto_passphrase, key);
/* get the value from the cache */
const char *cache_value = NULL;
if (cfg->cache->get(r, section, key, &cache_value) == FALSE) {
rc = FALSE;
goto out;
}
/* see if it is any good */
if (cache_value == NULL)
goto out;
/* see if encryption is turned on */
if (encrypted == 0) {
*value = apr_pstrdup(r->pool, cache_value);
goto out;
}
rc = (oidc_cache_crypto_decrypt(r, cache_value,
oidc_cache_hash_passphrase(r, cfg->crypto_passphrase),
(unsigned char **) value) > 0);
out:
/* log the result */
msg = apr_psprintf(r->pool, "from %s cache backend for %skey %s",
cfg->cache->name, encrypted ? "encrypted " : "", key);
if (rc == TRUE)
if (*value != NULL)
oidc_debug(r, "cache hit: return %d bytes %s",
*value ? (int )strlen(*value) : 0, msg);
else
oidc_debug(r, "cache miss %s", msg);
else
oidc_warn(r, "error retrieving value %s", msg);
return rc;
}
/*
* store a key/value string pair in the cache, possibly in encrypted form
*/
apr_byte_t oidc_cache_set(request_rec *r, const char *section, const char *key,
const char *value, apr_time_t expiry) {
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
int encrypted = oidc_cfg_cache_encrypt(r);
char *encoded = NULL;
apr_byte_t rc = FALSE;
char *msg = NULL;
oidc_debug(r,
"enter: %s (section=%s, len=%d, encrypt=%d, ttl(s)=%" APR_TIME_T_FMT ", type=%s)",
key, section, value ? (int )strlen(value) : 0, encrypted,
apr_time_sec(expiry - apr_time_now()), cfg->cache->name);
/* see if we need to encrypt */
if (encrypted == 1) {
key = oidc_cache_get_hashed_key(r, cfg->crypto_passphrase, key);
if (key == NULL)
goto out;
if (value != NULL) {
if (oidc_cache_crypto_encrypt(r, value,
oidc_cache_hash_passphrase(r, cfg->crypto_passphrase),
&encoded) <= 0)
goto out;
value = encoded;
}
}
/* store the resulting value in the cache */
rc = cfg->cache->set(r, section, key, value, expiry);
out:
/* log the result */
msg = apr_psprintf(r->pool, "%d bytes in %s cache backend for %skey %s",
value ? (int) strlen(value) : 0, cfg->cache->name,
encrypted ? "encrypted " : "", key);
if (rc == TRUE)
oidc_debug(r, "successfully stored %s", msg);
else
oidc_warn(r, "could NOT store %s", msg);
return rc;
}
mod_auth_openidc-2.3.3/src/oauth.c 0000644 0000765 0000024 00000056130 13202535004 016730 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*/
#include
#include
#include
#include
#include
#include "mod_auth_openidc.h"
#include "parse.h"
/*
* validate an access token against the validation endpoint of the Authorization server and gets a response back
*/
static apr_byte_t oidc_oauth_validate_access_token(request_rec *r, oidc_cfg *c,
const char *token, char **response) {
oidc_debug(r, "enter");
char *basic_auth = NULL;
char *bearer_auth = NULL;
/* assemble parameters to call the token endpoint for validation */
apr_table_t *params = apr_table_make(r->pool, 4);
/* add any configured extra static parameters to the introspection endpoint */
oidc_util_table_add_query_encoded_params(r->pool, params,
c->oauth.introspection_endpoint_params);
/* add the access_token itself */
apr_table_addn(params, c->oauth.introspection_token_param_name, token);
/* add the token endpoint authentication credentials */
if (oidc_proto_token_endpoint_auth(r, c,
c->oauth.introspection_endpoint_auth, c->oauth.client_id,
c->oauth.client_secret, c->oauth.introspection_endpoint_url, params,
&basic_auth, &bearer_auth) == FALSE)
return FALSE;
/* call the endpoint with the constructed parameter set and return the resulting response */
return apr_strnatcmp(c->oauth.introspection_endpoint_method,
OIDC_INTROSPECTION_METHOD_GET) == 0 ?
oidc_util_http_get(r, c->oauth.introspection_endpoint_url, params,
basic_auth, bearer_auth, c->oauth.ssl_validate_server, response,
c->http_timeout_long, c->outgoing_proxy,
oidc_dir_cfg_pass_cookies(r),
oidc_util_get_full_path(r->pool, c->oauth.introspection_endpoint_tls_client_cert),
oidc_util_get_full_path(r->pool, c->oauth.introspection_endpoint_tls_client_key)) :
oidc_util_http_post_form(r, c->oauth.introspection_endpoint_url,
params, basic_auth, bearer_auth, c->oauth.ssl_validate_server,
response, c->http_timeout_long, c->outgoing_proxy,
oidc_dir_cfg_pass_cookies(r),
oidc_util_get_full_path(r->pool, c->oauth.introspection_endpoint_tls_client_cert),
oidc_util_get_full_path(r->pool, c->oauth.introspection_endpoint_tls_client_key));
}
/*
* get the authorization header that should contain a bearer token
*/
apr_byte_t oidc_oauth_get_bearer_token(request_rec *r,
const char **access_token) {
/* get the directory specific setting on how the token can be passed in */
apr_byte_t accept_token_in = oidc_cfg_dir_accept_token_in(r);
const char *cookie_name = oidc_cfg_dir_accept_token_in_option(r,
OIDC_OAUTH_ACCEPT_TOKEN_IN_OPTION_COOKIE_NAME);
oidc_debug(r, "accept_token_in=%d", accept_token_in);
*access_token = NULL;
if ((accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER)
|| (accept_token_in == OIDC_OAUTH_ACCEPT_TOKEN_IN_DEFAULT)) {
/* get the authorization header */
const char *auth_line = oidc_util_hdr_in_authorization_get(r);
if (auth_line) {
oidc_debug(r, "authorization header found");
/* look for the Bearer keyword */
if (apr_strnatcasecmp(
ap_getword(r->pool, &auth_line, OIDC_CHAR_SPACE),
OIDC_PROTO_BEARER) == 0) {
/* skip any spaces after the Bearer keyword */
while (apr_isspace(*auth_line)) {
auth_line++;
}
/* copy the result in to the access_token */
*access_token = apr_pstrdup(r->pool, auth_line);
} else {
oidc_warn(r,
"client used unsupported authentication scheme: %s",
r->uri);
}
}
}
if ((*access_token == NULL) && (r->method_number == M_POST)
&& (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_POST)) {
apr_table_t *params = apr_table_make(r->pool, 8);
if (oidc_util_read_post_params(r, params) == TRUE) {
*access_token = apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN);
}
}
if ((*access_token == NULL)
&& (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY)) {
apr_table_t *params = apr_table_make(r->pool, 8);
oidc_util_read_form_encoded_params(r, params, r->args);
*access_token = apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN);
}
if ((*access_token == NULL)
&& (accept_token_in & OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE)) {
const char *auth_line = oidc_util_get_cookie(r, cookie_name);
if (auth_line != NULL) {
/* copy the result in to the access_token */
*access_token = apr_pstrdup(r->pool, auth_line);
} else {
oidc_warn(r, "no cookie found with name: %s", cookie_name);
}
}
if (*access_token == NULL) {
oidc_debug(r, "no bearer token found in the allowed methods: %s",
oidc_accept_oauth_token_in2str(r->pool, accept_token_in));
return FALSE;
}
/* log some stuff */
oidc_debug(r, "bearer token: %s", *access_token);
return TRUE;
}
/*
* copy over space separated scope value but do it in an array for authorization purposes
*/
/*
static void oidc_oauth_spaced_string_to_array(request_rec *r, json_t *src,
const char *src_key, json_t *dst, const char *dst_key) {
apr_hash_t *ht = NULL;
apr_hash_index_t *hi = NULL;
json_t *arr = NULL;
json_t *src_val = json_object_get(src, src_key);
if (src_val != NULL)
ht = oidc_util_spaced_string_to_hashtable(r->pool,
json_string_value(src_val));
if (ht != NULL) {
arr = json_array();
for (hi = apr_hash_first(NULL, ht); hi; hi = apr_hash_next(hi)) {
const char *k;
const char *v;
apr_hash_this(hi, (const void**) &k, NULL, (void**) &v);
json_array_append_new(arr, json_string(v));
}
json_object_set_new(dst, dst_key, arr);
}
}
*/
/*
* parse (custom/configurable) token expiry claim in introspection result
*/
static apr_byte_t oidc_oauth_parse_and_cache_token_expiry(request_rec *r,
oidc_cfg *c, json_t *introspection_response,
const char *expiry_claim_name, int expiry_format_absolute,
int expiry_claim_is_mandatory, apr_time_t *cache_until) {
oidc_debug(r,
"expiry_claim_name=%s, expiry_format_absolute=%d, expiry_claim_is_mandatory=%d",
expiry_claim_name, expiry_format_absolute,
expiry_claim_is_mandatory);
json_t *expiry = json_object_get(introspection_response, expiry_claim_name);
if (expiry == NULL) {
if (expiry_claim_is_mandatory) {
oidc_error(r,
"introspection response JSON object did not contain an \"%s\" claim",
expiry_claim_name);
return FALSE;
}
return TRUE;
}
if (!json_is_integer(expiry)) {
if (expiry_claim_is_mandatory) {
oidc_error(r,
"introspection response JSON object contains a \"%s\" claim but it is not a JSON integer",
expiry_claim_name);
return FALSE;
}
oidc_warn(r,
"introspection response JSON object contains a \"%s\" claim that is not an (optional) JSON integer: the introspection result will NOT be cached",
expiry_claim_name);
return TRUE;
}
json_int_t value = json_integer_value(expiry);
if (value <= 0) {
oidc_warn(r,
"introspection response JSON object integer number value <= 0 (%ld); introspection result will not be cached",
(long )value);
return TRUE;
}
*cache_until = apr_time_from_sec(value);
if (expiry_format_absolute == FALSE)
(*cache_until) += apr_time_now();
return TRUE;
}
#define OIDC_OAUTH_CACHE_KEY_RESPONSE "r"
#define OIDC_OAUTH_CACHE_KEY_TIMESTAMP "t"
static apr_byte_t oidc_oauth_cache_access_token(request_rec *r, oidc_cfg *c,
apr_time_t cache_until, const char *access_token, json_t *json) {
oidc_debug(r, "caching introspection result");
json_t *cache_entry = json_object();
json_object_set(cache_entry, OIDC_OAUTH_CACHE_KEY_RESPONSE, json);
json_object_set_new(cache_entry, OIDC_OAUTH_CACHE_KEY_TIMESTAMP,
json_integer(apr_time_sec(apr_time_now())));
char *cache_value = oidc_util_encode_json_object(r, cache_entry,
JSON_COMPACT);
/* set it in the cache so subsequent request don't need to validate the access_token and get the claims anymore */
oidc_cache_set_access_token(r, access_token, cache_value, cache_until);
json_decref(cache_entry);
return TRUE;
}
static apr_byte_t oidc_oauth_get_cached_access_token(request_rec *r,
oidc_cfg *c, const char *access_token, json_t **json) {
json_t *cache_entry = NULL;
char *s_cache_entry = NULL;
/* see if we've got the claims for this access_token cached already */
oidc_cache_get_access_token(r, access_token, &s_cache_entry);
if (s_cache_entry == NULL)
return FALSE;
/* json decode the cache entry */
if (oidc_util_decode_json_object(r, s_cache_entry, &cache_entry) == FALSE) {
*json = NULL;
return FALSE;
}
/* compare the timestamp against the freshness requirement */
json_t *v = json_object_get(cache_entry, OIDC_OAUTH_CACHE_KEY_TIMESTAMP);
apr_time_t now = apr_time_sec(apr_time_now());
int token_introspection_interval = oidc_cfg_token_introspection_interval(r);
if ((token_introspection_interval > 0)
&& (now > json_integer_value(v) + token_introspection_interval)) {
/* printout info about the event */
char buf[APR_RFC822_DATE_LEN + 1];
apr_rfc822_date(buf, apr_time_from_sec(json_integer_value(v)));
oidc_debug(r,
"token that was validated/cached at: [%s], does not meet token freshness requirement: %d)",
buf, token_introspection_interval);
/* invalidate the cache entry */
*json = NULL;
json_decref(cache_entry);
return FALSE;
}
oidc_debug(r,
"returning cached introspection result that meets freshness requirements: %s",
s_cache_entry);
/* we've got a cached introspection result that is still valid for this path's requirements */
*json = json_deep_copy(
json_object_get(cache_entry, OIDC_OAUTH_CACHE_KEY_RESPONSE));
json_decref(cache_entry);
return TRUE;
}
/*
* resolve and validate an access_token against the configured Authorization Server
*/
static apr_byte_t oidc_oauth_resolve_access_token(request_rec *r, oidc_cfg *c,
const char *access_token, json_t **token, char **response) {
json_t *result = NULL;
/* see if we've got the claims for this access_token cached already */
oidc_oauth_get_cached_access_token(r, c, access_token, &result);
if (result == NULL) {
char *s_json = NULL;
/* not cached, go out and validate the access_token against the Authorization server and get the JSON claims back */
if (oidc_oauth_validate_access_token(r, c, access_token,
&s_json) == FALSE) {
oidc_error(r,
"could not get a validation response from the Authorization server");
return FALSE;
}
/* decode and see if it is not an error response somehow */
if (oidc_util_decode_json_and_check_error(r, s_json, &result) == FALSE)
return FALSE;
json_t *active = json_object_get(result, OIDC_PROTO_ACTIVE);
apr_time_t cache_until;
if (active != NULL) {
if (json_is_boolean(active)) {
if (!json_is_true(active)) {
oidc_debug(r,
"\"%s\" boolean object with value \"false\" found in response JSON object",
OIDC_PROTO_ACTIVE);
json_decref(result);
return FALSE;
}
} else if (json_is_string(active)) {
if (apr_strnatcasecmp(json_string_value(active), "true") != 0) {
oidc_debug(r,
"\"%s\" string object with value that is not equal to \"true\" found in response JSON object: %s",
OIDC_PROTO_ACTIVE, json_string_value(active));
json_decref(result);
return FALSE;
}
} else {
oidc_debug(r,
"no \"%s\" boolean or string object found in response JSON object",
OIDC_PROTO_ACTIVE);
json_decref(result);
return FALSE;
}
if (oidc_oauth_parse_and_cache_token_expiry(r, c, result,
OIDC_CLAIM_EXP,
TRUE, FALSE, &cache_until) == FALSE) {
json_decref(result);
return FALSE;
}
/* set it in the cache so subsequent request don't need to validate the access_token and get the claims anymore */
oidc_oauth_cache_access_token(r, c, cache_until, access_token,
result);
} else {
if (oidc_oauth_parse_and_cache_token_expiry(r, c, result,
c->oauth.introspection_token_expiry_claim_name,
apr_strnatcmp(
c->oauth.introspection_token_expiry_claim_format,
OIDC_CLAIM_FORMAT_ABSOLUTE) == 0,
c->oauth.introspection_token_expiry_claim_required,
&cache_until) == FALSE) {
json_decref(result);
return FALSE;
}
/* set it in the cache so subsequent request don't need to validate the access_token and get the claims anymore */
oidc_oauth_cache_access_token(r, c, cache_until, access_token,
result);
}
}
/* return the access_token JSON object */
json_t *tkn = json_object_get(result, OIDC_PROTO_ACCESS_TOKEN);
if ((tkn != NULL) && (json_is_object(tkn))) {
/*
* assume PingFederate validation: copy over those claims from the access_token
* that are relevant for authorization purposes
*/
json_object_set(tkn, OIDC_PROTO_CLIENT_ID,
json_object_get(result, OIDC_PROTO_CLIENT_ID));
json_object_set(tkn, OIDC_PROTO_SCOPE,
json_object_get(result, OIDC_PROTO_SCOPE));
//oidc_oauth_spaced_string_to_array(r, result, OIDC_PROTO_SCOPE, tkn, "scopes");
/* return only the pimped access_token results */
*token = json_deep_copy(tkn);
json_decref(result);
} else {
//oidc_oauth_spaced_string_to_array(r, result, OIDC_PROTO_SCOPE, result, "scopes");
/* assume spec compliant introspection */
*token = result;
}
/* stringify the response */
*response = oidc_util_encode_json_object(r, *token, JSON_COMPACT);
return TRUE;
}
/*
* validate a JWT access token (locally)
*
* TODO: document that we're reusing the following settings from the OIDC config section:
* - JWKs URI refresh interval
* - encryption key material (OIDCPrivateKeyFiles)
*
* OIDCOAuthRemoteUserClaim client_id
* # 32x 61 hex
* OIDCOAuthVerifySharedKeys aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
*/
static apr_byte_t oidc_oauth_validate_jwt_access_token(request_rec *r,
oidc_cfg *c, const char *access_token, json_t **token, char **response) {
oidc_debug(r, "enter: JWT access_token header=%s",
oidc_proto_peek_jwt_header(r, access_token, NULL));
oidc_jose_error_t err;
oidc_jwk_t *jwk = NULL;
if (oidc_util_create_symmetric_key(r, c->provider.client_secret, 0, NULL,
TRUE, &jwk) == FALSE)
return FALSE;
oidc_jwt_t *jwt = NULL;
if (oidc_jwt_parse(r->pool, access_token, &jwt,
oidc_util_merge_symmetric_key(r->pool, c->private_keys, jwk),
&err) == FALSE) {
oidc_error(r, "could not parse JWT from access_token: %s",
oidc_jose_e2s(r->pool, err));
return FALSE;
}
oidc_jwk_destroy(jwk);
oidc_debug(r, "successfully parsed JWT with header: %s",
jwt->header.value.str);
/*
* validate the access token JWT by validating the (optional) exp claim
* don't enforce anything around iat since it doesn't make much sense for access tokens
*/
if (oidc_proto_validate_jwt(r, jwt, NULL, FALSE, FALSE, -1) == FALSE) {
oidc_jwt_destroy(jwt);
return FALSE;
}
oidc_debug(r,
"verify JWT against %d statically configured public keys and %d shared keys, with JWKs URI set to %s",
c->oauth.verify_public_keys ?
apr_hash_count(c->oauth.verify_public_keys) : 0,
c->oauth.verify_shared_keys ?
apr_hash_count(c->oauth.verify_shared_keys) : 0,
c->oauth.verify_jwks_uri);
oidc_jwks_uri_t jwks_uri = { c->oauth.verify_jwks_uri,
c->provider.jwks_refresh_interval, c->oauth.ssl_validate_server };
if (oidc_proto_jwt_verify(r, c, jwt, &jwks_uri,
oidc_util_merge_key_sets(r->pool, c->oauth.verify_public_keys,
c->oauth.verify_shared_keys)) == FALSE) {
oidc_error(r,
"JWT access token signature could not be validated, aborting");
oidc_jwt_destroy(jwt);
return FALSE;
}
oidc_debug(r, "successfully verified JWT access token: %s",
jwt->payload.value.str);
*token = jwt->payload.value.json;
*response = jwt->payload.value.str;
return TRUE;
}
/*
* set the WWW-Authenticate response header according to https://tools.ietf.org/html/rfc6750#section-3
*/
int oidc_oauth_return_www_authenticate(request_rec *r, const char *error,
const char *error_description) {
char *hdr = apr_psprintf(r->pool, "%s", OIDC_PROTO_BEARER);
if (ap_auth_name(r) != NULL)
hdr = apr_psprintf(r->pool, "%s %s=\"%s\"", hdr, OIDC_PROTO_REALM,
ap_auth_name(r));
if (error != NULL)
hdr = apr_psprintf(r->pool, "%s%s %s=\"%s\"", hdr,
(ap_auth_name(r) ? "," : ""), OIDC_PROTO_ERROR, error);
if (error_description != NULL)
hdr = apr_psprintf(r->pool, "%s, %s=\"%s\"", hdr,
OIDC_PROTO_ERROR_DESCRIPTION, error_description);
oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_WWW_AUTHENTICATE, hdr);
return HTTP_UNAUTHORIZED;
}
/*
* set the unique user identifier that will be propagated in the Apache r->user and REMOTE_USER variables
*/
static apr_byte_t oidc_oauth_set_request_user(request_rec *r, oidc_cfg *c,
json_t *token) {
char *remote_user = NULL;
if (oidc_get_remote_user(r, c->oauth.remote_user_claim.claim_name,
c->oauth.remote_user_claim.reg_exp, c->oauth.remote_user_claim.replace, token, &remote_user) == FALSE) {
oidc_error(r,
"" OIDCOAuthRemoteUserClaim " is set to \"%s\", but could not set the remote user based the available claims for the user",
c->oauth.remote_user_claim.claim_name);
return FALSE;
}
r->user = remote_user;
oidc_debug(r, "set user to \"%s\" based on claim: \"%s\"%s", r->user,
c->oauth.remote_user_claim.claim_name,
c->oauth.remote_user_claim.reg_exp ?
apr_psprintf(r->pool, " and expression: \"%s\" and replace string: \"%s\"",
c->oauth.remote_user_claim.reg_exp, c->oauth.remote_user_claim.replace) :
"");
return TRUE;
}
/*
* main routine: handle OAuth 2.0 authentication/authorization
*/
int oidc_oauth_check_userid(request_rec *r, oidc_cfg *c) {
/* check if this is a sub-request or an initial request */
if (!ap_is_initial_req(r)) {
if (r->main != NULL)
r->user = r->main->user;
else if (r->prev != NULL)
r->user = r->prev->user;
if (r->user != NULL) {
/* this is a sub-request and we have a session */
oidc_debug(r,
"recycling user '%s' from initial request for sub-request",
r->user);
/* strip any cookies that we need to */
oidc_strip_cookies(r);
return OK;
}
/* check if this is a request for the public (encryption) keys */
} else if (oidc_util_request_matches_url(r, oidc_get_redirect_uri(r, c))) {
if (oidc_util_request_has_parameter(r,
OIDC_REDIRECT_URI_REQUEST_JWKS)) {
return oidc_handle_jwks(r, c);
}
}
/* we don't have a session yet */
/* get the bearer access token from the Authorization header */
const char *access_token = NULL;
if (oidc_oauth_get_bearer_token(r, &access_token) == FALSE) {
if (r->method_number == M_OPTIONS) {
r->user = "";
return OK;
}
return oidc_oauth_return_www_authenticate(r,
OIDC_PROTO_ERR_INVALID_REQUEST, "No bearer token found in the request");
}
/* validate the obtained access token against the OAuth AS validation endpoint */
json_t *token = NULL;
char *s_token = NULL;
/* check if an introspection endpoint is set */
if (c->oauth.introspection_endpoint_url != NULL) {
/* we'll validate the token remotely */
if (oidc_oauth_resolve_access_token(r, c, access_token, &token,
&s_token) == FALSE)
return oidc_oauth_return_www_authenticate(r,
OIDC_PROTO_ERR_INVALID_TOKEN,
"Reference token could not be introspected");
} else {
/* no introspection endpoint is set, assume the token is a JWT and validate it locally */
if (oidc_oauth_validate_jwt_access_token(r, c, access_token, &token,
&s_token) == FALSE)
return oidc_oauth_return_www_authenticate(r,
OIDC_PROTO_ERR_INVALID_TOKEN, "JWT token could not be validated");
}
/* check that we've got something back */
if (token == NULL) {
oidc_error(r, "could not resolve claims (token == NULL)");
return oidc_oauth_return_www_authenticate(r,
OIDC_PROTO_ERR_INVALID_TOKEN,
"No claims could be parsed from the token");
}
/* store the parsed token (cq. the claims from the response) in the request state so it can be accessed by the authz routines */
oidc_request_state_set(r, OIDC_REQUEST_STATE_KEY_CLAIMS,
(const char *) s_token);
/* set the request user */
if (oidc_oauth_set_request_user(r, c, token) == FALSE) {
json_decref(token);
oidc_error(r,
"remote user could not be set, aborting with HTTP_UNAUTHORIZED");
return oidc_oauth_return_www_authenticate(r,
OIDC_PROTO_ERR_INVALID_TOKEN, "Could not set remote user");
}
/*
* we're going to pass the information that we have to the application,
* but first we need to scrub the headers that we're going to use for security reasons
*/
oidc_scrub_headers(r);
/* set the user authentication HTTP header if set and required */
char *authn_header = oidc_cfg_dir_authn_header(r);
apr_byte_t pass_headers = oidc_cfg_dir_pass_info_in_headers(r);
apr_byte_t pass_envvars = oidc_cfg_dir_pass_info_in_envvars(r);
if ((r->user != NULL) && (authn_header != NULL))
oidc_util_hdr_in_set(r, authn_header, r->user);
/* set the resolved claims in the HTTP headers for the target application */
oidc_util_set_app_infos(r, token, oidc_cfg_claim_prefix(r),
c->claim_delimiter, pass_headers, pass_envvars);
/* set the access_token in the app headers */
if (access_token != NULL) {
oidc_util_set_app_info(r, OIDC_APP_INFO_ACCESS_TOKEN, access_token,
OIDC_DEFAULT_HEADER_PREFIX, pass_headers, pass_envvars);
}
/* free JSON resources */
json_decref(token);
/* strip any cookies that we need to */
oidc_strip_cookies(r);
return OK;
}
mod_auth_openidc-2.3.3/src/proto.c 0000644 0000765 0000024 00000305174 13203320522 016756 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*/
#include
#include
#include
#include
#include "mod_auth_openidc.h"
#include "parse.h"
#include
#include
extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
/*
* generate a random string value value of a specified length
*/
static apr_byte_t oidc_proto_generate_random_string(request_rec *r,
char **output, int len) {
unsigned char *bytes = apr_pcalloc(r->pool, len);
if (apr_generate_random_bytes(bytes, len) != APR_SUCCESS) {
oidc_error(r, "apr_generate_random_bytes returned an error");
return FALSE;
}
if (oidc_base64url_encode(r, output, (const char *) bytes, len, TRUE)
<= 0) {
oidc_error(r, "oidc_base64url_encode returned an error");
return FALSE;
}
return TRUE;
}
#define OIDC_REQUEST_OJBECT_COPY_FROM_REQUEST "copy_from_request"
#define OIDC_REQUEST_OJBECT_COPY_AND_REMOVE_FROM_REQUEST "copy_and_remove_from_request"
/*
* indicates wether a request parameter from the authorization request needs to be
* copied and/or deleted to/from the protected request object based on the settings specified
* in the "copy_from_request"/"copy_and_remove_from_request" JSON array in the request object
*/
static apr_byte_t oidc_proto_param_needs_action(json_t *request_object_config,
const char *parameter_name, const char *action) {
json_t *copy_from_request = json_object_get(request_object_config, action);
size_t index = 0;
while (index < json_array_size(copy_from_request)) {
json_t *value = json_array_get(copy_from_request, index);
if ((json_is_string(value))
&& (apr_strnatcmp(json_string_value(value), parameter_name) == 0)) {
return TRUE;
}
index++;
}
return FALSE;
}
/* context structure for copying request parameters */
typedef struct oidc_proto_copy_req_ctx_t {
request_rec *r;
json_t *request_object_config;
oidc_jwt_t *request_object;
apr_table_t *params2;
} oidc_proto_copy_req_ctx_t;
/*
* copy a parameter key/value from the authorizion request to the
* request object if the configuration setting says to include it
*/
static int oidc_proto_copy_from_request(void* rec, const char* name,
const char* value) {
oidc_proto_copy_req_ctx_t *ctx = (oidc_proto_copy_req_ctx_t *) rec;
oidc_debug(ctx->r, "processing name: %s, value: %s", name, value);
if (oidc_proto_param_needs_action(ctx->request_object_config, name,
OIDC_REQUEST_OJBECT_COPY_FROM_REQUEST)
|| oidc_proto_param_needs_action(ctx->request_object_config, name,
OIDC_REQUEST_OJBECT_COPY_AND_REMOVE_FROM_REQUEST)) {
json_t *result = NULL;
json_error_t json_error;
result = json_loads(value, JSON_DECODE_ANY, &json_error);
if (result == NULL)
/* assume string */
result = json_string(value);
if (result) {
json_object_set_new(ctx->request_object->payload.value.json, name,
json_deep_copy(result));
json_decref(result);
}
if (oidc_proto_param_needs_action(ctx->request_object_config, name,
OIDC_REQUEST_OJBECT_COPY_AND_REMOVE_FROM_REQUEST)) {
apr_table_set(ctx->params2, name, name);
}
}
return 1;
}
/*
* delete a parameter key/value from the authorizion request if the configuration setting says to remove it
*/
static int oidc_proto_delete_from_request(void* rec, const char* name,
const char* value) {
oidc_proto_copy_req_ctx_t *ctx = (oidc_proto_copy_req_ctx_t *) rec;
oidc_debug(ctx->r, "deleting from query paramters: name: %s, value: %s",
name, value);
if (oidc_proto_param_needs_action(ctx->request_object_config, name,
OIDC_REQUEST_OJBECT_COPY_AND_REMOVE_FROM_REQUEST)) {
apr_table_unset(ctx->params2, name);
}
return 1;
}
/*
* obtain the public key for a provider to encrypt the request object with
*/
apr_byte_t oidc_proto_get_encryption_jwk_by_type(request_rec *r, oidc_cfg *cfg,
struct oidc_provider_t *provider, int key_type, oidc_jwk_t **jwk) {
oidc_jwks_uri_t jwks_uri = { provider->jwks_uri,
provider->jwks_refresh_interval, provider->ssl_validate_server };
oidc_jose_error_t err;
json_t *j_jwks = NULL;
apr_byte_t force_refresh = TRUE;
oidc_jwk_t *key = NULL;
char *jwk_json = NULL;
/* TODO: forcefully refresh now; we may want to relax that */
oidc_metadata_jwks_get(r, cfg, &jwks_uri, &j_jwks, &force_refresh);
if (j_jwks == NULL) {
oidc_error(r, "could not retrieve JSON Web Keys");
return FALSE;
}
json_t *keys = json_object_get(j_jwks, "keys");
if ((keys == NULL) || !(json_is_array(keys))) {
oidc_error(r, "\"keys\" array element is not a JSON array");
return FALSE;
}
int i;
/* walk the set of published keys to find the first that has a matching type */
for (i = 0; i < json_array_size(keys); i++) {
json_t *elem = json_array_get(keys, i);
const char *use = json_string_value(
json_object_get(elem, OIDC_JWK_USE));
if ((use != NULL) && (strcmp(use, OIDC_JWK_ENC) != 0)) {
oidc_debug(r, "skipping key because of non-matching \"%s\": \"%s\"",
OIDC_JWK_USE, use);
continue;
}
if (oidc_jwk_parse_json(r->pool, elem, &key, &err) == FALSE) {
oidc_warn(r, "oidc_jwk_parse_json failed: %s",
oidc_jose_e2s(r->pool, err));
continue;
}
if (key_type == key->kty) {
oidc_jwk_to_json(r->pool, key, &jwk_json, &err);
oidc_debug(r, "found matching encryption key type for key: %s",
jwk_json);
*jwk = key;
break;
}
oidc_jwk_destroy(key);
}
/* no need anymore for the parsed json_t contents, release the it */
json_decref(j_jwks);
return (*jwk != NULL);
}
/*
* generate a request object
*/
char *oidc_proto_create_request_object(request_rec *r,
struct oidc_provider_t *provider, json_t * request_object_config,
apr_table_t *params) {
oidc_debug(r, "enter");
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
/* create the request object value */
oidc_jwt_t *request_object = oidc_jwt_new(r->pool, TRUE, TRUE);
/* set basic values: iss and aud */
json_object_set_new(request_object->payload.value.json, OIDC_CLAIM_ISS,
json_string(provider->client_id));
json_object_set_new(request_object->payload.value.json, OIDC_CLAIM_AUD,
json_string(provider->issuer));
/* add static values to the request object as configured in the .conf file; may override iss/aud */
oidc_util_json_merge(r, json_object_get(request_object_config, "static"),
request_object->payload.value.json);
/* copy parameters from the authorization request as configured in the .conf file */
apr_table_t *delete_from_query_params = apr_table_make(r->pool, 0);
oidc_proto_copy_req_ctx_t data = { r, request_object_config, request_object,
delete_from_query_params };
apr_table_do(oidc_proto_copy_from_request, &data, params, NULL);
/* delete parameters from the query parameters of the authorization request as configured in the .conf file */
data.params2 = params;
apr_table_do(oidc_proto_delete_from_request, &data,
delete_from_query_params, NULL);
/* debug logging */
oidc_debug(r, "request object: %s",
oidc_util_encode_json_object(r, request_object->payload.value.json, JSON_COMPACT));
char *serialized_request_object = NULL;
oidc_jose_error_t err;
/* get the crypto settings from the configuration */
json_t *crypto = json_object_get(request_object_config, "crypto");
oidc_json_object_get_string(r->pool, crypto, "sign_alg",
&request_object->header.alg, "none");
/* see if we need to sign the request object */
if (strcmp(request_object->header.alg, "none") != 0) {
oidc_jwk_t *jwk = NULL;
int jwk_needs_destroy = 0;
switch (oidc_jwt_alg2kty(request_object)) {
case CJOSE_JWK_KTY_RSA:
if (cfg->private_keys != NULL) {
apr_ssize_t klen = 0;
apr_hash_index_t *hi = apr_hash_first(r->pool,
cfg->private_keys);
apr_hash_this(hi, (const void **) &request_object->header.kid,
&klen, (void **) &jwk);
} else {
oidc_error(r,
"no private keys have been configured to use for private_key_jwt client authentication (" OIDCPrivateKeyFiles ")");
}
break;
case CJOSE_JWK_KTY_OCT:
oidc_util_create_symmetric_key(r, provider->client_secret, 0, NULL,
FALSE, &jwk);
jwk_needs_destroy = 1;
break;
default:
oidc_error(r,
"unsupported signing algorithm, no key type for algorithm: %s",
request_object->header.alg);
break;
}
if (jwk == NULL) {
oidc_jwt_destroy(request_object);
json_decref(request_object_config);
return FALSE;
}
if (oidc_jwt_sign(r->pool, request_object, jwk, &err) == FALSE) {
oidc_error(r, "signing Request Object failed: %s",
oidc_jose_e2s(r->pool, err));
if (jwk_needs_destroy)
oidc_jwk_destroy(jwk);
oidc_jwt_destroy(request_object);
json_decref(request_object_config);
return FALSE;
}
if (jwk_needs_destroy)
oidc_jwk_destroy(jwk);
}
oidc_jwt_t *jwe = oidc_jwt_new(r->pool, TRUE, FALSE);
if (jwe == NULL) {
oidc_error(r, "creating JWE failed");
oidc_jwt_destroy(request_object);
json_decref(request_object_config);
return FALSE;
}
oidc_json_object_get_string(r->pool, crypto, "crypt_alg", &jwe->header.alg,
NULL);
oidc_json_object_get_string(r->pool, crypto, "crypt_enc", &jwe->header.enc,
NULL);
char *cser = oidc_jwt_serialize(r->pool, request_object, &err);
/* see if we need to encrypt the request object */
if (jwe->header.alg != NULL) {
oidc_jwk_t *jwk = NULL;
switch (oidc_jwt_alg2kty(jwe)) {
case CJOSE_JWK_KTY_RSA:
oidc_proto_get_encryption_jwk_by_type(r, cfg, provider,
CJOSE_JWK_KTY_RSA, &jwk);
break;
case CJOSE_JWK_KTY_OCT:
oidc_util_create_symmetric_key(r, provider->client_secret,
oidc_alg2keysize(jwe->header.alg), OIDC_JOSE_ALG_SHA256,
FALSE, &jwk);
break;
default:
oidc_error(r,
"unsupported encryption algorithm, no key type for algorithm: %s",
request_object->header.alg);
break;
}
if (jwk == NULL) {
oidc_jwt_destroy(jwe);
oidc_jwt_destroy(request_object);
json_decref(request_object_config);
return FALSE;
}
if (jwe->header.enc == NULL)
jwe->header.enc = apr_pstrdup(r->pool, CJOSE_HDR_ENC_A128CBC_HS256);
if (oidc_jwt_encrypt(r->pool, jwe, jwk, cser,
&serialized_request_object, &err) == FALSE) {
oidc_error(r, "encrypting JWT failed: %s",
oidc_jose_e2s(r->pool, err));
oidc_jwk_destroy(jwk);
oidc_jwt_destroy(jwe);
oidc_jwt_destroy(request_object);
json_decref(request_object_config);
return FALSE;
}
oidc_jwk_destroy(jwk);
} else {
/* should be sign only or "none" */
serialized_request_object = cser;
}
oidc_jwt_destroy(request_object);
oidc_jwt_destroy(jwe);
json_decref(request_object_config);
oidc_debug(r, "serialized request object JWT header = \"%s\"",
oidc_proto_peek_jwt_header(r, serialized_request_object, NULL));
oidc_debug(r, "serialized request object = \"%s\"",
serialized_request_object);
return serialized_request_object;
}
/*
* generate a request object and pass it by reference in the authorization request
*/
static char *oidc_proto_create_request_uri(request_rec *r,
struct oidc_provider_t *provider, json_t * request_object_config,
const char *redirect_uri, apr_table_t *params) {
oidc_debug(r, "enter");
/* see if we need to override the resolver URL, mostly for test purposes */
char *resolver_url = NULL;
if (json_object_get(request_object_config, "url") != NULL)
resolver_url = apr_pstrdup(r->pool,
json_string_value(
json_object_get(request_object_config, "url")));
else
resolver_url = apr_pstrdup(r->pool, redirect_uri);
char *serialized_request_object = oidc_proto_create_request_object(r,
provider, request_object_config, params);
/* generate a temporary reference, store the request object in the cache and generate a Request URI that references it */
char *request_uri = NULL;
if (serialized_request_object != NULL) {
char *request_ref = NULL;
if (oidc_proto_generate_random_string(r, &request_ref, 16) == TRUE) {
oidc_cache_set_request_uri(r, request_ref,
serialized_request_object,
apr_time_now() + apr_time_from_sec(OIDC_REQUEST_URI_CACHE_DURATION));
request_uri = apr_psprintf(r->pool, "%s?%s=%s", resolver_url,
OIDC_PROTO_REQUEST_URI, oidc_util_escape_string(r, request_ref));
}
}
return request_uri;
}
/*
* Generic function to generate request/request_object parameter with value
*/
static void oidc_proto_add_request_param(request_rec *r,
struct oidc_provider_t *provider, const char *redirect_uri,
apr_table_t *params) {
/* parse the request object configuration from a string in to a JSON structure */
json_t *request_object_config = NULL;
if (oidc_util_decode_json_object(r, provider->request_object,
&request_object_config) == FALSE)
return;
/* request_uri is used as default parameter for sending Request Object */
char* parameter = OIDC_PROTO_REQUEST_URI;
/* get request_object_type parameter from config */
json_t *request_object_type = json_object_get(request_object_config,
"request_object_type");
if (request_object_type != NULL) {
const char* request_object_type_str = json_string_value(
request_object_type);
if (request_object_type_str == NULL) {
oidc_error(r,
"Value of request_object_type in request_object config is not a string");
return;
}
/* ensure parameter variable to have a valid value */
if (strcmp(request_object_type_str, OIDC_PROTO_REQUEST_OBJECT) == 0) {
parameter = OIDC_PROTO_REQUEST_OBJECT;
} else if (strcmp(request_object_type_str, OIDC_PROTO_REQUEST_URI)
!= 0) {
oidc_error(r, "Bad request_object_type in config: %s",
request_object_type_str);
return;
}
}
/* create request value */
char * value = NULL;
if (strcmp(parameter, OIDC_PROTO_REQUEST_URI) == 0) {
/* parameter is "request_uri" */
value = oidc_proto_create_request_uri(r, provider,
request_object_config, redirect_uri, params);
apr_table_set(params, OIDC_PROTO_REQUEST_URI, value);
} else {
/* parameter is "request" */
value = oidc_proto_create_request_object(r, provider,
request_object_config, params);
apr_table_set(params, OIDC_PROTO_REQUEST_OBJECT, value);
}
}
/* context structure for encoding parameters */
typedef struct oidc_proto_form_post_ctx_t {
request_rec *r;
const char *html_body;
} oidc_proto_form_post_ctx_t;
/*
* add a key/value pair post parameter
*/
static int oidc_proto_add_form_post_param(void* rec, const char* key,
const char* value) {
oidc_proto_form_post_ctx_t *ctx = (oidc_proto_form_post_ctx_t *) rec;
oidc_debug(ctx->r, "processing: %s=%s", key, value);
ctx->html_body = apr_psprintf(ctx->r->pool,
"%s \n",
ctx->html_body, oidc_util_html_escape(ctx->r->pool, key),
oidc_util_html_escape(ctx->r->pool, value));
return 1;
}
/*
* make the browser POST parameters through Javascript auto-submit
*/
static int oidc_proto_html_post(request_rec *r, const char *url,
apr_table_t *params) {
oidc_debug(r, "enter");
const char *html_body = apr_psprintf(r->pool,
"
Submitting Authentication Request...
\n"
" \n");
return oidc_util_html_send(r, "Submitting...", NULL,
"document.forms[0].submit()", html_body, DONE);
}
/*
* send an OpenID Connect authorization request to the specified provider
*/
int oidc_proto_authorization_request(request_rec *r,
struct oidc_provider_t *provider, const char *login_hint,
const char *redirect_uri, const char *state,
oidc_proto_state_t *proto_state, const char *id_token_hint,
const char *code_challenge, const char *auth_request_params,
const char *path_scope) {
/* log some stuff */
oidc_debug(r,
"enter, issuer=%s, redirect_uri=%s, state=%s, proto_state=%s, code_challenge=%s, auth_request_params=%s, path_scope=%s",
provider->issuer, redirect_uri, state,
oidc_proto_state_to_string(r, proto_state), code_challenge,
auth_request_params, path_scope);
int rv = DONE;
char *authorization_request = NULL;
/* assemble parameters to call the token endpoint for validation */
apr_table_t *params = apr_table_make(r->pool, 4);
/* add the response type */
apr_table_setn(params, OIDC_PROTO_RESPONSE_TYPE,
oidc_proto_state_get_response_type(proto_state));
/* concat the per-path scopes with the per-provider scopes */
const char *scope = provider->scope;
if (path_scope != NULL)
scope = ((scope != NULL) && (apr_strnatcmp(scope, "") != 0)) ?
apr_pstrcat(r->pool, scope, OIDC_STR_SPACE, path_scope, NULL) :
path_scope;
if (scope != NULL) {
if (!oidc_util_spaced_string_contains(r->pool, scope,
OIDC_PROTO_SCOPE_OPENID)) {
oidc_warn(r,
"the configuration for the \"%s\" parameter does not include the \"%s\" scope, your provider may not return an \"id_token\": %s",
OIDC_PROTO_SCOPE, OIDC_PROTO_SCOPE_OPENID, provider->scope);
}
apr_table_setn(params, OIDC_PROTO_SCOPE, scope);
}
/* add the client ID */
apr_table_setn(params, OIDC_PROTO_CLIENT_ID, provider->client_id);
/* add the state */
apr_table_setn(params, OIDC_PROTO_STATE, state);
/* add the redirect uri */
apr_table_setn(params, OIDC_PROTO_REDIRECT_URI, redirect_uri);
/* add the nonce if set */
const char *nonce = oidc_proto_state_get_nonce(proto_state);
if (nonce != NULL)
apr_table_setn(params, OIDC_PROTO_NONCE, nonce);
/* add PKCE code challenge if set */
if (code_challenge != NULL) {
apr_table_setn(params, OIDC_PROTO_CODE_CHALLENGE, code_challenge);
apr_table_setn(params, OIDC_PROTO_CODE_CHALLENGE_METHOD,
provider->pkce->method);
}
/* add the response_mode if explicitly set */
const char *response_mode = oidc_proto_state_get_response_mode(proto_state);
if (response_mode != NULL)
apr_table_setn(params, OIDC_PROTO_RESPONSE_MODE, response_mode);
/* add the login_hint if provided */
if (login_hint != NULL)
apr_table_setn(params, OIDC_PROTO_LOGIN_HINT, login_hint);
/* add the id_token_hint if provided */
if (id_token_hint != NULL)
apr_table_setn(params, OIDC_PROTO_ID_TOKEN_HINT, id_token_hint);
/* add the prompt setting if provided (e.g. "none" for no-GUI checks) */
const char *prompt = oidc_proto_state_get_prompt(proto_state);
if (prompt != NULL)
apr_table_setn(params, OIDC_PROTO_PROMPT, prompt);
/* add any statically configured custom authorization request parameters */
if (provider->auth_request_params != NULL)
oidc_util_table_add_query_encoded_params(r->pool, params,
provider->auth_request_params);
/* add any dynamically configured custom authorization request parameters */
if (auth_request_params != NULL)
oidc_util_table_add_query_encoded_params(r->pool, params,
auth_request_params);
/* add request parameter (request or request_uri) if set */
if (provider->request_object != NULL)
oidc_proto_add_request_param(r, provider, redirect_uri, params);
/* send the full authentication request via POST or GET */
if (provider->auth_request_method == OIDC_AUTH_REQUEST_METHOD_POST) {
/* construct a HTML POST auto-submit page with the authorization request parameters */
rv = oidc_proto_html_post(r, provider->authorization_endpoint_url,
params);
} else {
/* construct the full authorization request URL */
authorization_request = oidc_util_http_query_encoded_url(r,
provider->authorization_endpoint_url, params);
// TODO: should also enable this when using the POST binding for the auth request
/* see if we need to preserve POST parameters through Javascript/HTML5 storage */
if (oidc_post_preserve_javascript(r, authorization_request, NULL,
NULL) == FALSE) {
/* add the redirect location header */
oidc_util_hdr_out_location_set(r, authorization_request);
/* and tell Apache to return an HTTP Redirect (302) message */
rv = HTTP_MOVED_TEMPORARILY;
}
}
/* add a referred token binding request for the provider if enabled */
if ((provider->token_binding_policy > OIDC_TOKEN_BINDING_POLICY_DISABLED)
&& (oidc_util_get_provided_token_binding_id(r) != NULL))
oidc_util_hdr_err_out_add(r,
OIDC_HTTP_HDR_INCLUDE_REFERRED_TOKEN_BINDING_ID, "true");
/* cleanup */
oidc_proto_state_destroy(proto_state);
/* log our exit code */
oidc_debug(r, "return: %d", rv);
return rv;
}
/*
* indicate whether the incoming HTTP POST request is an OpenID Connect Authorization Response
*/
apr_byte_t oidc_proto_is_post_authorization_response(request_rec *r,
oidc_cfg *cfg) {
/* prereq: this is a call to the configured redirect_uri; see if it is a POST */
return (r->method_number == M_POST);
}
/*
* indicate whether the incoming HTTP GET request is an OpenID Connect Authorization Response
*/
apr_byte_t oidc_proto_is_redirect_authorization_response(request_rec *r,
oidc_cfg *cfg) {
/* prereq: this is a call to the configured redirect_uri; see if it is a GET with state and id_token or code parameters */
return ((r->method_number == M_GET)
&& oidc_util_request_has_parameter(r, OIDC_PROTO_STATE)
&& (oidc_util_request_has_parameter(r, OIDC_PROTO_ID_TOKEN)
|| oidc_util_request_has_parameter(r, OIDC_PROTO_CODE)));
}
/*
* generate a random value (nonce) to correlate request/response through browser state
*/
apr_byte_t oidc_proto_generate_nonce(request_rec *r, char **nonce, int len) {
return oidc_proto_generate_random_string(r, nonce, len);
}
/*
* PCKE "plain" proto state
*/
static apr_byte_t oidc_proto_pkce_state_plain(request_rec *r, char **state) {
return oidc_proto_generate_random_string(r, state,
OIDC_PROTO_CODE_VERIFIER_LENGTH);
}
/*
* PCKE "plain" code_challenge
*/
static apr_byte_t oidc_proto_pkce_challenge_plain(request_rec *r,
const char *state, char **code_challenge) {
*code_challenge = apr_pstrdup(r->pool, state);
return TRUE;
}
/*
* PCKE "plain" code_verifier
*/
static apr_byte_t oidc_proto_pkce_verifier_plain(request_rec *r,
const char *state, char **code_verifier) {
*code_verifier = apr_pstrdup(r->pool, state);
return TRUE;
}
/*
* PCKE "s256" proto state
*/
static apr_byte_t oidc_proto_pkce_state_s256(request_rec *r, char **state) {
return oidc_proto_generate_random_string(r, state,
OIDC_PROTO_CODE_VERIFIER_LENGTH);
}
/*
* PCKE "s256" code_challenge
*/
static apr_byte_t oidc_proto_pkce_challenge_s256(request_rec *r,
const char *state, char **code_challenge) {
if (oidc_util_hash_string_and_base64url_encode(r, OIDC_JOSE_ALG_SHA256,
state, code_challenge) == FALSE) {
oidc_error(r,
"oidc_util_hash_string_and_base64url_encode returned an error for the code verifier");
return FALSE;
}
return TRUE;
}
/*
* PCKE "s256" code_verifier
*/
static apr_byte_t oidc_proto_pkce_verifier_s256(request_rec *r,
const char *state, char **code_verifier) {
*code_verifier = apr_pstrdup(r->pool, state);
return TRUE;
}
/*
* PCKE "referred_tb" proto state
*/
static apr_byte_t oidc_proto_pkce_state_referred_tb(request_rec *r,
char **state) {
*state = NULL;
return TRUE;
}
/*
* PCKE "referred_tb" code_challenge
*/
static apr_byte_t oidc_proto_pkce_challenge_referred_tb(request_rec *r,
const char *state, char **code_challenge) {
// state should be NULL
*code_challenge = OIDC_PKCE_METHOD_REFERRED_TB;
return TRUE;
}
/*
* PCKE "referred_tb" code_verifier
*/
static apr_byte_t oidc_proto_pkce_verifier_referred_tb(request_rec *r,
const char *state, char **code_verifier) {
const char *tb_id = oidc_util_get_provided_token_binding_id(r);
*code_verifier = tb_id ? apr_pstrdup(r->pool, tb_id) : NULL;
return TRUE;
}
/*
* PKCE plain
*/
oidc_proto_pkce_t oidc_pkce_plain = {
OIDC_PKCE_METHOD_PLAIN,
oidc_proto_pkce_state_plain,
oidc_proto_pkce_verifier_plain,
oidc_proto_pkce_challenge_plain
};
/*
* PKCE s256
*/
oidc_proto_pkce_t oidc_pkce_s256 = {
OIDC_PKCE_METHOD_S256,
oidc_proto_pkce_state_s256,
oidc_proto_pkce_verifier_s256,
oidc_proto_pkce_challenge_s256
};
/*
* PKCE referred_tb
*/
oidc_proto_pkce_t oidc_pkce_referred_tb = {
OIDC_PKCE_METHOD_REFERRED_TB,
oidc_proto_pkce_state_referred_tb,
oidc_proto_pkce_verifier_referred_tb,
oidc_proto_pkce_challenge_referred_tb
};
#define OIDC_PROTO_STATE_ISSUER "i"
#define OIDC_PROTO_STATE_ORIGINAL_URL "ou"
#define OIDC_PROTO_STATE_ORIGINAL_METHOD "om"
#define OIDC_PROTO_STATE_RESPONSE_MODE "rm"
#define OIDC_PROTO_STATE_RESPONSE_TYPE "rt"
#define OIDC_PROTO_STATE_NONCE "n"
#define OIDC_PROTO_STATE_TIMESTAMP "t"
#define OIDC_PROTO_STATE_PROMPT "pr"
#define OIDC_PROTO_STATE_PKCE_STATE "ps"
#define OIDC_PROTO_STATE_STATE "s"
static const char *oidc_proto_state_get_string_value(
oidc_proto_state_t *proto_state, const char *name) {
json_t *v = json_object_get(proto_state, name);
return v ? json_string_value(v) : NULL;
}
static void oidc_proto_state_set_string_value(oidc_proto_state_t *proto_state,
const char *name, const char *value) {
json_object_set_new(proto_state, name, json_string(value));
}
oidc_proto_state_t *oidc_proto_state_new() {
return json_object();
}
void oidc_proto_state_destroy(oidc_proto_state_t *proto_state) {
json_decref(proto_state);
}
oidc_proto_state_t * oidc_proto_state_from_cookie(request_rec *r, oidc_cfg *c,
const char *cookieValue) {
json_t *result = NULL;
oidc_util_jwt_verify(r, c->crypto_passphrase, cookieValue, &result);
return result;
}
char *oidc_proto_state_to_cookie(request_rec *r, oidc_cfg *c,
oidc_proto_state_t *proto_state) {
char *cookieValue = NULL;
oidc_util_jwt_create(r, c->crypto_passphrase, proto_state, &cookieValue);
return cookieValue;
}
char *oidc_proto_state_to_string(request_rec *r,
oidc_proto_state_t *proto_state) {
return oidc_util_encode_json_object(r, proto_state, JSON_COMPACT);
}
const char *oidc_proto_state_get_issuer(oidc_proto_state_t *proto_state) {
return oidc_proto_state_get_string_value(proto_state,
OIDC_PROTO_STATE_ISSUER);
}
const char *oidc_proto_state_get_nonce(oidc_proto_state_t *proto_state) {
return oidc_proto_state_get_string_value(proto_state,
OIDC_PROTO_STATE_NONCE);
}
apr_time_t oidc_proto_state_get_timestamp(oidc_proto_state_t *proto_state) {
json_t *v = json_object_get(proto_state, OIDC_PROTO_STATE_TIMESTAMP);
return v ? apr_time_from_sec(json_integer_value(v)) : -1;
}
const char *oidc_proto_state_get_prompt(oidc_proto_state_t *proto_state) {
return oidc_proto_state_get_string_value(proto_state,
OIDC_PROTO_STATE_PROMPT);
}
const char *oidc_proto_state_get_response_type(oidc_proto_state_t *proto_state) {
return oidc_proto_state_get_string_value(proto_state,
OIDC_PROTO_STATE_RESPONSE_TYPE);
}
const char *oidc_proto_state_get_response_mode(oidc_proto_state_t *proto_state) {
return oidc_proto_state_get_string_value(proto_state,
OIDC_PROTO_STATE_RESPONSE_MODE);
}
const char *oidc_proto_state_get_original_url(oidc_proto_state_t *proto_state) {
return oidc_proto_state_get_string_value(proto_state,
OIDC_PROTO_STATE_ORIGINAL_URL);
}
const char *oidc_proto_state_get_original_method(
oidc_proto_state_t *proto_state) {
return oidc_proto_state_get_string_value(proto_state,
OIDC_PROTO_STATE_ORIGINAL_METHOD);
}
const char *oidc_proto_state_get_state(oidc_proto_state_t *proto_state) {
return oidc_proto_state_get_string_value(proto_state,
OIDC_PROTO_STATE_STATE);
}
const char *oidc_proto_state_get_pkce_state(oidc_proto_state_t *proto_state) {
return oidc_proto_state_get_string_value(proto_state,
OIDC_PROTO_STATE_PKCE_STATE);
}
void oidc_proto_state_set_state(oidc_proto_state_t *proto_state,
const char *state) {
oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_STATE,
state);
}
void oidc_proto_state_set_issuer(oidc_proto_state_t *proto_state,
const char *issuer) {
oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_ISSUER,
issuer);
}
void oidc_proto_state_set_original_url(oidc_proto_state_t *proto_state,
const char *original_url) {
oidc_proto_state_set_string_value(proto_state,
OIDC_PROTO_STATE_ORIGINAL_URL, original_url);
}
void oidc_proto_state_set_original_method(oidc_proto_state_t *proto_state,
const char *original_method) {
oidc_proto_state_set_string_value(proto_state,
OIDC_PROTO_STATE_ORIGINAL_METHOD, original_method);
}
void oidc_proto_state_set_response_mode(oidc_proto_state_t *proto_state,
const char *response_mode) {
oidc_proto_state_set_string_value(proto_state,
OIDC_PROTO_STATE_RESPONSE_MODE, response_mode);
}
void oidc_proto_state_set_response_type(oidc_proto_state_t *proto_state,
const char *response_type) {
oidc_proto_state_set_string_value(proto_state,
OIDC_PROTO_STATE_RESPONSE_TYPE, response_type);
}
void oidc_proto_state_set_nonce(oidc_proto_state_t *proto_state,
const char *nonce) {
oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_NONCE,
nonce);
}
void oidc_proto_state_set_prompt(oidc_proto_state_t *proto_state,
const char *prompt) {
oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_PROMPT,
prompt);
}
void oidc_proto_state_set_pkce_state(oidc_proto_state_t *proto_state,
const char *pkce_state) {
oidc_proto_state_set_string_value(proto_state, OIDC_PROTO_STATE_PKCE_STATE,
pkce_state);
}
void oidc_proto_state_set_timestamp_now(oidc_proto_state_t *proto_state) {
json_object_set_new(proto_state, OIDC_PROTO_STATE_TIMESTAMP,
json_integer(apr_time_sec(apr_time_now())));
}
/*
* if a nonce was passed in the authorization request (and stored in the browser state),
* check that it matches the nonce value in the id_token payload
*/
// non-static for test.c
apr_byte_t oidc_proto_validate_nonce(request_rec *r, oidc_cfg *cfg,
oidc_provider_t *provider, const char *nonce, oidc_jwt_t *jwt) {
oidc_jose_error_t err;
/* see if we have this nonce cached already */
char *replay = NULL;
oidc_cache_get_nonce(r, nonce, &replay);
if (replay != NULL) {
oidc_error(r,
"the nonce value (%s) passed in the browser state was found in the cache already; possible replay attack!?",
nonce);
return FALSE;
}
/* get the "nonce" value in the id_token payload */
char *j_nonce = NULL;
if (oidc_jose_get_string(r->pool, jwt->payload.value.json, OIDC_CLAIM_NONCE,
TRUE, &j_nonce, &err) == FALSE) {
oidc_error(r,
"id_token JSON payload did not contain a \"%s\" string: %s",
OIDC_CLAIM_NONCE, oidc_jose_e2s(r->pool, err));
return FALSE;
}
/* see if the nonce in the id_token matches the one that we sent in the authorization request */
if (apr_strnatcmp(nonce, j_nonce) != 0) {
oidc_error(r,
"the nonce value (%s) in the id_token did not match the one stored in the browser session (%s)",
j_nonce, nonce);
return FALSE;
}
/*
* nonce cache duration (replay prevention window) is the 2x the configured
* slack on the timestamp (+-) for token issuance plus 10 seconds for safety
*/
apr_time_t nonce_cache_duration = apr_time_from_sec(
provider->idtoken_iat_slack * 2 + 10);
/* store it in the cache for the calculated duration */
oidc_cache_set_nonce(r, nonce, nonce,
apr_time_now() + nonce_cache_duration);
oidc_debug(r,
"nonce \"%s\" validated successfully and is now cached for %" APR_TIME_T_FMT " seconds",
nonce, apr_time_sec(nonce_cache_duration));
return TRUE;
}
/*
* validate the "aud" and "azp" claims in the id_token payload
*/
static apr_byte_t oidc_proto_validate_aud_and_azp(request_rec *r, oidc_cfg *cfg,
oidc_provider_t *provider, oidc_jwt_payload_t *id_token_payload) {
char *azp = NULL;
oidc_jose_get_string(r->pool, id_token_payload->value.json, OIDC_CLAIM_AZP,
FALSE, &azp,
NULL);
/*
* the "azp" claim is only needed when the id_token has a single audience value and that audience
* is different than the authorized party; it MAY be included even when the authorized party is
* the same as the sole audience.
*/
if ((azp != NULL) && (apr_strnatcmp(azp, provider->client_id) != 0)) {
oidc_error(r,
"the \"%s\" claim (%s) is present in the id_token, but is not equal to the configured client_id (%s)",
OIDC_CLAIM_AZP, azp, provider->client_id);
return FALSE;
}
/* get the "aud" value from the JSON payload */
json_t *aud = json_object_get(id_token_payload->value.json, OIDC_CLAIM_AUD);
if (aud != NULL) {
/* check if it is a single-value */
if (json_is_string(aud)) {
/* a single-valued audience must be equal to our client_id */
if (apr_strnatcmp(json_string_value(aud), provider->client_id)
!= 0) {
oidc_error(r,
"the configured client_id (%s) did not match the \"%s\" claim value (%s) in the id_token",
provider->client_id, OIDC_CLAIM_AUD,
json_string_value(aud));
return FALSE;
}
/* check if this is a multi-valued audience */
} else if (json_is_array(aud)) {
if ((json_array_size(aud) > 1) && (azp == NULL)) {
oidc_debug(r,
"the \"%s\" claim value in the id_token is an array with more than 1 element, but \"%s\" claim is not present (a SHOULD in the spec...)",
OIDC_CLAIM_AUD, OIDC_CLAIM_AZP);
}
if (oidc_util_json_array_has_value(r, aud,
provider->client_id) == FALSE) {
oidc_error(r,
"our configured client_id (%s) could not be found in the array of values for \"%s\" claim",
provider->client_id, OIDC_CLAIM_AUD);
return FALSE;
}
} else {
oidc_error(r,
"id_token JSON payload \"%s\" claim is not a string nor an array",
OIDC_CLAIM_AUD);
return FALSE;
}
} else {
oidc_error(r, "id_token JSON payload did not contain an \"%s\" claim",
OIDC_CLAIM_AUD);
return FALSE;
}
return TRUE;
}
#define OIDC_CLAIM_CNF "cnf"
#define OIDC_CLAIM_CNF_TBH "tbh"
/*
* validate the "cnf" claims in the id_token payload
*/
static apr_byte_t oidc_proto_validate_cnf(request_rec *r, oidc_cfg *cfg,
oidc_provider_t *provider, oidc_jwt_payload_t *id_token_payload) {
char *tbh_str = NULL;
char *tbh = NULL;
int tbh_len = -1;
const char *tbp_str = NULL;
char *tbp = NULL;
int tbp_len = -1;
unsigned char *tbp_hash = NULL;
unsigned int tbp_hash_len = -1;
oidc_debug(r, "enter: policy=%s",
oidc_token_binding_policy2str(r->pool,
provider->token_binding_policy));
if (provider->token_binding_policy == OIDC_TOKEN_BINDING_POLICY_DISABLED)
return TRUE;
tbp_str = oidc_util_get_provided_token_binding_id(r);
if (tbp_str == NULL) {
oidc_debug(r,
"no Provided Token Binding ID environment variable found");
goto out_err;
}
tbp_len = oidc_base64url_decode(r->pool, &tbp, tbp_str);
if (tbp_len <= 0) {
oidc_warn(r,
"Provided Token Binding ID environment variable could not be decoded");
return FALSE;
}
if (oidc_jose_hash_bytes(r->pool, OIDC_JOSE_ALG_SHA256,
(const unsigned char *) tbp, tbp_len, &tbp_hash, &tbp_hash_len,
NULL) == FALSE) {
oidc_warn(r,
"hashing Provided Token Binding ID environment variable failed");
return FALSE;
}
json_t *cnf = json_object_get(id_token_payload->value.json, OIDC_CLAIM_CNF);
if (cnf == NULL) {
oidc_debug(r, "no \"cnf\" claim found in id_token");
goto out_err;
}
oidc_jose_get_string(r->pool, cnf, OIDC_CLAIM_CNF_TBH, FALSE, &tbh_str,
NULL);
if (tbh_str == NULL) {
oidc_debug(r,
" \"cnf\" claim found in id_token but no \"tbh\" claim inside found");
goto out_err;
}
tbh_len = oidc_base64url_decode(r->pool, &tbh, tbh_str);
if (tbh_len <= 0) {
oidc_warn(r, "cnf[\"tbh\"] provided but it could not be decoded");
return FALSE;
}
if (tbp_hash_len != tbh_len) {
oidc_warn(r,
"hash length of provided token binding ID environment variable: %d does not match length of cnf[\"tbh\"]: %d",
tbp_hash_len, tbh_len);
return FALSE;
}
if (memcmp(tbp_hash, tbh, tbh_len) != 0) {
oidc_warn(r,
"hash of provided token binding ID environment variable does not match cnf[\"tbh\"]");
return FALSE;
}
oidc_debug(r,
"hash of provided token binding ID environment variable matches cnf[\"tbh\"]");
return TRUE;
out_err:
if (provider->token_binding_policy == OIDC_TOKEN_BINDING_POLICY_OPTIONAL)
return TRUE;
if (provider->token_binding_policy == OIDC_TOKEN_BINDING_POLICY_ENFORCED)
return FALSE;
// provider->token_binding_policy == OIDC_TOKEN_BINDING_POLICY_REQURIED
return (tbp_str == NULL);
}
/*
* validate "iat" claim in JWT
*/
static apr_byte_t oidc_proto_validate_iat(request_rec *r, oidc_jwt_t *jwt,
apr_byte_t is_mandatory, int slack) {
/* get the current time */
apr_time_t now = apr_time_sec(apr_time_now());
/* sanity check for iat being set */
if (jwt->payload.iat == OIDC_JWT_CLAIM_TIME_EMPTY) {
if (is_mandatory) {
oidc_error(r, "JWT did not contain an \"%s\" number value",
OIDC_CLAIM_IAT);
return FALSE;
}
return TRUE;
}
/* see if we are asked to enforce a time window at all */
if (slack < 0) {
oidc_debug(r, "slack for JWT set < 0, do not enforce boundary check");
return TRUE;
}
/* check if this id_token has been issued just now +- slack (default 10 minutes) */
if ((now - slack) > jwt->payload.iat) {
oidc_error(r,
"\"iat\" validation failure (%ld): JWT was issued more than %d seconds ago",
(long )jwt->payload.iat, slack);
return FALSE;
}
if ((now + slack) < jwt->payload.iat) {
oidc_error(r,
"\"iat\" validation failure (%ld): JWT was issued more than %d seconds in the future",
(long )jwt->payload.iat, slack);
return FALSE;
}
return TRUE;
}
/*
* validate "exp" claim in JWT
*/
static apr_byte_t oidc_proto_validate_exp(request_rec *r, oidc_jwt_t *jwt,
apr_byte_t is_mandatory) {
/* get the current time */
apr_time_t now = apr_time_sec(apr_time_now());
/* sanity check for exp being set */
if (jwt->payload.exp == OIDC_JWT_CLAIM_TIME_EMPTY) {
if (is_mandatory) {
oidc_error(r, "JWT did not contain an \"%s\" number value",
OIDC_CLAIM_EXP);
return FALSE;
}
return TRUE;
}
/* see if now is beyond the JWT expiry timestamp */
apr_time_t expires = jwt->payload.exp;
if (now > expires) {
oidc_error(r,
"\"exp\" validation failure (%ld): JWT expired %ld seconds ago",
(long )expires, (long )(now - expires));
return FALSE;
}
return TRUE;
}
/*
* validate a JSON Web token
*/
apr_byte_t oidc_proto_validate_jwt(request_rec *r, oidc_jwt_t *jwt,
const char *iss, apr_byte_t exp_is_mandatory,
apr_byte_t iat_is_mandatory, int iat_slack) {
if (iss != NULL) {
/* issuer is set and must match */
if (jwt->payload.iss == NULL) {
oidc_error(r,
"JWT did not contain an \"%s\" string (requested value: %s)",
OIDC_CLAIM_ISS, iss);
return FALSE;
}
/* check if the issuer matches the requested value */
if (oidc_util_issuer_match(iss, jwt->payload.iss) == FALSE) {
oidc_error(r,
"requested issuer (%s) does not match received \"%s\" value in id_token (%s)",
iss, OIDC_CLAIM_ISS, jwt->payload.iss);
return FALSE;
}
}
/* check exp */
if (oidc_proto_validate_exp(r, jwt, exp_is_mandatory) == FALSE)
return FALSE;
/* check iat */
if (oidc_proto_validate_iat(r, jwt, iat_is_mandatory, iat_slack) == FALSE)
return FALSE;
return TRUE;
}
/*
* check whether the provided JWT is a valid id_token for the specified "provider"
*/
static apr_byte_t oidc_proto_validate_idtoken(request_rec *r,
oidc_provider_t *provider, oidc_jwt_t *jwt, const char *nonce) {
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
oidc_debug(r, "enter, jwt.header=\"%s\", jwt.payload=\"%s\", nonce=\"%s\"",
jwt->header.value.str, jwt->payload.value.str, nonce);
/* if a nonce is not passed, we're doing a ("code") flow where the nonce is optional */
if (nonce != NULL) {
/* if present, verify the nonce */
if (oidc_proto_validate_nonce(r, cfg, provider, nonce, jwt) == FALSE)
return FALSE;
}
/* validate the ID Token JWT, requiring iss match, and valid exp + iat */
if (oidc_proto_validate_jwt(r, jwt, provider->issuer, TRUE, TRUE,
provider->idtoken_iat_slack) == FALSE)
return FALSE;
/* check if the required-by-spec "sub" claim is present */
if (jwt->payload.sub == NULL) {
oidc_error(r,
"id_token JSON payload did not contain the required-by-spec \"%s\" string value",
OIDC_CLAIM_SUB);
return FALSE;
}
/* verify the "aud" and "azp" values */
if (oidc_proto_validate_aud_and_azp(r, cfg, provider,
&jwt->payload) == FALSE)
return FALSE;
/* verify the included token binding ID if provided */
if (oidc_proto_validate_cnf(r, cfg, provider, &jwt->payload) == FALSE)
return FALSE;
return TRUE;
}
/*
* get the key from the JWKs that corresponds with the key specified in the header
*/
static apr_byte_t oidc_proto_get_key_from_jwks(request_rec *r, oidc_jwt_t *jwt,
json_t *j_jwks, apr_hash_t *result) {
apr_byte_t rc = TRUE;
oidc_jwk_t *jwk = NULL;
oidc_jose_error_t err;
char *jwk_json = NULL;
/* get the (optional) thumbprint for comparison */
const char *x5t = oidc_jwt_hdr_get(jwt, OIDC_JWK_X5T);
oidc_debug(r, "search for kid \"%s\" or thumbprint x5t \"%s\"",
jwt->header.kid, x5t);
/* get the "keys" JSON array from the JWKs object */
json_t *keys = json_object_get(j_jwks, OIDC_JWK_KEYS);
if ((keys == NULL) || !(json_is_array(keys))) {
oidc_error(r, "\"%s\" array element is not a JSON array",
OIDC_JWK_KEYS);
return FALSE;
}
int i;
for (i = 0; i < json_array_size(keys); i++) {
/* get the next element in the array */
json_t *elem = json_array_get(keys, i);
if (oidc_jwk_parse_json(r->pool, elem, &jwk, &err) == FALSE) {
oidc_warn(r, "oidc_jwk_parse_json failed: %s",
oidc_jose_e2s(r->pool, err));
continue;
}
/* get the key type and see if it is the type that we are looking for */
if (oidc_jwt_alg2kty(jwt) != jwk->kty) {
oidc_debug(r,
"skipping non matching kty=%d for kid=%s because it doesn't match requested kty=%d, kid=%s",
jwk->kty, jwk->kid, oidc_jwt_alg2kty(jwt), jwt->header.kid);
oidc_jwk_destroy(jwk);
continue;
}
/* see if we were looking for a specific kid, if not we'll include any key that matches the type */
if ((jwt->header.kid == NULL) && (x5t == NULL)) {
const char *use = json_string_value(
json_object_get(elem, OIDC_JWK_USE));
if ((use != NULL) && (strcmp(use, OIDC_JWK_SIG) != 0)) {
oidc_debug(r,
"skipping key because of non-matching \"%s\": \"%s\"",
OIDC_JWK_USE, use);
oidc_jwk_destroy(jwk);
} else {
oidc_jwk_to_json(r->pool, jwk, &jwk_json, &err);
oidc_debug(r,
"no kid/x5t to match, include matching key type: %s",
jwk_json);
if (jwk->kid != NULL)
apr_hash_set(result, jwk->kid, APR_HASH_KEY_STRING, jwk);
else
// can do this because we never remove anything from the list
apr_hash_set(result,
apr_psprintf(r->pool, "%d", apr_hash_count(result)),
APR_HASH_KEY_STRING, jwk);
}
continue;
}
/* we are looking for a specific kid, get the kid from the current element */
/* compare the requested kid against the current element */
if ((jwt->header.kid != NULL) && (jwk->kid != NULL)
&& (apr_strnatcmp(jwt->header.kid, jwk->kid) == 0)) {
oidc_jwk_to_json(r->pool, jwk, &jwk_json, &err);
oidc_debug(r, "found matching kid: \"%s\" for jwk: %s",
jwt->header.kid, jwk_json);
apr_hash_set(result, jwt->header.kid, APR_HASH_KEY_STRING, jwk);
break;
}
/* we are looking for a specific x5t, get the x5t from the current element */
char *s_x5t = NULL;
oidc_json_object_get_string(r->pool, elem, OIDC_JWK_X5T, &s_x5t, NULL);
/* compare the requested thumbprint against the current element */
if ((s_x5t != NULL) && (x5t != NULL)
&& (apr_strnatcmp(x5t, s_x5t) == 0)) {
oidc_jwk_to_json(r->pool, jwk, &jwk_json, &err);
oidc_debug(r, "found matching %s: \"%s\" for jwk: %s", OIDC_JWK_X5T,
x5t, jwk_json);
apr_hash_set(result, x5t, APR_HASH_KEY_STRING, jwk);
break;
}
/* the right key type but no matching kid/x5t */
oidc_jwk_destroy(jwk);
}
return rc;
}
/*
* get the keys from the (possibly cached) set of JWKs on the jwk_uri that corresponds with the key specified in the header
*/
apr_byte_t oidc_proto_get_keys_from_jwks_uri(request_rec *r, oidc_cfg *cfg,
oidc_jwt_t *jwt, const oidc_jwks_uri_t *jwks_uri, apr_hash_t *keys,
apr_byte_t *force_refresh) {
json_t *j_jwks = NULL;
/* get the set of JSON Web Keys for this provider (possibly by downloading them from the specified provider->jwk_uri) */
oidc_metadata_jwks_get(r, cfg, jwks_uri, &j_jwks, force_refresh);
if (j_jwks == NULL) {
oidc_error(r, "could not %s JSON Web Keys",
*force_refresh ? "refresh" : "get");
return FALSE;
}
/*
* get the key corresponding to the kid from the header, referencing the key that
* was used to sign this message (or get all keys in case no kid was set)
*
* we don't check the error return value because we'll treat "error" in the same
* way as "key not found" i.e. by refreshing the keys from the JWKs URI if not
* already done
*/
oidc_proto_get_key_from_jwks(r, jwt, j_jwks, keys);
/* no need anymore for the parsed json_t contents, release the it */
json_decref(j_jwks);
/* if we've got no keys and we did not do a fresh download, then the cache may be stale */
if ((apr_hash_count(keys) < 1) && (*force_refresh == FALSE)) {
/* we did not get a key, but we have not refreshed the JWKs from the jwks_uri yet */
oidc_warn(r,
"could not find a key in the cached JSON Web Keys, doing a forced refresh in case keys were rolled over");
/* get the set of JSON Web Keys forcing a fresh download from the specified JWKs URI */
*force_refresh = TRUE;
return oidc_proto_get_keys_from_jwks_uri(r, cfg, jwt, jwks_uri, keys,
force_refresh);
}
oidc_debug(r,
"returning %d key(s) obtained from the (possibly cached) JWKs URI",
apr_hash_count(keys));
return TRUE;
}
/*
* verify the signature on a JWT using the dynamically obtained and statically configured keys
*/
apr_byte_t oidc_proto_jwt_verify(request_rec *r, oidc_cfg *cfg, oidc_jwt_t *jwt,
const oidc_jwks_uri_t *jwks_uri, apr_hash_t *static_keys) {
oidc_jose_error_t err;
apr_hash_t *dynamic_keys = apr_hash_make(r->pool);
/* see if we've got a JWKs URI set for signature validation with dynamically obtained asymmetric keys */
if (jwks_uri->url == NULL) {
oidc_debug(r,
"\"jwks_uri\" is not set, signature validation will only be performed against statically configured keys");
/* the JWKs URI was provided, but let's see if it makes sense to pull down keys, i.e. if it is an asymmetric signature */
} /*else if (oidc_jose_signature_is_hmac(r->pool, jwt)) {
oidc_debug(r,
"\"jwks_uri\" is set, but the JWT has a symmetric signature so we won't pull/use keys from there");
} */else {
apr_byte_t force_refresh = FALSE;
/* get the key from the JWKs that corresponds with the key specified in the header */
if (oidc_proto_get_keys_from_jwks_uri(r, cfg, jwt, jwks_uri,
dynamic_keys, &force_refresh) == FALSE) {
oidc_jwk_list_destroy(r->pool, dynamic_keys);
return FALSE;
}
}
/* do the actual JWS verification with the locally and remotely provided key material */
// TODO: now static keys "win" if the same `kid` was used in both local and remote key sets
if (oidc_jwt_verify(r->pool, jwt,
oidc_util_merge_key_sets(r->pool, static_keys, dynamic_keys),
&err) == FALSE) {
oidc_error(r, "JWT signature verification failed: %s",
oidc_jose_e2s(r->pool, err));
oidc_jwk_list_destroy(r->pool, dynamic_keys);
return FALSE;
}
oidc_debug(r,
"JWT signature verification with algorithm \"%s\" was successful",
jwt->header.alg);
oidc_jwk_list_destroy(r->pool, dynamic_keys);
return TRUE;
}
/*
* return the compact-encoded JWT header contents
*/
char *oidc_proto_peek_jwt_header(request_rec *r,
const char *compact_encoded_jwt, char **alg) {
char *input = NULL, *result = NULL;
char *p = strstr(compact_encoded_jwt ? compact_encoded_jwt : "", ".");
if (p == NULL) {
oidc_warn(r,
"could not parse first element separated by \".\" from input");
return NULL;
}
input = apr_pstrmemdup(r->pool, compact_encoded_jwt,
strlen(compact_encoded_jwt) - strlen(p));
if (oidc_base64url_decode(r->pool, &result, input) <= 0) {
oidc_warn(r, "oidc_base64url_decode returned an error");
return NULL;
}
if (alg) {
json_t *json = NULL;
oidc_util_decode_json_object(r, result, &json);
if (json)
*alg = apr_pstrdup(r->pool,
json_string_value(json_object_get(json, CJOSE_HDR_ALG)));
json_decref(json);
}
return result;
}
/*
* check whether the provided string is a valid id_token and return its parsed contents
*/
apr_byte_t oidc_proto_parse_idtoken(request_rec *r, oidc_cfg *cfg,
oidc_provider_t *provider, const char *id_token, const char *nonce,
oidc_jwt_t **jwt, apr_byte_t is_code_flow) {
char *alg = NULL;
oidc_debug(r, "enter: id_token header=%s",
oidc_proto_peek_jwt_header(r, id_token, &alg));
char buf[APR_RFC822_DATE_LEN + 1];
oidc_jose_error_t err;
oidc_jwk_t *jwk = NULL;
if (oidc_util_create_symmetric_key(r, provider->client_secret,
oidc_alg2keysize(alg), OIDC_JOSE_ALG_SHA256,
TRUE, &jwk) == FALSE)
return FALSE;
if (oidc_jwt_parse(r->pool, id_token, jwt,
oidc_util_merge_symmetric_key(r->pool, cfg->private_keys, jwk),
&err) == FALSE) {
oidc_error(r, "oidc_jwt_parse failed: %s", oidc_jose_e2s(r->pool, err));
oidc_jwt_destroy(*jwt);
*jwt = NULL;
return FALSE;
}
oidc_jwk_destroy(jwk);
oidc_debug(r,
"successfully parsed (and possibly decrypted) JWT with header=%s, and payload=%s",
(*jwt)->header.value.str, (*jwt)->payload.value.str);
// make signature validation exception for 'code' flow and the algorithm NONE
if (is_code_flow == FALSE || strcmp((*jwt)->header.alg, "none") != 0) {
jwk = NULL;
if (oidc_util_create_symmetric_key(r, provider->client_secret, 0,
NULL, TRUE, &jwk) == FALSE)
return FALSE;
oidc_jwks_uri_t jwks_uri = { provider->jwks_uri,
provider->jwks_refresh_interval, provider->ssl_validate_server };
if (oidc_proto_jwt_verify(r, cfg, *jwt, &jwks_uri,
oidc_util_merge_symmetric_key(r->pool, NULL, jwk)) == FALSE) {
oidc_error(r,
"id_token signature could not be validated, aborting");
oidc_jwt_destroy(*jwt);
*jwt = NULL;
oidc_jwk_destroy(jwk);
return FALSE;
}
oidc_jwk_destroy(jwk);
}
/* this is where the meat is */
if (oidc_proto_validate_idtoken(r, provider, *jwt, nonce) == FALSE) {
oidc_error(r, "id_token payload could not be validated, aborting");
oidc_jwt_destroy(*jwt);
*jwt = NULL;
return FALSE;
}
/* log our results */
apr_rfc822_date(buf, apr_time_from_sec((*jwt)->payload.exp));
oidc_debug(r,
"valid id_token for user \"%s\" expires: [%s], in %ld secs from now)",
(*jwt)->payload.sub, buf,
(long)((*jwt)->payload.exp - apr_time_sec(apr_time_now())));
/* since we've made it so far, we may as well say it is a valid id_token */
return TRUE;
}
/*
* check that the access_token type is supported
*/
static apr_byte_t oidc_proto_validate_token_type(request_rec *r,
oidc_provider_t *provider, const char *token_type) {
/* we only support bearer/Bearer */
if ((token_type != NULL)
&& (apr_strnatcasecmp(token_type, OIDC_PROTO_BEARER) != 0)
&& (provider->userinfo_endpoint_url != NULL)) {
oidc_error(r,
"token_type is \"%s\" and UserInfo endpoint (%s) for issuer \"%s\" is set: can only deal with \"%s\" authentication against a UserInfo endpoint!",
token_type, provider->userinfo_endpoint_url, provider->issuer,
OIDC_PROTO_BEARER);
return FALSE;
}
return TRUE;
}
/*
* setup for an endpoint call without authentication
*/
static apr_byte_t oidc_proto_endpoint_auth_none(request_rec *r,
const char *client_id, apr_table_t *params) {
oidc_debug(r,
"no client secret is configured; calling the token endpoint without client authentication; only public clients are supported");
apr_table_set(params, OIDC_PROTO_CLIENT_ID, client_id);
return TRUE;
}
/*
* setup for an endpoint call with HTTP Basic authentication
*/
static apr_byte_t oidc_proto_endpoint_auth_basic(request_rec *r,
const char *client_id, const char *client_secret, char **basic_auth_str) {
oidc_debug(r, "enter");
if (client_secret == NULL) {
oidc_error(r, "no client secret is configured");
return FALSE;
}
*basic_auth_str = apr_psprintf(r->pool, "%s:%s", client_id, client_secret);
return TRUE;
}
/*
* setup for an endpoint call with authentication in POST parameters
*/
static apr_byte_t oidc_proto_endpoint_auth_post(request_rec *r,
const char *client_id, const char *client_secret, apr_table_t *params) {
oidc_debug(r, "enter");
if (client_secret == NULL) {
oidc_error(r, "no client secret is configured");
return FALSE;
}
apr_table_set(params, OIDC_PROTO_CLIENT_ID, client_id);
apr_table_set(params, OIDC_PROTO_CLIENT_SECRET, client_secret);
return TRUE;
}
#define OIDC_PROTO_ASSERTION_JTI_LEN 16
/*
* helper function to create a JWT assertion for endpoint authentication
*/
static apr_byte_t oidc_proto_jwt_create(request_rec *r, const char *client_id,
const char *audience, oidc_jwt_t **out) {
*out = oidc_jwt_new(r->pool, TRUE, TRUE);
oidc_jwt_t *jwt = *out;
char *jti = NULL;
oidc_proto_generate_random_string(r, &jti, OIDC_PROTO_ASSERTION_JTI_LEN);
json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_ISS,
json_string(client_id));
json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_SUB,
json_string(client_id));
json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_AUD,
json_string(audience));
json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_JTI,
json_string(jti));
json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_EXP,
json_integer(apr_time_sec(apr_time_now()) + 60));
json_object_set_new(jwt->payload.value.json, OIDC_CLAIM_IAT,
json_integer(apr_time_sec(apr_time_now())));
return TRUE;
}
/*
* helper function to add a JWT assertion to the HTTP request as endpoint authentication
*/
static apr_byte_t oidc_proto_jwt_sign_and_add(request_rec *r,
apr_table_t *params, oidc_jwt_t *jwt, oidc_jwk_t *jwk) {
oidc_jose_error_t err;
if (oidc_jwt_sign(r->pool, jwt, jwk, &err) == FALSE) {
oidc_error(r, "signing JWT failed: %s", oidc_jose_e2s(r->pool, err));
return FALSE;
}
char *cser = oidc_jwt_serialize(r->pool, jwt, &err);
if (cser == NULL) {
oidc_error(r, "oidc_jwt_serialize failed: %s",
oidc_jose_e2s(r->pool, err));
return FALSE;
}
apr_table_setn(params, OIDC_PROTO_CLIENT_ASSERTION_TYPE,
OIDC_PROTO_CLIENT_ASSERTION_TYPE_JWT_BEARER);
apr_table_set(params, OIDC_PROTO_CLIENT_ASSERTION, cser);
return TRUE;
}
#define OIDC_PROTO_JWT_ASSERTION_SYMMETRIC_ALG CJOSE_HDR_ALG_HS256
static apr_byte_t oidc_proto_endpoint_auth_client_secret_jwt(request_rec *r,
const char *client_id, const char *client_secret, const char *audience,
apr_table_t *params) {
oidc_jwt_t *jwt = NULL;
oidc_jose_error_t err;
oidc_debug(r, "enter");
if (oidc_proto_jwt_create(r, client_id, audience, &jwt) == FALSE)
return FALSE;
oidc_jwk_t *jwk = oidc_jwk_create_symmetric_key(r->pool, NULL,
(const unsigned char *) client_secret, strlen(client_secret), FALSE,
&err);
if (jwk == NULL) {
oidc_error(r, "parsing of client secret into JWK failed: %s",
oidc_jose_e2s(r->pool, err));
oidc_jwt_destroy(jwt);
return FALSE;
}
jwt->header.alg = apr_pstrdup(r->pool,
OIDC_PROTO_JWT_ASSERTION_SYMMETRIC_ALG);
oidc_proto_jwt_sign_and_add(r, params, jwt, jwk);
oidc_jwt_destroy(jwt);
oidc_jwk_destroy(jwk);
return TRUE;
}
static apr_byte_t oidc_proto_endpoint_access_token_bearer(request_rec *r,
oidc_cfg *cfg, apr_table_t *params, char **bearer_auth_str) {
const char *token =
strcmp(cfg->oauth.introspection_client_auth_bearer_token, "") == 0 ?
apr_table_get(params,
cfg->oauth.introspection_token_param_name) :
cfg->oauth.introspection_client_auth_bearer_token;
*bearer_auth_str = apr_psprintf(r->pool, "%s", token);
return TRUE;
}
#define OIDC_PROTO_JWT_ASSERTION_ASYMMETRIC_ALG CJOSE_HDR_ALG_RS256
static apr_byte_t oidc_proto_endpoint_auth_private_key_jwt(request_rec *r,
oidc_cfg *cfg, const char *client_id, const char *audience,
apr_table_t *params) {
oidc_jwt_t *jwt = NULL;
oidc_jwk_t *jwk = NULL;
oidc_debug(r, "enter");
if (oidc_proto_jwt_create(r, client_id, audience, &jwt) == FALSE)
return FALSE;
if (cfg->private_keys == NULL) {
oidc_error(r,
"no private keys have been configured to use for private_key_jwt client authentication (" OIDCPrivateKeyFiles ")");
oidc_jwt_destroy(jwt);
return FALSE;
}
apr_ssize_t klen = 0;
apr_hash_index_t *hi = apr_hash_first(r->pool, cfg->private_keys);
apr_hash_this(hi, (const void **) &jwt->header.kid, &klen, (void **) &jwk);
jwt->header.alg = apr_pstrdup(r->pool, CJOSE_HDR_ALG_RS256);
oidc_proto_jwt_sign_and_add(r, params, jwt, jwk);
oidc_jwt_destroy(jwt);
return TRUE;
}
apr_byte_t oidc_proto_token_endpoint_auth(request_rec *r, oidc_cfg *cfg,
const char *token_endpoint_auth, const char *client_id,
const char *client_secret, const char *audience, apr_table_t *params,
char **basic_auth_str, char **bearer_auth_str) {
if (cfg->oauth.introspection_client_auth_bearer_token != NULL)
return oidc_proto_endpoint_access_token_bearer(r, cfg, params,
bearer_auth_str);
oidc_debug(r, "token_endpoint_auth=%s", token_endpoint_auth);
if (client_id == NULL) {
oidc_debug(r, "no client ID set: assume we don't need to authenticate");
return TRUE;
}
// default is client_secret_basic, but only if a client_secret is set,
// otherwise we are a public client
if ((token_endpoint_auth == NULL) && (client_secret != NULL))
token_endpoint_auth = OIDC_PROTO_CLIENT_SECRET_BASIC;
if ((token_endpoint_auth == NULL) || (apr_strnatcmp(token_endpoint_auth,
OIDC_PROTO_ENDPOINT_AUTH_NONE) == 0))
return oidc_proto_endpoint_auth_none(r, client_id, params);
// if no client_secret is set and we don't authenticate using private_key_jwt,
// we can only be a public client since the other methods require a client_secret
if ((client_secret == NULL) && (apr_strnatcmp(token_endpoint_auth,
OIDC_PROTO_PRIVATE_KEY_JWT) != 0)) {
oidc_debug(r,
"no client secret set and not using private_key_jwt, assume we are a public client");
return oidc_proto_endpoint_auth_none(r, client_id, params);
}
if (apr_strnatcmp(token_endpoint_auth,
OIDC_PROTO_CLIENT_SECRET_BASIC) == 0)
return oidc_proto_endpoint_auth_basic(r, client_id, client_secret,
basic_auth_str);
if (apr_strnatcmp(token_endpoint_auth,
OIDC_PROTO_CLIENT_SECRET_POST) == 0)
return oidc_proto_endpoint_auth_post(r, client_id, client_secret,
params);
if (apr_strnatcmp(token_endpoint_auth,
OIDC_PROTO_CLIENT_SECRET_JWT) == 0)
return oidc_proto_endpoint_auth_client_secret_jwt(r, client_id,
client_secret, audience, params);
if (apr_strnatcmp(token_endpoint_auth,
OIDC_PROTO_PRIVATE_KEY_JWT) == 0)
return oidc_proto_endpoint_auth_private_key_jwt(r, cfg, client_id,
audience, params);
oidc_error(r, "uhm, shouldn't be here...");
return FALSE;
}
/*
* send a code/refresh request to the token endpoint and return the parsed contents
*/
static apr_byte_t oidc_proto_token_endpoint_request(request_rec *r,
oidc_cfg *cfg, oidc_provider_t *provider, apr_table_t *params,
char **id_token, char **access_token, char **token_type,
int *expires_in, char **refresh_token) {
char *response = NULL;
char *basic_auth = NULL;
char *bearer_auth = NULL;
/* add the token endpoint authentication credentials */
if (oidc_proto_token_endpoint_auth(r, cfg, provider->token_endpoint_auth,
provider->client_id, provider->client_secret,
provider->token_endpoint_url, params, &basic_auth,
&bearer_auth) == FALSE)
return FALSE;
/* add any configured extra static parameters to the token endpoint */
oidc_util_table_add_query_encoded_params(r->pool, params,
provider->token_endpoint_params);
/* send the refresh request to the token endpoint */
if (oidc_util_http_post_form(r, provider->token_endpoint_url, params,
basic_auth, bearer_auth, provider->ssl_validate_server, &response,
cfg->http_timeout_long, cfg->outgoing_proxy,
oidc_dir_cfg_pass_cookies(r),
oidc_util_get_full_path(r->pool,
provider->token_endpoint_tls_client_cert),
oidc_util_get_full_path(r->pool,
provider->token_endpoint_tls_client_key)) == FALSE) {
oidc_warn(r, "error when calling the token endpoint (%s)",
provider->token_endpoint_url);
return FALSE;
}
/* check for errors, the response itself will have been logged already */
json_t *result = NULL;
if (oidc_util_decode_json_and_check_error(r, response, &result) == FALSE)
return FALSE;
/* get the id_token from the parsed response */
oidc_json_object_get_string(r->pool, result, OIDC_PROTO_ID_TOKEN, id_token,
NULL);
/* get the access_token from the parsed response */
oidc_json_object_get_string(r->pool, result, OIDC_PROTO_ACCESS_TOKEN,
access_token,
NULL);
/* get the token type from the parsed response */
oidc_json_object_get_string(r->pool, result, OIDC_PROTO_TOKEN_TYPE,
token_type,
NULL);
/* check the new token type */
if (token_type != NULL) {
if (oidc_proto_validate_token_type(r, provider, *token_type) == FALSE) {
oidc_warn(r, "access token type did not validate, dropping it");
*access_token = NULL;
}
}
/* get the expires_in value */
oidc_json_object_get_int(r->pool, result, OIDC_PROTO_EXPIRES_IN, expires_in,
-1);
/* get the refresh_token from the parsed response */
oidc_json_object_get_string(r->pool, result, OIDC_PROTO_REFRESH_TOKEN,
refresh_token,
NULL);
json_decref(result);
return TRUE;
}
/*
* resolves the code received from the OP in to an id_token, access_token and refresh_token
*/
static apr_byte_t oidc_proto_resolve_code(request_rec *r, oidc_cfg *cfg,
oidc_provider_t *provider, const char *code, const char *code_verifier,
char **id_token, char **access_token, char **token_type,
int *expires_in, char **refresh_token, const char *state) {
oidc_debug(r, "enter");
/* assemble the parameters for a call to the token endpoint */
apr_table_t *params = apr_table_make(r->pool, 5);
apr_table_setn(params, OIDC_PROTO_GRANT_TYPE,
OIDC_PROTO_GRANT_TYPE_AUTHZ_CODE);
apr_table_setn(params, OIDC_PROTO_CODE, code);
apr_table_set(params, OIDC_PROTO_REDIRECT_URI,
oidc_get_redirect_uri_iss(r, cfg, provider));
if (code_verifier)
apr_table_setn(params, OIDC_PROTO_CODE_VERIFIER, code_verifier);
if (state)
apr_table_setn(params, OIDC_PROTO_STATE, state);
return oidc_proto_token_endpoint_request(r, cfg, provider, params, id_token,
access_token, token_type, expires_in, refresh_token);
}
/*
* refreshes the access_token/id_token /refresh_token received from the OP using the refresh_token
*/
apr_byte_t oidc_proto_refresh_request(request_rec *r, oidc_cfg *cfg,
oidc_provider_t *provider, const char *rtoken, char **id_token,
char **access_token, char **token_type, int *expires_in,
char **refresh_token) {
oidc_debug(r, "enter");
/* assemble the parameters for a call to the token endpoint */
apr_table_t *params = apr_table_make(r->pool, 5);
apr_table_setn(params, OIDC_PROTO_GRANT_TYPE,
OIDC_PROTO_GRANT_TYPE_REFRESH_TOKEN);
apr_table_setn(params, OIDC_PROTO_REFRESH_TOKEN, rtoken);
apr_table_setn(params, OIDC_PROTO_SCOPE, provider->scope);
return oidc_proto_token_endpoint_request(r, cfg, provider, params, id_token,
access_token, token_type, expires_in, refresh_token);
}
static apr_byte_t oidc_user_info_response_validate(request_rec *r,
oidc_cfg *cfg, oidc_provider_t *provider, char **response,
json_t **claims, char **userinfo_jwt) {
oidc_debug(r,
"enter: userinfo_signed_response_alg=%s, userinfo_encrypted_response_alg=%s, userinfo_encrypted_response_enc=%s",
provider->userinfo_signed_response_alg,
provider->userinfo_encrypted_response_alg,
provider->userinfo_encrypted_response_enc);
char *alg = NULL;
if ((provider->userinfo_signed_response_alg != NULL)
|| (provider->userinfo_encrypted_response_alg != NULL)
|| (provider->userinfo_encrypted_response_enc != NULL)) {
oidc_debug(r, "JWT header=%s",
oidc_proto_peek_jwt_header(r, *response, &alg));
}
oidc_jose_error_t err;
oidc_jwk_t *jwk = NULL;
oidc_jwt_t *jwt = NULL;
char *payload = NULL;
if (oidc_util_create_symmetric_key(r, provider->client_secret,
oidc_alg2keysize(alg), OIDC_JOSE_ALG_SHA256,
TRUE, &jwk) == FALSE)
return FALSE;
if (provider->userinfo_encrypted_response_alg != NULL) {
if (oidc_jwe_decrypt(r->pool, *response,
oidc_util_merge_symmetric_key(r->pool, cfg->private_keys, jwk),
&payload, &err, TRUE) == FALSE) {
oidc_error(r, "oidc_jwe_decrypt failed: %s",
oidc_jose_e2s(r->pool, err));
oidc_jwk_destroy(jwk);
return FALSE;
} else {
oidc_debug(r,
"successfully decrypted JWE returned from userinfo endpoint: %s",
payload);
*response = payload;
}
}
if (provider->userinfo_signed_response_alg != NULL) {
if (oidc_jwt_parse(r->pool, *response, &jwt,
oidc_util_merge_symmetric_key(r->pool, cfg->private_keys, jwk),
&err) == FALSE) {
oidc_error(r, "oidc_jwt_parse failed: %s",
oidc_jose_e2s(r->pool, err));
oidc_jwt_destroy(jwt);
oidc_jwk_destroy(jwk);
return FALSE;
}
oidc_debug(r, "successfully parsed JWT with header=%s, and payload=%s",
jwt->header.value.str, jwt->payload.value.str);
oidc_jwk_destroy(jwk);
jwk = NULL;
if (oidc_util_create_symmetric_key(r, provider->client_secret, 0,
NULL, TRUE, &jwk) == FALSE)
return FALSE;
oidc_jwks_uri_t jwks_uri = { provider->jwks_uri,
provider->jwks_refresh_interval, provider->ssl_validate_server };
if (oidc_proto_jwt_verify(r, cfg, jwt, &jwks_uri,
oidc_util_merge_symmetric_key(r->pool, NULL, jwk)) == FALSE) {
oidc_error(r, "JWT signature could not be validated, aborting");
oidc_jwt_destroy(jwt);
oidc_jwk_destroy(jwk);
return FALSE;
}
oidc_jwk_destroy(jwk);
oidc_debug(r,
"successfully verified signed JWT returned from userinfo endpoint: %s",
jwt->payload.value.str);
*userinfo_jwt = apr_pstrdup(r->pool, *response);
*claims = json_deep_copy(jwt->payload.value.json);
*response = apr_pstrdup(r->pool, jwt->payload.value.str);
oidc_jwt_destroy(jwt);
return TRUE;
}
oidc_jwk_destroy(jwk);
return oidc_util_decode_json_and_check_error(r, *response, claims);
}
#define OIDC_COMPOSITE_CLAIM_NAMES "_claim_names"
#define OIDC_COMPOSITE_CLAIM_SOURCES "_claim_sources"
#define OIDC_COMPOSITE_CLAIM_JWT "JWT"
#define OIDC_COMPOSITE_CLAIM_ACCESS_TOKEN OIDC_PROTO_ACCESS_TOKEN
#define OIDC_COMPOSITE_CLAIM_ENDPOINT "endpoint"
static apr_byte_t oidc_proto_resolve_composite_claims(request_rec *r,
oidc_cfg *cfg, json_t *claims) {
const char *key;
json_t *value;
void *iter;
json_t *sources, *names, *decoded;
oidc_jose_error_t err;
oidc_jwk_t *jwk = NULL;
oidc_debug(r, "enter");
names = json_object_get(claims, OIDC_COMPOSITE_CLAIM_NAMES);
if ((names == NULL) || (!json_is_object(names)))
return FALSE;
sources = json_object_get(claims, OIDC_COMPOSITE_CLAIM_SOURCES);
if ((sources == NULL) || (!json_is_object(sources))) {
oidc_debug(r, "%s found, but no %s found", OIDC_COMPOSITE_CLAIM_NAMES,
OIDC_COMPOSITE_CLAIM_SOURCES);
return FALSE;
}
decoded = json_object();
iter = json_object_iter(sources);
while (iter) {
key = json_object_iter_key(iter);
value = json_object_iter_value(iter);
if ((value != NULL) && (json_is_object(value))) {
json_t *jwt = json_object_get(value, OIDC_COMPOSITE_CLAIM_JWT);
char *s_json = NULL;
if ((jwt != NULL) && (json_is_string(jwt))) {
s_json = apr_pstrdup(r->pool, json_string_value(jwt));
} else {
const char *access_token = json_string_value(
json_object_get(value,
OIDC_COMPOSITE_CLAIM_ACCESS_TOKEN));
const char *endpoint = json_string_value(
json_object_get(value, OIDC_COMPOSITE_CLAIM_ENDPOINT));
if ((access_token != NULL) && (endpoint != NULL)) {
oidc_util_http_get(r, endpoint,
NULL, NULL, access_token, cfg->provider.ssl_validate_server,
&s_json, cfg->http_timeout_long,
cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(r),
NULL, NULL);
}
}
if ((s_json != NULL) && (strcmp(s_json, "") != 0)) {
oidc_jwt_t *jwt = NULL;
if (oidc_jwt_parse(r->pool, s_json, &jwt,
oidc_util_merge_symmetric_key(r->pool,
cfg->private_keys, jwk), &err) == FALSE) {
oidc_error(r,
"could not parse JWT from aggregated claim \"%s\": %s",
key, oidc_jose_e2s(r->pool, err));
} else {
json_t *v = json_object_get(decoded, key);
if (v == NULL) {
v = json_object();
json_object_set_new(decoded, key, v);
}
oidc_util_json_merge(r, jwt->payload.value.json, v);
}
oidc_jwt_destroy(jwt);
}
}
iter = json_object_iter_next(sources, iter);
}
iter = json_object_iter(names);
while (iter) {
key = json_object_iter_key(iter);
const char *s_value = json_string_value(json_object_iter_value(iter));
if (s_value != NULL) {
oidc_debug(r, "processing: %s: %s", key, s_value);
json_t *values = json_object_get(decoded, s_value);
if (values != NULL) {
json_object_set(claims, key, json_object_get(values, key));
} else {
oidc_warn(r, "no values for source \"%s\" found", s_value);
}
} else {
oidc_warn(r, "no string value found for claim \"%s\"", key);
}
iter = json_object_iter_next(names, iter);
}
json_object_del(claims, OIDC_COMPOSITE_CLAIM_NAMES);
json_object_del(claims, OIDC_COMPOSITE_CLAIM_SOURCES);
json_decref(decoded);
return TRUE;
}
/*
* get claims from the OP UserInfo endpoint using the provided access_token
*/
apr_byte_t oidc_proto_resolve_userinfo(request_rec *r, oidc_cfg *cfg,
oidc_provider_t *provider, const char *id_token_sub,
const char *access_token, char **response, char **userinfo_jwt) {
oidc_debug(r, "enter, endpoint=%s, access_token=%s",
provider->userinfo_endpoint_url, access_token);
/* get the JSON response */
if (provider->userinfo_token_method == OIDC_USER_INFO_TOKEN_METHOD_HEADER) {
if (oidc_util_http_get(r, provider->userinfo_endpoint_url,
NULL, NULL, access_token, provider->ssl_validate_server, response,
cfg->http_timeout_long, cfg->outgoing_proxy,
oidc_dir_cfg_pass_cookies(r), NULL, NULL) == FALSE)
return FALSE;
} else if (provider->userinfo_token_method
== OIDC_USER_INFO_TOKEN_METHOD_POST) {
apr_table_t *params = apr_table_make(r->pool, 4);
apr_table_setn(params, OIDC_PROTO_ACCESS_TOKEN, access_token);
if (oidc_util_http_post_form(r, provider->userinfo_endpoint_url, params,
NULL, access_token, provider->ssl_validate_server, response,
cfg->http_timeout_long, cfg->outgoing_proxy,
oidc_dir_cfg_pass_cookies(r), NULL, NULL) == FALSE)
return FALSE;
} else {
oidc_error(r, "unsupported userinfo token presentation method: %d",
provider->userinfo_token_method);
return FALSE;
}
json_t *claims = NULL;
if (oidc_user_info_response_validate(r, cfg, provider, response, &claims,
userinfo_jwt) == FALSE)
return FALSE;
if (oidc_proto_resolve_composite_claims(r, cfg, claims) == TRUE)
*response = oidc_util_encode_json_object(r, claims,
JSON_PRESERVE_ORDER | JSON_COMPACT);
char *user_info_sub = NULL;
oidc_jose_get_string(r->pool, claims, OIDC_CLAIM_SUB, FALSE, &user_info_sub,
NULL);
oidc_debug(r, "id_token_sub=%s, user_info_sub=%s", id_token_sub,
user_info_sub);
if ((id_token_sub != NULL) && (user_info_sub != NULL)) {
if (apr_strnatcmp(id_token_sub, user_info_sub) != 0) {
oidc_error(r,
"\"%s\" claim (\"%s\") returned from userinfo endpoint does not match the one in the id_token (\"%s\")",
OIDC_CLAIM_SUB, user_info_sub, id_token_sub);
json_decref(claims);
return FALSE;
}
}
json_decref(claims);
return TRUE;
}
/*
* based on a resource perform OpenID Connect Provider Issuer Discovery to find out the issuer and obtain and store its metadata
*/
static apr_byte_t oidc_proto_webfinger_discovery(request_rec *r, oidc_cfg *cfg,
const char *resource, const char *domain, char **issuer) {
const char *url = apr_psprintf(r->pool, "https://%s/.well-known/webfinger",
domain);
apr_table_t *params = apr_table_make(r->pool, 1);
apr_table_setn(params, "resource", resource);
apr_table_setn(params, "rel", "http://openid.net/specs/connect/1.0/issuer");
char *response = NULL;
if (oidc_util_http_get(r, url, params, NULL, NULL,
cfg->provider.ssl_validate_server, &response,
cfg->http_timeout_short, cfg->outgoing_proxy,
oidc_dir_cfg_pass_cookies(r), NULL, NULL) == FALSE) {
/* errors will have been logged by now */
return FALSE;
}
/* decode and see if it is not an error response somehow */
json_t *j_response = NULL;
if (oidc_util_decode_json_and_check_error(r, response, &j_response) == FALSE)
return FALSE;
/* get the links parameter */
json_t *j_links = json_object_get(j_response, "links");
if ((j_links == NULL) || (!json_is_array(j_links))) {
oidc_error(r, "response JSON object did not contain a \"links\" array");
json_decref(j_response);
return FALSE;
}
/* get the one-and-only object in the "links" array */
json_t *j_object = json_array_get(j_links, 0);
if ((j_object == NULL) || (!json_is_object(j_object))) {
oidc_error(r,
"response JSON object did not contain a JSON object as the first element in the \"links\" array");
json_decref(j_response);
return FALSE;
}
/* get the href from that object, which is the issuer value */
json_t *j_href = json_object_get(j_object, "href");
if ((j_href == NULL) || (!json_is_string(j_href))) {
oidc_error(r,
"response JSON object did not contain a \"href\" element in the first \"links\" array object");
json_decref(j_response);
return FALSE;
}
/* check that the link is on secure HTTPs */
if (oidc_valid_url(r->pool, json_string_value(j_href), "https") != NULL) {
oidc_error(r,
"response JSON object contains an \"href\" value that is not a valid \"https\" URL: %s",
json_string_value(j_href));
json_decref(j_response);
return FALSE;
}
*issuer = apr_pstrdup(r->pool, json_string_value(j_href));
oidc_debug(r,
"returning issuer \"%s\" for resource \"%s\" after doing successful webfinger-based discovery",
*issuer, resource);
json_decref(j_response);
return TRUE;
}
/*
* based on an account name, perform OpenID Connect Provider Issuer Discovery to find out the issuer and obtain and store its metadata
*/
apr_byte_t oidc_proto_account_based_discovery(request_rec *r, oidc_cfg *cfg,
const char *acct, char **issuer) {
// TODO: maybe show intermediate/progress screen "discovering..."
oidc_debug(r, "enter, acct=%s", acct);
const char *resource = apr_psprintf(r->pool, "acct:%s", acct);
const char *domain = strrchr(acct, OIDC_CHAR_AT);
if (domain == NULL) {
oidc_error(r, "invalid account name");
return FALSE;
}
domain++;
return oidc_proto_webfinger_discovery(r, cfg, resource, domain, issuer);
}
/*
* based on user identifier URL, perform OpenID Connect Provider Issuer Discovery to find out the issuer and obtain and store its metadata
*/
apr_byte_t oidc_proto_url_based_discovery(request_rec *r, oidc_cfg *cfg,
const char *url, char **issuer) {
oidc_debug(r, "enter, url=%s", url);
apr_uri_t uri;
apr_uri_parse(r->pool, url, &uri);
char *domain = uri.hostname;
if (uri.port_str != NULL)
domain = apr_psprintf(r->pool, "%s:%s", domain, uri.port_str);
return oidc_proto_webfinger_discovery(r, cfg, url, domain, issuer);
}
int oidc_proto_javascript_implicit(request_rec *r, oidc_cfg *c) {
oidc_debug(r, "enter");
const char *java_script =
" \n";
const char *html_body =
"
Submitting...
\n"
" \n";
return oidc_util_html_send(r, "Submitting...", java_script, "postOnLoad",
html_body, DONE);
}
/*
* check a provided hash value (at_hash|c_hash) against a corresponding hash calculated for a specified value and algorithm
*/
static apr_byte_t oidc_proto_validate_hash(request_rec *r, const char *alg,
const char *hash, const char *value, const char *type) {
char *calc = NULL;
unsigned int calc_len = 0;
unsigned int hash_len = oidc_jose_hash_length(alg) / 2;
oidc_jose_error_t err;
/* hash the provided access_token */
if (oidc_jose_hash_string(r->pool, alg, value, &calc, &calc_len,
&err) == FALSE) {
oidc_error(r, "oidc_jose_hash_string failed: %s",
oidc_jose_e2s(r->pool, err));
return FALSE;
}
/* calculate the base64url-encoded value of the hash */
char *decoded = NULL;
unsigned int decoded_len = oidc_base64url_decode(r->pool, &decoded, hash);
if (decoded_len <= 0) {
oidc_error(r, "oidc_base64url_decode returned an error");
return FALSE;
}
oidc_debug(r, "hash_len=%d, decoded_len=%d, calc_len=%d", hash_len,
decoded_len, calc_len);
/* compare the calculated hash against the provided hash */
if ((decoded_len < hash_len) || (calc_len < hash_len)
|| (memcmp(decoded, calc, hash_len) != 0)) {
oidc_error(r,
"provided \"%s\" hash value (%s) does not match the calculated value",
type, hash);
return FALSE;
}
oidc_debug(r,
"successfully validated the provided \"%s\" hash value (%s) against the calculated value",
type, hash);
return TRUE;
}
/*
* check a hash value in the id_token against the corresponding hash calculated over a provided value
*/
static apr_byte_t oidc_proto_validate_hash_value(request_rec *r,
oidc_provider_t *provider, oidc_jwt_t *jwt, const char *response_type,
const char *value, const char *key,
apr_array_header_t *required_for_flows) {
/*
* get the hash value from the id_token
*/
char *hash = NULL;
oidc_jose_get_string(r->pool, jwt->payload.value.json, key, FALSE, &hash,
NULL);
/*
* check if the hash was present
*/
if (hash == NULL) {
/* no hash..., now see if the flow required it */
int i;
for (i = 0; i < required_for_flows->nelts; i++) {
if (oidc_util_spaced_string_equals(r->pool, response_type,
((const char**) required_for_flows->elts)[i])) {
oidc_warn(r, "flow is \"%s\", but no %s found in id_token",
response_type, key);
return FALSE;
}
}
/* no hash but it was not required anyway */
return TRUE;
}
/*
* we have a hash, validate it and return the result
*/
return oidc_proto_validate_hash(r, jwt->header.alg, hash, value, key);
}
/*
* check the c_hash value in the id_token against the code
*/
apr_byte_t oidc_proto_validate_code(request_rec *r, oidc_provider_t *provider,
oidc_jwt_t *jwt, const char *response_type, const char *code) {
apr_array_header_t *required_for_flows = apr_array_make(r->pool, 2,
sizeof(const char*));
*(const char**) apr_array_push(required_for_flows) =
OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN;
*(const char**) apr_array_push(required_for_flows) =
OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN;
if (oidc_proto_validate_hash_value(r, provider, jwt, response_type, code,
OIDC_CLAIM_C_HASH, required_for_flows) == FALSE) {
oidc_error(r, "could not validate code against \"%s\" claim value",
OIDC_CLAIM_C_HASH);
return FALSE;
}
return TRUE;
}
/*
* check the at_hash value in the id_token against the access_token
*/
apr_byte_t oidc_proto_validate_access_token(request_rec *r,
oidc_provider_t *provider, oidc_jwt_t *jwt, const char *response_type,
const char *access_token) {
apr_array_header_t *required_for_flows = apr_array_make(r->pool, 2,
sizeof(const char*));
*(const char**) apr_array_push(required_for_flows) =
OIDC_PROTO_RESPONSE_TYPE_IDTOKEN_TOKEN;
*(const char**) apr_array_push(required_for_flows) =
OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN;
if (oidc_proto_validate_hash_value(r, provider, jwt, response_type,
access_token, OIDC_CLAIM_AT_HASH, required_for_flows) == FALSE) {
oidc_error(r,
"could not validate access token against \"%s\" claim value",
OIDC_CLAIM_AT_HASH);
return FALSE;
}
return TRUE;
}
/*
* return the supported flows
*/
apr_array_header_t *oidc_proto_supported_flows(apr_pool_t *pool) {
apr_array_header_t *result = apr_array_make(pool, 6, sizeof(const char*));
*(const char**) apr_array_push(result) = OIDC_PROTO_RESPONSE_TYPE_CODE;
*(const char**) apr_array_push(result) = OIDC_PROTO_RESPONSE_TYPE_IDTOKEN;
*(const char**) apr_array_push(result) =
OIDC_PROTO_RESPONSE_TYPE_IDTOKEN_TOKEN;
*(const char**) apr_array_push(result) =
OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN;
*(const char**) apr_array_push(result) =
OIDC_PROTO_RESPONSE_TYPE_CODE_TOKEN;
*(const char**) apr_array_push(result) =
OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN;
return result;
}
/*
* check if a particular OpenID Connect flow is supported
*/
apr_byte_t oidc_proto_flow_is_supported(apr_pool_t *pool, const char *flow) {
apr_array_header_t *flows = oidc_proto_supported_flows(pool);
int i;
for (i = 0; i < flows->nelts; i++) {
if (oidc_util_spaced_string_equals(pool, flow,
((const char**) flows->elts)[i]))
return TRUE;
}
return FALSE;
}
/*
* check the required parameters for the various flows after resolving the authorization code
*/
static apr_byte_t oidc_proto_validate_code_response(request_rec *r,
const char *response_type, char *id_token, char *access_token,
char *token_type) {
oidc_debug(r, "enter");
/*
* check id_token parameter
*/
if (!oidc_util_spaced_string_contains(r->pool, response_type,
OIDC_PROTO_RESPONSE_TYPE_IDTOKEN)) {
if (id_token == NULL) {
oidc_error(r,
"requested flow is \"%s\" but no \"%s\" parameter found in the code response",
response_type, OIDC_PROTO_ID_TOKEN);
return FALSE;
}
} else {
if (id_token != NULL) {
oidc_warn(r,
"requested flow is \"%s\" but there is an \"%s\" parameter in the code response that will be dropped",
response_type, OIDC_PROTO_ID_TOKEN);
}
}
/*
* check access_token parameter
*/
if (!oidc_util_spaced_string_contains(r->pool, response_type,
OIDC_PROTO_RESPONSE_TYPE_TOKEN)) {
if (access_token == NULL) {
oidc_error(r,
"requested flow is \"%s\" but no \"%s\" parameter found in the code response",
response_type, OIDC_PROTO_ACCESS_TOKEN);
return FALSE;
}
if (token_type == NULL) {
oidc_error(r,
"requested flow is \"%s\" but no \"%s\" parameter found in the code response",
response_type, OIDC_PROTO_TOKEN_TYPE);
return FALSE;
}
} else {
if (access_token != NULL) {
oidc_warn(r,
"requested flow is \"%s\" but there is an \"%s\" parameter in the code response that will be dropped",
response_type, OIDC_PROTO_ACCESS_TOKEN);
}
if (token_type != NULL) {
oidc_warn(r,
"requested flow is \"%s\" but there is a \"%s\" parameter in the code response that will be dropped",
response_type, OIDC_PROTO_TOKEN_TYPE);
}
}
return TRUE;
}
/*
* validate the response parameters provided by the OP against the requested response type
*/
static apr_byte_t oidc_proto_validate_response_type(request_rec *r,
const char *requested_response_type, const char *code,
const char *id_token, const char *access_token) {
if (oidc_util_spaced_string_contains(r->pool, requested_response_type,
OIDC_PROTO_RESPONSE_TYPE_CODE)) {
if (code == NULL) {
oidc_error(r,
"the requested response type was (%s) but the response does not contain a \"%s\" parameter",
requested_response_type, OIDC_PROTO_CODE);
return FALSE;
}
} else if (code != NULL) {
oidc_error(r,
"the requested response type was (%s) but the response contains a \"%s\" parameter",
requested_response_type, OIDC_PROTO_CODE);
return FALSE;
}
if (oidc_util_spaced_string_contains(r->pool, requested_response_type,
OIDC_PROTO_RESPONSE_TYPE_IDTOKEN)) {
if (id_token == NULL) {
oidc_error(r,
"the requested response type was (%s) but the response does not contain an \"%s\" parameter",
requested_response_type, OIDC_PROTO_ID_TOKEN);
return FALSE;
}
} else if (id_token != NULL) {
oidc_error(r,
"the requested response type was (%s) but the response contains an \"%s\" parameter",
requested_response_type, OIDC_PROTO_ID_TOKEN);
return FALSE;
}
if (oidc_util_spaced_string_contains(r->pool, requested_response_type,
OIDC_PROTO_RESPONSE_TYPE_TOKEN)) {
if (access_token == NULL) {
oidc_error(r,
"the requested response type was (%s) but the response does not contain an \"%s\" parameter",
requested_response_type, OIDC_PROTO_ACCESS_TOKEN);
return FALSE;
}
} else if (access_token != NULL) {
oidc_error(r,
"the requested response type was (%s) but the response contains an \"%s\" parameter",
requested_response_type, OIDC_PROTO_ACCESS_TOKEN);
return FALSE;
}
return TRUE;
}
/*
* validate the response mode used by the OP against the requested response mode
*/
static apr_byte_t oidc_proto_validate_response_mode(request_rec *r,
oidc_proto_state_t *proto_state, const char *response_mode,
const char *default_response_mode) {
const char *requested_response_mode = oidc_proto_state_get_response_mode(
proto_state);
if (requested_response_mode == NULL)
requested_response_mode = default_response_mode;
if (apr_strnatcmp(requested_response_mode, response_mode) != 0) {
oidc_error(r,
"requested response mode (%s) does not match the response mode used by the OP (%s)",
requested_response_mode, response_mode);
return FALSE;
}
return TRUE;
}
/*
* validate the client_id/iss provided by the OP against the client_id/iss registered with the provider that the request was sent to
*/
static apr_byte_t oidc_proto_validate_issuer_client_id(request_rec *r,
const char *configured_issuer, const char *response_issuer,
const char *configured_client_id, const char *response_client_id) {
if (response_issuer != NULL) {
if (apr_strnatcmp(configured_issuer, response_issuer) != 0) {
oidc_error(r,
"configured issuer (%s) does not match the issuer provided in the response by the OP (%s)",
configured_issuer, response_issuer);
return FALSE;
}
}
if (response_client_id != NULL) {
if (apr_strnatcmp(configured_client_id, response_client_id) != 0) {
oidc_error(r,
"configured client_id (%s) does not match the client_id provided in the response by the OP (%s)",
configured_client_id, response_client_id);
return FALSE;
}
}
oidc_debug(r, "iss and/or client_id matched OK: %s, %s, %s, %s",
response_issuer, configured_issuer, response_client_id,
configured_client_id);
return TRUE;
}
/*
* helper function to validate both the response type and the response mode in a single function call
*/
static apr_byte_t oidc_proto_validate_response_type_mode_issuer(request_rec *r,
const char *requested_response_type, apr_table_t *params,
oidc_proto_state_t *proto_state, const char *response_mode,
const char *default_response_mode, const char *issuer,
const char *c_client_id) {
const char *code = apr_table_get(params, OIDC_PROTO_CODE);
const char *id_token = apr_table_get(params, OIDC_PROTO_ID_TOKEN);
const char *access_token = apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN);
const char *iss = apr_table_get(params, OIDC_PROTO_ISS);
const char *client_id = apr_table_get(params, OIDC_PROTO_CLIENT_ID);
if (oidc_proto_validate_issuer_client_id(r, issuer, iss, c_client_id,
client_id) == FALSE)
return FALSE;
if (oidc_proto_validate_response_type(r, requested_response_type, code,
id_token, access_token) == FALSE)
return FALSE;
if (oidc_proto_validate_response_mode(r, proto_state, response_mode,
default_response_mode) == FALSE)
return FALSE;
return TRUE;
}
/*
* parse and id_token and check the c_hash if the code is provided
*/
static apr_byte_t oidc_proto_parse_idtoken_and_validate_code(request_rec *r,
oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider,
const char *response_type, apr_table_t *params, oidc_jwt_t **jwt,
apr_byte_t must_validate_code) {
const char *code = apr_table_get(params, OIDC_PROTO_CODE);
const char *id_token = apr_table_get(params, OIDC_PROTO_ID_TOKEN);
apr_byte_t is_code_flow = (oidc_util_spaced_string_contains(r->pool,
response_type, OIDC_PROTO_RESPONSE_TYPE_CODE) == TRUE)
&& (oidc_util_spaced_string_contains(r->pool, response_type,
OIDC_PROTO_RESPONSE_TYPE_IDTOKEN) == FALSE);
const char *nonce = oidc_proto_state_get_nonce(proto_state);
if (oidc_proto_parse_idtoken(r, c, provider, id_token, nonce, jwt,
is_code_flow) == FALSE)
return FALSE;
if ((must_validate_code == TRUE)
&& (oidc_proto_validate_code(r, provider, *jwt, response_type, code)
== FALSE))
return FALSE;
return TRUE;
}
/*
* resolve the code against the token endpoint and validate the response that is returned by the OP
*/
static apr_byte_t oidc_proto_resolve_code_and_validate_response(request_rec *r,
oidc_cfg *c, oidc_provider_t *provider, const char *response_type,
apr_table_t *params, oidc_proto_state_t *proto_state) {
char *id_token = NULL;
char *access_token = NULL;
char *token_type = NULL;
int expires_in = -1;
char *refresh_token = NULL;
char *code_verifier = NULL;
if (provider->pkce != NULL)
provider->pkce->verifier(r,
oidc_proto_state_get_pkce_state(proto_state), &code_verifier);
const char *state = oidc_proto_state_get_state(proto_state);
if (oidc_proto_resolve_code(r, c, provider,
apr_table_get(params, OIDC_PROTO_CODE), code_verifier, &id_token,
&access_token, &token_type, &expires_in, &refresh_token,
state) == FALSE) {
oidc_error(r, "failed to resolve the code");
return FALSE;
}
if (oidc_proto_validate_code_response(r, response_type, id_token,
access_token, token_type) == FALSE) {
oidc_error(r, "code response validation failed");
return FALSE;
}
/* don't override parameters that may already have been (rightfully) set in the authorization response */
if ((apr_table_get(params, OIDC_PROTO_ID_TOKEN) == NULL)
&& (id_token != NULL)) {
apr_table_set(params, OIDC_PROTO_ID_TOKEN, id_token);
}
if ((apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN) == NULL)
&& (access_token != NULL)) {
apr_table_set(params, OIDC_PROTO_ACCESS_TOKEN, access_token);
if (token_type != NULL)
apr_table_set(params, OIDC_PROTO_TOKEN_TYPE, token_type);
if (expires_in != -1)
apr_table_setn(params, OIDC_PROTO_EXPIRES_IN,
apr_psprintf(r->pool, "%d", expires_in));
}
/* refresh token should not have been set before */
if (refresh_token != NULL) {
apr_table_set(params, OIDC_PROTO_REFRESH_TOKEN, refresh_token);
}
return TRUE;
}
/*
* handle the "code id_token" response type
*/
apr_byte_t oidc_proto_authorization_response_code_idtoken(request_rec *r,
oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider,
apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) {
oidc_debug(r, "enter");
static const char *response_type = OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN;
if (oidc_proto_validate_response_type_mode_issuer(r, response_type, params,
proto_state, response_mode, OIDC_PROTO_RESPONSE_MODE_FRAGMENT,
provider->issuer, provider->client_id) == FALSE)
return FALSE;
if (oidc_proto_parse_idtoken_and_validate_code(r, c, proto_state, provider,
response_type, params, jwt, TRUE) == FALSE)
return FALSE;
/* clear parameters that should only be set from the token endpoint */
apr_table_unset(params, OIDC_PROTO_ACCESS_TOKEN);
apr_table_unset(params, OIDC_PROTO_TOKEN_TYPE);
apr_table_unset(params, OIDC_PROTO_EXPIRES_IN);
apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN);
if (oidc_proto_resolve_code_and_validate_response(r, c, provider,
response_type, params, proto_state) == FALSE)
return FALSE;
return TRUE;
}
/*
* handle the "code token" response type
*/
apr_byte_t oidc_proto_handle_authorization_response_code_token(request_rec *r,
oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider,
apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) {
oidc_debug(r, "enter");
static const char *response_type = OIDC_PROTO_RESPONSE_TYPE_CODE_TOKEN;
if (oidc_proto_validate_response_type_mode_issuer(r, response_type, params,
proto_state, response_mode, OIDC_PROTO_RESPONSE_MODE_FRAGMENT,
provider->issuer, provider->client_id) == FALSE)
return FALSE;
/* clear parameters that should only be set from the token endpoint */
apr_table_unset(params, OIDC_PROTO_ID_TOKEN);
apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN);
if (oidc_proto_resolve_code_and_validate_response(r, c, provider,
response_type, params, proto_state) == FALSE)
return FALSE;
if (oidc_proto_parse_idtoken_and_validate_code(r, c, proto_state, provider,
response_type, params, jwt, FALSE) == FALSE)
return FALSE;
return TRUE;
}
/*
* handle the "code" response type
*/
apr_byte_t oidc_proto_handle_authorization_response_code(request_rec *r,
oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider,
apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) {
oidc_debug(r, "enter");
static const char *response_type = OIDC_PROTO_RESPONSE_TYPE_CODE;
if (oidc_proto_validate_response_type_mode_issuer(r, response_type, params,
proto_state, response_mode, OIDC_PROTO_RESPONSE_MODE_QUERY,
provider->issuer, provider->client_id) == FALSE)
return FALSE;
/* clear parameters that should only be set from the token endpoint */
apr_table_unset(params, OIDC_PROTO_ACCESS_TOKEN);
apr_table_unset(params, OIDC_PROTO_TOKEN_TYPE);
apr_table_unset(params, OIDC_PROTO_EXPIRES_IN);
apr_table_unset(params, OIDC_PROTO_ID_TOKEN);
apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN);
if (oidc_proto_resolve_code_and_validate_response(r, c, provider,
response_type, params, proto_state) == FALSE)
return FALSE;
/*
* in this flow it is actually optional to check the code token against the c_hash
*/
if (oidc_proto_parse_idtoken_and_validate_code(r, c, proto_state, provider,
response_type, params, jwt, TRUE) == FALSE)
return FALSE;
/*
* in this flow it is actually optional to check the access token against the at_hash
*/
if ((apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN) != NULL)
&& (oidc_proto_validate_access_token(r, provider, *jwt,
response_type,
apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN)) == FALSE))
return FALSE;
return TRUE;
}
/*
* helper function for implicit flows: shared code for "id_token token" and "id_token"
*/
static apr_byte_t oidc_proto_handle_implicit_flow(request_rec *r, oidc_cfg *c,
const char *response_type, oidc_proto_state_t *proto_state,
oidc_provider_t *provider, apr_table_t *params,
const char *response_mode, oidc_jwt_t **jwt) {
if (oidc_proto_validate_response_type_mode_issuer(r, response_type, params,
proto_state, response_mode, OIDC_PROTO_RESPONSE_MODE_FRAGMENT,
provider->issuer, provider->client_id) == FALSE)
return FALSE;
if (oidc_proto_parse_idtoken_and_validate_code(r, c, proto_state, provider,
response_type, params, jwt, TRUE) == FALSE)
return FALSE;
return TRUE;
}
/*
* handle the "code id_token token" response type
*/
apr_byte_t oidc_proto_authorization_response_code_idtoken_token(request_rec *r,
oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider,
apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) {
oidc_debug(r, "enter");
static const char *response_type =
OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN;
if (oidc_proto_handle_implicit_flow(r, c, response_type, proto_state,
provider, params, response_mode, jwt) == FALSE)
return FALSE;
if (oidc_proto_validate_access_token(r, provider, *jwt, response_type,
apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN)) == FALSE)
return FALSE;
/* clear parameters that should only be set from the token endpoint */
apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN);
if (oidc_proto_resolve_code_and_validate_response(r, c, provider,
response_type, params, proto_state) == FALSE)
return FALSE;
return TRUE;
}
/*
* handle the "id_token token" response type
*/
apr_byte_t oidc_proto_handle_authorization_response_idtoken_token(
request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state,
oidc_provider_t *provider, apr_table_t *params,
const char *response_mode, oidc_jwt_t **jwt) {
oidc_debug(r, "enter");
static const char *response_type = OIDC_PROTO_RESPONSE_TYPE_IDTOKEN_TOKEN;
if (oidc_proto_handle_implicit_flow(r, c, response_type, proto_state,
provider, params, response_mode, jwt) == FALSE)
return FALSE;
if (oidc_proto_validate_access_token(r, provider, *jwt, response_type,
apr_table_get(params, OIDC_PROTO_ACCESS_TOKEN)) == FALSE)
return FALSE;
/* clear parameters that should not be part of this flow */
apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN);
return TRUE;
}
/*
* handle the "id_token" response type
*/
apr_byte_t oidc_proto_handle_authorization_response_idtoken(request_rec *r,
oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider,
apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt) {
oidc_debug(r, "enter");
static const char *response_type = OIDC_PROTO_RESPONSE_TYPE_IDTOKEN;
if (oidc_proto_handle_implicit_flow(r, c, response_type, proto_state,
provider, params, response_mode, jwt) == FALSE)
return FALSE;
/* clear parameters that should not be part of this flow */
apr_table_unset(params, OIDC_PROTO_TOKEN_TYPE);
apr_table_unset(params, OIDC_PROTO_EXPIRES_IN);
apr_table_unset(params, OIDC_PROTO_REFRESH_TOKEN);
return TRUE;
}
mod_auth_openidc-2.3.3/src/config.c 0000644 0000765 0000024 00000323543 13203320522 017060 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "mod_auth_openidc.h"
#include "parse.h"
#define OPENSSL_THREAD_DEFINES
#include
#include
#include
#if (OPENSSL_VERSION_NUMBER < 0x01000000)
#define OPENSSL_NO_THREADID
#endif
/* validate SSL server certificates by default */
#define OIDC_DEFAULT_SSL_VALIDATE_SERVER 1
/* default scope requested from the OP */
#define OIDC_DEFAULT_SCOPE "openid"
/* default claim delimiter for multi-valued claims passed in a HTTP header */
#define OIDC_DEFAULT_CLAIM_DELIMITER ","
/* default prefix for claim names being passed in HTTP headers */
#define OIDC_DEFAULT_CLAIM_PREFIX "OIDC_CLAIM_"
/* default name for the claim that will contain the REMOTE_USER value for OpenID Connect protected paths */
#define OIDC_DEFAULT_CLAIM_REMOTE_USER "sub@"
/* default name for the claim that will contain the REMOTE_USER value for OAuth 2.0 protected paths */
#define OIDC_DEFAULT_OAUTH_CLAIM_REMOTE_USER "sub"
/* default name of the session cookie */
#define OIDC_DEFAULT_COOKIE "mod_auth_openidc_session"
/* default for the HTTP header name in which the remote user name is passed */
#define OIDC_DEFAULT_AUTHN_HEADER NULL
/* scrub HTTP headers by default unless overridden (and insecure) */
#define OIDC_DEFAULT_SCRUB_REQUEST_HEADERS 1
/* default client_name the client uses for dynamic client registration */
#define OIDC_DEFAULT_CLIENT_NAME "OpenID Connect Apache Module (mod_auth_openidc)"
/* timeouts in seconds for HTTP calls that may take a long time */
#define OIDC_DEFAULT_HTTP_TIMEOUT_LONG 60
/* timeouts in seconds for HTTP calls that should take a short time (registry/discovery related) */
#define OIDC_DEFAULT_HTTP_TIMEOUT_SHORT 5
/* default session storage type */
#define OIDC_DEFAULT_SESSION_TYPE OIDC_SESSION_TYPE_SERVER_CACHE
/* default client-cookie chunking size */
#define OIDC_DEFAULT_SESSION_CLIENT_COOKIE_CHUNK_SIZE 4000
/* timeout in seconds after which state expires */
#define OIDC_DEFAULT_STATE_TIMEOUT 300
/* default session inactivity timeout */
#define OIDC_DEFAULT_SESSION_INACTIVITY_TIMEOUT 300
/* default session max duration */
#define OIDC_DEFAULT_SESSION_MAX_DURATION 3600 * 8
/* default OpenID Connect authorization response type */
#define OIDC_DEFAULT_RESPONSE_TYPE OIDC_PROTO_CODE
/* default duration in seconds after which retrieved JWS should be refreshed */
#define OIDC_DEFAULT_JWKS_REFRESH_INTERVAL 3600
/* default max cache size for shm */
#define OIDC_DEFAULT_CACHE_SHM_SIZE 500
/* default max cache entry size for shm: # value + # key + # overhead */
#define OIDC_DEFAULT_CACHE_SHM_ENTRY_SIZE_MAX 16384 + 512 + 17
/* minimum size of a cache entry */
#define OIDC_MINIMUM_CACHE_SHM_ENTRY_SIZE_MAX 8192 + 512 + 17
/* for issued-at timestamp (iat) checking */
#define OIDC_DEFAULT_IDTOKEN_IAT_SLACK 600
/* for file-based caching: clean interval in seconds */
#define OIDC_DEFAULT_CACHE_FILE_CLEAN_INTERVAL 60
/* set httponly flag on cookies */
#define OIDC_DEFAULT_COOKIE_HTTPONLY 1
/* set Same-Site flag on cookies */
#define OIDC_DEFAULT_COOKIE_SAME_SITE 0
/* default cookie path */
#define OIDC_DEFAULT_COOKIE_PATH "/"
/* default OAuth 2.0 introspection token parameter name */
#define OIDC_DEFAULT_OAUTH_TOKEN_PARAM_NAME "token"
/* default OAuth 2.0 introspection call HTTP method */
#define OIDC_DEFAULT_OAUTH_ENDPOINT_METHOD OIDC_INTROSPECTION_METHOD_POST
/* default OAuth 2.0 non-spec compliant introspection expiry claim name */
#define OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_NAME OIDC_PROTO_EXPIRES_IN
/* default OAuth 2.0 non-spec compliant introspection expiry claim format */
#define OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_FORMAT OIDC_CLAIM_FORMAT_RELATIVE
/* default OAuth 2.0 non-spec compliant introspection expiry claim required */
#define OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_REQUIRED TRUE
/* default refresh interval in seconds after which claims from the user info endpoint should be refreshed */
#define OIDC_DEFAULT_USERINFO_REFRESH_INTERVAL 0
/* default for preserving POST parameters across authentication requests */
#define OIDC_DEFAULT_PRESERVE_POST 0
/* default for passing the access token in a header/environment variable */
#define OIDC_DEFAULT_PASS_REFRESH_TOKEN 0
/* default for passing app info in headers */
#define OIDC_DEFAULT_PASS_APP_INFO_IN_HEADERS 1
/* default for passing app info in environment variables */
#define OIDC_DEFAULT_PASS_APP_INFO_IN_ENVVARS 1
/* default value for the token introspection interval (0 = disabled, no expiry of claims) */
#define OIDC_DEFAULT_TOKEN_INTROSPECTION_INTERVAL 0
/* default action to take on an incoming unauthenticated request */
#define OIDC_DEFAULT_UNAUTH_ACTION OIDC_UNAUTH_AUTHENTICATE
/* default action to take on an incoming authorized request */
#define OIDC_DEFAULT_UNAUTZ_ACTION OIDC_UNAUTZ_RETURN403
/* defines for how long provider metadata will be cached */
#define OIDC_DEFAULT_PROVIDER_METADATA_REFRESH_INTERVAL 0
/* defines the default token binding policy for a provider */
#define OIDC_DEFAULT_PROVIDER_TOKEN_BINDING_POLICY OIDC_TOKEN_BINDING_POLICY_OPTIONAL
/* define the default HTTP method used to send the authentication request to the provider */
#define OIDC_DEFAULT_AUTH_REQUEST_METHOD OIDC_AUTH_REQUEST_METHOD_GET
/* define whether the issuer will be added to the redirect uri by default to mitigate the IDP mixup attack */
#define OIDC_DEFAULT_PROVIDER_ISSUER_SPECIFIC_REDIRECT_URI 0
#define OIDCProviderMetadataURL "OIDCProviderMetadataURL"
#define OIDCProviderIssuer "OIDCProviderIssuer"
#define OIDCProviderAuthorizationEndpoint "OIDCProviderAuthorizationEndpoint"
#define OIDCProviderTokenEndpoint "OIDCProviderTokenEndpoint"
#define OIDCProviderTokenEndpointAuth "OIDCProviderTokenEndpointAuth"
#define OIDCProviderTokenEndpointParams "OIDCProviderTokenEndpointParams"
#define OIDCProviderRegistrationEndpointJson "OIDCProviderRegistrationEndpointJson"
#define OIDCProviderUserInfoEndpoint "OIDCProviderUserInfoEndpoint"
#define OIDCProviderCheckSessionIFrame "OIDCProviderCheckSessionIFrame"
#define OIDCProviderEndSessionEndpoint "OIDCProviderEndSessionEndpoint"
#define OIDCProviderJwksUri "OIDCProviderJwksUri"
#define OIDCResponseType "OIDCResponseType"
#define OIDCResponseMode "OIDCResponseMode"
#define OIDCPublicKeyFiles "OIDCPublicKeyFiles"
#define OIDCClientJwksUri "OIDCClientJwksUri"
#define OIDCIDTokenSignedResponseAlg "OIDCIDTokenSignedResponseAlg"
#define OIDCIDTokenEncryptedResponseAlg "OIDCIDTokenEncryptedResponseAlg"
#define OIDCIDTokenEncryptedResponseEnc "OIDCIDTokenEncryptedResponseEnc"
#define OIDCUserInfoSignedResponseAlg "OIDCUserInfoSignedResponseAlg"
#define OIDCUserInfoEncryptedResponseAlg "OIDCUserInfoEncryptedResponseAlg"
#define OIDCUserInfoEncryptedResponseEnc "OIDCUserInfoEncryptedResponseEnc"
#define OIDCUserInfoTokenMethod "OIDCUserInfoTokenMethod"
#define OIDCTokenBindingPolicy "OIDCTokenBindingPolicy"
#define OIDCSSLValidateServer "OIDCSSLValidateServer"
#define OIDCClientName "OIDCClientName"
#define OIDCClientContact "OIDCClientContact"
#define OIDCScope "OIDCScope"
#define OIDCPathScope "OIDCPathScope"
#define OIDCJWKSRefreshInterval "OIDCJWKSRefreshInterval"
#define OIDCIDTokenIatSlack "OIDCIDTokenIatSlack"
#define OIDCSessionMaxDuration "OIDCSessionMaxDuration"
#define OIDCAuthRequestParams "OIDCAuthRequestParams"
#define OIDCPathAuthRequestParams "OIDCPathAuthRequestParams"
#define OIDCPKCEMethod "OIDCPKCEMethod"
#define OIDCClientID "OIDCClientID"
#define OIDCClientSecret "OIDCClientSecret"
#define OIDCClientTokenEndpointCert "OIDCClientTokenEndpointCert"
#define OIDCClientTokenEndpointKey "OIDCClientTokenEndpointKey"
#define OIDCDefaultLoggedOutURL "OIDCDefaultLoggedOutURL"
#define OIDCCookieHTTPOnly "OIDCCookieHTTPOnly"
#define OIDCCookieSameSite "OIDCCookieSameSite"
#define OIDCOutgoingProxy "OIDCOutgoingProxy"
#define OIDCCryptoPassphrase "OIDCCryptoPassphrase"
#define OIDCClaimDelimiter "OIDCClaimDelimiter"
#define OIDCPassIDTokenAs "OIDCPassIDTokenAs"
#define OIDCPassUserInfoAs "OIDCPassUserInfoAs"
#define OIDCOAuthClientID "OIDCOAuthClientID"
#define OIDCOAuthClientSecret "OIDCOAuthClientSecret"
#define OIDCOAuthIntrospectionClientAuthBearerToken "OIDCOAuthIntrospectionClientAuthBearerToken"
#define OIDCOAuthIntrospectionEndpoint "OIDCOAuthIntrospectionEndpoint"
#define OIDCOAuthIntrospectionEndpointMethod "OIDCOAuthIntrospectionEndpointMethod"
#define OIDCOAuthIntrospectionEndpointParams "OIDCOAuthIntrospectionEndpointParams"
#define OIDCOAuthIntrospectionEndpointAuth "OIDCOAuthIntrospectionEndpointAuth"
#define OIDCOAuthIntrospectionEndpointCert "OIDCOAuthIntrospectionEndpointCert"
#define OIDCOAuthIntrospectionEndpointKey "OIDCOAuthIntrospectionEndpointKey"
#define OIDCOAuthIntrospectionTokenParamName "OIDCOAuthIntrospectionTokenParamName"
#define OIDCOAuthTokenExpiryClaim "OIDCOAuthTokenExpiryClaim"
#define OIDCOAuthSSLValidateServer "OIDCOAuthSSLValidateServer"
#define OIDCOAuthVerifyCertFiles "OIDCOAuthVerifyCertFiles"
#define OIDCOAuthVerifySharedKeys "OIDCOAuthVerifySharedKeys"
#define OIDCOAuthVerifyJwksUri "OIDCOAuthVerifyJwksUri"
#define OIDCHTTPTimeoutLong "OIDCHTTPTimeoutLong"
#define OIDCHTTPTimeoutShort "OIDCHTTPTimeoutShort"
#define OIDCStateTimeout "OIDCStateTimeout"
#define OIDCSessionInactivityTimeout "OIDCSessionInactivityTimeout"
#define OIDCMetadataDir "OIDCMetadataDir"
#define OIDCSessionCacheFallbackToCookie "OIDCSessionCacheFallbackToCookie"
#define OIDCSessionCookieChunkSize "OIDCSessionCookieChunkSize"
#define OIDCScrubRequestHeaders "OIDCScrubRequestHeaders"
#define OIDCCacheType "OIDCCacheType"
#define OIDCCacheEncrypt "OIDCCacheEncrypt"
#define OIDCCacheDir "OIDCCacheDir"
#define OIDCCacheFileCleanInterval "OIDCCacheFileCleanInterval"
#define OIDCRedisCachePassword "OIDCRedisCachePassword"
#define OIDCHTMLErrorTemplate "OIDCHTMLErrorTemplate"
#define OIDCDiscoverURL "OIDCDiscoverURL"
#define OIDCPassCookies "OIDCPassCookies"
#define OIDCStripCookies "OIDCStripCookies"
#define OIDCAuthNHeader "OIDCAuthNHeader"
#define OIDCCookie "OIDCCookie"
#define OIDCUnAuthAction "OIDCUnAuthAction"
#define OIDCUnAutzAction "OIDCUnAutzAction"
#define OIDCPassClaimsAs "OIDCPassClaimsAs"
#define OIDCOAuthAcceptTokenAs "OIDCOAuthAcceptTokenAs"
#define OIDCUserInfoRefreshInterval "OIDCUserInfoRefreshInterval"
#define OIDCOAuthTokenIntrospectionInterval "OIDCOAuthTokenIntrospectionInterval"
#define OIDCPreservePost "OIDCPreservePost"
#define OIDCPassRefreshToken "OIDCPassRefreshToken"
#define OIDCRequestObject "OIDCRequestObject"
#define OIDCProviderMetadataRefreshInterval "OIDCProviderMetadataRefreshInterval"
#define OIDCProviderAuthRequestMethod "OIDCProviderAuthRequestMethod"
#define OIDCBlackListedClaims "OIDCBlackListedClaims"
extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
/*
* directory related configuration
*/
typedef struct oidc_dir_cfg {
char *discover_url;
char *cookie_path;
char *cookie;
char *authn_header;
int unauth_action;
int unautz_action;
apr_array_header_t *pass_cookies;
apr_array_header_t *strip_cookies;
int pass_info_in_headers;
int pass_info_in_env_vars;
int oauth_accept_token_in;
apr_hash_t *oauth_accept_token_options;
int oauth_token_introspect_interval;
int preserve_post;
int pass_refresh_token;
char *path_auth_request_params;
char *path_scope;
} oidc_dir_cfg;
#define OIDC_CONFIG_DIR_RV(cmd, rv) rv != NULL ? apr_psprintf(cmd->pool, "Invalid value for directive '%s': %s", cmd->directive->directive, rv) : NULL
/*
* set a boolean value in the server config
*/
static const char *oidc_set_flag_slot(cmd_parms *cmd, void *struct_ptr, int arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
return ap_set_flag_slot(cmd, cfg, arg);
}
/*
* set a string value in the server config
*/
static const char *oidc_set_string_slot(cmd_parms *cmd, void *struct_ptr,
const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
return ap_set_string_slot(cmd, cfg, arg);
}
/*
* set an integer value in the server config
*/
static const char *oidc_set_int_slot(cmd_parms *cmd, void *struct_ptr,
const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
return ap_set_int_slot(cmd, cfg, arg);
}
/*
* set a URL value in a config record
*/
static const char *oidc_set_url_slot_type(cmd_parms *cmd, void *ptr,
const char *arg, const char *type) {
const char *rv =
type != NULL ?
oidc_valid_url(cmd->pool, arg, type) :
oidc_valid_http_url(cmd->pool, arg);
if (rv == NULL)
rv = ap_set_string_slot(cmd, ptr, arg);
return rv;
}
/*
* set a HTTPS value in the server config
*/
static const char *oidc_set_https_slot(cmd_parms *cmd, void *ptr,
const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
return oidc_set_url_slot_type(cmd, cfg, arg, "https");
}
/*
* set a HTTPS/HTTP value in the server config
*/
static const char *oidc_set_url_slot(cmd_parms *cmd, void *ptr, const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
return oidc_set_url_slot_type(cmd, cfg, arg, NULL);
}
/*
* set a relative or absolute URL value in the server config
*/
static const char *oidc_set_relative_or_absolute_url_slot(cmd_parms *cmd,
void *ptr, const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
if (arg[0] == OIDC_CHAR_FORWARD_SLASH) {
// relative uri
apr_uri_t uri;
if (apr_uri_parse(cmd->pool, arg, &uri) != APR_SUCCESS) {
const char *rv = apr_psprintf(cmd->pool,
"cannot parse '%s' as relative URI", arg);
return OIDC_CONFIG_DIR_RV(cmd, rv);
} else {
return ap_set_string_slot(cmd, cfg, arg);
}
} else {
// absolute uri
return oidc_set_url_slot_type(cmd, cfg, arg, NULL);
}
}
/*
* set a HTTPS/HTTP value in the directory config
*/
static const char *oidc_set_url_slot_dir_cfg(cmd_parms *cmd, void *ptr,
const char *arg) {
return oidc_set_url_slot_type(cmd, ptr, arg, NULL);
}
/*
* set a directory value in the server config
*/
// TODO: it's not really a syntax error... (could be fixed at runtime but then we'd have to restart the server)
static const char *oidc_set_dir_slot(cmd_parms *cmd, void *ptr, const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_valid_dir(cmd->pool, arg);
if (rv == NULL)
rv = ap_set_string_slot(cmd, cfg, arg);
return rv;
}
/*
* set the cookie domain in the server config and check it syntactically
*/
static const char *oidc_set_cookie_domain(cmd_parms *cmd, void *ptr,
const char *value) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_valid_cookie_domain(cmd->pool, value);
if (rv == NULL)
cfg->cookie_domain = apr_pstrdup(cmd->pool, value);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set the session storage type
*/
static const char *oidc_set_session_type(cmd_parms *cmd, void *ptr,
const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_parse_session_type(cmd->pool, arg, &cfg->session_type,
&cfg->persistent_session_cookie);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set the maximum size of a shared memory cache entry and enforces a minimum
*/
static const char *oidc_set_cache_shm_entry_size_max(cmd_parms *cmd, void *ptr,
const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_parse_cache_shm_entry_size_max(cmd->pool, arg,
&cfg->cache_shm_entry_size_max);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set the cache type
*/
static const char *oidc_set_cache_type(cmd_parms *cmd, void *ptr,
const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_parse_cache_type(cmd->pool, arg, &cfg->cache);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set SSL validation slot
*/
static const char *oidc_set_ssl_validate_slot(cmd_parms *cmd, void *struct_ptr,
const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
int b = 0;
const char *rv = oidc_parse_boolean(cmd->pool, arg, &b);
if (rv == NULL)
rv = ap_set_flag_slot(cmd, cfg, b);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* return the right token endpoint authentication method validation function, based on whether private keys are set
*/
oidc_valid_function_t oidc_cfg_get_valid_endpoint_auth_function(oidc_cfg *cfg) {
return (cfg->private_keys != NULL) ?
oidc_valid_endpoint_auth_method :
oidc_valid_endpoint_auth_method_no_private_key;
}
/*
* set an authentication method for an endpoint and check it is one that we support
*/
static const char *oidc_set_endpoint_auth_slot(cmd_parms *cmd, void *struct_ptr,
const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_cfg_get_valid_endpoint_auth_function(cfg)(cmd->pool,
arg);
if (rv == NULL)
rv = ap_set_string_slot(cmd, cfg, arg);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set the response type used
*/
static const char *oidc_set_response_type(cmd_parms *cmd, void *struct_ptr,
const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_valid_response_type(cmd->pool, arg);
if (rv == NULL)
rv = ap_set_string_slot(cmd, cfg, arg);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
const char *oidc_parse_pkce_type(apr_pool_t *pool, const char *arg,
oidc_proto_pkce_t **type) {
const char *rv = oidc_valid_pkce_method(pool, arg);
if (rv != NULL)
return rv;
if (apr_strnatcmp(arg, OIDC_PKCE_METHOD_PLAIN) == 0) {
*type = &oidc_pkce_plain;
} else if (apr_strnatcmp(arg, OIDC_PKCE_METHOD_S256) == 0) {
*type = &oidc_pkce_s256;
} else if (apr_strnatcmp(arg, OIDC_PKCE_METHOD_REFERRED_TB) == 0) {
*type = &oidc_pkce_referred_tb;
}
return NULL;
}
/*
* define the PCKE method to use
*/
static const char *oidc_set_pkce_method(cmd_parms *cmd, void *ptr,
const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_parse_pkce_type(cmd->pool, arg, &cfg->provider.pkce);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set the response mode used
*/
static const char *oidc_set_response_mode(cmd_parms *cmd, void *struct_ptr,
const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_valid_response_mode(cmd->pool, arg);
if (rv == NULL)
rv = ap_set_string_slot(cmd, cfg, arg);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set the signing algorithm to be used by the OP (id_token/user_info)
*/
static const char *oidc_set_signed_response_alg(cmd_parms *cmd,
void *struct_ptr, const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_valid_signed_response_alg(cmd->pool, arg);
if (rv == NULL)
rv = ap_set_string_slot(cmd, cfg, arg);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set the Content Encryption Key encryption algorithm to be used by the OP (id_token/user_info)
*/
static const char *oidc_set_encrypted_response_alg(cmd_parms *cmd,
void *struct_ptr, const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_valid_encrypted_response_alg(cmd->pool, arg);
if (rv == NULL)
rv = ap_set_string_slot(cmd, cfg, arg);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set the content encryption algorithm to be used by the OP (id_token/user_info)
*/
static const char *oidc_set_encrypted_response_enc(cmd_parms *cmd,
void *struct_ptr, const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_valid_encrypted_response_enc(cmd->pool, arg);
if (rv == NULL)
rv = ap_set_string_slot(cmd, cfg, arg);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set the userinfo endpoint token presentation method
*/
static const char *oidc_set_userinfo_token_method(cmd_parms *cmd,
void *struct_ptr, const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_parse_userinfo_token_method(cmd->pool, arg,
&cfg->provider.userinfo_token_method);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set the session inactivity timeout
*/
static const char *oidc_set_session_inactivity_timeout(cmd_parms *cmd,
void *struct_ptr, const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_parse_session_inactivity_timeout(cmd->pool, arg,
&cfg->session_inactivity_timeout);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set the maximum session duration; 0 means take it from the ID token expiry time
*/
static const char *oidc_set_session_max_duration(cmd_parms *cmd,
void *struct_ptr, const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_parse_session_max_duration(cmd->pool, arg,
&cfg->provider.session_max_duration);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* add a public key from an X.509 file to our list of JWKs with public keys
*/
static const char *oidc_set_public_key_files(cmd_parms *cmd, void *struct_ptr,
const char *arg) {
oidc_jwk_t *jwk = NULL;
oidc_jose_error_t err;
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
int offset = (int) (long) cmd->info;
apr_hash_t **public_keys = (apr_hash_t **) ((char *) cfg + offset);
char *kid = NULL, *fname = NULL;
int fname_len;
const char *rv = oidc_parse_enc_kid_key_tuple(cmd->pool, arg, &kid, &fname,
&fname_len, FALSE);
if (rv != NULL)
return rv;
fname = oidc_util_get_full_path(cmd->pool, fname);
if (oidc_jwk_parse_rsa_public_key(cmd->pool, kid, fname, &jwk,
&err) == FALSE) {
return apr_psprintf(cmd->pool,
"oidc_jwk_parse_rsa_public_key failed for (kid=%s) \"%s\": %s",
kid, fname, oidc_jose_e2s(cmd->pool, err));
}
if (*public_keys == NULL)
*public_keys = apr_hash_make(cmd->pool);
apr_hash_set(*public_keys, jwk->kid, APR_HASH_KEY_STRING, jwk);
return NULL;
}
/*
* add a shared key to a list of JWKs with shared keys
*/
static const char *oidc_set_shared_keys(cmd_parms *cmd, void *struct_ptr,
const char *arg) {
oidc_jose_error_t err;
oidc_jwk_t *jwk = NULL;
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
int offset = (int) (long) cmd->info;
apr_hash_t **shared_keys = (apr_hash_t **) ((char *) cfg + offset);
char *kid = NULL, *secret = NULL;
int key_len = 0;
const char *rv = oidc_parse_enc_kid_key_tuple(cmd->pool, arg, &kid, &secret,
&key_len, TRUE);
if (rv != NULL)
return rv;
jwk = oidc_jwk_create_symmetric_key(cmd->pool, kid,
(const unsigned char *) secret, key_len, TRUE, &err);
if (jwk == NULL) {
return apr_psprintf(cmd->pool,
"oidc_jwk_create_symmetric_key failed for (kid=%s) \"%s\": %s",
kid, secret, oidc_jose_e2s(cmd->pool, err));
}
if (*shared_keys == NULL)
*shared_keys = apr_hash_make(cmd->pool);
apr_hash_set(*shared_keys, jwk->kid,
APR_HASH_KEY_STRING, jwk);
return NULL;
}
/*
* add a private key from an RSA private key file to our list of JWKs with private keys
*/
static const char *oidc_set_private_key_files_enc(cmd_parms *cmd, void *dummy,
const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
oidc_jwk_t *jwk = NULL;
oidc_jose_error_t err;
char *kid = NULL, *fname = NULL;
int fname_len;
const char *rv = oidc_parse_enc_kid_key_tuple(cmd->pool, arg, &kid, &fname,
&fname_len, FALSE);
if (rv != NULL)
return rv;
fname = oidc_util_get_full_path(cmd->pool, fname);
if (oidc_jwk_parse_rsa_private_key(cmd->pool, kid, fname, &jwk,
&err) == FALSE) {
return apr_psprintf(cmd->pool,
"oidc_jwk_parse_rsa_private_key failed for (kid=%s) \"%s\": %s",
kid, fname, oidc_jose_e2s(cmd->pool, err));
}
if (cfg->private_keys == NULL)
cfg->private_keys = apr_hash_make(cmd->pool);
apr_hash_set(cfg->private_keys, jwk->kid, APR_HASH_KEY_STRING, jwk);
return NULL;
}
/*
* define how to pass the id_token/claims in HTTP headers
*/
static const char * oidc_set_pass_idtoken_as(cmd_parms *cmd, void *dummy,
const char *v1, const char *v2, const char *v3) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_parse_pass_idtoken_as(cmd->pool, v1, v2, v3,
&cfg->pass_idtoken_as);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* define how to pass the userinfo/claims in HTTP headers
*/
static const char * oidc_set_pass_userinfo_as(cmd_parms *cmd, void *dummy,
const char *v1, const char *v2, const char *v3) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_parse_pass_userinfo_as(cmd->pool, v1, v2, v3,
&cfg->pass_userinfo_as);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* define which method of pass an OAuth Bearer token is accepted
*/
static const char * oidc_set_accept_oauth_token_in(cmd_parms *cmd, void *m,
const char *arg) {
oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *) m;
const char *rv = oidc_parse_accept_oauth_token_in(cmd->pool, arg,
&dir_cfg->oauth_accept_token_in,
dir_cfg->oauth_accept_token_options);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set the syntax of the token expiry claim in the introspection response
*/
static const char * oidc_set_token_expiry_claim(cmd_parms *cmd, void *dummy,
const char *claim_name, const char *claim_format,
const char *claim_required) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = NULL;
cfg->oauth.introspection_token_expiry_claim_name = apr_pstrdup(cmd->pool,
claim_name);
if ((rv == NULL) && (claim_format != NULL)) {
rv = oidc_valid_claim_format(cmd->pool, claim_format);
if (rv == NULL)
cfg->oauth.introspection_token_expiry_claim_format = apr_pstrdup(
cmd->pool, claim_format);
}
if ((rv == NULL) && (claim_required != NULL)) {
rv = oidc_parse_claim_required(cmd->pool, claim_required,
&cfg->oauth.introspection_token_expiry_claim_required);
}
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* specify cookies names to pass/strip
*/
static const char * oidc_set_cookie_names(cmd_parms *cmd, void *m,
const char *arg) {
oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *) m;
int offset = (int) (long) cmd->info;
apr_array_header_t **cookie_names =
(apr_array_header_t **) ((char *) dir_cfg + offset);
if (*cookie_names == NULL)
*cookie_names = apr_array_make(cmd->pool, 3, sizeof(const char *));
*(const char**) apr_array_push((*cookie_names)) = arg;
return NULL;
}
/*
* set the HTTP method to use in an OAuth 2.0 token introspection/validation call
*/
static const char * oidc_set_introspection_method(cmd_parms *cmd, void *m,
const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_valid_introspection_method(cmd->pool, arg);
if (rv == NULL)
rv = ap_set_string_slot(cmd, cfg, arg);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set POST preservation behavior
*/
static const char *oidc_set_preserve_post(cmd_parms *cmd, void *m,
const char *arg) {
oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *) m;
int b = 0;
const char *rv = oidc_parse_boolean(cmd->pool, arg, &b);
if (rv == NULL)
rv = ap_set_flag_slot(cmd, dir_cfg, b);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set the remote user name claims, optionally plus the regular expression applied to it
*/
static const char *oidc_set_remote_user_claim(cmd_parms *cmd, void *struct_ptr,
const char *v1, const char *v2, const char *v3) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
int offset = (int) (long) cmd->info;
oidc_remote_user_claim_t *remote_user_claim =
(oidc_remote_user_claim_t *) ((char *) cfg + offset);
remote_user_claim->claim_name = v1;
if (v2)
remote_user_claim->reg_exp = v2;
if (v3)
remote_user_claim->replace = v3;
return NULL;
}
/*
* define how to pass claims information to the application: in headers and/or environment variables
*/
static const char * oidc_set_pass_claims_as(cmd_parms *cmd, void *m,
const char *arg) {
oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *) m;
const char *rv = oidc_parse_set_claims_as(cmd->pool, arg,
&dir_cfg->pass_info_in_headers, &dir_cfg->pass_info_in_env_vars);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* define how to act on unauthenticated requests
*/
static const char * oidc_set_unauth_action(cmd_parms *cmd, void *m,
const char *arg) {
oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *) m;
const char *rv = oidc_parse_unauth_action(cmd->pool, arg,
&dir_cfg->unauth_action);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* define how to act on unauthorized requests
*/
static const char * oidc_set_unautz_action(cmd_parms *cmd, void *m,
const char *arg) {
oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *) m;
const char *rv = oidc_parse_unautz_action(cmd->pool, arg,
&dir_cfg->unautz_action);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set the JWKS refresh interval
*/
static const char *oidc_set_jwks_refresh_interval(cmd_parms *cmd,
void *struct_ptr, const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_parse_jwks_refresh_interval(cmd->pool, arg,
&cfg->provider.jwks_refresh_interval);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set the ID token "iat" slack
*/
static const char *oidc_set_idtoken_iat_slack(cmd_parms *cmd, void *struct_ptr,
const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_parse_idtoken_iat_slack(cmd->pool, arg,
&cfg->provider.idtoken_iat_slack);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set the userinfo refresh interval
*/
static const char *oidc_set_userinfo_refresh_interval(cmd_parms *cmd,
void *struct_ptr, const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_parse_userinfo_refresh_interval(cmd->pool, arg,
&cfg->provider.userinfo_refresh_interval);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* define which data will be returned from the info hook
*/
static const char * oidc_set_info_hook_data(cmd_parms *cmd, void *m,
const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_parse_info_hook_data(cmd->pool, arg,
&cfg->info_hook_data);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
static const char * oidc_set_filtered_claims(cmd_parms *cmd, void *m,
const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
int offset = (int) (long) cmd->info;
apr_hash_t **list = (apr_hash_t **) ((char *) cfg + offset);
if (*list == NULL)
*list = apr_hash_make(cmd->pool);
apr_hash_set(*list, arg, APR_HASH_KEY_STRING, arg);
return NULL;
}
/*
* set the token binding policy
*/
static const char *oidc_set_token_binding_policy(cmd_parms *cmd,
void *struct_ptr, const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_parse_token_binding_policy(cmd->pool, arg,
&cfg->provider.token_binding_policy);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set the claim prefix
*/
static const char *oidc_cfg_set_claim_prefix(cmd_parms *cmd, void *struct_ptr,
const char *args) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
char *w = ap_getword_conf(cmd->pool, &args);
if (*w == '\0' || *args != 0)
cfg->claim_prefix = "";
else
cfg->claim_prefix = w;
return NULL;
}
/*
* get the claim prefix
*/
const char *oidc_cfg_claim_prefix(request_rec *r) {
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
if (cfg->claim_prefix == NULL)
return OIDC_DEFAULT_CLAIM_PREFIX;
return cfg->claim_prefix;
}
/*
* set the HTTP method used to send the authentication request to the provider
*/
const char *oidc_set_auth_request_method(cmd_parms *cmd, void *struct_ptr,
const char *arg) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
const char *rv = oidc_parse_auth_request_method(cmd->pool, arg,
&cfg->provider.auth_request_method);
return OIDC_CONFIG_DIR_RV(cmd, rv);
}
/*
* set the introspection authorization static bearer token
*/
static const char *oidc_set_client_auth_bearer_token(cmd_parms *cmd,
void *struct_ptr, const char *args) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(
cmd->server->module_config, &auth_openidc_module);
char *w = ap_getword_conf(cmd->pool, &args);
cfg->oauth.introspection_client_auth_bearer_token =
(*w == '\0' || *args != 0) ? "" : w;
return NULL;
}
/*
* create a new server config record with defaults
*/
void *oidc_create_server_config(apr_pool_t *pool, server_rec *svr) {
oidc_cfg *c = apr_pcalloc(pool, sizeof(oidc_cfg));
c->merged = FALSE;
c->redirect_uri = NULL;
c->default_sso_url = NULL;
c->default_slo_url = NULL;
c->public_keys = NULL;
c->private_keys = NULL;
c->provider.metadata_url = NULL;
c->provider.issuer = NULL;
c->provider.authorization_endpoint_url = NULL;
c->provider.token_endpoint_url = NULL;
c->provider.token_endpoint_auth = NULL;
c->provider.token_endpoint_params = NULL;
c->provider.userinfo_endpoint_url = NULL;
c->provider.client_id = NULL;
c->provider.client_secret = NULL;
c->provider.token_endpoint_tls_client_cert = NULL;
c->provider.token_endpoint_tls_client_key = NULL;
c->provider.registration_endpoint_url = NULL;
c->provider.registration_endpoint_json = NULL;
c->provider.check_session_iframe = NULL;
c->provider.end_session_endpoint = NULL;
c->provider.jwks_uri = NULL;
c->provider.ssl_validate_server = OIDC_DEFAULT_SSL_VALIDATE_SERVER;
c->provider.client_name = OIDC_DEFAULT_CLIENT_NAME;
c->provider.client_contact = NULL;
c->provider.registration_token = NULL;
c->provider.scope = OIDC_DEFAULT_SCOPE;
c->provider.response_type = OIDC_DEFAULT_RESPONSE_TYPE;
c->provider.response_mode = NULL;
c->provider.jwks_refresh_interval = OIDC_DEFAULT_JWKS_REFRESH_INTERVAL;
c->provider.idtoken_iat_slack = OIDC_DEFAULT_IDTOKEN_IAT_SLACK;
c->provider.session_max_duration = OIDC_DEFAULT_SESSION_MAX_DURATION;
c->provider.auth_request_params = NULL;
c->provider.pkce = NULL;
c->provider.client_jwks_uri = NULL;
c->provider.id_token_signed_response_alg = NULL;
c->provider.id_token_encrypted_response_alg = NULL;
c->provider.id_token_encrypted_response_enc = NULL;
c->provider.userinfo_signed_response_alg = NULL;
c->provider.userinfo_encrypted_response_alg = NULL;
c->provider.userinfo_encrypted_response_enc = NULL;
c->provider.userinfo_token_method = OIDC_USER_INFO_TOKEN_METHOD_HEADER;
c->provider.auth_request_method = OIDC_DEFAULT_AUTH_REQUEST_METHOD;
c->oauth.ssl_validate_server = OIDC_DEFAULT_SSL_VALIDATE_SERVER;
c->oauth.client_id = NULL;
c->oauth.client_secret = NULL;
c->oauth.introspection_endpoint_tls_client_cert = NULL;
c->oauth.introspection_endpoint_tls_client_key = NULL;
c->oauth.introspection_endpoint_url = NULL;
c->oauth.introspection_endpoint_method = OIDC_DEFAULT_OAUTH_ENDPOINT_METHOD;
c->oauth.introspection_endpoint_params = NULL;
c->oauth.introspection_endpoint_auth = NULL;
c->oauth.introspection_client_auth_bearer_token = NULL;
c->oauth.introspection_token_param_name =
OIDC_DEFAULT_OAUTH_TOKEN_PARAM_NAME;
c->oauth.introspection_token_expiry_claim_name =
OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_NAME;
c->oauth.introspection_token_expiry_claim_format =
OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_FORMAT;
c->oauth.introspection_token_expiry_claim_required =
OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_REQUIRED;
c->oauth.remote_user_claim.claim_name =
OIDC_DEFAULT_OAUTH_CLAIM_REMOTE_USER;
c->oauth.remote_user_claim.reg_exp = NULL;
c->oauth.remote_user_claim.replace = NULL;
c->oauth.verify_jwks_uri = NULL;
c->oauth.verify_public_keys = NULL;
c->oauth.verify_shared_keys = NULL;
c->cache = &oidc_cache_shm;
c->cache_cfg = NULL;
c->cache_encrypt = OIDC_CONFIG_POS_INT_UNSET;
c->cache_file_dir = NULL;
c->cache_file_clean_interval = OIDC_DEFAULT_CACHE_FILE_CLEAN_INTERVAL;
c->cache_memcache_servers = NULL;
c->cache_shm_size_max = OIDC_DEFAULT_CACHE_SHM_SIZE;
c->cache_shm_entry_size_max = OIDC_DEFAULT_CACHE_SHM_ENTRY_SIZE_MAX;
#ifdef USE_LIBHIREDIS
c->cache_redis_server = NULL;
c->cache_redis_password = NULL;
#endif
c->metadata_dir = NULL;
c->session_type = OIDC_DEFAULT_SESSION_TYPE;
c->session_cache_fallback_to_cookie = OIDC_CONFIG_POS_INT_UNSET;
c->persistent_session_cookie = 0;
c->session_cookie_chunk_size =
OIDC_DEFAULT_SESSION_CLIENT_COOKIE_CHUNK_SIZE;
c->http_timeout_long = OIDC_DEFAULT_HTTP_TIMEOUT_LONG;
c->http_timeout_short = OIDC_DEFAULT_HTTP_TIMEOUT_SHORT;
c->state_timeout = OIDC_DEFAULT_STATE_TIMEOUT;
c->session_inactivity_timeout = OIDC_DEFAULT_SESSION_INACTIVITY_TIMEOUT;
c->cookie_domain = NULL;
c->claim_delimiter = OIDC_DEFAULT_CLAIM_DELIMITER;
c->claim_prefix = NULL;
c->remote_user_claim.claim_name = OIDC_DEFAULT_CLAIM_REMOTE_USER;
c->remote_user_claim.reg_exp = NULL;
c->remote_user_claim.replace = NULL;
c->pass_idtoken_as = OIDC_PASS_IDTOKEN_AS_CLAIMS;
c->pass_userinfo_as = OIDC_PASS_USERINFO_AS_CLAIMS;
c->cookie_http_only = OIDC_DEFAULT_COOKIE_HTTPONLY;
c->cookie_same_site = OIDC_DEFAULT_COOKIE_SAME_SITE;
c->outgoing_proxy = NULL;
c->crypto_passphrase = NULL;
c->scrub_request_headers = OIDC_DEFAULT_SCRUB_REQUEST_HEADERS;
c->error_template = NULL;
c->provider.userinfo_refresh_interval =
OIDC_DEFAULT_USERINFO_REFRESH_INTERVAL;
c->provider.request_object = NULL;
c->provider_metadata_refresh_interval =
OIDC_DEFAULT_PROVIDER_METADATA_REFRESH_INTERVAL;
c->provider.token_binding_policy =
OIDC_DEFAULT_PROVIDER_TOKEN_BINDING_POLICY;
c->info_hook_data = NULL;
c->black_listed_claims = NULL;
c->white_listed_claims = NULL;
c->provider.issuer_specific_redirect_uri =
OIDC_DEFAULT_PROVIDER_ISSUER_SPECIFIC_REDIRECT_URI;
return c;
}
/*
* merge a new server config with a base one
*/
void *oidc_merge_server_config(apr_pool_t *pool, void *BASE, void *ADD) {
oidc_cfg *c = apr_pcalloc(pool, sizeof(oidc_cfg));
oidc_cfg *base = BASE;
oidc_cfg *add = ADD;
c->merged = TRUE;
c->redirect_uri =
add->redirect_uri != NULL ? add->redirect_uri : base->redirect_uri;
c->default_sso_url =
add->default_sso_url != NULL ?
add->default_sso_url : base->default_sso_url;
c->default_slo_url =
add->default_slo_url != NULL ?
add->default_slo_url : base->default_slo_url;
c->public_keys =
add->public_keys != NULL ? add->public_keys : base->public_keys;
c->private_keys =
add->private_keys != NULL ? add->private_keys : base->private_keys;
c->provider.metadata_url =
add->provider.metadata_url != NULL ?
add->provider.metadata_url : base->provider.metadata_url;
c->provider.issuer =
add->provider.issuer != NULL ?
add->provider.issuer : base->provider.issuer;
c->provider.authorization_endpoint_url =
add->provider.authorization_endpoint_url != NULL ?
add->provider.authorization_endpoint_url :
base->provider.authorization_endpoint_url;
c->provider.token_endpoint_url =
add->provider.token_endpoint_url != NULL ?
add->provider.token_endpoint_url :
base->provider.token_endpoint_url;
c->provider.token_endpoint_auth =
add->provider.token_endpoint_auth != NULL ?
add->provider.token_endpoint_auth :
base->provider.token_endpoint_auth;
c->provider.token_endpoint_params =
add->provider.token_endpoint_params != NULL ?
add->provider.token_endpoint_params :
base->provider.token_endpoint_params;
c->provider.userinfo_endpoint_url =
add->provider.userinfo_endpoint_url != NULL ?
add->provider.userinfo_endpoint_url :
base->provider.userinfo_endpoint_url;
c->provider.jwks_uri =
add->provider.jwks_uri != NULL ?
add->provider.jwks_uri : base->provider.jwks_uri;
c->provider.client_id =
add->provider.client_id != NULL ?
add->provider.client_id : base->provider.client_id;
c->provider.client_secret =
add->provider.client_secret != NULL ?
add->provider.client_secret : base->provider.client_secret;
c->provider.token_endpoint_tls_client_key =
add->provider.token_endpoint_tls_client_key != NULL ?
add->provider.token_endpoint_tls_client_key :
base->provider.token_endpoint_tls_client_key;
c->provider.token_endpoint_tls_client_cert =
add->provider.token_endpoint_tls_client_cert != NULL ?
add->provider.token_endpoint_tls_client_cert :
base->provider.token_endpoint_tls_client_cert;
c->provider.registration_endpoint_url =
add->provider.registration_endpoint_url != NULL ?
add->provider.registration_endpoint_url :
base->provider.registration_endpoint_url;
c->provider.registration_endpoint_json =
add->provider.registration_endpoint_json != NULL ?
add->provider.registration_endpoint_json :
base->provider.registration_endpoint_json;
c->provider.check_session_iframe =
add->provider.check_session_iframe != NULL ?
add->provider.check_session_iframe :
base->provider.check_session_iframe;
c->provider.end_session_endpoint =
add->provider.end_session_endpoint != NULL ?
add->provider.end_session_endpoint :
base->provider.end_session_endpoint;
c->provider.ssl_validate_server =
add->provider.ssl_validate_server
!= OIDC_DEFAULT_SSL_VALIDATE_SERVER ?
add->provider.ssl_validate_server :
base->provider.ssl_validate_server;
c->provider.client_name =
apr_strnatcmp(add->provider.client_name, OIDC_DEFAULT_CLIENT_NAME)
!= 0 ?
add->provider.client_name : base->provider.client_name;
c->provider.client_contact =
add->provider.client_contact != NULL ?
add->provider.client_contact :
base->provider.client_contact;
c->provider.registration_token =
add->provider.registration_token != NULL ?
add->provider.registration_token :
base->provider.registration_token;
c->provider.scope =
apr_strnatcmp(add->provider.scope, OIDC_DEFAULT_SCOPE) != 0 ?
add->provider.scope : base->provider.scope;
c->provider.response_type =
apr_strnatcmp(add->provider.response_type,
OIDC_DEFAULT_RESPONSE_TYPE) != 0 ?
add->provider.response_type : base->provider.response_type;
c->provider.response_mode =
add->provider.response_mode != NULL ?
add->provider.response_mode : base->provider.response_mode;
c->provider.jwks_refresh_interval =
add->provider.jwks_refresh_interval
!= OIDC_DEFAULT_JWKS_REFRESH_INTERVAL ?
add->provider.jwks_refresh_interval :
base->provider.jwks_refresh_interval;
c->provider.idtoken_iat_slack =
add->provider.idtoken_iat_slack != OIDC_DEFAULT_IDTOKEN_IAT_SLACK ?
add->provider.idtoken_iat_slack :
base->provider.idtoken_iat_slack;
c->provider.session_max_duration =
add->provider.session_max_duration
!= OIDC_DEFAULT_SESSION_MAX_DURATION ?
add->provider.session_max_duration :
base->provider.session_max_duration;
c->provider.auth_request_params =
add->provider.auth_request_params != NULL ?
add->provider.auth_request_params :
base->provider.auth_request_params;
c->provider.pkce =
add->provider.pkce != NULL ?
add->provider.pkce : base->provider.pkce;
c->provider.client_jwks_uri =
add->provider.client_jwks_uri != NULL ?
add->provider.client_jwks_uri :
base->provider.client_jwks_uri;
c->provider.id_token_signed_response_alg =
add->provider.id_token_signed_response_alg != NULL ?
add->provider.id_token_signed_response_alg :
base->provider.id_token_signed_response_alg;
c->provider.id_token_encrypted_response_alg =
add->provider.id_token_encrypted_response_alg != NULL ?
add->provider.id_token_encrypted_response_alg :
base->provider.id_token_encrypted_response_alg;
c->provider.id_token_encrypted_response_enc =
add->provider.id_token_encrypted_response_enc != NULL ?
add->provider.id_token_encrypted_response_enc :
base->provider.id_token_encrypted_response_enc;
c->provider.userinfo_signed_response_alg =
add->provider.userinfo_signed_response_alg != NULL ?
add->provider.userinfo_signed_response_alg :
base->provider.userinfo_signed_response_alg;
c->provider.userinfo_encrypted_response_alg =
add->provider.userinfo_encrypted_response_alg != NULL ?
add->provider.userinfo_encrypted_response_alg :
base->provider.userinfo_encrypted_response_alg;
c->provider.userinfo_encrypted_response_enc =
add->provider.userinfo_encrypted_response_enc != NULL ?
add->provider.userinfo_encrypted_response_enc :
base->provider.userinfo_encrypted_response_enc;
c->provider.userinfo_token_method =
add->provider.userinfo_token_method
!= OIDC_USER_INFO_TOKEN_METHOD_HEADER ?
add->provider.userinfo_token_method :
base->provider.userinfo_token_method;
c->provider.auth_request_method =
add->provider.auth_request_method
!= OIDC_DEFAULT_AUTH_REQUEST_METHOD ?
add->provider.auth_request_method :
base->provider.auth_request_method;
c->oauth.ssl_validate_server =
add->oauth.ssl_validate_server != OIDC_DEFAULT_SSL_VALIDATE_SERVER ?
add->oauth.ssl_validate_server :
base->oauth.ssl_validate_server;
c->oauth.client_id =
add->oauth.client_id != NULL ?
add->oauth.client_id : base->oauth.client_id;
c->oauth.client_secret =
add->oauth.client_secret != NULL ?
add->oauth.client_secret : base->oauth.client_secret;
c->oauth.introspection_endpoint_tls_client_key =
add->oauth.introspection_endpoint_tls_client_key != NULL ?
add->oauth.introspection_endpoint_tls_client_key :
base->oauth.introspection_endpoint_tls_client_key;
c->oauth.introspection_endpoint_tls_client_cert =
add->oauth.introspection_endpoint_tls_client_cert != NULL ?
add->oauth.introspection_endpoint_tls_client_cert :
base->oauth.introspection_endpoint_tls_client_cert;
c->oauth.introspection_endpoint_url =
add->oauth.introspection_endpoint_url != NULL ?
add->oauth.introspection_endpoint_url :
base->oauth.introspection_endpoint_url;
c->oauth.introspection_endpoint_method =
apr_strnatcmp(add->oauth.introspection_endpoint_method,
OIDC_DEFAULT_OAUTH_ENDPOINT_METHOD) != 0 ?
add->oauth.introspection_endpoint_method :
base->oauth.introspection_endpoint_method;
c->oauth.introspection_endpoint_params =
add->oauth.introspection_endpoint_params != NULL ?
add->oauth.introspection_endpoint_params :
base->oauth.introspection_endpoint_params;
c->oauth.introspection_endpoint_auth =
add->oauth.introspection_endpoint_auth != NULL ?
add->oauth.introspection_endpoint_auth :
base->oauth.introspection_endpoint_auth;
c->oauth.introspection_client_auth_bearer_token =
add->oauth.introspection_client_auth_bearer_token != NULL ?
add->oauth.introspection_client_auth_bearer_token :
base->oauth.introspection_client_auth_bearer_token;
c->oauth.introspection_token_param_name =
apr_strnatcmp(add->oauth.introspection_token_param_name,
OIDC_DEFAULT_OAUTH_TOKEN_PARAM_NAME) != 0 ?
add->oauth.introspection_token_param_name :
base->oauth.introspection_token_param_name;
c->oauth.introspection_token_expiry_claim_name =
apr_strnatcmp(add->oauth.introspection_token_expiry_claim_name,
OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_NAME) != 0 ?
add->oauth.introspection_token_expiry_claim_name :
base->oauth.introspection_token_expiry_claim_name;
c->oauth.introspection_token_expiry_claim_format =
apr_strnatcmp(add->oauth.introspection_token_expiry_claim_format,
OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_FORMAT) != 0 ?
add->oauth.introspection_token_expiry_claim_format :
base->oauth.introspection_token_expiry_claim_format;
c->oauth.introspection_token_expiry_claim_required =
add->oauth.introspection_token_expiry_claim_required
!= OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_REQUIRED ?
add->oauth.introspection_token_expiry_claim_required :
base->oauth.introspection_token_expiry_claim_required;
c->oauth.remote_user_claim.claim_name =
apr_strnatcmp(add->oauth.remote_user_claim.claim_name,
OIDC_DEFAULT_OAUTH_CLAIM_REMOTE_USER) != 0 ?
add->oauth.remote_user_claim.claim_name :
base->oauth.remote_user_claim.claim_name;
c->oauth.remote_user_claim.reg_exp =
add->oauth.remote_user_claim.reg_exp != NULL ?
add->oauth.remote_user_claim.reg_exp :
base->oauth.remote_user_claim.reg_exp;
c->oauth.remote_user_claim.replace =
add->oauth.remote_user_claim.replace != NULL ?
add->oauth.remote_user_claim.replace :
base->oauth.remote_user_claim.replace;
c->oauth.verify_jwks_uri =
add->oauth.verify_jwks_uri != NULL ?
add->oauth.verify_jwks_uri : base->oauth.verify_jwks_uri;
c->oauth.verify_public_keys =
add->oauth.verify_public_keys != NULL ?
add->oauth.verify_public_keys :
base->oauth.verify_public_keys;
c->oauth.verify_shared_keys =
add->oauth.verify_shared_keys != NULL ?
add->oauth.verify_shared_keys :
base->oauth.verify_shared_keys;
c->http_timeout_long =
add->http_timeout_long != OIDC_DEFAULT_HTTP_TIMEOUT_LONG ?
add->http_timeout_long : base->http_timeout_long;
c->http_timeout_short =
add->http_timeout_short != OIDC_DEFAULT_HTTP_TIMEOUT_SHORT ?
add->http_timeout_short : base->http_timeout_short;
c->state_timeout =
add->state_timeout != OIDC_DEFAULT_STATE_TIMEOUT ?
add->state_timeout : base->state_timeout;
c->session_inactivity_timeout =
add->session_inactivity_timeout
!= OIDC_DEFAULT_SESSION_INACTIVITY_TIMEOUT ?
add->session_inactivity_timeout :
base->session_inactivity_timeout;
if (add->cache != &oidc_cache_shm) {
c->cache = add->cache;
} else {
c->cache = base->cache;
}
c->cache_encrypt =
add->cache_encrypt != OIDC_CONFIG_POS_INT_UNSET ?
add->cache_encrypt : base->cache_encrypt;
c->cache_cfg = NULL;
c->cache_file_dir =
add->cache_file_dir != NULL ?
add->cache_file_dir : base->cache_file_dir;
c->cache_file_clean_interval =
add->cache_file_clean_interval
!= OIDC_DEFAULT_CACHE_FILE_CLEAN_INTERVAL ?
add->cache_file_clean_interval :
base->cache_file_clean_interval;
c->cache_memcache_servers =
add->cache_memcache_servers != NULL ?
add->cache_memcache_servers : base->cache_memcache_servers;
c->cache_shm_size_max =
add->cache_shm_size_max != OIDC_DEFAULT_CACHE_SHM_SIZE ?
add->cache_shm_size_max : base->cache_shm_size_max;
c->cache_shm_entry_size_max =
add->cache_shm_entry_size_max
!= OIDC_DEFAULT_CACHE_SHM_ENTRY_SIZE_MAX ?
add->cache_shm_entry_size_max :
base->cache_shm_entry_size_max;
#ifdef USE_LIBHIREDIS
c->cache_redis_server =
add->cache_redis_server != NULL ?
add->cache_redis_server : base->cache_redis_server;
c->cache_redis_password =
add->cache_redis_password != NULL ?
add->cache_redis_password : base->cache_redis_password;
#endif
c->metadata_dir =
add->metadata_dir != NULL ? add->metadata_dir : base->metadata_dir;
c->session_type =
add->session_type != OIDC_DEFAULT_SESSION_TYPE ?
add->session_type : base->session_type;
c->session_cache_fallback_to_cookie =
add->session_cache_fallback_to_cookie != OIDC_CONFIG_POS_INT_UNSET ?
add->session_cache_fallback_to_cookie :
base->session_cache_fallback_to_cookie;
c->persistent_session_cookie =
add->persistent_session_cookie != 0 ?
add->persistent_session_cookie :
base->persistent_session_cookie;
c->session_cookie_chunk_size =
add->session_cookie_chunk_size
!= OIDC_DEFAULT_SESSION_CLIENT_COOKIE_CHUNK_SIZE ?
add->session_cookie_chunk_size :
base->session_cookie_chunk_size;
c->cookie_domain =
add->cookie_domain != NULL ?
add->cookie_domain : base->cookie_domain;
c->claim_delimiter =
apr_strnatcmp(add->claim_delimiter, OIDC_DEFAULT_CLAIM_DELIMITER)
!= 0 ? add->claim_delimiter : base->claim_delimiter;
c->claim_prefix =
add->claim_prefix != NULL ? add->claim_prefix : base->claim_prefix;
c->remote_user_claim.claim_name =
apr_strnatcmp(add->remote_user_claim.claim_name,
OIDC_DEFAULT_CLAIM_REMOTE_USER) != 0 ?
add->remote_user_claim.claim_name :
base->remote_user_claim.claim_name;
c->remote_user_claim.reg_exp =
add->remote_user_claim.reg_exp != NULL ?
add->remote_user_claim.reg_exp :
base->remote_user_claim.reg_exp;
c->remote_user_claim.replace =
add->remote_user_claim.replace != NULL ?
add->remote_user_claim.replace :
base->remote_user_claim.replace;
c->pass_idtoken_as =
add->pass_idtoken_as != OIDC_PASS_IDTOKEN_AS_CLAIMS ?
add->pass_idtoken_as : base->pass_idtoken_as;
c->pass_userinfo_as =
add->pass_userinfo_as != OIDC_PASS_USERINFO_AS_CLAIMS ?
add->pass_userinfo_as : base->pass_userinfo_as;
c->cookie_http_only =
add->cookie_http_only != OIDC_DEFAULT_COOKIE_HTTPONLY ?
add->cookie_http_only : base->cookie_http_only;
c->cookie_same_site =
add->cookie_same_site != OIDC_DEFAULT_COOKIE_SAME_SITE ?
add->cookie_same_site : base->cookie_same_site;
c->outgoing_proxy =
add->outgoing_proxy != NULL ?
add->outgoing_proxy : base->outgoing_proxy;
c->crypto_passphrase =
add->crypto_passphrase != NULL ?
add->crypto_passphrase : base->crypto_passphrase;
c->scrub_request_headers =
add->scrub_request_headers != OIDC_DEFAULT_SCRUB_REQUEST_HEADERS ?
add->scrub_request_headers : base->scrub_request_headers;
c->error_template =
add->error_template != NULL ?
add->error_template : base->error_template;
c->provider.userinfo_refresh_interval =
add->provider.userinfo_refresh_interval
!= OIDC_DEFAULT_USERINFO_REFRESH_INTERVAL ?
add->provider.userinfo_refresh_interval :
base->provider.userinfo_refresh_interval;
c->provider.request_object =
add->provider.request_object != NULL ?
add->provider.request_object :
base->provider.request_object;
c->provider_metadata_refresh_interval =
add->provider_metadata_refresh_interval
!= OIDC_DEFAULT_PROVIDER_METADATA_REFRESH_INTERVAL ?
add->provider_metadata_refresh_interval :
base->provider_metadata_refresh_interval;
c->provider.token_binding_policy =
add->provider.token_binding_policy
!= OIDC_DEFAULT_PROVIDER_TOKEN_BINDING_POLICY ?
add->provider.token_binding_policy :
base->provider.token_binding_policy;
c->info_hook_data =
add->info_hook_data != NULL ?
add->info_hook_data : base->info_hook_data;
c->black_listed_claims =
add->black_listed_claims != NULL ?
add->black_listed_claims : base->black_listed_claims;
c->white_listed_claims =
add->white_listed_claims != NULL ?
add->white_listed_claims : base->white_listed_claims;
c->provider.issuer_specific_redirect_uri =
add->provider.issuer_specific_redirect_uri
!= OIDC_DEFAULT_PROVIDER_ISSUER_SPECIFIC_REDIRECT_URI ?
add->provider.issuer_specific_redirect_uri :
base->provider.issuer_specific_redirect_uri;
return c;
}
int oidc_cfg_cache_encrypt(request_rec *r) {
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
if (cfg->cache_encrypt == OIDC_CONFIG_POS_INT_UNSET)
return cfg->cache->encrypt_by_default;
return cfg->cache_encrypt;
}
int oidc_cfg_session_cache_fallback_to_cookie(request_rec *r) {
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
if (cfg->session_cache_fallback_to_cookie == OIDC_CONFIG_POS_INT_UNSET)
return 0;
return cfg->session_cache_fallback_to_cookie;
}
/*
* create a new directory config record with defaults
*/
void *oidc_create_dir_config(apr_pool_t *pool, char *path) {
oidc_dir_cfg *c = apr_pcalloc(pool, sizeof(oidc_dir_cfg));
c->discover_url = OIDC_CONFIG_STRING_UNSET;
c->cookie = OIDC_CONFIG_STRING_UNSET;
c->cookie_path = OIDC_CONFIG_STRING_UNSET;
c->authn_header = OIDC_CONFIG_STRING_UNSET;
c->unauth_action = OIDC_CONFIG_POS_INT_UNSET;
c->unautz_action = OIDC_CONFIG_POS_INT_UNSET;
c->pass_cookies = NULL;
c->strip_cookies = NULL;
c->pass_info_in_headers = OIDC_CONFIG_POS_INT_UNSET;
c->pass_info_in_env_vars = OIDC_CONFIG_POS_INT_UNSET;
c->oauth_accept_token_in = OIDC_CONFIG_POS_INT_UNSET;
c->oauth_accept_token_options = apr_hash_make(pool);
c->oauth_token_introspect_interval = OIDC_CONFIG_POS_INT_UNSET;
c->preserve_post = OIDC_CONFIG_POS_INT_UNSET;
c->pass_refresh_token = OIDC_CONFIG_POS_INT_UNSET;
c->path_auth_request_params = NULL;
c->path_scope = NULL;
return (c);
}
char *oidc_cfg_dir_discover_url(request_rec *r) {
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
if ((dir_cfg->discover_url != NULL) && (apr_strnatcmp(dir_cfg->discover_url,
OIDC_CONFIG_STRING_UNSET) == 0))
return NULL;
return dir_cfg->discover_url;
}
char *oidc_cfg_dir_cookie(request_rec *r) {
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
if ((dir_cfg->cookie == NULL)
|| ((dir_cfg->cookie != NULL)
&& (apr_strnatcmp(dir_cfg->cookie, OIDC_CONFIG_STRING_UNSET)
== 0)))
return OIDC_DEFAULT_COOKIE;
return dir_cfg->cookie;
}
char *oidc_cfg_dir_cookie_path(request_rec *r) {
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
if ((dir_cfg->cookie_path == NULL)
|| ((dir_cfg->cookie_path != NULL)
&& (apr_strnatcmp(dir_cfg->cookie_path,
OIDC_CONFIG_STRING_UNSET) == 0)))
return OIDC_DEFAULT_COOKIE_PATH;
return dir_cfg->cookie_path;
}
char *oidc_cfg_dir_authn_header(request_rec *r) {
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
if ((dir_cfg->authn_header == NULL)
|| ((dir_cfg->authn_header != NULL)
&& (apr_strnatcmp(dir_cfg->authn_header,
OIDC_CONFIG_STRING_UNSET) == 0)))
return OIDC_DEFAULT_AUTHN_HEADER;
return dir_cfg->authn_header;
}
apr_byte_t oidc_cfg_dir_pass_info_in_headers(request_rec *r) {
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
if (dir_cfg->pass_info_in_headers == OIDC_CONFIG_POS_INT_UNSET)
return OIDC_DEFAULT_PASS_APP_INFO_IN_HEADERS;
return dir_cfg->pass_info_in_headers;
}
apr_byte_t oidc_cfg_dir_pass_info_in_envvars(request_rec *r) {
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
if (dir_cfg->pass_info_in_env_vars == OIDC_CONFIG_POS_INT_UNSET)
return OIDC_DEFAULT_PASS_APP_INFO_IN_ENVVARS;
return dir_cfg->pass_info_in_env_vars;
}
apr_byte_t oidc_cfg_dir_pass_refresh_token(request_rec *r) {
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
if (dir_cfg->pass_refresh_token == OIDC_CONFIG_POS_INT_UNSET)
return OIDC_DEFAULT_PASS_REFRESH_TOKEN;
return dir_cfg->pass_refresh_token;
}
apr_byte_t oidc_cfg_dir_accept_token_in(request_rec *r) {
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
if (dir_cfg->oauth_accept_token_in == OIDC_CONFIG_POS_INT_UNSET)
return OIDC_OAUTH_ACCEPT_TOKEN_IN_DEFAULT;
return dir_cfg->oauth_accept_token_in;
}
char *oidc_cfg_dir_accept_token_in_option(request_rec *r, const char *key) {
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
return apr_hash_get(dir_cfg->oauth_accept_token_options, key,
APR_HASH_KEY_STRING);
}
int oidc_cfg_token_introspection_interval(request_rec *r) {
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
if (dir_cfg->oauth_token_introspect_interval == OIDC_CONFIG_POS_INT_UNSET)
return OIDC_DEFAULT_TOKEN_INTROSPECTION_INTERVAL;
return dir_cfg->oauth_token_introspect_interval;
}
int oidc_cfg_dir_preserve_post(request_rec *r) {
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
if (dir_cfg->preserve_post == OIDC_CONFIG_POS_INT_UNSET)
return OIDC_DEFAULT_PRESERVE_POST;
return dir_cfg->preserve_post;
}
apr_array_header_t *oidc_dir_cfg_pass_cookies(request_rec *r) {
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
return dir_cfg->pass_cookies;
}
apr_array_header_t *oidc_dir_cfg_strip_cookies(request_rec *r) {
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
return dir_cfg->strip_cookies;
}
int oidc_dir_cfg_unauth_action(request_rec *r) {
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
if (dir_cfg->unauth_action == OIDC_CONFIG_POS_INT_UNSET)
return OIDC_DEFAULT_UNAUTH_ACTION;
return dir_cfg->unauth_action;
}
int oidc_dir_cfg_unautz_action(request_rec *r) {
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
if (dir_cfg->unautz_action == OIDC_CONFIG_POS_INT_UNSET)
return OIDC_DEFAULT_UNAUTZ_ACTION;
return dir_cfg->unautz_action;
}
char *oidc_dir_cfg_path_auth_request_params(request_rec *r) {
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
return dir_cfg->path_auth_request_params;
}
char *oidc_dir_cfg_path_scope(request_rec *r) {
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
return dir_cfg->path_scope;
}
/*
* merge a new directory config with a base one
*/
void *oidc_merge_dir_config(apr_pool_t *pool, void *BASE, void *ADD) {
oidc_dir_cfg *c = apr_pcalloc(pool, sizeof(oidc_dir_cfg));
oidc_dir_cfg *base = BASE;
oidc_dir_cfg *add = ADD;
c->discover_url =
(apr_strnatcmp(add->discover_url, OIDC_CONFIG_STRING_UNSET) != 0) ?
add->discover_url : base->discover_url;
c->cookie =
(apr_strnatcmp(add->cookie, OIDC_CONFIG_STRING_UNSET) != 0) ?
add->cookie : base->cookie;
c->cookie_path =
(apr_strnatcmp(add->cookie_path, OIDC_CONFIG_STRING_UNSET) != 0) ?
add->cookie_path : base->cookie_path;
c->authn_header =
(apr_strnatcmp(add->authn_header, OIDC_CONFIG_STRING_UNSET) != 0) ?
add->authn_header : base->authn_header;
c->unauth_action =
add->unauth_action != OIDC_CONFIG_POS_INT_UNSET ?
add->unauth_action : base->unauth_action;
c->unautz_action =
add->unautz_action != OIDC_CONFIG_POS_INT_UNSET ?
add->unautz_action : base->unautz_action;
c->pass_cookies =
add->pass_cookies != NULL ? add->pass_cookies : base->pass_cookies;
c->strip_cookies =
add->strip_cookies != NULL ?
add->strip_cookies : base->strip_cookies;
c->pass_info_in_headers =
add->pass_info_in_headers != OIDC_CONFIG_POS_INT_UNSET ?
add->pass_info_in_headers : base->pass_info_in_headers;
c->pass_info_in_env_vars =
add->pass_info_in_env_vars != OIDC_CONFIG_POS_INT_UNSET ?
add->pass_info_in_env_vars : base->pass_info_in_env_vars;
c->oauth_accept_token_in =
add->oauth_accept_token_in != OIDC_CONFIG_POS_INT_UNSET ?
add->oauth_accept_token_in : base->oauth_accept_token_in;
c->oauth_accept_token_options =
apr_hash_count(add->oauth_accept_token_options) > 0 ?
add->oauth_accept_token_options :
base->oauth_accept_token_options;
c->oauth_token_introspect_interval =
add->oauth_token_introspect_interval != OIDC_CONFIG_POS_INT_UNSET ?
add->oauth_token_introspect_interval :
base->oauth_token_introspect_interval;
c->preserve_post =
add->preserve_post != OIDC_CONFIG_POS_INT_UNSET ?
add->preserve_post : base->preserve_post;
c->pass_refresh_token =
add->pass_refresh_token != OIDC_CONFIG_POS_INT_UNSET ?
add->pass_refresh_token : base->pass_refresh_token;
c->path_auth_request_params =
add->path_auth_request_params != NULL ?
add->path_auth_request_params :
base->path_auth_request_params;
c->path_scope =
add->path_scope != NULL ? add->path_scope : base->path_scope;
return (c);
}
/*
* report a config error
*/
static int oidc_check_config_error(server_rec *s, const char *config_str) {
oidc_serror(s, "mandatory parameter '%s' is not set", config_str);
return HTTP_INTERNAL_SERVER_ERROR;
}
/*
* check the config required for the OpenID Connect RP role
*/
static int oidc_check_config_openid_openidc(server_rec *s, oidc_cfg *c) {
apr_uri_t r_uri;
apr_byte_t redirect_uri_is_relative;
if ((c->metadata_dir == NULL) && (c->provider.issuer == NULL)
&& (c->provider.metadata_url == NULL)) {
oidc_serror(s,
"one of '" OIDCProviderIssuer "', '" OIDCProviderMetadataURL "' or '" OIDCMetadataDir "' must be set");
return HTTP_INTERNAL_SERVER_ERROR;
}
if (c->redirect_uri == NULL)
return oidc_check_config_error(s, OIDCRedirectURI);
redirect_uri_is_relative = (c->redirect_uri[0] == OIDC_CHAR_FORWARD_SLASH);
if (c->crypto_passphrase == NULL)
return oidc_check_config_error(s, OIDCCryptoPassphrase);
if (c->metadata_dir == NULL) {
if (c->provider.metadata_url == NULL) {
if (c->provider.issuer == NULL)
return oidc_check_config_error(s, OIDCProviderIssuer);
if (c->provider.authorization_endpoint_url == NULL)
return oidc_check_config_error(s,
OIDCProviderAuthorizationEndpoint);
} else {
apr_uri_parse(s->process->pconf, c->provider.metadata_url, &r_uri);
if ((r_uri.scheme == NULL)
|| (apr_strnatcmp(r_uri.scheme, "https") != 0)) {
oidc_swarn(s,
"the URL scheme (%s) of the configured " OIDCProviderMetadataURL " SHOULD be \"https\" for security reasons!",
r_uri.scheme);
}
}
if (c->provider.client_id == NULL)
return oidc_check_config_error(s, OIDCClientID);
} else {
if (c->provider.metadata_url != NULL) {
oidc_serror(s,
"only one of '" OIDCProviderMetadataURL "' or '" OIDCMetadataDir "' should be set");
return HTTP_INTERNAL_SERVER_ERROR;
}
}
apr_uri_parse(s->process->pconf, c->redirect_uri, &r_uri);
if (!redirect_uri_is_relative) {
if (apr_strnatcmp(r_uri.scheme, "https") != 0) {
oidc_swarn(s,
"the URL scheme (%s) of the configured " OIDCRedirectURI " SHOULD be \"https\" for security reasons (moreover: some Providers may reject non-HTTPS URLs)",
r_uri.scheme);
}
}
if (c->cookie_domain != NULL) {
if (redirect_uri_is_relative) {
oidc_swarn(s,
"if the configured " OIDCRedirectURI " is relative, " OIDCCookieDomain " SHOULD be empty");
} else if (!oidc_util_cookie_domain_valid(r_uri.hostname,
c->cookie_domain)) {
oidc_serror(s,
"the domain (%s) configured in " OIDCCookieDomain " does not match the URL hostname (%s) of the configured " OIDCRedirectURI " (%s): setting \"state\" and \"session\" cookies will not work!",
c->cookie_domain, r_uri.hostname, c->redirect_uri);
return HTTP_INTERNAL_SERVER_ERROR;
}
}
return OK;
}
/*
* check the config required for the OAuth 2.0 RS role
*/
static int oidc_check_config_oauth(server_rec *s, oidc_cfg *c) {
if (c->oauth.introspection_endpoint_url == NULL) {
if ((c->oauth.verify_jwks_uri == NULL)
&& (c->oauth.verify_public_keys == NULL)
&& (c->oauth.verify_shared_keys == NULL)) {
oidc_serror(s,
"one of '" OIDCOAuthIntrospectionEndpoint "', '" OIDCOAuthVerifyJwksUri "', '" OIDCOAuthVerifySharedKeys "' or '" OIDCOAuthVerifyCertFiles "' must be set");
return HTTP_INTERNAL_SERVER_ERROR;
}
} else if ((c->oauth.verify_jwks_uri != NULL)
|| (c->oauth.verify_public_keys != NULL)
|| (c->oauth.verify_shared_keys != NULL)) {
oidc_serror(s,
"only '" OIDCOAuthIntrospectionEndpoint "' OR one (or more) out of ('" OIDCOAuthVerifyJwksUri "', '" OIDCOAuthVerifySharedKeys "' or '" OIDCOAuthVerifyCertFiles "') must be set");
return HTTP_INTERNAL_SERVER_ERROR;
}
return OK;
}
/*
* check the config of a vhost
*/
static int oidc_config_check_vhost_config(apr_pool_t *pool, server_rec *s) {
oidc_cfg *cfg = ap_get_module_config(s->module_config,
&auth_openidc_module);
oidc_sdebug(s, "enter");
if ((cfg->metadata_dir != NULL) || (cfg->provider.issuer != NULL)
|| (cfg->provider.metadata_url != NULL)
|| (cfg->redirect_uri != NULL)) {
if (oidc_check_config_openid_openidc(s, cfg) != OK)
return HTTP_INTERNAL_SERVER_ERROR;
}
if ((cfg->oauth.client_id != NULL) || (cfg->oauth.client_secret != NULL)
|| (cfg->oauth.introspection_endpoint_url != NULL)
|| (cfg->oauth.verify_jwks_uri != NULL)
|| (cfg->oauth.verify_public_keys != NULL)
|| (cfg->oauth.verify_shared_keys != NULL)) {
if (oidc_check_config_oauth(s, cfg) != OK)
return HTTP_INTERNAL_SERVER_ERROR;
}
return OK;
}
/*
* check the config of a merged vhost
*/
static int oidc_config_check_merged_vhost_configs(apr_pool_t *pool,
server_rec *s) {
int status = OK;
while (s != NULL && status == OK) {
oidc_cfg *cfg = ap_get_module_config(s->module_config,
&auth_openidc_module);
if (cfg->merged) {
status = oidc_config_check_vhost_config(pool, s);
}
s = s->next;
}
return status;
}
/*
* check if any merged vhost configs exist
*/
static int oidc_config_merged_vhost_configs_exist(server_rec *s) {
while (s != NULL) {
oidc_cfg *cfg = ap_get_module_config(s->module_config,
&auth_openidc_module);
if (cfg->merged) {
return TRUE;
}
s = s->next;
}
return FALSE;
}
/*
* SSL initialization magic copied from mod_auth_cas
*/
#if defined(OPENSSL_THREADS) && APR_HAS_THREADS
static apr_thread_mutex_t **ssl_locks;
static int ssl_num_locks;
static void oidc_ssl_locking_callback(int mode, int type, const char *file,
int line) {
if (type < ssl_num_locks) {
if (mode & CRYPTO_LOCK)
apr_thread_mutex_lock(ssl_locks[type]);
else
apr_thread_mutex_unlock(ssl_locks[type]);
}
}
#ifdef OPENSSL_NO_THREADID
static unsigned long oidc_ssl_id_callback(void) {
return (unsigned long) apr_os_thread_current();
}
#else
static void oidc_ssl_id_callback(CRYPTO_THREADID *id) {
CRYPTO_THREADID_set_numeric(id, (unsigned long) apr_os_thread_current());
}
#endif /* OPENSSL_NO_THREADID */
#endif /* defined(OPENSSL_THREADS) && APR_HAS_THREADS */
static apr_status_t oidc_cleanup_child(void *data) {
server_rec *sp = (server_rec *) data;
while (sp != NULL) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(sp->module_config,
&auth_openidc_module);
if (cfg->cache->destroy != NULL) {
if (cfg->cache->destroy(sp) != APR_SUCCESS) {
oidc_serror(sp, "cache destroy function failed");
}
}
// can do this even though we haven't got a deep copy
// since references within the object will be set to NULL
oidc_jwk_list_destroy(sp->process->pool, cfg->oauth.verify_public_keys);
oidc_jwk_list_destroy(sp->process->pool, cfg->oauth.verify_shared_keys);
oidc_jwk_list_destroy(sp->process->pool, cfg->public_keys);
oidc_jwk_list_destroy(sp->process->pool, cfg->private_keys);
sp = sp->next;
}
return APR_SUCCESS;
}
static apr_status_t oidc_cleanup_parent(void *data) {
oidc_cleanup_child(data);
#if (defined (OPENSSL_THREADS) && APR_HAS_THREADS)
if (CRYPTO_get_locking_callback() == oidc_ssl_locking_callback)
CRYPTO_set_locking_callback(NULL);
#ifdef OPENSSL_NO_THREADID
if (CRYPTO_get_id_callback() == oidc_ssl_id_callback)
CRYPTO_set_id_callback(NULL);
#else
if (CRYPTO_THREADID_get_callback() == oidc_ssl_id_callback)
CRYPTO_THREADID_set_callback(NULL);
#endif /* OPENSSL_NO_THREADID */
#endif /* defined(OPENSSL_THREADS) && APR_HAS_THREADS */
EVP_cleanup();
curl_global_cleanup();
ap_log_error(APLOG_MARK, APLOG_INFO, 0, (server_rec * ) data,
"%s - shutdown", NAMEVERSION);
return APR_SUCCESS;
}
/*
* handler that is called (twice) after the configuration phase; check if everything is OK
*/
static int oidc_post_config(apr_pool_t *pool, apr_pool_t *p1, apr_pool_t *p2,
server_rec *s) {
const char *userdata_key = "oidc_post_config";
void *data = NULL;
int i;
/* Since the post_config hook is invoked twice (once
* for 'sanity checking' of the config and once for
* the actual server launch, we have to use a hack
* to not run twice
*/
apr_pool_userdata_get(&data, userdata_key, s->process->pool);
if (data == NULL) {
apr_pool_userdata_set((const void *) 1, userdata_key,
apr_pool_cleanup_null, s->process->pool);
return OK;
}
ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
"%s - init - cjose %s, %s, EC=%s, GCM=%s, Redis=%s, JQ=%s",
NAMEVERSION,
cjose_version(),
OPENSSL_VERSION_TEXT,
OIDC_JOSE_EC_SUPPORT ? "yes" : "no",
OIDC_JOSE_GCM_SUPPORT ? "yes" : "no",
#ifdef USE_LIBHIREDIS
"yes"
#else
"no"
#endif
,
#ifdef USE_LIBJQ
"yes"
#else
"no"
#endif
);
curl_global_init(CURL_GLOBAL_ALL);
OpenSSL_add_all_digests();
#if (defined(OPENSSL_THREADS) && APR_HAS_THREADS)
ssl_num_locks = CRYPTO_num_locks();
ssl_locks = apr_pcalloc(s->process->pool,
ssl_num_locks * sizeof(*ssl_locks));
for (i = 0; i < ssl_num_locks; i++)
apr_thread_mutex_create(&(ssl_locks[i]), APR_THREAD_MUTEX_DEFAULT,
s->process->pool);
#ifdef OPENSSL_NO_THREADID
if (CRYPTO_get_locking_callback() == NULL && CRYPTO_get_id_callback() == NULL) {
CRYPTO_set_locking_callback(oidc_ssl_locking_callback);
CRYPTO_set_id_callback(oidc_ssl_id_callback);
}
#else
if (CRYPTO_get_locking_callback() == NULL
&& CRYPTO_THREADID_get_callback() == NULL) {
CRYPTO_set_locking_callback(oidc_ssl_locking_callback);
CRYPTO_THREADID_set_callback(oidc_ssl_id_callback);
}
#endif /* OPENSSL_NO_THREADID */
#endif /* defined(OPENSSL_THREADS) && APR_HAS_THREADS */
apr_pool_cleanup_register(pool, s, oidc_cleanup_parent,
apr_pool_cleanup_null);
server_rec *sp = s;
while (sp != NULL) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(sp->module_config,
&auth_openidc_module);
if (cfg->cache->post_config != NULL) {
if (cfg->cache->post_config(sp) != OK)
return HTTP_INTERNAL_SERVER_ERROR;
}
sp = sp->next;
}
/*
* Apache has a base vhost that true vhosts derive from.
* There are two startup scenarios:
*
* 1. Only the base vhost contains OIDC settings.
* No server configs have been merged.
* Only the base vhost needs to be checked.
*
* 2. The base vhost contains zero or more OIDC settings.
* One or more vhosts override these.
* These vhosts have a merged config.
* All merged configs need to be checked.
*/
if (!oidc_config_merged_vhost_configs_exist(s)) {
/* nothing merged, only check the base vhost */
return oidc_config_check_vhost_config(pool, s);
}
return oidc_config_check_merged_vhost_configs(pool, s);
}
#if MODULE_MAGIC_NUMBER_MAJOR >= 20100714
static const authz_provider oidc_authz_claim_provider = {
&oidc_authz_checker_claim,
NULL,
};
#ifdef USE_LIBJQ
static const authz_provider oidc_authz_claims_expr_provider = {
&oidc_authz_checker_claims_expr,
NULL,
};
#endif
#endif
/*
* initialize cache context in child process if required
*/
static void oidc_child_init(apr_pool_t *p, server_rec *s) {
server_rec *sp = s;
while (sp != NULL) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(sp->module_config,
&auth_openidc_module);
if (cfg->cache->child_init != NULL) {
if (cfg->cache->child_init(p, sp) != APR_SUCCESS) {
oidc_serror(sp, "cfg->cache->child_init failed");
}
}
sp = sp->next;
}
apr_pool_cleanup_register(p, s, oidc_cleanup_child, apr_pool_cleanup_null);
}
/*
* fixup handler: be authoritative for environment variables at late processing
*/
static int oidc_auth_fixups(request_rec *r) {
apr_table_t *env = NULL;
apr_pool_userdata_get((void **) &env, OIDC_USERDATA_ENV_KEY, r->pool);
if ((env == NULL) || apr_is_empty_table(env))
return DECLINED;
oidc_debug(r, "overlaying env with %d elements",
apr_table_elts(env)->nelts);
r->subprocess_env = apr_table_overlay(r->pool, r->subprocess_env, env);
return OK;
}
/*
* register our authentication and authorization functions
*/
void oidc_register_hooks(apr_pool_t *pool) {
ap_hook_post_config(oidc_post_config, NULL, NULL, APR_HOOK_LAST);
ap_hook_child_init(oidc_child_init, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_handler(oidc_content_handler, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_fixups(oidc_auth_fixups, NULL, NULL, APR_HOOK_MIDDLE);
#if MODULE_MAGIC_NUMBER_MAJOR >= 20100714
ap_hook_check_authn(oidc_check_user_id, NULL, NULL, APR_HOOK_MIDDLE,
AP_AUTH_INTERNAL_PER_CONF);
ap_register_auth_provider(pool, AUTHZ_PROVIDER_GROUP,
OIDC_REQUIRE_CLAIM_NAME, "0", &oidc_authz_claim_provider,
AP_AUTH_INTERNAL_PER_CONF);
#ifdef USE_LIBJQ
ap_register_auth_provider(pool, AUTHZ_PROVIDER_GROUP,
OIDC_REQUIRE_CLAIMS_EXPR_NAME, "0",
&oidc_authz_claims_expr_provider, AP_AUTH_INTERNAL_PER_CONF);
#endif
#else
static const char * const authzSucc[] = {"mod_authz_user.c", NULL};
ap_hook_check_user_id(oidc_check_user_id, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_auth_checker(oidc_auth_checker, NULL, authzSucc, APR_HOOK_MIDDLE);
#endif
}
/*
* set of configuration primitives
*/
const command_rec oidc_config_cmds[] = {
AP_INIT_TAKE1(OIDCProviderMetadataURL,
oidc_set_url_slot,
(void*)APR_OFFSETOF(oidc_cfg, provider.metadata_url),
RSRC_CONF,
"OpenID Connect OP configuration metadata URL."),
AP_INIT_TAKE1(OIDCProviderIssuer,
oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, provider.issuer),
RSRC_CONF,
"OpenID Connect OP issuer identifier."),
AP_INIT_TAKE1(OIDCProviderAuthorizationEndpoint,
oidc_set_https_slot,
(void *)APR_OFFSETOF(oidc_cfg, provider.authorization_endpoint_url),
RSRC_CONF,
"Define the OpenID OP Authorization Endpoint URL (e.g.: https://localhost:9031/as/authorization.oauth2)"),
AP_INIT_TAKE1(OIDCProviderTokenEndpoint,
oidc_set_https_slot,
(void *)APR_OFFSETOF(oidc_cfg, provider.token_endpoint_url),
RSRC_CONF,
"Define the OpenID OP Token Endpoint URL (e.g.: https://localhost:9031/as/token.oauth2)"),
AP_INIT_TAKE1(OIDCProviderTokenEndpointAuth,
oidc_set_endpoint_auth_slot,
(void *)APR_OFFSETOF(oidc_cfg, provider.token_endpoint_auth),
RSRC_CONF,
"Specify an authentication method for the OpenID OP Token Endpoint (e.g.: client_secret_basic)"),
AP_INIT_TAKE1(OIDCProviderTokenEndpointParams,
oidc_set_string_slot,
(void *)APR_OFFSETOF(oidc_cfg, provider.token_endpoint_params),
RSRC_CONF,
"Define extra parameters that will be posted to the OpenID OP Token Endpoint (e.g.: param1=value1¶m2=value2, all urlencoded)."),
AP_INIT_TAKE1(OIDCProviderRegistrationEndpointJson,
oidc_set_string_slot,
(void *)APR_OFFSETOF(oidc_cfg, provider.registration_endpoint_json),
RSRC_CONF,
"Define a JSON object with parameters that will be merged into the client registration request to the OpenID OP Registration Endpoint (e.g.: { \"request_uris\" : [ \"https://example.com/uri\"] })."),
AP_INIT_TAKE1(OIDCProviderUserInfoEndpoint,
oidc_set_https_slot,
(void *)APR_OFFSETOF(oidc_cfg, provider.userinfo_endpoint_url),
RSRC_CONF,
"Define the OpenID OP UserInfo Endpoint URL (e.g.: https://localhost:9031/idp/userinfo.openid)"),
AP_INIT_TAKE1(OIDCProviderCheckSessionIFrame,
oidc_set_url_slot,
(void *)APR_OFFSETOF(oidc_cfg, provider.check_session_iframe),
RSRC_CONF,
"Define the OpenID OP Check Session iFrame URL."),
AP_INIT_TAKE1(OIDCProviderEndSessionEndpoint,
oidc_set_url_slot,
(void *)APR_OFFSETOF(oidc_cfg, provider.end_session_endpoint),
RSRC_CONF,
"Define the OpenID OP End Session Endpoint URL."),
AP_INIT_TAKE1(OIDCProviderJwksUri,
oidc_set_https_slot,
(void *)APR_OFFSETOF(oidc_cfg, provider.jwks_uri),
RSRC_CONF,
"Define the OpenID OP JWKS URL (e.g.: https://localhost:9031/pf/JWKS)"),
AP_INIT_TAKE1(OIDCResponseType,
oidc_set_response_type,
(void *)APR_OFFSETOF(oidc_cfg, provider.response_type),
RSRC_CONF,
"The response type (or OpenID Connect Flow) used; must be one of \"code\", \"id_token\", \"id_token token\", \"code id_token\", \"code token\" or \"code id_token token\" (serves as default value for discovered OPs too)"),
AP_INIT_TAKE1(OIDCResponseMode,
oidc_set_response_mode,
(void *)APR_OFFSETOF(oidc_cfg, provider.response_mode),
RSRC_CONF,
"The response mode used; must be one of \"fragment\", \"query\" or \"form_post\" (serves as default value for discovered OPs too)"),
AP_INIT_ITERATE(OIDCPublicKeyFiles,
oidc_set_public_key_files,
(void *)APR_OFFSETOF(oidc_cfg, public_keys),
RSRC_CONF,
"The fully qualified names of the files that contain the X.509 certificates that contains the RSA public keys that can be used for encryption by the OP."),
AP_INIT_ITERATE(OIDCPrivateKeyFiles,
oidc_set_private_key_files_enc,
NULL,
RSRC_CONF,
"The fully qualified names of the files that contain the RSA private keys that can be used to decrypt content sent to us by the OP."),
AP_INIT_TAKE1(OIDCClientJwksUri,
oidc_set_https_slot,
(void *)APR_OFFSETOF(oidc_cfg, provider.client_jwks_uri),
RSRC_CONF,
"Define the Client JWKS URL (e.g.: https://localhost/protected/?jwks=rsa)"),
AP_INIT_TAKE1(OIDCIDTokenSignedResponseAlg,
oidc_set_signed_response_alg,
(void *)APR_OFFSETOF(oidc_cfg, provider.id_token_signed_response_alg),
RSRC_CONF,
"The algorithm that the OP should use to sign the id_token (used only in dynamic client registration); must be one of [RS256|RS384|RS512|PS256|PS384|PS512|HS256|HS384|HS512]"),
AP_INIT_TAKE1(OIDCIDTokenEncryptedResponseAlg,
oidc_set_encrypted_response_alg,
(void *)APR_OFFSETOF(oidc_cfg, provider.id_token_encrypted_response_alg),
RSRC_CONF,
"The algorithm that the OP should use to encrypt the Content Encryption Key that is used to encrypt the id_token (used only in dynamic client registration); must be one of [RSA1_5|A128KW|A256KW|RSA-OAEP]"),
AP_INIT_TAKE1(OIDCIDTokenEncryptedResponseEnc,
oidc_set_encrypted_response_enc,
(void *)APR_OFFSETOF(oidc_cfg, provider.id_token_encrypted_response_enc),
RSRC_CONF,
"The algorithm that the OP should use to encrypt to the id_token with the Content Encryption Key (used only in dynamic client registration); must be one of [A128CBC-HS256|A256CBC-HS512|A256GCM]"),
AP_INIT_TAKE1(OIDCUserInfoSignedResponseAlg,
oidc_set_signed_response_alg,
(void *)APR_OFFSETOF(oidc_cfg, provider.userinfo_signed_response_alg),
RSRC_CONF,
"The algorithm that the OP should use to sign the UserInfo response (used only in dynamic client registration); must be one of [RS256|RS384|RS512|PS256|PS384|PS512|HS256|HS384|HS512]"),
AP_INIT_TAKE1(OIDCUserInfoEncryptedResponseAlg,
oidc_set_encrypted_response_alg,
(void *)APR_OFFSETOF(oidc_cfg, provider.userinfo_encrypted_response_alg),
RSRC_CONF,
"The algorithm that the OP should use to encrypt the Content Encryption Key that is used to encrypt the UserInfo response (used only in dynamic client registration); must be one of [RSA1_5|A128KW|A256KW|RSA-OAEP]"),
AP_INIT_TAKE1(OIDCUserInfoEncryptedResponseEnc,
oidc_set_encrypted_response_enc,
(void *)APR_OFFSETOF(oidc_cfg, provider.userinfo_encrypted_response_enc),
RSRC_CONF,
"The algorithm that the OP should use to encrypt to encrypt the UserInfo response with the Content Encryption Key (used only in dynamic client registration); must be one of [A128CBC-HS256|A256CBC-HS512|A256GCM]"),
AP_INIT_TAKE1(OIDCUserInfoTokenMethod,
oidc_set_userinfo_token_method,
(void *)APR_OFFSETOF(oidc_cfg, provider.userinfo_token_method),
RSRC_CONF,
"The method that is used to present the access token to the userinfo endpoint; must be one of [authz_header|post_param]"),
AP_INIT_TAKE1(OIDCTokenBindingPolicy,
oidc_set_token_binding_policy,
(void *)APR_OFFSETOF(oidc_cfg, provider.token_binding_policy),
RSRC_CONF,
"The token binding policy used with the provider; must be one of [disabled|optional|required|enforced]"),
AP_INIT_TAKE1(OIDCSSLValidateServer,
oidc_set_ssl_validate_slot,
(void*)APR_OFFSETOF(oidc_cfg, provider.ssl_validate_server),
RSRC_CONF,
"Require validation of the OpenID Connect OP SSL server certificate for successful authentication (On or Off)"),
AP_INIT_TAKE1(OIDCClientName,
oidc_set_string_slot,
(void *) APR_OFFSETOF(oidc_cfg, provider.client_name),
RSRC_CONF,
"Define the (client_name) name that the client uses for dynamic registration to the OP."),
AP_INIT_TAKE1(OIDCClientContact,
oidc_set_string_slot,
(void *) APR_OFFSETOF(oidc_cfg, provider.client_contact),
RSRC_CONF,
"Define the contact that the client registers in dynamic registration with the OP."),
AP_INIT_TAKE1(OIDCScope,
oidc_set_string_slot,
(void *) APR_OFFSETOF(oidc_cfg, provider.scope),
RSRC_CONF,
"Define the OpenID Connect scope that is requested from the OP."),
AP_INIT_TAKE1(OIDCPathScope,
ap_set_string_slot,
(void*)APR_OFFSETOF(oidc_dir_cfg, path_scope),
RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
"Define the OpenID Connect scope that is requested from all providers for a specific path/context."),
AP_INIT_TAKE1(OIDCJWKSRefreshInterval,
oidc_set_jwks_refresh_interval,
(void*)APR_OFFSETOF(oidc_cfg, provider.jwks_refresh_interval),
RSRC_CONF,
"Duration in seconds after which retrieved JWS should be refreshed."),
AP_INIT_TAKE1(OIDCIDTokenIatSlack,
oidc_set_idtoken_iat_slack,
(void*)APR_OFFSETOF(oidc_cfg, provider.idtoken_iat_slack),
RSRC_CONF,
"Acceptable offset (both before and after) for checking the \"iat\" (= issued at) timestamp in the id_token."),
AP_INIT_TAKE1(OIDCSessionMaxDuration,
oidc_set_session_max_duration,
(void*)APR_OFFSETOF(oidc_cfg, provider.session_max_duration),
RSRC_CONF,
"Maximum duration of a session in seconds."),
AP_INIT_TAKE1(OIDCAuthRequestParams,
oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, provider.auth_request_params),
RSRC_CONF,
"Extra parameters that need to be sent in the Authorization Request (must be query-encoded like \"display=popup&prompt=consent\"."),
AP_INIT_TAKE1(OIDCPathAuthRequestParams,
ap_set_string_slot,
(void*)APR_OFFSETOF(oidc_dir_cfg, path_auth_request_params),
RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
"Extra parameters that need to be sent in the Authorization Request (must be query-encoded like \"display=popup&prompt=consent\"."),
AP_INIT_TAKE1(OIDCPKCEMethod,
oidc_set_pkce_method,
(void *)APR_OFFSETOF(oidc_cfg, provider.pkce),
RSRC_CONF,
"The RFC 7636 PCKE mode used; must be one of \"plain\", \"S256\" or \"referred_tb\""),
AP_INIT_TAKE1(OIDCClientID,
oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, provider.client_id),
RSRC_CONF,
"Client identifier used in calls to OpenID Connect OP."),
AP_INIT_TAKE1(OIDCClientSecret,
oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, provider.client_secret),
RSRC_CONF,
"Client secret used in calls to OpenID Connect OP."),
AP_INIT_TAKE1(OIDCClientTokenEndpointCert,
oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, provider.token_endpoint_tls_client_cert),
RSRC_CONF,
"TLS client certificate used for calls to OpenID Connect OP token endpoint."),
AP_INIT_TAKE1(OIDCClientTokenEndpointKey,
oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, provider.token_endpoint_tls_client_key),
RSRC_CONF,
"TLS client certificate private key used for calls to OpenID Connect OP token endpoint."),
AP_INIT_TAKE1(OIDCRedirectURI,
oidc_set_relative_or_absolute_url_slot,
(void *)APR_OFFSETOF(oidc_cfg, redirect_uri),
RSRC_CONF,
"Define the Redirect URI (e.g.: https://localhost:9031/protected/example/)"),
AP_INIT_TAKE1(OIDCDefaultURL,
oidc_set_url_slot,
(void *)APR_OFFSETOF(oidc_cfg, default_sso_url),
RSRC_CONF,
"Defines the default URL where the user is directed to in case of 3rd-party initiated SSO."),
AP_INIT_TAKE1(OIDCDefaultLoggedOutURL,
oidc_set_url_slot,
(void *)APR_OFFSETOF(oidc_cfg, default_slo_url),
RSRC_CONF,
"Defines the default URL where the user is directed to after logout."),
AP_INIT_TAKE1(OIDCCookieDomain,
oidc_set_cookie_domain,
NULL,
RSRC_CONF,
"Specify domain element for OIDC session cookie."),
AP_INIT_FLAG(OIDCCookieHTTPOnly,
oidc_set_flag_slot,
(void *) APR_OFFSETOF(oidc_cfg, cookie_http_only),
RSRC_CONF,
"Defines whether or not the cookie httponly flag is set on cookies."),
AP_INIT_FLAG(OIDCCookieSameSite,
oidc_set_flag_slot,
(void *) APR_OFFSETOF(oidc_cfg, cookie_same_site),
RSRC_CONF,
"Defines whether or not the cookie Same-Site flag is set on cookies."),
AP_INIT_TAKE1(OIDCOutgoingProxy,
oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, outgoing_proxy),
RSRC_CONF,
"Specify an outgoing proxy for your network ([:]."),
AP_INIT_TAKE1(OIDCCryptoPassphrase,
oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, crypto_passphrase),
RSRC_CONF,
"Passphrase used for AES crypto on cookies and state."),
AP_INIT_TAKE1(OIDCClaimDelimiter,
oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, claim_delimiter),
RSRC_CONF,
"The delimiter to use when setting multi-valued claims in the HTTP headers."),
AP_INIT_RAW_ARGS(OIDCClaimPrefix,
oidc_cfg_set_claim_prefix,
(void*)APR_OFFSETOF(oidc_cfg, claim_prefix),
RSRC_CONF,
"The prefix to use when setting claims in the HTTP headers."),
AP_INIT_TAKE123(OIDCRemoteUserClaim,
oidc_set_remote_user_claim,
(void*)APR_OFFSETOF(oidc_cfg, remote_user_claim),
RSRC_CONF,
"The claim that is used when setting the REMOTE_USER variable for OpenID Connect protected paths."),
AP_INIT_TAKE123(OIDCPassIDTokenAs,
oidc_set_pass_idtoken_as,
NULL,
RSRC_CONF,
"The format in which the id_token is passed in (a) header(s); must be one or more of: claims|payload|serialized"),
AP_INIT_TAKE123(OIDCPassUserInfoAs,
oidc_set_pass_userinfo_as,
NULL,
RSRC_CONF,
"The format in which the userinfo is passed in (a) header(s); must be one or more of: claims|json|jwt"),
AP_INIT_TAKE1(OIDCOAuthClientID,
oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, oauth.client_id),
RSRC_CONF,
"Client identifier used in calls to OAuth 2.0 Authorization server validation calls."),
AP_INIT_TAKE1(OIDCOAuthClientSecret,
oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, oauth.client_secret),
RSRC_CONF,
"Client secret used in calls to OAuth 2.0 Authorization server validation calls."),
AP_INIT_TAKE1(OIDCOAuthIntrospectionEndpoint,
oidc_set_https_slot,
(void *)APR_OFFSETOF(oidc_cfg, oauth.introspection_endpoint_url),
RSRC_CONF,
"Define the OAuth AS Introspection Endpoint URL (e.g.: https://localhost:9031/as/token.oauth2)"),
AP_INIT_TAKE1(OIDCOAuthIntrospectionEndpointMethod,
oidc_set_introspection_method,
(void *)APR_OFFSETOF(oidc_cfg, oauth.introspection_endpoint_method),
RSRC_CONF,
"Define the HTTP method to use for the introspection call: one of \"GET\" or \"POST\" (default)"),
AP_INIT_TAKE1(OIDCOAuthIntrospectionEndpointParams,
oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, oauth.introspection_endpoint_params),
RSRC_CONF,
"Extra parameters that need to be sent in the token introspection request (must be query-encoded like \"grant_type=urn%3Apingidentity.com%3Aoauth2%3Agrant_type%3Avalidate_bearer\"."),
AP_INIT_TAKE1(OIDCOAuthIntrospectionEndpointAuth,
oidc_set_endpoint_auth_slot,
(void *)APR_OFFSETOF(oidc_cfg, oauth.introspection_endpoint_auth),
RSRC_CONF,
"Specify an authentication method for the OAuth AS Introspection Endpoint (e.g.: client_secret_basic)"),
AP_INIT_RAW_ARGS(OIDCOAuthIntrospectionClientAuthBearerToken,
oidc_set_client_auth_bearer_token,
NULL,
RSRC_CONF,
"Specify a bearer token to authorize against the OAuth AS Introspection Endpoint (e.g.: 55554ee-2491-11e3-be72-001fe2e44345 or empty to use the introspected token itself)"),
AP_INIT_TAKE1(OIDCOAuthIntrospectionEndpointCert,
oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, oauth.introspection_endpoint_tls_client_cert),
RSRC_CONF,
"TLS client certificate used for calls to the OAuth 2.0 Authorization server introspection endpoint."),
AP_INIT_TAKE1(OIDCOAuthIntrospectionEndpointKey,
oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, oauth.introspection_endpoint_tls_client_key),
RSRC_CONF,
"TLS client certificate private key used for calls to the OAuth 2.0 Authorization server introspection endpoint."),
AP_INIT_TAKE1(OIDCOAuthIntrospectionTokenParamName,
oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, oauth.introspection_token_param_name),
RSRC_CONF,
"Name of the parameter whose value carries the access token value in an validation request to the token introspection endpoint."),
AP_INIT_TAKE123(OIDCOAuthTokenExpiryClaim,
oidc_set_token_expiry_claim,
NULL,
RSRC_CONF,
"Name of the claim that carries the token expiry value in the introspection result, optionally followed by absolute|relative, optionally followed by optional|mandatory"),
AP_INIT_TAKE1(OIDCOAuthSSLValidateServer,
oidc_set_ssl_validate_slot,
(void*)APR_OFFSETOF(oidc_cfg, oauth.ssl_validate_server),
RSRC_CONF,
"Require validation of the OAuth 2.0 AS Validation Endpoint SSL server certificate for successful authentication (On or Off)"),
AP_INIT_TAKE123(OIDCOAuthRemoteUserClaim,
oidc_set_remote_user_claim,
(void*)APR_OFFSETOF(oidc_cfg, oauth.remote_user_claim),
RSRC_CONF,
"The claim that is used when setting the REMOTE_USER variable for OAuth 2.0 protected paths."),
AP_INIT_ITERATE(OIDCOAuthVerifyCertFiles,
oidc_set_public_key_files,
(void*)APR_OFFSETOF(oidc_cfg, oauth.verify_public_keys),
RSRC_CONF,
"The fully qualified names of the files that contain the X.509 certificates that contains the RSA public keys that can be used for access token validation."),
AP_INIT_ITERATE(OIDCOAuthVerifySharedKeys,
oidc_set_shared_keys,
(void*)APR_OFFSETOF(oidc_cfg, oauth.verify_shared_keys),
RSRC_CONF,
"Shared secret(s) that is/are used to verify signed JWT access tokens locally."),
AP_INIT_TAKE1(OIDCOAuthVerifyJwksUri,
oidc_set_https_slot,
(void *)APR_OFFSETOF(oidc_cfg, oauth.verify_jwks_uri),
RSRC_CONF,
"The JWKs URL on which the Authorization publishes the keys used to sign its JWT access tokens."),
AP_INIT_TAKE1(OIDCHTTPTimeoutLong,
oidc_set_int_slot,
(void*)APR_OFFSETOF(oidc_cfg, http_timeout_long),
RSRC_CONF,
"Timeout for long duration HTTP calls (default)."),
AP_INIT_TAKE1(OIDCHTTPTimeoutShort,
oidc_set_int_slot,
(void*)APR_OFFSETOF(oidc_cfg, http_timeout_short),
RSRC_CONF,
"Timeout for short duration HTTP calls (registry/discovery)."),
AP_INIT_TAKE1(OIDCStateTimeout,
oidc_set_int_slot,
(void*)APR_OFFSETOF(oidc_cfg, state_timeout),
RSRC_CONF,
"Time to live in seconds for state parameter (cq. interval in which the authorization request and the corresponding response need to be completed)."),
AP_INIT_TAKE1(OIDCSessionInactivityTimeout,
oidc_set_session_inactivity_timeout,
(void*)APR_OFFSETOF(oidc_cfg, session_inactivity_timeout),
RSRC_CONF,
"Inactivity interval after which the session is invalidated when no interaction has occurred."),
AP_INIT_TAKE1(OIDCMetadataDir,
oidc_set_dir_slot,
(void*)APR_OFFSETOF(oidc_cfg, metadata_dir),
RSRC_CONF,
"Directory that contains provider and client metadata files."),
AP_INIT_TAKE1(OIDCSessionType,
oidc_set_session_type,
(void*)APR_OFFSETOF(oidc_cfg, session_type),
RSRC_CONF,
"OpenID Connect session storage type (Apache 2.0/2.2 only). Must be one of \"server-cache\" or \"client-cookie\" with an optional suffix \":persistent\"."),
AP_INIT_FLAG(OIDCSessionCacheFallbackToCookie,
oidc_set_flag_slot,
(void*)APR_OFFSETOF(oidc_cfg, session_cache_fallback_to_cookie),
RSRC_CONF,
"Fallback to client-side cookie session storage when server side cache fails."),
AP_INIT_TAKE1(OIDCSessionCookieChunkSize,
oidc_set_int_slot,
(void*)APR_OFFSETOF(oidc_cfg, session_cookie_chunk_size),
RSRC_CONF,
"Chunk size for client-cookie session storage type in bytes. Defaults to 4k. Set 0 to suppress chunking."),
AP_INIT_FLAG(OIDCScrubRequestHeaders,
oidc_set_flag_slot,
(void *) APR_OFFSETOF(oidc_cfg, scrub_request_headers),
RSRC_CONF,
"Scrub user name and claim headers from the user's request."),
AP_INIT_TAKE1(OIDCCacheType,
oidc_set_cache_type,
(void*)APR_OFFSETOF(oidc_cfg, cache), RSRC_CONF,
"Cache type; must be one of \"file\", \"memcache\" or \"shm\"."),
AP_INIT_FLAG(OIDCCacheEncrypt,
oidc_set_flag_slot,
(void*)APR_OFFSETOF(oidc_cfg, cache_encrypt),
RSRC_CONF,
"Encrypt the data in the cache backend (On or Off)"),
AP_INIT_TAKE1(OIDCCacheDir,
oidc_set_dir_slot,
(void*)APR_OFFSETOF(oidc_cfg, cache_file_dir),
RSRC_CONF,
"Directory used for file-based caching."),
AP_INIT_TAKE1(OIDCCacheFileCleanInterval,
oidc_set_int_slot,
(void*)APR_OFFSETOF(oidc_cfg, cache_file_clean_interval),
RSRC_CONF,
"Cache file clean interval in seconds."),
AP_INIT_TAKE1(OIDCMemCacheServers,
oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, cache_memcache_servers),
RSRC_CONF,
"Memcache servers used for caching (space separated list of [:] tuples)"),
AP_INIT_TAKE1(OIDCCacheShmMax,
oidc_set_int_slot,
(void*)APR_OFFSETOF(oidc_cfg, cache_shm_size_max),
RSRC_CONF,
"Maximum number of cache entries to use for \"shm\" caching."),
AP_INIT_TAKE1(OIDCCacheShmEntrySizeMax,
oidc_set_cache_shm_entry_size_max,
(void*)APR_OFFSETOF(oidc_cfg, cache_shm_entry_size_max),
RSRC_CONF,
"Maximum size of a single cache entry used for \"shm\" caching."),
#ifdef USE_LIBHIREDIS
AP_INIT_TAKE1(OIDCRedisCacheServer,
oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, cache_redis_server),
RSRC_CONF,
"Redis server used for caching ([:])"),
AP_INIT_TAKE1(OIDCRedisCachePassword,
oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, cache_redis_password),
RSRC_CONF,
"Password for authentication to the Redis servers."),
#endif
AP_INIT_TAKE1(OIDCHTMLErrorTemplate,
oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, error_template),
RSRC_CONF,
"Name of a HTML error template: needs to contain two \"%s\" characters, one for the error message, one for the description."),
AP_INIT_TAKE1(OIDCDiscoverURL,
oidc_set_url_slot_dir_cfg,
(void *)APR_OFFSETOF(oidc_dir_cfg, discover_url),
RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
"Defines an external IDP Discovery page"),
AP_INIT_ITERATE(OIDCPassCookies,
oidc_set_cookie_names,
(void *) APR_OFFSETOF(oidc_dir_cfg, pass_cookies),
RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
"Specify cookies that need to be passed from the browser on to the backend to the OP/AS."),
AP_INIT_ITERATE(OIDCStripCookies,
oidc_set_cookie_names,
(void *) APR_OFFSETOF(oidc_dir_cfg, strip_cookies),
RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
"Specify cookies that should be stripped from the incoming request before passing it on to the backend."),
AP_INIT_TAKE1(OIDCAuthNHeader,
ap_set_string_slot,
(void *) APR_OFFSETOF(oidc_dir_cfg, authn_header),
RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
"Specify the HTTP header variable to set with the name of the authenticated user. By default no explicit header is added but Apache's default REMOTE_USER will be set."),
AP_INIT_TAKE1(OIDCCookiePath,
ap_set_string_slot,
(void *) APR_OFFSETOF(oidc_dir_cfg, cookie_path),
RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
"Define the cookie path for the session cookie."),
AP_INIT_TAKE1(OIDCCookie,
ap_set_string_slot,
(void *) APR_OFFSETOF(oidc_dir_cfg, cookie),
RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
"Define the cookie name for the session cookie."),
AP_INIT_TAKE1(OIDCUnAuthAction,
oidc_set_unauth_action,
(void *) APR_OFFSETOF(oidc_dir_cfg, unauth_action),
RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
"Sets the action taken when an unauthenticated request occurs: must be one of \"auth\" (default), \"pass\" , \"401\" or \"410\"."),
AP_INIT_TAKE1(OIDCUnAutzAction,
oidc_set_unautz_action,
(void *) APR_OFFSETOF(oidc_dir_cfg, unautz_action),
RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
"Sets the action taken when an unauthorized request occurs: must be one of \"401\" (default), \"403\" or \"auth\"."),
AP_INIT_TAKE1(OIDCPassClaimsAs,
oidc_set_pass_claims_as, NULL,
RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
"Specify how claims are passed to the application(s); must be one of \"none\", \"headers\", \"environment\" or \"both\" (default)."),
AP_INIT_ITERATE(OIDCOAuthAcceptTokenAs,
oidc_set_accept_oauth_token_in,
NULL,
RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
"The method in which an OAuth token can be presented; must be one or more of: header|post|query|cookie"),
AP_INIT_TAKE1(OIDCUserInfoRefreshInterval,
oidc_set_userinfo_refresh_interval,
(void*)APR_OFFSETOF(oidc_cfg, provider.userinfo_refresh_interval),
RSRC_CONF,
"Duration in seconds after which retrieved claims from the userinfo endpoint should be refreshed."),
AP_INIT_TAKE1(OIDCOAuthTokenIntrospectionInterval,
ap_set_int_slot,
(void *) APR_OFFSETOF(oidc_dir_cfg, oauth_token_introspect_interval),
RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
"Sets the token introspection refresh interval."),
AP_INIT_TAKE1(OIDCPreservePost,
oidc_set_preserve_post,
(void *) APR_OFFSETOF(oidc_dir_cfg, preserve_post),
RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
"Indicates whether POST parameters will be preserved across authentication requests."),
AP_INIT_FLAG(OIDCPassRefreshToken,
ap_set_flag_slot,
(void*)APR_OFFSETOF(oidc_dir_cfg, pass_refresh_token),
RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
"Pass the refresh token in a header and/or environment variable (On or Off)"),
AP_INIT_TAKE1(OIDCRequestObject,
oidc_set_string_slot,
(void *)APR_OFFSETOF(oidc_cfg, provider.request_object),
RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
"The default request object settings"),
AP_INIT_TAKE1(OIDCProviderMetadataRefreshInterval,
oidc_set_int_slot,
(void*)APR_OFFSETOF(oidc_cfg, provider_metadata_refresh_interval),
RSRC_CONF,
"Provider metadata refresh interval in seconds."),
AP_INIT_TAKE1(OIDCProviderAuthRequestMethod,
oidc_set_auth_request_method,
(void*)APR_OFFSETOF(oidc_cfg, provider.auth_request_method),
RSRC_CONF,
"HTTP method used to send the authentication request to the provider (GET or POST)."),
AP_INIT_ITERATE(OIDCInfoHook,
oidc_set_info_hook_data,
(void *)APR_OFFSETOF(oidc_cfg, info_hook_data),
RSRC_CONF,
"The data that will be returned from the info hook."),
AP_INIT_ITERATE(OIDCBlackListedClaims,
oidc_set_filtered_claims,
(void *) APR_OFFSETOF(oidc_cfg, black_listed_claims),
RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
"Specify claims that should be removed from the userinfo and/or id_token before storing them in the session."),
AP_INIT_ITERATE(OIDCWhiteListedClaims,
oidc_set_filtered_claims,
(void *) APR_OFFSETOF(oidc_cfg, white_listed_claims),
RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
"Specify claims from the userinfo and/or id_token that should be stored in the session (all other claims will be discarded)."),
{ NULL }
};
mod_auth_openidc-2.3.3/src/util.c 0000644 0000765 0000024 00000204020 13202556251 016565 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*/
#include
#include
#include
#include
#include
#include
#include
#include "http_protocol.h"
#include
#include "mod_auth_openidc.h"
#include
#include "pcre_subst.h"
/* hrm, should we get rid of this by adding parameters to the (3) functions? */
extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
/*
* base64url encode a string
*/
int oidc_base64url_encode(request_rec *r, char **dst, const char *src,
int src_len, int remove_padding) {
if ((src == NULL) || (src_len <= 0)) {
oidc_error(r, "not encoding anything; src=NULL and/or src_len<1");
return -1;
}
unsigned int enc_len = apr_base64_encode_len(src_len);
char *enc = apr_palloc(r->pool, enc_len);
apr_base64_encode(enc, (const char *) src, src_len);
unsigned int i = 0;
while (enc[i] != '\0') {
if (enc[i] == '+')
enc[i] = '-';
if (enc[i] == '/')
enc[i] = '_';
if (enc[i] == '=')
enc[i] = ',';
i++;
}
if (remove_padding) {
/* remove /0 and padding */
if (enc_len > 0)
enc_len--;
if ((enc_len > 0) && (enc[enc_len - 1] == ','))
enc_len--;
if ((enc_len > 0) &&(enc[enc_len - 1] == ','))
enc_len--;
enc[enc_len] = '\0';
}
*dst = enc;
return enc_len;
}
/*
* base64url decode a string
*/
int oidc_base64url_decode(apr_pool_t *pool, char **dst, const char *src) {
if (src == NULL) {
return -1;
}
char *dec = apr_pstrdup(pool, src);
int i = 0;
while (dec[i] != '\0') {
if (dec[i] == '-')
dec[i] = '+';
if (dec[i] == '_')
dec[i] = '/';
if (dec[i] == ',')
dec[i] = '=';
i++;
}
switch (strlen(dec) % 4) {
case 0:
break;
case 2:
dec = apr_pstrcat(pool, dec, "==", NULL);
break;
case 3:
dec = apr_pstrcat(pool, dec, "=", NULL);
break;
default:
return 0;
}
int dlen = apr_base64_decode_len(dec);
*dst = apr_palloc(pool, dlen);
return apr_base64_decode(*dst, dec);
}
apr_byte_t oidc_util_jwt_create(request_rec *r, const char *secret,
json_t *payload, char **compact_encoded_jwt) {
apr_byte_t rv = FALSE;
oidc_jose_error_t err;
oidc_jwk_t *jwk = NULL;
oidc_jwt_t *jwt = NULL;
oidc_jwt_t *jwe = NULL;
if (oidc_util_create_symmetric_key(r, secret, 0, OIDC_JOSE_ALG_SHA256,
FALSE, &jwk) == FALSE)
goto end;
jwt = oidc_jwt_new(r->pool, TRUE, FALSE);
if (jwt == NULL) {
oidc_error(r, "creating JWT failed");
goto end;
}
jwt->header.alg = apr_pstrdup(r->pool, CJOSE_HDR_ALG_HS256);
jwt->payload.value.json = payload;
if (oidc_jwt_sign(r->pool, jwt, jwk, &err) == FALSE) {
oidc_error(r, "signing JWT failed: %s", oidc_jose_e2s(r->pool, err));
goto end;
}
jwe = oidc_jwt_new(r->pool, TRUE, FALSE);
if (jwe == NULL) {
oidc_error(r, "creating JWE failed");
goto end;
}
jwe->header.alg = apr_pstrdup(r->pool, CJOSE_HDR_ALG_DIR);
jwe->header.enc = apr_pstrdup(r->pool, CJOSE_HDR_ENC_A256GCM);
const char *cser = oidc_jwt_serialize(r->pool, jwt, &err);
if (oidc_jwt_encrypt(r->pool, jwe, jwk, cser, compact_encoded_jwt,
&err) == FALSE) {
oidc_error(r, "encrypting JWT failed: %s", oidc_jose_e2s(r->pool, err));
goto end;
}
rv = TRUE;
end:
if (jwe != NULL)
oidc_jwt_destroy(jwe);
if (jwk != NULL)
oidc_jwk_destroy(jwk);
if (jwt != NULL) {
jwt->payload.value.json = NULL;
oidc_jwt_destroy(jwt);
}
return rv;
}
apr_byte_t oidc_util_jwt_verify(request_rec *r, const char *secret,
const char *compact_encoded_jwt, json_t **result) {
oidc_debug(r, "enter: JWT header=%s",
oidc_proto_peek_jwt_header(r, compact_encoded_jwt, NULL));
apr_byte_t rv = FALSE;
oidc_jose_error_t err;
oidc_jwk_t *jwk = NULL;
oidc_jwt_t *jwt = NULL;
if (oidc_util_create_symmetric_key(r, secret, 0, OIDC_JOSE_ALG_SHA256,
FALSE, &jwk) == FALSE)
goto end;
apr_hash_t *keys = apr_hash_make(r->pool);
apr_hash_set(keys, "", APR_HASH_KEY_STRING, jwk);
if (oidc_jwt_parse(r->pool, compact_encoded_jwt, &jwt, keys, &err) == FALSE) {
oidc_error(r, "parsing JWT failed: %s", oidc_jose_e2s(r->pool, err));
goto end;
}
if (oidc_jwt_verify(r->pool, jwt, keys, &err) == FALSE) {
oidc_error(r, "verifying JWT failed: %s", oidc_jose_e2s(r->pool, err));
goto end;
}
*result = json_deep_copy(jwt->payload.value.json);
rv = TRUE;
end:
if (jwk != NULL)
oidc_jwk_destroy(jwk);
if (jwt != NULL)
oidc_jwt_destroy(jwt);
return rv;
}
/*
* convert a character to an ENVIRONMENT-variable-safe variant
*/
int oidc_char_to_env(int c) {
return apr_isalnum(c) ? apr_toupper(c) : '_';
}
/*
* compare two strings based on how they would be converted to an
* environment variable, as per oidc_char_to_env. If len is specified
* as less than zero, then the full strings will be compared. Returns
* less than, equal to, or greater than zero based on whether the
* first argument's conversion to an environment variable is less
* than, equal to, or greater than the second.
*/
int oidc_strnenvcmp(const char *a, const char *b, int len) {
int d, i = 0;
while (1) {
/* If len < 0 then we don't stop based on length */
if (len >= 0 && i >= len)
return 0;
/* If we're at the end of both strings, they're equal */
if (!*a && !*b)
return 0;
/* If the second string is shorter, pick it: */
if (*a && !*b)
return 1;
/* If the first string is shorter, pick it: */
if (!*a && *b)
return -1;
/* Normalize the characters as for conversion to an
* environment variable. */
d = oidc_char_to_env(*a) - oidc_char_to_env(*b);
if (d)
return d;
a++;
b++;
i++;
}
return 0;
}
/*
* escape a string
*/
char *oidc_util_escape_string(const request_rec *r, const char *str) {
CURL *curl = curl_easy_init();
if (curl == NULL) {
oidc_error(r, "curl_easy_init() error");
return NULL;
}
char *result = curl_easy_escape(curl, str, 0);
if (result == NULL) {
oidc_error(r, "curl_easy_escape() error");
return NULL;
}
char *rv = apr_pstrdup(r->pool, result);
curl_free(result);
curl_easy_cleanup(curl);
return rv;
}
/*
* escape a string
*/
char *oidc_util_unescape_string(const request_rec *r, const char *str) {
CURL *curl = curl_easy_init();
if (curl == NULL) {
oidc_error(r, "curl_easy_init() error");
return NULL;
}
char *result = curl_easy_unescape(curl, str, 0, 0);
if (result == NULL) {
oidc_error(r, "curl_easy_unescape() error");
return NULL;
}
char *rv = apr_pstrdup(r->pool, result);
curl_free(result);
curl_easy_cleanup(curl);
//oidc_debug(r, "input=\"%s\", output=\"%s\"", str, rv);
return rv;
}
/*
* HTML escape a string
*/
char *oidc_util_html_escape(apr_pool_t *pool, const char *s) {
// TODO: this has performance/memory issues for large chunks of HTML
const char chars[6] = { '&', '\'', '\"', '>', '<', '\0' };
const char * const replace[] =
{ "&", "'", """, ">", "<", };
unsigned int i, j = 0, k, n = 0, len = strlen(chars);
unsigned int m = 0;
char *r = apr_pcalloc(pool, strlen(s) * 6);
for (i = 0; i < strlen(s); i++) {
for (n = 0; n < len; n++) {
if (s[i] == chars[n]) {
m = (unsigned int)strlen(replace[n]);
for (k = 0; k < m; k++)
r[j + k] = replace[n][k];
j += m;
break;
}
}
if (n == len) {
r[j] = s[i];
j++;
}
}
r[j] = '\0';
return apr_pstrdup(pool, r);
}
/*
* get the URL scheme that is currently being accessed
*/
static const char *oidc_get_current_url_scheme(const request_rec *r) {
/* first see if there's a proxy/load-balancer in front of us */
const char *scheme_str = oidc_util_hdr_in_x_forwarded_proto_get(r);
/* if not we'll determine the scheme used to connect to this server */
if (scheme_str == NULL) {
#ifdef APACHE2_0
scheme_str = (char *) ap_http_method(r);
#else
scheme_str = (char *) ap_http_scheme(r);
#endif
}
if ((scheme_str == NULL)
|| ((apr_strnatcmp(scheme_str, "http") != 0)
&& (apr_strnatcmp(scheme_str, "https") != 0))) {
oidc_warn(r,
"detected HTTP scheme \"%s\" is not \"http\" nor \"https\"; perhaps your reverse proxy passes a wrongly configured \"%s\" header: falling back to default \"https\"",
scheme_str, OIDC_HTTP_HDR_X_FORWARDED_PROTO);
scheme_str = "https";
}
return scheme_str;
}
/*
* get the URL port that is currently being accessed
*/
static const char *oidc_get_current_url_port(const request_rec *r,
const char *scheme_str) {
/*
* first see if there's a proxy/load-balancer in front of us
* that sets X-Forwarded-Port
*/
const char *port_str = oidc_util_hdr_in_x_forwarded_port_get(r);
if (port_str)
return port_str;
/*
* see if we can get the port from the "X-Forwarded-Host" header
* and if that header was set we'll assume defaults
*/
const char *host_hdr = oidc_util_hdr_in_x_forwarded_host_get(r);
if (host_hdr) {
port_str = strchr(host_hdr, OIDC_CHAR_COLON);
if (port_str)
port_str++;
return port_str;
}
/*
* see if we can get the port from the "Host" header; if not
* we'll determine the port locally
*/
host_hdr = oidc_util_hdr_in_host_get(r);
if (host_hdr) {
port_str = strchr(host_hdr, OIDC_CHAR_COLON);
if (port_str) {
port_str++;
return port_str;
}
}
/*
* if X-Forwarded-Proto assume the default port otherwise the
* port should have been set in the X-Forwarded-Port header
*/
if (oidc_util_hdr_in_x_forwarded_proto_get(r))
return NULL;
/*
* if no port was set in the Host header and no X-Forwarded-Proto was set, we'll
* determine the port locally and don't print it when it's the default for the protocol
*/
const apr_port_t port = r->connection->local_addr->port;
if ((apr_strnatcmp(scheme_str, "https") == 0) && port == 443)
return NULL;
else if ((apr_strnatcmp(scheme_str, "http") == 0) && port == 80)
return NULL;
port_str = apr_psprintf(r->pool, "%u", port);
return port_str;
}
/*
* get the hostname part of the URL that is currently being accessed
*/
const char *oidc_get_current_url_host(request_rec *r) {
const char *host_str = oidc_util_hdr_in_x_forwarded_host_get(r);
if (host_str == NULL)
host_str = oidc_util_hdr_in_host_get(r);
if (host_str) {
host_str = apr_pstrdup(r->pool, host_str);
char *p = strchr(host_str, OIDC_CHAR_COLON);
if (p != NULL)
*p = '\0';
} else {
/* no Host header, HTTP 1.0 */
host_str = ap_get_server_name(r);
}
return host_str;
}
/*
* get the base part of the current URL (scheme + host (+ port))
*/
static const char *oidc_get_current_url_base(request_rec *r) {
const char *scheme_str = oidc_get_current_url_scheme(r);
const char *host_str = oidc_get_current_url_host(r);
const char *port_str = oidc_get_current_url_port(r, scheme_str);
port_str = port_str ? apr_psprintf(r->pool, ":%s", port_str) : "";
char *url = apr_pstrcat(r->pool, scheme_str, "://", host_str, port_str,
NULL);
return url;
}
/*
* get the URL that is currently being accessed
*/
char *oidc_get_current_url(request_rec *r) {
char *url = apr_pstrcat(r->pool, oidc_get_current_url_base(r), r->uri,
(r->args != NULL && *r->args != '\0' ? "?" : ""), r->args,
NULL);
oidc_debug(r, "current URL '%s'", url);
return url;
}
/*
* determine absolute redirect uri
*/
const char *oidc_get_redirect_uri(request_rec *r, oidc_cfg *cfg) {
char *redirect_uri = cfg->redirect_uri;
if ((redirect_uri != NULL)
&& (redirect_uri[0] == OIDC_CHAR_FORWARD_SLASH)) {
// relative redirect uri
redirect_uri = apr_pstrcat(r->pool, oidc_get_current_url_base(r),
cfg->redirect_uri, NULL);
oidc_debug(r, "determined absolute redirect uri: %s", redirect_uri);
}
return redirect_uri;
}
/*
* determine absolute redirect uri that is issuer specific
*/
const char *oidc_get_redirect_uri_iss(request_rec *r, oidc_cfg *cfg,
oidc_provider_t *provider) {
const char *redirect_uri = oidc_get_redirect_uri(r, cfg);
if (provider->issuer_specific_redirect_uri != 0) {
redirect_uri = apr_psprintf(r->pool, "%s%s%s=%s", redirect_uri,
strchr(redirect_uri ? redirect_uri : "", OIDC_CHAR_QUERY) != NULL ?
OIDC_STR_AMP :
OIDC_STR_QUERY,
OIDC_PROTO_ISS, oidc_util_escape_string(r, provider->issuer));
// OIDC_PROTO_CLIENT_ID,
// oidc_util_escape_string(r, provider->client_id));
oidc_debug(r, "determined issuer specific redirect uri: %s",
redirect_uri);
}
return redirect_uri;
}
/* buffer to hold HTTP call responses */
typedef struct oidc_curl_buffer {
request_rec *r;
char *memory;
size_t size;
} oidc_curl_buffer;
/* maximum acceptable size of HTTP responses: 1 Mb */
#define OIDC_CURL_MAX_RESPONSE_SIZE 1024 * 1024
/*
* callback for CURL to write bytes that come back from an HTTP call
*/
size_t oidc_curl_write(void *contents, size_t size, size_t nmemb, void *userp) {
size_t realsize = size * nmemb;
oidc_curl_buffer *mem = (oidc_curl_buffer *) userp;
/* check if we don't run over the maximum buffer/memory size for HTTP responses */
if (mem->size + realsize > OIDC_CURL_MAX_RESPONSE_SIZE) {
oidc_error(mem->r,
"HTTP response larger than maximum allowed size: current size=%ld, additional size=%ld, max=%d",
mem->size, realsize, OIDC_CURL_MAX_RESPONSE_SIZE);
return 0;
}
/* allocate the new buffer for the current + new response bytes */
char *newptr = apr_palloc(mem->r->pool, mem->size + realsize + 1);
if (newptr == NULL) {
oidc_error(mem->r,
"memory allocation for new buffer of %ld bytes failed",
mem->size + realsize + 1);
return 0;
}
/* copy over the data from current memory plus the cURL buffer */
memcpy(newptr, mem->memory, mem->size);
memcpy(&(newptr[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory = newptr;
mem->memory[mem->size] = 0;
return realsize;
}
/* context structure for encoding parameters */
typedef struct oidc_http_encode_t {
request_rec *r;
char *encoded_params;
} oidc_http_encode_t;
/*
* add a url-form-encoded name/value pair
*/
static int oidc_util_http_add_form_url_encoded_param(void* rec, const char* key,
const char* value) {
oidc_http_encode_t *ctx = (oidc_http_encode_t*) rec;
oidc_debug(ctx->r, "processing: %s=%s", key, value);
const char *sep = ctx->encoded_params ? OIDC_STR_AMP : "";
ctx->encoded_params = apr_psprintf(ctx->r->pool, "%s%s%s=%s",
ctx->encoded_params ? ctx->encoded_params : "", sep,
oidc_util_escape_string(ctx->r, key),
oidc_util_escape_string(ctx->r, value));
return 1;
}
/*
* construct a URL with query parameters
*/
char *oidc_util_http_query_encoded_url(request_rec *r, const char *url,
const apr_table_t *params) {
char *result = NULL;
if ((params != NULL) && (apr_table_elts(params)->nelts > 0)) {
oidc_http_encode_t data = { r, NULL };
apr_table_do(oidc_util_http_add_form_url_encoded_param, &data, params,
NULL);
const char *sep = NULL;
if (data.encoded_params)
sep = strchr(url ? url : "", OIDC_CHAR_QUERY) != NULL ?
OIDC_STR_AMP :
OIDC_STR_QUERY;
result = apr_psprintf(r->pool, "%s%s%s", url, sep ? sep : "",
data.encoded_params ? data.encoded_params : "");
} else {
result = apr_pstrdup(r->pool, url);
}
oidc_debug(r, "url=%s", result);
return result;
}
/*
* construct form-encoded POST data
*/
static char *oidc_util_http_form_encoded_data(request_rec *r,
const apr_table_t *params) {
char *data = NULL;
if ((params != NULL) && (apr_table_elts(params)->nelts > 0)) {
oidc_http_encode_t encode_data = { r, NULL };
apr_table_do(oidc_util_http_add_form_url_encoded_param, &encode_data,
params,
NULL);
data = encode_data.encoded_params;
}
oidc_debug(r, "data=%s", data);
return data;
}
/*
* execute a HTTP (GET or POST) request
*/
static apr_byte_t oidc_util_http_call(request_rec *r, const char *url,
const char *data, const char *content_type, const char *basic_auth,
const char *bearer_token, int ssl_validate_server, char **response,
int timeout, const char *outgoing_proxy,
apr_array_header_t *pass_cookies, const char *ssl_cert,
const char *ssl_key) {
char curlError[CURL_ERROR_SIZE];
oidc_curl_buffer curlBuffer;
CURL *curl;
struct curl_slist *h_list = NULL;
int i;
/* do some logging about the inputs */
oidc_debug(r,
"url=%s, data=%s, content_type=%s, basic_auth=%s, bearer_token=%s, ssl_validate_server=%d, timeout=%d, outgoing_proxy=%s, pass_cookies=%pp, ssl_cert=%s, ssl_key=%s",
url, data, content_type, basic_auth, bearer_token,
ssl_validate_server, timeout, outgoing_proxy, pass_cookies,
ssl_cert, ssl_key);
curl = curl_easy_init();
if (curl == NULL) {
oidc_error(r, "curl_easy_init() error");
return FALSE;
}
/* set the error buffer as empty before performing a request */
curlError[0] = 0;
/* some of these are not really required */
curl_easy_setopt(curl, CURLOPT_HEADER, 0L);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curlError);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5L);
/* set the timeout */
curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
/* setup the buffer where the response will be written to */
curlBuffer.r = r;
curlBuffer.memory = NULL;
curlBuffer.size = 0;
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, oidc_curl_write);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void * )&curlBuffer);
#ifndef LIBCURL_NO_CURLPROTO
curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS,
CURLPROTO_HTTP|CURLPROTO_HTTPS);
curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS);
#endif
/* set the options for validating the SSL server certificate that the remote site presents */
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER,
(ssl_validate_server != FALSE ? 1L : 0L));
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST,
(ssl_validate_server != FALSE ? 2L : 0L));
#ifdef WIN32
DWORD buflen;
char *ptr = NULL;
char *retval = (char *) malloc(sizeof (TCHAR) * (MAX_PATH + 1));
retval[0] = '\0';
buflen = SearchPath(NULL, "curl-ca-bundle.crt", NULL, MAX_PATH+1, retval, &ptr);
if (buflen > 0)
curl_easy_setopt(curl, CURLOPT_CAINFO, retval);
else
oidc_warn(r, "no curl-ca-bundle.crt file found in path");
free(retval);
#endif
/* identify this HTTP client */
curl_easy_setopt(curl, CURLOPT_USERAGENT, "mod_auth_openidc");
/* set optional outgoing proxy for the local network */
if (outgoing_proxy) {
curl_easy_setopt(curl, CURLOPT_PROXY, outgoing_proxy);
}
/* see if we need to add token in the Bearer Authorization header */
if (bearer_token != NULL) {
h_list = curl_slist_append(h_list,
apr_psprintf(r->pool, "Authorization: Bearer %s",
bearer_token));
}
/* see if we need to perform HTTP basic authentication to the remote site */
if (basic_auth != NULL) {
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_easy_setopt(curl, CURLOPT_USERPWD, basic_auth);
}
if (ssl_cert != NULL)
curl_easy_setopt(curl, CURLOPT_SSLCERT, ssl_cert);
if (ssl_key != NULL)
curl_easy_setopt(curl, CURLOPT_SSLKEY, ssl_key);
if (data != NULL) {
/* set POST data */
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
/* set HTTP method to POST */
curl_easy_setopt(curl, CURLOPT_POST, 1);
}
if (content_type != NULL) {
/* set content type */
h_list = curl_slist_append(h_list,
apr_psprintf(r->pool, "%s: %s", OIDC_HTTP_HDR_CONTENT_TYPE,
content_type));
}
/* see if we need to add any custom headers */
if (h_list != NULL)
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, h_list);
if (pass_cookies != NULL) {
/* gather cookies that we need to pass on from the incoming request */
char *cookie_string = NULL;
for (i = 0; i < pass_cookies->nelts; i++) {
const char *cookie_name = ((const char**) pass_cookies->elts)[i];
char *cookie_value = oidc_util_get_cookie(r, cookie_name);
if (cookie_value != NULL) {
cookie_string =
(cookie_string == NULL) ?
apr_psprintf(r->pool, "%s=%s", cookie_name,
cookie_value) :
apr_psprintf(r->pool, "%s; %s=%s",
cookie_string, cookie_name,
cookie_value);
}
}
/* see if we need to pass any cookies */
if (cookie_string != NULL) {
oidc_debug(r, "passing browser cookies on backend call: %s",
cookie_string);
curl_easy_setopt(curl, CURLOPT_COOKIE, cookie_string);
}
}
/* set the target URL */
curl_easy_setopt(curl, CURLOPT_URL, url);
/* call it and record the result */
int rv = TRUE;
if (curl_easy_perform(curl) != CURLE_OK) {
oidc_error(r, "curl_easy_perform() failed on: %s (%s)", url,
curlError[0] ? curlError : "");
rv = FALSE;
goto out;
}
long response_code;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
oidc_debug(r, "HTTP response code=%ld", response_code);
*response = apr_pstrmemdup(r->pool, curlBuffer.memory, curlBuffer.size);
/* set and log the response */
oidc_debug(r, "response=%s", *response ? *response : "");
out:
/* cleanup and return the result */
if (h_list != NULL)
curl_slist_free_all(h_list);
curl_easy_cleanup(curl);
return rv;
}
/*
* execute HTTP GET request
*/
apr_byte_t oidc_util_http_get(request_rec *r, const char *url,
const apr_table_t *params, const char *basic_auth,
const char *bearer_token, int ssl_validate_server, char **response,
int timeout, const char *outgoing_proxy,
apr_array_header_t *pass_cookies, const char *ssl_cert,
const char *ssl_key) {
char *query_url = oidc_util_http_query_encoded_url(r, url, params);
return oidc_util_http_call(r, query_url, NULL, NULL, basic_auth,
bearer_token, ssl_validate_server, response, timeout,
outgoing_proxy, pass_cookies, ssl_cert, ssl_key);
}
/*
* execute HTTP POST request with form-encoded data
*/
apr_byte_t oidc_util_http_post_form(request_rec *r, const char *url,
const apr_table_t *params, const char *basic_auth,
const char *bearer_token, int ssl_validate_server, char **response,
int timeout, const char *outgoing_proxy,
apr_array_header_t *pass_cookies, const char *ssl_cert,
const char *ssl_key) {
char *data = oidc_util_http_form_encoded_data(r, params);
return oidc_util_http_call(r, url, data,
OIDC_CONTENT_TYPE_FORM_ENCODED, basic_auth, bearer_token,
ssl_validate_server, response, timeout, outgoing_proxy,
pass_cookies, ssl_cert, ssl_key);
}
/*
* execute HTTP POST request with JSON-encoded data
*/
apr_byte_t oidc_util_http_post_json(request_rec *r, const char *url,
json_t *json, const char *basic_auth, const char *bearer_token,
int ssl_validate_server, char **response, int timeout,
const char *outgoing_proxy, apr_array_header_t *pass_cookies,
const char *ssl_cert, const char *ssl_key) {
char *data =
json != NULL ?
oidc_util_encode_json_object(r, json, JSON_COMPACT) : NULL;
return oidc_util_http_call(r, url, data, OIDC_CONTENT_TYPE_JSON, basic_auth,
bearer_token, ssl_validate_server, response, timeout,
outgoing_proxy, pass_cookies, ssl_cert, ssl_key);
}
/*
* get the current path from the request in a normalized way
*/
static char *oidc_util_get_path(request_rec *r) {
size_t i;
char *p;
p = r->parsed_uri.path;
if ((p == NULL) || (p[0] == '\0'))
return apr_pstrdup(r->pool, OIDC_STR_FORWARD_SLASH);
for (i = strlen(p) - 1; i > 0; i--)
if (p[i] == OIDC_CHAR_FORWARD_SLASH)
break;
return apr_pstrndup(r->pool, p, i + 1);
}
/*
* get the cookie path setting and check that it matches the request path; cook it up if it is not set
*/
static char *oidc_util_get_cookie_path(request_rec *r) {
char *rv = NULL, *requestPath = oidc_util_get_path(r);
char *cookie_path = oidc_cfg_dir_cookie_path(r);
if (cookie_path != NULL) {
if (strncmp(cookie_path, requestPath, strlen(cookie_path)) == 0)
rv = cookie_path;
else {
oidc_warn(r,
"" OIDCCookiePath " (%s) is not a substring of request path, using request path (%s) for cookie",
cookie_path, requestPath);
rv = requestPath;
}
} else {
rv = requestPath;
}
return (rv);
}
#define OIDC_COOKIE_FLAG_DOMAIN "Domain"
#define OIDC_COOKIE_FLAG_PATH "Path"
#define OIDC_COOKIE_FLAG_EXPIRES "Expires"
#define OIDC_COOKIE_FLAG_SECURE "Secure"
#define OIDC_COOKIE_FLAG_HTTP_ONLY "HttpOnly"
#define OIDC_COOKIE_MAX_SIZE 4093
/*
* set a cookie in the HTTP response headers
*/
void oidc_util_set_cookie(request_rec *r, const char *cookieName,
const char *cookieValue, apr_time_t expires, const char *ext) {
oidc_cfg *c = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
char *headerString, *expiresString = NULL;
/* see if we need to clear the cookie */
if (apr_strnatcmp(cookieValue, "") == 0)
expires = 0;
/* construct the expire value */
if (expires != -1) {
expiresString = (char *) apr_pcalloc(r->pool, APR_RFC822_DATE_LEN);
if (apr_rfc822_date(expiresString, expires) != APR_SUCCESS) {
oidc_error(r, "could not set cookie expiry date");
}
}
/* construct the cookie value */
headerString = apr_psprintf(r->pool, "%s=%s", cookieName, cookieValue);
headerString = apr_psprintf(r->pool, "%s; %s=%s", headerString,
OIDC_COOKIE_FLAG_PATH, oidc_util_get_cookie_path(r));
if (expiresString != NULL)
headerString = apr_psprintf(r->pool, "%s; %s=%s", headerString,
OIDC_COOKIE_FLAG_EXPIRES, expiresString);
if (c->cookie_domain != NULL)
headerString = apr_psprintf(r->pool, "%s; %s=%s", headerString,
OIDC_COOKIE_FLAG_DOMAIN, c->cookie_domain);
if (apr_strnatcasecmp("https", oidc_get_current_url_scheme(r)) == 0)
headerString = apr_psprintf(r->pool, "%s; %s", headerString,
OIDC_COOKIE_FLAG_SECURE);
if (c->cookie_http_only != FALSE)
headerString = apr_psprintf(r->pool, "%s; %s", headerString,
OIDC_COOKIE_FLAG_HTTP_ONLY);
if (ext != NULL)
headerString = apr_psprintf(r->pool, "%s; %s", headerString, ext);
/* sanity check on overall cookie value size */
if (strlen(headerString) > OIDC_COOKIE_MAX_SIZE) {
oidc_warn(r,
"the length of the cookie value (%d) is greater than %d(!) bytes, this may not work with all browsers/server combinations: consider switching to a server side caching!",
(int )strlen(headerString), OIDC_COOKIE_MAX_SIZE);
}
/* use r->err_headers_out so we always print our headers (even on 302 redirect) - headers_out only prints on 2xx responses */
oidc_util_hdr_err_out_add(r, OIDC_HTTP_HDR_SET_COOKIE, headerString);
}
/*
* get a cookie from the HTTP request
*/
char *oidc_util_get_cookie(request_rec *r, const char *cookieName) {
char *cookie, *tokenizerCtx, *rv = NULL;
/* get the Cookie value */
char *cookies = apr_pstrdup(r->pool, oidc_util_hdr_in_cookie_get(r));
if (cookies != NULL) {
/* tokenize on ; to find the cookie we want */
cookie = apr_strtok(cookies, OIDC_STR_SEMI_COLON, &tokenizerCtx);
while (cookie != NULL) {
while (*cookie == OIDC_CHAR_SPACE)
cookie++;
/* see if we've found the cookie that we're looking for */
if ((strncmp(cookie, cookieName, strlen(cookieName)) == 0)
&& (cookie[strlen(cookieName)] == OIDC_CHAR_EQUAL)) {
/* skip to the meat of the parameter (the value after the '=') */
cookie += (strlen(cookieName) + 1);
rv = apr_pstrdup(r->pool, cookie);
break;
}
/* go to the next cookie */
cookie = apr_strtok(NULL, OIDC_STR_SEMI_COLON, &tokenizerCtx);
}
}
/* log what we've found */
oidc_debug(r, "returning \"%s\" = %s", cookieName,
rv ? apr_psprintf(r->pool, "\"%s\"", rv) : "");
return rv;
}
#define OIDC_COOKIE_CHUNKS_SEPARATOR "_"
#define OIDC_COOKIE_CHUNKS_POSTFIX "chunks"
/*
* get the name of the cookie that contains the number of chunks
*/
static char *oidc_util_get_chunk_count_name(request_rec *r,
const char *cookieName) {
return apr_psprintf(r->pool, "%s%s%s", cookieName,
OIDC_COOKIE_CHUNKS_SEPARATOR, OIDC_COOKIE_CHUNKS_POSTFIX);
}
/*
* get the number of cookie chunks set by the browser
*/
static int oidc_util_get_chunked_count(request_rec *r, const char *cookieName) {
int chunkCount = 0;
char* chunkCountValue = oidc_util_get_cookie(r,
oidc_util_get_chunk_count_name(r, cookieName));
if (chunkCountValue != NULL) {
char *endptr = NULL;
chunkCount = strtol(chunkCountValue, &endptr, 10);
if ((*chunkCountValue == '\0') || (*endptr != '\0'))
chunkCount = 0;
}
return chunkCount;
}
/*
* get the name of a chunk
*/
static char *oidc_util_get_chunk_cookie_name(request_rec *r,
const char *cookieName, int i) {
return apr_psprintf(r->pool, "%s%s%d", cookieName,
OIDC_COOKIE_CHUNKS_SEPARATOR, i);
}
/*
* get a cookie value that is split over a number of chunked cookies
*/
char *oidc_util_get_chunked_cookie(request_rec *r, const char *cookieName,
int chunkSize) {
char *cookieValue = NULL;
char *chunkValue = NULL;
int i = 0;
if (chunkSize == 0) {
cookieValue = oidc_util_get_cookie(r, cookieName);
} else {
int chunkCount = oidc_util_get_chunked_count(r, cookieName);
if (chunkCount > 0) {
cookieValue = "";
for (i = 0; i < chunkCount; i++) {
chunkValue = oidc_util_get_cookie(r,
oidc_util_get_chunk_cookie_name(r, cookieName, i));
if (chunkValue != NULL)
cookieValue = apr_psprintf(r->pool, "%s%s", cookieValue,
chunkValue);
}
} else {
cookieValue = oidc_util_get_cookie(r, cookieName);
}
}
return cookieValue;
}
/*
* set a cookie value that is split over a number of chunked cookies
*/
void oidc_util_set_chunked_cookie(request_rec *r, const char *cookieName,
const char *cookieValue, apr_time_t expires, int chunkSize,
const char *ext) {
int i = 0;
int cookieLength = strlen(cookieValue);
char *chunkCountName = oidc_util_get_chunk_count_name(r, cookieName);
char *chunkValue = NULL;
/* see if we need to chunk at all */
if ((chunkSize == 0)
|| ((cookieLength > 0) && (cookieLength < chunkSize))) {
oidc_util_set_cookie(r, cookieName, cookieValue, expires, ext);
return;
}
/* see if we need to clear a possibly chunked cookie */
if (cookieLength == 0) {
int chunkCount = oidc_util_get_chunked_count(r, cookieName);
if (chunkCount > 0) {
for (i = 0; i < chunkCount; i++)
oidc_util_set_cookie(r,
oidc_util_get_chunk_cookie_name(r, cookieName, i), "",
expires, ext);
oidc_util_set_cookie(r, chunkCountName, "", expires, ext);
} else {
oidc_util_set_cookie(r, cookieName, "", expires, ext);
}
return;
}
/* set a chunked cookie */
int chunkCountValue = cookieLength / chunkSize + 1;
const char *ptr = cookieValue;
for (i = 0; i < chunkCountValue; i++) {
chunkValue = apr_pstrndup(r->pool, ptr, chunkSize);
ptr += chunkSize;
oidc_util_set_cookie(r,
oidc_util_get_chunk_cookie_name(r, cookieName, i), chunkValue,
expires, ext);
};
oidc_util_set_cookie(r, chunkCountName,
apr_psprintf(r->pool, "%d", chunkCountValue), expires, ext);
}
/*
* normalize a string for use as an HTTP Header Name. Any invalid
* characters (per http://tools.ietf.org/html/rfc2616#section-4.2 and
* http://tools.ietf.org/html/rfc2616#section-2.2) are replaced with
* a dash ('-') character.
*/
char *oidc_normalize_header_name(const request_rec *r, const char *str) {
/* token = 1*
* CTL =
* separators = "(" | ")" | "<" | ">" | "@"
* | "," | ";" | ":" | "\" | <">
* | "/" | "[" | "]" | "?" | "="
* | "{" | "}" | SP | HT */
const char *separators = "()<>@,;:\\\"/[]?={} \t";
char *ns = apr_pstrdup(r->pool, str);
size_t i;
for (i = 0; i < strlen(ns); i++) {
if (ns[i] < 32 || ns[i] == 127)
ns[i] = '-';
else if (strchr(separators, ns[i]) != NULL)
ns[i] = '-';
}
return ns;
}
/*
* see if the currently accessed path matches a path from a defined URL
*/
apr_byte_t oidc_util_request_matches_url(request_rec *r, const char *url) {
apr_uri_t uri;
memset(&uri, 0, sizeof(apr_uri_t));
if ((url == NULL) || (apr_uri_parse(r->pool, url, &uri) != APR_SUCCESS))
return FALSE;
oidc_debug(r, "comparing \"%s\"==\"%s\"", r->parsed_uri.path, uri.path);
if ((r->parsed_uri.path == NULL) || (uri.path == NULL))
return (r->parsed_uri.path == uri.path);
return (apr_strnatcmp(r->parsed_uri.path, uri.path) == 0);
}
/*
* see if the currently accessed path has a certain query parameter
*/
apr_byte_t oidc_util_request_has_parameter(request_rec *r, const char* param) {
if (r->args == NULL)
return FALSE;
const char *option1 = apr_psprintf(r->pool, "%s=", param);
const char *option2 = apr_psprintf(r->pool, "&%s=", param);
return ((strstr(r->args, option1) == r->args)
|| (strstr(r->args, option2) != NULL)) ? TRUE : FALSE;
}
/*
* get a query parameter
*/
apr_byte_t oidc_util_get_request_parameter(request_rec *r, char *name,
char **value) {
char *tokenizer_ctx, *p, *args;
const char *k_param = apr_psprintf(r->pool, "%s=", name);
const size_t k_param_sz = strlen(k_param);
*value = NULL;
if (r->args == NULL || strlen(r->args) == 0)
return FALSE;
/* not sure why we do this, but better be safe than sorry */
args = apr_pstrmemdup(r->pool, r->args, strlen(r->args));
p = apr_strtok(args, OIDC_STR_AMP, &tokenizer_ctx);
do {
if (p && strncmp(p, k_param, k_param_sz) == 0) {
*value = apr_pstrdup(r->pool, p + k_param_sz);
*value = oidc_util_unescape_string(r, *value);
}
p = apr_strtok(NULL, OIDC_STR_AMP, &tokenizer_ctx);
} while (p);
return (*value != NULL ? TRUE : FALSE);
}
/*
* printout a JSON string value
*/
static apr_byte_t oidc_util_json_string_print(request_rec *r, json_t *result,
const char *key, const char *log) {
json_t *value = json_object_get(result, key);
if (value != NULL && !json_is_null(value)) {
oidc_error(r,
"%s: response contained an \"%s\" entry with value: \"%s\"",
log, key,
oidc_util_encode_json_object(r, value, JSON_ENCODE_ANY));
return TRUE;
}
return FALSE;
}
/*
* check a JSON object for "error" results and printout
*/
static apr_byte_t oidc_util_check_json_error(request_rec *r, json_t *json) {
if (oidc_util_json_string_print(r, json, OIDC_PROTO_ERROR,
"oidc_util_check_json_error") == TRUE) {
oidc_util_json_string_print(r, json, OIDC_PROTO_ERROR_DESCRIPTION,
"oidc_util_check_json_error");
return TRUE;
}
return FALSE;
}
/*
* parse a JSON object
*/
apr_byte_t oidc_util_decode_json_object(request_rec *r, const char *str,
json_t **json) {
if (str == NULL)
return FALSE;
json_error_t json_error;
*json = json_loads(str, 0, &json_error);
/* decode the JSON contents of the buffer */
if (*json == NULL) {
/* something went wrong */
oidc_error(r, "JSON parsing returned an error: %s (%s)",
json_error.text, str);
return FALSE;
}
if (!json_is_object(*json)) {
/* oops, no JSON */
oidc_error(r, "parsed JSON did not contain a JSON object");
json_decref(*json);
*json = NULL;
return FALSE;
}
return TRUE;
}
/*
* encode a JSON object
*/
char *oidc_util_encode_json_object(request_rec *r, json_t *json, size_t flags) {
char *s = json_dumps(json, flags);
char *s_value = apr_pstrdup(r->pool, s);
free(s);
return s_value;
}
/*
* decode a JSON string, check for "error" results and printout
*/
apr_byte_t oidc_util_decode_json_and_check_error(request_rec *r,
const char *str, json_t **json) {
if (oidc_util_decode_json_object(r, str, json) == FALSE)
return FALSE;
// see if it is not an error response somehow
if (oidc_util_check_json_error(r, *json) == TRUE) {
json_decref(*json);
*json = NULL;
return FALSE;
}
return TRUE;
}
/*
* sends content to the user agent
*/
int oidc_util_http_send(request_rec *r, const char *data, size_t data_len,
const char *content_type, int success_rvalue) {
ap_set_content_type(r, content_type);
apr_bucket_brigade *bb = apr_brigade_create(r->pool,
r->connection->bucket_alloc);
apr_bucket *b = apr_bucket_transient_create(data, data_len,
r->connection->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
b = apr_bucket_eos_create(r->connection->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
int rc = ap_pass_brigade(r->output_filters, bb);
if (rc != APR_SUCCESS) {
oidc_error(r,
"ap_pass_brigade returned an error: %d; if you're using this module combined with mod_deflate try make an exception for the " OIDCRedirectURI " e.g. using SetEnvIf Request_URI no-gzip",
rc);
return HTTP_INTERNAL_SERVER_ERROR;
}
//r->status = success_rvalue;
return success_rvalue;
}
/*
* send HTML content to the user agent
*/
int oidc_util_html_send(request_rec *r, const char *title,
const char *html_head, const char *on_load, const char *html_body,
int status_code) {
char *html =
"\n"
"\n"
" \n"
" \n"
" %s\n"
" %s\n"
" \n"
" \n"
"%s\n"
" \n"
"\n";
html = apr_psprintf(r->pool, html,
title ? oidc_util_html_escape(r->pool, title) : "",
html_head ? html_head : "",
on_load ? apr_psprintf(r->pool, " onload=\"%s()\"", on_load) : "",
html_body ? html_body : "");
return oidc_util_http_send(r, html, strlen(html), OIDC_CONTENT_TYPE_HTML,
status_code);
}
static char *html_error_template_contents = NULL;
/*
* get the full path to a file based on an (already) absolute filename or a filename
* that is relative to the Apache root directory
*/
char *oidc_util_get_full_path(apr_pool_t *pool, const char *abs_or_rel_filename) {
return (abs_or_rel_filename) ? ap_server_root_relative(pool, abs_or_rel_filename) : NULL;
}
/*
* send a user-facing error to the browser
*/
int oidc_util_html_send_error(request_rec *r, const char *html_template,
const char *error, const char *description, int status_code) {
char *html = "";
if (html_template != NULL) {
html_template = oidc_util_get_full_path(r->pool, html_template);
if (html_error_template_contents == NULL) {
int rc = oidc_util_file_read(r, html_template,
r->server->process->pool, &html_error_template_contents);
if (rc == FALSE) {
oidc_error(r, "could not read HTML error template: %s",
html_template);
html_error_template_contents = NULL;
}
}
if (html_error_template_contents) {
html = apr_psprintf(r->pool, html_error_template_contents,
oidc_util_html_escape(r->pool, error ? error : ""),
oidc_util_html_escape(r->pool,
description ? description : ""));
return oidc_util_http_send(r, html, strlen(html),
OIDC_CONTENT_TYPE_HTML, status_code);
}
}
if (error != NULL) {
html = apr_psprintf(r->pool, "%s
Error:
%s
", html,
oidc_util_html_escape(r->pool, error));
}
if (description != NULL) {
html = apr_psprintf(r->pool, "%s
Description:
%s
",
html, oidc_util_html_escape(r->pool, description));
}
return oidc_util_html_send(r, "Error", NULL, NULL, html, status_code);
}
/*
* read all bytes from the HTTP request
*/
static apr_byte_t oidc_util_read(request_rec *r, char **rbuf) {
apr_size_t bytes_read;
apr_size_t bytes_left;
apr_size_t len;
long read_length;
if (ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK) != OK)
return FALSE;
len = ap_should_client_block(r) ? r->remaining : 0;
if (len > OIDC_MAX_POST_DATA_LEN) {
oidc_error(r, "POST parameter value is too large: %lu bytes (max=%d)",
(unsigned long ) len, OIDC_MAX_POST_DATA_LEN);
return FALSE;
}
*rbuf = (char *) apr_palloc(r->pool, len + 1);
if (*rbuf == NULL) {
oidc_error(r, "could not allocate memory for %lu bytes of POST data.",
(unsigned long )len);
return FALSE;
}
(*rbuf)[len] = '\0';
bytes_read = 0;
bytes_left = len;
while (bytes_left > 0) {
read_length = ap_get_client_block(r, &(*rbuf)[bytes_read], bytes_left);
if (read_length == 0) {
(*rbuf)[bytes_read] = '\0';
break;
} else if (read_length < 0) {
oidc_error(r, "failed to read POST data from client");
return FALSE;
}
bytes_read += read_length;
bytes_left -= read_length;
}
return TRUE;
}
/*
* read form-encoded parameters from a string in to a table
*/
apr_byte_t oidc_util_read_form_encoded_params(request_rec *r,
apr_table_t *table, char *data) {
const char *key, *val, *p = data;
while (p && *p && (val = ap_getword(r->pool, &p, OIDC_CHAR_AMP))) {
key = ap_getword(r->pool, &val, OIDC_CHAR_EQUAL);
key = oidc_util_unescape_string(r, key);
val = oidc_util_unescape_string(r, val);
oidc_debug(r, "read: %s=%s", key, val);
apr_table_set(table, key, val);
}
oidc_debug(r, "parsed: %d bytes into %d elements",
data ? (int )strlen(data) : 0, apr_table_elts(table)->nelts);
return TRUE;
}
/*
* read the POST parameters in to a table
*/
apr_byte_t oidc_util_read_post_params(request_rec *r, apr_table_t *table) {
char *data = NULL;
if (r->method_number != M_POST)
return FALSE;
if (oidc_util_read(r, &data) != TRUE)
return FALSE;
return oidc_util_read_form_encoded_params(r, table, data);
}
/*
* read a file from a path on disk
*/
apr_byte_t oidc_util_file_read(request_rec *r, const char *path,
apr_pool_t *pool, char **result) {
apr_file_t *fd = NULL;
apr_status_t rc = APR_SUCCESS;
char s_err[128];
apr_finfo_t finfo;
/* open the file if it exists */
if ((rc = apr_file_open(&fd, path, APR_FOPEN_READ | APR_FOPEN_BUFFERED,
APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) {
oidc_warn(r, "no file found at: \"%s\"", path);
return FALSE;
}
/* the file exists, now lock it */
apr_file_lock(fd, APR_FLOCK_EXCLUSIVE);
/* move the read pointer to the very start of the cache file */
apr_off_t begin = 0;
apr_file_seek(fd, APR_SET, &begin);
/* get the file info so we know its size */
if ((rc = apr_file_info_get(&finfo, APR_FINFO_SIZE, fd)) != APR_SUCCESS) {
oidc_error(r, "error calling apr_file_info_get on file: \"%s\" (%s)",
path, apr_strerror(rc, s_err, sizeof(s_err)));
goto error_close;
}
/* now that we have the size of the file, allocate a buffer that can contain its contents */
*result = apr_palloc(pool, finfo.size + 1);
/* read the file in to the buffer */
apr_size_t bytes_read = 0;
if ((rc = apr_file_read_full(fd, *result, finfo.size, &bytes_read))
!= APR_SUCCESS) {
oidc_error(r, "apr_file_read_full on (%s) returned an error: %s", path,
apr_strerror(rc, s_err, sizeof(s_err)));
goto error_close;
}
/* just to be sure, we set a \0 (we allocated space for it anyway) */
(*result)[bytes_read] = '\0';
/* check that we've got all of it */
if (bytes_read != finfo.size) {
oidc_error(r,
"apr_file_read_full on (%s) returned less bytes (%" APR_SIZE_T_FMT ") than expected: (%" APR_OFF_T_FMT ")",
path, bytes_read, finfo.size);
goto error_close;
}
/* we're done, unlock and close the file */
apr_file_unlock(fd);
apr_file_close(fd);
/* log successful content retrieval */
oidc_debug(r, "file read successfully \"%s\"", path);
return TRUE;
error_close:
apr_file_unlock(fd);
apr_file_close(fd);
oidc_error(r, "return error");
return FALSE;
}
/*
* write data to a file
*/
apr_byte_t oidc_util_file_write(request_rec *r, const char *path,
const char *data) {
apr_file_t *fd = NULL;
apr_status_t rc = APR_SUCCESS;
apr_size_t bytes_written = 0;
char s_err[128];
/* try to open the metadata file for writing, creating it if it does not exist */
if ((rc = apr_file_open(&fd, path,
(APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE),
APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) {
oidc_error(r, "file \"%s\" could not be opened (%s)", path,
apr_strerror(rc, s_err, sizeof(s_err)));
return FALSE;
}
/* lock the file and move the write pointer to the start of it */
apr_file_lock(fd, APR_FLOCK_EXCLUSIVE);
apr_off_t begin = 0;
apr_file_seek(fd, APR_SET, &begin);
/* calculate the length of the data, which is a string length */
apr_size_t len = strlen(data);
/* (blocking) write the number of bytes in the buffer */
rc = apr_file_write_full(fd, data, len, &bytes_written);
/* check for a system error */
if (rc != APR_SUCCESS) {
oidc_error(r, "could not write to: \"%s\" (%s)", path,
apr_strerror(rc, s_err, sizeof(s_err)));
return FALSE;
}
/* check that all bytes from the header were written */
if (bytes_written != len) {
oidc_error(r,
"could not write enough bytes to: \"%s\", bytes_written (%" APR_SIZE_T_FMT ") != len (%" APR_SIZE_T_FMT ")",
path, bytes_written, len);
return FALSE;
}
/* unlock and close the written file */
apr_file_unlock(fd);
apr_file_close(fd);
oidc_debug(r, "file \"%s\" written; number of bytes (%" APR_SIZE_T_FMT ")",
path, len);
return TRUE;
}
/*
* see if two provided issuer identifiers match (cq. ignore trailing slash)
*/
apr_byte_t oidc_util_issuer_match(const char *a, const char *b) {
/* check the "issuer" value against the one configure for the provider we got this id_token from */
if (apr_strnatcmp(a, b) != 0) {
/* no strict match, but we are going to accept if the difference is only a trailing slash */
int n1 = strlen(a);
int n2 = strlen(b);
int n = ((n1 == n2 + 1) && (a[n1 - 1] == OIDC_CHAR_FORWARD_SLASH)) ?
n2 :
(((n2 == n1 + 1) && (b[n2 - 1] == OIDC_CHAR_FORWARD_SLASH)) ?
n1 : 0);
if ((n == 0) || (strncmp(a, b, n) != 0))
return FALSE;
}
return TRUE;
}
/*
* see if a certain string value is part of a JSON array with string elements
*/
apr_byte_t oidc_util_json_array_has_value(request_rec *r, json_t *haystack,
const char *needle) {
if ((haystack == NULL) || (!json_is_array(haystack)))
return FALSE;
int i;
for (i = 0; i < json_array_size(haystack); i++) {
json_t *elem = json_array_get(haystack, i);
if (!json_is_string(elem)) {
oidc_error(r, "unhandled in-array JSON non-string object type [%d]",
elem->type);
continue;
}
if (apr_strnatcmp(json_string_value(elem), needle) == 0) {
break;
}
}
// oidc_debug(r,
// "returning (%d=%d)", i,
// haystack->value.array->nelts);
return (i == json_array_size(haystack)) ? FALSE : TRUE;
}
/*
* set a HTTP header and/or environment variable to pass information to the application
*/
void oidc_util_set_app_info(request_rec *r, const char *s_key,
const char *s_value, const char *claim_prefix, apr_byte_t as_header,
apr_byte_t as_env_var) {
apr_table_t *env = NULL;
/* construct the header name, cq. put the prefix in front of a normalized key name */
const char *s_name = apr_psprintf(r->pool, "%s%s", claim_prefix,
oidc_normalize_header_name(r, s_key));
if (as_header)
oidc_util_hdr_in_set(r, s_name, s_value);
if (as_env_var) {
/* do some logging about this event */
oidc_debug(r, "setting environment variable \"%s: %s\"", s_name,
s_value);
apr_pool_userdata_get((void **) &env, OIDC_USERDATA_ENV_KEY, r->pool);
if (env == NULL)
env = apr_table_make(r->pool, 10);
apr_table_set(env, s_name, s_value);
apr_pool_userdata_set(env, OIDC_USERDATA_ENV_KEY, NULL, r->pool);
}
}
/*
* set the user/claims information from the session in HTTP headers passed on to the application
*/
void oidc_util_set_app_infos(request_rec *r, const json_t *j_attrs,
const char *claim_prefix, const char *claim_delimiter,
apr_byte_t as_header, apr_byte_t as_env_var) {
char s_int[255];
json_t *j_value = NULL;
const char *s_key = NULL;
/* if not attributes are set, nothing needs to be done */
if (j_attrs == NULL) {
oidc_debug(r, "no attributes to set");
return;
}
/* loop over the claims in the JSON structure */
void *iter = json_object_iter((json_t*) j_attrs);
while (iter) {
/* get the next key/value entry */
s_key = json_object_iter_key(iter);
j_value = json_object_iter_value(iter);
// char *s_value= json_dumps(j_value, JSON_ENCODE_ANY);
// oidc_util_set_app_info(r, s_key, s_value, claim_prefix);
// free(s_value);
/* check if it is a single value string */
if (json_is_string(j_value)) {
/* set the single string in the application header whose name is based on the key and the prefix */
oidc_util_set_app_info(r, s_key, json_string_value(j_value),
claim_prefix, as_header, as_env_var);
} else if (json_is_boolean(j_value)) {
/* set boolean value in the application header whose name is based on the key and the prefix */
oidc_util_set_app_info(r, s_key,
(json_is_true(j_value) ? "1" : "0"), claim_prefix,
as_header, as_env_var);
} else if (json_is_integer(j_value)) {
if (sprintf(s_int, "%ld", (long) json_integer_value(j_value)) > 0) {
/* set long value in the application header whose name is based on the key and the prefix */
oidc_util_set_app_info(r, s_key, s_int, claim_prefix, as_header,
as_env_var);
} else {
oidc_warn(r,
"could not convert JSON number to string (> 255 characters?), skipping");
}
} else if (json_is_real(j_value)) {
/* set float value in the application header whose name is based on the key and the prefix */
oidc_util_set_app_info(r, s_key,
apr_psprintf(r->pool, "%lf", json_real_value(j_value)),
claim_prefix, as_header, as_env_var);
} else if (json_is_object(j_value)) {
/* set json value in the application header whose name is based on the key and the prefix */
oidc_util_set_app_info(r, s_key,
oidc_util_encode_json_object(r, j_value, 0), claim_prefix,
as_header, as_env_var);
/* check if it is a multi-value string */
} else if (json_is_array(j_value)) {
/* some logging about what we're going to do */
oidc_debug(r,
"parsing attribute array for key \"%s\" (#nr-of-elems: %llu)",
s_key, (unsigned long long )json_array_size(j_value));
/* string to hold the concatenated array string values */
char *s_concat = apr_pstrdup(r->pool, "");
size_t i = 0;
/* loop over the array */
for (i = 0; i < json_array_size(j_value); i++) {
/* get the current element */
json_t *elem = json_array_get(j_value, i);
/* check if it is a string */
if (json_is_string(elem)) {
/* concatenate the string to the s_concat value using the configured separator char */
// TODO: escape the delimiter in the values (maybe reuse/extract url-formatted code from oidc_session_identity_encode)
if (apr_strnatcmp(s_concat, "") != 0) {
s_concat = apr_psprintf(r->pool, "%s%s%s", s_concat,
claim_delimiter, json_string_value(elem));
} else {
s_concat = apr_psprintf(r->pool, "%s",
json_string_value(elem));
}
} else if (json_is_boolean(elem)) {
if (apr_strnatcmp(s_concat, "") != 0) {
s_concat = apr_psprintf(r->pool, "%s%s%s", s_concat,
claim_delimiter,
json_is_true(elem) ? "1" : "0");
} else {
s_concat = apr_psprintf(r->pool, "%s",
json_is_true(elem) ? "1" : "0");
}
} else {
/* don't know how to handle a non-string array element */
oidc_warn(r,
"unhandled in-array JSON object type [%d] for key \"%s\" when parsing claims array elements",
elem->type, s_key);
}
}
/* set the concatenated string */
oidc_util_set_app_info(r, s_key, s_concat, claim_prefix, as_header,
as_env_var);
} else {
/* no string and no array, so unclear how to handle this */
oidc_warn(r,
"unhandled JSON object type [%d] for key \"%s\" when parsing claims",
j_value->type, s_key);
}
iter = json_object_iter_next((json_t *) j_attrs, iter);
}
}
/*
* parse a space separated string in to a hash table
*/
apr_hash_t *oidc_util_spaced_string_to_hashtable(apr_pool_t *pool,
const char *str) {
char *val;
const char *data = apr_pstrdup(pool, str);
apr_hash_t *result = apr_hash_make(pool);
while (*data && (val = ap_getword_white(pool, &data))) {
apr_hash_set(result, val, APR_HASH_KEY_STRING, val);
}
return result;
}
/*
* compare two space separated value types
*/
apr_byte_t oidc_util_spaced_string_equals(apr_pool_t *pool, const char *a,
const char *b) {
/* parse both entries as hash tables */
apr_hash_t *ht_a = oidc_util_spaced_string_to_hashtable(pool, a);
apr_hash_t *ht_b = oidc_util_spaced_string_to_hashtable(pool, b);
/* first compare the length of both response_types */
if (apr_hash_count(ht_a) != apr_hash_count(ht_b))
return FALSE;
/* then loop over all entries */
apr_hash_index_t *hi;
for (hi = apr_hash_first(NULL, ht_a); hi; hi = apr_hash_next(hi)) {
const char *k;
const char *v;
apr_hash_this(hi, (const void**) &k, NULL, (void**) &v);
if (apr_hash_get(ht_b, k, APR_HASH_KEY_STRING) == NULL)
return FALSE;
}
/* if we've made it this far, a an b are equal in length and every element in a is in b */
return TRUE;
}
/*
* see if a particular value is part of a space separated value
*/
apr_byte_t oidc_util_spaced_string_contains(apr_pool_t *pool, const char *str,
const char *match) {
apr_hash_t *ht = oidc_util_spaced_string_to_hashtable(pool, str);
return (apr_hash_get(ht, match, APR_HASH_KEY_STRING) != NULL);
}
/*
* get (optional) string from a JSON object
*/
apr_byte_t oidc_json_object_get_string(apr_pool_t *pool, json_t *json,
const char *name, char **value, const char *default_value) {
*value = default_value ? apr_pstrdup(pool, default_value) : NULL;
if (json != NULL) {
json_t *v = json_object_get(json, name);
if ((v != NULL) && (json_is_string(v))) {
*value = apr_pstrdup(pool, json_string_value(v));
}
}
return TRUE;
}
/*
* get (optional) int from a JSON object
*/
apr_byte_t oidc_json_object_get_int(apr_pool_t *pool, json_t *json,
const char *name, int *value, const int default_value) {
*value = default_value;
if (json != NULL) {
json_t *v = json_object_get(json, name);
if ((v != NULL) && (json_is_integer(v))) {
*value = json_integer_value(v);
}
}
return TRUE;
}
/*
* get (optional) boolean from a JSON object
*/
apr_byte_t oidc_json_object_get_bool(apr_pool_t *pool, json_t *json,
const char *name, int *value, const int default_value) {
*value = default_value;
if (json != NULL) {
json_t *v = json_object_get(json, name);
if ((v != NULL) && (json_is_boolean(v))) {
*value = json_is_true(v);
return TRUE;
}
}
return FALSE;
}
/*
* merge two JSON objects
*/
apr_byte_t oidc_util_json_merge(request_rec *r, json_t *src, json_t *dst) {
const char *key;
json_t *value = NULL;
void *iter = NULL;
if ((src == NULL) || (dst == NULL))
return FALSE;
oidc_debug(r, "src=%s, dst=%s",
oidc_util_encode_json_object(r, src, JSON_COMPACT),
oidc_util_encode_json_object(r, dst, JSON_COMPACT));
iter = json_object_iter(src);
while (iter) {
key = json_object_iter_key(iter);
value = json_object_iter_value(iter);
json_object_set(dst, key, value);
iter = json_object_iter_next(src, iter);
}
oidc_debug(r, "result dst=%s",
oidc_util_encode_json_object(r, dst, JSON_COMPACT));
return TRUE;
}
/*
* add query encoded parameters to a table
*/
void oidc_util_table_add_query_encoded_params(apr_pool_t *pool,
apr_table_t *table, const char *params) {
if (params != NULL) {
const char *key, *val;
const char *p = params;
while (*p && (val = ap_getword(pool, &p, OIDC_CHAR_AMP))) {
key = ap_getword(pool, &val, OIDC_CHAR_EQUAL);
ap_unescape_url((char *) key);
ap_unescape_url((char *) val);
apr_table_add(table, key, val);
}
}
}
/*
* create a symmetric key from a client_secret
*/
apr_byte_t oidc_util_create_symmetric_key(request_rec *r,
const char *client_secret, unsigned int r_key_len, const char *hash_algo,
apr_byte_t set_kid, oidc_jwk_t **jwk) {
oidc_jose_error_t err;
unsigned char *key = NULL;
unsigned int key_len;
if ((client_secret != NULL) && (strlen(client_secret) > 0)) {
if (hash_algo == NULL) {
key = (unsigned char *) client_secret;
key_len = strlen(client_secret);
} else {
/* hash the client_secret first, this is OpenID Connect specific */
oidc_jose_hash_bytes(r->pool, hash_algo,
(const unsigned char *) client_secret,
strlen(client_secret), &key, &key_len, &err);
}
if ((key != NULL) && (key_len > 0)) {
if ((r_key_len != 0) && (key_len >= r_key_len))
key_len = r_key_len;
oidc_debug(r, "key_len=%d", key_len);
*jwk = oidc_jwk_create_symmetric_key(r->pool, NULL, key, key_len,
set_kid, &err);
}
if (*jwk == NULL) {
oidc_error(r,
"could not create JWK from the provided secret %s: %s",
client_secret, oidc_jose_e2s(r->pool, err));
return FALSE;
}
}
return TRUE;
}
/*
* merge provided keys and client secret in to a single hashtable
*/
apr_hash_t * oidc_util_merge_symmetric_key(apr_pool_t *pool, apr_hash_t *keys,
oidc_jwk_t *jwk) {
apr_hash_t *result =
(keys != NULL) ? apr_hash_copy(pool, keys) : apr_hash_make(pool);
if (jwk != NULL) {
apr_hash_set(result, jwk->kid, APR_HASH_KEY_STRING, jwk);
}
return result;
}
/*
* openssl hash and base64 encode
*/
apr_byte_t oidc_util_hash_string_and_base64url_encode(request_rec *r,
const char *openssl_hash_algo, const char *input, char **output) {
oidc_jose_error_t err;
unsigned char *hashed = NULL;
unsigned int hashed_len = 0;
if (oidc_jose_hash_bytes(r->pool, openssl_hash_algo,
(const unsigned char *) input, strlen(input), &hashed, &hashed_len,
&err) == FALSE) {
oidc_error(r, "oidc_jose_hash_bytes returned an error: %s", err.text);
return FALSE;
}
if (oidc_base64url_encode(r, output, (const char *) hashed, hashed_len,
TRUE) <= 0) {
oidc_error(r, "oidc_base64url_encode returned an error: %s", err.text);
return FALSE;
}
return TRUE;
}
/*
* merge two key sets
*/
apr_hash_t * oidc_util_merge_key_sets(apr_pool_t *pool, apr_hash_t *k1,
apr_hash_t *k2) {
if (k1 == NULL) {
if (k2 == NULL)
return apr_hash_make(pool);
return k2;
}
if (k2 == NULL)
return k1;
return apr_hash_overlay(pool, k1, k2);
}
/*
* regexp substitute
* Example:
* regex: "^.*([0-9]+).*$"
* replace: "$1"
* text_original: "match 292 numbers"
* text_replaced: "292"
*/
apr_byte_t oidc_util_regexp_substitute(
apr_pool_t *pool, const char *input,
const char *regexp, const char *replace, char **output, char **error_str) {
const char *errorptr;
int erroffset;
pcre *preg;
char *substituted;
preg = pcre_compile(regexp, 0, &errorptr, &erroffset, NULL);
if (preg == NULL) {
*error_str = apr_psprintf(pool, "pattern [%s] is not a valid regular expression", regexp);
pcre_free(preg);
return FALSE;
}
substituted = pcre_subst(preg, NULL, input, (int) strlen(input), 0, 0, replace);
if (substituted) {
*output = apr_pstrdup(pool, substituted);
pcre_free(preg);
pcre_free(substituted);
return TRUE;
} else {
*error_str = apr_psprintf(pool,"unknown error could not match string [%s] using pattern [%s] and replace matches in [%s]",
input, regexp, replace);
pcre_free(preg);
}
return FALSE;
}
/*
* regexp match
*/
#define OIDC_UTIL_REGEXP_MATCH_SIZE 30
#define OIDC_UTIL_REGEXP_MATCH_NR 1
apr_byte_t oidc_util_regexp_first_match(apr_pool_t *pool, const char *input,
const char *regexp, char **output, char **error_str) {
const char *errorptr;
int erroffset;
pcre *preg;
int subStr[OIDC_UTIL_REGEXP_MATCH_SIZE];
const char *psubStrMatchStr;
preg = pcre_compile(regexp, 0, &errorptr, &erroffset, NULL);
if (preg == NULL) {
*error_str = apr_psprintf(pool,
"pattern [%s] is not a valid regular expression", regexp);
pcre_free(preg);
return FALSE;
}
int rc = 0;
if ((rc = pcre_exec(preg, NULL, input, (int) strlen(input), 0, 0, subStr,
OIDC_UTIL_REGEXP_MATCH_SIZE)) < 0) {
switch (rc) {
case PCRE_ERROR_NOMATCH:
*error_str = apr_pstrdup(pool, "string did not match the pattern");
break;
case PCRE_ERROR_NULL:
*error_str = apr_pstrdup(pool, "something was null");
break;
case PCRE_ERROR_BADOPTION:
*error_str = apr_pstrdup(pool, "a bad option was passed");
break;
case PCRE_ERROR_BADMAGIC:
*error_str = apr_pstrdup(pool,
"magic number bad (compiled re corrupt?)");
break;
case PCRE_ERROR_UNKNOWN_NODE:
*error_str = apr_pstrdup(pool,
"something kooky in the compiled re");
break;
case PCRE_ERROR_NOMEMORY:
*error_str = apr_pstrdup(pool, "ran out of memory");
break;
default:
*error_str = apr_psprintf(pool, "unknown error: %d", rc);
break;
}
pcre_free(preg);
return FALSE;
}
if (pcre_get_substring(input, subStr, rc, OIDC_UTIL_REGEXP_MATCH_NR,
&(psubStrMatchStr)) <= 0) {
*error_str = apr_psprintf(pool, "pcre_get_substring failed (rc=%d)",
rc);
pcre_free(preg);
return FALSE;
}
*output = apr_pstrdup(pool, psubStrMatchStr);
pcre_free_substring(psubStrMatchStr);
pcre_free(preg);
return TRUE;
}
int oidc_util_cookie_domain_valid(const char *hostname, char *cookie_domain) {
char *p = NULL;
char *check_cookie = cookie_domain;
// Skip past the first char of a cookie_domain that starts
// with a ".", ASCII 46
if (check_cookie[0] == 46) {
p = strstr(hostname, ++check_cookie);
} else {
p = strstr(hostname, check_cookie);
}
if ((p == NULL) || (apr_strnatcmp(check_cookie, p) != 0)) {
return FALSE;
}
return TRUE;
}
static const char *oidc_util_hdr_in_get(const request_rec *r, const char *name) {
const char *value = apr_table_get(r->headers_in, name);
if (value)
oidc_debug(r, "%s=%s", name, value);
return value;
}
static const char *oidc_util_hdr_in_get_left_most_only(const request_rec *r, const char *name, const char *separator) {
char *last = NULL;
const char *value = oidc_util_hdr_in_get(r, name);
if (value)
return apr_strtok(apr_pstrdup(r->pool, value), separator, &last);
return NULL;
}
static void oidc_util_hdr_table_set(const request_rec *r, apr_table_t *table,
const char *name, const char *value) {
if (value != NULL) {
char *s_value = apr_pstrdup(r->pool, value);
/*
* sanitize the header value by replacing line feeds with spaces
* just like the Apache header input algorithms do for incoming headers
*
* this makes it impossible to have line feeds in values but that is
* compliant with RFC 7230 (and impossible for regular headers due to Apache's
* parsing of headers anyway) and fixes a security vulnerability on
* overwriting/setting outgoing headers when used in proxy mode
*/
char *p = NULL;
while ((p = strchr(s_value, '\n')))
*p = OIDC_CHAR_SPACE;
oidc_debug(r, "%s: %s", name, s_value);
apr_table_set(table, name, s_value);
} else {
oidc_debug(r, "unset %s", name);
apr_table_unset(table, name);
}
}
static void oidc_util_hdr_out_set(const request_rec *r, const char *name,
const char *value) {
oidc_util_hdr_table_set(r, r->headers_out, name, value);
}
static const char *oidc_util_hdr_out_get(const request_rec *r, const char *name) {
return apr_table_get(r->headers_out, name);
}
void oidc_util_hdr_err_out_add(const request_rec *r, const char *name,
const char *value) {
oidc_debug(r, "%s: %s", name, value);
apr_table_add(r->err_headers_out, name, value);
}
void oidc_util_hdr_in_set(const request_rec *r, const char *name,
const char *value) {
oidc_util_hdr_table_set(r, r->headers_in, name, value);
}
const char *oidc_util_hdr_in_cookie_get(const request_rec *r) {
return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_COOKIE);
}
void oidc_util_hdr_in_cookie_set(const request_rec *r, const char *value) {
oidc_util_hdr_in_set(r, OIDC_HTTP_HDR_COOKIE, value);
}
const char *oidc_util_hdr_in_user_agent_get(const request_rec *r) {
return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_USER_AGENT);
}
const char *oidc_util_hdr_in_x_forwarded_for_get(const request_rec *r) {
return oidc_util_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_FOR, OIDC_STR_COMMA);
}
const char *oidc_util_hdr_in_content_type_get(const request_rec *r) {
return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_CONTENT_TYPE);
}
const char *oidc_util_hdr_in_x_requested_with_get(const request_rec *r) {
return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_X_REQUESTED_WITH);
}
const char *oidc_util_hdr_in_accept_get(const request_rec *r) {
return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_ACCEPT);
}
const char *oidc_util_hdr_in_authorization_get(const request_rec *r) {
return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_AUTHORIZATION);
}
const char *oidc_util_hdr_in_x_forwarded_proto_get(const request_rec *r) {
return oidc_util_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_PROTO, OIDC_STR_COMMA);
}
const char *oidc_util_hdr_in_x_forwarded_port_get(const request_rec *r) {
return oidc_util_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_PORT, OIDC_STR_COMMA);
}
const char *oidc_util_hdr_in_x_forwarded_host_get(const request_rec *r) {
return oidc_util_hdr_in_get_left_most_only(r, OIDC_HTTP_HDR_X_FORWARDED_HOST, OIDC_STR_COMMA);
}
const char *oidc_util_hdr_in_host_get(const request_rec *r) {
return oidc_util_hdr_in_get(r, OIDC_HTTP_HDR_HOST);
}
void oidc_util_hdr_out_location_set(const request_rec *r, const char *value) {
oidc_util_hdr_out_set(r, OIDC_HTTP_HDR_LOCATION, value);
}
const char *oidc_util_hdr_out_location_get(const request_rec *r) {
return oidc_util_hdr_out_get(r, OIDC_HTTP_HDR_LOCATION);
}
const char *oidc_util_get_provided_token_binding_id(const request_rec *r) {
const char *result = NULL;
if (r->subprocess_env != NULL)
result = apr_table_get(r->subprocess_env, OIDC_TB_CFG_PROVIDED_ENV_VAR);
return result;
}
mod_auth_openidc-2.3.3/src/authz.c 0000644 0000765 0000024 00000030333 13200625410 016737 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* mostly copied from mod_auth_cas
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*/
#include
#include
#include
#include "mod_auth_openidc.h"
#include
#ifdef USE_LIBJQ
#include "jq.h"
#endif
static apr_byte_t oidc_authz_match_value(request_rec *r, const char *spec_c,
json_t *val, const char *key) {
int i = 0;
/* see if it is a string and it (case-insensitively) matches the Require'd value */
if (json_is_string(val)) {
if (apr_strnatcmp(json_string_value(val), spec_c) == 0)
return TRUE;
/* see if it is a integer and it equals the Require'd value */
} else if (json_is_integer(val)) {
if (json_integer_value(val) == atoi(spec_c))
return TRUE;
/* see if it is a boolean and it (case-insensitively) matches the Require'd value */
} else if (json_is_boolean(val)) {
if (apr_strnatcmp(json_is_true(val) ? "true" : "false", spec_c) == 0)
return TRUE;
/* if it is an array, we'll walk it */
} else if (json_is_array(val)) {
/* compare the claim values */
for (i = 0; i < json_array_size(val); i++) {
json_t *elem = json_array_get(val, i);
if (json_is_string(elem)) {
/*
* approximately compare the claim value (ignoring
* whitespace). At this point, spec_c points to the
* NULL-terminated value pattern.
*/
if (apr_strnatcmp(json_string_value(elem), spec_c) == 0)
return TRUE;
} else if (json_is_boolean(elem)) {
if (apr_strnatcmp(
json_is_true(elem) ? "true" : "false", spec_c) == 0)
return TRUE;
} else if (json_is_integer(elem)) {
if (json_integer_value(elem) == atoi(spec_c))
return TRUE;
} else {
oidc_warn(r,
"unhandled in-array JSON object type [%d] for key \"%s\"",
elem->type, (const char * ) key);
}
}
} else {
oidc_warn(r, "unhandled JSON object type [%d] for key \"%s\"",
val->type, (const char * ) key);
}
return FALSE;
}
static apr_byte_t oidc_authz_match_expression(request_rec *r,
const char *spec_c, json_t *val) {
const char *errorptr;
int erroffset;
pcre *preg;
int i = 0;
/* setup the regex; spec_c points to the NULL-terminated value pattern */
preg = pcre_compile(spec_c, 0, &errorptr, &erroffset, NULL);
if (preg == NULL) {
oidc_error(r, "pattern [%s] is not a valid regular expression", spec_c);
pcre_free(preg);
return FALSE;
}
/* see if the claim is a literal string */
if (json_is_string(val)) {
/* PCRE-compare the string value against the expression */
if (pcre_exec(preg, NULL, json_string_value(val),
(int) strlen(json_string_value(val)), 0, 0, NULL, 0) == 0) {
pcre_free(preg);
return TRUE;
}
/* see if the claim value is an array */
} else if (json_is_array(val)) {
/* compare the claim values in the array against the expression */
for (i = 0; i < json_array_size(val); i++) {
json_t *elem = json_array_get(val, i);
if (json_is_string(elem)) {
/* PCRE-compare the string value against the expression */
if (pcre_exec(preg, NULL, json_string_value(elem),
(int) strlen(json_string_value(elem)), 0, 0,
NULL, 0) == 0) {
pcre_free(preg);
return TRUE;
}
}
}
}
pcre_free(preg);
return FALSE;
}
/*
* see if a the Require value matches with a set of provided claims
*/
apr_byte_t oidc_authz_match_claim(request_rec *r,
const char * const attr_spec, const json_t * const claims) {
const char *key;
json_t *val;
/* if we don't have any claims, they can never match any Require claim primitive */
if (claims == NULL)
return FALSE;
/* loop over all of the user claims */
void *iter = json_object_iter((json_t*) claims);
while (iter) {
key = json_object_iter_key(iter);
val = json_object_iter_value(iter);
oidc_debug(r, "evaluating key \"%s\"", (const char * ) key);
const char *attr_c = (const char *) key;
const char *spec_c = attr_spec;
/* walk both strings until we get to the end of either or we find a differing character */
while ((*attr_c) && (*spec_c) && (*attr_c) == (*spec_c)) {
attr_c++;
spec_c++;
}
/* The match is a success if we walked the whole claim name and the attr_spec is at a colon. */
if (!(*attr_c) && (*spec_c) == OIDC_CHAR_COLON) {
/* skip the colon */
spec_c++;
if (oidc_authz_match_value(r, spec_c, val, key) == TRUE)
return TRUE;
/* a tilde denotes a string PCRE match */
} else if (!(*attr_c) && (*spec_c) == OIDC_CHAR_TILDE) {
/* skip the tilde */
spec_c++;
if (oidc_authz_match_expression(r, spec_c, val) == TRUE)
return TRUE;
/* dot means child nodes must be evaluated */
} else if (!(*attr_c) && (*spec_c) == OIDC_CHAR_DOT) {
/* skip the dot */
spec_c++;
if (!json_is_object(val)) {
oidc_warn(r, "\"%s\" matched, and child nodes should be evaluated, but value is not an object.", key);
return FALSE;
}
oidc_debug(r, "Attribute chunk matched. Evaluating children of key: \"%s\".", key);
return oidc_authz_match_claim(r, spec_c, json_object_get(claims, key));
}
iter = json_object_iter_next((json_t *) claims, iter);
}
return FALSE;
}
#ifdef USE_LIBJQ
static apr_byte_t jq_parse(request_rec *r, jq_state *jq, struct jv_parser *parser) {
apr_byte_t rv = FALSE;
jv value;
while (jv_is_valid((value = jv_parser_next(parser)))) {
jq_start(jq, value, 0);
jv result;
while (jv_is_valid(result = jq_next(jq))) {
jv dumped = jv_dump_string(result, 0);
const char *str = jv_string_value(dumped);
oidc_debug(r, "dumped: %s", str);
rv = (apr_strnatcmp(str, "true") == 0);
}
jv_free(result);
}
if (jv_invalid_has_msg(jv_copy(value))) {
jv msg = jv_invalid_get_msg(value);
oidc_error(r, "invalid: %s", jv_string_value(msg));
jv_free(msg);
rv = FALSE;
} else {
jv_free(value);
}
return rv;
}
/*
* see if a the Require value matches a configured expression
*/
apr_byte_t oidc_authz_match_claims_expr(request_rec *r,
const char * const attr_spec, const json_t * const claims) {
apr_byte_t rv = FALSE;
oidc_debug(r, "enter: '%s'", attr_spec);
jq_state *jq = jq_init();
if (jq_compile(jq, attr_spec) == 0) {
jq_teardown(&jq);
return FALSE;
}
struct jv_parser *parser = jv_parser_new(0);
char *buf = oidc_util_encode_json_object(r, (json_t *)claims, 0);
jv_parser_set_buf(parser, buf, strlen(buf), 0);
rv = jq_parse(r, jq, parser);
jv_parser_free(parser);
jq_teardown(&jq);
return rv;
}
#endif
#if MODULE_MAGIC_NUMBER_MAJOR < 20100714
/*
* Apache <2.4 authorization routine: match the claims from the authenticated user against the Require primitive
*/
int oidc_authz_worker22(request_rec *r, const json_t * const claims,
const require_line * const reqs, int nelts) {
const int m = r->method_number;
const char *token;
const char *requirement;
int i;
int have_oauthattr = 0;
int count_oauth_claims = 0;
oidc_authz_match_claim_fn_type match_claim_fn = NULL;
/* go through applicable Require directives */
for (i = 0; i < nelts; ++i) {
/* ignore this Require if it's in a section that exclude this method */
if (!(reqs[i].method_mask & (AP_METHOD_BIT << m))) {
continue;
}
/* ignore if it's not a "Require claim ..." */
requirement = reqs[i].requirement;
token = ap_getword_white(r->pool, &requirement);
/* see if we've got anything meant for us */
if (apr_strnatcasecmp(token, OIDC_REQUIRE_CLAIM_NAME) == 0) {
match_claim_fn = oidc_authz_match_claim;
#ifdef USE_LIBJQ
} else if (apr_strnatcasecmp(token, OIDC_REQUIRE_CLAIMS_EXPR_NAME) == 0) {
match_claim_fn = oidc_authz_match_claims_expr;
#endif
} else {
continue;
}
/* ok, we have a "Require claim/claims_expr" to satisfy */
have_oauthattr = 1;
/*
* If we have an applicable claim, but no claims were sent in the request, then we can
* just stop looking here, because it's not satisfiable. The code after this loop will
* give the appropriate response.
*/
if (!claims) {
break;
}
/*
* iterate over the claim specification strings in this require directive searching
* for a specification that matches one of the claims/expressions.
*/
while (*requirement) {
token = ap_getword_conf(r->pool, &requirement);
count_oauth_claims++;
oidc_debug(r, "evaluating claim/expr specification: %s", token);
if (match_claim_fn(r, token, claims) == TRUE) {
/* if *any* claim matches, then authorization has succeeded and all of the others are ignored */
oidc_debug(r, "require claim/expr '%s' matched", token);
return OK;
}
}
}
/* if there weren't any "Require claim" directives, we're irrelevant */
if (!have_oauthattr) {
oidc_debug(r, "no claim/expr statements found, not performing authz");
return DECLINED;
}
/* if there was a "Require claim", but no actual claims, that's cause to warn the admin of an iffy configuration */
if (count_oauth_claims == 0) {
oidc_warn(r,
"'require claim/expr' missing specification(s) in configuration, declining");
return DECLINED;
}
/* log the event, also in Apache speak */
oidc_debug(r, "authorization denied for client session");
ap_note_auth_failure(r);
return HTTP_UNAUTHORIZED;
}
#else
/*
* Apache >=2.4 authorization routine: match the claims from the authenticated user against the Require primitive
*/
authz_status oidc_authz_worker24(request_rec *r, const json_t * const claims,
const char *require_args, oidc_authz_match_claim_fn_type match_claim_fn) {
int count_oauth_claims = 0;
const char *t, *w;
/* needed for anonymous authentication */
if (r->user == NULL)
return AUTHZ_DENIED_NO_USER;
/* if no claims, impossible to satisfy */
if (!claims)
return AUTHZ_DENIED;
/* loop over the Required specifications */
t = require_args;
while ((w = ap_getword_conf(r->pool, &t)) && w[0]) {
count_oauth_claims++;
oidc_debug(r, "evaluating claim/expr specification: %s", w);
/* see if we can match any of out input claims against this Require'd value */
if (match_claim_fn(r, w, claims) == TRUE) {
oidc_debug(r, "require claim/expr '%s' matched", w);
return AUTHZ_GRANTED;
}
}
/* if there wasn't anything after the Require claims directive... */
if (count_oauth_claims == 0) {
oidc_warn(r,
"'require claim/expr' missing specification(s) in configuration, denying");
}
return AUTHZ_DENIED;
}
#endif
mod_auth_openidc-2.3.3/src/session.c 0000644 0000765 0000024 00000053756 13203320522 017304 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*/
#include
#include
#include
#include
#include
#include
#include "mod_auth_openidc.h"
extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
/* the name of the remote-user attribute in the session */
#define OIDC_SESSION_REMOTE_USER_KEY "r"
/* the name of the session expiry attribute in the session */
#define OIDC_SESSION_EXPIRY_KEY "e"
/* the name of the provided token binding attribute in the session */
#define OIDC_SESSION_PROVIDED_TOKEN_BINDING_KEY "ptb"
static apr_byte_t oidc_session_encode(request_rec *r, oidc_cfg *c,
oidc_session_t *z, char **s_value, apr_byte_t encrypt) {
if (encrypt == FALSE) {
*s_value = oidc_util_encode_json_object(r, z->state, JSON_COMPACT);
return (*s_value != NULL);
}
if (oidc_util_jwt_create(r, c->crypto_passphrase, z->state,
s_value) == FALSE)
return FALSE;
return TRUE;
}
static apr_byte_t oidc_session_decode(request_rec *r, oidc_cfg *c,
oidc_session_t *z, const char *s_json, apr_byte_t encrypt) {
if (encrypt == FALSE) {
return oidc_util_decode_json_object(r, s_json, &z->state);
}
if (oidc_util_jwt_verify(r, c->crypto_passphrase, s_json,
&z->state) == FALSE) {
oidc_error(r,
"could not verify secure JWT: cache value possibly corrupted");
return FALSE;
}
return TRUE;
}
/*
* load the session from the cache using the cookie as the index
*/
static apr_byte_t oidc_session_load_cache(request_rec *r, oidc_session_t *z) {
oidc_cfg *c = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
apr_byte_t rc = FALSE;
/* get the cookie that should be our uuid/key */
char *uuid = oidc_util_get_cookie(r, oidc_cfg_dir_cookie(r));
/* get the string-encoded session from the cache based on the key; decryption is based on the cache backend config */
if (uuid != NULL) {
char *s_json = NULL;
rc = oidc_cache_get_session(r, uuid, &s_json);
if ((rc == TRUE) && (s_json != NULL)) {
rc = oidc_session_decode(r, c, z, s_json, FALSE);
if (rc == TRUE)
strncpy(z->uuid, uuid, strlen(uuid));
}
}
return rc;
}
/*
* save the session to the cache using a cookie for the index
*/
static apr_byte_t oidc_session_save_cache(request_rec *r, oidc_session_t *z,
apr_byte_t first_time) {
oidc_cfg *c = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
apr_byte_t rc = TRUE;
if (z->state != NULL) {
if (apr_strnatcmp(z->uuid, "") == 0) {
/* get a new uuid for this session */
apr_uuid_t uuid;
apr_uuid_get(&uuid);
apr_uuid_format((char *) &z->uuid, &uuid);
}
/* store the string-encoded session in the cache; encryption depends on cache backend settings */
char *s_value = NULL;
if (oidc_session_encode(r, c, z, &s_value, FALSE) == FALSE)
return FALSE;
rc = oidc_cache_set_session(r, z->uuid, s_value, z->expiry);
if (rc == TRUE)
/* set the uuid in the cookie */
oidc_util_set_cookie(r, oidc_cfg_dir_cookie(r), z->uuid,
c->persistent_session_cookie ? z->expiry : -1,
c->cookie_same_site ?
(first_time ?
OIDC_COOKIE_EXT_SAME_SITE_LAX :
OIDC_COOKIE_EXT_SAME_SITE_STRICT) :
NULL);
} else {
/* clear the cookie */
oidc_util_set_cookie(r, oidc_cfg_dir_cookie(r), "", 0, NULL);
/* remove the session from the cache */
rc = oidc_cache_set_session(r, z->uuid, NULL, 0);
}
return rc;
}
/*
* load the session from a self-contained client-side cookie
*/
static apr_byte_t oidc_session_load_cookie(request_rec *r, oidc_cfg *c,
oidc_session_t *z) {
char *cookieValue = oidc_util_get_chunked_cookie(r, oidc_cfg_dir_cookie(r),
c->session_cookie_chunk_size);
if ((cookieValue != NULL)
&& (oidc_session_decode(r, c, z, cookieValue, TRUE) == FALSE))
return FALSE;
return TRUE;
}
/*
* store the session in a self-contained client-side-only cookie storage
*/
static apr_byte_t oidc_session_save_cookie(request_rec *r, oidc_session_t *z,
apr_byte_t first_time) {
oidc_cfg *c = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
char *cookieValue = "";
if ((z->state != NULL)
&& (oidc_session_encode(r, c, z, &cookieValue, TRUE) == FALSE))
return FALSE;
oidc_util_set_chunked_cookie(r, oidc_cfg_dir_cookie(r), cookieValue,
c->persistent_session_cookie ? z->expiry : -1,
c->session_cookie_chunk_size,
c->cookie_same_site ?
(first_time ?
OIDC_COOKIE_EXT_SAME_SITE_LAX :
OIDC_COOKIE_EXT_SAME_SITE_STRICT) :
NULL);
return TRUE;
}
/*
* load a session from the cache/cookie
*/
apr_byte_t oidc_session_load(request_rec *r, oidc_session_t **zz) {
oidc_cfg *c = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
apr_byte_t rc = FALSE;
const char *ses_p_tb_id = NULL, *env_p_tb_id = NULL;
/* allocate space for the session object and fill it */
oidc_session_t *z = (*zz = apr_pcalloc(r->pool, sizeof(oidc_session_t)));
strncpy(z->uuid, "", strlen(""));
z->remote_user = NULL;
z->state = NULL;
if (c->session_type == OIDC_SESSION_TYPE_SERVER_CACHE)
/* load the session from the cache */
rc = oidc_session_load_cache(r, z);
/* if we get here we configured client-cookie or retrieving from the cache failed */
if ((c->session_type == OIDC_SESSION_TYPE_CLIENT_COOKIE)
|| ((rc == FALSE) && oidc_cfg_session_cache_fallback_to_cookie(r)))
/* load the session from a self-contained cookie */
rc = oidc_session_load_cookie(r, c, z);
if (z->state != NULL) {
json_t *j_expires = json_object_get(z->state, OIDC_SESSION_EXPIRY_KEY);
if (j_expires)
z->expiry = apr_time_from_sec(json_integer_value(j_expires));
/* check whether it has expired */
if (apr_time_now() > z->expiry) {
oidc_warn(r, "session restored from cache has expired");
oidc_session_free(r, z);
z->state = json_object();
} else {
oidc_session_get(r, z, OIDC_SESSION_PROVIDED_TOKEN_BINDING_KEY,
&ses_p_tb_id);
if (ses_p_tb_id != NULL) {
env_p_tb_id = oidc_util_get_provided_token_binding_id(r);
if ((env_p_tb_id == NULL)
|| (apr_strnatcmp(env_p_tb_id, ses_p_tb_id) != 0)) {
oidc_error(r,
"the Provided Token Binding ID stored in the session doesn't match the one presented by the user agent");
oidc_session_free(r, z);
z->state = json_object();
}
}
oidc_session_get(r, z, OIDC_SESSION_REMOTE_USER_KEY,
&z->remote_user);
}
} else {
z->state = json_object();
}
return rc;
}
/*
* save a session to cache/cookie
*/
apr_byte_t oidc_session_save(request_rec *r, oidc_session_t *z,
apr_byte_t first_time) {
oidc_cfg *c = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
apr_byte_t rc = FALSE;
const char *p_tb_id = oidc_util_get_provided_token_binding_id(r);
if (z->state != NULL) {
oidc_session_set(r, z, OIDC_SESSION_REMOTE_USER_KEY, z->remote_user);
json_object_set_new(z->state, OIDC_SESSION_EXPIRY_KEY,
json_integer(apr_time_sec(z->expiry)));
}
if ((first_time) && (p_tb_id != NULL)) {
oidc_debug(r,
"Provided Token Binding ID environment variable found; adding its value to the session state");
oidc_session_set(r, z, OIDC_SESSION_PROVIDED_TOKEN_BINDING_KEY,
p_tb_id);
}
if (c->session_type == OIDC_SESSION_TYPE_SERVER_CACHE)
/* store the session in the cache */
rc = oidc_session_save_cache(r, z, first_time);
/* if we get here we configured client-cookie or saving in the cache failed */
if ((c->session_type == OIDC_SESSION_TYPE_CLIENT_COOKIE)
|| ((rc == FALSE) && oidc_cfg_session_cache_fallback_to_cookie(r)))
/* store the session in a self-contained cookie */
rc = oidc_session_save_cookie(r, z, first_time);
return rc;
}
/*
* free resources allocated for a session
*/
apr_byte_t oidc_session_free(request_rec *r, oidc_session_t *z) {
if (z->state) {
json_decref(z->state);
z->state = NULL;
}
z->expiry = 0;
return TRUE;
}
/*
* terminate a session
*/
apr_byte_t oidc_session_kill(request_rec *r, oidc_session_t *z) {
oidc_session_free(r, z);
return oidc_session_save(r, z, FALSE);
}
/*
* get a value from the session based on the name from a name/value pair
*/
apr_byte_t oidc_session_get(request_rec *r, oidc_session_t *z, const char *key,
const char **value) {
/* just return the value for the key */
oidc_json_object_get_string(r->pool, z->state, key, (char **) value, NULL);
return TRUE;
}
/*
* set a name/value key pair in the session
*/
apr_byte_t oidc_session_set(request_rec *r, oidc_session_t *z, const char *key,
const char *value) {
/* only set it if non-NULL, otherwise delete the entry */
if (value) {
json_object_set_new(z->state, key, json_string(value));
} else {
json_object_del(z->state, key);
}
return TRUE;
}
/*
* session object keys
*/
/* key for storing the userinfo claims in the session context */
#define OIDC_SESSION_KEY_USERINFO_CLAIMS "uic"
/* key for storing the userinfo JWT in the session context */
#define OIDC_SESSION_KEY_USERINFO_JWT "uij"
/* key for storing the id_token in the session context */
#define OIDC_SESSION_KEY_IDTOKEN_CLAIMS "idc"
/* key for storing the raw id_token in the session context */
#define OIDC_SESSION_KEY_IDTOKEN "idt"
/* key for storing the access_token in the session context */
#define OIDC_SESSION_KEY_ACCESSTOKEN "at"
/* key for storing the access_token expiry in the session context */
#define OIDC_SESSION_KEY_ACCESSTOKEN_EXPIRES "ate"
/* key for storing the refresh_token in the session context */
#define OIDC_SESSION_KEY_REFRESH_TOKEN "rt"
/* key for storing maximum session duration in the session context */
#define OIDC_SESSION_KEY_SESSION_EXPIRES "se"
/* key for storing the cookie domain in the session context */
#define OIDC_SESSION_KEY_COOKIE_DOMAIN "cd"
/* key for storing last user info refresh timestamp in the session context */
#define OIDC_SESSION_KEY_USERINFO_LAST_REFRESH "uilr"
/* key for storing last access token refresh timestamp in the session context */
#define OIDC_SESSION_KEY_ACCESS_TOKEN_LAST_REFRESH "atlr"
/* key for storing request state */
#define OIDC_SESSION_KEY_REQUEST_STATE "rs"
/* key for storing the original URL */
#define OIDC_SESSION_KEY_ORIGINAL_URL "ou"
/* key for storing the session_state in the session context */
#define OIDC_SESSION_KEY_SESSION_STATE "ss"
/* key for storing the issuer in the session context */
#define OIDC_SESSION_KEY_ISSUER "iss"
/* key for storing the client_id in the session context */
#define OIDC_SESSION_KEY_CLIENT_ID "cid"
/* key for storing the check_session_iframe in the session context */
#define OIDC_SESSION_KEY_CHECK_SESSION_IFRAME "csi"
/* key for storing the end_session_endpoint in the session context */
#define OIDC_SESSION_KEY_LOGOUT_ENDPOINT "ese"
/*
* helper functions
*/
typedef const char *(*oidc_session_get_str_function)(request_rec *r,
oidc_session_t *z);
static void oidc_session_set_timestamp(request_rec *r, oidc_session_t *z,
const char *key, const apr_time_t timestamp) {
if (timestamp != -1)
oidc_session_set(r, z, key,
apr_psprintf(r->pool, "%" APR_TIME_T_FMT, timestamp));
}
static json_t *oidc_session_get_str2json(request_rec *r, oidc_session_t *z,
oidc_session_get_str_function session_get_str_fn) {
json_t *json = NULL;
const char *str = session_get_str_fn(r, z);
if (str != NULL)
oidc_util_decode_json_object(r, str, &json);
return json;
}
static const char *oidc_session_get_key2string(request_rec *r,
oidc_session_t *z, const char *key) {
const char *s_value = NULL;
oidc_session_get(r, z, key, &s_value);
return s_value;
}
static apr_time_t oidc_session_get_key2timestamp(request_rec *r,
oidc_session_t *z, const char *key) {
apr_time_t t_expires = 0;
const char *s_expires = oidc_session_get_key2string(r, z, key);
if (s_expires != NULL)
sscanf(s_expires, "%" APR_TIME_T_FMT, &t_expires);
return t_expires;
}
void oidc_session_set_filtered_claims(request_rec *r, oidc_session_t *z,
const char *session_key, const char *claims) {
oidc_cfg *c = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
const char *name;
json_t *src = NULL, *dst = NULL, *value = NULL;
void *iter = NULL;
apr_byte_t is_allowed;
if (oidc_util_decode_json_object(r, claims, &src) == FALSE)
return;
dst = json_object();
iter = json_object_iter(src);
while (iter) {
is_allowed = TRUE;
name = json_object_iter_key(iter);
value = json_object_iter_value(iter);
if ((c->black_listed_claims != NULL)
&& (apr_hash_get(c->black_listed_claims, name,
APR_HASH_KEY_STRING) != NULL)) {
oidc_debug(r, "removing blacklisted claim [%s]: '%s'", session_key,
name);
is_allowed = FALSE;
}
if ((is_allowed == TRUE) && (c->white_listed_claims != NULL)
&& (apr_hash_get(c->white_listed_claims, name,
APR_HASH_KEY_STRING) == NULL)) {
oidc_debug(r, "removing non-whitelisted claim [%s]: '%s'",
session_key, name);
is_allowed = FALSE;
}
if (is_allowed == TRUE)
json_object_set(dst, name, value);
iter = json_object_iter_next(src, iter);
}
char *filtered_claims = oidc_util_encode_json_object(r, dst, JSON_COMPACT);
json_decref(dst);
json_decref(src);
oidc_session_set(r, z, session_key, filtered_claims);
}
/*
* userinfo claims
*/
void oidc_session_set_userinfo_claims(request_rec *r, oidc_session_t *z,
const char *claims) {
oidc_session_set_filtered_claims(r, z, OIDC_SESSION_KEY_USERINFO_CLAIMS,
claims);
}
const char * oidc_session_get_userinfo_claims(request_rec *r, oidc_session_t *z) {
return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_USERINFO_CLAIMS);
}
json_t *oidc_session_get_userinfo_claims_json(request_rec *r, oidc_session_t *z) {
return oidc_session_get_str2json(r, z, oidc_session_get_userinfo_claims);
}
void oidc_session_set_userinfo_jwt(request_rec *r, oidc_session_t *z,
const char *s_userinfo_jwt) {
oidc_session_set(r, z, OIDC_SESSION_KEY_USERINFO_JWT, s_userinfo_jwt);
}
const char * oidc_session_get_userinfo_jwt(request_rec *r, oidc_session_t *z) {
return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_USERINFO_JWT);
}
/*
* id_token claims
*/
void oidc_session_set_idtoken_claims(request_rec *r, oidc_session_t *z,
const char *idtoken_claims) {
oidc_session_set_filtered_claims(r, z, OIDC_SESSION_KEY_IDTOKEN_CLAIMS,
idtoken_claims);
}
const char * oidc_session_get_idtoken_claims(request_rec *r, oidc_session_t *z) {
return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_IDTOKEN_CLAIMS);
}
json_t *oidc_session_get_idtoken_claims_json(request_rec *r, oidc_session_t *z) {
return oidc_session_get_str2json(r, z, oidc_session_get_idtoken_claims);
}
/*
* compact serialized id_token
*/
void oidc_session_set_idtoken(request_rec *r, oidc_session_t *z,
const char *s_id_token) {
oidc_session_set(r, z, OIDC_SESSION_KEY_IDTOKEN, s_id_token);
}
const char * oidc_session_get_idtoken(request_rec *r, oidc_session_t *z) {
return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_IDTOKEN);
}
/*
* access token
*/
void oidc_session_set_access_token(request_rec *r, oidc_session_t *z,
const char *access_token) {
oidc_session_set(r, z, OIDC_SESSION_KEY_ACCESSTOKEN, access_token);
}
const char * oidc_session_get_access_token(request_rec *r, oidc_session_t *z) {
return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_ACCESSTOKEN);
}
/*
* access token expires
*/
void oidc_session_set_access_token_expires(request_rec *r, oidc_session_t *z,
const int expires_in) {
if (expires_in != -1) {
oidc_session_set(r, z, OIDC_SESSION_KEY_ACCESSTOKEN_EXPIRES,
apr_psprintf(r->pool, "%" APR_TIME_T_FMT,
apr_time_sec(apr_time_now()) + expires_in));
}
}
const char * oidc_session_get_access_token_expires(request_rec *r,
oidc_session_t *z) {
return oidc_session_get_key2string(r, z,
OIDC_SESSION_KEY_ACCESSTOKEN_EXPIRES);
}
/*
* refresh token
*/
void oidc_session_set_refresh_token(request_rec *r, oidc_session_t *z,
const char *refresh_token) {
oidc_session_set(r, z, OIDC_SESSION_KEY_REFRESH_TOKEN, refresh_token);
}
const char * oidc_session_get_refresh_token(request_rec *r, oidc_session_t *z) {
return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_REFRESH_TOKEN);
}
/*
* session expires
*/
void oidc_session_set_session_expires(request_rec *r, oidc_session_t *z,
const apr_time_t expires) {
oidc_session_set_timestamp(r, z, OIDC_SESSION_KEY_SESSION_EXPIRES, expires);
}
apr_time_t oidc_session_get_session_expires(request_rec *r, oidc_session_t *z) {
return oidc_session_get_key2timestamp(r, z,
OIDC_SESSION_KEY_SESSION_EXPIRES);
}
/*
* cookie domain
*/
void oidc_session_set_cookie_domain(request_rec *r, oidc_session_t *z,
const char *cookie_domain) {
oidc_session_set(r, z, OIDC_SESSION_KEY_COOKIE_DOMAIN, cookie_domain);
}
const char * oidc_session_get_cookie_domain(request_rec *r, oidc_session_t *z) {
return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_COOKIE_DOMAIN);
}
/*
* userinfo last refresh
*/
void oidc_session_reset_userinfo_last_refresh(request_rec *r, oidc_session_t *z) {
oidc_session_set_timestamp(r, z, OIDC_SESSION_KEY_USERINFO_LAST_REFRESH,
apr_time_now());
}
apr_time_t oidc_session_get_userinfo_last_refresh(request_rec *r,
oidc_session_t *z) {
return oidc_session_get_key2timestamp(r, z,
OIDC_SESSION_KEY_USERINFO_LAST_REFRESH);
}
/*
* access_token last refresh
*/
void oidc_session_reset_access_token_last_refresh(request_rec *r,
oidc_session_t *z) {
oidc_session_set_timestamp(r, z, OIDC_SESSION_KEY_ACCESS_TOKEN_LAST_REFRESH,
apr_time_now());
}
apr_time_t oidc_session_get_access_token_last_refresh(request_rec *r,
oidc_session_t *z) {
return oidc_session_get_key2timestamp(r, z,
OIDC_SESSION_KEY_ACCESS_TOKEN_LAST_REFRESH);
}
/*
* request state
*/
void oidc_session_set_request_state(request_rec *r, oidc_session_t *z,
const char *request_state) {
oidc_session_set(r, z, OIDC_SESSION_KEY_REQUEST_STATE, request_state);
}
const char * oidc_session_get_request_state(request_rec *r, oidc_session_t *z) {
return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_REQUEST_STATE);
}
/*
* original url
*/
void oidc_session_set_original_url(request_rec *r, oidc_session_t *z,
const char *original_url) {
oidc_session_set(r, z, OIDC_SESSION_KEY_ORIGINAL_URL, original_url);
}
const char * oidc_session_get_original_url(request_rec *r, oidc_session_t *z) {
return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_ORIGINAL_URL);
}
/*
* session state
*/
void oidc_session_set_session_state(request_rec *r, oidc_session_t *z,
const char *session_state) {
oidc_session_set(r, z, OIDC_SESSION_KEY_SESSION_STATE, session_state);
}
const char * oidc_session_get_session_state(request_rec *r, oidc_session_t *z) {
return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_SESSION_STATE);
}
/*
* issuer
*/
void oidc_session_set_issuer(request_rec *r, oidc_session_t *z,
const char *issuer) {
oidc_session_set(r, z, OIDC_SESSION_KEY_ISSUER, issuer);
}
const char * oidc_session_get_issuer(request_rec *r, oidc_session_t *z) {
return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_ISSUER);
}
/*
* client_id
*/
void oidc_session_set_client_id(request_rec *r, oidc_session_t *z,
const char *client_id) {
oidc_session_set(r, z, OIDC_SESSION_KEY_CLIENT_ID, client_id);
}
const char * oidc_session_get_client_id(request_rec *r, oidc_session_t *z) {
return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_CLIENT_ID);
}
/*
* check session iframe URL
*/
void oidc_session_set_check_session_iframe(request_rec *r, oidc_session_t *z,
const char *check_session_iframe) {
oidc_session_set(r, z, OIDC_SESSION_KEY_CHECK_SESSION_IFRAME,
check_session_iframe);
}
const char * oidc_session_get_check_session_iframe(request_rec *r,
oidc_session_t *z) {
return oidc_session_get_key2string(r, z,
OIDC_SESSION_KEY_CHECK_SESSION_IFRAME);
}
/*
* logout endpoint URL
*/
void oidc_session_set_logout_endpoint(request_rec *r, oidc_session_t *z,
const char *logout_endpoint) {
oidc_session_set(r, z, OIDC_SESSION_KEY_LOGOUT_ENDPOINT, logout_endpoint);
}
const char * oidc_session_get_logout_endpoint(request_rec *r, oidc_session_t *z) {
return oidc_session_get_key2string(r, z, OIDC_SESSION_KEY_LOGOUT_ENDPOINT);
}
mod_auth_openidc-2.3.3/src/metadata.c 0000644 0000765 0000024 00000140033 13202535004 017364 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* OpenID Connect metadata handling routines, for both OP discovery and client registration
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*/
#include
#include
#include
#include
#include
#include
#include "mod_auth_openidc.h"
#include "parse.h"
extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
#define OIDC_METADATA_SUFFIX_PROVIDER "provider"
#define OIDC_METADATA_SUFFIX_CLIENT "client"
#define OIDC_METADATA_SUFFIX_CONF "conf"
#define OIDC_METADATA_ISSUER "issuer"
#define OIDC_METADATA_RESPONSE_TYPES_SUPPORTED "response_types_supported"
#define OIDC_METADATA_RESPONSE_MODES_SUPPORTED "response_modes_supported"
#define OIDC_METADATA_AUTHORIZATION_ENDPOINT "authorization_endpoint"
#define OIDC_METADATA_TOKEN_ENDPOINT "token_endpoint"
#define OIDC_METADATA_USERINFO_ENDPOINT "userinfo_endpoint"
#define OIDC_METADATA_JWKS_URI "jwks_uri"
#define OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED "token_endpoint_auth_methods_supported"
#define OIDC_METADATA_REGISTRATION_ENDPOINT "registration_endpoint"
#define OIDC_METADATA_CHECK_SESSION_IFRAME "check_session_iframe"
#define OIDC_METADATA_END_SESSION_ENDPOINT "end_session_endpoint"
#define OIDC_METADATA_CLIENT_ID "client_id"
#define OIDC_METADATA_CLIENT_SECRET "client_secret"
#define OIDC_METADATA_CLIENT_SECRET_EXPIRES_AT "client_secret_expires_at"
#define OIDC_METADATA_KEYS "keys"
#define OIDC_METADATA_CLIENT_JWKS_URI "client_jwks_uri"
#define OIDC_METADATA_ID_TOKEN_SIGNED_RESPONSE_ALG "id_token_signed_response_alg"
#define OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ALG "id_token_encrypted_response_alg"
#define OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ENC "id_token_encrypted_response_enc"
#define OIDC_METADATA_USERINFO_SIGNED_RESPONSE_ALG "userinfo_signed_response_alg"
#define OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ALG "userinfo_encrypted_response_alg"
#define OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ENC "userinfo_encrypted_response_enc"
#define OIDC_METADATA_CLIENT_NAME "client_name"
#define OIDC_METADATA_REDIRECT_URIS "redirect_uris"
#define OIDC_METADATA_RESPONSE_TYPES "response_types"
#define OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHOD "token_endpoint_auth_method"
#define OIDC_METADATA_CONTACTS "contacts"
#define OIDC_METADATA_INITIATE_LOGIN_URI "initiate_login_uri"
#define OIDC_METADATA_FRONTCHANNEL_LOGOUT_URI "frontchannel_logout_uri"
#define OIDC_METADATA_POST_LOGOUT_REDIRECT_URIS "post_logout_redirect_uris"
#define OIDC_METADATA_SSL_VALIDATE_SERVER "ssl_validate_server"
#define OIDC_METADATA_SCOPE "scope"
#define OIDC_METADATA_JWKS_REFRESH_INTERVAL "jwks_refresh_interval"
#define OIDC_METADATA_IDTOKEN_IAT_SLACK "idtoken_iat_slack"
#define OIDC_METADATA_SESSION_MAX_DURATION "session_max_duration"
#define OIDC_METADATA_AUTH_REQUEST_PARAMS "auth_request_params"
#define OIDC_METADATA_TOKEN_ENDPOINT_PARAMS "token_endpoint_params"
#define OIDC_METADATA_RESPONSE_MODE "response_mode"
#define OIDC_METADATA_PKCE_METHOD "pkce_method"
#define OIDC_METADATA_CLIENT_CONTACT "client_contact"
#define OIDC_METADATA_TOKEN_ENDPOINT_AUTH "token_endpoint_auth"
#define OIDC_METADATA_REGISTRATION_TOKEN "registration_token"
#define OIDC_METADATA_REGISTRATION_ENDPOINT_JSON "registration_endpoint_json"
#define OIDC_METADATA_RESPONSE_TYPE "response_type"
#define OIDC_METADATA_USERINFO_REFRESH_INTERVAL "userinfo_refresh_interval"
#define OIDC_METADATA_TOKEN_ENDPOINT_TLS_CLIENT_CERT "token_endpoint_tls_client_cert"
#define OIDC_METADATA_TOKEN_ENDPOINT_TLS_CLIENT_KEY "token_endpoint_tls_client_key"
#define OIDC_METADATA_REQUEST_OBJECT "request_object"
#define OIDC_METADATA_USERINFO_TOKEN_METHOD "userinfo_token_method"
#define OIDC_METADATA_TOKEN_BINDING_POLICY "token_binding_policy"
#define OIDC_METADATA_AUTH_REQUEST_METHOD "auth_request_method"
#define OIDC_METADATA_ISSUER_SPECIFIC_REDIRECT_URI "issuer_specific_redirect_uri"
/*
* get the metadata filename for a specified issuer (cq. urlencode it)
*/
static const char *oidc_metadata_issuer_to_filename(request_rec *r,
const char *issuer) {
/* strip leading https:// */
char *p = strstr(issuer, "https://");
if (p == issuer) {
p = apr_pstrdup(r->pool, issuer + strlen("https://"));
} else {
p = strstr(issuer, "http://");
if (p == issuer) {
p = apr_pstrdup(r->pool, issuer + strlen("http://"));
} else {
p = apr_pstrdup(r->pool, issuer);
}
}
/* strip trailing '/' */
int n = strlen(p);
if (p[n - 1] == OIDC_CHAR_FORWARD_SLASH)
p[n - 1] = '\0';
return oidc_util_escape_string(r, p);
}
/*
* get the issuer from a metadata filename (cq. urldecode it)
*/
static const char *oidc_metadata_filename_to_issuer(request_rec *r,
const char *filename) {
char *result = apr_pstrdup(r->pool, filename);
char *p = strrchr(result, OIDC_CHAR_DOT);
*p = '\0';
p = oidc_util_unescape_string(r, result);
return apr_psprintf(r->pool, "https://%s", p);
}
/*
* get the full path to the metadata file for a specified issuer and directory
*/
static const char *oidc_metadata_file_path(request_rec *r, oidc_cfg *cfg,
const char *issuer, const char *type) {
return apr_psprintf(r->pool, "%s/%s.%s", cfg->metadata_dir,
oidc_metadata_issuer_to_filename(r, issuer), type);
}
/*
* get the full path to the provider metadata file for a specified issuer
*/
static const char *oidc_metadata_provider_file_path(request_rec *r,
const char *issuer) {
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
return oidc_metadata_file_path(r, cfg, issuer,
OIDC_METADATA_SUFFIX_PROVIDER);
}
/*
* get the full path to the client metadata file for a specified issuer
*/
static const char *oidc_metadata_client_file_path(request_rec *r,
const char *issuer) {
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
return oidc_metadata_file_path(r, cfg, issuer, OIDC_METADATA_SUFFIX_CLIENT);
}
/*
* get the full path to the custom config file for a specified issuer
*/
static const char *oidc_metadata_conf_path(request_rec *r, const char *issuer) {
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
return oidc_metadata_file_path(r, cfg, issuer, OIDC_METADATA_SUFFIX_CONF);
}
/*
* get cache key for the JWKs file for a specified URI
*/
static const char *oidc_metadata_jwks_cache_key(request_rec *r,
const char *jwks_uri) {
return jwks_uri;
}
/*
* read a JSON metadata file from disk
*/
static apr_byte_t oidc_metadata_file_read_json(request_rec *r, const char *path,
json_t **result) {
char *buf = NULL;
/* read the file contents */
if (oidc_util_file_read(r, path, r->pool, &buf) == FALSE)
return FALSE;
/* decode the JSON contents of the buffer */
return oidc_util_decode_json_object(r, buf, result);
}
/*
* check if the specified entry in metadata is a valid URI
*/
static apr_byte_t oidc_metadata_is_valid_uri(request_rec *r, const char *type,
const char *issuer, json_t *json, const char *key, char **value,
apr_byte_t is_mandatory) {
char *s_value = NULL;
oidc_json_object_get_string(r->pool, json, key, &s_value, NULL);
if (s_value == NULL) {
if (is_mandatory) {
oidc_error(r,
"%s (%s) JSON metadata does not contain the mandatory \"%s\" string entry",
type, issuer, key);
}
return (!is_mandatory);
}
if (oidc_valid_http_url(r->pool, s_value) != NULL) {
oidc_warn(r, "\"%s\" is not a valid http URL for key \"%s\"", s_value,
key);
return FALSE;
}
if (value)
*value = s_value;
return TRUE;
}
/*
* check to see if JSON provider metadata is valid
*/
static apr_byte_t oidc_metadata_provider_is_valid(request_rec *r, oidc_cfg *cfg,
json_t *j_provider, const char *issuer) {
/* get the "issuer" from the provider metadata and double-check that it matches what we looked for */
char *s_issuer = NULL;
oidc_json_object_get_string(r->pool, j_provider, OIDC_METADATA_ISSUER,
&s_issuer, NULL);
if (s_issuer == NULL) {
oidc_error(r,
"provider (%s) JSON metadata did not contain an \"" OIDC_METADATA_ISSUER "\" string",
issuer);
return FALSE;
}
/* check that the issuer matches */
if (issuer != NULL) {
if (oidc_util_issuer_match(issuer, s_issuer) == FALSE) {
oidc_error(r,
"requested issuer (%s) does not match the \"" OIDC_METADATA_ISSUER "\" value in the provider metadata file: %s",
issuer, s_issuer);
return FALSE;
}
}
/* verify that the provider supports the a flow that we implement */
if (oidc_valid_string_in_array(r->pool, j_provider,
OIDC_METADATA_RESPONSE_TYPES_SUPPORTED, oidc_valid_response_type, NULL,
FALSE) != NULL) {
if (json_object_get(j_provider,
OIDC_METADATA_RESPONSE_TYPES_SUPPORTED) != NULL) {
oidc_error(r,
"could not find a supported response type in provider metadata (%s) for entry \"" OIDC_METADATA_RESPONSE_TYPES_SUPPORTED "\"",
issuer);
return FALSE;
}
oidc_warn(r,
"could not find (required) supported response types (\"" OIDC_METADATA_RESPONSE_TYPES_SUPPORTED "\") in provider metadata (%s); assuming that \"code\" flow is supported...",
issuer);
}
/* verify that the provider supports a response_mode that we implement */
if (oidc_valid_string_in_array(r->pool, j_provider,
OIDC_METADATA_RESPONSE_MODES_SUPPORTED, oidc_valid_response_mode, NULL,
TRUE) != NULL) {
oidc_error(r,
"could not find a supported response mode in provider metadata (%s) for entry \"" OIDC_METADATA_RESPONSE_MODES_SUPPORTED "\"",
issuer);
return FALSE;
}
/* check the required authorization endpoint */
if (oidc_metadata_is_valid_uri(r, OIDC_METADATA_SUFFIX_PROVIDER, issuer,
j_provider,
OIDC_METADATA_AUTHORIZATION_ENDPOINT, NULL, TRUE) == FALSE)
return FALSE;
/* check the optional token endpoint */
if (oidc_metadata_is_valid_uri(r, OIDC_METADATA_SUFFIX_PROVIDER, issuer,
j_provider,
OIDC_METADATA_TOKEN_ENDPOINT, NULL, FALSE) == FALSE)
return FALSE;
/* check the optional user info endpoint */
if (oidc_metadata_is_valid_uri(r, OIDC_METADATA_SUFFIX_PROVIDER, issuer,
j_provider,
OIDC_METADATA_USERINFO_ENDPOINT, NULL, FALSE) == FALSE)
return FALSE;
/* check the optional JWKs URI */
if (oidc_metadata_is_valid_uri(r, OIDC_METADATA_SUFFIX_PROVIDER, issuer,
j_provider,
OIDC_METADATA_JWKS_URI, NULL, FALSE) == FALSE)
return FALSE;
/* find out what type of authentication the token endpoint supports */
if (oidc_valid_string_in_array(r->pool, j_provider,
OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED,
oidc_cfg_get_valid_endpoint_auth_function(cfg), NULL,
TRUE) != NULL) {
oidc_error(r,
"could not find a supported token endpoint authentication method in provider metadata (%s) for entry \"" OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED "\"",
issuer);
return FALSE;
}
return TRUE;
}
/*
* check to see if dynamically registered JSON client metadata is valid and has not expired
*/
static apr_byte_t oidc_metadata_client_is_valid(request_rec *r,
json_t *j_client, const char *issuer) {
char *str;
/* get a handle to the client_id we need to use for this provider */
str = NULL;
oidc_json_object_get_string(r->pool, j_client, OIDC_METADATA_CLIENT_ID,
&str, NULL);
if (str == NULL) {
oidc_error(r,
"client (%s) JSON metadata did not contain a \"" OIDC_METADATA_CLIENT_ID "\" string",
issuer);
return FALSE;
}
/* get a handle to the client_secret we need to use for this provider */
str = NULL;
oidc_json_object_get_string(r->pool, j_client, OIDC_METADATA_CLIENT_SECRET,
&str, NULL);
if (str == NULL) {
oidc_warn(r,
"client (%s) JSON metadata did not contain a \"" OIDC_METADATA_CLIENT_SECRET "\" string",
issuer);
}
/* the expiry timestamp from the JSON object */
json_t *expires_at = json_object_get(j_client,
OIDC_METADATA_CLIENT_SECRET_EXPIRES_AT);
if ((expires_at == NULL) || (!json_is_integer(expires_at))) {
oidc_debug(r,
"client (%s) metadata did not contain a \"" OIDC_METADATA_CLIENT_SECRET_EXPIRES_AT "\" setting",
issuer);
/* assume that it never expires */
return TRUE;
}
/* see if it is unrestricted */
if (json_integer_value(expires_at) == 0) {
oidc_debug(r,
"client (%s) metadata never expires (" OIDC_METADATA_CLIENT_SECRET_EXPIRES_AT "=0)",
issuer);
return TRUE;
}
/* check if the value >= now */
if (apr_time_sec(apr_time_now()) > json_integer_value(expires_at)) {
oidc_warn(r, "client (%s) secret expired", issuer);
return FALSE;
}
oidc_debug(r, "client (%s) metadata is valid", issuer);
return TRUE;
}
/*
* checks if a parsed JWKs file is a valid one, cq. contains "keys"
*/
static apr_byte_t oidc_metadata_jwks_is_valid(request_rec *r,
const oidc_jwks_uri_t *jwks_uri, json_t *j_jwks) {
json_t *keys = json_object_get(j_jwks, OIDC_METADATA_KEYS);
if ((keys == NULL) || (!json_is_array(keys))) {
oidc_error(r,
"JWKs JSON metadata obtained from URL \"%s\" did not contain a \"" OIDC_METADATA_KEYS "\" array",
jwks_uri->url);
return FALSE;
}
return TRUE;
}
/*
* check is a specified JOSE feature is supported
*/
static apr_byte_t oidc_metadata_conf_jose_is_supported(request_rec *r,
json_t *j_conf, const char *issuer, const char *key,
oidc_valid_function_t valid_function) {
char *s_value = NULL;
oidc_json_object_get_string(r->pool, j_conf, key, &s_value, NULL);
if (s_value == NULL)
return TRUE;
const char *rv = valid_function(r->pool, s_value);
if (rv != NULL) {
oidc_error(r,
"(%s) JSON conf data has \"%s\" entry but it contains an unsupported algorithm or encryption type: \"%s\" (%s)",
issuer, key, s_value, rv);
return FALSE;
}
return TRUE;
}
/*
* check to see if JSON configuration data is valid
*/
static apr_byte_t oidc_metadata_conf_is_valid(request_rec *r, json_t *j_conf,
const char *issuer) {
if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer,
OIDC_METADATA_ID_TOKEN_SIGNED_RESPONSE_ALG,
oidc_valid_signed_response_alg) == FALSE)
return FALSE;
if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer,
OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ALG,
oidc_valid_encrypted_response_alg) == FALSE)
return FALSE;
if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer,
OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ENC,
oidc_valid_encrypted_response_enc) == FALSE)
return FALSE;
if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer,
OIDC_METADATA_USERINFO_SIGNED_RESPONSE_ALG,
oidc_valid_signed_response_alg) == FALSE)
return FALSE;
if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer,
OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ALG,
oidc_valid_encrypted_response_alg) == FALSE)
return FALSE;
if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer,
OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ENC,
oidc_valid_encrypted_response_enc) == FALSE)
return FALSE;
return TRUE;
}
/*
* register the client with the OP using Dynamic Client Registration
*/
static apr_byte_t oidc_metadata_client_register(request_rec *r, oidc_cfg *cfg,
oidc_provider_t *provider, json_t **j_client, char **response) {
/* assemble the JSON registration request */
json_t *data = json_object();
json_object_set_new(data, OIDC_METADATA_CLIENT_NAME,
json_string(provider->client_name));
json_object_set_new(data, OIDC_METADATA_REDIRECT_URIS,
json_pack("[s]", oidc_get_redirect_uri_iss(r, cfg, provider)));
json_t *response_types = json_array();
apr_array_header_t *flows = oidc_proto_supported_flows(r->pool);
int i;
for (i = 0; i < flows->nelts; i++) {
json_array_append_new(response_types,
json_string(((const char**) flows->elts)[i]));
}
json_object_set_new(data, OIDC_METADATA_RESPONSE_TYPES, response_types);
if (provider->token_endpoint_auth != NULL) {
json_object_set_new(data, OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHOD,
json_string(provider->token_endpoint_auth));
}
if (provider->client_contact != NULL) {
json_object_set_new(data, OIDC_METADATA_CONTACTS,
json_pack("[s]", provider->client_contact));
}
if (provider->client_jwks_uri) {
json_object_set_new(data, OIDC_METADATA_JWKS_URI,
json_string(provider->client_jwks_uri));
} else if (cfg->public_keys != NULL) {
json_object_set_new(data, OIDC_METADATA_JWKS_URI,
json_string(
apr_psprintf(r->pool, "%s?%s=rsa",
oidc_get_redirect_uri(r, cfg),
OIDC_REDIRECT_URI_REQUEST_JWKS)));
}
if (provider->id_token_signed_response_alg != NULL) {
json_object_set_new(data, OIDC_METADATA_ID_TOKEN_SIGNED_RESPONSE_ALG,
json_string(provider->id_token_signed_response_alg));
}
if (provider->id_token_encrypted_response_alg != NULL) {
json_object_set_new(data, OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ALG,
json_string(provider->id_token_encrypted_response_alg));
}
if (provider->id_token_encrypted_response_enc != NULL) {
json_object_set_new(data, OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ENC,
json_string(provider->id_token_encrypted_response_enc));
}
if (provider->userinfo_signed_response_alg != NULL) {
json_object_set_new(data, OIDC_METADATA_USERINFO_SIGNED_RESPONSE_ALG,
json_string(provider->userinfo_signed_response_alg));
}
if (provider->userinfo_encrypted_response_alg != NULL) {
json_object_set_new(data, OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ALG,
json_string(provider->userinfo_encrypted_response_alg));
}
if (provider->userinfo_encrypted_response_enc != NULL) {
json_object_set_new(data, OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ENC,
json_string(provider->userinfo_encrypted_response_enc));
}
json_object_set_new(data, OIDC_METADATA_INITIATE_LOGIN_URI,
json_string(oidc_get_redirect_uri(r, cfg)));
json_object_set_new(data, OIDC_METADATA_FRONTCHANNEL_LOGOUT_URI,
json_string(
apr_psprintf(r->pool, "%s?%s=%s",
oidc_get_redirect_uri(r, cfg),
OIDC_REDIRECT_URI_REQUEST_LOGOUT,
OIDC_GET_STYLE_LOGOUT_PARAM_VALUE)));
if (cfg->default_slo_url != NULL) {
json_object_set_new(data, OIDC_METADATA_POST_LOGOUT_REDIRECT_URIS,
json_pack("[s]", cfg->default_slo_url));
}
/* add any custom JSON in to the registration request */
if (provider->registration_endpoint_json != NULL) {
json_t *json = NULL;
if (oidc_util_decode_json_object(r,
provider->registration_endpoint_json, &json) == FALSE)
return FALSE;
oidc_util_json_merge(r, json, data);
json_decref(json);
}
/* dynamically register the client with the specified parameters */
if (oidc_util_http_post_json(r, provider->registration_endpoint_url, data,
NULL, provider->registration_token, provider->ssl_validate_server, response,
cfg->http_timeout_short, cfg->outgoing_proxy,
oidc_dir_cfg_pass_cookies(r),
NULL, NULL) == FALSE) {
json_decref(data);
return FALSE;
}
json_decref(data);
/* decode and see if it is not an error response somehow */
if (oidc_util_decode_json_and_check_error(r, *response, j_client) == FALSE) {
oidc_error(r,
"JSON parsing of dynamic client registration response failed");
return FALSE;
}
return TRUE;
}
/*
* helper function to get the JWKs for the specified issuer
*/
static apr_byte_t oidc_metadata_jwks_retrieve_and_cache(request_rec *r,
oidc_cfg *cfg, const oidc_jwks_uri_t *jwks_uri, json_t **j_jwks) {
char *response = NULL;
/* no valid provider metadata, get it at the specified URL with the specified parameters */
if (oidc_util_http_get(r, jwks_uri->url, NULL, NULL,
NULL, jwks_uri->ssl_validate_server, &response, cfg->http_timeout_long,
cfg->outgoing_proxy, oidc_dir_cfg_pass_cookies(r), NULL,
NULL) == FALSE)
return FALSE;
/* decode and see if it is not an error response somehow */
if (oidc_util_decode_json_and_check_error(r, response, j_jwks) == FALSE) {
oidc_error(r, "JSON parsing of JWKs published at the jwks_uri failed");
return FALSE;
}
/* check to see if it is valid metadata */
if (oidc_metadata_jwks_is_valid(r, jwks_uri, *j_jwks) == FALSE)
return FALSE;
/* store the JWKs in the cache */
oidc_cache_set_jwks(r, oidc_metadata_jwks_cache_key(r, jwks_uri->url),
response,
apr_time_now() + apr_time_from_sec(jwks_uri->refresh_interval));
return TRUE;
}
/*
* return JWKs for the specified issuer
*/
apr_byte_t oidc_metadata_jwks_get(request_rec *r, oidc_cfg *cfg,
const oidc_jwks_uri_t *jwks_uri, json_t **j_jwks, apr_byte_t *refresh) {
oidc_debug(r, "enter, jwks_uri=%s, refresh=%d", jwks_uri->url, *refresh);
/* see if we need to do a forced refresh */
if (*refresh == TRUE) {
oidc_debug(r, "doing a forced refresh of the JWKs from URI \"%s\"",
jwks_uri->url);
if (oidc_metadata_jwks_retrieve_and_cache(r, cfg, jwks_uri,
j_jwks) == TRUE)
return TRUE;
// else: fallback on any cached JWKs
}
/* see if the JWKs is cached */
char *value = NULL;
oidc_cache_get_jwks(r, oidc_metadata_jwks_cache_key(r, jwks_uri->url),
&value);
if (value == NULL) {
/* it is non-existing or expired: do a forced refresh */
*refresh = TRUE;
return oidc_metadata_jwks_retrieve_and_cache(r, cfg, jwks_uri, j_jwks);
}
/* decode and see if it is not an error response somehow */
if (oidc_util_decode_json_and_check_error(r, value, j_jwks) == FALSE) {
oidc_error(r, "JSON parsing of cached JWKs data failed");
return FALSE;
}
return TRUE;
}
/*
* use OpenID Connect Discovery to get metadata for the specified issuer
*/
apr_byte_t oidc_metadata_provider_retrieve(request_rec *r, oidc_cfg *cfg,
const char *issuer, const char *url, json_t **j_metadata,
char **response) {
/* get provider metadata from the specified URL with the specified parameters */
if (oidc_util_http_get(r, url, NULL, NULL, NULL,
cfg->provider.ssl_validate_server, response,
cfg->http_timeout_short, cfg->outgoing_proxy,
oidc_dir_cfg_pass_cookies(r),
NULL, NULL) == FALSE)
return FALSE;
/* decode and see if it is not an error response somehow */
if (oidc_util_decode_json_and_check_error(r, *response, j_metadata) == FALSE) {
oidc_error(r, "JSON parsing of retrieved Discovery document failed");
return FALSE;
}
/* check to see if it is valid metadata */
if (oidc_metadata_provider_is_valid(r, cfg, *j_metadata, issuer) == FALSE)
return FALSE;
/* all OK */
return TRUE;
}
/*
* see if we have provider metadata and check its validity
* if not, use OpenID Connect Discovery to get it, check it and store it
*/
static apr_byte_t oidc_metadata_provider_get(request_rec *r, oidc_cfg *cfg,
const char *issuer, json_t **j_provider, apr_byte_t allow_discovery) {
/* holds the response data/string/JSON from the OP */
char *response = NULL;
/* get the full file path to the provider metadata for this issuer */
const char *provider_path = oidc_metadata_provider_file_path(r, issuer);
/* check the last-modified timestamp */
apr_byte_t use_cache = TRUE;
apr_finfo_t fi;
json_t *j_cache = NULL;
apr_byte_t have_cache = FALSE;
/* see if we are refreshing metadata and we need a refresh */
if (cfg->provider_metadata_refresh_interval > 0) {
have_cache = (apr_stat(&fi, provider_path, APR_FINFO_MTIME, r->pool)
== APR_SUCCESS);
if (have_cache == TRUE)
use_cache = (apr_time_now()
< fi.mtime
+ apr_time_from_sec(
cfg->provider_metadata_refresh_interval));
oidc_debug(r, "use_cache: %s", use_cache ? "yes" : "no");
}
/* see if we have valid metadata already, if so, return it */
if (oidc_metadata_file_read_json(r, provider_path, &j_cache) == TRUE) {
/* return the validation result */
if (use_cache == TRUE) {
*j_provider = j_cache;
return oidc_metadata_provider_is_valid(r, cfg, *j_provider, issuer);
}
}
if ((have_cache == FALSE) && (!allow_discovery)) {
oidc_warn(r,
"no metadata found for the requested issuer (%s), and Discovery is not allowed",
issuer);
return FALSE;
}
/* assemble the URL to the .well-known OpenID metadata */
const char *url = apr_psprintf(r->pool, "%s",
((strstr(issuer, "http://") == issuer)
|| (strstr(issuer, "https://") == issuer)) ?
issuer : apr_psprintf(r->pool, "https://%s", issuer));
url = apr_psprintf(r->pool, "%s%s.well-known/openid-configuration", url,
url[strlen(url) - 1] != OIDC_CHAR_FORWARD_SLASH ?
OIDC_STR_FORWARD_SLASH : "");
/* get the metadata for the issuer using OpenID Connect Discovery and validate it */
if (oidc_metadata_provider_retrieve(r, cfg, issuer, url, j_provider,
&response) == FALSE) {
oidc_debug(r,
"could not retrieve provider metadata; have_cache: %s (data=%pp)",
have_cache ? "yes" : "no", j_cache);
/* see if we can use at least the cache that may have expired by now */
if ((cfg->provider_metadata_refresh_interval > 0)
&& (have_cache == TRUE) && (j_cache != NULL)) {
/* reset the file-modified timestamp so it is cached for a while again */
apr_file_mtime_set(provider_path, apr_time_now(), r->pool);
/* return the validated cached data */
*j_provider = j_cache;
return oidc_metadata_provider_is_valid(r, cfg, *j_provider, issuer);
}
return FALSE;
}
/* since it is valid, write the obtained provider metadata file */
if (oidc_util_file_write(r, provider_path, response) == FALSE)
return FALSE;
return TRUE;
}
/*
* see if we have config metadata
*/
static apr_byte_t oidc_metadata_conf_get(request_rec *r, oidc_cfg *cfg,
const char *issuer, json_t **j_conf) {
/* get the full file path to the conf metadata for this issuer */
const char *conf_path = oidc_metadata_conf_path(r, issuer);
/* the .conf file is optional */
apr_finfo_t fi;
if (apr_stat(&fi, conf_path, APR_FINFO_MTIME, r->pool) != APR_SUCCESS)
return TRUE;
/* see if we have valid metadata already, if so, return it */
if (oidc_metadata_file_read_json(r, conf_path, j_conf) == TRUE) {
/* return the validation result */
return oidc_metadata_conf_is_valid(r, *j_conf, issuer);
}
return FALSE;
}
/*
* see if we have client metadata and check its validity
* if not, use OpenID Connect Client Registration to get it, check it and store it
*/
static apr_byte_t oidc_metadata_client_get(request_rec *r, oidc_cfg *cfg,
const char *issuer, oidc_provider_t *provider, json_t **j_client) {
/* get the full file path to the client metadata for this issuer */
const char *client_path = oidc_metadata_client_file_path(r, issuer);
/* see if we have valid metadata already, if so, return it */
if (oidc_metadata_file_read_json(r, client_path, j_client) == TRUE) {
/* if the client metadata is (still) valid, return it */
if (oidc_metadata_client_is_valid(r, *j_client, issuer) == TRUE)
return TRUE;
}
/* at this point we have no valid client metadata, see if there's a registration endpoint for this provider */
if (provider->registration_endpoint_url == NULL) {
oidc_error(r,
"no (valid) client metadata exists for provider (%s) and provider JSON object did not contain a (valid) \"" OIDC_METADATA_REGISTRATION_ENDPOINT "\" string",
issuer);
return FALSE;
}
/* try and get client metadata by registering the client at the registration endpoint */
char *response = NULL;
if (oidc_metadata_client_register(r, cfg, provider, j_client,
&response) == FALSE)
return FALSE;
/* check to see if it is valid metadata */
if (oidc_metadata_client_is_valid(r, *j_client, issuer) == FALSE)
return FALSE;
/* since it is valid, write the obtained client metadata file */
if (oidc_util_file_write(r, client_path, response) == FALSE)
return FALSE;
return TRUE;
}
/*
* get a list of configured OIDC providers based on the entries in the provider metadata directory
*/
apr_byte_t oidc_metadata_list(request_rec *r, oidc_cfg *cfg,
apr_array_header_t **list) {
apr_status_t rc;
apr_dir_t *dir;
apr_finfo_t fi;
char s_err[128];
oidc_debug(r, "enter");
/* open the metadata directory */
if ((rc = apr_dir_open(&dir, cfg->metadata_dir, r->pool)) != APR_SUCCESS) {
oidc_error(r, "error opening metadata directory '%s' (%s)",
cfg->metadata_dir, apr_strerror(rc, s_err, sizeof(s_err)));
return FALSE;
}
/* allocate some space in the array that will hold the list of providers */
*list = apr_array_make(r->pool, 5, sizeof(const char*));
/* BTW: we could estimate the number in the array based on # directory entries... */
/* loop over the entries in the provider metadata directory */
while (apr_dir_read(&fi, APR_FINFO_NAME, dir) == APR_SUCCESS) {
/* skip "." and ".." entries */
if (fi.name[0] == OIDC_CHAR_DOT)
continue;
/* skip other non-provider entries */
char *ext = strrchr(fi.name, OIDC_CHAR_DOT);
if ((ext == NULL)
|| (strcmp(++ext, OIDC_METADATA_SUFFIX_PROVIDER) != 0))
continue;
/* get the issuer from the filename */
const char *issuer = oidc_metadata_filename_to_issuer(r, fi.name);
/* get the provider and client metadata, do all checks and registration if possible */
oidc_provider_t *provider = NULL;
if (oidc_metadata_get(r, cfg, issuer, &provider, FALSE) == TRUE) {
/* push the decoded issuer filename in to the array */
*(const char**) apr_array_push(*list) = provider->issuer;
}
}
/* we're done, cleanup now */
apr_dir_close(dir);
return TRUE;
}
/*
* parse boolean value from JSON configuration
*/
static void oidc_metadata_parse_boolean(request_rec *r, json_t *json,
const char *key, int *value, int default_value) {
int int_value = 0;
char *s_value = NULL;
if (oidc_json_object_get_bool(r->pool, json, key, &int_value,
default_value) == FALSE) {
oidc_json_object_get_string(r->pool, json, key, &s_value,
NULL);
if (s_value != NULL) {
const char *rv = oidc_parse_boolean(r->pool, s_value, &int_value);
if (rv != NULL) {
oidc_warn(r, "%s: %s", key, rv);
int_value = default_value;
}
} else {
oidc_json_object_get_int(r->pool, json, key, &int_value,
default_value);
}
}
*value = (int_value != 0) ? TRUE : FALSE;
}
/*
* parse URL value from JSON configuration
*/
static void oidc_metadata_parse_url(request_rec *r, const char *type,
const char *issuer, json_t *json, const char *key, char **value,
const char *default_value) {
if ((oidc_metadata_is_valid_uri(r, type, issuer, json, key, value,
FALSE) == FALSE) || ((*value == NULL) && (default_value != NULL))) {
*value = apr_pstrdup(r->pool, default_value);
}
}
/*
* parse the JSON provider metadata in to a oidc_provider_t struct but do not override values already set
*/
apr_byte_t oidc_metadata_provider_parse(request_rec *r, oidc_cfg *cfg,
json_t *j_provider, oidc_provider_t *provider) {
if (provider->issuer == NULL) {
/* get the "issuer" from the provider metadata */
oidc_json_object_get_string(r->pool, j_provider, OIDC_METADATA_ISSUER,
&provider->issuer, NULL);
}
if (provider->authorization_endpoint_url == NULL) {
/* get a handle to the authorization endpoint */
oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER,
provider->issuer, j_provider,
OIDC_METADATA_AUTHORIZATION_ENDPOINT,
&provider->authorization_endpoint_url,
NULL);
}
if (provider->token_endpoint_url == NULL) {
/* get a handle to the token endpoint */
oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER,
provider->issuer, j_provider,
OIDC_METADATA_TOKEN_ENDPOINT, &provider->token_endpoint_url,
NULL);
}
if (provider->userinfo_endpoint_url == NULL) {
/* get a handle to the user_info endpoint */
oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER,
provider->issuer, j_provider,
OIDC_METADATA_USERINFO_ENDPOINT,
&provider->userinfo_endpoint_url, NULL);
}
if (provider->jwks_uri == NULL) {
/* get a handle to the jwks_uri endpoint */
oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER,
provider->issuer, j_provider,
OIDC_METADATA_JWKS_URI, &provider->jwks_uri,
NULL);
}
if (provider->registration_endpoint_url == NULL) {
/* get a handle to the client registration endpoint */
oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER,
provider->issuer, j_provider,
OIDC_METADATA_REGISTRATION_ENDPOINT,
&provider->registration_endpoint_url,
NULL);
}
if (provider->check_session_iframe == NULL) {
/* get a handle to the check session iframe */
oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER,
provider->issuer, j_provider,
OIDC_METADATA_CHECK_SESSION_IFRAME,
&provider->check_session_iframe, NULL);
}
if (provider->end_session_endpoint == NULL) {
/* get a handle to the end session endpoint */
oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_PROVIDER,
provider->issuer, j_provider,
OIDC_METADATA_END_SESSION_ENDPOINT,
&provider->end_session_endpoint, NULL);
}
if (provider->token_endpoint_auth == NULL) {
if (oidc_valid_string_in_array(r->pool, j_provider,
OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED,
oidc_cfg_get_valid_endpoint_auth_function(cfg),
&provider->token_endpoint_auth,
TRUE) != NULL) {
oidc_error(r,
"could not find a supported token endpoint authentication method in provider metadata (%s) for entry \"" OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED "\"",
provider->issuer);
return FALSE;
}
}
return TRUE;
}
/*
* get a string value from a JSON object and see if it is a valid value according to the specified validation function
*/
void oidc_metadata_get_valid_string(request_rec *r, json_t *json,
const char *key, oidc_valid_function_t valid_function, char **str_value,
const char *default_str_value) {
char *v = NULL;
oidc_json_object_get_string(r->pool, json, key, &v, default_str_value);
if (v != NULL) {
const char *rv = valid_function(r->pool, v);
if (rv != NULL) {
oidc_warn(r,
"string value %s for key \"%s\" is invalid: %s; using default: %s",
v, key, rv, default_str_value);
v = apr_pstrdup(r->pool, default_str_value);
}
}
*str_value = v;
}
/*
* get an integer value from a JSON object and see if it is a valid value according to the specified validation function
*/
void oidc_metadata_get_valid_int(request_rec *r, json_t *json, const char *key,
oidc_valid_int_function_t valid_int_function, int *int_value,
int default_int_value) {
int v = 0;
oidc_json_object_get_int(r->pool, json, key, &v, default_int_value);
const char *rv = valid_int_function(r->pool, v);
if (rv != NULL) {
oidc_warn(r,
"integer value %d for key \"%s\" is invalid: %s; using default: %d",
v, key, rv, default_int_value);
v = default_int_value;
}
*int_value = v;
}
/*
* parse the JSON conf metadata in to a oidc_provider_t struct
*/
apr_byte_t oidc_metadata_conf_parse(request_rec *r, oidc_cfg *cfg,
json_t *j_conf, oidc_provider_t *provider) {
oidc_metadata_parse_url(r, OIDC_METADATA_SUFFIX_CONF, provider->issuer,
j_conf,
OIDC_METADATA_CLIENT_JWKS_URI, &provider->client_jwks_uri,
cfg->provider.client_jwks_uri);
/* get the (optional) signing & encryption settings for the id_token */
oidc_metadata_get_valid_string(r, j_conf,
OIDC_METADATA_ID_TOKEN_SIGNED_RESPONSE_ALG,
oidc_valid_signed_response_alg,
&provider->id_token_signed_response_alg,
cfg->provider.id_token_signed_response_alg);
oidc_metadata_get_valid_string(r, j_conf,
OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ALG,
oidc_valid_encrypted_response_alg,
&provider->id_token_encrypted_response_alg,
cfg->provider.id_token_encrypted_response_alg);
oidc_metadata_get_valid_string(r, j_conf,
OIDC_METADATA_ID_TOKEN_ENCRYPTED_RESPONSE_ENC,
oidc_valid_encrypted_response_enc,
&provider->id_token_encrypted_response_enc,
cfg->provider.id_token_encrypted_response_enc);
/* get the (optional) signing & encryption settings for the userinfo response */
oidc_metadata_get_valid_string(r, j_conf,
OIDC_METADATA_USERINFO_SIGNED_RESPONSE_ALG,
oidc_valid_signed_response_alg,
&provider->userinfo_signed_response_alg,
cfg->provider.userinfo_signed_response_alg);
oidc_metadata_get_valid_string(r, j_conf,
OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ALG,
oidc_valid_encrypted_response_alg,
&provider->userinfo_encrypted_response_alg,
cfg->provider.userinfo_encrypted_response_alg);
oidc_metadata_get_valid_string(r, j_conf,
OIDC_METADATA_USERINFO_ENCRYPTED_RESPONSE_ENC,
oidc_valid_encrypted_response_enc,
&provider->userinfo_encrypted_response_enc,
cfg->provider.userinfo_encrypted_response_enc);
/* find out if we need to perform SSL server certificate validation on the token_endpoint and user_info_endpoint for this provider */
oidc_metadata_parse_boolean(r, j_conf, OIDC_METADATA_SSL_VALIDATE_SERVER,
&provider->ssl_validate_server, cfg->provider.ssl_validate_server);
/* find out what scopes we should be requesting from this provider */
// TODO: use the provider "scopes_supported" to mix-and-match with what we've configured for the client
// TODO: check that "openid" is always included in the configured scopes, right?
oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_SCOPE,
&provider->scope, cfg->provider.scope);
/* see if we've got a custom JWKs refresh interval */
oidc_metadata_get_valid_int(r, j_conf, OIDC_METADATA_JWKS_REFRESH_INTERVAL,
oidc_valid_jwks_refresh_interval, &provider->jwks_refresh_interval,
cfg->provider.jwks_refresh_interval);
/* see if we've got a custom IAT slack interval */
oidc_metadata_get_valid_int(r, j_conf, OIDC_METADATA_IDTOKEN_IAT_SLACK,
oidc_valid_idtoken_iat_slack, &provider->idtoken_iat_slack,
cfg->provider.idtoken_iat_slack);
/* see if we've got a custom max session duration */
oidc_metadata_get_valid_int(r, j_conf, OIDC_METADATA_SESSION_MAX_DURATION,
oidc_valid_session_max_duration, &provider->session_max_duration,
cfg->provider.session_max_duration);
/* see if we've got custom authentication request parameter values */
oidc_json_object_get_string(r->pool, j_conf,
OIDC_METADATA_AUTH_REQUEST_PARAMS, &provider->auth_request_params,
cfg->provider.auth_request_params);
/* see if we've got custom token endpoint parameter values */
oidc_json_object_get_string(r->pool, j_conf,
OIDC_METADATA_TOKEN_ENDPOINT_PARAMS,
&provider->token_endpoint_params,
cfg->provider.token_endpoint_params);
/* get the response mode to use */
oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_RESPONSE_MODE,
oidc_valid_response_mode, &provider->response_mode,
cfg->provider.response_mode);
/* get the PKCE method to use */
char *pkce_method = NULL;
oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_PKCE_METHOD,
oidc_valid_pkce_method, &pkce_method,
cfg->provider.pkce ? cfg->provider.pkce->method : NULL);
if (pkce_method != NULL)
oidc_parse_pkce_type(r->pool, pkce_method, &provider->pkce);
/* get the client name */
oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_CLIENT_NAME,
&provider->client_name, cfg->provider.client_name);
/* get the client contact */
oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_CLIENT_CONTACT,
&provider->client_contact, cfg->provider.client_contact);
/* get the token endpoint authentication method */
oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_TOKEN_ENDPOINT_AUTH,
oidc_cfg_get_valid_endpoint_auth_function(cfg),
&provider->token_endpoint_auth, provider->token_endpoint_auth);
/* get the dynamic client registration token */
oidc_json_object_get_string(r->pool, j_conf,
OIDC_METADATA_REGISTRATION_TOKEN, &provider->registration_token,
cfg->provider.registration_token);
/* see if we've got custom registration request parameter values */
oidc_json_object_get_string(r->pool, j_conf,
OIDC_METADATA_REGISTRATION_ENDPOINT_JSON,
&provider->registration_endpoint_json,
cfg->provider.registration_endpoint_json);
/* get the flow to use; let the .client file set it otherwise (pass NULL as default value) */
oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_RESPONSE_TYPE,
oidc_valid_response_type, &provider->response_type,
NULL);
/* see if we've got a custom user info refresh interval */
oidc_metadata_get_valid_int(r, j_conf,
OIDC_METADATA_USERINFO_REFRESH_INTERVAL,
oidc_valid_userinfo_refresh_interval,
&provider->userinfo_refresh_interval,
cfg->provider.userinfo_refresh_interval);
/* TLS client cert auth settings */
oidc_json_object_get_string(r->pool, j_conf,
OIDC_METADATA_TOKEN_ENDPOINT_TLS_CLIENT_CERT,
&provider->token_endpoint_tls_client_cert,
cfg->provider.token_endpoint_tls_client_cert);
oidc_json_object_get_string(r->pool, j_conf,
OIDC_METADATA_TOKEN_ENDPOINT_TLS_CLIENT_KEY,
&provider->token_endpoint_tls_client_key,
cfg->provider.token_endpoint_tls_client_key);
oidc_json_object_get_string(r->pool, j_conf, OIDC_METADATA_REQUEST_OBJECT,
&provider->request_object, cfg->provider.request_object);
/* see if we've got a custom userinfo endpoint token presentation method */
char *method = NULL;
oidc_metadata_get_valid_string(r, j_conf,
OIDC_METADATA_USERINFO_TOKEN_METHOD,
oidc_valid_userinfo_token_method, &method,
NULL);
if (method != NULL)
oidc_parse_userinfo_token_method(r->pool, method,
&provider->userinfo_token_method);
else
provider->userinfo_token_method = OIDC_USER_INFO_TOKEN_METHOD_HEADER;
/* see if we've got a custom token binding policy */
char *policy = NULL;
oidc_metadata_get_valid_string(r, j_conf,
OIDC_METADATA_TOKEN_BINDING_POLICY, oidc_valid_token_binding_policy,
&policy,
NULL);
if (policy != NULL)
oidc_parse_token_binding_policy(r->pool, policy,
&provider->token_binding_policy);
else
provider->token_binding_policy = cfg->provider.token_binding_policy;
/* see if we've got a custom HTTP method for passing the auth request */
oidc_metadata_get_valid_string(r, j_conf, OIDC_METADATA_AUTH_REQUEST_METHOD,
oidc_valid_auth_request_method, &method,
NULL);
if (method != NULL)
oidc_parse_auth_request_method(r->pool, method,
&provider->auth_request_method);
else
provider->auth_request_method = cfg->provider.auth_request_method;
/* get the issuer specific redirect URI option */
oidc_metadata_parse_boolean(r, j_conf,
OIDC_METADATA_ISSUER_SPECIFIC_REDIRECT_URI,
&provider->issuer_specific_redirect_uri,
cfg->provider.issuer_specific_redirect_uri);
return TRUE;
}
/*
* parse the JSON client metadata in to a oidc_provider_t struct
*/
apr_byte_t oidc_metadata_client_parse(request_rec *r, oidc_cfg *cfg,
json_t *j_client, oidc_provider_t *provider) {
/* get a handle to the client_id we need to use for this provider */
oidc_json_object_get_string(r->pool, j_client, OIDC_METADATA_CLIENT_ID,
&provider->client_id, NULL);
/* get a handle to the client_secret we need to use for this provider */
oidc_json_object_get_string(r->pool, j_client, OIDC_METADATA_CLIENT_SECRET,
&provider->client_secret, NULL);
/* see if the token endpoint auth method defined in the client metadata overrides the provider one */
char *token_endpoint_auth = NULL;
oidc_json_object_get_string(r->pool, j_client,
OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHOD, &token_endpoint_auth,
NULL);
if (token_endpoint_auth != NULL) {
if ((apr_strnatcmp(token_endpoint_auth, OIDC_PROTO_CLIENT_SECRET_POST)
== 0)
|| (apr_strnatcmp(token_endpoint_auth,
OIDC_PROTO_CLIENT_SECRET_BASIC) == 0)
|| (apr_strnatcmp(token_endpoint_auth,
OIDC_PROTO_CLIENT_SECRET_JWT) == 0)
|| (apr_strnatcmp(token_endpoint_auth,
OIDC_PROTO_PRIVATE_KEY_JWT) == 0)) {
provider->token_endpoint_auth = apr_pstrdup(r->pool,
token_endpoint_auth);
} else {
oidc_warn(r,
"unsupported client auth method \"%s\" in client metadata for entry \"" OIDC_METADATA_TOKEN_ENDPOINT_AUTH_METHOD "\"",
token_endpoint_auth);
}
}
/* determine the response type if not set by .conf */
if (provider->response_type == NULL) {
provider->response_type = cfg->provider.response_type;
/* "response_types" is an array in the client metadata as by spec */
json_t *j_response_types = json_object_get(j_client,
OIDC_METADATA_RESPONSE_TYPES);
if ((j_response_types != NULL) && (json_is_array(j_response_types))) {
/* if there's an array we'll prefer the configured response_type if supported */
if (oidc_util_json_array_has_value(r, j_response_types,
provider->response_type) == FALSE) {
/* if the configured response_type is not supported, we'll fallback to the first one that is listed */
json_t *j_response_type = json_array_get(j_response_types, 0);
if (json_is_string(j_response_type)) {
provider->response_type = apr_pstrdup(r->pool,
json_string_value(j_response_type));
}
}
}
}
return TRUE;
}
/*
* get the metadata for a specified issuer
*
* this fill the oidc_provider_t struct based on the issuer filename by reading and merging
* contents from both provider metadata directory and client metadata directory
*/
apr_byte_t oidc_metadata_get(request_rec *r, oidc_cfg *cfg, const char *issuer,
oidc_provider_t **provider, apr_byte_t allow_discovery) {
apr_byte_t rc = FALSE;
/* pointers to the parsed JSON metadata */
json_t *j_provider = NULL;
json_t *j_client = NULL;
json_t *j_conf = NULL;
/* allocate space for a parsed-and-merged metadata struct */
*provider = apr_pcalloc(r->pool, sizeof(oidc_provider_t));
/*
* read and parse the provider, conf and client metadata respectively
* NB: order is important here
*/
if (oidc_metadata_provider_get(r, cfg, issuer, &j_provider,
allow_discovery) == FALSE)
goto end;
if (oidc_metadata_provider_parse(r, cfg, j_provider, *provider) == FALSE)
goto end;
if (oidc_metadata_conf_get(r, cfg, issuer, &j_conf) == FALSE)
goto end;
if (oidc_metadata_conf_parse(r, cfg, j_conf, *provider) == FALSE)
goto end;
if (oidc_metadata_client_get(r, cfg, issuer, *provider, &j_client) == FALSE)
goto end;
if (oidc_metadata_client_parse(r, cfg, j_client, *provider) == FALSE)
goto end;
rc = TRUE;
end:
if (j_provider)
json_decref(j_provider);
if (j_conf)
json_decref(j_conf);
if (j_client)
json_decref(j_client);
return rc;
}
mod_auth_openidc-2.3.3/src/jose.c 0000644 0000765 0000024 00000107420 13200665522 016556 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* JSON Web Token handling
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*/
#include
#include "jose.h"
#include
#include
#include
#include
#include
#include
#ifdef WIN32
#define snprintf _snprintf
#endif
/*
* assemble an error report
*/
void _oidc_jose_error_set(oidc_jose_error_t *error, const char *source,
const int line, const char *function, const char *msg, ...) {
if (error == NULL)
return;
snprintf(error->source, OIDC_JOSE_ERROR_SOURCE_LENGTH, "%s", source);
error->line = line;
snprintf(error->function, OIDC_JOSE_ERROR_FUNCTION_LENGTH, "%s", function);
va_list ap;
va_start(ap, msg);
vsnprintf(error->text, OIDC_JOSE_ERROR_TEXT_LENGTH, msg, ap);
va_end(ap);
}
/*
* set a header value in a JWT
*/
static void oidc_jwt_hdr_set(oidc_jwt_t *jwt, const char *key,
const char *value) {
json_object_set_new(jwt->header.value.json, key, json_string(value));
}
/*
* create a new JWT
*/
oidc_jwt_t *oidc_jwt_new(apr_pool_t *pool, int create_header,
int create_payload) {
oidc_jwt_t *jwt = apr_pcalloc(pool, sizeof(oidc_jwt_t));
if (create_header) {
jwt->header.value.json = json_object();
//oidc_jwt_hdr_set(jwt, "typ", "JWT");
}
if (create_payload) {
jwt->payload.value.json = json_object();
}
return jwt;
}
/*
* get a header value from a JWT
*/
const char *oidc_jwt_hdr_get(oidc_jwt_t *jwt, const char *key) {
cjose_err cjose_err;
cjose_header_t *hdr = cjose_jws_get_protected(jwt->cjose_jws);
return hdr ? cjose_header_get(hdr, key, &cjose_err) : NULL;
}
/*
* {"alg":"none"}
*/
#define OIDC_JOSE_HDR_ALG_NONE "eyJhbGciOiJub25lIn0"
/*
* perform compact serialization on a JWT and return the resulting string
*/
char *oidc_jwt_serialize(apr_pool_t *pool, oidc_jwt_t *jwt,
oidc_jose_error_t *err) {
cjose_err cjose_err;
const char *cser = NULL;
if (strcmp(jwt->header.alg, CJOSE_HDR_ALG_NONE) != 0) {
if (cjose_jws_export(jwt->cjose_jws, &cser, &cjose_err) == FALSE) {
oidc_jose_error(err, "cjose_jws_export failed: %s",
oidc_cjose_e2s(pool, cjose_err));
return NULL;
}
} else {
char *s_payload = json_dumps(jwt->payload.value.json,
JSON_PRESERVE_ORDER | JSON_COMPACT);
char *out = NULL;
size_t out_len;
if (cjose_base64url_encode((const uint8_t *) s_payload,
strlen(s_payload), &out, &out_len, &cjose_err) == FALSE)
return FALSE;
cser = apr_pstrmemdup(pool, out, out_len);
cjose_get_dealloc()(out);
free(s_payload);
cser = apr_psprintf(pool, "%s.%s.", OIDC_JOSE_HDR_ALG_NONE, cser);
}
return apr_pstrdup(pool, cser);
}
/*
* return the key type for an algorithm
*/
static int oidc_alg2kty(const char *alg) {
if (strcmp(alg, CJOSE_HDR_ALG_DIR) == 0)
return CJOSE_JWK_KTY_OCT;
if (strncmp(alg, "RS", 2) == 0)
return CJOSE_JWK_KTY_RSA;
if (strncmp(alg, "PS", 2) == 0)
return CJOSE_JWK_KTY_RSA;
if (strncmp(alg, "HS", 2) == 0)
return CJOSE_JWK_KTY_OCT;
#if (OIDC_JOSE_EC_SUPPORT)
if (strncmp(alg, "ES", 2) == 0)
return CJOSE_JWK_KTY_EC;
#endif
if ((strcmp(alg, CJOSE_HDR_ALG_A128KW) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_A192KW) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_A256KW) == 0))
return CJOSE_JWK_KTY_OCT;
if ((strcmp(alg, CJOSE_HDR_ALG_RSA1_5) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_RSA_OAEP) == 0))
return CJOSE_JWK_KTY_RSA;
return -1;
}
/*
* return the key type of a JWT
*/
int oidc_jwt_alg2kty(oidc_jwt_t *jwt) {
return oidc_alg2kty(jwt->header.alg);
}
/*
* return the key size for an algorithm
*/
unsigned int oidc_alg2keysize(const char *alg) {
if (alg == NULL)
return 0;
if (strcmp(alg, CJOSE_HDR_ALG_A128KW) == 0)
return 16;
if (strcmp(alg, CJOSE_HDR_ALG_A192KW) == 0)
return 24;
if (strcmp(alg, CJOSE_HDR_ALG_A256KW) == 0)
return 32;
if ((strcmp(alg, CJOSE_HDR_ALG_RS256) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_PS256) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_HS256) == 0))
return 32;
if ((strcmp(alg, CJOSE_HDR_ALG_RS384) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_PS384) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_HS384) == 0))
return 48;
if ((strcmp(alg, CJOSE_HDR_ALG_RS512) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_PS512) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_HS512) == 0))
return 64;
return 0;
}
/*
* create a new JWK
*/
static oidc_jwk_t *oidc_jwk_new(apr_pool_t *pool) {
oidc_jwk_t *jwk = apr_pcalloc(pool, sizeof(oidc_jwk_t));
return jwk;
}
static apr_byte_t oidc_jwk_parse_rsa_x5c(apr_pool_t *pool, json_t *json,
cjose_jwk_t **jwk, oidc_jose_error_t *err);
#define OIDC_JOSE_HDR_KTY "kty"
#define OIDC_JOSE_HDR_KTY_RSA "RSA"
#define OIDC_JOSE_HDR_X5C "x5c"
/*
* parse a JSON object with an RSA "x5c" JWK representation in to a cjose JWK object
*/
static cjose_jwk_t *oidc_jwk_parse_rsa_x5c_spec(apr_pool_t *pool,
const char *s_json, oidc_jose_error_t *err) {
cjose_jwk_t *cjose_jwk = NULL;
json_error_t json_error;
json_t *json = json_loads(s_json, 0, &json_error);
if (json == NULL) {
oidc_jose_error(err, "could not parse JWK: %s (%s)", json_error.text,
s_json);
goto end;
}
char *kty = NULL;
oidc_jose_get_string(pool, json, OIDC_JOSE_HDR_KTY, FALSE, &kty, NULL);
if (kty == NULL) {
oidc_jose_error(err,
"no key type \"" OIDC_JOSE_HDR_KTY "\" found in JWK JSON value");
goto end;
}
if (apr_strnatcmp(kty, OIDC_JOSE_HDR_KTY_RSA) != 0) {
oidc_jose_error(err,
"no \"" OIDC_JOSE_HDR_KTY_RSA "\" key type found JWK JSON value");
goto end;
}
json_t *v = json_object_get(json, OIDC_JOSE_HDR_X5C);
if (v == NULL) {
oidc_jose_error(err,
"no \"" OIDC_JOSE_HDR_X5C "\" key found in JWK JSON value");
goto end;
}
oidc_jwk_parse_rsa_x5c(pool, json, &cjose_jwk, err);
end:
if (json)
json_decref(json);
return cjose_jwk;
}
/*
* create a JWK struct from a cjose_jwk object
*/
static oidc_jwk_t *oidc_jwk_from_cjose(apr_pool_t *pool, cjose_jwk_t *cjose_jwk) {
cjose_err cjose_err;
oidc_jwk_t *jwk = oidc_jwk_new(pool);
jwk->cjose_jwk = cjose_jwk;
jwk->kid = apr_pstrdup(pool, cjose_jwk_get_kid(jwk->cjose_jwk, &cjose_err));
jwk->kty = cjose_jwk_get_kty(jwk->cjose_jwk, &cjose_err);
return jwk;
}
/*
* parse a JSON string to a JWK struct
*/
oidc_jwk_t *oidc_jwk_parse(apr_pool_t *pool, const char *s_json,
oidc_jose_error_t *err) {
cjose_err cjose_err;
cjose_jwk_t *cjose_jwk = cjose_jwk_import(s_json, strlen(s_json),
&cjose_err);
if (cjose_jwk == NULL) {
// exception because x5c is not supported by cjose natively
// ignore errors set by oidc_jwk_parse_rsa_x5c_spec
oidc_jose_error_t x5c_err;
cjose_jwk = oidc_jwk_parse_rsa_x5c_spec(pool, s_json, &x5c_err);
if (cjose_jwk == NULL) {
oidc_jose_error(err, "JWK parsing failed: %s",
oidc_cjose_e2s(pool, cjose_err));
return NULL;
}
}
return oidc_jwk_from_cjose(pool, cjose_jwk);
}
/*
* destroy resources allocated for a JWK struct
*/
void oidc_jwk_destroy(oidc_jwk_t *jwk) {
if (jwk) {
if (jwk->cjose_jwk) {
cjose_jwk_release(jwk->cjose_jwk);
jwk->cjose_jwk = NULL;
}
}
}
/*
* destroy a list of JWKs structs
*/
void oidc_jwk_list_destroy(apr_pool_t *pool, apr_hash_t *keys) {
apr_hash_index_t *hi = NULL;
if (keys == NULL)
return;
for (hi = apr_hash_first(pool, keys); hi; hi = apr_hash_next(hi)) {
oidc_jwk_t *jwk = NULL;
apr_hash_this(hi, NULL, NULL, (void **) &jwk);
oidc_jwk_destroy(jwk);
}
}
/*
* parse a JSON object in to a JWK struct
*/
apr_byte_t oidc_jwk_parse_json(apr_pool_t *pool, json_t *json, oidc_jwk_t **jwk,
oidc_jose_error_t *err) {
char *s_json = json_dumps(json, 0);
*jwk = oidc_jwk_parse(pool, s_json, err);
free(s_json);
return (*jwk != NULL);
}
/*
* convert a JWK struct to a JSON string
*/
apr_byte_t oidc_jwk_to_json(apr_pool_t *pool, oidc_jwk_t *jwk, char **s_json,
oidc_jose_error_t *err) {
cjose_err cjose_err;
char *s = cjose_jwk_to_json(jwk->cjose_jwk, TRUE, &cjose_err);
if (s == NULL) {
oidc_jose_error(err, "cjose_jwk_to_json failed: %s",
oidc_cjose_e2s(pool, cjose_err));
return FALSE;
}
*s_json = apr_pstrdup(pool, s);
free(s);
return TRUE;
}
/*
* hash a sequence of bytes with a specific algorithm and return the result as a base64url-encoded \0 terminated string
*/
static apr_byte_t oidc_jose_hash_and_base64url_encode(apr_pool_t *pool,
const char *openssl_hash_algo, const char *input, int input_len,
char **output) {
oidc_jose_error_t err;
unsigned char *hashed = NULL;
unsigned int hashed_len = 0;
if (oidc_jose_hash_bytes(pool, openssl_hash_algo,
(const unsigned char *) input, input_len, &hashed, &hashed_len,
&err) == FALSE) {
return FALSE;
}
char *out = NULL;
size_t out_len;
cjose_err cjose_err;
if (cjose_base64url_encode(hashed, hashed_len, &out, &out_len,
&cjose_err) == FALSE)
return FALSE;
*output = apr_pstrmemdup(pool, out, out_len);
cjose_get_dealloc()(out);
return TRUE;
}
/*
* set a specified key identifier or generate a key identifier and set it
*/
static apr_byte_t oidc_jwk_set_or_generate_kid(apr_pool_t *pool,
cjose_jwk_t *cjose_jwk, const char *s_kid, const char *key_params,
int key_params_len, oidc_jose_error_t *err) {
char *jwk_kid = NULL;
if (s_kid != NULL) {
jwk_kid = apr_pstrdup(pool, s_kid);
} else {
/* calculate a unique key identifier (kid) by fingerprinting the key params */
if (oidc_jose_hash_and_base64url_encode(pool, OIDC_JOSE_ALG_SHA256,
key_params, key_params_len, &jwk_kid) == FALSE) {
oidc_jose_error(err, "oidc_jose_hash_and_base64urlencode failed");
return FALSE;
}
}
cjose_err cjose_err;
if (cjose_jwk_set_kid(cjose_jwk, jwk_kid, strlen(jwk_kid),
&cjose_err) == FALSE) {
oidc_jose_error(err, "cjose_jwk_set_kid failed: %s",
oidc_cjose_e2s(pool, cjose_err));
return FALSE;
}
return TRUE;
}
/*
* create an "oct" symmetric JWK
*/
oidc_jwk_t *oidc_jwk_create_symmetric_key(apr_pool_t *pool, const char *skid,
const unsigned char *key, unsigned int key_len, apr_byte_t set_kid,
oidc_jose_error_t *err) {
cjose_err cjose_err;
cjose_jwk_t *cjose_jwk = cjose_jwk_create_oct_spec(key, key_len,
&cjose_err);
if (cjose_jwk == NULL) {
oidc_jose_error(err, "cjose_jwk_create_oct_spec failed: %s",
oidc_cjose_e2s(pool, cjose_err));
return FALSE;
}
if (set_kid == TRUE) {
if (oidc_jwk_set_or_generate_kid(pool, cjose_jwk, skid,
(const char *) key, key_len, err) == FALSE) {
cjose_jwk_release(cjose_jwk);
return FALSE;
}
}
oidc_jwk_t *jwk = oidc_jwk_new(pool);
jwk->cjose_jwk = cjose_jwk;
jwk->kid = apr_pstrdup(pool, cjose_jwk_get_kid(jwk->cjose_jwk, &cjose_err));
jwk->kty = cjose_jwk_get_kty(jwk->cjose_jwk, &cjose_err);
return jwk;
}
/*
* check if a string is an element of an array of strings
*/
static apr_byte_t oidc_jose_array_has_string(apr_array_header_t *haystack,
const char *needle) {
int i = 0;
while (i < haystack->nelts) {
if (apr_strnatcmp(((const char**) haystack->elts)[i], needle) == 0)
return TRUE;
i++;
}
return FALSE;
}
/*
* return all supported signing algorithms
*/
apr_array_header_t *oidc_jose_jws_supported_algorithms(apr_pool_t *pool) {
apr_array_header_t *result = apr_array_make(pool, 12, sizeof(const char*));
*(const char**) apr_array_push(result) = CJOSE_HDR_ALG_RS256;
*(const char**) apr_array_push(result) = CJOSE_HDR_ALG_RS384;
*(const char**) apr_array_push(result) = CJOSE_HDR_ALG_RS512;
*(const char**) apr_array_push(result) = CJOSE_HDR_ALG_PS256;
*(const char**) apr_array_push(result) = CJOSE_HDR_ALG_PS384;
*(const char**) apr_array_push(result) = CJOSE_HDR_ALG_PS512;
*(const char**) apr_array_push(result) = CJOSE_HDR_ALG_HS256;
*(const char**) apr_array_push(result) = CJOSE_HDR_ALG_HS384;
*(const char**) apr_array_push(result) = CJOSE_HDR_ALG_HS512;
#if (OIDC_JOSE_EC_SUPPORT)
*(const char**) apr_array_push(result) = CJOSE_HDR_ALG_ES256;
*(const char**) apr_array_push(result) = CJOSE_HDR_ALG_ES384;
*(const char**) apr_array_push(result) = CJOSE_HDR_ALG_ES512;
#endif
*(const char**) apr_array_push(result) = CJOSE_HDR_ALG_NONE;
return result;
}
/*
* check if the provided signing algorithm is supported
*/
apr_byte_t oidc_jose_jws_algorithm_is_supported(apr_pool_t *pool,
const char *alg) {
return oidc_jose_array_has_string(oidc_jose_jws_supported_algorithms(pool),
alg);
}
/*
* return all supported content encryption key algorithms
*/
apr_array_header_t *oidc_jose_jwe_supported_algorithms(apr_pool_t *pool) {
apr_array_header_t *result = apr_array_make(pool, 4, sizeof(const char*));
*(const char**) apr_array_push(result) = CJOSE_HDR_ALG_RSA1_5;
*(const char**) apr_array_push(result) = CJOSE_HDR_ALG_A128KW;
*(const char**) apr_array_push(result) = CJOSE_HDR_ALG_A192KW;
*(const char**) apr_array_push(result) = CJOSE_HDR_ALG_A256KW;
*(const char**) apr_array_push(result) = CJOSE_HDR_ALG_RSA_OAEP;
return result;
}
/*
* check if the provided content encryption key algorithm is supported
*/
apr_byte_t oidc_jose_jwe_algorithm_is_supported(apr_pool_t *pool,
const char *alg) {
return oidc_jose_array_has_string(oidc_jose_jwe_supported_algorithms(pool),
alg);
}
/*
* return all supported encryption algorithms
*/
apr_array_header_t *oidc_jose_jwe_supported_encryptions(apr_pool_t *pool) {
apr_array_header_t *result = apr_array_make(pool, 5, sizeof(const char*));
*(const char**) apr_array_push(result) = CJOSE_HDR_ENC_A128CBC_HS256;
*(const char**) apr_array_push(result) = CJOSE_HDR_ENC_A192CBC_HS384;
*(const char**) apr_array_push(result) = CJOSE_HDR_ENC_A256CBC_HS512;
#if (OIDC_JOSE_GCM_SUPPORT)
*(const char**) apr_array_push(result) = CJOSE_HDR_ENC_A256GCM;
#endif
return result;
}
/*
* check if the provided encryption algorithm is supported
*/
apr_byte_t oidc_jose_jwe_encryption_is_supported(apr_pool_t *pool,
const char *enc) {
return oidc_jose_array_has_string(oidc_jose_jwe_supported_encryptions(pool),
enc);
}
/*
* get (optional) string from JWT
*/
apr_byte_t oidc_jose_get_string(apr_pool_t *pool, json_t *json,
const char *claim_name, apr_byte_t is_mandatory, char **result,
oidc_jose_error_t *err) {
json_t *v = json_object_get(json, claim_name);
if (v != NULL) {
if (json_is_string(v)) {
*result = apr_pstrdup(pool, json_string_value(v));
} else if (is_mandatory) {
oidc_jose_error(err,
"mandatory JSON key \"%s\" was found but the type is not a string",
claim_name);
return FALSE;
}
} else if (is_mandatory) {
oidc_jose_error(err, "mandatory JSON key \"%s\" could not be found",
claim_name);
return FALSE;
}
return TRUE;
}
/*
* parse (optional) timestamp from payload
*/
static apr_byte_t oidc_jose_get_timestamp(apr_pool_t *pool, json_t *json,
const char *claim_name, apr_byte_t is_mandatory, double *result,
oidc_jose_error_t *err) {
*result = OIDC_JWT_CLAIM_TIME_EMPTY;
json_t *v = json_object_get(json, claim_name);
if (v != NULL) {
if (json_is_number(v)) {
*result = json_number_value(v);
} else if (is_mandatory) {
oidc_jose_error(err,
"mandatory JSON key \"%s\" was found but the type is not a number",
claim_name);
return FALSE;
}
} else if (is_mandatory) {
oidc_jose_error(err, "mandatory JSON key \"%s\" could not be found",
claim_name);
return FALSE;
}
return TRUE;
}
#define OIDC_JOSE_JWT_ISS "iss"
#define OIDC_JOSE_JWT_SUB "sub"
#define OIDC_JOSE_JWT_EXP "exp"
#define OIDC_JOSE_JWT_IAT "iat"
/*
* parse JWT payload
*/
static apr_byte_t oidc_jose_parse_payload(apr_pool_t *pool,
const char *s_payload, size_t s_payload_len,
oidc_jwt_payload_t *payload, oidc_jose_error_t *err) {
/* decode the string in to a JSON structure into value->json */
json_error_t json_error;
payload->value.str = apr_pstrndup(pool, s_payload, s_payload_len);
payload->value.json = json_loads(payload->value.str, 0, &json_error);
/* check that we've actually got a JSON value back */
if (payload->value.json == NULL) {
oidc_jose_error(err, "JSON parsing (json_loads) failed: %s (%s)",
json_error.text, s_payload);
return FALSE;
}
/* check that the value is a JSON object */
if (!json_is_object(payload->value.json)) {
oidc_jose_error(err, "JSON value is not an object");
return FALSE;
}
/* get the (optional) "iss" value from the JSON payload */
oidc_jose_get_string(pool, payload->value.json, OIDC_JOSE_JWT_ISS, FALSE,
&payload->iss,
NULL);
/* get the (optional) "exp" value from the JSON payload */
oidc_jose_get_timestamp(pool, payload->value.json, OIDC_JOSE_JWT_EXP, FALSE,
&payload->exp,
NULL);
/* get the (optional) "iat" value from the JSON payload */
oidc_jose_get_timestamp(pool, payload->value.json, OIDC_JOSE_JWT_IAT, FALSE,
&payload->iat,
NULL);
/* get the (optional) "sub" value from the JSON payload */
oidc_jose_get_string(pool, payload->value.json, OIDC_JOSE_JWT_SUB, FALSE,
&payload->sub,
NULL);
return TRUE;
}
/*
* decrypt a JWT and return the plaintext
*/
static uint8_t *oidc_jwe_decrypt_impl(apr_pool_t *pool, cjose_jwe_t *jwe,
apr_hash_t *keys, size_t *content_len, oidc_jose_error_t *err) {
uint8_t *decrypted = NULL;
oidc_jwk_t *jwk = NULL;
apr_hash_index_t *hi;
cjose_err cjose_err;
cjose_header_t *hdr = cjose_jwe_get_protected(jwe);
const char *kid = cjose_header_get(hdr, CJOSE_HDR_KID, &cjose_err);
const char *alg = cjose_header_get(hdr, CJOSE_HDR_ALG, &cjose_err);
if (kid != NULL) {
jwk = apr_hash_get(keys, kid, APR_HASH_KEY_STRING);
if (jwk != NULL) {
decrypted = cjose_jwe_decrypt(jwe, jwk->cjose_jwk, content_len,
&cjose_err);
if (decrypted == NULL)
oidc_jose_error(err,
"encrypted JWT could not be decrypted with kid %s: %s",
kid, oidc_cjose_e2s(pool, cjose_err));
} else {
oidc_jose_error(err, "could not find key with kid: %s", kid);
}
} else {
for (hi = apr_hash_first(pool, keys); hi; hi = apr_hash_next(hi)) {
apr_hash_this(hi, NULL, NULL, (void **) &jwk);
if (jwk->kty == oidc_alg2kty(alg)) {
decrypted = cjose_jwe_decrypt(jwe, jwk->cjose_jwk, content_len,
&cjose_err);
if (decrypted != NULL)
break;
}
}
if (decrypted == NULL)
oidc_jose_error(err,
"encrypted JWT could not be decrypted with any of the %d keys: error for last tried key is: %s",
apr_hash_count(keys), oidc_cjose_e2s(pool, cjose_err));
}
return decrypted;
}
/*
* decrypt a JSON Web Token
*/
apr_byte_t oidc_jwe_decrypt(apr_pool_t *pool, const char *input_json,
apr_hash_t *keys, char **s_json, oidc_jose_error_t *err,
apr_byte_t import_must_succeed) {
cjose_err cjose_err;
cjose_jwe_t *jwe = cjose_jwe_import(input_json, strlen(input_json),
&cjose_err);
if (jwe != NULL) {
size_t content_len = 0;
uint8_t *decrypted = oidc_jwe_decrypt_impl(pool, jwe, keys,
&content_len, err);
if (decrypted != NULL) {
decrypted[content_len] = '\0';
*s_json = apr_pstrdup(pool, (const char *) decrypted);
cjose_get_dealloc()(decrypted);
}
cjose_jwe_release(jwe);
} else if (import_must_succeed == FALSE) {
*s_json = apr_pstrdup(pool, input_json);
} else {
oidc_jose_error(err, "cjose_jwe_import failed: %s",
oidc_cjose_e2s(pool, cjose_err));
}
return (*s_json != NULL);
}
/*
* parse and (optionally) decrypt a JSON Web Token
*/
apr_byte_t oidc_jwt_parse(apr_pool_t *pool, const char *input_json,
oidc_jwt_t **j_jwt, apr_hash_t *keys, oidc_jose_error_t *err) {
cjose_err cjose_err;
char *s_json = NULL;
if (oidc_jwe_decrypt(pool, input_json, keys, &s_json, err, FALSE) == FALSE)
return FALSE;
*j_jwt = oidc_jwt_new(pool, FALSE, FALSE);
if (*j_jwt == NULL)
return FALSE;
oidc_jwt_t *jwt = *j_jwt;
jwt->cjose_jws = cjose_jws_import(s_json, strlen(s_json), &cjose_err);
if (jwt->cjose_jws == NULL) {
oidc_jose_error(err, "cjose_jws_import failed: %s",
oidc_cjose_e2s(pool, cjose_err));
oidc_jwt_destroy(jwt);
*j_jwt = NULL;
return FALSE;
}
cjose_header_t *hdr = cjose_jws_get_protected(jwt->cjose_jws);
jwt->header.value.json = json_deep_copy((json_t *) hdr);
char *str = json_dumps(jwt->header.value.json,
JSON_PRESERVE_ORDER | JSON_COMPACT);
jwt->header.value.str = apr_pstrdup(pool, str);
free(str);
jwt->header.alg = apr_pstrdup(pool,
cjose_header_get(hdr, CJOSE_HDR_ALG, &cjose_err));
jwt->header.enc = apr_pstrdup(pool,
cjose_header_get(hdr, CJOSE_HDR_ENC, &cjose_err));
jwt->header.kid = apr_pstrdup(pool,
cjose_header_get(hdr, CJOSE_HDR_KID, &cjose_err));
uint8_t *plaintext = NULL;
size_t plaintext_len = 0;
if (cjose_jws_get_plaintext(jwt->cjose_jws, &plaintext, &plaintext_len,
&cjose_err) == FALSE) {
oidc_jose_error(err, "cjose_jws_get_plaintext failed: %s",
oidc_cjose_e2s(pool, cjose_err));
return FALSE;
}
if (oidc_jose_parse_payload(pool, (const char *) plaintext, plaintext_len,
&jwt->payload, err) == FALSE) {
oidc_jwt_destroy(jwt);
*j_jwt = NULL;
}
return TRUE;
}
/* destroy resources allocated for JWT */
void oidc_jwt_destroy(oidc_jwt_t *jwt) {
if (jwt) {
if (jwt->header.value.json) {
json_decref(jwt->header.value.json);
jwt->header.value.json = NULL;
jwt->header.value.str = NULL;
}
if (jwt->payload.value.json) {
json_decref(jwt->payload.value.json);
jwt->payload.value.json = NULL;
jwt->payload.value.str = NULL;
}
if (jwt->cjose_jws) {
cjose_jws_release(jwt->cjose_jws);
jwt->cjose_jws = NULL;
}
}
}
/*
* sign JWT
*/
apr_byte_t oidc_jwt_sign(apr_pool_t *pool, oidc_jwt_t *jwt, oidc_jwk_t *jwk,
oidc_jose_error_t *err) {
cjose_header_t *hdr = (cjose_header_t *) jwt->header.value.json;
if (jwt->header.alg)
oidc_jwt_hdr_set(jwt, CJOSE_HDR_ALG, jwt->header.alg);
if (jwt->header.kid)
oidc_jwt_hdr_set(jwt, CJOSE_HDR_KID, jwt->header.kid);
if (jwt->header.enc)
oidc_jwt_hdr_set(jwt, CJOSE_HDR_ENC, jwt->header.enc);
if (jwt->cjose_jws)
cjose_jws_release(jwt->cjose_jws);
cjose_err cjose_err;
char *s_payload = json_dumps(jwt->payload.value.json,
JSON_PRESERVE_ORDER | JSON_COMPACT);
jwt->payload.value.str = apr_pstrdup(pool, s_payload);
jwt->cjose_jws = cjose_jws_sign(jwk->cjose_jwk, hdr,
(const uint8_t *) s_payload, strlen(s_payload), &cjose_err);
free(s_payload);
if (jwt->cjose_jws == NULL) {
oidc_jose_error(err, "cjose_jws_sign failed: %s",
oidc_cjose_e2s(pool, cjose_err));
return FALSE;
}
return TRUE;
}
#if (OPENSSL_VERSION_NUMBER < 0x10100000)
EVP_MD_CTX * EVP_MD_CTX_new() {
return malloc(sizeof(EVP_MD_CTX));
}
void EVP_MD_CTX_free(EVP_MD_CTX *ctx) {
if (ctx)
free(ctx);
}
#endif
/*
* encrypt JWT
*/
apr_byte_t oidc_jwt_encrypt(apr_pool_t *pool, oidc_jwt_t *jwe, oidc_jwk_t *jwk,
const char *payload, char **serialized, oidc_jose_error_t *err) {
cjose_header_t *hdr = (cjose_header_t *) jwe->header.value.json;
if (jwe->header.alg)
oidc_jwt_hdr_set(jwe, CJOSE_HDR_ALG, jwe->header.alg);
if (jwe->header.kid)
oidc_jwt_hdr_set(jwe, CJOSE_HDR_KID, jwe->header.kid);
if (jwe->header.enc)
oidc_jwt_hdr_set(jwe, CJOSE_HDR_ENC, jwe->header.enc);
cjose_err cjose_err;
cjose_jwe_t *cjose_jwe = cjose_jwe_encrypt(jwk->cjose_jwk, hdr,
(const uint8_t *) payload, strlen(payload), &cjose_err);
if (cjose_jwe == NULL) {
oidc_jose_error(err, "cjose_jwe_encrypt failed: %s",
oidc_cjose_e2s(pool, cjose_err));
return FALSE;
}
char *cser = cjose_jwe_export(cjose_jwe, &cjose_err);
if (cser == NULL) {
oidc_jose_error(err, "cjose_jwe_export failed: %s",
oidc_cjose_e2s(pool, cjose_err));
return FALSE;
}
*serialized = apr_pstrdup(pool, cser);
cjose_get_dealloc()(cser);
cjose_jwe_release(cjose_jwe);
return TRUE;
}
#define OIDC_JOSE_CJOSE_VERSION_DEPRECATED "0.4."
/*
* check for a version of cjose < 0.5.0 that has a version of
* cjose_jws_verify that resources after a verification failure
*/
apr_byte_t oidc_jose_version_deprecated(apr_pool_t *pool) {
char *version = apr_pstrdup(pool, cjose_version());
return (strstr(version, OIDC_JOSE_CJOSE_VERSION_DEPRECATED) == version);
}
/*
* verify the signature on a JWT
*/
apr_byte_t oidc_jwt_verify(apr_pool_t *pool, oidc_jwt_t *jwt, apr_hash_t *keys,
oidc_jose_error_t *err) {
apr_byte_t rc = FALSE;
oidc_jwk_t *jwk = NULL;
apr_hash_index_t *hi;
cjose_err cjose_err;
if (jwt->header.kid != NULL) {
jwk = apr_hash_get(keys, jwt->header.kid, APR_HASH_KEY_STRING);
if (jwk != NULL) {
rc = cjose_jws_verify(jwt->cjose_jws, jwk->cjose_jwk, &cjose_err);
if (rc == FALSE) {
oidc_jose_error(err, "cjose_jws_verify failed: %s",
oidc_cjose_e2s(pool, cjose_err));
if (oidc_jose_version_deprecated(pool))
jwt->cjose_jws = NULL;
}
} else {
oidc_jose_error(err, "could not find key with kid: %s",
jwt->header.kid);
rc = FALSE;
}
} else {
for (hi = apr_hash_first(pool, keys); hi; hi = apr_hash_next(hi)) {
apr_hash_this(hi, NULL, NULL, (void **) &jwk);
if (jwk->kty == oidc_jwt_alg2kty(jwt)) {
rc = cjose_jws_verify(jwt->cjose_jws, jwk->cjose_jwk,
&cjose_err);
if (rc == FALSE) {
oidc_jose_error(err, "cjose_jws_verify failed: %s",
oidc_cjose_e2s(pool, cjose_err));
if (oidc_jose_version_deprecated(pool))
jwt->cjose_jws = NULL;
}
}
if ((rc == TRUE) || (jwt->cjose_jws == NULL))
break;
}
if (rc == FALSE)
oidc_jose_error(err,
"could not verify signature against any of the (%d) provided keys%s",
apr_hash_count(keys),
apr_hash_count(keys) > 0 ?
"" :
apr_psprintf(pool,
"; you have probably provided no or incorrect keys/key-types for algorithm: %s",
jwt->header.alg));
}
return rc;
}
/*
* hash a byte sequence with the specified algorithm
*/
apr_byte_t oidc_jose_hash_bytes(apr_pool_t *pool, const char *s_digest,
const unsigned char *input, unsigned int input_len,
unsigned char **output, unsigned int *output_len,
oidc_jose_error_t *err) {
unsigned char md_value[EVP_MAX_MD_SIZE];
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
EVP_MD_CTX_init(ctx);
const EVP_MD *evp_digest = NULL;
if ((evp_digest = EVP_get_digestbyname(s_digest)) == NULL) {
oidc_jose_error(err,
"no OpenSSL digest algorithm found for algorithm \"%s\"",
s_digest);
return FALSE;
}
if (!EVP_DigestInit_ex(ctx, evp_digest, NULL)) {
oidc_jose_error_openssl(err, "EVP_DigestInit_ex");
return FALSE;
}
if (!EVP_DigestUpdate(ctx, input, input_len)) {
oidc_jose_error_openssl(err, "EVP_DigestUpdate");
return FALSE;
}
if (!EVP_DigestFinal(ctx, md_value, output_len)) {
oidc_jose_error_openssl(err, "EVP_DigestFinal");
return FALSE;
}
EVP_MD_CTX_free(ctx);
*output = apr_pcalloc(pool, *output_len);
memcpy(*output, md_value, *output_len);
return TRUE;
}
/*
* return the OpenSSL hash algorithm associated with a specified JWT algorithm
*/
static char *oidc_jose_alg_to_openssl_digest(const char *alg) {
if ((strcmp(alg, CJOSE_HDR_ALG_RS256) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_PS256) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_HS256) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_ES256) == 0)) {
return LN_sha256;
}
if ((strcmp(alg, CJOSE_HDR_ALG_RS384) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_PS384) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_HS384) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_ES384) == 0)) {
return LN_sha384;
}
if ((strcmp(alg, CJOSE_HDR_ALG_RS512) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_PS512) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_HS512) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_ES512) == 0)) {
return LN_sha512;
}
return NULL;
}
/*
* hash a string value with the specified algorithm
*/
apr_byte_t oidc_jose_hash_string(apr_pool_t *pool, const char *alg,
const char *msg, char **hash, unsigned int *hash_len,
oidc_jose_error_t *err) {
char *s_digest = oidc_jose_alg_to_openssl_digest(alg);
if (s_digest == NULL) {
oidc_jose_error(err,
"no OpenSSL digest algorithm name found for algorithm \"%s\"",
alg);
return FALSE;
}
return oidc_jose_hash_bytes(pool, s_digest, (const unsigned char *) msg,
strlen(msg), (unsigned char **) hash, hash_len, err);
}
/*
* return hash length
*/
int oidc_jose_hash_length(const char *alg) {
if ((strcmp(alg, CJOSE_HDR_ALG_RS256) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_PS256) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_HS256) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_ES256) == 0)) {
return 32;
}
if ((strcmp(alg, CJOSE_HDR_ALG_RS384) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_PS384) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_HS384) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_ES384) == 0)) {
return 48;
}
if ((strcmp(alg, CJOSE_HDR_ALG_RS512) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_PS512) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_HS512) == 0)
|| (strcmp(alg, CJOSE_HDR_ALG_ES512) == 0)) {
return 64;
}
return 0;
}
/*
* convert the RSA public key in the X.509 certificate in the BIO pointed to
* by "input" to a JSON Web Key object
*/
static apr_byte_t oidc_jwk_rsa_bio_to_jwk(apr_pool_t *pool, BIO *input,
const char *kid, cjose_jwk_t **jwk, int is_private_key,
oidc_jose_error_t *err) {
X509 *x509 = NULL;
EVP_PKEY *pkey = NULL;
apr_byte_t rv = FALSE;
cjose_jwk_rsa_keyspec key_spec;
memset(&key_spec, 0, sizeof(cjose_jwk_rsa_keyspec));
if (is_private_key) {
/* get the private key struct from the BIO */
if ((pkey = PEM_read_bio_PrivateKey(input, NULL, NULL, NULL)) == NULL) {
oidc_jose_error_openssl(err, "PEM_read_bio_PrivateKey");
goto end;
}
} else {
/* read the X.509 struct */
if ((x509 = PEM_read_bio_X509_AUX(input, NULL, NULL, NULL)) == NULL) {
oidc_jose_error_openssl(err, "PEM_read_bio_X509_AUX");
goto end;
}
/* get the public key struct from the X.509 struct */
if ((pkey = X509_get_pubkey(x509)) == NULL) {
oidc_jose_error_openssl(err, "X509_get_pubkey");
goto end;
}
}
/* get the RSA key from the public key struct */
RSA *rsa = EVP_PKEY_get1_RSA(pkey);
if (rsa == NULL) {
oidc_jose_error_openssl(err, "EVP_PKEY_get1_RSA");
goto end;
}
const BIGNUM *rsa_n, *rsa_e, *rsa_d;
#if OPENSSL_VERSION_NUMBER >= 0x10100005L && !defined (LIBRESSL_VERSION_NUMBER)
RSA_get0_key(rsa, &rsa_n, &rsa_e, &rsa_d);
#else
rsa_n = rsa->n;
rsa_e = rsa->e;
rsa_d = rsa->d;
#endif
RSA_free(rsa);
/* convert the modulus bignum in to a key/len */
key_spec.nlen = BN_num_bytes(rsa_n);
key_spec.n = apr_pcalloc(pool, key_spec.nlen);
BN_bn2bin(rsa_n, key_spec.n);
/* convert the exponent bignum in to a key/len */
key_spec.elen = BN_num_bytes(rsa_e);
key_spec.e = apr_pcalloc(pool, key_spec.elen);
BN_bn2bin(rsa_e, key_spec.e);
/* convert the private exponent bignum in to a key/len */
if (rsa_d != NULL) {
key_spec.dlen = BN_num_bytes(rsa_d);
key_spec.d = apr_pcalloc(pool, key_spec.dlen);
BN_bn2bin(rsa_d, key_spec.d);
}
cjose_err cjose_err;
*jwk = cjose_jwk_create_RSA_spec(&key_spec, &cjose_err);
if (*jwk == NULL) {
oidc_jose_error(err, "cjose_jwk_create_RSA_spec failed: %s",
oidc_cjose_e2s(pool, cjose_err));
goto end;
}
char *fingerprint = apr_pcalloc(pool, key_spec.nlen + key_spec.elen);
memcpy(fingerprint, key_spec.n, key_spec.nlen);
memcpy(fingerprint + key_spec.nlen, key_spec.e, key_spec.elen);
if (oidc_jwk_set_or_generate_kid(pool, *jwk, kid, fingerprint,
key_spec.nlen + key_spec.elen, err) == FALSE) {
goto end;
}
rv = TRUE;
end:
if (pkey)
EVP_PKEY_free(pkey);
if (x509)
X509_free(x509);
return rv;
}
/*
* parse an RSA public or private key from the specified file
*/
static apr_byte_t oidc_jwk_parse_rsa_key(apr_pool_t *pool, int is_private_key,
const char *kid, const char *filename, oidc_jwk_t **jwk,
oidc_jose_error_t *err) {
BIO *input = NULL;
apr_byte_t rv = FALSE;
if ((input = BIO_new(BIO_s_file())) == NULL) {
oidc_jose_error_openssl(err, "BIO_new/BIO_s_file");
goto end;
}
if (BIO_read_filename(input, filename) <= 0) {
oidc_jose_error_openssl(err, "BIO_read_filename");
goto end;
}
cjose_jwk_t *cjose_jwk = NULL;
if (oidc_jwk_rsa_bio_to_jwk(pool, input, kid, &cjose_jwk, is_private_key,
err) == FALSE)
goto end;
*jwk = oidc_jwk_from_cjose(pool, cjose_jwk);
rv = TRUE;
end:
if (input)
BIO_free(input);
return rv;
}
#define OIDC_JOSE_CERT_BEGIN "-----BEGIN CERTIFICATE-----"
#define OIDC_JOSE_CERT_END "-----END CERTIFICATE-----"
/*
* parse an RSA key from a JSON object in to a cjose JWK object
*/
static apr_byte_t oidc_jwk_parse_rsa_x5c(apr_pool_t *pool, json_t *json,
cjose_jwk_t **jwk, oidc_jose_error_t *err) {
apr_byte_t rv = FALSE;
/* get the "x5c" array element from the JSON object */
json_t *v = json_object_get(json, OIDC_JOSE_HDR_X5C);
if (v == NULL) {
oidc_jose_error(err, "JSON key \"%s\" could not be found",
OIDC_JOSE_HDR_X5C);
return FALSE;
}
if (!json_is_array(v)) {
oidc_jose_error(err,
"JSON key \"%s\" was found but its value is not a JSON array",
OIDC_JOSE_HDR_X5C);
return FALSE;
}
/* take the first element of the array */
v = json_array_get(v, 0);
if (v == NULL) {
oidc_jose_error(err, "first element in JSON array is \"null\"");
return FALSE;
}
if (!json_is_string(v)) {
oidc_jose_error(err, "first element in array is not a JSON string");
return FALSE;
}
const char *s_x5c = json_string_value(v);
/* PEM-format it */
const int len = 75;
int i = 0;
char *s = apr_psprintf(pool, "%s\n", OIDC_JOSE_CERT_BEGIN);
while (i < strlen(s_x5c)) {
s = apr_psprintf(pool, "%s%s\n", s, apr_pstrmemdup(pool, s_x5c + i, len));
i += len;
}
s = apr_psprintf(pool, "%s%s\n", s, OIDC_JOSE_CERT_END);
BIO *input = NULL;
/* put it in BIO memory */
if ((input = BIO_new(BIO_s_mem())) == NULL) {
oidc_jose_error_openssl(err, "memory allocation BIO_new/BIO_s_mem");
return FALSE;
}
if (BIO_puts(input, s) <= 0) {
BIO_free(input);
oidc_jose_error_openssl(err, "BIO_puts");
return FALSE;
}
/* do the actual parsing */
rv = oidc_jwk_rsa_bio_to_jwk(pool, input, NULL, jwk, FALSE, err);
BIO_free(input);
return rv;
}
/*
* parse an X.509 PEM formatted certificate file with an RSA public key to a JWK struct
*/
apr_byte_t oidc_jwk_parse_rsa_private_key(apr_pool_t *pool, const char *kid,
const char *filename, oidc_jwk_t **jwk, oidc_jose_error_t *err) {
return oidc_jwk_parse_rsa_key(pool, TRUE, kid, filename, jwk, err);
}
/*
* parse an X.509 PEM formatted RSA private key file to a JWK
*/
apr_byte_t oidc_jwk_parse_rsa_public_key(apr_pool_t *pool, const char *kid,
const char *filename, oidc_jwk_t **jwk, oidc_jose_error_t *err) {
return oidc_jwk_parse_rsa_key(pool, FALSE, kid, filename, jwk, err);
}
mod_auth_openidc-2.3.3/src/parse.c 0000644 0000765 0000024 00000107012 13203320522 016714 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Validation and parsing of configuration values.
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*/
#include
#include "mod_auth_openidc.h"
#include "parse.h"
#include "jose.h"
/*
* parse a URL according to one of two schemes (NULL for any)
*/
static const char * oidc_valid_url_scheme(apr_pool_t *pool, const char *arg,
const char *scheme1, const char *scheme2) {
apr_uri_t uri;
if (apr_uri_parse(pool, arg, &uri) != APR_SUCCESS) {
return apr_psprintf(pool, "'%s' cannot be parsed as a URL", arg);
}
if (uri.scheme == NULL) {
return apr_psprintf(pool,
"'%s' cannot be parsed as a URL (no scheme set)", arg);
}
if ((scheme1 != NULL) && (apr_strnatcmp(uri.scheme, scheme1) != 0)) {
if ((scheme2 != NULL) && (apr_strnatcmp(uri.scheme, scheme2) != 0)) {
return apr_psprintf(pool,
"'%s' cannot be parsed as a \"%s\" or \"%s\" URL (scheme == %s)!",
arg, scheme1, scheme2, uri.scheme);
} else if (scheme2 == NULL) {
return apr_psprintf(pool,
"'%s' cannot be parsed as a \"%s\" URL (scheme == %s)!",
arg, scheme1, uri.scheme);
}
}
if (uri.hostname == NULL) {
return apr_psprintf(pool,
"'%s' cannot be parsed as a valid URL (no hostname set, check your slashes)",
arg);
}
return NULL;
}
/*
* parse a URL according to a scheme
*/
const char *oidc_valid_url(apr_pool_t *pool, const char *arg,
const char *scheme) {
return oidc_valid_url_scheme(pool, arg, scheme, NULL);
}
/*
* parse a URL that should conform to any HTTP scheme (http/https)
*/
const char *oidc_valid_http_url(apr_pool_t *pool, const char *arg) {
return oidc_valid_url_scheme(pool, arg, "https", "http");
}
#define STR_ERROR_MAX 128
/*
* check if arg is a valid directory on the file system
*/
const char *oidc_valid_dir(apr_pool_t *pool, const char *arg) {
char s_err[STR_ERROR_MAX];
apr_dir_t *dir = NULL;
apr_status_t rc = APR_SUCCESS;
/* ensure the directory exists */
if ((rc = apr_dir_open(&dir, arg, pool)) != APR_SUCCESS) {
return apr_psprintf(pool, "cannot access directory '%s' (%s)", arg,
apr_strerror(rc, s_err, STR_ERROR_MAX));
}
/* and cleanup... */
if ((rc = apr_dir_close(dir)) != APR_SUCCESS) {
return apr_psprintf(pool, "cannot close directory '%s' (%s)", arg,
apr_strerror(rc, s_err, STR_ERROR_MAX));
}
return NULL;
}
/*
* check if arg is a valid cookie domain value
*/
const char *oidc_valid_cookie_domain(apr_pool_t *pool, const char *arg) {
size_t sz, limit;
char d;
limit = strlen(arg);
for (sz = 0; sz < limit; sz++) {
d = arg[sz];
if ((d < '0' || d > '9') && (d < 'a' || d > 'z') && (d < 'A' || d > 'Z')
&& d != '.' && d != '-') {
return (apr_psprintf(pool,
"invalid character '%c' in cookie domain value: %s", d, arg));
}
}
return NULL;
}
/*
* parse an integer value from a string
*/
const char *oidc_parse_int(apr_pool_t *pool, const char *arg, int *int_value) {
char *endptr;
int v = strtol(arg, &endptr, 10);
if ((*arg == '\0') || (*endptr != '\0')) {
return apr_psprintf(pool, "invalid integer value: %s", arg);
}
*int_value = v;
return NULL;
}
/*
* check if the provided integer value is between a specified minimum and maximum
*/
static const char *oidc_valid_int_min_max(apr_pool_t *pool, int value,
int min_value, int max_value) {
if (value < min_value) {
return apr_psprintf(pool,
"integer value %d is smaller than the minimum allowed value %d",
value, min_value);
}
if (value > max_value) {
return apr_psprintf(pool,
"integer value %d is greater than the maximum allowed value %d",
value, max_value);
}
return NULL;
}
/*
* parse an integer and check validity
*/
static const char *oidc_parse_int_valid(apr_pool_t *pool, const char *arg,
int *int_value, oidc_valid_int_function_t valid_int_function) {
int v = 0;
const char *rv = NULL;
rv = oidc_parse_int(pool, arg, &v);
if (rv != NULL)
return rv;
rv = valid_int_function(pool, v);
if (rv != NULL)
return rv;
*int_value = v;
return NULL;
}
/*
* parse an integer value from a string that must be between a specified minimum and maximum
*/
static const char *oidc_parse_int_min_max(apr_pool_t *pool, const char *arg,
int *int_value, int min_value, int max_value) {
int v = 0;
const char *rv = NULL;
rv = oidc_parse_int(pool, arg, &v);
if (rv != NULL)
return rv;
rv = oidc_valid_int_min_max(pool, v, min_value, max_value);
if (rv != NULL)
return rv;
*int_value = v;
return NULL;
}
#define OIDC_LIST_OPTIONS_START "["
#define OIDC_LIST_OPTIONS_END "]"
#define OIDC_LIST_OPTIONS_SEPARATOR "|"
#define OIDC_LIST_OPTIONS_QUOTE "'"
/*
* flatten the list of string options, separated by the specified separator char
*/
static char *oidc_flatten_list_options(apr_pool_t *pool, char *options[]) {
int i = 0;
char *result = OIDC_LIST_OPTIONS_START;
while (options[i] != NULL) {
if (i == 0)
result = apr_psprintf(pool, "%s%s%s%s", OIDC_LIST_OPTIONS_START,
OIDC_LIST_OPTIONS_QUOTE, options[i],
OIDC_LIST_OPTIONS_QUOTE);
else
result = apr_psprintf(pool, "%s%s%s%s%s", result,
OIDC_LIST_OPTIONS_SEPARATOR, OIDC_LIST_OPTIONS_QUOTE, options[i],
OIDC_LIST_OPTIONS_QUOTE);
i++;
}
result = apr_psprintf(pool, "%s%s", result, OIDC_LIST_OPTIONS_END);
return result;
}
/*
* check if arg is a valid option in the list of provided string options
*/
static const char *oidc_valid_string_option(apr_pool_t *pool, const char *arg,
char *options[]) {
int i = 0;
while (options[i] != NULL) {
if (apr_strnatcmp(arg, options[i]) == 0)
break;
i++;
}
if (options[i] == NULL) {
return apr_psprintf(pool, "invalid value %s%s%s, must be one of %s",
OIDC_LIST_OPTIONS_QUOTE, arg, OIDC_LIST_OPTIONS_QUOTE,
oidc_flatten_list_options(pool, options));
}
return NULL;
}
#define OIDC_CACHE_TYPE_SHM "shm"
#define OIDC_CACHE_TYPE_MEMCACHE "memcache"
#define OIDC_CACHE_TYPE_REDIS "redis"
#define OIDC_CACHE_TYPE_FILE "file"
/*
* parse the cache backend type
*/
const char *oidc_parse_cache_type(apr_pool_t *pool, const char *arg,
oidc_cache_t **type) {
static char *options[] = {
OIDC_CACHE_TYPE_SHM,
OIDC_CACHE_TYPE_MEMCACHE,
#ifdef USE_LIBHIREDIS
OIDC_CACHE_TYPE_REDIS,
#endif
OIDC_CACHE_TYPE_FILE,
NULL };
const char *rv = oidc_valid_string_option(pool, arg, options);
if (rv != NULL)
return rv;
if (apr_strnatcmp(arg, OIDC_CACHE_TYPE_SHM) == 0) {
*type = &oidc_cache_shm;
} else if (apr_strnatcmp(arg, OIDC_CACHE_TYPE_MEMCACHE) == 0) {
*type = &oidc_cache_memcache;
} else if (apr_strnatcmp(arg, OIDC_CACHE_TYPE_FILE) == 0) {
*type = &oidc_cache_file;
#ifdef USE_LIBHIREDIS
} else if (apr_strnatcmp(arg, OIDC_CACHE_TYPE_REDIS) == 0) {
*type = &oidc_cache_redis;
#endif
}
return NULL;
}
#define OIDC_SESSION_TYPE_SERVER_CACHE_STR "server-cache"
#define OIDC_SESSION_TYPE_CLIENT_COOKIE_STR "client-cookie"
#define OIDC_SESSION_TYPE_PERSISTENT "persistent"
#define OIDC_SESSION_TYPE_SEPARATOR ":"
/*
* parse the session mechanism type and the cookie persistency property
*/
const char *oidc_parse_session_type(apr_pool_t *pool, const char *arg,
int *type, int *persistent) {
static char *options[] =
{
OIDC_SESSION_TYPE_SERVER_CACHE_STR,
OIDC_SESSION_TYPE_SERVER_CACHE_STR OIDC_SESSION_TYPE_SEPARATOR OIDC_SESSION_TYPE_PERSISTENT,
OIDC_SESSION_TYPE_CLIENT_COOKIE_STR,
OIDC_SESSION_TYPE_CLIENT_COOKIE_STR OIDC_SESSION_TYPE_SEPARATOR OIDC_SESSION_TYPE_PERSISTENT,
NULL };
const char *rv = oidc_valid_string_option(pool, arg, options);
if (rv != NULL)
return rv;
char *s = apr_pstrdup(pool, arg);
char *p = strstr(s, OIDC_SESSION_TYPE_SEPARATOR);
if (p) {
*persistent = 1;
*p = '\0';
}
if (apr_strnatcmp(s, OIDC_SESSION_TYPE_SERVER_CACHE_STR) == 0) {
*type = OIDC_SESSION_TYPE_SERVER_CACHE;
} else if (apr_strnatcmp(s, OIDC_SESSION_TYPE_CLIENT_COOKIE_STR) == 0) {
*type = OIDC_SESSION_TYPE_CLIENT_COOKIE;
}
return NULL;
}
/* minimum size of a SHM cache entry */
#define OIDC_MINIMUM_CACHE_SHM_ENTRY_SIZE_MAX 8192 + 512 + 17 // 8Kb plus overhead
/* maximum size of a SHM cache entry */
#define OIDC_MAXIMUM_CACHE_SHM_ENTRY_SIZE_MAX 1024 * 512 // 512Kb incl. overhead
/*
* parse the slot size of a SHM cache entry
*/
const char *oidc_parse_cache_shm_entry_size_max(apr_pool_t *pool,
const char *arg, int *int_value) {
return oidc_parse_int_min_max(pool, arg, int_value,
OIDC_MINIMUM_CACHE_SHM_ENTRY_SIZE_MAX,
OIDC_MAXIMUM_CACHE_SHM_ENTRY_SIZE_MAX);
}
/*
* parse a boolean value from a provided string
*/
const char *oidc_parse_boolean(apr_pool_t *pool, const char *arg,
int *bool_value) {
if ((apr_strnatcasecmp(arg, "true") == 0)
|| (apr_strnatcasecmp(arg, "on") == 0)
|| (apr_strnatcasecmp(arg, "yes") == 0)
|| (apr_strnatcasecmp(arg, "1") == 0)) {
*bool_value = TRUE;
return NULL;
}
if ((apr_strnatcasecmp(arg, "false") == 0)
|| (apr_strnatcasecmp(arg, "off") == 0)
|| (apr_strnatcasecmp(arg, "no") == 0)
|| (apr_strnatcasecmp(arg, "0") == 0)) {
*bool_value = FALSE;
return NULL;
}
return apr_psprintf(pool,
"oidc_parse_boolean: could not parse boolean value from \"%s\"",
arg);
}
#define OIDC_ENDPOINT_AUTH_CLIENT_SECRET_POST "client_secret_post"
#define OIDC_ENDPOINT_AUTH_CLIENT_SECRET_BASIC "client_secret_basic"
#define OIDC_ENDPOINT_AUTH_CLIENT_SECRET_JWT "client_secret_jwt"
#define OIDC_ENDPOINT_AUTH_PRIVATE_KEY_JWT "private_key_jwt"
#define OIDC_ENDPOINT_AUTH_NONE "none"
/*
* check if the provided endpoint authentication method is supported
*/
static const char *oidc_valid_endpoint_auth_method_impl(apr_pool_t *pool,
const char *arg, apr_byte_t has_private_key) {
static char *options[] = {
OIDC_ENDPOINT_AUTH_CLIENT_SECRET_POST,
OIDC_ENDPOINT_AUTH_CLIENT_SECRET_BASIC,
OIDC_ENDPOINT_AUTH_CLIENT_SECRET_JWT,
OIDC_ENDPOINT_AUTH_NONE,
NULL,
NULL };
if (has_private_key)
options[3] = OIDC_ENDPOINT_AUTH_PRIVATE_KEY_JWT;
return oidc_valid_string_option(pool, arg, options);
}
const char *oidc_valid_endpoint_auth_method(apr_pool_t *pool, const char *arg) {
return oidc_valid_endpoint_auth_method_impl(pool, arg, TRUE);
}
const char *oidc_valid_endpoint_auth_method_no_private_key(apr_pool_t *pool,
const char *arg) {
return oidc_valid_endpoint_auth_method_impl(pool, arg, FALSE);
}
/*
* check if the provided OAuth/OIDC response type is supported
*/
const char *oidc_valid_response_type(apr_pool_t *pool, const char *arg) {
if (oidc_proto_flow_is_supported(pool, arg) == FALSE) {
return apr_psprintf(pool,
"oidc_valid_response_type: type must be one of %s",
apr_array_pstrcat(pool, oidc_proto_supported_flows(pool),
OIDC_CHAR_PIPE));
}
return NULL;
}
/*
* check if the provided PKCE method is supported
*/
const char *oidc_valid_pkce_method(apr_pool_t *pool, const char *arg) {
static char *options[] = {
OIDC_PKCE_METHOD_PLAIN,
OIDC_PKCE_METHOD_S256,
OIDC_PKCE_METHOD_REFERRED_TB,
NULL };
return oidc_valid_string_option(pool, arg, options);
}
#define OIDC_RESPONSE_TYPE_FRAGMENT "fragment"
#define OIDC_RESPONSE_TYPE_QUERY "query"
#define OIDC_RESPONSE_TYPE_FORM_POST "form_post"
/*
* check if the provided OAuth 2.0 response mode is supported
*/
const char *oidc_valid_response_mode(apr_pool_t *pool, const char *arg) {
static char *options[] = {
OIDC_RESPONSE_TYPE_FRAGMENT,
OIDC_RESPONSE_TYPE_QUERY,
OIDC_RESPONSE_TYPE_FORM_POST,
NULL };
return oidc_valid_string_option(pool, arg, options);
}
/*
* check if the provided JWT signature algorithm is supported
*/
const char *oidc_valid_signed_response_alg(apr_pool_t *pool, const char *arg) {
if (oidc_jose_jws_algorithm_is_supported(pool, arg) == FALSE) {
return apr_psprintf(pool,
"unsupported/invalid signing algorithm '%s'; must be one of [%s]",
arg,
apr_array_pstrcat(pool,
oidc_jose_jws_supported_algorithms(pool),
OIDC_CHAR_PIPE));
}
return NULL;
}
/*
* check if the provided JWT content key encryption algorithm is supported
*/
const char *oidc_valid_encrypted_response_alg(apr_pool_t *pool, const char *arg) {
if (oidc_jose_jwe_algorithm_is_supported(pool, arg) == FALSE) {
return apr_psprintf(pool,
"unsupported/invalid encryption algorithm '%s'; must be one of [%s]",
arg,
apr_array_pstrcat(pool,
oidc_jose_jwe_supported_algorithms(pool),
OIDC_CHAR_PIPE));
}
return NULL;
}
/*
* check if the provided JWT encryption cipher is supported
*/
const char *oidc_valid_encrypted_response_enc(apr_pool_t *pool, const char *arg) {
if (oidc_jose_jwe_encryption_is_supported(pool, arg) == FALSE) {
return apr_psprintf(pool,
"unsupported/invalid encryption type '%s'; must be one of [%s]",
arg,
apr_array_pstrcat(pool,
oidc_jose_jwe_supported_encryptions(pool),
OIDC_CHAR_PIPE));
}
return NULL;
}
#define OIDC_SESSION_INACTIVITY_TIMEOUT_MIN 10
#define OIDC_SESSION_INACTIVITY_TIMEOUT_MAX 3600 * 24 * 365
/*
* parse a session inactivity timeout value from the provided string
*/
const char *oidc_parse_session_inactivity_timeout(apr_pool_t *pool,
const char *arg, int *int_value) {
return oidc_parse_int_min_max(pool, arg, int_value,
OIDC_SESSION_INACTIVITY_TIMEOUT_MIN,
OIDC_SESSION_INACTIVITY_TIMEOUT_MAX);
}
#define OIDC_SESSION_MAX_DURATION_MIN 15
#define OIDC_SESSION_MAX_DURATION_MAX 3600 * 24 * 365
/*
* check the boundaries for session max lifetime
*/
const char *oidc_valid_session_max_duration(apr_pool_t *pool, int v) {
if (v == 0) {
return NULL;
}
if (v < OIDC_SESSION_MAX_DURATION_MIN) {
return apr_psprintf(pool, "value must not be less than %d seconds",
OIDC_SESSION_MAX_DURATION_MIN);
}
if (v > OIDC_SESSION_MAX_DURATION_MAX) {
return apr_psprintf(pool, "value must not be greater than %d seconds",
OIDC_SESSION_MAX_DURATION_MAX);
}
return NULL;
}
/*
* parse a session max duration value from the provided string
*/
const char *oidc_parse_session_max_duration(apr_pool_t *pool, const char *arg,
int *int_value) {
return oidc_parse_int_valid(pool, arg, int_value,
oidc_valid_session_max_duration);
}
/*
* parse a base64 encoded binary value from the provided string
*/
static char *oidc_parse_base64(apr_pool_t *pool, const char *input,
char **output, int *output_len) {
int len = apr_base64_decode_len(input);
*output = apr_palloc(pool, len);
*output_len = apr_base64_decode(*output, input);
if (*output_len <= 0)
return apr_psprintf(pool, "base64-decoding of \"%s\" failed", input);
return NULL;
}
/*
* parse a base64url encoded binary value from the provided string
*/
static char *oidc_parse_base64url(apr_pool_t *pool, const char *input,
char **output, int *output_len) {
*output_len = oidc_base64url_decode(pool, output, input);
if (*output_len <= 0)
return apr_psprintf(pool, "base64url-decoding of \"%s\" failed", input);
return NULL;
}
/*
* parse a hexadecimal encoded binary value from the provided string
*/
static char *oidc_parse_hex(apr_pool_t *pool, const char *input, char **output,
int *output_len) {
*output_len = strlen(input) / 2;
const char *pos = input;
unsigned char *val = apr_palloc(pool, *output_len);
size_t count = 0;
for (count = 0; count < (*output_len) / sizeof(unsigned char); count++) {
sscanf(pos, "%2hhx", &val[count]);
pos += 2;
}
*output = (char*) val;
return NULL;
}
#define OIDC_KEY_ENCODING_BASE64 "b64"
#define OIDC_KEY_ENCODING_BASE64_URL "b64url"
#define OIDC_KEY_ENCODING_HEX "hex"
#define OIDC_KEY_ENCODING_PLAIN "plain"
/*
* parse a key value based on the provided encoding: b64|b64url|hex|plain
*/
static const char *oidc_parse_key_value(apr_pool_t *pool, const char *enc,
const char *input, char **key, int *key_len) {
static char *options[] = {
OIDC_KEY_ENCODING_BASE64,
OIDC_KEY_ENCODING_BASE64_URL,
OIDC_KEY_ENCODING_HEX,
OIDC_KEY_ENCODING_PLAIN,
NULL };
const char *rv = oidc_valid_string_option(pool, enc, options);
if (rv != NULL)
return rv;
if (apr_strnatcmp(enc, OIDC_KEY_ENCODING_BASE64) == 0)
return oidc_parse_base64(pool, input, key, key_len);
if (apr_strnatcmp(enc, OIDC_KEY_ENCODING_BASE64_URL) == 0)
return oidc_parse_base64url(pool, input, key, key_len);
if (apr_strnatcmp(enc, OIDC_KEY_ENCODING_HEX) == 0)
return oidc_parse_hex(pool, input, key, key_len);
if (apr_strnatcmp(enc, OIDC_KEY_ENCODING_PLAIN) == 0) {
*key = apr_pstrdup(pool, input);
*key_len = strlen(*key);
}
return NULL;
}
#define OIDC_KEY_TUPLE_SEPARATOR "#"
/*
* parse a ## tuple
*/
const char *oidc_parse_enc_kid_key_tuple(apr_pool_t *pool, const char *tuple,
char **kid, char **key, int *key_len, apr_byte_t triplet) {
const char *rv = NULL;
char *s = NULL, *p = NULL, *q = NULL, *enc = NULL;
if ((tuple == NULL) || (apr_strnatcmp(tuple, "") == 0))
return "tuple value not set";
s = apr_pstrdup(pool, tuple);
p = strstr(s, OIDC_KEY_TUPLE_SEPARATOR);
if (p && triplet)
q = strstr(p + 1, OIDC_KEY_TUPLE_SEPARATOR);
if (p) {
if (q) {
*p = '\0';
*q = '\0';
enc = s;
p++;
if (p != q)
*kid = apr_pstrdup(pool, p);
rv = oidc_parse_key_value(pool, enc, q + 1, key, key_len);
} else {
*p = '\0';
*kid = s;
*key = p + 1;
*key_len = strlen(*key);
}
} else {
*kid = NULL;
*key = s;
*key_len = strlen(*key);
}
return rv;
}
#define OIDC_PASS_ID_TOKEN_AS_CLAIMS_STR "claims"
#define OIDC_PASS_IDTOKEN_AS_PAYLOAD_STR "payload"
#define OIDC_PASS_IDTOKEN_AS_SERIALIZED_STR "serialized"
/*
* convert a "pass id token as" value to an integer
*/
static int oidc_parse_pass_idtoken_as_str2int(const char *v) {
if (apr_strnatcmp(v, OIDC_PASS_ID_TOKEN_AS_CLAIMS_STR) == 0)
return OIDC_PASS_IDTOKEN_AS_CLAIMS;
if (apr_strnatcmp(v, OIDC_PASS_IDTOKEN_AS_PAYLOAD_STR) == 0)
return OIDC_PASS_IDTOKEN_AS_PAYLOAD;
if (apr_strnatcmp(v, OIDC_PASS_IDTOKEN_AS_SERIALIZED_STR) == 0)
return OIDC_PASS_IDTOKEN_AS_SERIALIZED;
return -1;
}
/*
* parse a "pass id token as" value from the provided strings
*/
const char *oidc_parse_pass_idtoken_as(apr_pool_t *pool, const char *v1,
const char *v2, const char *v3, int *int_value) {
static char *options[] = {
OIDC_PASS_ID_TOKEN_AS_CLAIMS_STR,
OIDC_PASS_IDTOKEN_AS_PAYLOAD_STR,
OIDC_PASS_IDTOKEN_AS_SERIALIZED_STR,
NULL };
const char *rv = NULL;
rv = oidc_valid_string_option(pool, v1, options);
if (rv != NULL)
return rv;
*int_value = oidc_parse_pass_idtoken_as_str2int(v1);
if (v2 == NULL)
return NULL;
rv = oidc_valid_string_option(pool, v2, options);
if (rv != NULL)
return rv;
*int_value |= oidc_parse_pass_idtoken_as_str2int(v2);
if (v3 == NULL)
return NULL;
rv = oidc_valid_string_option(pool, v3, options);
if (rv != NULL)
return rv;
*int_value |= oidc_parse_pass_idtoken_as_str2int(v3);
return NULL;
}
#define OIDC_PASS_USERINFO_AS_CLAIMS_STR "claims"
#define OIDC_PASS_USERINFO_AS_JSON_OBJECT_STR "json"
#define OIDC_PASS_USERINFO_AS_JWT_STR "jwt"
/*
* convert a "pass userinfo as" value to an integer
*/
static int oidc_parse_pass_userinfo_as_str2int(const char *v) {
if (apr_strnatcmp(v, OIDC_PASS_USERINFO_AS_CLAIMS_STR) == 0)
return OIDC_PASS_USERINFO_AS_CLAIMS;
if (apr_strnatcmp(v, OIDC_PASS_USERINFO_AS_JSON_OBJECT_STR) == 0)
return OIDC_PASS_USERINFO_AS_JSON_OBJECT;
if (apr_strnatcmp(v, OIDC_PASS_USERINFO_AS_JWT_STR) == 0)
return OIDC_PASS_USERINFO_AS_JWT;
return -1;
}
/*
* parse a "pass id token as" value from the provided strings
*/
const char *oidc_parse_pass_userinfo_as(apr_pool_t *pool, const char *v1,
const char *v2, const char *v3, int *int_value) {
static char *options[] = {
OIDC_PASS_USERINFO_AS_CLAIMS_STR,
OIDC_PASS_USERINFO_AS_JSON_OBJECT_STR,
OIDC_PASS_USERINFO_AS_JWT_STR,
NULL };
const char *rv = NULL;
rv = oidc_valid_string_option(pool, v1, options);
if (rv != NULL)
return rv;
*int_value = oidc_parse_pass_userinfo_as_str2int(v1);
if (v2 == NULL)
return NULL;
rv = oidc_valid_string_option(pool, v2, options);
if (rv != NULL)
return rv;
*int_value |= oidc_parse_pass_userinfo_as_str2int(v2);
if (v3 == NULL)
return NULL;
rv = oidc_valid_string_option(pool, v3, options);
if (rv != NULL)
return rv;
*int_value |= oidc_parse_pass_userinfo_as_str2int(v3);
return NULL;
}
#define OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR "header"
#define OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR "post"
#define OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR "query"
#define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_STR "cookie"
/*
* convert an "accept OAuth 2.0 token in" byte value to a string representation
*/
const char *oidc_accept_oauth_token_in2str(apr_pool_t *pool, apr_byte_t v) {
static char *options[] = { NULL, NULL, NULL, NULL, NULL };
int i = 0;
if (v & OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER) {
options[i] = OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR;
i++;
}
if (v & OIDC_OAUTH_ACCEPT_TOKEN_IN_POST) {
options[i] = OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR;
i++;
}
if (v & OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY) {
options[i] = OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR;
i++;
}
if (v & OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE) {
options[i] = OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_STR;
i++;
}
return oidc_flatten_list_options(pool, options);
}
/*
* convert an "accept OAuth 2.0 token in" value to an integer
*/
static apr_byte_t oidc_parse_oauth_accept_token_in_str2byte(const char *v) {
if (apr_strnatcmp(v, OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR) == 0)
return OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER;
if (apr_strnatcmp(v, OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR) == 0)
return OIDC_OAUTH_ACCEPT_TOKEN_IN_POST;
if (apr_strnatcmp(v, OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR) == 0)
return OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY;
if (strstr(v, OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_STR) == v)
return OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE;
return OIDC_OAUTH_ACCEPT_TOKEN_IN_DEFAULT;
}
#define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_NAME_DEFAULT "PA.global"
#define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_SEPARATOR ":"
/*
* parse an "accept OAuth 2.0 token in" value from the provided string
*/
const char *oidc_parse_accept_oauth_token_in(apr_pool_t *pool, const char *arg,
int *b_value, apr_hash_t *list_options) {
static char *options[] = {
OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER_STR,
OIDC_OAUTH_ACCEPT_TOKEN_IN_POST_STR,
OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY_STR,
OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_STR,
NULL };
const char *rv = NULL;
const char *s = apr_pstrdup(pool, arg);
char *p = strstr(s, OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_SEPARATOR);
if (p != NULL) {
*p = '\0';
p++;
} else {
p = OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE_NAME_DEFAULT;
}
apr_hash_set(list_options, OIDC_OAUTH_ACCEPT_TOKEN_IN_OPTION_COOKIE_NAME,
APR_HASH_KEY_STRING, p);
rv = oidc_valid_string_option(pool, s, options);
if (rv != NULL)
return rv;
int v = oidc_parse_oauth_accept_token_in_str2byte(s);
if (*b_value == OIDC_CONFIG_POS_INT_UNSET)
*b_value = v;
else
*b_value |= v;
return NULL;
}
/*
* check if the specified string is a valid claim formatting configuration value
*/
const char *oidc_valid_claim_format(apr_pool_t *pool, const char *arg) {
static char *options[] = {
OIDC_CLAIM_FORMAT_RELATIVE,
OIDC_CLAIM_FORMAT_ABSOLUTE,
NULL };
return oidc_valid_string_option(pool, arg, options);
}
/*
* parse a "claim required" value from the provided string
*/
const char *oidc_parse_claim_required(apr_pool_t *pool, const char *arg,
int *is_required) {
static char *options[] = {
OIDC_CLAIM_REQUIRED_MANDATORY,
OIDC_CLAIM_REQUIRED_OPTIONAL,
NULL };
const char *rv = oidc_valid_string_option(pool, arg, options);
if (rv != NULL)
return rv;
*is_required = (apr_strnatcmp(arg, OIDC_CLAIM_REQUIRED_MANDATORY) == 0);
return NULL;
}
/*
* check if the provided string is a valid HTTP method for the OAuth token introspection endpoint
*/
const char *oidc_valid_introspection_method(apr_pool_t *pool, const char *arg) {
static char *options[] = {
OIDC_INTROSPECTION_METHOD_GET,
OIDC_INTROSPECTION_METHOD_POST,
NULL };
return oidc_valid_string_option(pool, arg, options);
}
#define OIDC_PASS_CLAIMS_AS_BOTH "both"
#define OIDC_PASS_CLAIMS_AS_HEADERS "headers"
#define OIDC_PASS_CLAIMS_AS_ENV "environment"
#define OIDC_PASS_CLAIMS_AS_NONE "none"
/*
* parse a "set claims as" value from the provided string
*/
const char *oidc_parse_set_claims_as(apr_pool_t *pool, const char *arg,
int *in_headers, int *in_env_vars) {
static char *options[] = {
OIDC_PASS_CLAIMS_AS_BOTH,
OIDC_PASS_CLAIMS_AS_HEADERS,
OIDC_PASS_CLAIMS_AS_ENV,
OIDC_PASS_CLAIMS_AS_NONE,
NULL };
const char *rv = oidc_valid_string_option(pool, arg, options);
if (rv != NULL)
return rv;
if (apr_strnatcmp(arg, OIDC_PASS_CLAIMS_AS_BOTH) == 0) {
*in_headers = 1;
*in_env_vars = 1;
} else if (apr_strnatcmp(arg, OIDC_PASS_CLAIMS_AS_HEADERS) == 0) {
*in_headers = 1;
*in_env_vars = 0;
} else if (apr_strnatcmp(arg, OIDC_PASS_CLAIMS_AS_ENV) == 0) {
*in_headers = 0;
*in_env_vars = 1;
} else if (apr_strnatcmp(arg, OIDC_PASS_CLAIMS_AS_NONE) == 0) {
*in_headers = 0;
*in_env_vars = 0;
}
return NULL;
}
#define OIDC_UNAUTH_ACTION_AUTH_STR "auth"
#define OIDC_UNAUTH_ACTION_PASS_STR "pass"
#define OIDC_UNAUTH_ACTION_401_STR "401"
#define OIDC_UNAUTH_ACTION_410_STR "410"
/*
* parse an "unauthenticated action" value from the provided string
*/
const char *oidc_parse_unauth_action(apr_pool_t *pool, const char *arg,
int *action) {
static char *options[] = {
OIDC_UNAUTH_ACTION_AUTH_STR,
OIDC_UNAUTH_ACTION_PASS_STR,
OIDC_UNAUTH_ACTION_401_STR,
OIDC_UNAUTH_ACTION_410_STR,
NULL };
const char *rv = oidc_valid_string_option(pool, arg, options);
if (rv != NULL)
return rv;
if (apr_strnatcmp(arg, OIDC_UNAUTH_ACTION_AUTH_STR) == 0)
*action = OIDC_UNAUTH_AUTHENTICATE;
else if (apr_strnatcmp(arg, OIDC_UNAUTH_ACTION_PASS_STR) == 0)
*action = OIDC_UNAUTH_PASS;
else if (apr_strnatcmp(arg, OIDC_UNAUTH_ACTION_401_STR) == 0)
*action = OIDC_UNAUTH_RETURN401;
else if (apr_strnatcmp(arg, OIDC_UNAUTH_ACTION_410_STR) == 0)
*action = OIDC_UNAUTH_RETURN410;
return NULL;
}
#define OIDC_UNAUTZ_ACTION_AUTH_STR "auth"
#define OIDC_UNAUTZ_ACTION_401_STR "401"
#define OIDC_UNAUTZ_ACTION_403_STR "403"
/*
* parse an "unauthorized action" value from the provided string
*/
const char *oidc_parse_unautz_action(apr_pool_t *pool, const char *arg,
int *action) {
static char *options[] = {
OIDC_UNAUTZ_ACTION_AUTH_STR,
OIDC_UNAUTZ_ACTION_401_STR,
OIDC_UNAUTZ_ACTION_403_STR,
NULL };
const char *rv = oidc_valid_string_option(pool, arg, options);
if (rv != NULL)
return rv;
if (apr_strnatcmp(arg, OIDC_UNAUTZ_ACTION_AUTH_STR) == 0)
*action = OIDC_UNAUTZ_AUTHENTICATE;
else if (apr_strnatcmp(arg, OIDC_UNAUTZ_ACTION_401_STR) == 0)
*action = OIDC_UNAUTZ_RETURN401;
else if (apr_strnatcmp(arg, OIDC_UNAUTZ_ACTION_403_STR) == 0)
*action = OIDC_UNAUTZ_RETURN403;
return NULL;
}
/*
* check if there's one valid entry in a string of arrays
*/
const char *oidc_valid_string_in_array(apr_pool_t *pool, json_t *json,
const char *key, oidc_valid_function_t valid_function, char **value,
apr_byte_t optional) {
int i = 0;
json_t *json_arr = json_object_get(json, key);
if ((json_arr != NULL) && (json_is_array(json_arr))) {
for (i = 0; i < json_array_size(json_arr); i++) {
json_t *elem = json_array_get(json_arr, i);
if (!json_is_string(elem)) {
return apr_psprintf(pool,
"unhandled in-array JSON non-string object type [%d]",
elem->type);
continue;
}
if (valid_function(pool, json_string_value(elem)) == NULL) {
if (value != NULL)
*value = apr_pstrdup(pool, json_string_value(elem));
break;
}
}
if (i == json_array_size(json_arr)) {
return apr_psprintf(pool,
"could not find a valid array string element for entry \"%s\"",
key);
}
} else if (optional == FALSE) {
return apr_psprintf(pool, "JSON object did not contain a \"%s\" array",
key);
}
return NULL;
}
#define OIDC_JWKS_REFRESH_INTERVAL_MIN 300
#define OIDC_JWKS_REFRESH_INTERVAL_MAX 3600 * 24 * 365
/*
* check the boundaries for JWKs refresh interval
*/
const char *oidc_valid_jwks_refresh_interval(apr_pool_t *pool, int v) {
return oidc_valid_int_min_max(pool, v, OIDC_JWKS_REFRESH_INTERVAL_MIN,
OIDC_JWKS_REFRESH_INTERVAL_MAX);
}
/*
* parse a JWKs refresh interval from the provided string
*/
const char *oidc_parse_jwks_refresh_interval(apr_pool_t *pool, const char *arg,
int *int_value) {
return oidc_parse_int_valid(pool, arg, int_value,
oidc_valid_jwks_refresh_interval);
}
#define OIDC_IDTOKEN_IAT_SLACK_MIN 0
#define OIDC_IDTOKEN_IAT_SLACK_MAX 3600
/*
* check the boundaries for ID token "issued-at" (iat) timestamp slack
*/
const char *oidc_valid_idtoken_iat_slack(apr_pool_t *pool, int v) {
return oidc_valid_int_min_max(pool, v, OIDC_IDTOKEN_IAT_SLACK_MIN,
OIDC_IDTOKEN_IAT_SLACK_MAX);
}
/*
* parse an ID token "iat" slack interval
*/
const char *oidc_parse_idtoken_iat_slack(apr_pool_t *pool, const char *arg,
int *int_value) {
return oidc_parse_int_valid(pool, arg, int_value,
oidc_valid_idtoken_iat_slack);
}
#define OIDC_USERINFO_REFRESH_INTERVAL_MIN 0
#define OIDC_USERINFO_REFRESH_INTERVAL_MAX 3600 * 24 * 365
/*
* check the boundaries for the userinfo refresh interval
*/
const char *oidc_valid_userinfo_refresh_interval(apr_pool_t *pool, int v) {
return oidc_valid_int_min_max(pool, v, OIDC_USERINFO_REFRESH_INTERVAL_MIN,
OIDC_USERINFO_REFRESH_INTERVAL_MAX);
}
/*
* parse a userinfo refresh interval from the provided string
*/
const char *oidc_parse_userinfo_refresh_interval(apr_pool_t *pool,
const char *arg, int *int_value) {
return oidc_parse_int_valid(pool, arg, int_value,
oidc_valid_userinfo_refresh_interval);
}
#define OIDC_USER_INFO_TOKEN_METHOD_HEADER_STR "authz_header"
#define OIDC_USER_INFO_TOKEN_METHOD_POST_STR "post_param"
/*
* check if the provided string is a valid userinfo token presentation method
*/
const char *oidc_valid_userinfo_token_method(apr_pool_t *pool, const char *arg) {
static char *options[] = {
OIDC_USER_INFO_TOKEN_METHOD_HEADER_STR,
OIDC_USER_INFO_TOKEN_METHOD_POST_STR,
NULL };
return oidc_valid_string_option(pool, arg, options);
}
/*
* parse a userinfo token method string value to an integer
*/
const char *oidc_parse_userinfo_token_method(apr_pool_t *pool, const char *arg,
int *int_value) {
const char *rv = oidc_valid_userinfo_token_method(pool, arg);
if (rv != NULL)
return rv;
if (apr_strnatcmp(arg, OIDC_USER_INFO_TOKEN_METHOD_HEADER_STR) == 0)
*int_value = OIDC_USER_INFO_TOKEN_METHOD_HEADER;
if (apr_strnatcmp(arg, OIDC_USER_INFO_TOKEN_METHOD_POST_STR) == 0)
*int_value = OIDC_USER_INFO_TOKEN_METHOD_POST;
return NULL;
}
/*
* parse an "info hook data" value from the provided string
*/
const char *oidc_parse_info_hook_data(apr_pool_t *pool, const char *arg,
apr_hash_t **hook_data) {
static char *options[] = {
OIDC_HOOK_INFO_TIMESTAMP,
OIDC_HOOK_INFO_ACCES_TOKEN,
OIDC_HOOK_INFO_ACCES_TOKEN_EXP,
OIDC_HOOK_INFO_ID_TOKEN,
OIDC_HOOK_INFO_USER_INFO,
OIDC_HOOK_INFO_REFRESH_TOKEN,
OIDC_HOOK_INFO_SESSION,
NULL };
const char *rv = oidc_valid_string_option(pool, arg, options);
if (rv != NULL)
return rv;
if (*hook_data == NULL)
*hook_data = apr_hash_make(pool);
apr_hash_set(*hook_data, arg, APR_HASH_KEY_STRING, arg);
return NULL;
}
#define OIDC_TOKEN_BINDING_POLICY_DISABLED_STR "disabled"
#define OIDC_TOKEN_BINDING_POLICY_OPTIONAL_STR "optional"
#define OIDC_TOKEN_BINDING_POLICY_REQUIRED_STR "required"
#define OIDC_TOKEN_BINDING_POLICY_ENFORCED_STR "enforced"
const char *oidc_token_binding_policy2str(apr_pool_t *pool, int v) {
if (v == OIDC_TOKEN_BINDING_POLICY_DISABLED)
return OIDC_TOKEN_BINDING_POLICY_DISABLED;
if (v == OIDC_TOKEN_BINDING_POLICY_OPTIONAL)
return OIDC_TOKEN_BINDING_POLICY_OPTIONAL_STR;
if (v == OIDC_TOKEN_BINDING_POLICY_REQUIRED)
return OIDC_TOKEN_BINDING_POLICY_REQUIRED_STR;
if (v == OIDC_TOKEN_BINDING_POLICY_ENFORCED)
return OIDC_TOKEN_BINDING_POLICY_ENFORCED_STR;
return NULL;
}
/*
* check token binding policy string value
*/
const char *oidc_valid_token_binding_policy(apr_pool_t *pool, const char *arg) {
static char *options[] = {
OIDC_TOKEN_BINDING_POLICY_DISABLED_STR,
OIDC_TOKEN_BINDING_POLICY_OPTIONAL_STR,
OIDC_TOKEN_BINDING_POLICY_REQUIRED_STR,
OIDC_TOKEN_BINDING_POLICY_ENFORCED_STR,
NULL };
return oidc_valid_string_option(pool, arg, options);
}
/*
* parse token binding policy
*/
const char *oidc_parse_token_binding_policy(apr_pool_t *pool, const char *arg,
int *policy) {
const char *rv = oidc_valid_token_binding_policy(pool, arg);
if (rv != NULL)
return rv;
if (apr_strnatcmp(arg, OIDC_TOKEN_BINDING_POLICY_DISABLED_STR) == 0)
*policy = OIDC_TOKEN_BINDING_POLICY_DISABLED;
else if (apr_strnatcmp(arg, OIDC_TOKEN_BINDING_POLICY_OPTIONAL_STR) == 0)
*policy = OIDC_TOKEN_BINDING_POLICY_OPTIONAL;
else if (apr_strnatcmp(arg, OIDC_TOKEN_BINDING_POLICY_REQUIRED_STR) == 0)
*policy = OIDC_TOKEN_BINDING_POLICY_REQUIRED;
else if (apr_strnatcmp(arg, OIDC_TOKEN_BINDING_POLICY_ENFORCED_STR) == 0)
*policy = OIDC_TOKEN_BINDING_POLICY_ENFORCED;
return NULL;
}
#define OIDC_AUTH_REQUEST_METHOD_GET_STR "GET"
#define OIDC_AUTH_REQEUST_METHOD_POST_STR "POST"
/*
* parse method for sending the authentication request
*/
const char *oidc_valid_auth_request_method(apr_pool_t *pool, const char *arg) {
static char *options[] = {
OIDC_AUTH_REQUEST_METHOD_GET_STR,
OIDC_AUTH_REQEUST_METHOD_POST_STR,
NULL };
return oidc_valid_string_option(pool, arg, options);
}
/*
* parse method for sending the authentication request
*/
const char *oidc_parse_auth_request_method(apr_pool_t *pool, const char *arg,
int *method) {
const char *rv = oidc_valid_auth_request_method(pool, arg);
if (rv != NULL)
return rv;
if (apr_strnatcmp(arg, OIDC_AUTH_REQUEST_METHOD_GET_STR) == 0)
*method = OIDC_AUTH_REQUEST_METHOD_GET;
else if (apr_strnatcmp(arg, OIDC_AUTH_REQEUST_METHOD_POST_STR) == 0)
*method = OIDC_AUTH_REQUEST_METHOD_POST;
return NULL;
}
mod_auth_openidc-2.3.3/src/pcre_subst.c 0000644 0000765 0000024 00000011154 13200625410 017755 0 ustar hzandbelt staff /*************************************************
* PCRE string replacement *
*************************************************/
/*
PCRE is a library of functions to support regular expressions whose syntax
and semantics are as close as possible to those of the Perl 5 language.
pcre_subst is a wrapper around pcre_exec designed to make it easier to
perform PERL style replacements with PCRE.
Written by: Bert Driehuis
Copyright (c) 2000 Bert Driehuis
-----------------------------------------------------------------------------
Permission is granted to anyone to use this software for any purpose on any
computer system, and to redistribute it freely, subject to the following
restrictions:
1. This software is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
2. The origin of this software must not be misrepresented, either by
explicit claim or by omission.
3. Altered versions must be plainly marked as such, and must not be
misrepresented as being the original software.
4. If PCRE is embedded in any software that is released under the GNU
General Purpose Licence (GPL), then the terms of that licence shall
supersede any condition above with which it is incompatible.
*/
#include
#include
#include
#include
#include "pcre_subst.h"
#define MAXCAPTURE 50
#ifdef DEBUG_PCRE_SUBST
static void
dumpstr(const char *str, int len, int start, int end)
{
int i;
for (i = 0; i < strlen(str); i++) {
if (i >= start && i < end)
putchar(str[i]);
else
putchar('-');
}
putchar('\n');
}
static void
dumpmatch(const char *str, int len, const char *rep, int nmat, const int *ovec)
{
int i;
printf("%s Input\n", str);
printf("nmat=%d", nmat);
for (i = 0; i < nmat * 2; i++)
printf(" %d", ovec[i]);
printf("\n");
for (i = 0; i < nmat * 2; i += 2)
dumpstr(str, len, ovec[i], ovec[i+1]);
printf("\n");
}
#endif
static int
findreplen(const char *rep, int nmat, const int *replen)
{
int len = 0;
int val;
char *cp = (char *)rep;
while(*cp) {
if (*cp == '$' && isdigit(cp[1])) {
val = strtoul(&cp[1], &cp, 10);
if (val && val <= nmat + 1)
len += replen[val -1];
else
fprintf(stderr, "repl %d out of range\n", val);
} else {
cp++;
len++;
}
}
return len;
}
static void
doreplace(char *out, const char *rep, int nmat, int *replen, const char **repstr)
{
int val;
char *cp = (char *)rep;
while(*cp) {
if (*cp == '$' && isdigit(cp[1])) {
val = strtoul(&cp[1], &cp, 10);
if (val && val <= nmat + 1) {
strncpy(out, repstr[val - 1], replen[val - 1]);
out += replen[val -1];
}
} else {
*out++ = *cp++;
}
}
}
static char *
edit(const char *str, int len, const char *rep, int nmat, const int *ovec)
{
int i, slen, rlen;
const int *mvec = ovec;
char *res, *cp;
int replen[MAXCAPTURE];
const char *repstr[MAXCAPTURE];
nmat--;
ovec += 2;
for (i = 0; i < nmat; i++) {
replen[i] = ovec[i * 2 + 1] - ovec[i * 2];
repstr[i] = &str[ovec[i * 2]];
#ifdef DEBUG_PCRE_SUBST
printf(">>>%d %d %.*s\n", i, replen[i], replen[i], repstr[i]);
#endif
}
slen = len;
len -= mvec[1] - mvec[0];
len += rlen = findreplen(rep, nmat, replen);
#ifdef DEBUG_PCRE_SUBST
printf("resulting length %d (srclen=%d)\n", len, slen);
#endif
cp = res = pcre_malloc(len + 1);
if (mvec[0] > 0) {
strncpy(cp, str, mvec[0]);
cp += mvec[0];
}
doreplace(cp, rep, nmat, replen, repstr);
cp += rlen;
if (mvec[1] < slen)
strcpy(cp, &str[mvec[1]]);
res[len] = 0;
return res;
}
char *
pcre_subst(const pcre *ppat, const pcre_extra *extra, const char *str, int len,
int offset, int options, const char *rep)
{
int nmat;
int ovec[MAXCAPTURE * 3];
nmat = pcre_exec(ppat, extra, str, len, offset, options,
ovec, sizeof(ovec));
#ifdef DEBUG_PCRE_SUBST
dumpmatch(str, len, rep, nmat, ovec);
#endif
if (nmat <= 0)
return NULL;
return(edit(str, len, rep, nmat, ovec));
}
#ifdef DEBUG_BUILD
int
main()
{
char *pat = "quick\\s(\\w+)\\s(fox)";
char *rep = "$1ish $2";
char *str = "The quick brown foxy";
char *newstr;
const char *err;
int erroffset;
pcre_extra *extra;
pcre *ppat = pcre_compile(pat, 0, &err, &erroffset, NULL);
if (ppat == NULL) {
fprintf(stderr, "%s at %d\n", err, erroffset);
exit(1);
}
extra = pcre_study(ppat, 0, &err);
if (err != NULL)
fprintf(stderr, "Study %s failed: %s\n", pat, err);
newstr = pcre_subst(ppat, extra, str, strlen(str), 0, 0, rep);
if (newstr) {
printf("Newstr\t%s\n", newstr);
pcre_free(newstr);
} else {
printf("No match\n");
}
return 0;
}
#endif
mod_auth_openidc-2.3.3/src/cache/redis.c 0000644 0000765 0000024 00000027145 13200625410 017764 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* caching using a Redis backend
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*/
#include "apr_general.h"
#include "apr_strings.h"
#include
#include
#include
#include "../mod_auth_openidc.h"
#include "hiredis/hiredis.h"
// TODO: proper Redis error reporting (server unreachable etc.)
extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
typedef struct oidc_cache_cfg_redis_t {
/* cache_type = redis: Redis ptr */
oidc_cache_mutex_t *mutex;
char *host_str;
apr_port_t port;
char *passwd;
} oidc_cache_cfg_redis_t;
/* create the cache context */
static void *oidc_cache_redis_cfg_create(apr_pool_t *pool) {
oidc_cache_cfg_redis_t *context = apr_pcalloc(pool,
sizeof(oidc_cache_cfg_redis_t));
context->mutex = oidc_cache_mutex_create(pool);
context->passwd = NULL;
return context;
}
/*
* initialize the Redis struct the specified Redis server
*/
static int oidc_cache_redis_post_config(server_rec *s) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(s->module_config,
&auth_openidc_module);
if (cfg->cache_cfg != NULL)
return APR_SUCCESS;
oidc_cache_cfg_redis_t *context = oidc_cache_redis_cfg_create(
s->process->pool);
cfg->cache_cfg = context;
apr_status_t rv = APR_SUCCESS;
/* parse the host:post tuple from the configuration */
if (cfg->cache_redis_server == NULL) {
oidc_serror(s,
"cache type is set to \"redis\", but no valid " OIDCRedisCacheServer " setting was found");
return HTTP_INTERNAL_SERVER_ERROR;
}
char* scope_id;
rv = apr_parse_addr_port(&context->host_str, &scope_id, &context->port,
cfg->cache_redis_server, s->process->pool);
if (rv != APR_SUCCESS) {
oidc_serror(s, "failed to parse cache server: '%s'",
cfg->cache_redis_server);
return HTTP_INTERNAL_SERVER_ERROR;
}
if (context->host_str == NULL) {
oidc_serror(s,
"failed to parse cache server, no hostname specified: '%s'",
cfg->cache_redis_server);
return HTTP_INTERNAL_SERVER_ERROR;
}
if (context->port == 0)
context->port = 6379;
if (cfg->cache_redis_password != NULL) {
context->passwd = apr_pstrdup(s->process->pool,
cfg->cache_redis_password);
}
if (oidc_cache_mutex_post_config(s, context->mutex, "redis") == FALSE)
return HTTP_INTERNAL_SERVER_ERROR;
return OK;
}
/*
* initialize the Redis cache in a child process
*/
int oidc_cache_redis_child_init(apr_pool_t *p, server_rec *s) {
oidc_cfg *cfg = ap_get_module_config(s->module_config,
&auth_openidc_module);
oidc_cache_cfg_redis_t *context = (oidc_cache_cfg_redis_t *) cfg->cache_cfg;
/* initialize the lock for the child process */
return oidc_cache_mutex_child_init(p, s, context->mutex);
}
/*
* assemble single key name based on section/key input
*/
static char *oidc_cache_redis_get_key(apr_pool_t *pool, const char *section,
const char *key) {
return apr_psprintf(pool, "%s:%s", section, key);
}
/* key for storing data in the process pool */
#define OIDC_CACHE_REDIS_CONTEXT "oidc_cache_redis_context"
/*
* per-process Redis connection context
*/
typedef struct {
redisContext *ctx;
} oidc_cache_redis_ctx_t;
/*
* free resources allocated for the per-process Redis connection context
*/
static apr_status_t oidc_cache_redis_free(void *ptr) {
oidc_cache_redis_ctx_t *rctx = (oidc_cache_redis_ctx_t *) ptr;
if ((rctx != NULL) && (rctx->ctx != NULL)) {
redisFree(rctx->ctx);
rctx->ctx = NULL;
}
return APR_SUCCESS;
}
/*
* connect to Redis server
*/
static oidc_cache_redis_ctx_t * oidc_cache_redis_connect(request_rec *r,
oidc_cache_cfg_redis_t *context) {
/* see if we already have a connection by looking it up in the process context */
oidc_cache_redis_ctx_t *rctx = NULL;
apr_pool_userdata_get((void **) &rctx, OIDC_CACHE_REDIS_CONTEXT,
r->server->process->pool);
if (rctx == NULL) {
rctx = apr_pcalloc(r->server->process->pool,
sizeof(oidc_cache_redis_ctx_t));
rctx->ctx = NULL;
/* store the connection in the process context */
apr_pool_userdata_set(rctx, OIDC_CACHE_REDIS_CONTEXT,
oidc_cache_redis_free, r->server->process->pool);
}
if (rctx->ctx == NULL) {
/* no connection, connect to the configured Redis server */
rctx->ctx = redisConnect(context->host_str, context->port);
/* check for errors */
if ((rctx->ctx == NULL) || (rctx->ctx->err != 0)) {
oidc_error(r, "failed to connect to Redis server (%s:%d): '%s'",
context->host_str, context->port,
rctx->ctx != NULL ? rctx->ctx->errstr : "");
oidc_cache_redis_free(rctx);
} else {
/* log the connection */
oidc_debug(r, "successfully connected to Redis server (%s:%d)",
context->host_str, context->port);
}
}
return rctx;
}
/*
* execute Redis command and deal with return value
*/
static redisReply* oidc_cache_redis_command(request_rec *r,
oidc_cache_cfg_redis_t *context, const char *format, ...) {
oidc_cache_redis_ctx_t *rctx = NULL;
redisReply *reply = NULL;
int i = 0;
/* try to execute a command at max 2 times while reconnecting */
for (i = 0; i < 2; i++) {
/* connect */
rctx = oidc_cache_redis_connect(r, context);
if ((rctx == NULL) || (rctx->ctx == NULL))
break;
if (context->passwd != NULL) {
redisAppendCommand(rctx->ctx,
apr_psprintf(r->pool, "AUTH %s", context->passwd));
}
/* execute the command */
va_list args;
va_start(args, format);
redisvAppendCommand(rctx->ctx, format, args);
va_end(args);
if (context->passwd != NULL) {
/* get the reply for the AUTH command */
redisGetReply(rctx->ctx, (void **) &reply);
if (reply == NULL) {
oidc_error(r,
"authentication to the Redis server (%s:%d) failed, reply == NULL",
context->host_str, context->port);
} else if (reply->type == REDIS_REPLY_ERROR) {
oidc_error(r,
"authentication to the Redis server (%s:%d) failed, reply.status = %s",
context->host_str, context->port, reply->str);
}
}
/* get the reply for the actual command */
reply = NULL;
redisGetReply(rctx->ctx, (void **) &reply);
/* errors will result in an empty reply */
if (reply != NULL) {
if (reply->type == REDIS_REPLY_ERROR) {
oidc_error(r,
"command to the Redis server (%s:%d) returned an error, reply.status = %s",
context->host_str, context->port, reply->str);
}
break;
}
/* something went wrong, log it */
oidc_error(r,
"redisvAppendCommand/redisGetReply (%d) failed, disconnecting: '%s'",
i, rctx->ctx->errstr);
/* cleanup, we may try again (once) after reconnecting */
oidc_cache_redis_free(rctx);
}
return reply;
}
/*
* get a name/value pair from Redis
*/
static apr_byte_t oidc_cache_redis_get(request_rec *r, const char *section,
const char *key, const char **value) {
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
oidc_cache_cfg_redis_t *context = (oidc_cache_cfg_redis_t *) cfg->cache_cfg;
redisReply *reply = NULL;
/* grab the global lock */
if (oidc_cache_mutex_lock(r, context->mutex) == FALSE)
return FALSE;
/* get */
reply = oidc_cache_redis_command(r, context, "GET %s",
oidc_cache_redis_get_key(r->pool, section, key));
if (reply == NULL) {
oidc_cache_mutex_unlock(r, context->mutex);
return FALSE;
}
/* check that we got a string back */
if (reply->type != REDIS_REPLY_STRING) {
freeReplyObject(reply);
/* this is a normal cache miss, so we'll return OK */
oidc_cache_mutex_unlock(r, context->mutex);
return TRUE;
}
/* do a sanity check on the returned value */
if (reply->len != strlen(reply->str)) {
oidc_error(r, "redisCommand reply->len != strlen(reply->str): '%s'",
reply->str);
freeReplyObject(reply);
oidc_cache_mutex_unlock(r, context->mutex);
return FALSE;
}
/* copy it in to the request memory pool */
*value = apr_pstrdup(r->pool, reply->str);
freeReplyObject(reply);
/* release the global lock */
oidc_cache_mutex_unlock(r, context->mutex);
return TRUE;
}
/*
* store a name/value pair in Redis
*/
static apr_byte_t oidc_cache_redis_set(request_rec *r, const char *section,
const char *key, const char *value, apr_time_t expiry) {
oidc_cfg *cfg = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
oidc_cache_cfg_redis_t *context = (oidc_cache_cfg_redis_t *) cfg->cache_cfg;
redisReply *reply = NULL;
/* grab the global lock */
if (oidc_cache_mutex_lock(r, context->mutex) == FALSE)
return FALSE;
/* see if we should be clearing this entry */
if (value == NULL) {
/* delete it */
reply = oidc_cache_redis_command(r, context, "DEL %s",
oidc_cache_redis_get_key(r->pool, section, key));
if (reply == NULL) {
oidc_cache_mutex_unlock(r, context->mutex);
return FALSE;
}
freeReplyObject(reply);
} else {
/* calculate the timeout from now */
apr_uint32_t timeout = apr_time_sec(expiry - apr_time_now());
/* store it */
reply = oidc_cache_redis_command(r, context, "SETEX %s %d %s",
oidc_cache_redis_get_key(r->pool, section, key), timeout,
value);
if (reply == NULL) {
oidc_cache_mutex_unlock(r, context->mutex);
return FALSE;
}
freeReplyObject(reply);
}
/* release the global lock */
oidc_cache_mutex_unlock(r, context->mutex);
return TRUE;
}
static int oidc_cache_redis_destroy(server_rec *s) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(s->module_config,
&auth_openidc_module);
oidc_cache_cfg_redis_t *context = (oidc_cache_cfg_redis_t *) cfg->cache_cfg;
oidc_cache_mutex_destroy(s, context->mutex);
return APR_SUCCESS;
}
oidc_cache_t oidc_cache_redis = {
"redis",
1,
oidc_cache_redis_post_config,
oidc_cache_redis_child_init,
oidc_cache_redis_get,
oidc_cache_redis_set,
oidc_cache_redis_destroy
};
mod_auth_openidc-2.3.3/src/mod_auth_openidc.h 0000644 0000765 0000024 00000127702 13203320522 021120 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*/
#ifndef MOD_AUTH_OPENIDC_H_
#define MOD_AUTH_OPENIDC_H_
#include
#include
#include
#include
#include
#include "jose.h"
#include "cache/cache.h"
#include "parse.h"
#include
#ifdef APLOG_USE_MODULE
APLOG_USE_MODULE(auth_openidc);
#endif
#ifndef OIDC_DEBUG
#define OIDC_DEBUG APLOG_DEBUG
#endif
#define oidc_log(r, level, fmt, ...) ap_log_rerror(APLOG_MARK, level, 0, r,"%s: %s", __FUNCTION__, apr_psprintf(r->pool, fmt, ##__VA_ARGS__))
#define oidc_slog(s, level, fmt, ...) ap_log_error(APLOG_MARK, level, 0, s, "%s: %s", __FUNCTION__, apr_psprintf(s->process->pool, fmt, ##__VA_ARGS__))
#define oidc_debug(r, fmt, ...) oidc_log(r, OIDC_DEBUG, fmt, ##__VA_ARGS__)
#define oidc_warn(r, fmt, ...) oidc_log(r, APLOG_WARNING, fmt, ##__VA_ARGS__)
#define oidc_error(r, fmt, ...) oidc_log(r, APLOG_ERR, fmt, ##__VA_ARGS__)
#define oidc_sdebug(s, fmt, ...) oidc_slog(s, OIDC_DEBUG, fmt, ##__VA_ARGS__)
#define oidc_swarn(s, fmt, ...) oidc_slog(s, APLOG_WARNING, fmt, ##__VA_ARGS__)
#define oidc_serror(s, fmt, ...) oidc_slog(s, APLOG_ERR, fmt, ##__VA_ARGS__)
#ifndef NAMEVER
#define NAMEVERSION "mod_auth_openidc-0.0.0"
#else
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define NAMEVERSION TOSTRING(NAMEVER)
#endif
/* keys for storing info in the request state */
#define OIDC_REQUEST_STATE_KEY_IDTOKEN "i"
#define OIDC_REQUEST_STATE_KEY_CLAIMS "c"
/* parameter name of the callback URL in the discovery response */
#define OIDC_DISC_CB_PARAM "oidc_callback"
/* parameter name of the OP provider selection in the discovery response */
#define OIDC_DISC_OP_PARAM "iss"
/* parameter name of the user URL in the discovery response */
#define OIDC_DISC_USER_PARAM "disc_user"
/* parameter name of the original URL in the discovery response */
#define OIDC_DISC_RT_PARAM "target_link_uri"
/* parameter name of the original method in the discovery response */
#define OIDC_DISC_RM_PARAM "method"
/* parameter name of login hint in the discovery response */
#define OIDC_DISC_LH_PARAM "login_hint"
/* parameter name of parameters that need to be passed in the authentication request */
#define OIDC_DISC_AR_PARAM "auth_request_params"
/* parameter name of the scopes required in the discovery response */
#define OIDC_DISC_SC_PARAM "scopes"
/* value that indicates to use server-side cache based session tracking */
#define OIDC_SESSION_TYPE_SERVER_CACHE 0
/* value that indicates to use client cookie based session tracking */
#define OIDC_SESSION_TYPE_CLIENT_COOKIE 1
/* nonce bytes length */
#define OIDC_PROTO_NONCE_LENGTH 32
/* code verifier length */
#define OIDC_PROTO_CODE_VERIFIER_LENGTH 32
/* pass id_token as individual claims in headers (default) */
#define OIDC_PASS_IDTOKEN_AS_CLAIMS 1
/* pass id_token payload as JSON object in header */
#define OIDC_PASS_IDTOKEN_AS_PAYLOAD 2
/* pass id_token in compact serialized format in header */
#define OIDC_PASS_IDTOKEN_AS_SERIALIZED 4
/* pass userinfo as individual claims in headers (default) */
#define OIDC_PASS_USERINFO_AS_CLAIMS 1
/* pass userinfo payload as JSON object in header */
#define OIDC_PASS_USERINFO_AS_JSON_OBJECT 2
/* pass userinfo as a JWT in header (when returned as a JWT) */
#define OIDC_PASS_USERINFO_AS_JWT 4
#define OIDC_OAUTH_ACCEPT_TOKEN_IN_DEFAULT 0
/* accept bearer token in header (default) */
#define OIDC_OAUTH_ACCEPT_TOKEN_IN_HEADER 1
/* accept bearer token as a post parameter */
#define OIDC_OAUTH_ACCEPT_TOKEN_IN_POST 2
/* accept bearer token as a query parameter */
#define OIDC_OAUTH_ACCEPT_TOKEN_IN_QUERY 4
/* accept bearer token as a cookie parameter (PingAccess) */
#define OIDC_OAUTH_ACCEPT_TOKEN_IN_COOKIE 8
/* the hash key of the cookie name value in the list of options */
#define OIDC_OAUTH_ACCEPT_TOKEN_IN_OPTION_COOKIE_NAME "cookie-name"
/* introspection method options */
#define OIDC_INTROSPECTION_METHOD_GET "GET"
#define OIDC_INTROSPECTION_METHOD_POST "POST"
/* HTTP methods to send authentication requests */
#define OIDC_AUTH_REQUEST_METHOD_GET 0
#define OIDC_AUTH_REQUEST_METHOD_POST 1
/* prefix of the cookie that binds the state in the authorization request/response to the browser */
#define OIDC_STATE_COOKIE_PREFIX "mod_auth_openidc_state_"
/* default prefix for information passed in HTTP headers */
#define OIDC_DEFAULT_HEADER_PREFIX "OIDC_"
/* the (global) key for the mod_auth_openidc related state that is stored in the request userdata context */
#define OIDC_USERDATA_KEY "mod_auth_openidc_state"
#define OIDC_USERDATA_ENV_KEY "mod_auth_openidc_env"
/* input filter hook name */
#define OIDC_UTIL_HTTP_SENDSTRING "OIDC_UTIL_HTTP_SENDSTRING"
/* the name of the keyword that follows the Require primitive to indicate claims-based authorization */
#define OIDC_REQUIRE_CLAIM_NAME "claim"
#ifdef USE_LIBJQ
/* the name of the keyword that follows the Require primitive to indicate claims-expression-based authorization */
#define OIDC_REQUIRE_CLAIMS_EXPR_NAME "claims_expr"
#endif
/* defines for how long provider metadata will be cached */
#define OIDC_CACHE_PROVIDER_METADATA_EXPIRY_DEFAULT 86400
/* define the parameter value for the "logout" request that indicates a GET-style logout call from the OP */
#define OIDC_GET_STYLE_LOGOUT_PARAM_VALUE "get"
#define OIDC_IMG_STYLE_LOGOUT_PARAM_VALUE "img"
/* define the name of the cookie/parameter for CSRF protection */
#define OIDC_CSRF_NAME "x_csrf"
/* http methods */
#define OIDC_METHOD_GET "get"
#define OIDC_METHOD_FORM_POST "form_post"
/* the maximum size of data that we accept in a single POST value: 1MB */
#define OIDC_MAX_POST_DATA_LEN 1024 * 1024
#define OIDC_UNAUTH_AUTHENTICATE 1
#define OIDC_UNAUTH_PASS 2
#define OIDC_UNAUTH_RETURN401 3
#define OIDC_UNAUTH_RETURN410 4
#define OIDC_UNAUTZ_RETURN403 1
#define OIDC_UNAUTZ_RETURN401 2
#define OIDC_UNAUTZ_AUTHENTICATE 3
#define OIDC_REQUEST_URI_CACHE_DURATION 30
#define OIDC_USER_INFO_TOKEN_METHOD_HEADER 0
#define OIDC_USER_INFO_TOKEN_METHOD_POST 1
#define OIDC_COOKIE_EXT_SAME_SITE_LAX "SameSite=Lax"
#define OIDC_COOKIE_EXT_SAME_SITE_STRICT "SameSite=Strict"
/* https://tools.ietf.org/html/draft-ietf-tokbind-ttrp-01 */
#define OIDC_TB_CFG_PROVIDED_ENV_VAR "Sec-Provided-Token-Binding-ID"
#define OIDC_TOKEN_BINDING_POLICY_DISABLED 0
#define OIDC_TOKEN_BINDING_POLICY_OPTIONAL 1
#define OIDC_TOKEN_BINDING_POLICY_REQUIRED 2
#define OIDC_TOKEN_BINDING_POLICY_ENFORCED 3
typedef apr_byte_t (*oidc_proto_pkce_state)(request_rec *r, char **state);
typedef apr_byte_t (*oidc_proto_pkce_challenge)(request_rec *r, const char *state, char **code_challenge);
typedef apr_byte_t (*oidc_proto_pkce_verifier)(request_rec *r, const char *state, char **code_verifier);
typedef struct oidc_proto_pkce_t {
const char *method;
oidc_proto_pkce_state state;
oidc_proto_pkce_verifier verifier;
oidc_proto_pkce_challenge challenge;
} oidc_proto_pkce_t;
extern oidc_proto_pkce_t oidc_pkce_plain;
extern oidc_proto_pkce_t oidc_pkce_s256;
extern oidc_proto_pkce_t oidc_pkce_referred_tb;
typedef struct oidc_jwks_uri_t {
const char *url;
int refresh_interval;
int ssl_validate_server;
} oidc_jwks_uri_t;
typedef struct oidc_provider_t {
char *metadata_url;
char *issuer;
char *authorization_endpoint_url;
char *token_endpoint_url;
char *token_endpoint_auth;
char *token_endpoint_params;
char *userinfo_endpoint_url;
char *registration_endpoint_url;
char *check_session_iframe;
char *end_session_endpoint;
char *jwks_uri;
char *client_id;
char *client_secret;
char *token_endpoint_tls_client_key;
char *token_endpoint_tls_client_cert;
// the next ones function as global default settings too
int ssl_validate_server;
char *client_name;
char *client_contact;
char *registration_token;
char *registration_endpoint_json;
char *scope;
char *response_type;
char *response_mode;
int jwks_refresh_interval;
int idtoken_iat_slack;
char *auth_request_params;
int session_max_duration;
oidc_proto_pkce_t *pkce;
int userinfo_refresh_interval;
char *client_jwks_uri;
char *id_token_signed_response_alg;
char *id_token_encrypted_response_alg;
char *id_token_encrypted_response_enc;
char *userinfo_signed_response_alg;
char *userinfo_encrypted_response_alg;
char *userinfo_encrypted_response_enc;
int userinfo_token_method;
char *request_object;
int auth_request_method;
int token_binding_policy;
int issuer_specific_redirect_uri;
} oidc_provider_t ;
typedef struct oidc_remote_user_claim_t {
const char *claim_name;
const char *reg_exp;
const char *replace;
} oidc_remote_user_claim_t;
typedef struct oidc_oauth_t {
int ssl_validate_server;
char *client_id;
char *client_secret;
char *introspection_endpoint_tls_client_key;
char *introspection_endpoint_tls_client_cert;
char *introspection_endpoint_url;
char *introspection_endpoint_method;
char *introspection_endpoint_params;
char *introspection_endpoint_auth;
char *introspection_client_auth_bearer_token;
char *introspection_token_param_name;
char *introspection_token_expiry_claim_name;
char *introspection_token_expiry_claim_format;
int introspection_token_expiry_claim_required;
oidc_remote_user_claim_t remote_user_claim;
apr_hash_t *verify_shared_keys;
char *verify_jwks_uri;
apr_hash_t *verify_public_keys;
} oidc_oauth_t;
typedef struct oidc_cfg {
/* indicates whether this is a derived config, merged from a base one */
unsigned int merged;
/* HTML to display error messages+description */
char *error_template;
/* the redirect URI as configured with the OpenID Connect OP's that we talk to */
char *redirect_uri;
/* (optional) default URL for 3rd-party initiated SSO */
char *default_sso_url;
/* (optional) default URL to go to after logout */
char *default_slo_url;
/* public keys in JWK format, used by parters for encrypting JWTs sent to us */
apr_hash_t *public_keys;
/* private keys in JWK format used for decrypting encrypted JWTs sent to us */
apr_hash_t *private_keys;
/* a pointer to the (single) provider that we connect to */
/* NB: if metadata_dir is set, these settings will function as defaults for the metadata read from there) */
oidc_provider_t provider;
/* a pointer to the oauth server settings */
oidc_oauth_t oauth;
/* directory that holds the provider & client metadata files */
char *metadata_dir;
/* type of session management/storage */
int session_type;
/* session cookie or persistent cookie */
int persistent_session_cookie;
/* session cookie chunk size */
int session_cookie_chunk_size;
/* pointer to cache functions */
oidc_cache_t *cache;
void *cache_cfg;
/* cache_type = file: directory that holds the cache files (if not set, we'll try and use an OS defined one like "/tmp" */
char *cache_file_dir;
/* cache_type = file: clean interval */
int cache_file_clean_interval;
/* cache_type= memcache: list of memcache host/port servers to use */
char *cache_memcache_servers;
/* cache_type = shm: size of the shared memory segment (cq. max number of cached entries) */
int cache_shm_size_max;
/* cache_type = shm: maximum size in bytes of a cache entry */
int cache_shm_entry_size_max;
#ifdef USE_LIBHIREDIS
/* cache_type= redis: Redis host/port server to use */
char *cache_redis_server;
char *cache_redis_password;
#endif
int cache_encrypt;
/* tell the module to strip any mod_auth_openidc related headers that already have been set by the user-agent, normally required for secure operation */
int scrub_request_headers;
int http_timeout_long;
int http_timeout_short;
int state_timeout;
int session_inactivity_timeout;
int session_cache_fallback_to_cookie;
char *cookie_domain;
char *claim_delimiter;
char *claim_prefix;
oidc_remote_user_claim_t remote_user_claim;
int pass_idtoken_as;
int pass_userinfo_as;
int cookie_http_only;
int cookie_same_site;
char *outgoing_proxy;
char *crypto_passphrase;
int provider_metadata_refresh_interval;
apr_hash_t *info_hook_data;
apr_hash_t *black_listed_claims;
apr_hash_t *white_listed_claims;
} oidc_cfg;
int oidc_check_user_id(request_rec *r);
#if MODULE_MAGIC_NUMBER_MAJOR >= 20100714
authz_status oidc_authz_checker_claim(request_rec *r, const char *require_args, const void *parsed_require_args);
#ifdef USE_LIBJQ
authz_status oidc_authz_checker_claims_expr(request_rec *r, const char *require_args, const void *parsed_require_args);
#endif
#else
int oidc_auth_checker(request_rec *r);
#endif
void oidc_request_state_set(request_rec *r, const char *key, const char *value);
const char*oidc_request_state_get(request_rec *r, const char *key);
int oidc_handle_jwks(request_rec *r, oidc_cfg *c);
apr_byte_t oidc_post_preserve_javascript(request_rec *r, const char *location, char **javascript, char **javascript_method);
void oidc_scrub_headers(request_rec *r);
void oidc_strip_cookies(request_rec *r);
int oidc_content_handler(request_rec *r);
apr_byte_t oidc_get_remote_user(request_rec *r, const char *claim_name, const char *replace, const char *reg_exp,
json_t *json, char **request_user);
#define OIDC_REDIRECT_URI_REQUEST_INFO "info"
#define OIDC_REDIRECT_URI_REQUEST_LOGOUT "logout"
#define OIDC_REDIRECT_URI_REQUEST_JWKS "jwks"
#define OIDC_REDIRECT_URI_REQUEST_SESSION "session"
#define OIDC_REDIRECT_URI_REQUEST_REFRESH "refresh"
#define OIDC_REDIRECT_URI_REQUEST_REMOVE_AT_CACHE "remove_at_cache"
#define OIDC_REDIRECT_URI_REQUEST_REQUEST_URI "request_uri"
// oidc_oauth
int oidc_oauth_check_userid(request_rec *r, oidc_cfg *c);
apr_byte_t oidc_oauth_get_bearer_token(request_rec *r, const char **access_token);
// oidc_proto.c
#define OIDC_PROTO_ISS "iss"
#define OIDC_PROTO_CODE "code"
#define OIDC_PROTO_CLIENT_ID "client_id"
#define OIDC_PROTO_CLIENT_SECRET "client_secret"
#define OIDC_PROTO_CLIENT_ASSERTION "client_assertion"
#define OIDC_PROTO_CLIENT_ASSERTION_TYPE "client_assertion_type"
#define OIDC_PROTO_ACCESS_TOKEN "access_token"
#define OIDC_PROTO_ID_TOKEN "id_token"
#define OIDC_PROTO_STATE "state"
#define OIDC_PROTO_GRANT_TYPE "grant_type"
#define OIDC_PROTO_REDIRECT_URI "redirect_uri"
#define OIDC_PROTO_CODE_VERIFIER "code_verifier"
#define OIDC_PROTO_CODE_CHALLENGE "code_challenge"
#define OIDC_PROTO_CODE_CHALLENGE_METHOD "code_challenge_method"
#define OIDC_PROTO_SCOPE "scope"
#define OIDC_PROTO_REFRESH_TOKEN "refresh_token"
#define OIDC_PROTO_TOKEN_TYPE "token_type"
#define OIDC_PROTO_EXPIRES_IN "expires_in"
#define OIDC_PROTO_RESPONSE_TYPE "response_type"
#define OIDC_PROTO_RESPONSE_MODE "response_mode"
#define OIDC_PROTO_NONCE "nonce"
#define OIDC_PROTO_PROMPT "prompt"
#define OIDC_PROTO_LOGIN_HINT "login_hint"
#define OIDC_PROTO_ID_TOKEN_HINT "id_token_hint"
#define OIDC_PROTO_REQUEST_URI "request_uri"
#define OIDC_PROTO_REQUEST_OBJECT "request"
#define OIDC_PROTO_SESSION_STATE "session_state"
#define OIDC_PROTO_ACTIVE "active"
#define OIDC_PROTO_RESPONSE_TYPE_CODE "code"
#define OIDC_PROTO_RESPONSE_TYPE_IDTOKEN "id_token"
#define OIDC_PROTO_RESPONSE_TYPE_IDTOKEN_TOKEN "id_token token"
#define OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN "code id_token"
#define OIDC_PROTO_RESPONSE_TYPE_CODE_TOKEN "code token"
#define OIDC_PROTO_RESPONSE_TYPE_CODE_IDTOKEN_TOKEN "code id_token token"
#define OIDC_PROTO_RESPONSE_TYPE_TOKEN "token"
#define OIDC_PROTO_RESPONSE_MODE_QUERY "query"
#define OIDC_PROTO_RESPONSE_MODE_FRAGMENT "fragment"
#define OIDC_PROTO_RESPONSE_MODE_FORM_POST "form_post"
#define OIDC_PROTO_SCOPE_OPENID "openid"
#define OIDC_PROTO_PROMPT_NONE "none"
#define OIDC_PROTO_ERROR "error"
#define OIDC_PROTO_ERROR_DESCRIPTION "error_description"
#define OIDC_PROTO_REALM "realm"
#define OIDC_PROTO_ERR_INVALID_TOKEN "invalid_token"
#define OIDC_PROTO_ERR_INVALID_REQUEST "invalid_request"
#define OIDC_PROTO_GRANT_TYPE_AUTHZ_CODE "authorization_code"
#define OIDC_PROTO_GRANT_TYPE_REFRESH_TOKEN "refresh_token"
#define OIDC_PROTO_CLIENT_ASSERTION_TYPE_JWT_BEARER "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
#define OIDC_PROTO_CLIENT_SECRET_BASIC "client_secret_basic"
#define OIDC_PROTO_CLIENT_SECRET_POST "client_secret_post"
#define OIDC_PROTO_CLIENT_SECRET_JWT "client_secret_jwt"
#define OIDC_PROTO_PRIVATE_KEY_JWT "private_key_jwt"
#define OIDC_PROTO_ENDPOINT_AUTH_NONE "none"
#define OIDC_PROTO_BEARER "Bearer"
#define OIDC_CLAIM_ISS "iss"
#define OIDC_CLAIM_AUD "aud"
#define OIDC_CLAIM_AZP "azp"
#define OIDC_CLAIM_SUB "sub"
#define OIDC_CLAIM_JTI "jti"
#define OIDC_CLAIM_EXP "exp"
#define OIDC_CLAIM_IAT "iat"
#define OIDC_CLAIM_NONCE "nonce"
#define OIDC_CLAIM_AT_HASH "at_hash"
#define OIDC_CLAIM_C_HASH "c_hash"
#define OIDC_CLAIM_RFP "rfp"
#define OIDC_CLAIM_TARGET_LINK_URI "target_link_uri"
#define OIDC_JWK_X5T "x5t"
#define OIDC_JWK_KEYS "keys"
#define OIDC_JWK_USE "use"
#define OIDC_JWK_SIG "sig"
#define OIDC_JWK_ENC "enc"
#define OIDC_HOOK_INFO_FORMAT_JSON "json"
#define OIDC_HOOK_INFO_TIMESTAMP "iat"
#define OIDC_HOOK_INFO_ACCES_TOKEN "access_token"
#define OIDC_HOOK_INFO_ACCES_TOKEN_EXP "access_token_expires"
#define OIDC_HOOK_INFO_ID_TOKEN "id_token"
#define OIDC_HOOK_INFO_USER_INFO "userinfo"
#define OIDC_HOOK_INFO_SESSION "session"
#define OIDC_HOOK_INFO_SESSION_STATE "state"
#define OIDC_HOOK_INFO_SESSION_UUID "uuid"
#define OIDC_HOOK_INFO_SESSION_EXP "exp"
#define OIDC_HOOK_INFO_SESSION_REMOTE_USER "remote_user"
#define OIDC_HOOK_INFO_REFRESH_TOKEN "refresh_token"
#define OIDC_CONTENT_TYPE_JSON "application/json"
#define OIDC_CONTENT_TYPE_JWT "application/jwt"
#define OIDC_CONTENT_TYPE_FORM_ENCODED "application/x-www-form-urlencoded"
#define OIDC_CONTENT_TYPE_IMAGE_PNG "image/png"
#define OIDC_CONTENT_TYPE_HTML "text/html"
#define OIDC_STR_SPACE " "
#define OIDC_STR_EQUAL "="
#define OIDC_STR_AMP "&"
#define OIDC_STR_QUERY "?"
#define OIDC_STR_COLON ":"
#define OIDC_STR_SEMI_COLON ";"
#define OIDC_STR_FORWARD_SLASH "/"
#define OIDC_STR_AT "@"
#define OIDC_STR_COMMA ","
#define OIDC_CHAR_EQUAL '='
#define OIDC_CHAR_COLON ':'
#define OIDC_CHAR_TILDE '~'
#define OIDC_CHAR_SPACE ' '
#define OIDC_CHAR_COMMA ','
#define OIDC_CHAR_QUERY '?'
#define OIDC_CHAR_DOT '.'
#define OIDC_CHAR_AT '@'
#define OIDC_CHAR_FORWARD_SLASH '/'
#define OIDC_CHAR_PIPE '|'
#define OIDC_CHAR_AMP '&'
#define OIDC_APP_INFO_REFRESH_TOKEN "refresh_token"
#define OIDC_APP_INFO_ACCESS_TOKEN "access_token"
#define OIDC_APP_INFO_ACCESS_TOKEN_EXP "access_token_expires"
#define OIDC_APP_INFO_ID_TOKEN "id_token"
#define OIDC_APP_INFO_ID_TOKEN_PAYLOAD "id_token_payload"
#define OIDC_APP_INFO_USERINFO_JSON "userinfo_json"
#define OIDC_APP_INFO_USERINFO_JWT "userinfo_jwt"
typedef json_t oidc_proto_state_t;
oidc_proto_state_t *oidc_proto_state_new();
void oidc_proto_state_destroy(oidc_proto_state_t *proto_state);
oidc_proto_state_t *oidc_proto_state_from_cookie(request_rec *r, oidc_cfg *c, const char *cookieValue);
char *oidc_proto_state_to_cookie(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state);
char *oidc_proto_state_to_string(request_rec *r, oidc_proto_state_t *proto_state);
const char *oidc_proto_state_get_issuer(oidc_proto_state_t *proto_state);
const char *oidc_proto_state_get_nonce(oidc_proto_state_t *proto_state);
apr_time_t oidc_proto_state_get_timestamp(oidc_proto_state_t *proto_state);
const char *oidc_proto_state_get_state(oidc_proto_state_t *proto_state);
const char *oidc_proto_state_get_original_url(oidc_proto_state_t *proto_state);
const char *oidc_proto_state_get_prompt(oidc_proto_state_t *proto_state);
const char *oidc_proto_state_get_response_type(oidc_proto_state_t *proto_state);
const char *oidc_proto_state_get_response_mode(oidc_proto_state_t *proto_state);
const char *oidc_proto_state_get_original_url(oidc_proto_state_t *proto_state);
const char *oidc_proto_state_get_original_method(oidc_proto_state_t *proto_state);
const char *oidc_proto_state_get_pkce_state(oidc_proto_state_t *proto_state);
void oidc_proto_state_set_state(oidc_proto_state_t *proto_state, const char *state);
void oidc_proto_state_set_issuer(oidc_proto_state_t *proto_state, const char *issuer);
void oidc_proto_state_set_original_url(oidc_proto_state_t *proto_state, const char *original_url);
void oidc_proto_state_set_original_method(oidc_proto_state_t *proto_state, const char *original_method);
void oidc_proto_state_set_response_mode(oidc_proto_state_t *proto_state, const char *response_mode);
void oidc_proto_state_set_response_type(oidc_proto_state_t *proto_state, const char *response_type);
void oidc_proto_state_set_nonce(oidc_proto_state_t *proto_state, const char *nonce);
void oidc_proto_state_set_prompt(oidc_proto_state_t *proto_state, const char *prompt);
void oidc_proto_state_set_pkce_state(oidc_proto_state_t *proto_state, const char *pkce_state);
void oidc_proto_state_set_timestamp_now(oidc_proto_state_t *proto_state);
apr_byte_t oidc_proto_token_endpoint_auth(request_rec *r, oidc_cfg *cfg, const char *token_endpoint_auth, const char *client_id, const char *client_secret, const char *audience, apr_table_t *params, char **basic_auth_str, char **bearer_auth_str);
char *oidc_proto_peek_jwt_header(request_rec *r, const char *jwt, char **alg);
int oidc_proto_authorization_request(request_rec *r, struct oidc_provider_t *provider, const char *login_hint, const char *redirect_uri, const char *state, oidc_proto_state_t *proto_state, const char *id_token_hint, const char *code_challenge, const char *auth_request_params, const char *path_scope);
apr_byte_t oidc_proto_is_post_authorization_response(request_rec *r, oidc_cfg *cfg);
apr_byte_t oidc_proto_is_redirect_authorization_response(request_rec *r, oidc_cfg *cfg);
apr_byte_t oidc_proto_refresh_request(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *rtoken, char **id_token, char **access_token, char **token_type, int *expires_in, char **refresh_token);
apr_byte_t oidc_proto_resolve_userinfo(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *id_token_sub, const char *access_token, char **response, char **userinfo_jwt);
apr_byte_t oidc_proto_account_based_discovery(request_rec *r, oidc_cfg *cfg, const char *acct, char **issuer);
apr_byte_t oidc_proto_url_based_discovery(request_rec *r, oidc_cfg *cfg, const char *url, char **issuer);
apr_byte_t oidc_proto_parse_idtoken(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *id_token, const char *nonce, oidc_jwt_t **jwt, apr_byte_t is_code_flow);
int oidc_proto_javascript_implicit(request_rec *r, oidc_cfg *c);
apr_array_header_t *oidc_proto_supported_flows(apr_pool_t *pool);
apr_byte_t oidc_proto_flow_is_supported(apr_pool_t *pool, const char *flow);
apr_byte_t oidc_proto_validate_authorization_response(request_rec *r, const char *response_type, const char *requested_response_mode, char **code, char **id_token, char **access_token, char **token_type, const char *used_response_mode);
apr_byte_t oidc_proto_jwt_verify(request_rec *r, oidc_cfg *cfg, oidc_jwt_t *jwt, const oidc_jwks_uri_t *jwks_uri, apr_hash_t *symmetric_keys);
apr_byte_t oidc_proto_validate_jwt(request_rec *r, oidc_jwt_t *jwt, const char *iss, apr_byte_t exp_is_mandatory, apr_byte_t iat_is_mandatory, int iat_slack);
apr_byte_t oidc_proto_generate_nonce(request_rec *r, char **nonce, int len);
apr_byte_t oidc_proto_authorization_response_code_idtoken_token(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt);
apr_byte_t oidc_proto_authorization_response_code_idtoken(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt);
apr_byte_t oidc_proto_handle_authorization_response_code_token(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt);
apr_byte_t oidc_proto_handle_authorization_response_code(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt);
apr_byte_t oidc_proto_handle_authorization_response_idtoken_token(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt);
apr_byte_t oidc_proto_handle_authorization_response_idtoken(request_rec *r, oidc_cfg *c, oidc_proto_state_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, oidc_jwt_t **jwt);
// non-static for test.c
apr_byte_t oidc_proto_validate_access_token(request_rec *r, oidc_provider_t *provider, oidc_jwt_t *jwt, const char *response_type, const char *access_token);
apr_byte_t oidc_proto_validate_code(request_rec *r, oidc_provider_t *provider, oidc_jwt_t *jwt, const char *response_type, const char *code);
apr_byte_t oidc_proto_validate_nonce(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *nonce, oidc_jwt_t *jwt);
// oidc_authz.c
typedef apr_byte_t (*oidc_authz_match_claim_fn_type)(request_rec *, const char * const, const json_t * const);
apr_byte_t oidc_authz_match_claim(request_rec *r, const char * const attr_spec, const json_t * const claims);
#ifdef USE_LIBJQ
apr_byte_t oidc_authz_match_claims_expr(request_rec *r, const char * const attr_spec, const json_t * const claims);
#endif
#if MODULE_MAGIC_NUMBER_MAJOR < 20100714
int oidc_authz_worker22(request_rec *r, const json_t *const claims, const require_line *const reqs, int nelts);
#else
authz_status oidc_authz_worker24(request_rec *r, const json_t * const claims, const char *require_args, oidc_authz_match_claim_fn_type match_claim_fn);
#endif
int oidc_oauth_return_www_authenticate(request_rec *r, const char *error, const char *error_description);
// oidc_config.c
#define OIDCPrivateKeyFiles "OIDCPrivateKeyFiles"
#define OIDCRedirectURI "OIDCRedirectURI"
#define OIDCDefaultURL "OIDCDefaultURL"
#define OIDCCookieDomain "OIDCCookieDomain"
#define OIDCClaimPrefix "OIDCClaimPrefix"
#define OIDCRemoteUserClaim "OIDCRemoteUserClaim"
#define OIDCOAuthRemoteUserClaim "OIDCOAuthRemoteUserClaim"
#define OIDCSessionType "OIDCSessionType"
#define OIDCMemCacheServers "OIDCMemCacheServers"
#define OIDCCacheShmMax "OIDCCacheShmMax"
#define OIDCCacheShmEntrySizeMax "OIDCCacheShmEntrySizeMax"
#define OIDCRedisCacheServer "OIDCRedisCacheServer"
#define OIDCCookiePath "OIDCCookiePath"
#define OIDCInfoHook "OIDCInfoHook"
#define OIDCWhiteListedClaims "OIDCWhiteListedClaims"
void *oidc_create_server_config(apr_pool_t *pool, server_rec *svr);
void *oidc_merge_server_config(apr_pool_t *pool, void *BASE, void *ADD);
void *oidc_create_dir_config(apr_pool_t *pool, char *path);
void *oidc_merge_dir_config(apr_pool_t *pool, void *BASE, void *ADD);
void oidc_register_hooks(apr_pool_t *pool);
char *oidc_cfg_dir_discover_url(request_rec *r);
char *oidc_cfg_dir_cookie(request_rec *r);
char *oidc_cfg_dir_cookie_path(request_rec *r);
char *oidc_cfg_dir_authn_header(request_rec *r);
apr_byte_t oidc_cfg_dir_pass_info_in_headers(request_rec *r);
apr_byte_t oidc_cfg_dir_pass_info_in_envvars(request_rec *r);
apr_byte_t oidc_cfg_dir_pass_refresh_token(request_rec *r);
apr_byte_t oidc_cfg_dir_accept_token_in(request_rec *r);
char *oidc_cfg_dir_accept_token_in_option(request_rec *r, const char *key);
int oidc_cfg_token_introspection_interval(request_rec *r);
int oidc_cfg_dir_preserve_post(request_rec *r);
apr_array_header_t *oidc_dir_cfg_pass_cookies(request_rec *r);
apr_array_header_t *oidc_dir_cfg_strip_cookies(request_rec *r);
int oidc_dir_cfg_unauth_action(request_rec *r);
int oidc_dir_cfg_unautz_action(request_rec *r);
char *oidc_dir_cfg_path_auth_request_params(request_rec *r);
char *oidc_dir_cfg_path_scope(request_rec *r);
oidc_valid_function_t oidc_cfg_get_valid_endpoint_auth_function(oidc_cfg *cfg);
int oidc_cfg_cache_encrypt(request_rec *r);
int oidc_cfg_session_cache_fallback_to_cookie(request_rec *r);
const char *oidc_parse_pkce_type(apr_pool_t *pool, const char *arg, oidc_proto_pkce_t **type);
const char *oidc_cfg_claim_prefix(request_rec *r);
// oidc_util.c
int oidc_strnenvcmp(const char *a, const char *b, int len);
int oidc_base64url_encode(request_rec *r, char **dst, const char *src, int src_len, int remove_padding);
int oidc_base64url_decode(apr_pool_t *pool, char **dst, const char *src);
const char *oidc_get_current_url_host(request_rec *r);
char *oidc_get_current_url(request_rec *r);
const char *oidc_get_redirect_uri(request_rec *r, oidc_cfg *c);
const char *oidc_get_redirect_uri_iss(request_rec *r, oidc_cfg *c, oidc_provider_t *provider);
char *oidc_url_encode(const request_rec *r, const char *str, const char *charsToEncode);
char *oidc_normalize_header_name(const request_rec *r, const char *str);
void oidc_util_set_cookie(request_rec *r, const char *cookieName, const char *cookieValue, apr_time_t expires, const char *ext);
char *oidc_util_get_cookie(request_rec *r, const char *cookieName);
apr_byte_t oidc_util_http_get(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, const char *bearer_token, int ssl_validate_server, char **response, int timeout, const char *outgoing_proxy, apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key);
apr_byte_t oidc_util_http_post_form(request_rec *r, const char *url, const apr_table_t *params, const char *basic_auth, const char *bearer_token, int ssl_validate_server, char **response, int timeout, const char *outgoing_proxy, apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key);
apr_byte_t oidc_util_http_post_json(request_rec *r, const char *url, json_t *data, const char *basic_auth, const char *bearer_token, int ssl_validate_server, char **response, int timeout, const char *outgoing_proxy, apr_array_header_t *pass_cookies, const char *ssl_cert, const char *ssl_key);
apr_byte_t oidc_util_request_matches_url(request_rec *r, const char *url);
apr_byte_t oidc_util_request_has_parameter(request_rec *r, const char* param);
apr_byte_t oidc_util_get_request_parameter(request_rec *r, char *name, char **value);
char *oidc_util_encode_json_object(request_rec *r, json_t *json, size_t flags);
apr_byte_t oidc_util_decode_json_object(request_rec *r, const char *str, json_t **json);
apr_byte_t oidc_util_decode_json_and_check_error(request_rec *r, const char *str, json_t **json);
int oidc_util_http_send(request_rec *r, const char *data, size_t data_len, const char *content_type, int success_rvalue);
int oidc_util_html_send(request_rec *r, const char *title, const char *html_head, const char *on_load, const char *html_body, int status_code);
char *oidc_util_escape_string(const request_rec *r, const char *str);
char *oidc_util_unescape_string(const request_rec *r, const char *str);
apr_byte_t oidc_util_read_form_encoded_params(request_rec *r, apr_table_t *table, char *data);
apr_byte_t oidc_util_read_post_params(request_rec *r, apr_table_t *table);
apr_byte_t oidc_util_file_read(request_rec *r, const char *path, apr_pool_t *pool, char **result);
apr_byte_t oidc_util_file_write(request_rec *r, const char *path, const char *data);
apr_byte_t oidc_util_issuer_match(const char *a, const char *b);
int oidc_util_html_send_error(request_rec *r, const char *html_template, const char *error, const char *description, int status_code);
apr_byte_t oidc_util_json_array_has_value(request_rec *r, json_t *haystack, const char *needle);
void oidc_util_set_app_info(request_rec *r, const char *s_key, const char *s_value, const char *claim_prefix, apr_byte_t as_header, apr_byte_t as_env_var);
void oidc_util_set_app_infos(request_rec *r, const json_t *j_attrs, const char *claim_prefix, const char *claim_delimiter, apr_byte_t as_header, apr_byte_t as_env_var);
apr_hash_t *oidc_util_spaced_string_to_hashtable(apr_pool_t *pool, const char *str);
apr_byte_t oidc_util_spaced_string_equals(apr_pool_t *pool, const char *a, const char *b);
apr_byte_t oidc_util_spaced_string_contains(apr_pool_t *pool, const char *str, const char *match);
apr_byte_t oidc_json_object_get_string(apr_pool_t *pool, json_t *json, const char *name, char **value, const char *default_value);
apr_byte_t oidc_json_object_get_int(apr_pool_t *pool, json_t *json, const char *name, int *value, const int default_value);
apr_byte_t oidc_json_object_get_bool(apr_pool_t *pool, json_t *json, const char *name, int *value, const int default_value);
char *oidc_util_html_escape(apr_pool_t *pool, const char *input);
void oidc_util_table_add_query_encoded_params(apr_pool_t *pool, apr_table_t *table, const char *params);
apr_hash_t * oidc_util_merge_key_sets(apr_pool_t *pool, apr_hash_t *k1, apr_hash_t *k2);
apr_byte_t oidc_util_regexp_substitute(apr_pool_t *pool, const char *input, const char *regexp, const char *replace, char **output, char **error_str);
apr_byte_t oidc_util_regexp_first_match(apr_pool_t *pool, const char *input, const char *regexp, char **output, char **error_str);
apr_byte_t oidc_util_json_merge(request_rec *r, json_t *src, json_t *dst);
int oidc_util_cookie_domain_valid(const char *hostname, char *cookie_domain);
apr_byte_t oidc_util_hash_string_and_base64url_encode(request_rec *r, const char *openssl_hash_algo, const char *input, char **output);
apr_byte_t oidc_util_jwt_create(request_rec *r, const char *secret, json_t *payload, char **compact_encoded_jwt);
apr_byte_t oidc_util_jwt_verify(request_rec *r, const char *secret, const char *compact_encoded_jwt, json_t **result);
char *oidc_util_get_chunked_cookie(request_rec *r, const char *cookieName, int cookie_chunk_size);
void oidc_util_set_chunked_cookie(request_rec *r, const char *cookieName, const char *cookieValue, apr_time_t expires, int chunkSize, const char *ext);
apr_byte_t oidc_util_create_symmetric_key(request_rec *r, const char *client_secret, unsigned int r_key_len, const char *hash_algo, apr_byte_t set_kid, oidc_jwk_t **jwk);
apr_hash_t * oidc_util_merge_symmetric_key(apr_pool_t *pool, apr_hash_t *private_keys, oidc_jwk_t *jwk);
const char *oidc_util_get_provided_token_binding_id(const request_rec *r);
char *oidc_util_http_query_encoded_url(request_rec *r, const char *url, const apr_table_t *params);
char *oidc_util_get_full_path(apr_pool_t *pool, const char *abs_or_rel_filename);
/* HTTP header constants */
#define OIDC_HTTP_HDR_COOKIE "Cookie"
#define OIDC_HTTP_HDR_SET_COOKIE "Set-Cookie"
#define OIDC_HTTP_HDR_USER_AGENT "User-Agent"
#define OIDC_HTTP_HDR_X_FORWARDED_FOR "X-Forwarded-For"
#define OIDC_HTTP_HDR_CONTENT_TYPE "Content-Type"
#define OIDC_HTTP_HDR_X_REQUESTED_WITH "X-Requested-With"
#define OIDC_HTTP_HDR_ACCEPT "Accept"
#define OIDC_HTTP_HDR_AUTHORIZATION "Authorization"
#define OIDC_HTTP_HDR_X_FORWARDED_PROTO "X-Forwarded-Proto"
#define OIDC_HTTP_HDR_X_FORWARDED_PORT "X-Forwarded-Port"
#define OIDC_HTTP_HDR_X_FORWARDED_HOST "X-Forwarded-Host"
#define OIDC_HTTP_HDR_HOST "Host"
#define OIDC_HTTP_HDR_LOCATION "Location"
#define OIDC_HTTP_HDR_CACHE_CONTROL "Cache-Control"
#define OIDC_HTTP_HDR_PRAGMA "Pragma"
#define OIDC_HTTP_HDR_P3P "P3P"
#define OIDC_HTTP_HDR_EXPIRES "Expires"
#define OIDC_HTTP_HDR_X_FRAME_OPTIONS "X-Frame-Options"
#define OIDC_HTTP_HDR_WWW_AUTHENTICATE "WWW-Authenticate"
#define OIDC_HTTP_HDR_INCLUDE_REFERRED_TOKEN_BINDING_ID "Include-Referred-Token-Binding-ID"
#define OIDC_HTTP_HDR_VAL_XML_HTTP_REQUEST "XMLHttpRequest"
void oidc_util_hdr_in_set(const request_rec *r, const char *name, const char *value);
const char *oidc_util_hdr_in_cookie_get(const request_rec *r);
void oidc_util_hdr_in_cookie_set(const request_rec *r, const char *value);
const char *oidc_util_hdr_in_user_agent_get(const request_rec *r);
const char *oidc_util_hdr_in_x_forwarded_for_get(const request_rec *r);
const char *oidc_util_hdr_in_content_type_get(const request_rec *r);
const char *oidc_util_hdr_in_x_requested_with_get(const request_rec *r);
const char *oidc_util_hdr_in_accept_get(const request_rec *r);
const char *oidc_util_hdr_in_authorization_get(const request_rec *r);
const char *oidc_util_hdr_in_x_forwarded_proto_get(const request_rec *r);
const char *oidc_util_hdr_in_x_forwarded_port_get(const request_rec *r);
const char *oidc_util_hdr_in_x_forwarded_host_get(const request_rec *r);
const char *oidc_util_hdr_in_host_get(const request_rec *r);
void oidc_util_hdr_out_location_set(const request_rec *r, const char *value);
const char *oidc_util_hdr_out_location_get(const request_rec *r);
void oidc_util_hdr_err_out_add(const request_rec *r, const char *name, const char *value);
// oidc_metadata.c
apr_byte_t oidc_metadata_provider_retrieve(request_rec *r, oidc_cfg *cfg, const char *issuer, const char *url, json_t **j_metadata, char **response);
apr_byte_t oidc_metadata_provider_parse(request_rec *r, oidc_cfg *cfg, json_t *j_provider, oidc_provider_t *provider);
apr_byte_t oidc_metadata_list(request_rec *r, oidc_cfg *cfg, apr_array_header_t **arr);
apr_byte_t oidc_metadata_get(request_rec *r, oidc_cfg *cfg, const char *selected, oidc_provider_t **provider, apr_byte_t allow_discovery);
apr_byte_t oidc_metadata_jwks_get(request_rec *r, oidc_cfg *cfg, const oidc_jwks_uri_t *jwks_uri, json_t **j_jwks, apr_byte_t *refresh);
// oidc_session.c
typedef struct {
char uuid[APR_UUID_FORMATTED_LENGTH + 1]; /* unique id */
const char *remote_user; /* user who owns this particular session */
json_t *state; /* the state for this session, encoded in a JSON object */
apr_time_t expiry; /* if > 0, the time of expiry of this session */
} oidc_session_t;
apr_byte_t oidc_session_load(request_rec *r, oidc_session_t **z);
apr_byte_t oidc_session_get(request_rec *r, oidc_session_t *z, const char *key, const char **value);
apr_byte_t oidc_session_set(request_rec *r, oidc_session_t *z, const char *key, const char *value);
apr_byte_t oidc_session_save(request_rec *r, oidc_session_t *z, apr_byte_t first_time);
apr_byte_t oidc_session_kill(request_rec *r, oidc_session_t *z);
apr_byte_t oidc_session_free(request_rec *r, oidc_session_t *z);
void oidc_session_set_userinfo_jwt(request_rec *r, oidc_session_t *z, const char *userinfo_jwt);
const char * oidc_session_get_userinfo_jwt(request_rec *r, oidc_session_t *z);
void oidc_session_set_userinfo_claims(request_rec *r, oidc_session_t *z, const char *claims);
const char * oidc_session_get_userinfo_claims(request_rec *r, oidc_session_t *z);
json_t *oidc_session_get_userinfo_claims_json(request_rec *r, oidc_session_t *z);
void oidc_session_set_idtoken_claims(request_rec *r, oidc_session_t *z, const char *idtoken_claims);
const char * oidc_session_get_idtoken_claims(request_rec *r, oidc_session_t *z);
json_t *oidc_session_get_idtoken_claims_json(request_rec *r, oidc_session_t *z);
void oidc_session_set_idtoken(request_rec *r, oidc_session_t *z, const char *s_id_token);
const char * oidc_session_get_idtoken(request_rec *r, oidc_session_t *z);
void oidc_session_set_access_token(request_rec *r, oidc_session_t *z, const char *access_token);
const char * oidc_session_get_access_token(request_rec *r, oidc_session_t *z);
void oidc_session_set_access_token_expires(request_rec *r, oidc_session_t *z, const int expires_in);
const char * oidc_session_get_access_token_expires(request_rec *r, oidc_session_t *z);
void oidc_session_set_refresh_token(request_rec *r, oidc_session_t *z, const char *refresh_token);
const char * oidc_session_get_refresh_token(request_rec *r, oidc_session_t *z);
void oidc_session_set_session_expires(request_rec *r, oidc_session_t *z, const apr_time_t expires);
apr_time_t oidc_session_get_session_expires(request_rec *r, oidc_session_t *z);
void oidc_session_set_cookie_domain(request_rec *r, oidc_session_t *z, const char *cookie_domain);
const char * oidc_session_get_cookie_domain(request_rec *r, oidc_session_t *z);
void oidc_session_reset_userinfo_last_refresh(request_rec *r, oidc_session_t *z);
apr_time_t oidc_session_get_userinfo_last_refresh(request_rec *r, oidc_session_t *z);
void oidc_session_reset_access_token_last_refresh(request_rec *r, oidc_session_t *z);
apr_time_t oidc_session_get_access_token_last_refresh(request_rec *r, oidc_session_t *z);
void oidc_session_set_request_state(request_rec *r, oidc_session_t *z, const char *request_state);
const char * oidc_session_get_request_state(request_rec *r, oidc_session_t *z);
void oidc_session_set_original_url(request_rec *r, oidc_session_t *z, const char *original_url);
const char * oidc_session_get_original_url(request_rec *r, oidc_session_t *z);
void oidc_session_set_session_state(request_rec *r, oidc_session_t *z, const char *session_state);
const char * oidc_session_get_session_state(request_rec *r, oidc_session_t *z);
void oidc_session_set_issuer(request_rec *r, oidc_session_t *z, const char *issuer);
const char * oidc_session_get_issuer(request_rec *r, oidc_session_t *z);
void oidc_session_set_client_id(request_rec *r, oidc_session_t *z, const char *client_id);
const char * oidc_session_get_client_id(request_rec *r, oidc_session_t *z);
void oidc_session_set_check_session_iframe(request_rec *r, oidc_session_t *z, const char *check_session_iframe);
const char * oidc_session_get_check_session_iframe(request_rec *r, oidc_session_t *z);
void oidc_session_set_logout_endpoint(request_rec *r, oidc_session_t *z, const char *logout_endpoint);
const char * oidc_session_get_logout_endpoint(request_rec *r, oidc_session_t *z);
#endif /* MOD_AUTH_OPENIDC_H_ */
mod_auth_openidc-2.3.3/src/jose.h 0000644 0000765 0000024 00000022050 13200665522 016556 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* JSON Object Signing and Encryption
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*/
#ifndef MOD_AUTH_OPENIDC_JOSE_H_
#define MOD_AUTH_OPENIDC_JOSE_H_
#include
#include "apr_pools.h"
#include "apr_tables.h"
#include "apr_hash.h"
#include "apr_strings.h"
#include "jansson.h"
#include "cjose/cjose.h"
#define OIDC_JOSE_ALG_SHA256 "sha256"
/* indicate support for OpenSSL version dependent features */
#define OIDC_JOSE_EC_SUPPORT OPENSSL_VERSION_NUMBER >= 0x1000100f
#define OIDC_JOSE_GCM_SUPPORT OPENSSL_VERSION_NUMBER >= 0x1000100f
/* error message element sizes */
#define OIDC_JOSE_ERROR_TEXT_LENGTH 200
#define OIDC_JOSE_ERROR_SOURCE_LENGTH 80
#define OIDC_JOSE_ERROR_FUNCTION_LENGTH 80
/* struct for returning errors to the caller */
typedef struct {
char source[OIDC_JOSE_ERROR_SOURCE_LENGTH];
int line;
char function[OIDC_JOSE_ERROR_FUNCTION_LENGTH];
char text[OIDC_JOSE_ERROR_TEXT_LENGTH];
} oidc_jose_error_t;
/*
* error handling functions
*/
void _oidc_jose_error_set(oidc_jose_error_t *, const char *, const int,
const char *, const char *msg, ...);
#define oidc_jose_error(err, msg, ...) _oidc_jose_error_set(err, __FILE__, __LINE__, __FUNCTION__, msg, ##__VA_ARGS__)
#define oidc_jose_error_openssl(err, msg, ...) _oidc_jose_error_set(err, __FILE__, __LINE__, __FUNCTION__, "%s() failed: %s", msg, ERR_error_string(ERR_get_error(), NULL), ##__VA_ARGS__)
#define oidc_jose_e2s(pool, err) apr_psprintf(pool, "[%s:%d: %s]: %s\n", err.source, err.line, err.function, err.text)
#define oidc_cjose_e2s(pool, cjose_err) apr_psprintf(pool, "%s [file: %s, function: %s, line: %ld]\n", cjose_err.message, cjose_err.file, cjose_err.function, cjose_err.line)
/*
* helper functions
*/
/* helpers to find out about the supported ala/enc algorithms */
apr_array_header_t *oidc_jose_jws_supported_algorithms(apr_pool_t *pool);
apr_byte_t oidc_jose_jws_algorithm_is_supported(apr_pool_t *pool,
const char *alg);
apr_array_header_t *oidc_jose_jwe_supported_algorithms(apr_pool_t *pool);
apr_byte_t oidc_jose_jwe_algorithm_is_supported(apr_pool_t *pool,
const char *alg);
apr_array_header_t *oidc_jose_jwe_supported_encryptions(apr_pool_t *pool);
apr_byte_t oidc_jose_jwe_encryption_is_supported(apr_pool_t *pool,
const char *enc);
/* hash helpers */
apr_byte_t oidc_jose_hash_string(apr_pool_t *pool, const char *alg,
const char *msg, char **hash, unsigned int *hash_len,
oidc_jose_error_t *err);
int oidc_jose_hash_length(const char *alg);
apr_byte_t oidc_jose_hash_bytes(apr_pool_t *pool, const char *s_digest,
const unsigned char *input, unsigned int input_len,
unsigned char **output, unsigned int *output_len,
oidc_jose_error_t *err);
/* return a string claim value from a JSON object */
apr_byte_t oidc_jose_get_string(apr_pool_t *pool, json_t *json,
const char *claim_name, apr_byte_t is_mandatory, char **result,
oidc_jose_error_t *err);
/* a parsed JWK/JWT JSON object */
typedef struct oidc_jose_json_t {
/* parsed JSON struct representation */
json_t *json;
/* string representation */
char *str;
} oidc_jose_json_t;
/*
* JSON Web Key handling
*/
/* parsed JWK */
typedef struct oidc_jwk_t {
/* key type */
int kty;
/* key identifier */
char *kid;
/* cjose JWK structure */
cjose_jwk_t *cjose_jwk;
} oidc_jwk_t;
/* decrypt a JWT */
apr_byte_t oidc_jwe_decrypt(apr_pool_t *pool, const char *input_json,
apr_hash_t *keys, char **s_json, oidc_jose_error_t *err,
apr_byte_t import_must_succeed);
/* parse a JSON string to a JWK struct */
oidc_jwk_t *oidc_jwk_parse(apr_pool_t *pool, const char *s_json,
oidc_jose_error_t *err);
/* parse a JSON object in to a JWK struct */
apr_byte_t oidc_jwk_parse_json(apr_pool_t *pool, json_t *json, oidc_jwk_t **jwk,
oidc_jose_error_t *err);
/* convert a JWK struct to a JSON string */
apr_byte_t oidc_jwk_to_json(apr_pool_t *pool, oidc_jwk_t *jwk, char **s_json,
oidc_jose_error_t *err);
/* destroy resources allocated for a JWK struct */
void oidc_jwk_destroy(oidc_jwk_t *jwk);
/* destroy a list of JWKs structs */
void oidc_jwk_list_destroy(apr_pool_t *pool, apr_hash_t *key);
/* create an "oct" symmetric JWK */
oidc_jwk_t *oidc_jwk_create_symmetric_key(apr_pool_t *pool, const char *kid,
const unsigned char *key, unsigned int key_len, apr_byte_t set_kid,
oidc_jose_error_t *err);
/* parse an X.509 PEM formatted certificate file with an RSA public key to a JWK struct */
apr_byte_t oidc_jwk_parse_rsa_public_key(apr_pool_t *pool, const char *kid,
const char *filename, oidc_jwk_t **jwk, oidc_jose_error_t *err);
/* parse an X.509 PEM formatted RSA private key file to a JWK */
apr_byte_t oidc_jwk_parse_rsa_private_key(apr_pool_t *pool, const char *kid,
const char *filename, oidc_jwk_t **jwk, oidc_jose_error_t *err);
/*
* JSON Web Token handling
*/
/* represents NULL timestamp */
#define OIDC_JWT_CLAIM_TIME_EMPTY -1
/* a parsed JWT header */
typedef struct oidc_jwt_hdr_t {
/* parsed header value */
oidc_jose_json_t value;
/* JWT "alg" claim value; signing algorithm */
char *alg;
/* JWT "kid" claim value; key identifier */
char *kid;
/* JWT "enc" claim value; encryption algorithm */
char *enc;
} oidc_jwt_hdr_t;
/* parsed JWT payload */
typedef struct oidc_jwt_payload_t {
/* parsed payload value */
oidc_jose_json_t value;
/* JWT "iss" claim value; JWT issuer */
char *iss;
/* JWT "sub" claim value; subject/principal */
char *sub;
/* parsed JWT "exp" claim value; token expiry */
double exp;
/* parsed JWT "iat" claim value; issued-at timestamp */
double iat;
} oidc_jwt_payload_t;
/* parsed JWT */
typedef struct oidc_jwt_t {
/* parsed JWT header */
oidc_jwt_hdr_t header;
/* parsed JWT payload */
oidc_jwt_payload_t payload;
/* cjose JWS structure */
cjose_jws_t *cjose_jws;
} oidc_jwt_t;
/* parse a string into a JSON Web Token struct and (optionally) decrypt it */
apr_byte_t oidc_jwt_parse(apr_pool_t *pool, const char *s_json,
oidc_jwt_t **j_jwt, apr_hash_t *keys, oidc_jose_error_t *err);
/* sign a JWT with a JWK */
apr_byte_t oidc_jwt_sign(apr_pool_t *pool, oidc_jwt_t *jwt, oidc_jwk_t *jwk,
oidc_jose_error_t *err);
/* verify a JWT a key in a list of JWKs */
apr_byte_t oidc_jwt_verify(apr_pool_t *pool, oidc_jwt_t *jwt, apr_hash_t *keys,
oidc_jose_error_t *err);
/* perform compact serialization on a JWT and return the resulting string */
char *oidc_jwt_serialize(apr_pool_t *pool, oidc_jwt_t *jwt,
oidc_jose_error_t *err);
/* encrypt JWT */
apr_byte_t oidc_jwt_encrypt(apr_pool_t *pool, oidc_jwt_t *jwe, oidc_jwk_t *jwk,
const char *payload, char **serialized, oidc_jose_error_t *err);
/* create a new JWT */
oidc_jwt_t *oidc_jwt_new(apr_pool_t *pool, int create_header,
int create_payload);
/* destroy resources allocated for JWT */
void oidc_jwt_destroy(oidc_jwt_t *);
/* get a header value from a JWT */
const char *oidc_jwt_hdr_get(oidc_jwt_t *jwt, const char *key);
/* return the key type of a JWT */
int oidc_jwt_alg2kty(oidc_jwt_t *jwt);
/* return the key size for an algorithm */
unsigned int oidc_alg2keysize(const char *alg);
#endif /* MOD_AUTH_OPENIDC_JOSE_H_ */
mod_auth_openidc-2.3.3/src/parse.h 0000644 0000765 0000024 00000015713 13203320522 016727 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Validation and parsing of configuration values.
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*/
#ifndef MOD_AUTH_OPENIDC_PARSE_H_
#define MOD_AUTH_OPENIDC_PARSE_H_
#include "apr_pools.h"
#define OIDC_CONFIG_STRING_UNSET "_UNSET_"
#define OIDC_CONFIG_STRING_EMPTY ""
#define OIDC_CONFIG_POS_INT_UNSET -1
#define OIDC_CLAIM_FORMAT_RELATIVE "relative"
#define OIDC_CLAIM_FORMAT_ABSOLUTE "absolute"
#define OIDC_CLAIM_REQUIRED_MANDATORY "mandatory"
#define OIDC_CLAIM_REQUIRED_OPTIONAL "optional"
#define OIDC_PKCE_METHOD_PLAIN "plain"
#define OIDC_PKCE_METHOD_S256 "S256"
#define OIDC_PKCE_METHOD_REFERRED_TB "referred_tb"
const char *oidc_valid_url(apr_pool_t *pool, const char *arg, const char *scheme);
const char *oidc_valid_http_url(apr_pool_t *pool, const char *arg);
const char *oidc_valid_dir(apr_pool_t *pool, const char *arg);
const char *oidc_valid_cookie_domain(apr_pool_t *pool, const char *arg);
const char *oidc_valid_endpoint_auth_method(apr_pool_t *pool,const char *arg);
const char *oidc_valid_endpoint_auth_method_no_private_key(apr_pool_t *pool, const char *arg);
const char *oidc_valid_response_type(apr_pool_t *pool, const char *arg);
const char *oidc_valid_pkce_method(apr_pool_t *pool, const char *arg);
const char *oidc_valid_response_mode(apr_pool_t *pool, const char *arg);
const char *oidc_valid_signed_response_alg(apr_pool_t *pool, const char *arg);
const char *oidc_valid_encrypted_response_alg(apr_pool_t *pool, const char *arg);
const char *oidc_valid_encrypted_response_enc(apr_pool_t *pool, const char *arg);
const char *oidc_valid_claim_format(apr_pool_t *pool, const char *arg);
const char *oidc_valid_introspection_method(apr_pool_t *pool, const char *arg);
const char *oidc_valid_session_max_duration(apr_pool_t *pool, int v);
const char *oidc_valid_jwks_refresh_interval(apr_pool_t *pool, int v);
const char *oidc_valid_idtoken_iat_slack(apr_pool_t *pool, int v);
const char *oidc_valid_userinfo_refresh_interval(apr_pool_t *pool, int v);
const char *oidc_valid_userinfo_token_method(apr_pool_t *pool, const char *arg);
const char *oidc_valid_token_binding_policy(apr_pool_t *pool, const char *arg);
const char *oidc_valid_auth_request_method(apr_pool_t *pool, const char *arg);
const char *oidc_parse_int(apr_pool_t *pool, const char *arg, int *int_value);
const char *oidc_parse_boolean(apr_pool_t *pool, const char *arg, int *bool_value);
const char *oidc_parse_cache_type(apr_pool_t *pool, const char *arg, oidc_cache_t **type);
const char *oidc_parse_session_type(apr_pool_t *pool, const char *arg, int *type, int *persistent);
const char *oidc_parse_cache_shm_entry_size_max(apr_pool_t *pool, const char *arg, int *int_value);
const char *oidc_parse_session_inactivity_timeout(apr_pool_t *pool, const char *arg, int *int_value);
const char *oidc_parse_session_max_duration(apr_pool_t *pool, const char *arg, int *int_value);
const char *oidc_parse_enc_kid_key_tuple(apr_pool_t *pool, const char *tuple, char **kid, char **key, int *key_len, apr_byte_t triplet);
const char *oidc_parse_pass_idtoken_as(apr_pool_t *pool, const char *v1, const char *v2, const char *v3, int *int_value);
const char *oidc_parse_pass_userinfo_as(apr_pool_t *pool, const char *v1, const char *v2, const char *v3, int *int_value);
const char *oidc_parse_accept_oauth_token_in(apr_pool_t *pool, const char *arg, int *b_value, apr_hash_t *list_options);
const char *oidc_accept_oauth_token_in2str(apr_pool_t *pool, apr_byte_t v);
const char *oidc_parse_claim_required(apr_pool_t *pool, const char *arg, int *is_required);
const char *oidc_parse_set_claims_as(apr_pool_t *pool, const char *arg, int *in_headers, int *in_env_vars);
const char *oidc_parse_unauth_action(apr_pool_t *pool, const char *arg, int *action);
const char *oidc_parse_unautz_action(apr_pool_t *pool, const char *arg, int *action);
const char *oidc_parse_jwks_refresh_interval(apr_pool_t *pool, const char *arg, int *int_value);
const char *oidc_parse_idtoken_iat_slack(apr_pool_t *pool, const char *arg, int *int_value);
const char *oidc_parse_userinfo_refresh_interval(apr_pool_t *pool, const char *arg, int *int_value);
const char *oidc_parse_userinfo_token_method(apr_pool_t *pool, const char *arg, int *int_value);
const char *oidc_parse_info_hook_data(apr_pool_t *pool, const char *arg, apr_hash_t **hook_data);
const char *oidc_parse_token_binding_policy(apr_pool_t *pool, const char *arg, int *int_value);
const char *oidc_token_binding_policy2str(apr_pool_t *pool, int v);
const char *oidc_parse_auth_request_method(apr_pool_t *pool, const char *arg, int *method);
typedef const char *(*oidc_valid_int_function_t)(apr_pool_t *, int);
typedef const char *(*oidc_valid_function_t)(apr_pool_t *, const char *);
const char *oidc_valid_string_in_array(apr_pool_t *pool, json_t *json, const char *key, oidc_valid_function_t valid_function, char **value, apr_byte_t optional);
#endif /* MOD_AUTH_OPENIDC_PARSE_H_ */
mod_auth_openidc-2.3.3/src/cache/cache.h 0000644 0000765 0000024 00000014045 13200625410 017721 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* mem_cache-like interface and semantics (string keys/values) using a storage backend
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*/
#ifndef _MOD_AUTH_OPENIDC_CACHE_H_
#define _MOD_AUTH_OPENIDC_CACHE_H_
#include "apr_global_mutex.h"
#include "apr_shm.h"
typedef void * (*oidc_cache_cfg_create)(apr_pool_t *pool);
typedef int (*oidc_cache_post_config_function)(server_rec *s);
typedef int (*oidc_cache_child_init_function)(apr_pool_t *p, server_rec *s);
typedef apr_byte_t (*oidc_cache_get_function)(request_rec *r,
const char *section, const char *key, const char **value);
typedef apr_byte_t (*oidc_cache_set_function)(request_rec *r,
const char *section, const char *key, const char *value,
apr_time_t expiry);
typedef int (*oidc_cache_destroy_function)(server_rec *s);
typedef struct oidc_cache_t {
const char *name;
int encrypt_by_default;
oidc_cache_post_config_function post_config;
oidc_cache_child_init_function child_init;
oidc_cache_get_function get;
oidc_cache_set_function set;
oidc_cache_destroy_function destroy;
} oidc_cache_t;
typedef struct oidc_cache_mutex_t {
apr_global_mutex_t *mutex;
char *mutex_filename;
apr_shm_t *shm;
int *sema;
} oidc_cache_mutex_t;
oidc_cache_mutex_t *oidc_cache_mutex_create(apr_pool_t *pool);
apr_byte_t oidc_cache_mutex_post_config(server_rec *s, oidc_cache_mutex_t *m,
const char *type);
apr_status_t oidc_cache_mutex_child_init(apr_pool_t *p, server_rec *s,
oidc_cache_mutex_t *m);
apr_byte_t oidc_cache_mutex_lock(request_rec *r, oidc_cache_mutex_t *m);
apr_byte_t oidc_cache_mutex_unlock(request_rec *r, oidc_cache_mutex_t *m);
apr_byte_t oidc_cache_mutex_destroy(server_rec *s, oidc_cache_mutex_t *m);
apr_byte_t oidc_cache_get(request_rec *r, const char *section, const char *key,
char **value);
apr_byte_t oidc_cache_set(request_rec *r, const char *section, const char *key,
const char *value, apr_time_t expiry);
#define OIDC_CACHE_SECTION_SESSION "s"
#define OIDC_CACHE_SECTION_NONCE "n"
#define OIDC_CACHE_SECTION_JWKS "j"
#define OIDC_CACHE_SECTION_ACCESS_TOKEN "a"
#define OIDC_CACHE_SECTION_PROVIDER "p"
#define OIDC_CACHE_SECTION_JTI "t"
#define OIDC_CACHE_SECTION_REQUEST_URI "r"
#define oidc_cache_get_session(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_SESSION, key, value)
#define oidc_cache_get_nonce(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_NONCE, key, value)
#define oidc_cache_get_jwks(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_JWKS, key, value)
#define oidc_cache_get_access_token(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_ACCESS_TOKEN, key, value)
#define oidc_cache_get_provider(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_PROVIDER, key, value)
#define oidc_cache_get_jti(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_JTI, key, value)
#define oidc_cache_get_request_uri(r, key, value) oidc_cache_get(r, OIDC_CACHE_SECTION_REQUEST_URI, key, value)
#define oidc_cache_set_session(r, key, value, expiry) oidc_cache_set(r, OIDC_CACHE_SECTION_SESSION, key, value, expiry)
#define oidc_cache_set_nonce(r, key, value, expiry) oidc_cache_set(r, OIDC_CACHE_SECTION_NONCE, key, value, expiry)
#define oidc_cache_set_jwks(r, key, value, expiry) oidc_cache_set(r, OIDC_CACHE_SECTION_JWKS, key, value, expiry)
#define oidc_cache_set_access_token(r, key, value, expiry) oidc_cache_set(r, OIDC_CACHE_SECTION_ACCESS_TOKEN, key, value, expiry)
#define oidc_cache_set_provider(r, key, value, expiry) oidc_cache_set(r, OIDC_CACHE_SECTION_PROVIDER, key, value, expiry)
#define oidc_cache_set_jti(r, key, value, expiry) oidc_cache_set(r, OIDC_CACHE_SECTION_JTI, key, value, expiry)
#define oidc_cache_set_request_uri(r, key, value, expiry) oidc_cache_set(r, OIDC_CACHE_SECTION_REQUEST_URI, key, value, expiry)
extern oidc_cache_t oidc_cache_file;
extern oidc_cache_t oidc_cache_memcache;
extern oidc_cache_t oidc_cache_shm;
#ifdef USE_LIBHIREDIS
extern oidc_cache_t oidc_cache_redis;
#endif
#endif /* _MOD_AUTH_OPENIDC_CACHE_H_ */
mod_auth_openidc-2.3.3/src/pcre_subst.h 0000644 0000765 0000024 00000002704 13200625410 017763 0 ustar hzandbelt staff /*************************************************
* PCRE string replacement *
*************************************************/
/*
PCRE is a library of functions to support regular expressions whose syntax
and semantics are as close as possible to those of the Perl 5 language.
pcre_subst is a wrapper around pcre_exec designed to make it easier to
perform PERL style replacements with PCRE.
Written by: Bert Driehuis
Copyright (c) 2000 Bert Driehuis
-----------------------------------------------------------------------------
Permission is granted to anyone to use this software for any purpose on any
computer system, and to redistribute it freely, subject to the following
restrictions:
1. This software is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
2. The origin of this software must not be misrepresented, either by
explicit claim or by omission.
3. Altered versions must be plainly marked as such, and must not be
misrepresented as being the original software.
4. If PCRE is embedded in any software that is released under the GNU
General Purpose Licence (GPL), then the terms of that licence shall
supersede any condition above with which it is incompatible.
*/
char *pcre_subst(const pcre *, const pcre_extra *, const char *, int, int, int, const char *);
mod_auth_openidc-2.3.3/test/test.c 0000755 0000765 0000024 00000153661 13200625410 016770 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*
**************************************************************************/
#include
#include
#include
#include
#include "apr.h"
#include "apr_errno.h"
#include "apr_general.h"
#include "apr_time.h"
#include "apr_base64.h"
#include "mod_auth_openidc.h"
extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
static int test_nr_run = 0;
static char TST_ERR_MSG[512];
static int TST_RC;
#define TST_FORMAT(fmt) \
" # %s: error in %s: result \"" fmt "\" != expected \"" fmt "\""
#define TST_ASSERT(message, expression) \
TST_RC = (expression); \
if (!TST_RC) { \
sprintf(TST_ERR_MSG, TST_FORMAT("%d"), __FUNCTION__, message, TST_RC, 1); \
return TST_ERR_MSG; \
}
#define TST_ASSERT_ERR(message, expression, pool, err) \
TST_RC = (expression); \
if (!TST_RC) { \
sprintf(TST_ERR_MSG, TST_FORMAT("%d") " %s", __FUNCTION__, message, TST_RC, 1, oidc_jose_e2s(pool, err)); \
return TST_ERR_MSG; \
}
#define TST_ASSERT_CJOSE_ERR(message, expression, pool, cjose_err) \
TST_RC = (expression); \
if (!TST_RC) { \
sprintf(TST_ERR_MSG, TST_FORMAT("%d") " %s", __FUNCTION__, message, TST_RC, 1, oidc_cjose_e2s(pool, cjose_err)); \
return TST_ERR_MSG; \
}
#define TST_ASSERT_STR(message, result, expected) \
TST_RC = (result && expected) ? (apr_strnatcmp(result, expected) != 0) : ((result != NULL) || (expected != NULL)); \
if (TST_RC) { \
sprintf(TST_ERR_MSG, TST_FORMAT("%s"), __FUNCTION__, message, result ? result : "(null)", expected ? expected : "(null)"); \
return TST_ERR_MSG; \
}
#define TST_ASSERT_STRN(message, result, expected, len) \
TST_RC = (result && expected) ? (strncmp(result, expected, len) != 0) : ((result != NULL) || (expected != NULL)); \
if (TST_RC) { \
sprintf(TST_ERR_MSG, TST_FORMAT("%s"), __FUNCTION__, message, result ? result : "(null)", expected ? expected : "(null)"); \
return TST_ERR_MSG; \
}
#define TST_ASSERT_LONG(message, result, expected) \
if (result != expected) { \
sprintf(TST_ERR_MSG, TST_FORMAT("%ld"), __FUNCTION__, message, result, expected); \
return TST_ERR_MSG; \
}
#define TST_RUN(test, pool) message = test(pool); test_nr_run++; if (message) return message;
static char *_jwk_parse(apr_pool_t *pool, const char *s, oidc_jwk_t **jwk,
oidc_jose_error_t *err) {
oidc_jwk_t *k = oidc_jwk_parse(pool, s, err);
TST_ASSERT_ERR("oidc_jwk_parse", k != NULL, pool, (*err));
*jwk = k;
return 0;
}
static char *test_jwt_parse(apr_pool_t *pool) {
// from http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20
// 3.1. Example JWT
char *s =
apr_pstrdup(pool,
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9"
".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
".dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk");
oidc_jose_error_t err;
oidc_jwt_t *jwt = NULL;
TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(pool, s, &jwt, NULL, &err),
pool, err);
TST_ASSERT_STR("header.alg", jwt->header.alg, "HS256");
TST_ASSERT_STR("header.enc", jwt->header.enc, NULL);
TST_ASSERT_STR("header.kid", jwt->header.kid, NULL);
TST_ASSERT_STR("payload.iss", jwt->payload.iss, "joe");
TST_ASSERT_LONG("payload.exp", (long )jwt->payload.exp, 1300819380L);
apr_hash_t *keys = apr_hash_make(pool);
oidc_jwk_t *jwk;
const char * k =
"{\"kty\":\"oct\", \"k\":\"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\"}";
jwk = NULL;
TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, k, &jwk, &err) == 0,
pool, err);
apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk);
TST_ASSERT_ERR("oidc_jwt_verify", oidc_jwt_verify(pool, jwt, keys, &err),
pool, err);
oidc_jwt_destroy(jwt);
oidc_jwk_destroy(jwk);
s[5] = OIDC_CHAR_DOT;
TST_ASSERT_ERR("corrupted header (1) oidc_jwt_parse",
oidc_jwt_parse(pool, s, &jwt, NULL, &err) == FALSE, pool, err);
oidc_jwt_destroy(jwt);
s[0] = '\0';
TST_ASSERT_ERR("corrupted header (2) oidc_jwt_parse",
oidc_jwt_parse(pool, s, &jwt, NULL, &err) == FALSE, pool, err);
oidc_jwt_destroy(jwt);
return 0;
}
#if (APR_JWS_EC_SUPPORT)
static char *test_jwt_verify_ec(apr_pool_t *pool) {
// {
// "sub": "joe",
// "aud": "ac_oic_client",
// "jti": "oDWivWPJB47zkjOm2cygDv",
// "iss": "https://localhost:9031",
// "iat": 1467997207,
// "exp": 1467997507,
// "nonce": "WLxmv5StYyUk9JlWI8SaXTLPkGZ0Vs8aSTdj_VQ6rao"
// }
char *s_jwt = apr_pstrdup(pool, "eyJhbGciOiJFUzI1NiIsImtpZCI6ImY2cXRqIn0.eyJzdWIiOiJqb2UiLCJhdWQiOiJhY19vaWNfY2xpZW50IiwianRpIjoib0RXaXZXUEpCNDd6a2pPbTJjeWdEdiIsImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTAzMSIsImlhdCI6MTQ2Nzk5NzIwNywiZXhwIjoxNDY3OTk3NTA3LCJub25jZSI6IldMeG12NVN0WXlVazlKbFdJOFNhWFRMUGtHWjBWczhhU1Rkal9WUTZyYW8ifQ.2kqX56QNow37gOlnfLn0SIzwie4mLLIUx_p9OSQa0hiUXKQWQLmMYBjIp5qGh2-R-KPHwNEBxqXwuPgXG4Y7Eg");
oidc_jwt_t *jwt = NULL;
oidc_jose_error_t err;
TST_ASSERT_ERR("oidc_jwt_parse (ec0)",
oidc_jwt_parse(pool, s_jwt, &jwt, NULL, &err), pool, err);
char *s_key = "{"
"\"kty\": \"EC\","
"\"kid\": \"f6qtj\","
"\"use\": \"sig\","
"\"x\": \"iARwFlN3B3xa8Zn_O-CVfqry68tXIhO9DckKo1yrNg0\","
"\"y\": \"583S_mPS7YVZtLCjx2O69G_JzQPnMxjieOli-9cc_6Q\","
"\"crv\": \"P-256\""
"}";
apr_hash_t *keys = apr_hash_make(pool);
oidc_jwk_t *jwk = NULL;
TST_ASSERT_ERR("oidc_jwk_parse",
_jwk_parse(pool, s_key, &jwk, &err) == 0, pool, err);
apr_hash_set(keys, "f6qtj", APR_HASH_KEY_STRING, jwk);
TST_ASSERT_ERR("oidc_jwt_verify (ec0)", oidc_jwt_verify(pool, jwt, keys, &err),
pool, err);
oidc_jwt_destroy(jwt);
s_jwt = apr_pstrdup(pool, "eyJhbGciOiJFUzI1NiIsImtpZCI6ImY2cXRqIn0.eyJzdWIiOiJqb2UiLCJhdWQiOiJhY19vaWNfY2xpZW50IiwianRpIjoib0RXaXZXUEpCNDd6a2pPbTJjeWdEdiIsImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTAzMSIsImlhdCI6MTQ2Nzk5NzIwNywiZXhwIjoxNDY3OTk3NTA3LCJub25jZSI6IldMeG12NVN0WXlVazlKbFdJOFNhWFRMUGtHWjBWczhhU1Rkal9WUTZyYW8ifQ.2kqX56QNow37gOlnfLn0SIzwie4mLLIUx_p9OSQa0hiUXKQWQLmMYBjIp5qGh2-R-KPHwNEBxqXwuPgXG4Y7EG");
jwt = NULL;
TST_ASSERT_ERR("oidc_jwt_parse (ec1)",
oidc_jwt_parse(pool, s_jwt, &jwt, NULL, &err), pool, err);
TST_ASSERT_ERR("oidc_jwt_verify (ec1)", oidc_jwt_verify(pool, jwt, keys, &err) == FALSE,
pool, err);
oidc_jwt_destroy(jwt);
s_jwt = apr_pstrdup(pool, "eyJhbGciOiJFUzI1NiIsImtpZCI6ImY2cXRqIn0.eyJzdWIiOiJqb2UiLCJHdWQiOiJhY19vaWNfY2xpZW50IiwianRpIjoib0RXaXZXUEpCNDd6a2pPbTJjeWdEdiIsImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTAzMSIsImlhdCI6MTQ2Nzk5NzIwNywiZXhwIjoxNDY3OTk3NTA3LCJub25jZSI6IldMeG12NVN0WXlVazlKbFdJOFNhWFRMUGtHWjBWczhhU1Rkal9WUTZyYW8ifQ.2kqX56QNow37gOlnfLn0SIzwie4mLLIUx_p9OSQa0hiUXKQWQLmMYBjIp5qGh2-R-KPHwNEBxqXwuPgXG4Y7Eg");
jwt = NULL;
TST_ASSERT_ERR("oidc_jwt_parse (ec2)",
oidc_jwt_parse(pool, s_jwt, &jwt, NULL, &err), pool, err);
TST_ASSERT_ERR("oidc_jwt_verify (ec2)", oidc_jwt_verify(pool, jwt, keys, &err) == FALSE,
pool, err);
oidc_jwt_destroy(jwt);
oidc_jwk_destroy(jwk);
return 0;
}
#endif
static char *test_jwt_verify_rsa(apr_pool_t *pool) {
/*
* {
* "typ": "JWT",
* "alg": "RS256",
* "x5t": "Z1NCjojeiHAib-Gm8vFE6ya6lPM"
* }
* {
* "nonce": "avSk7S69G4kEE8Km4bPiOjrfChHt6nO4Z397Lp_bQnc,",
* "iat": 1411580876,
* "at_hash": "yTqsoONZbuWbN6TbgevuDQ",
* "sub": "6343a29c-5399-44a7-9b35-4990f4377c96",
* "amr": "password",
* "auth_time": 1411577267,
* "idp": "idsrv",
* "name": "ksonaty",
* "iss": "https://agsync.com",
* "aud": "agsync_implicit",
* "exp": 1411584475,
* "nbf": 1411580875
* }
*/
char *s_jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IloxTkNqb2plaUhBaWItR204dkZFNnlhNmxQTSJ9.eyJub25jZSI6ImF2U2s3UzY5RzRrRUU4S200YlBpT2pyZkNoSHQ2bk80WjM5N0xwX2JRbmMsIiwiaWF0IjoxNDExNTgwODc2LCJhdF9oYXNoIjoieVRxc29PTlpidVdiTjZUYmdldnVEUSIsInN1YiI6IjYzNDNhMjljLTUzOTktNDRhNy05YjM1LTQ5OTBmNDM3N2M5NiIsImFtciI6InBhc3N3b3JkIiwiYXV0aF90aW1lIjoxNDExNTc3MjY3LCJpZHAiOiJpZHNydiIsIm5hbWUiOiJrc29uYXR5IiwiaXNzIjoiaHR0cHM6Ly9hZ3N5bmMuY29tIiwiYXVkIjoiYWdzeW5jX2ltcGxpY2l0IiwiZXhwIjoxNDExNTg0NDc1LCJuYmYiOjE0MTE1ODA4NzV9.lEG-DgHHa0JuOEuOTBvCqyexjRVcKXBnJJm289o2HyTgclpH80DsOMED9RlXCFfuDY7nw9i2cxUmIMAV42AdTxkMPomK3chytcajvpAZJirlk653bo9GTDXJSKZr5fwyEu--qahsoT5t9qvoWyFdYkvmMHFw1-mAHDGgVe23voc9jPuFFIhRRqIn4e8ikzN4VQeEV1UXJD02kYYFn2TRWURgiFyVeTr2r0MTn-auCEsFS_AfR1Bl_kmpMfqwrsicf5MTBvfPJeuSMt3t3d3LOGBkg36_z21X-ZRN7wy1KTjagr7iQ_y5csIpmtqs_QM55TTB9dW1HIosJPhiuMEJEA";
oidc_jwt_t *jwt = NULL;
oidc_jose_error_t err;
TST_ASSERT_ERR("oidc_jwt_parse",
oidc_jwt_parse(pool, s_jwt, &jwt, NULL, &err), pool, err);
char *s_key =
"{"
"\"kty\": \"RSA\","
"\"n\": \"3lDyn_ZvG32Pw5kYbRuVxHsPfe9Xt8s9vOXnt8z7_T-hZZvealNhCxz9VEwTJ7TsZ9CLi5c30FjoEJYFkKddLAdxKo0oOXWc_AWrQvPwht9a-o6dX2fL_9CmXW1hGHXMH0qiLMrFqMSzZeh-GUY6F1woE_eKsAo6LOhP8X77FlEQT2Eu71wu8KC4B3sH_9QTco50KNw14-bRY5j2V2TZelvsXJnvrN4lXtEVYWFkREKeXzMH8DhDyZzh0NcHa7dFBa7rDusyfIHjuP6uAju_Ao6hhdOGjlKePMVtfusWBAI7MWDChLTqiCTvlZnCpkpTTh5m-i7TbE1TwmdbLceq1w\","
"\"e\": \"AQAB\""
"}";
apr_hash_t *keys = apr_hash_make(pool);
oidc_jwk_t *jwk = NULL;
TST_ASSERT_ERR("oidc_jwk_parse",
_jwk_parse(pool, s_key, &jwk, &err) == 0, pool, err);
apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk);
TST_ASSERT_ERR("oidc_jwt_verify", oidc_jwt_verify(pool, jwt, keys, &err),
pool, err);
oidc_jwt_destroy(jwt);
jwt = NULL;
s_jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IloxTkNqb2plaUhBaWItR204dkZFNnlhNmxQTSJ9.eyJub25jZSI6ImF2U2s3UzY5RzRrRUU4S200YlBpT2pyZkNoSHQ2bk80WjM5N0xwX2JRbmMsIiwiaWF0IjoxNDExNTgwODc2LCJhdF9oYXNoIjoieVRxc29PTlpidVdiTjZUYmdldnVEUSIsInN1YiI6IjYzNDNhMjljLTUzOTktNDRhNy05YjM1LTQ5OTBmNDM3N2M5NiIsImFtciI6InBhc3N3b3JkIiwiYXV0aF90aW1lIjoxNDExNTc3MjY3LCJpZHAiOiJpZHNydiIsIm5hbWUiOiJrc29uYXR5IiwiaXNzIjoiaHR0cHM6Ly9hZ3N5bmMuY29tIiwiYXVkIjoiYWdzeW5jX2ltcGxpY2l0IiwiZXhwIjoxNDExNTg0NDc1LCJuYmYiOjE1MTE1ODA4NzV9.lEG-DgHHa0JuOEuOTBvCqyexjRVcKXBnJJm289o2HyTgclpH80DsOMED9RlXCFfuDY7nw9i2cxUmIMAV42AdTxkMPomK3chytcajvpAZJirlk653bo9GTDXJSKZr5fwyEu--qahsoT5t9qvoWyFdYkvmMHFw1-mAHDGgVe23voc9jPuFFIhRRqIn4e8ikzN4VQeEV1UXJD02kYYFn2TRWURgiFyVeTr2r0MTn-auCEsFS_AfR1Bl_kmpMfqwrsicf5MTBvfPJeuSMt3t3d3LOGBkg36_z21X-ZRN7wy1KTjagr7iQ_y5csIpmtqs_QM55TTB9dW1HIosJPhiuMEJEA";
TST_ASSERT_ERR("oidc_jwt_parse (rsa1)",
oidc_jwt_parse(pool, s_jwt, &jwt, NULL, &err), pool, err);
TST_ASSERT_ERR("oidc_jwt_verify (rsa1)", oidc_jwt_verify(pool, jwt, keys, &err) == FALSE,
pool, err);
oidc_jwt_destroy(jwt);
jwt = NULL;
s_jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IloxTkNqb2plaUhBaWItR204dkZFNnlhNmxQTSJ9.eyJub25jZSI6ImF2U2s3UzY5RzRrRUU4S200YlBpT2pyZkNoSHQ2bk80WjM5N0xwX2JRbmMsIiwiaWF0IjoxNDExNTgwODc2LCJhdF9oYXNoIjoieVRxc29PTlpidVdiTjZUYmdldnVEUSIsInN1YiI6IjYzNDNhMjljLTUzOTktNDRhNy05YjM1LTQ5OTBmNDM3N2M5NiIsImFtciI6InBhc3N3b3JkIiwiYXV0aF90aW1lIjoxNDExNTc3MjY3LCJpZHAiOiJpZHNydiIsIm5hbWUiOiJrc29uYXR5IiwiaXNzIjoiaHR0cHM6Ly9hZ3N5bmMuY29tIiwiYXVkIjoiYWdzeW5jX2ltcGxpY2l0IiwiZXhwIjoxNDExNTg0NDc1LCJuYmYiOjE0MTE1ODA4NzV9.lEG-DgHHa0JuOEuOTBvCqyexjRVcKXBnJJm289o2HyTgclpH80DsOMED9RlXCFfuDY7nw9i2cxUmIMAV42AdTxkMPomK3chytcajvpAZJirlk653bo9GTDXJSKZr5fwyEu--qahsoT5t9qvoWyFdYkvmMHFw1-mAHDGgVe23voc9jPuFFIhRRqIn4e8ikzN4VQeEV1UXJD02kYYFn2TRWURgiFyVeTr2r0MTn-auCEsFS_AfR1Bl_kmpMfqwrsicf5MTBvfPJeuSMt3t3d3LOGBkg36_z21X-ZRN7wy1KTjagr7iQ_y5csIpmtqs_QM55TTB9dW1HIosJPhiuMEJEa";
TST_ASSERT_ERR("oidc_jwt_parse (rsa2)",
oidc_jwt_parse(pool, s_jwt, &jwt, NULL, &err), pool, err);
TST_ASSERT_ERR("oidc_jwt_verify (rsa2)", oidc_jwt_verify(pool, jwt, keys, &err) == FALSE,
pool, err);
oidc_jwt_destroy(jwt);
oidc_jwk_destroy(jwk);
return 0;
}
static char *test_jwt_sign_verify(apr_pool_t *pool) {
oidc_jwt_t *jwt = NULL;
oidc_jwk_t *jwk = NULL;
char *cser = NULL;
oidc_jose_error_t err;
char *s_key =
"{"
"\"kty\" : \"RSA\","
"\"n\": \"ym7jipmB37CgdonwGFVRuZmRfCl3lVh91fmm5CXHcNlUFZNR3D6Q9r63PpGRnfSsX3dOweh8BXd2AJ3mxvcE4z9xH--tA5EaOGI7IVF0Ip_i3flGg85xOADlb8rX3ez1NqkqMVJeeJypKhCCDNfvu_MXSdPLglU969YQF5xKAK8VFRfI6EfxxrZ_3Dvt2CKDV4LTPPJe9KI2_LuLQFBJ3MzlCTVxY6gyaljrWaDq7q5Lt3GB1KYS0Yd8COEQwsclOLm0Tddhg4cle-DfaTMi7xsTZsPKyac5x17Y4N4isHhZULuWHX7o1bs809xcj-_-YCRq6C61je_mzFhuF4pczw\","
"\"e\": \"AQAB\","
"\"d\": \"qvxW_e8DoCnUn8uLHUKTsS1hkXqFI4SHZYFl0jeG6m7ncwHolxvR3ljg9tyGHuFX55sizu7MMuHgrkyxbUWgv0ILD2qmvOiHOTDfuRjP-58JRW0UfqiVQTSgl3jCNRW9WdoxZU-ptD6_NGSVNLwAJsUB2r4mm4PctaMuHINKjp_TnuD-5vfi9Tj88hbqvX_0j8T62ZaLRdERb1KGDM_8bnqQpnLZ0MZQnpLQ8cKIcjj7p0II6pzvqgdO1RqfYx7qG0cbcIRh26rnB9X4rp5BrbvDzKe6NOqacZUcNUmbPzI01-hiT0HgJvV592CBOxt2T31ltQ4wCEdzhQeT3n9_wQ\""
"}";
apr_hash_t *keys = apr_hash_make(pool);
TST_ASSERT_ERR("oidc_jwk_parse",
_jwk_parse(pool, s_key, &jwk, &err) == 0, pool, err);
apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk);
jwt = oidc_jwt_new(pool, TRUE, TRUE);
json_object_set_new(jwt->payload.value.json, "iss",
json_string("https://example.org"));
json_object_set_new(jwt->payload.value.json, "sub",
json_string("https://example.org"));
json_object_set_new(jwt->payload.value.json, "aud",
json_string("sample_client"));
json_object_set_new(jwt->payload.value.json, "exp",
json_integer(apr_time_sec(apr_time_now()) + 60));
json_object_set_new(jwt->payload.value.json, "iat",
json_integer(apr_time_sec(apr_time_now())));
jwt->header.alg = apr_pstrdup(pool, CJOSE_HDR_ALG_RS256);
TST_ASSERT_ERR("oidc_jwt_sign (rsa)", oidc_jwt_sign(pool, jwt, jwk, &err),
pool, err);
cser = oidc_jwt_serialize(pool, jwt, &err);
TST_ASSERT_ERR("oidc_jwt_serialize (rsa)", cser != NULL, pool, err);
oidc_jwt_t *rsa_jwt = NULL;
TST_ASSERT_ERR("oidc_jwt_parse (rsa)", oidc_jwt_parse(pool, cser, &rsa_jwt, NULL, &err),
pool, err);
TST_ASSERT_ERR("oidc_jwt_verify (rsa)",
oidc_jwt_verify(pool, rsa_jwt, keys, &err), pool, err);
oidc_jwt_destroy(rsa_jwt);
oidc_jwk_destroy(jwk);
const char *secret = "my_secret4321";
jwk = oidc_jwk_create_symmetric_key(pool, NULL, (const unsigned char *)secret, strlen(secret), FALSE, &err);
TST_ASSERT_ERR("oidc_jwk_create_symmetric_key", jwk != NULL, pool, err);
apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk);
jwt->header.alg = apr_pstrdup(pool, "HS256");
TST_ASSERT_ERR("oidc_jwt_sign (hmac)", oidc_jwt_sign(pool, jwt, jwk, &err),
pool, err);
cser = oidc_jwt_serialize(pool, jwt, &err);
TST_ASSERT_ERR("oidc_jwt_serialize (hmac)", cser != NULL, pool, err);
oidc_jwt_t *hmac_jwt = NULL;
TST_ASSERT_ERR("oidc_jwt_parse (rsa)", oidc_jwt_parse(pool, cser, &hmac_jwt, NULL, &err),
pool, err);
TST_ASSERT_ERR("oidc_jwt_verify (rsa)",
oidc_jwt_verify(pool, hmac_jwt, keys, &err), pool, err);
oidc_jwt_destroy(hmac_jwt);
oidc_jwk_destroy(jwk);
oidc_jwt_destroy(jwt);
return 0;
}
static char *test_plaintext_jwt_parse(apr_pool_t *pool) {
// from http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20
// 6.1. Example Plaintext JWT
char *s =
apr_pstrdup(pool,
"eyJhbGciOiJub25lIn0"
".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
".");
oidc_jose_error_t err;
oidc_jwt_t *jwt = NULL;
TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(pool, s, &jwt, NULL, &err),
pool, err);
TST_ASSERT_STR("header.alg", jwt->header.alg, "none");
TST_ASSERT_STR("payload.iss", jwt->payload.iss, "joe");
TST_ASSERT_LONG("payload.exp", (long )jwt->payload.exp, 1300819380L);
oidc_jwt_destroy(jwt);
return 0;
}
static char *test_jwt_get_string(apr_pool_t *pool) {
//oidc_jose_get_string
const char *s =
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9"
".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
".dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
oidc_jwt_t *jwt = NULL;
oidc_jose_error_t err;
TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(pool, s, &jwt, NULL, &err),
pool, err);
char *dst = NULL;
TST_ASSERT("oidc_jose_get_string (1a)",
oidc_jose_get_string(pool, jwt->header.value.json, "typ", TRUE, &dst, &err));
TST_ASSERT_STR("oidc_jose_get_string (1b)", dst, "JWT");
dst = NULL;
TST_ASSERT("oidc_jose_get_string (2a)",
oidc_jose_get_string(pool, jwt->header.value.json, "alg", TRUE, &dst, &err));
TST_ASSERT_STR("oidc_jose_get_string (2b)", dst, "HS256");
dst = NULL;
TST_ASSERT("oidc_jose_get_string (3a)",
oidc_jose_get_string(pool, jwt->header.value.json, "dummy", FALSE, &dst, &err));
TST_ASSERT_STR("oidc_jose_get_string (3b)", dst, NULL);
oidc_jwt_destroy(jwt);
return 0;
}
static char *test_jwk_parse_json(apr_pool_t *pool) {
const char *s =
"{\"kty\":\"EC\",\"use\":\"sig\","
"\"kid\":\"the key\","
"\"x\":\"amuk6RkDZi-48mKrzgBN_zUZ_9qupIwTZHJjM03qL-4\","
"\"y\":\"ZOESj6_dpPiZZR-fJ-XVszQta28Cjgti7JudooQJ0co\",\"crv\":\"P-256\"}";
oidc_jose_error_t err;
oidc_jwk_t *jwk;
jwk = NULL;
TST_ASSERT_ERR("oidc_jwk_parse (1)",
_jwk_parse(pool, s, &jwk, &err) == 0, pool, err);
oidc_jwk_destroy(jwk);
// https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41#appendix-A.3
// A.3. Example Symmetric Keys #1
s = "{"
"\"kty\":\"oct\","
"\"alg\":\"A128KW\","
"\"k\" :\"GawgguFyGrWKav7AX4VKUg\""
"}";
jwk = NULL;
TST_ASSERT_ERR(
"oidc_jwk_parse (draft-ietf-jose-json-web-key-41#appendix-A.3 #1)",
_jwk_parse(pool, s, &jwk, &err) == 0, pool, err);
oidc_jwk_destroy(jwk);
//TST_ASSERT_LONG("#1 jwk->type", (long )jwk->type, (long )APR_JWK_KEY_OCT);
//TST_ASSERT_LONG("#1 jwk->key.oct->k_len", (long )jwk->key.oct->k_len, 16L);
// https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41#appendix-A.3
// A.3. Example Symmetric Keys #2
s =
"{"
"\"kty\":\"oct\","
"\"k\" :\"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\","
"\"kid\":\"HMAC key used in JWS A.1 example\""
"}";
jwk = NULL;
TST_ASSERT_ERR(
"oidc_jwk_parse (draft-ietf-jose-json-web-key-41#appendix-A.3 #2)",
_jwk_parse(pool, s, &jwk, &err) == 0, pool, err);
oidc_jwk_destroy(jwk);
//TST_ASSERT_LONG("#2 jwk->type", (long )jwk->type, (long )APR_JWK_KEY_OCT);
//TST_ASSERT_LONG("#2 jwk->key.oct->k_len", (long )jwk->key.oct->k_len, 64L);
// https://tools.ietf.org/html/draft-ietf-jose-cookbook-08#section-3.1
// 3.1. EC Public Key
s =
"{"
"\"kty\": \"EC\","
"\"kid\": \"bilbo.baggins@hobbiton.example\","
"\"use\": \"sig\","
"\"crv\": \"P-521\","
"\"x\": \"AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt\","
"\"y\": \"AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1\""
"}";
jwk = NULL;
TST_ASSERT_ERR(
"oidc_jwk_parse (draft-ietf-jose-cookbook-08#section-3.1, EC Public Key)",
_jwk_parse(pool, s, &jwk, &err) == 0, pool, err);
oidc_jwk_destroy(jwk);
// https://tools.ietf.org/html/draft-ietf-jose-cookbook-08#section-3.2
// 3.2. EC Private Key
s =
"{"
"\"kty\": \"EC\","
"\"kid\": \"bilbo.baggins@hobbiton.example\","
"\"use\": \"sig\","
"\"crv\": \"P-521\","
"\"x\": \"AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt\","
"\"y\": \"AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1\","
"\"d\": \"AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zbKipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt\""
"}";
jwk = NULL;
TST_ASSERT_ERR(
"oidc_jwk_parse (draft-ietf-jose-cookbook-08#section-3.2, EC Private Key)",
_jwk_parse(pool, s, &jwk, &err) == 0, pool, err);
oidc_jwk_destroy(jwk);
// https://tools.ietf.org/html/draft-ietf-jose-cookbook-08#section-3.3
// 3.3. RSA Public Key
s = "{"
"\"kty\": \"RSA\","
"\"kid\": \"bilbo.baggins@hobbiton.example\","
"\"use\": \"sig\","
"\"n\": \"n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT"
"-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV"
"wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-"
"oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde"
"3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC"
"LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g"
"HdrNP5zw\","
"\"e\": \"AQAB\""
"}";
jwk = NULL;
TST_ASSERT_ERR(
"oidc_jwk_parse (draft-ietf-jose-cookbook-08#section-3.3, RSA Public Key)",
_jwk_parse(pool, s, &jwk, &err) == 0, pool, err);
oidc_jwk_destroy(jwk);
// https://tools.ietf.org/html/draft-ietf-jose-cookbook-08#section-3.4
// 3.4. RSA Private Key
s = "{"
"\"kty\": \"RSA\","
"\"kid\": \"bilbo.baggins@hobbiton.example\","
"\"use\": \"sig\","
"\"n\": \"n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT"
"-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV"
"wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-"
"oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde"
"3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC"
"LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g"
"HdrNP5zw\","
"\"e\": \"AQAB\","
"\"d\": \"bWUC9B-EFRIo8kpGfh0ZuyGPvMNKvYWNtB_ikiH9k20eT-O1q_I78e"
"iZkpXxXQ0UTEs2LsNRS-8uJbvQ-A1irkwMSMkK1J3XTGgdrhCku9gRld"
"Y7sNA_AKZGh-Q661_42rINLRCe8W-nZ34ui_qOfkLnK9QWDDqpaIsA-b"
"MwWWSDFu2MUBYwkHTMEzLYGqOe04noqeq1hExBTHBOBdkMXiuFhUq1BU"
"6l-DqEiWxqg82sXt2h-LMnT3046AOYJoRioz75tSUQfGCshWTBnP5uDj"
"d18kKhyv07lhfSJdrPdM5Plyl21hsFf4L_mHCuoFau7gdsPfHPxxjVOc"
"OpBrQzwQ\","
"\"p\": \"3Slxg_DwTXJcb6095RoXygQCAZ5RnAvZlno1yhHtnUex_fp7AZ_9nR"
"aO7HX_-SFfGQeutao2TDjDAWU4Vupk8rw9JR0AzZ0N2fvuIAmr_WCsmG"
"peNqQnev1T7IyEsnh8UMt-n5CafhkikzhEsrmndH6LxOrvRJlsPp6Zv8"
"bUq0k\","
"\"q\": \"uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT"
"8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7an"
"V5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0"
"s7pFc\","
"\"dp\": \"B8PVvXkvJrj2L-GYQ7v3y9r6Kw5g9SahXBwsWUzp19TVlgI-YV85q"
"1NIb1rxQtD-IsXXR3-TanevuRPRt5OBOdiMGQp8pbt26gljYfKU_E9xn"
"-RULHz0-ed9E9gXLKD4VGngpz-PfQ_q29pk5xWHoJp009Qf1HvChixRX"
"59ehik\","
"\"dq\": \"CLDmDGduhylc9o7r84rEUVn7pzQ6PF83Y-iBZx5NT-TpnOZKF1pEr"
"AMVeKzFEl41DlHHqqBLSM0W1sOFbwTxYWZDm6sI6og5iTbwQGIC3gnJK"
"bi_7k_vJgGHwHxgPaX2PnvP-zyEkDERuf-ry4c_Z11Cq9AqC2yeL6kdK"
"T1cYF8\","
"\"qi\": \"3PiqvXQN0zwMeE-sBvZgi289XP9XCQF3VWqPzMKnIgQp7_Tugo6-N"
"ZBKCQsMf3HaEGBjTVJs_jcK8-TRXvaKe-7ZMaQj8VfBdYkssbu0NKDDh"
"jJ-GtiseaDVWt7dcH0cfwxgFUHpQh7FoCrjFJ6h6ZEpMF6xmujs4qMpP"
"z8aaI4\""
"}";
jwk = NULL;
TST_ASSERT_ERR(
"oidc_jwk_parse (draft-ietf-jose-cookbook-08#section-3.4, RSA Private Key)",
_jwk_parse(pool, s, &jwk, &err) == 0, pool, err);
oidc_jwk_destroy(jwk);
return 0;
}
static char *test_plaintext_decrypt(apr_pool_t *pool) {
// from http://tools.ietf.org/html/draft-ietf-jose-json-web-encryption-30
// A.2. Example JWE using RSAES-PKCS1-V1_5 and AES_128_CBC_HMAC_SHA_256
char *s =
apr_pstrdup(pool,
"eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0"
".UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7PcHALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A"
".AxY8DCtDaGlsbGljb3RoZQ"
".KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY"
".9hH0vgRfYgPnAHOd8stkvw");
char * k =
"{\"kty\":\"RSA\","
"\"n\":\"sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1WlUzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5yjU1hGzHHyXss8UDprecbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_7svlJJQ4H9_NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBIY2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_RJLqHpM2kgWFLU7-VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw\","
"\"e\":\"AQAB\","
"\"d\":\"VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq1OPThh_J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-rynq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-QgajZGPbE_0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj-VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-KyvjT1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ\""
"}";
oidc_jose_error_t err;
apr_hash_t *keys = apr_hash_make(pool);
oidc_jwk_t *jwk = NULL;
TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, k, &jwk, &err) == 0,
pool, err);
apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk);
cjose_err cjose_err;
cjose_jwe_t *jwe = cjose_jwe_import(s, strlen(s), &cjose_err);
TST_ASSERT_CJOSE_ERR("cjose_jwe_import", jwe != NULL, pool, cjose_err);
size_t content_len = 0;
uint8_t *decrypted = cjose_jwe_decrypt(jwe, jwk->cjose_jwk, &content_len, &cjose_err);
TST_ASSERT_CJOSE_ERR("cjose_jwe_decrypt", decrypted != NULL, pool, cjose_err);
TST_ASSERT_STRN("decrypted", (const char *)decrypted, "Live long and prosper.", content_len);
cjose_get_dealloc()(decrypted);
oidc_jwk_destroy(jwk);
cjose_jwe_release(jwe);
return 0;
}
static char *test_plaintext_decrypt2(apr_pool_t *pool) {
// http://tools.ietf.org/html/draft-ietf-jose-cookbook-08#section-5.1.5
// 5.1. Key Encryption using RSA v1.5 and AES-HMAC-SHA2
char *s =
apr_pstrdup(pool,
"eyJhbGciOiJSU0ExXzUiLCJraWQiOiJmcm9kby5iYWdnaW5zQGhvYmJpdG9uLm"
"V4YW1wbGUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0"
"."
"laLxI0j-nLH-_BgLOXMozKxmy9gffy2gTdvqzfTihJBuuzxg0V7yk1WClnQePF"
"vG2K-pvSlWc9BRIazDrn50RcRai__3TDON395H3c62tIouJJ4XaRvYHFjZTZ2G"
"Xfz8YAImcc91Tfk0WXC2F5Xbb71ClQ1DDH151tlpH77f2ff7xiSxh9oSewYrcG"
"TSLUeeCt36r1Kt3OSj7EyBQXoZlN7IxbyhMAfgIe7Mv1rOTOI5I8NQqeXXW8Vl"
"zNmoxaGMny3YnGir5Wf6Qt2nBq4qDaPdnaAuuGUGEecelIO1wx1BpyIfgvfjOh"
"MBs9M8XL223Fg47xlGsMXdfuY-4jaqVw"
"."
"bbd5sTkYwhAIqfHsx8DayA"
"."
"0fys_TY_na7f8dwSfXLiYdHaA2DxUjD67ieF7fcVbIR62JhJvGZ4_FNVSiGc_r"
"aa0HnLQ6s1P2sv3Xzl1p1l_o5wR_RsSzrS8Z-wnI3Jvo0mkpEEnlDmZvDu_k8O"
"WzJv7eZVEqiWKdyVzFhPpiyQU28GLOpRc2VbVbK4dQKPdNTjPPEmRqcaGeTWZV"
"yeSUvf5k59yJZxRuSvWFf6KrNtmRdZ8R4mDOjHSrM_s8uwIFcqt4r5GX8TKaI0"
"zT5CbL5Qlw3sRc7u_hg0yKVOiRytEAEs3vZkcfLkP6nbXdC_PkMdNS-ohP78T2"
"O6_7uInMGhFeX4ctHG7VelHGiT93JfWDEQi5_V9UN1rhXNrYu-0fVMkZAKX3VW"
"i7lzA6BP430m"
"."
"kvKuFBXHe5mQr4lqgobAUg");
char * k = "{"
"\"kty\": \"RSA\","
"\"kid\": \"frodo.baggins@hobbiton.example\","
"\"use\": \"enc\","
"\"n\": \"maxhbsmBtdQ3CNrKvprUE6n9lYcregDMLYNeTAWcLj8NnPU9XIYegT"
"HVHQjxKDSHP2l-F5jS7sppG1wgdAqZyhnWvXhYNvcM7RfgKxqNx_xAHx"
"6f3yy7s-M9PSNCwPC2lh6UAkR4I00EhV9lrypM9Pi4lBUop9t5fS9W5U"
"NwaAllhrd-osQGPjIeI1deHTwx-ZTHu3C60Pu_LJIl6hKn9wbwaUmA4c"
"R5Bd2pgbaY7ASgsjCUbtYJaNIHSoHXprUdJZKUMAzV0WOKPfA6OPI4oy"
"pBadjvMZ4ZAj3BnXaSYsEZhaueTXvZB4eZOAjIyh2e_VOIKVMsnDrJYA"
"VotGlvMQ\","
"\"e\": \"AQAB\","
"\"d\": \"Kn9tgoHfiTVi8uPu5b9TnwyHwG5dK6RE0uFdlpCGnJN7ZEi963R7wy"
"bQ1PLAHmpIbNTztfrheoAniRV1NCIqXaW_qS461xiDTp4ntEPnqcKsyO"
"5jMAji7-CL8vhpYYowNFvIesgMoVaPRYMYT9TW63hNM0aWs7USZ_hLg6"
"Oe1mY0vHTI3FucjSM86Nff4oIENt43r2fspgEPGRrdE6fpLc9Oaq-qeP"
"1GFULimrRdndm-P8q8kvN3KHlNAtEgrQAgTTgz80S-3VD0FgWfgnb1PN"
"miuPUxO8OpI9KDIfu_acc6fg14nsNaJqXe6RESvhGPH2afjHqSy_Fd2v"
"pzj85bQQ\","
"\"p\": \"2DwQmZ43FoTnQ8IkUj3BmKRf5Eh2mizZA5xEJ2MinUE3sdTYKSLtaE"
"oekX9vbBZuWxHdVhM6UnKCJ_2iNk8Z0ayLYHL0_G21aXf9-unynEpUsH"
"7HHTklLpYAzOOx1ZgVljoxAdWNn3hiEFrjZLZGS7lOH-a3QQlDDQoJOJ"
"2VFmU\","
"\"q\": \"te8LY4-W7IyaqH1ExujjMqkTAlTeRbv0VLQnfLY2xINnrWdwiQ93_V"
"F099aP1ESeLja2nw-6iKIe-qT7mtCPozKfVtUYfz5HrJ_XY2kfexJINb"
"9lhZHMv5p1skZpeIS-GPHCC6gRlKo1q-idn_qxyusfWv7WAxlSVfQfk8"
"d6Et0\","
"\"dp\": \"UfYKcL_or492vVc0PzwLSplbg4L3-Z5wL48mwiswbpzOyIgd2xHTH"
"QmjJpFAIZ8q-zf9RmgJXkDrFs9rkdxPtAsL1WYdeCT5c125Fkdg317JV"
"RDo1inX7x2Kdh8ERCreW8_4zXItuTl_KiXZNU5lvMQjWbIw2eTx1lpsf"
"lo0rYU\","
"\"dq\": \"iEgcO-QfpepdH8FWd7mUFyrXdnOkXJBCogChY6YKuIHGc_p8Le9Mb"
"pFKESzEaLlN1Ehf3B6oGBl5Iz_ayUlZj2IoQZ82znoUrpa9fVYNot87A"
"CfzIG7q9Mv7RiPAderZi03tkVXAdaBau_9vs5rS-7HMtxkVrxSUvJY14"
"TkXlHE\","
"\"qi\": \"kC-lzZOqoFaZCr5l0tOVtREKoVqaAYhQiqIRGL-MzS4sCmRkxm5vZ"
"lXYx6RtE1n_AagjqajlkjieGlxTTThHD8Iga6foGBMaAr5uR1hGQpSc7"
"Gl7CF1DZkBJMTQN6EshYzZfxW08mIO8M6Rzuh0beL6fG9mkDcIyPrBXx"
"2bQ_mM\""
"}";
oidc_jose_error_t err;
apr_hash_t *keys = apr_hash_make(pool);
oidc_jwk_t *jwk = NULL;
TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, k, &jwk, &err) == 0,
pool, err);
apr_hash_set(keys, "frodo.baggins@hobbiton.example", APR_HASH_KEY_STRING, jwk);
cjose_err cjose_err;
cjose_jwe_t *jwe = cjose_jwe_import(s, strlen(s), &cjose_err);
TST_ASSERT_CJOSE_ERR("cjose_jwe_import", jwe != NULL, pool, cjose_err);
size_t content_len = 0;
uint8_t *decrypted = cjose_jwe_decrypt(jwe, jwk->cjose_jwk, &content_len, &cjose_err);
TST_ASSERT_CJOSE_ERR("cjose_jwe_decrypt", decrypted != NULL, pool, cjose_err);
TST_ASSERT_STRN("decrypted", (const char *)decrypted,
"You can trust us to stick with you through thick and "
"thin\u2013to the bitter end. And you can trust us to "
"keep any secret of yours\u2013closer than you keep it "
"yourself. But you cannot trust us to let you face trouble "
"alone, and go off without a word. We are your friends, Frodo.", content_len);
cjose_get_dealloc()(decrypted);
oidc_jwk_destroy(jwk);
cjose_jwe_release(jwe);
return 0;
}
static char *test_plaintext_decrypt_symmetric(apr_pool_t *pool) {
oidc_jose_error_t err;
apr_hash_t *keys = apr_hash_make(pool);
oidc_jwk_t *jwk;
// http://tools.ietf.org/html/draft-ietf-jose-json-web-encryption-40#appendix-A.3
// A.3. Example JWE using AES Key Wrap and AES_128_CBC_HMAC_SHA_256
const char * k = "{\"kty\":\"oct\", \"k\":\"GawgguFyGrWKav7AX4VKUg\"}";
jwk = NULL;
TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, k, &jwk, &err) == 0,
pool, err);
apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk);
const char *s = "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0."
"6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ."
"AxY8DCtDaGlsbGljb3RoZQ."
"KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY."
"U0m_YmjN04DJvceFICbCVQ";
cjose_err cjose_err;
cjose_jwe_t *jwe = cjose_jwe_import(s, strlen(s), &cjose_err);
TST_ASSERT_CJOSE_ERR("cjose_jwe_import", jwe != NULL, pool, cjose_err);
size_t content_len = 0;
uint8_t *decrypted = cjose_jwe_decrypt(jwe, jwk->cjose_jwk, &content_len, &cjose_err);
TST_ASSERT_CJOSE_ERR("cjose_jwe_decrypt", decrypted != NULL, pool, cjose_err);
TST_ASSERT_STRN("decrypted", (const char *)decrypted, "Live long and prosper.", content_len);
cjose_get_dealloc()(decrypted);
oidc_jwk_destroy(jwk);
cjose_jwe_release(jwe);
return 0;
}
static char *test_jwt_decrypt(apr_pool_t *pool) {
// https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-32#appendix-A.1
// A.2. Example Nested JWT
char * s =
apr_pstrdup(pool,
"eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiY3R5IjoiSldU"
"In0."
"g_hEwksO1Ax8Qn7HoN-BVeBoa8FXe0kpyk_XdcSmxvcM5_P296JXXtoHISr_DD_M"
"qewaQSH4dZOQHoUgKLeFly-9RI11TG-_Ge1bZFazBPwKC5lJ6OLANLMd0QSL4fYE"
"b9ERe-epKYE3xb2jfY1AltHqBO-PM6j23Guj2yDKnFv6WO72tteVzm_2n17SBFvh"
"DuR9a2nHTE67pe0XGBUS_TK7ecA-iVq5COeVdJR4U4VZGGlxRGPLRHvolVLEHx6D"
"YyLpw30Ay9R6d68YCLi9FYTq3hIXPK_-dmPlOUlKvPr1GgJzRoeC9G5qCvdcHWsq"
"JGTO_z3Wfo5zsqwkxruxwA."
"UmVkbW9uZCBXQSA5ODA1Mg."
"VwHERHPvCNcHHpTjkoigx3_ExK0Qc71RMEParpatm0X_qpg-w8kozSjfNIPPXiTB"
"BLXR65CIPkFqz4l1Ae9w_uowKiwyi9acgVztAi-pSL8GQSXnaamh9kX1mdh3M_TT"
"-FZGQFQsFhu0Z72gJKGdfGE-OE7hS1zuBD5oEUfk0Dmb0VzWEzpxxiSSBbBAzP10"
"l56pPfAtrjEYw-7ygeMkwBl6Z_mLS6w6xUgKlvW6ULmkV-uLC4FUiyKECK4e3WZY"
"Kw1bpgIqGYsw2v_grHjszJZ-_I5uM-9RA8ycX9KqPRp9gc6pXmoU_-27ATs9XCvr"
"ZXUtK2902AUzqpeEUJYjWWxSNsS-r1TJ1I-FMJ4XyAiGrfmo9hQPcNBYxPz3GQb2"
"8Y5CLSQfNgKSGt0A4isp1hBUXBHAndgtcslt7ZoQJaKe_nNJgNliWtWpJ_ebuOpE"
"l8jdhehdccnRMIwAmU1n7SPkmhIl1HlSOpvcvDfhUN5wuqU955vOBvfkBOh5A11U"
"zBuo2WlgZ6hYi9-e3w29bR0C2-pp3jbqxEDw3iWaf2dc5b-LnR0FEYXvI_tYk5rd"
"_J9N0mg0tQ6RbpxNEMNoA9QWk5lgdPvbh9BaO195abQ."
"AVO9iT5AV4CzvDJCdhSFlQ");
char * ek =
"{\"kty\":\"RSA\","
"\"n\":\"sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1WlUzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5yjU1hGzHHyXss8UDprecbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_7svlJJQ4H9_NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBIY2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_RJLqHpM2kgWFLU7-VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw\","
"\"e\":\"AQAB\","
"\"d\":\"VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq1OPThh_J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-rynq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-QgajZGPbE_0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj-VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-KyvjT1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ\""
"}";
char * sk = "{"
"\"kty\":\"RSA\","
"\"n\":\"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx"
"HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs"
"D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH"
"SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV"
"MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8"
"NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ\","
"\"e\":\"AQAB\","
"\"d\":\"Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I"
"jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0"
"BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn"
"439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT"
"CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh"
"BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ\","
"\"p\":\"4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdi"
"YrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPG"
"BY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc\","
"\"q\":\"uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxa"
"ewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA"
"-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc\","
"\"dp\":\"BwKfV3Akq5_MFZDFZCnW-wzl-CCo83WoZvnLQwCTeDv8uzluRSnm71I3Q"
"CLdhrqE2e9YkxvuxdBfpT_PI7Yz-FOKnu1R6HsJeDCjn12Sk3vmAktV2zb"
"34MCdy7cpdTh_YVr7tss2u6vneTwrA86rZtu5Mbr1C1XsmvkxHQAdYo0\","
"\"dq\":\"h_96-mK1R_7glhsum81dZxjTnYynPbZpHziZjeeHcXYsXaaMwkOlODsWa"
"7I9xXDoRwbKgB719rrmI2oKr6N3Do9U0ajaHF-NKJnwgjMd2w9cjz3_-ky"
"NlxAr2v4IKhGNpmM5iIgOS1VZnOZ68m6_pbLBSp3nssTdlqvd0tIiTHU\","
"\"qi\":\"IYd7DHOhrWvxkwPQsRM2tOgrjbcrfvtQJipd-DlcxyVuuM9sQLdgjVk2o"
"y26F0EmpScGLq2MowX7fhd_QJQ3ydy5cY7YIBi87w93IKLEdfnbJtoOPLU"
"W0ITrJReOgo1cq9SbsxYawBgfp_gh6A5603k2-ZQwVK0JKSHuLFkuQ3U\""
"}";
oidc_jose_error_t err;
apr_hash_t *keys = apr_hash_make(pool);
oidc_jwk_t *jwk_e = NULL;
oidc_jwk_t *jwk_s = NULL;
oidc_jwt_t *jwt = NULL;
TST_ASSERT_ERR("oidc_jwk_parse (encryption key)",
_jwk_parse(pool, ek, &jwk_e, &err) == 0, pool, err);
apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk_e);
TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(pool, s, &jwt, keys, &err),
pool, err);
oidc_jwk_destroy(jwk_e);
TST_ASSERT_ERR("oidc_jwk_parse (signing key)",
_jwk_parse(pool, sk, &jwk_s, &err) == 0, pool, err);
apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk_s);
TST_ASSERT_ERR("oidc_jwt_verify", oidc_jwt_verify(pool, jwt, keys, &err),
pool, err);
oidc_jwk_destroy(jwk_s);
TST_ASSERT_STR("header.alg", jwt->header.alg, CJOSE_HDR_ALG_RS256);
TST_ASSERT_STR("payload.iss", jwt->payload.iss, "joe");
TST_ASSERT_LONG("payload.exp", (long )jwt->payload.exp, 1300819380L);
oidc_jwt_destroy(jwt);
return 0;
}
#if (OPENSSL_VERSION_NUMBER >= 0x1000100f)
static char *test_jwt_decrypt_gcm(apr_pool_t *pool) {
// https://tools.ietf.org/html/rfc7516#appendix-A.1
// A.1. Example JWE using RSAES-OAEP and AES GCM
char * s =
apr_pstrdup(pool,
"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ."
"OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe"
"ipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDb"
"Sv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaV"
"mqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je8"
"1860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi"
"6UklfCpIMfIjf7iGdXKHzg."
"48V1_ALb6US04U3b."
"5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6ji"
"SdiwkIr3ajwQzaBtQD_A."
"XFBoMYUZodetZdvTiFvSkQ");
char * k =
"{\"kty\":\"RSA\","
"\"n\":\"oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUW"
"cJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3S"
"psk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2a"
"sbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMS"
"tPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2dj"
"YgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw\","
"\"e\":\"AQAB\","
"\"d\":\"kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5N"
"WV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD9"
"3Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghk"
"qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl"
"t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd"
"VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ\","
"\"p\":\"1r52Xk46c-LsfB5P442p7atdPUrxQSy4mti_tZI3Mgf2EuFVbUoDBvaRQ-"
"SWxkbkmoEzL7JXroSBjSrK3YIQgYdMgyAEPTPjXv_hI2_1eTSPVZfzL0lf"
"fNn03IXqWF5MDFuoUYE0hzb2vhrlN_rKrbfDIwUbTrjjgieRbwC6Cl0\","
"\"q\":\"wLb35x7hmQWZsWJmB_vle87ihgZ19S8lBEROLIsZG4ayZVe9Hi9gDVCOBm"
"UDdaDYVTSNx_8Fyw1YYa9XGrGnDew00J28cRUoeBB_jKI1oma0Orv1T9aX"
"IWxKwd4gvxFImOWr3QRL9KEBRzk2RatUBnmDZJTIAfwTs0g68UZHvtc\","
"\"dp\":\"ZK-YwE7diUh0qR1tR7w8WHtolDx3MZ_OTowiFvgfeQ3SiresXjm9gZ5KL"
"hMXvo-uz-KUJWDxS5pFQ_M0evdo1dKiRTjVw_x4NyqyXPM5nULPkcpU827"
"rnpZzAJKpdhWAgqrXGKAECQH0Xt4taznjnd_zVpAmZZq60WPMBMfKcuE\","
"\"dq\":\"Dq0gfgJ1DdFGXiLvQEZnuKEN0UUmsJBxkjydc3j4ZYdBiMRAy86x0vHCj"
"ywcMlYYg4yoC4YZa9hNVcsjqA3FeiL19rk8g6Qn29Tt0cj8qqyFpz9vNDB"
"UfCAiJVeESOjJDZPYHdHY8v1b-o-Z2X5tvLx-TCekf7oxyeKDUqKWjis\","
"\"qi\":\"VIMpMYbPf47dT1w_zDUXfPimsSegnMOA1zTaX7aGk_8urY6R8-ZW1FxU7"
"AlWAyLWybqq6t16VFd7hQd0y6flUK4SlOydB61gwanOsXGOAOv82cHq0E3"
"eL4HrtZkUuKvnPrMnsUUFlfUdybVzxyjz9JF_XyaY14ardLSjf4L_FNY\""
"}";
oidc_jose_error_t err;
apr_hash_t *keys = apr_hash_make(pool);
oidc_jwk_t *jwk = NULL;
TST_ASSERT_ERR("oidc_jwk_parse", _jwk_parse(pool, k, &jwk, &err) == 0,
pool, err);
apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk);
cjose_err cjose_err;
cjose_jwe_t *jwe = cjose_jwe_import(s, strlen(s), &cjose_err);
TST_ASSERT_CJOSE_ERR("cjose_jwe_import", jwe != NULL, pool, cjose_err);
size_t content_len = 0;
uint8_t *decrypted = cjose_jwe_decrypt(jwe, jwk->cjose_jwk, &content_len, &cjose_err);
TST_ASSERT_CJOSE_ERR("cjose_jwe_decrypt", decrypted != NULL, pool, cjose_err);
TST_ASSERT_STRN("decrypted", (const char *)decrypted, "The true sign of intelligence is not knowledge but imagination.", content_len);
cjose_get_dealloc()(decrypted);
cjose_jwe_release(jwe);
oidc_jwk_destroy(jwk);
return 0;
}
#endif
static char *test_proto_validate_access_token(request_rec *r) {
// from http://openid.net/specs/openid-connect-core-1_0.html#id_token-tokenExample
// A.3 Example using response_type=id_token token
const char *s = "eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.ewogIml"
"zcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ"
"4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiA"
"ibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDE"
"zMTEyODA5NzAsCiAiYXRfaGFzaCI6ICI3N1FtVVB0alBmeld0RjJBbnBLOVJ"
"RIgp9.F9gRev0Dt2tKcrBkHy72cmRqnLdzw9FLCCSebV7mWs7o_sv2O5s6zM"
"ky2kmhHTVx9HmdvNnx9GaZ8XMYRFeYk8L5NZ7aYlA5W56nsG1iWOou_-gji0"
"ibWIuuf4Owaho3YSoi7EvsTuLFz6tq-dLyz0dKABMDsiCmJ5wqkPUDTE3QTX"
"jzbUmOzUDli-gCh5QPuZAq0cNW3pf_2n4zpvTYtbmj12cVcxGIMZby7TMWES"
"RjQ9_o3jvhVNcCGcE0KAQXejhA1ocJhNEvQNqMFGlBb6_0RxxKjDZ-Oa329e"
"GDidOvvp0h5hoES4a8IuGKS7NOcpp-aFwp0qVMDLI-Xnm-Pg";
oidc_jose_error_t err;
oidc_jwt_t *jwt = NULL;
TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(r->pool, s, &jwt, NULL, &err),
r->pool, err);
const char *access_token = "jHkWEdUXMU1BwAsC4vtUsZwnNvTIxEl0z9K3vx5KF0Y";
TST_ASSERT("oidc_proto_validate_access_token",
oidc_proto_validate_access_token(r, NULL, jwt, "id_token token", access_token));
oidc_jwt_destroy(jwt);
return 0;
}
static char *test_proto_validate_code(request_rec *r) {
// from http://openid.net/specs/openid-connect-core-1_0.html#code-id_tokenExample
// A.4 Example using response_type=code id_token
const char *s = "eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.ewogIml"
"zcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ"
"4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiA"
"ibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDE"
"zMTEyODA5NzAsCiAiY19oYXNoIjogIkxEa3RLZG9RYWszUGswY25YeENsdEE"
"iCn0.XW6uhdrkBgcGx6zVIrCiROpWURs-4goO1sKA4m9jhJIImiGg5muPUcN"
"egx6sSv43c5DSn37sxCRrDZZm4ZPBKKgtYASMcE20SDgvYJdJS0cyuFw7Ijp"
"_7WnIjcrl6B5cmoM6ylCvsLMwkoQAxVublMwH10oAxjzD6NEFsu9nipkszWh"
"sPePf_rM4eMpkmCbTzume-fzZIi5VjdWGGEmzTg32h3jiex-r5WTHbj-u5HL"
"7u_KP3rmbdYNzlzd1xWRYTUs4E8nOTgzAUwvwXkIQhOh5TPcSMBYy6X3E7-_"
"gr9Ue6n4ND7hTFhtjYs3cjNKIA08qm5cpVYFMFMG6PkhzLQ";
oidc_jose_error_t err;
oidc_jwt_t *jwt = NULL;
TST_ASSERT_ERR("oidc_jwt_parse", oidc_jwt_parse(r->pool, s, &jwt, NULL, &err),
r->pool, err);
const char *code =
"Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk";
TST_ASSERT("oidc_proto_validate_code",
oidc_proto_validate_code(r, NULL, jwt, "code id_token", code));
oidc_jwt_destroy(jwt);
return 0;
}
static char * test_proto_authorization_request(request_rec *r) {
oidc_provider_t provider;
provider.issuer = "https://idp.example.com";
provider.authorization_endpoint_url = "https://idp.example.com/authorize";
provider.scope = "openid";
provider.client_id = "client_id";
provider.client_secret = NULL;
provider.response_type = "code";
provider.auth_request_params = NULL;
provider.request_object = NULL;
provider.token_binding_policy = OIDC_TOKEN_BINDING_POLICY_OPTIONAL;
const char *redirect_uri = "https://www.example.com/protected/";
const char *state = "12345";
oidc_proto_state_t *proto_state = oidc_proto_state_new();
oidc_proto_state_set_nonce(proto_state, "anonce");
oidc_proto_state_set_original_url(proto_state, "https://localhost/protected/index.php");
oidc_proto_state_set_original_method(proto_state, OIDC_METHOD_GET);
oidc_proto_state_set_issuer(proto_state, provider.issuer);
oidc_proto_state_set_response_type(proto_state, provider.response_type);
oidc_proto_state_set_timestamp_now(proto_state);
TST_ASSERT("oidc_proto_authorization_request (1)",
oidc_proto_authorization_request(r, &provider, NULL, redirect_uri, state, proto_state, NULL, NULL, NULL, NULL) == HTTP_MOVED_TEMPORARILY);
TST_ASSERT_STR("oidc_proto_authorization_request (2)",
apr_table_get(r->headers_out, "Location"),
"https://idp.example.com/authorize?response_type=code&scope=openid&client_id=client_id&state=12345&redirect_uri=https%3A%2F%2Fwww.example.com%2Fprotected%2F&nonce=anonce");
return 0;
}
static char * test_proto_validate_nonce(request_rec *r) {
oidc_cfg *c = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
const char *nonce = "avSk7S69G4kEE8Km4bPiOjrfChHt6nO4Z397Lp_bQnc,";
/*
* {
* "typ": "JWT",
* "alg": "RS256",
* "x5t": "Z1NCjojeiHAib-Gm8vFE6ya6lPM"
* }
* {
* "nonce": "avSk7S69G4kEE8Km4bPiOjrfChHt6nO4Z397Lp_bQnc,",
* "iat": 1411580876,
* "at_hash": "yTqsoONZbuWbN6TbgevuDQ",
* "sub": "6343a29c-5399-44a7-9b35-4990f4377c96",
* "amr": "password",
* "auth_time": 1411577267,
* "idp": "idsrv",
* "name": "ksonaty",
* "iss": "https://agsync.com",
* "aud": "agsync_implicit",
* "exp": 1411584475,
* "nbf": 1411580875
* }
*/
char *s_jwt =
apr_pstrdup(r->pool,
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IloxTkNqb2plaUhBaWItR204dkZFNnlhNmxQTSJ9.eyJub25jZSI6ImF2U2s3UzY5RzRrRUU4S200YlBpT2pyZkNoSHQ2bk80WjM5N0xwX2JRbmMsIiwiaWF0IjoxNDExNTgwODc2LCJhdF9oYXNoIjoieVRxc29PTlpidVdiTjZUYmdldnVEUSIsInN1YiI6IjYzNDNhMjljLTUzOTktNDRhNy05YjM1LTQ5OTBmNDM3N2M5NiIsImFtciI6InBhc3N3b3JkIiwiYXV0aF90aW1lIjoxNDExNTc3MjY3LCJpZHAiOiJpZHNydiIsIm5hbWUiOiJrc29uYXR5IiwiaXNzIjoiaHR0cHM6Ly9hZ3N5bmMuY29tIiwiYXVkIjoiYWdzeW5jX2ltcGxpY2l0IiwiZXhwIjoxNDExNTg0NDc1LCJuYmYiOjE0MTE1ODA4NzV9.lEG-DgHHa0JuOEuOTBvCqyexjRVcKXBnJJm289o2HyTgclpH80DsOMED9RlXCFfuDY7nw9i2cxUmIMAV42AdTxkMPomK3chytcajvpAZJirlk653bo9GTDXJSKZr5fwyEu--qahsoT5t9qvoWyFdYkvmMHFw1-mAHDGgVe23voc9jPuFFIhRRqIn4e8ikzN4VQeEV1UXJD02kYYFn2TRWURgiFyVeTr2r0MTn-auCEsFS_AfR1Bl_kmpMfqwrsicf5MTBvfPJeuSMt3t3d3LOGBkg36_z21X-ZRN7wy1KTjagr7iQ_y5csIpmtqs_QM55TTB9dW1HIosJPhiuMEJEA");
oidc_jwt_t *jwt = NULL;
oidc_jose_error_t err;
TST_ASSERT_ERR("oidc_jwt_parse",
oidc_jwt_parse(r->pool, s_jwt, &jwt, NULL, &err), r->pool, err);
TST_ASSERT("oidc_proto_validate_nonce (1)",
oidc_proto_validate_nonce(r, c, &c->provider, nonce, jwt));
TST_ASSERT("oidc_proto_validate_nonce (2)",
oidc_proto_validate_nonce( r, c, &c->provider, nonce, jwt) == FALSE);
oidc_jwt_destroy(jwt);
return 0;
}
static char * test_proto_validate_jwt(request_rec *r) {
oidc_jwt_t *jwt = NULL;
oidc_jose_error_t err;
const char *s_secret = "secret";
const char *s_issuer = "https://localhost";
apr_time_t now = apr_time_sec(apr_time_now());
const char *s_jwt_header = "{"
"\"alg\": \"HS256\""
"}";
const char *s_jwt_payload = "{"
"\"nonce\": \"543210,\","
"\"iat\": %" APR_TIME_T_FMT ","
"\"sub\": \"alice\","
"\"iss\": \"%s\","
"\"aud\": \"bob\","
"\"exp\": %" APR_TIME_T_FMT
"}";
s_jwt_payload = apr_psprintf(r->pool, s_jwt_payload, now, s_issuer,
now + 600);
char *s_jwt_header_encoded = NULL;
oidc_base64url_encode(r, &s_jwt_header_encoded, s_jwt_header,
strlen(s_jwt_header), 1);
char *s_jwt_payload_encoded = NULL;
oidc_base64url_encode(r, &s_jwt_payload_encoded, s_jwt_payload,
strlen(s_jwt_payload), 1);
char *s_jwt_message = apr_psprintf(r->pool, "%s.%s", s_jwt_header_encoded,
s_jwt_payload_encoded);
unsigned int md_len = 0;
unsigned char md[EVP_MAX_MD_SIZE];
const EVP_MD *digest = EVP_get_digestbyname("sha256");
TST_ASSERT("HMAC",
HMAC(digest, (const unsigned char * )s_secret, strlen(s_secret),
(const unsigned char * )s_jwt_message,
strlen(s_jwt_message), md, &md_len) != 0);
char *s_jwt_signature_encoded = NULL;
oidc_base64url_encode(r, &s_jwt_signature_encoded, (const char *) md,
md_len, 1);
char *s_jwt = apr_psprintf(r->pool, "%s.%s.%s", s_jwt_header_encoded,
s_jwt_payload_encoded, s_jwt_signature_encoded);
TST_ASSERT_ERR("oidc_jwt_parse",
oidc_jwt_parse(r->pool, s_jwt, &jwt, NULL, &err), r->pool, err);
oidc_jwk_t *jwk = NULL;
TST_ASSERT_ERR("oidc_util_create_symmetric_key",
oidc_util_create_symmetric_key(r, s_secret, 0, NULL, TRUE, &jwk) == TRUE,
r->pool, err);
TST_ASSERT_ERR("oidc_util_create_symmetric_key (jwk)", jwk != NULL, r->pool,
err);
TST_ASSERT_ERR("oidc_jwt_verify",
oidc_jwt_verify(r->pool, jwt, oidc_util_merge_symmetric_key(r->pool, NULL, jwk), &err),
r->pool, err);
TST_ASSERT_ERR("oidc_proto_validate_jwt",
oidc_proto_validate_jwt(r, jwt, s_issuer, TRUE, TRUE, 10), r->pool,
err);
oidc_jwk_destroy(jwk);
oidc_jwt_destroy(jwt);
return 0;
}
static char * test_current_url(request_rec *r) {
char *url = oidc_get_current_url(r);
TST_ASSERT_STR("test_headers (1)", url, "https://www.example.com");
apr_table_set(r->headers_in, "X-Forwarded-Host", "www.outer.com");
url = oidc_get_current_url(r);
TST_ASSERT_STR("test_headers (2)", url, "https://www.outer.com");
apr_table_set(r->headers_in, "X-Forwarded-Host", "www.outer.com:654");
url = oidc_get_current_url(r);
TST_ASSERT_STR("test_headers (3)", url, "https://www.outer.com:654");
apr_table_set(r->headers_in, "X-Forwarded-Port", "321");
url = oidc_get_current_url(r);
TST_ASSERT_STR("test_headers (4)", url, "https://www.outer.com:321");
apr_table_set(r->headers_in, "X-Forwarded-Proto", "http");
url = oidc_get_current_url(r);
TST_ASSERT_STR("test_headers (5)", url, "http://www.outer.com:321");
apr_table_set(r->headers_in, "X-Forwarded-Proto", "https , http");
url = oidc_get_current_url(r);
TST_ASSERT_STR("test_headers (6)", url, "https://www.outer.com:321");
apr_table_unset(r->headers_in, "X-Forwarded-Host");
apr_table_unset(r->headers_in, "X-Forwarded-Port");
url = oidc_get_current_url(r);
TST_ASSERT_STR("test_headers (7)", url, "https://www.example.com");
return 0;
}
static char * all_tests(apr_pool_t *pool, request_rec *r) {
char *message;
TST_RUN(test_jwt_parse, pool);
TST_RUN(test_plaintext_jwt_parse, pool);
TST_RUN(test_jwt_get_string, pool);
TST_RUN(test_jwk_parse_json, pool);
TST_RUN(test_plaintext_decrypt, pool);
TST_RUN(test_plaintext_decrypt2, pool);
TST_RUN(test_plaintext_decrypt_symmetric, pool);
TST_RUN(test_jwt_decrypt, pool);
#if (OPENSSL_VERSION_NUMBER >= 0x1000100f)
TST_RUN(test_jwt_decrypt_gcm, pool);
#endif
#if (APR_JWS_EC_SUPPORT)
TST_RUN(test_jwt_verify_ec, pool);
#endif
TST_RUN(test_jwt_verify_rsa, pool);
TST_RUN(test_jwt_sign_verify, pool);
TST_RUN(test_proto_validate_access_token, r);
TST_RUN(test_proto_validate_code, r);
TST_RUN(test_proto_authorization_request, r);
TST_RUN(test_proto_validate_nonce, r);
TST_RUN(test_proto_validate_jwt, r);
TST_RUN(test_current_url, r);
return 0;
}
typedef struct oidc_dir_cfg oidc_dir_cfg;
static request_rec * test_setup(apr_pool_t *pool) {
const unsigned int kIdx = 0;
const unsigned int kEls = kIdx + 1;
request_rec *request = (request_rec *) apr_pcalloc(pool,
sizeof(request_rec));
request->pool = pool;
request->headers_in = apr_table_make(request->pool, 0);
request->headers_out = apr_table_make(request->pool, 0);
request->err_headers_out = apr_table_make(request->pool, 0);
apr_table_set(request->headers_in, "Host", "www.example.com");
apr_table_set(request->headers_in, "OIDC_foo", "some-value");
apr_table_set(request->headers_in, "Cookie", "foo=bar; "
"mod_auth_openidc_session" "=0123456789abcdef; baz=zot");
request->server = apr_pcalloc(request->pool, sizeof(struct server_rec));
request->server->process = apr_pcalloc(request->pool,
sizeof(struct process_rec));
request->server->process->pool = request->pool;
request->connection = apr_pcalloc(request->pool, sizeof(struct conn_rec));
request->connection->local_addr = apr_pcalloc(request->pool,
sizeof(apr_sockaddr_t));
apr_pool_userdata_set("https", "scheme", NULL, request->pool);
request->server->server_hostname = "www.example.com";
request->connection->local_addr->port = 443;
request->unparsed_uri = "/bla?foo=bar¶m1=value1";
request->args = "foo=bar¶m1=value1";
apr_uri_parse(request->pool,
"https://www.example.com/bla?foo=bar¶m1=value1",
&request->parsed_uri);
auth_openidc_module.module_index = kIdx;
oidc_cfg *cfg = oidc_create_server_config(request->pool, request->server);
cfg->provider.issuer = "https://idp.example.com";
cfg->provider.authorization_endpoint_url =
"https://idp.example.com/authorize";
cfg->provider.scope = "openid";
cfg->provider.client_id = "client_id";
cfg->provider.token_binding_policy = OIDC_TOKEN_BINDING_POLICY_OPTIONAL;
cfg->redirect_uri = "https://www.example.com/protected/";
oidc_dir_cfg *d_cfg = oidc_create_dir_config(request->pool, NULL);
request->server->module_config = apr_pcalloc(request->pool,
sizeof(ap_conf_vector_t *) * kEls);
request->per_dir_config = apr_pcalloc(request->pool,
sizeof(ap_conf_vector_t *) * kEls);
ap_set_module_config(request->server->module_config, &auth_openidc_module,
cfg);
ap_set_module_config(request->per_dir_config, &auth_openidc_module, d_cfg);
cfg->crypto_passphrase = "12345678901234567890123456789012";
cfg->cache = &oidc_cache_shm;
cfg->cache_cfg = NULL;
cfg->cache_shm_size_max = 500;
cfg->cache_shm_entry_size_max = 16384 + 255 + 17;
cfg->cache_encrypt = 1;
if (cfg->cache->post_config(request->server) != OK) {
printf("cfg->cache->post_config failed!\n");
exit(-1);
}
return request;
}
int main(int argc, char **argv, char **env) {
if (apr_app_initialize(&argc, (const char * const **) argv,
(const char * const **) env) != APR_SUCCESS) {
printf("apr_app_initialize failed\n");
return -1;
}
apr_pool_t *pool = NULL;
apr_pool_create(&pool, NULL);
request_rec *r = test_setup(pool);
OpenSSL_add_all_algorithms();
char *result = all_tests(pool, r);
if (result != 0) {
printf("Failed: %s\n", result);
} else {
printf("All %d tests passed!\n", test_nr_run);
}
EVP_cleanup();
apr_pool_destroy(pool);
apr_terminate();
return result != 0;
}
mod_auth_openidc-2.3.3/test/test-cmd.c 0000644 0000765 0000024 00000034773 13202535004 017531 0 ustar hzandbelt staff /*
* 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.
*/
/***************************************************************************
* Copyright (C) 2013-2017 Ping Identity Corporation
* All rights reserved.
*
* For further information please contact:
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @Author: Hans Zandbelt - hans.zandbelt@zmartzone.eu
*
**************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include
int usage(int argc, char **argv, const char *msg) {
fprintf(stderr, "Usage: %s %s\n", argv[0],
msg ? msg : "[ sign | verify | jwk2cert | cert2jwk | enckey | hash_base64url | timestamp] ");
return -1;
}
int file_read(apr_pool_t *pool, const char *path, char **rbuf) {
apr_file_t *fd = NULL;
char s_err[128];
int rc;
apr_size_t bytes_read = 0;
apr_finfo_t finfo;
apr_size_t len;
rc = apr_file_open(&fd, path, APR_FOPEN_READ | APR_FOPEN_BUFFERED,
APR_OS_DEFAULT, pool);
if (rc != APR_SUCCESS) {
fprintf(stderr, "could not open file %s: %s", path,
apr_strerror(rc, s_err, sizeof(s_err)));
return -1;
}
apr_file_info_get(&finfo, APR_FINFO_NORM, fd);
len = (apr_size_t) finfo.size;
*rbuf = apr_pcalloc(pool, len + 1);
rc = apr_file_read_full(fd, *rbuf, len, &bytes_read);
if (rc != APR_SUCCESS) {
fprintf(stderr, "could not read file %s: %s", path,
apr_strerror(rc, s_err, sizeof(s_err)));
return -1;
}
(*rbuf)[bytes_read] = '\0';
bytes_read--;
while ((*rbuf)[bytes_read] == '\n') {
(*rbuf)[bytes_read] = '\0';
bytes_read --;
}
apr_file_close(fd);
return 0;
}
int sign(int argc, char **argv, apr_pool_t *pool) {
if (argc <= 4)
return usage(argc, argv, "sign ");
char *s_jwt = NULL, *s_jwk = NULL;
const char *cser = NULL;
if (file_read(pool, argv[3], &s_jwt) != 0)
return -1;
if (file_read(pool, argv[4], &s_jwk) != 0)
return -1;
cjose_err cjose_err;
cjose_header_t *hdr = cjose_header_new(&cjose_err);
cjose_header_set(hdr, "alg", argv[2], &cjose_err);
cjose_jwk_t *jwk = cjose_jwk_import(s_jwk, strlen(s_jwk), &cjose_err);
if (jwk == NULL) {
fprintf(stderr,
"could not import JWK: %s [file: %s, function: %s, line: %ld]\n",
cjose_err.message, cjose_err.file, cjose_err.function,
cjose_err.line);
return -1;
}
cjose_jws_t *jws = cjose_jws_sign(jwk, hdr, (const uint8_t *) s_jwt,
strlen(s_jwt), &cjose_err);
if (jws == NULL) {
fprintf(stderr,
"could not sign JWS: %s [file: %s, function: %s, line: %ld]\n",
cjose_err.message, cjose_err.file, cjose_err.function,
cjose_err.line);
return -1;
}
if (cjose_jws_export(jws, &cser, &cjose_err) == FALSE) {
fprintf(stderr,
"could not serialize JWS: %s [file: %s, function: %s, line: %ld]\n",
cjose_err.message, cjose_err.file, cjose_err.function,
cjose_err.line);
return -1;
}
fprintf(stdout, "%s", cser);
cjose_jws_release(jws);
cjose_jwk_release(jwk);
return 0;
}
int verify(int argc, char **argv, apr_pool_t *pool) {
if (argc <= 3)
return usage(argc, argv, "verify ");
char *s_jwt = NULL, *s_jwk = NULL;
if (file_read(pool, argv[2], &s_jwt) != 0)
return -1;
if (file_read(pool, argv[3], &s_jwk) != 0)
return -1;
cjose_err cjose_err;
cjose_jws_t *jws = cjose_jws_import(s_jwt, strlen(s_jwt), &cjose_err);
if (jws == NULL) {
fprintf(stderr,
"could not import JWS: %s [file: %s, function: %s, line: %ld]\n",
cjose_err.message, cjose_err.file, cjose_err.function,
cjose_err.line);
return -1;
}
cjose_jwk_t *jwk = cjose_jwk_import(s_jwk, strlen(s_jwk), &cjose_err);
if (jwk == NULL) {
fprintf(stderr,
"could not import JWK: %s [file: %s, function: %s, line: %ld]\n",
cjose_err.message, cjose_err.file, cjose_err.function,
cjose_err.line);
return -1;
}
if (cjose_jws_verify(jws, jwk, &cjose_err) == FALSE) {
fprintf(stderr,
"could not verify JWS: %s [file: %s, function: %s, line: %ld]\n",
cjose_err.message, cjose_err.file, cjose_err.function,
cjose_err.line);
return -1;
}
uint8_t *plaintext = NULL;
size_t plaintext_len = 0;
if (cjose_jws_get_plaintext(jws, &plaintext, &plaintext_len,
&cjose_err) == FALSE) {
fprintf(stderr,
"could not get plaintext: %s [file: %s, function: %s, line: %ld]\n",
cjose_err.message, cjose_err.file, cjose_err.function,
cjose_err.line);
return -1;
}
fprintf(stdout, "%s", plaintext);
cjose_jws_release(jws);
cjose_jwk_release(jwk);
return 0;
}
int mkcert(RSA *rsa, X509 **x509p, EVP_PKEY **pkeyp, int serial, int days) {
X509 *x;
EVP_PKEY *pk;
X509_NAME *name = NULL;
if ((pkeyp == NULL) || (*pkeyp == NULL)) {
if ((pk = EVP_PKEY_new()) == NULL)
return -1;
} else
pk = *pkeyp;
if ((x509p == NULL) || (*x509p == NULL)) {
if ((x = X509_new()) == NULL)
return -1;
} else
x = *x509p;
if (!EVP_PKEY_assign_RSA(pk, rsa))
return -1;
X509_set_version(x, 2);
ASN1_INTEGER_set(X509_get_serialNumber(x), serial);
X509_gmtime_adj(X509_get_notBefore(x), 0);
X509_gmtime_adj(X509_get_notAfter(x), (long) 60 * 60 * 24 * days);
X509_set_pubkey(x, pk);
name = X509_get_subject_name(x);
X509_NAME_add_entry_by_txt(name, "C",
MBSTRING_ASC, (const unsigned char *) "NL", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN",
MBSTRING_ASC, (const unsigned char *) "Ping Identity", -1, -1, 0);
X509_set_issuer_name(x, name);
if (!X509_sign(x, pk, EVP_md5()))
return -1;
*x509p = x;
*pkeyp = pk;
return 0;
}
int jwk2cert(int argc, char **argv, apr_pool_t *pool) {
if (argc <= 2)
return usage(argc, argv, "jwk2cert ");
char *s_jwk = NULL;
if (file_read(pool, argv[2], &s_jwk) != 0)
return -1;
cjose_err cjose_err;
cjose_jwk_t *jwk = cjose_jwk_import(s_jwk, strlen(s_jwk), &cjose_err);
if (jwk == NULL) {
fprintf(stderr,
"could not import JWK: %s [file: %s, function: %s, line: %ld]\n",
cjose_err.message, cjose_err.file, cjose_err.function,
cjose_err.line);
return -1;
}
if (cjose_jwk_get_kty(jwk, &cjose_err) != CJOSE_JWK_KTY_RSA) {
fprintf(stderr, "wrong key type");
return -1;
}
RSA *rsa = cjose_jwk_get_keydata(jwk, &cjose_err);
//PEM_write_RSAPublicKey(stdout, rsa);
PEM_write_RSA_PUBKEY(stdout, rsa);
X509 *x509 = NULL;
EVP_PKEY *pkey = NULL;
if (mkcert(rsa, &x509, &pkey, 0, 365) != 0)
return -1;
//RSA_print_fp(stdout,pkey->pkey.rsa,0);
//X509_print_fp(stdout,x509);
//PEM_write_PrivateKey(stdout,pkey,NULL,NULL,0,NULL, NULL);
PEM_write_X509(stdout, x509);
X509_free(x509);
EVP_PKEY_free(pkey);
return 0;
}
int cert2jwk(int argc, char **argv, apr_pool_t *pool) {
if (argc <= 2)
return usage(argc, argv, "cert2jwk ");
oidc_jwk_t *jwk = NULL;
oidc_jose_error_t err;
if (oidc_jwk_parse_rsa_public_key(pool, NULL, argv[2], &jwk, &err) == FALSE) {
fprintf(stderr, "oidc_jwk_parse_rsa_public_key failed: %s", oidc_jose_e2s(pool, err));
return -1;
}
char *s_json = NULL;
if (oidc_jwk_to_json(pool, jwk, &s_json, &err) == FALSE) {
fprintf(stderr, "oidc_jwk_to_json failed: %s", oidc_jose_e2s(pool, err));
return -1;
}
fprintf(stdout, "%s", s_json);
oidc_jwk_destroy(jwk);
return 0;
}
extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
typedef struct oidc_dir_cfg oidc_dir_cfg;
static request_rec * request_setup(apr_pool_t *pool) {
const unsigned int kIdx = 0;
const unsigned int kEls = kIdx + 1;
request_rec *request = (request_rec *) apr_pcalloc(pool,
sizeof(request_rec));
request->pool = pool;
request->headers_in = apr_table_make(request->pool, 0);
request->headers_out = apr_table_make(request->pool, 0);
request->err_headers_out = apr_table_make(request->pool, 0);
apr_table_set(request->headers_in, "Host", "www.example.com");
apr_table_set(request->headers_in, "OIDC_foo", "some-value");
apr_table_set(request->headers_in, "Cookie", "foo=bar; "
"mod_auth_openidc_session" "=0123456789abcdef; baz=zot");
request->server = apr_pcalloc(request->pool, sizeof(struct server_rec));
request->server->process = apr_pcalloc(request->pool,
sizeof(struct process_rec));
request->server->process->pool = request->pool;
request->connection = apr_pcalloc(request->pool, sizeof(struct conn_rec));
request->connection->local_addr = apr_pcalloc(request->pool,
sizeof(apr_sockaddr_t));
apr_pool_userdata_set("https", "scheme", NULL, request->pool);
request->server->server_hostname = "www.example.com";
request->connection->local_addr->port = 443;
request->unparsed_uri = "/bla?foo=bar¶m1=value1";
request->args = "foo=bar¶m1=value1";
apr_uri_parse(request->pool,
"https://www.example.com/bla?foo=bar¶m1=value1",
&request->parsed_uri);
auth_openidc_module.module_index = kIdx;
oidc_cfg *cfg = oidc_create_server_config(request->pool, request->server);
cfg->provider.issuer = "https://idp.example.com";
cfg->provider.authorization_endpoint_url =
"https://idp.example.com/authorize";
cfg->provider.scope = "openid";
cfg->provider.client_id = "client_id";
cfg->redirect_uri = "https://www.example.com/protected/";
oidc_dir_cfg *d_cfg = oidc_create_dir_config(request->pool, NULL);
request->server->module_config = apr_pcalloc(request->pool,
sizeof(ap_conf_vector_t *) * kEls);
request->per_dir_config = apr_pcalloc(request->pool,
sizeof(ap_conf_vector_t *) * kEls);
ap_set_module_config(request->server->module_config, &auth_openidc_module,
cfg);
ap_set_module_config(request->per_dir_config, &auth_openidc_module, d_cfg);
cfg->cache = &oidc_cache_shm;
cfg->cache_cfg = NULL;
cfg->cache_shm_size_max = 500;
cfg->cache_shm_entry_size_max = 16384 + 255 + 17;
if (cfg->cache->post_config(request->server) != OK) {
printf("cfg->cache->post_config failed!\n");
exit(-1);
}
return request;
}
int enckey(int argc, char **argv, apr_pool_t *pool) {
if (argc <= 2)
return usage(argc, argv, "enckey [hash] [key-length]");
request_rec *r = request_setup(pool);
oidc_jwk_t *jwk = NULL;
if (oidc_util_create_symmetric_key(r, argv[2], argc > 4 ? atoi(argv[4]) : 0, argc > 3 ? argv[3] : NULL, FALSE, &jwk) == FALSE) {
fprintf(stderr, "oidc_util_create_symmetric_key failed");
return -1;
}
oidc_jose_error_t err;
char *s_json = NULL;
if (oidc_jwk_to_json(pool, jwk, &s_json, &err) == FALSE) {
fprintf(stderr, "oidc_jwk_to_json failed");
return -1;
}
cjose_err cjose_err;
int src_len = cjose_jwk_get_keysize(jwk->cjose_jwk, &cjose_err) / 8;
int enc_len = apr_base64_encode_len(src_len);
char *b64 = apr_palloc(r->pool, enc_len);
apr_base64_encode(b64, (const char *) cjose_jwk_get_keydata(jwk->cjose_jwk, &cjose_err), src_len);
fprintf(stdout, "\nJWK:\n%s\n\nbase64:\n%s\n\n", s_json, b64);
return 0;
}
int hash_base64url(int argc, char **argv, apr_pool_t *pool) {
if (argc <= 2)
return usage(argc, argv, "hash_base64url [algo]");
char *algo = argc > 3 ? argv[3] : "sha256";
char *output = NULL;
request_rec *r = request_setup(pool);
if (oidc_util_hash_string_and_base64url_encode(r, algo, argv[2], &output) == FALSE) {
fprintf(stderr, "oidc_util_hash_string_and_base64url_encode failed");
return -1;
}
fprintf(stdout, "%s\n", output);
return 0;
}
int timestamp(int argc, char **argv, apr_pool_t *pool) {
if (argc <= 2)
return usage(argc, argv, "timestamp ");
long delta = strtol(argv[2], NULL, 10);
apr_time_t t1 = apr_time_now() + apr_time_from_sec(delta);
char *s = apr_psprintf(pool, "%" APR_TIME_T_FMT, t1);
fprintf(stderr, "timestamp (1) = %s\n", s);
apr_time_t t2;
sscanf(s, "%" APR_TIME_T_FMT, &t2);
fprintf(stderr, "timestamp (2) = %" APR_TIME_T_FMT "\n", t2);
char buf[APR_RFC822_DATE_LEN + 1];
apr_rfc822_date(buf, t2);
fprintf(stderr, "timestamp (3): %s (%" APR_TIME_T_FMT " secs from now)\n", buf, apr_time_sec(t2 - apr_time_now()));
return 0;
}
int main(int argc, char **argv, char **env) {
if (argc <= 1)
return usage(argc, argv, NULL);
if (apr_app_initialize(&argc, (const char * const **) argv,
(const char * const **) env) != APR_SUCCESS) {
printf("apr_app_initialize failed\n");
return -1;
}
OpenSSL_add_all_algorithms();
apr_pool_t *pool = NULL;
apr_pool_create(&pool, NULL);
if (strcmp(argv[1], "sign") == 0)
return sign(argc, argv, pool);
if (strcmp(argv[1], "verify") == 0)
return verify(argc, argv, pool);
if (strcmp(argv[1], "jwk2cert") == 0)
return jwk2cert(argc, argv, pool);
if (strcmp(argv[1], "cert2jwk") == 0)
return cert2jwk(argc, argv, pool);
if (strcmp(argv[1], "enckey") == 0)
return enckey(argc, argv, pool);
if (strcmp(argv[1], "hash_base64url") == 0)
return hash_base64url(argc, argv, pool);
if (strcmp(argv[1], "timestamp") == 0)
return timestamp(argc, argv, pool);
apr_pool_destroy(pool);
apr_terminate();
return usage(argc, argv, NULL);
}
mod_auth_openidc-2.3.3/test/stub.c 0000644 0000765 0000024 00000011227 13200625410 016752 0 ustar hzandbelt staff #include
#include
#include
#include
#include
#define ap_HOOK_check_user_id_t void
AP_DECLARE(void) ap_hook_check_authn(ap_HOOK_check_user_id_t *pf,
const char * const *aszPre,
const char * const *aszSucc,
int nOrder, int type) {
}
AP_DECLARE(apr_status_t) ap_register_auth_provider(apr_pool_t *pool,
const char *provider_group,
const char *provider_name,
const char *provider_version,
const void *provider,
int type) {
return 0;
}
AP_DECLARE(apr_status_t) ap_unixd_set_global_mutex_perms(apr_global_mutex_t *gmutex) {
return 0;
}
AP_DECLARE(const char *) ap_auth_type(request_rec *r) {
return "openid-connect";
}
AP_DECLARE(const char *) ap_auth_name(request_rec *r) {
return NULL;
}
AP_DECLARE(long) ap_get_client_block(request_rec * r, char * buffer,
apr_size_t bufsiz) {
return 0;
}
AP_DECLARE(char *) ap_getword(apr_pool_t *p, const char **line, char stop) {
return "";
}
AP_DECLARE(char *) ap_getword_conf(apr_pool_t *p, const char **line) {
return "";
}
AP_DECLARE(char *) ap_getword_white(apr_pool_t *p, const char **line) {
return 0;
}
AP_DECLARE(int) ap_hook_check_user_id(request_rec *r) {
return 0;
}
AP_DECLARE(int) ap_hook_auth_checker(request_rec *r) {
return 0;
}
AP_DECLARE(int) ap_hook_fixups(request_rec *r) {
return 0;
}
AP_DECLARE(void) ap_hook_post_config(
int (*post_config)(apr_pool_t *pool, apr_pool_t *p1, apr_pool_t *p2,
server_rec *s), const char * const *aszPre,
const char * const *aszSucc, int nOrder) {
}
AP_DECLARE(void) ap_hook_child_init(
void (*child_init)(apr_pool_t *p, server_rec *s),
const char * const *aszPre, const char * const *aszSucc, int nOrder) {
}
AP_DECLARE(void) ap_hook_handler(
int (*handler)(request_rec *r),
const char * const *aszPre, const char * const *aszSucc, int nOrder) {
}
AP_DECLARE(int) ap_is_initial_req(request_rec *r) {
return 0;
}
#if MODULE_MAGIC_NUMBER_MAJOR >= 20100714
AP_DECLARE(void) ap_log_error_(const char *file, int line, int module_index, int level,
apr_status_t status, const server_rec *s, const char *fmt, ...) {
#else
AP_DECLARE(void) ap_log_error(const char *file, int line, int level,
apr_status_t status, const server_rec *s, const char *fmt, ...) {
#endif
if (level < APLOG_DEBUG) {
fprintf(stderr, "%s:%d [%d] [%d] ", file, line, level, status);
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
}
}
#if MODULE_MAGIC_NUMBER_MAJOR >= 20100714
AP_DECLARE(void) ap_log_rerror_(const char *file, int line, int module_index, int level,
apr_status_t status, const request_rec *r, const char *fmt, ...) {
#else
AP_DECLARE(void) ap_log_rerror(const char *file, int line, int level,
apr_status_t status, const request_rec *r, const char *fmt, ...) {
#endif
if (level < APLOG_DEBUG) {
fprintf(stderr, "%s:%d [%d] [%d] ", file, line, level, status);
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
}
}
AP_DECLARE(void) ap_note_auth_failure(request_rec *r) {
}
AP_DECLARE(apr_status_t) ap_pass_brigade(ap_filter_t *filter,
apr_bucket_brigade *bucket) {
return APR_SUCCESS;
}
AP_DECLARE(const apr_array_header_t *) ap_requires(request_rec *r) {
return NULL;
}
const char *ap_run_http_scheme(const request_rec *r) {
char *rv;
apr_pool_userdata_get((void **) &rv, "scheme", r->pool);
return (const char *) rv;
}
AP_DECLARE(void) ap_set_content_type(request_rec *r, const char *ct) {
}
AP_DECLARE_NONSTD(const char *) ap_set_flag_slot(cmd_parms *cmd,
void *struct_ptr,
int arg) {
return "";
}
AP_DECLARE_NONSTD(const char *) ap_set_string_slot(cmd_parms *cmd,
void *struct_ptr,
const char *arg) {
return "";
}
AP_DECLARE_NONSTD(const char *) ap_set_int_slot(cmd_parms *cmd,
void *struct_ptr,
const char *arg) {
return "";
}
AP_DECLARE(int) ap_setup_client_block(request_rec *r, int read_policy) {
return 0;
}
AP_DECLARE(int) ap_should_client_block(request_rec *r) {
return 0;
}
AP_DECLARE(int) ap_unescape_url(char *url) {
return 0;
}
AP_DECLARE(apr_status_t) unixd_set_global_mutex_perms(
apr_global_mutex_t *gmutex) {
return APR_SUCCESS;
}
AP_DECLARE(const char *) ap_get_server_name(request_rec *r) {
return "www.example.com";
}
AP_DECLARE(char *) ap_server_root_relative(apr_pool_t *p, const char *file) {
return "";
}
mod_auth_openidc-2.3.3/configure 0000755 0000765 0000024 00000475522 13203321123 016572 0 ustar hzandbelt staff #! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.69 for mod_auth_openidc 2.3.3.
#
# Report bugs to .
#
#
# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
#
#
# This configure script is free software; the Free Software Foundation
# gives unlimited permission to copy, distribute and modify it.
## -------------------- ##
## M4sh Initialization. ##
## -------------------- ##
# Be more Bourne compatible
DUALCASE=1; export DUALCASE # for MKS sh
if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
emulate sh
NULLCMD=:
# Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
# is contrary to our usage. Disable this feature.
alias -g '${1+"$@"}'='"$@"'
setopt NO_GLOB_SUBST
else
case `(set -o) 2>/dev/null` in #(
*posix*) :
set -o posix ;; #(
*) :
;;
esac
fi
as_nl='
'
export as_nl
# Printing a long string crashes Solaris 7 /usr/bin/printf.
as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
# Prefer a ksh shell builtin over an external printf program on Solaris,
# but without wasting forks for bash or zsh.
if test -z "$BASH_VERSION$ZSH_VERSION" \
&& (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
as_echo='print -r --'
as_echo_n='print -rn --'
elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
as_echo='printf %s\n'
as_echo_n='printf %s'
else
if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
as_echo_n='/usr/ucb/echo -n'
else
as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
as_echo_n_body='eval
arg=$1;
case $arg in #(
*"$as_nl"*)
expr "X$arg" : "X\\(.*\\)$as_nl";
arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
esac;
expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
'
export as_echo_n_body
as_echo_n='sh -c $as_echo_n_body as_echo'
fi
export as_echo_body
as_echo='sh -c $as_echo_body as_echo'
fi
# The user is always right.
if test "${PATH_SEPARATOR+set}" != set; then
PATH_SEPARATOR=:
(PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
(PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
PATH_SEPARATOR=';'
}
fi
# IFS
# We need space, tab and new line, in precisely that order. Quoting is
# there to prevent editors from complaining about space-tab.
# (If _AS_PATH_WALK were called with IFS unset, it would disable word
# splitting by setting IFS to empty value.)
IFS=" "" $as_nl"
# Find who we are. Look in the path if we contain no directory separator.
as_myself=
case $0 in #((
*[\\/]* ) as_myself=$0 ;;
*) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
done
IFS=$as_save_IFS
;;
esac
# We did not find ourselves, most probably we were run as `sh COMMAND'
# in which case we are not to be found in the path.
if test "x$as_myself" = x; then
as_myself=$0
fi
if test ! -f "$as_myself"; then
$as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
exit 1
fi
# Unset variables that we do not need and which cause bugs (e.g. in
# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1"
# suppresses any "Segmentation fault" message there. '((' could
# trigger a bug in pdksh 5.2.14.
for as_var in BASH_ENV ENV MAIL MAILPATH
do eval test x\${$as_var+set} = xset \
&& ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
done
PS1='$ '
PS2='> '
PS4='+ '
# NLS nuisances.
LC_ALL=C
export LC_ALL
LANGUAGE=C
export LANGUAGE
# CDPATH.
(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
# Use a proper internal environment variable to ensure we don't fall
# into an infinite loop, continuously re-executing ourselves.
if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then
_as_can_reexec=no; export _as_can_reexec;
# We cannot yet assume a decent shell, so we have to provide a
# neutralization value for shells without unset; and this also
# works around shells that cannot unset nonexistent variables.
# Preserve -v and -x to the replacement shell.
BASH_ENV=/dev/null
ENV=/dev/null
(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
case $- in # ((((
*v*x* | *x*v* ) as_opts=-vx ;;
*v* ) as_opts=-v ;;
*x* ) as_opts=-x ;;
* ) as_opts= ;;
esac
exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
# Admittedly, this is quite paranoid, since all the known shells bail
# out after a failed `exec'.
$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
as_fn_exit 255
fi
# We don't want this to propagate to other subprocesses.
{ _as_can_reexec=; unset _as_can_reexec;}
if test "x$CONFIG_SHELL" = x; then
as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then :
emulate sh
NULLCMD=:
# Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which
# is contrary to our usage. Disable this feature.
alias -g '\${1+\"\$@\"}'='\"\$@\"'
setopt NO_GLOB_SUBST
else
case \`(set -o) 2>/dev/null\` in #(
*posix*) :
set -o posix ;; #(
*) :
;;
esac
fi
"
as_required="as_fn_return () { (exit \$1); }
as_fn_success () { as_fn_return 0; }
as_fn_failure () { as_fn_return 1; }
as_fn_ret_success () { return 0; }
as_fn_ret_failure () { return 1; }
exitcode=0
as_fn_success || { exitcode=1; echo as_fn_success failed.; }
as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; }
as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; }
as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; }
if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then :
else
exitcode=1; echo positional parameters were not saved.
fi
test x\$exitcode = x0 || exit 1
test -x / || exit 1"
as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO
as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO
eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" &&
test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1
test \$(( 1 + 1 )) = 2 || exit 1"
if (eval "$as_required") 2>/dev/null; then :
as_have_required=yes
else
as_have_required=no
fi
if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then :
else
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
as_found=false
for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
as_found=:
case $as_dir in #(
/*)
for as_base in sh bash ksh sh5; do
# Try only shells that exist, to save several forks.
as_shell=$as_dir/$as_base
if { test -f "$as_shell" || test -f "$as_shell.exe"; } &&
{ $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then :
CONFIG_SHELL=$as_shell as_have_required=yes
if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then :
break 2
fi
fi
done;;
esac
as_found=false
done
$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } &&
{ $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then :
CONFIG_SHELL=$SHELL as_have_required=yes
fi; }
IFS=$as_save_IFS
if test "x$CONFIG_SHELL" != x; then :
export CONFIG_SHELL
# We cannot yet assume a decent shell, so we have to provide a
# neutralization value for shells without unset; and this also
# works around shells that cannot unset nonexistent variables.
# Preserve -v and -x to the replacement shell.
BASH_ENV=/dev/null
ENV=/dev/null
(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
case $- in # ((((
*v*x* | *x*v* ) as_opts=-vx ;;
*v* ) as_opts=-v ;;
*x* ) as_opts=-x ;;
* ) as_opts= ;;
esac
exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
# Admittedly, this is quite paranoid, since all the known shells bail
# out after a failed `exec'.
$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
exit 255
fi
if test x$as_have_required = xno; then :
$as_echo "$0: This script requires a shell more modern than all"
$as_echo "$0: the shells that I found on your system."
if test x${ZSH_VERSION+set} = xset ; then
$as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should"
$as_echo "$0: be upgraded to zsh 4.3.4 or later."
else
$as_echo "$0: Please tell bug-autoconf@gnu.org and
$0: hans.zandbelt@zmartzone.eu about your system, including
$0: any error possibly output before this message. Then
$0: install a modern shell, or manually run the script
$0: under such a shell if you do have one."
fi
exit 1
fi
fi
fi
SHELL=${CONFIG_SHELL-/bin/sh}
export SHELL
# Unset more variables known to interfere with behavior of common tools.
CLICOLOR_FORCE= GREP_OPTIONS=
unset CLICOLOR_FORCE GREP_OPTIONS
## --------------------- ##
## M4sh Shell Functions. ##
## --------------------- ##
# as_fn_unset VAR
# ---------------
# Portably unset VAR.
as_fn_unset ()
{
{ eval $1=; unset $1;}
}
as_unset=as_fn_unset
# as_fn_set_status STATUS
# -----------------------
# Set $? to STATUS, without forking.
as_fn_set_status ()
{
return $1
} # as_fn_set_status
# as_fn_exit STATUS
# -----------------
# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
as_fn_exit ()
{
set +e
as_fn_set_status $1
exit $1
} # as_fn_exit
# as_fn_mkdir_p
# -------------
# Create "$as_dir" as a directory, including parents if necessary.
as_fn_mkdir_p ()
{
case $as_dir in #(
-*) as_dir=./$as_dir;;
esac
test -d "$as_dir" || eval $as_mkdir_p || {
as_dirs=
while :; do
case $as_dir in #(
*\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
*) as_qdir=$as_dir;;
esac
as_dirs="'$as_qdir' $as_dirs"
as_dir=`$as_dirname -- "$as_dir" ||
$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
X"$as_dir" : 'X\(//\)[^/]' \| \
X"$as_dir" : 'X\(//\)$' \| \
X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X"$as_dir" |
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
s//\1/
q
}
/^X\(\/\/\)[^/].*/{
s//\1/
q
}
/^X\(\/\/\)$/{
s//\1/
q
}
/^X\(\/\).*/{
s//\1/
q
}
s/.*/./; q'`
test -d "$as_dir" && break
done
test -z "$as_dirs" || eval "mkdir $as_dirs"
} || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
} # as_fn_mkdir_p
# as_fn_executable_p FILE
# -----------------------
# Test if FILE is an executable regular file.
as_fn_executable_p ()
{
test -f "$1" && test -x "$1"
} # as_fn_executable_p
# as_fn_append VAR VALUE
# ----------------------
# Append the text in VALUE to the end of the definition contained in VAR. Take
# advantage of any shell optimizations that allow amortized linear growth over
# repeated appends, instead of the typical quadratic growth present in naive
# implementations.
if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
eval 'as_fn_append ()
{
eval $1+=\$2
}'
else
as_fn_append ()
{
eval $1=\$$1\$2
}
fi # as_fn_append
# as_fn_arith ARG...
# ------------------
# Perform arithmetic evaluation on the ARGs, and store the result in the
# global $as_val. Take advantage of shells that can avoid forks. The arguments
# must be portable across $(()) and expr.
if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
eval 'as_fn_arith ()
{
as_val=$(( $* ))
}'
else
as_fn_arith ()
{
as_val=`expr "$@" || test $? -eq 1`
}
fi # as_fn_arith
# as_fn_error STATUS ERROR [LINENO LOG_FD]
# ----------------------------------------
# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
# script with STATUS, using 1 if that was 0.
as_fn_error ()
{
as_status=$1; test $as_status -eq 0 && as_status=1
if test "$4"; then
as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
$as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
fi
$as_echo "$as_me: error: $2" >&2
as_fn_exit $as_status
} # as_fn_error
if expr a : '\(a\)' >/dev/null 2>&1 &&
test "X`expr 00001 : '.*\(...\)'`" = X001; then
as_expr=expr
else
as_expr=false
fi
if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
as_basename=basename
else
as_basename=false
fi
if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
as_dirname=dirname
else
as_dirname=false
fi
as_me=`$as_basename -- "$0" ||
$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
X"$0" : 'X\(//\)$' \| \
X"$0" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X/"$0" |
sed '/^.*\/\([^/][^/]*\)\/*$/{
s//\1/
q
}
/^X\/\(\/\/\)$/{
s//\1/
q
}
/^X\/\(\/\).*/{
s//\1/
q
}
s/.*/./; q'`
# Avoid depending upon Character Ranges.
as_cr_letters='abcdefghijklmnopqrstuvwxyz'
as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
as_cr_Letters=$as_cr_letters$as_cr_LETTERS
as_cr_digits='0123456789'
as_cr_alnum=$as_cr_Letters$as_cr_digits
as_lineno_1=$LINENO as_lineno_1a=$LINENO
as_lineno_2=$LINENO as_lineno_2a=$LINENO
eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" &&
test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || {
# Blame Lee E. McMahon (1931-1989) for sed's syntax. :-)
sed -n '
p
/[$]LINENO/=
' <$as_myself |
sed '
s/[$]LINENO.*/&-/
t lineno
b
:lineno
N
:loop
s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/
t loop
s/-\n.*//
' >$as_me.lineno &&
chmod +x "$as_me.lineno" ||
{ $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; }
# If we had to re-execute with $CONFIG_SHELL, we're ensured to have
# already done that, so ensure we don't try to do so again and fall
# in an infinite loop. This has already happened in practice.
_as_can_reexec=no; export _as_can_reexec
# Don't try to exec as it changes $[0], causing all sort of problems
# (the dirname of $[0] is not the place where we might find the
# original and so on. Autoconf is especially sensitive to this).
. "./$as_me.lineno"
# Exit status is that of the last command.
exit
}
ECHO_C= ECHO_N= ECHO_T=
case `echo -n x` in #(((((
-n*)
case `echo 'xy\c'` in
*c*) ECHO_T=' ';; # ECHO_T is single tab character.
xy) ECHO_C='\c';;
*) echo `echo ksh88 bug on AIX 6.1` > /dev/null
ECHO_T=' ';;
esac;;
*)
ECHO_N='-n';;
esac
rm -f conf$$ conf$$.exe conf$$.file
if test -d conf$$.dir; then
rm -f conf$$.dir/conf$$.file
else
rm -f conf$$.dir
mkdir conf$$.dir 2>/dev/null
fi
if (echo >conf$$.file) 2>/dev/null; then
if ln -s conf$$.file conf$$ 2>/dev/null; then
as_ln_s='ln -s'
# ... but there are two gotchas:
# 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
# 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
# In both cases, we have to default to `cp -pR'.
ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
as_ln_s='cp -pR'
elif ln conf$$.file conf$$ 2>/dev/null; then
as_ln_s=ln
else
as_ln_s='cp -pR'
fi
else
as_ln_s='cp -pR'
fi
rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
rmdir conf$$.dir 2>/dev/null
if mkdir -p . 2>/dev/null; then
as_mkdir_p='mkdir -p "$as_dir"'
else
test -d ./-p && rmdir ./-p
as_mkdir_p=false
fi
as_test_x='test -x'
as_executable_p=as_fn_executable_p
# Sed expression to map a string onto a valid CPP name.
as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
# Sed expression to map a string onto a valid variable name.
as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
test -n "$DJDIR" || exec 7<&0 &1
# Name of the host.
# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status,
# so uname gets run too.
ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
#
# Initializations.
#
ac_default_prefix=/usr/local
ac_clean_files=
ac_config_libobj_dir=.
LIBOBJS=
cross_compiling=no
subdirs=
MFLAGS=
MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='mod_auth_openidc'
PACKAGE_TARNAME='mod_auth_openidc'
PACKAGE_VERSION='2.3.3'
PACKAGE_STRING='mod_auth_openidc 2.3.3'
PACKAGE_BUGREPORT='hans.zandbelt@zmartzone.eu'
PACKAGE_URL=''
# Factoring default headers for most tests.
ac_includes_default="\
#include
#ifdef HAVE_SYS_TYPES_H
# include
#endif
#ifdef HAVE_SYS_STAT_H
# include
#endif
#ifdef STDC_HEADERS
# include
# include
#else
# ifdef HAVE_STDLIB_H
# include
# endif
#endif
#ifdef HAVE_STRING_H
# if !defined STDC_HEADERS && defined HAVE_MEMORY_H
# include
# endif
# include
#endif
#ifdef HAVE_STRINGS_H
# include
#endif
#ifdef HAVE_INTTYPES_H
# include
#endif
#ifdef HAVE_STDINT_H
# include
#endif
#ifdef HAVE_UNISTD_H
# include
#endif"
ac_subst_vars='LTLIBOBJS
LIBOBJS
JQ_LIBS
JQ_CFLAGS
HAVE_LIBJQ
EGREP
GREP
CPP
OBJEXT
EXEEXT
ac_ct_CC
CPPFLAGS
LDFLAGS
CFLAGS
CC
HAVE_LIBHIREDIS
HIREDIS_LIBS
HIREDIS_CFLAGS
PCRE_LIBS
PCRE_CFLAGS
CJOSE_LIBS
CJOSE_CFLAGS
JANSSON_LIBS
JANSSON_CFLAGS
APR_LIBS
APR_CFLAGS
OPENSSL_LIBS
OPENSSL_CFLAGS
CURL_LIBS
CURL_CFLAGS
PKG_CONFIG_LIBDIR
PKG_CONFIG_PATH
PKG_CONFIG
APXS2_OPTS
APXS2
NAMEVER
target_alias
host_alias
build_alias
LIBS
ECHO_T
ECHO_N
ECHO_C
DEFS
mandir
localedir
libdir
psdir
pdfdir
dvidir
htmldir
infodir
docdir
oldincludedir
includedir
localstatedir
sharedstatedir
sysconfdir
datadir
datarootdir
libexecdir
sbindir
bindir
program_transform_name
prefix
exec_prefix
PACKAGE_URL
PACKAGE_BUGREPORT
PACKAGE_STRING
PACKAGE_VERSION
PACKAGE_TARNAME
PACKAGE_NAME
PATH_SEPARATOR
SHELL'
ac_subst_files=''
ac_user_opts='
enable_option_checking
with_apxs2
with_hiredis
with_jq
'
ac_precious_vars='build_alias
host_alias
target_alias
APXS2_OPTS
PKG_CONFIG
PKG_CONFIG_PATH
PKG_CONFIG_LIBDIR
CURL_CFLAGS
CURL_LIBS
OPENSSL_CFLAGS
OPENSSL_LIBS
APR_CFLAGS
APR_LIBS
JANSSON_CFLAGS
JANSSON_LIBS
CJOSE_CFLAGS
CJOSE_LIBS
PCRE_CFLAGS
PCRE_LIBS
HIREDIS_CFLAGS
HIREDIS_LIBS
CC
CFLAGS
LDFLAGS
LIBS
CPPFLAGS
CPP'
# Initialize some variables set by options.
ac_init_help=
ac_init_version=false
ac_unrecognized_opts=
ac_unrecognized_sep=
# The variables have the same names as the options, with
# dashes changed to underlines.
cache_file=/dev/null
exec_prefix=NONE
no_create=
no_recursion=
prefix=NONE
program_prefix=NONE
program_suffix=NONE
program_transform_name=s,x,x,
silent=
site=
srcdir=
verbose=
x_includes=NONE
x_libraries=NONE
# Installation directory options.
# These are left unexpanded so users can "make install exec_prefix=/foo"
# and all the variables that are supposed to be based on exec_prefix
# by default will actually change.
# Use braces instead of parens because sh, perl, etc. also accept them.
# (The list follows the same order as the GNU Coding Standards.)
bindir='${exec_prefix}/bin'
sbindir='${exec_prefix}/sbin'
libexecdir='${exec_prefix}/libexec'
datarootdir='${prefix}/share'
datadir='${datarootdir}'
sysconfdir='${prefix}/etc'
sharedstatedir='${prefix}/com'
localstatedir='${prefix}/var'
includedir='${prefix}/include'
oldincludedir='/usr/include'
docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
infodir='${datarootdir}/info'
htmldir='${docdir}'
dvidir='${docdir}'
pdfdir='${docdir}'
psdir='${docdir}'
libdir='${exec_prefix}/lib'
localedir='${datarootdir}/locale'
mandir='${datarootdir}/man'
ac_prev=
ac_dashdash=
for ac_option
do
# If the previous option needs an argument, assign it.
if test -n "$ac_prev"; then
eval $ac_prev=\$ac_option
ac_prev=
continue
fi
case $ac_option in
*=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
*=) ac_optarg= ;;
*) ac_optarg=yes ;;
esac
# Accept the important Cygnus configure options, so we can diagnose typos.
case $ac_dashdash$ac_option in
--)
ac_dashdash=yes ;;
-bindir | --bindir | --bindi | --bind | --bin | --bi)
ac_prev=bindir ;;
-bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
bindir=$ac_optarg ;;
-build | --build | --buil | --bui | --bu)
ac_prev=build_alias ;;
-build=* | --build=* | --buil=* | --bui=* | --bu=*)
build_alias=$ac_optarg ;;
-cache-file | --cache-file | --cache-fil | --cache-fi \
| --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
ac_prev=cache_file ;;
-cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
| --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
cache_file=$ac_optarg ;;
--config-cache | -C)
cache_file=config.cache ;;
-datadir | --datadir | --datadi | --datad)
ac_prev=datadir ;;
-datadir=* | --datadir=* | --datadi=* | --datad=*)
datadir=$ac_optarg ;;
-datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \
| --dataroo | --dataro | --datar)
ac_prev=datarootdir ;;
-datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \
| --dataroot=* | --dataroo=* | --dataro=* | --datar=*)
datarootdir=$ac_optarg ;;
-disable-* | --disable-*)
ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
# Reject names that are not valid shell variable names.
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
as_fn_error $? "invalid feature name: $ac_useropt"
ac_useropt_orig=$ac_useropt
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
case $ac_user_opts in
*"
"enable_$ac_useropt"
"*) ;;
*) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig"
ac_unrecognized_sep=', ';;
esac
eval enable_$ac_useropt=no ;;
-docdir | --docdir | --docdi | --doc | --do)
ac_prev=docdir ;;
-docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*)
docdir=$ac_optarg ;;
-dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv)
ac_prev=dvidir ;;
-dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*)
dvidir=$ac_optarg ;;
-enable-* | --enable-*)
ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
# Reject names that are not valid shell variable names.
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
as_fn_error $? "invalid feature name: $ac_useropt"
ac_useropt_orig=$ac_useropt
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
case $ac_user_opts in
*"
"enable_$ac_useropt"
"*) ;;
*) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig"
ac_unrecognized_sep=', ';;
esac
eval enable_$ac_useropt=\$ac_optarg ;;
-exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
| --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
| --exec | --exe | --ex)
ac_prev=exec_prefix ;;
-exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
| --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
| --exec=* | --exe=* | --ex=*)
exec_prefix=$ac_optarg ;;
-gas | --gas | --ga | --g)
# Obsolete; use --with-gas.
with_gas=yes ;;
-help | --help | --hel | --he | -h)
ac_init_help=long ;;
-help=r* | --help=r* | --hel=r* | --he=r* | -hr*)
ac_init_help=recursive ;;
-help=s* | --help=s* | --hel=s* | --he=s* | -hs*)
ac_init_help=short ;;
-host | --host | --hos | --ho)
ac_prev=host_alias ;;
-host=* | --host=* | --hos=* | --ho=*)
host_alias=$ac_optarg ;;
-htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht)
ac_prev=htmldir ;;
-htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \
| --ht=*)
htmldir=$ac_optarg ;;
-includedir | --includedir | --includedi | --included | --include \
| --includ | --inclu | --incl | --inc)
ac_prev=includedir ;;
-includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
| --includ=* | --inclu=* | --incl=* | --inc=*)
includedir=$ac_optarg ;;
-infodir | --infodir | --infodi | --infod | --info | --inf)
ac_prev=infodir ;;
-infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
infodir=$ac_optarg ;;
-libdir | --libdir | --libdi | --libd)
ac_prev=libdir ;;
-libdir=* | --libdir=* | --libdi=* | --libd=*)
libdir=$ac_optarg ;;
-libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
| --libexe | --libex | --libe)
ac_prev=libexecdir ;;
-libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
| --libexe=* | --libex=* | --libe=*)
libexecdir=$ac_optarg ;;
-localedir | --localedir | --localedi | --localed | --locale)
ac_prev=localedir ;;
-localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*)
localedir=$ac_optarg ;;
-localstatedir | --localstatedir | --localstatedi | --localstated \
| --localstate | --localstat | --localsta | --localst | --locals)
ac_prev=localstatedir ;;
-localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
| --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*)
localstatedir=$ac_optarg ;;
-mandir | --mandir | --mandi | --mand | --man | --ma | --m)
ac_prev=mandir ;;
-mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
mandir=$ac_optarg ;;
-nfp | --nfp | --nf)
# Obsolete; use --without-fp.
with_fp=no ;;
-no-create | --no-create | --no-creat | --no-crea | --no-cre \
| --no-cr | --no-c | -n)
no_create=yes ;;
-no-recursion | --no-recursion | --no-recursio | --no-recursi \
| --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
no_recursion=yes ;;
-oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
| --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
| --oldin | --oldi | --old | --ol | --o)
ac_prev=oldincludedir ;;
-oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
| --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
| --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
oldincludedir=$ac_optarg ;;
-prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
ac_prev=prefix ;;
-prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
prefix=$ac_optarg ;;
-program-prefix | --program-prefix | --program-prefi | --program-pref \
| --program-pre | --program-pr | --program-p)
ac_prev=program_prefix ;;
-program-prefix=* | --program-prefix=* | --program-prefi=* \
| --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
program_prefix=$ac_optarg ;;
-program-suffix | --program-suffix | --program-suffi | --program-suff \
| --program-suf | --program-su | --program-s)
ac_prev=program_suffix ;;
-program-suffix=* | --program-suffix=* | --program-suffi=* \
| --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
program_suffix=$ac_optarg ;;
-program-transform-name | --program-transform-name \
| --program-transform-nam | --program-transform-na \
| --program-transform-n | --program-transform- \
| --program-transform | --program-transfor \
| --program-transfo | --program-transf \
| --program-trans | --program-tran \
| --progr-tra | --program-tr | --program-t)
ac_prev=program_transform_name ;;
-program-transform-name=* | --program-transform-name=* \
| --program-transform-nam=* | --program-transform-na=* \
| --program-transform-n=* | --program-transform-=* \
| --program-transform=* | --program-transfor=* \
| --program-transfo=* | --program-transf=* \
| --program-trans=* | --program-tran=* \
| --progr-tra=* | --program-tr=* | --program-t=*)
program_transform_name=$ac_optarg ;;
-pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd)
ac_prev=pdfdir ;;
-pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*)
pdfdir=$ac_optarg ;;
-psdir | --psdir | --psdi | --psd | --ps)
ac_prev=psdir ;;
-psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*)
psdir=$ac_optarg ;;
-q | -quiet | --quiet | --quie | --qui | --qu | --q \
| -silent | --silent | --silen | --sile | --sil)
silent=yes ;;
-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
ac_prev=sbindir ;;
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
| --sbi=* | --sb=*)
sbindir=$ac_optarg ;;
-sharedstatedir | --sharedstatedir | --sharedstatedi \
| --sharedstated | --sharedstate | --sharedstat | --sharedsta \
| --sharedst | --shareds | --shared | --share | --shar \
| --sha | --sh)
ac_prev=sharedstatedir ;;
-sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
| --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
| --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
| --sha=* | --sh=*)
sharedstatedir=$ac_optarg ;;
-site | --site | --sit)
ac_prev=site ;;
-site=* | --site=* | --sit=*)
site=$ac_optarg ;;
-srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
ac_prev=srcdir ;;
-srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
srcdir=$ac_optarg ;;
-sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
| --syscon | --sysco | --sysc | --sys | --sy)
ac_prev=sysconfdir ;;
-sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
| --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
sysconfdir=$ac_optarg ;;
-target | --target | --targe | --targ | --tar | --ta | --t)
ac_prev=target_alias ;;
-target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
target_alias=$ac_optarg ;;
-v | -verbose | --verbose | --verbos | --verbo | --verb)
verbose=yes ;;
-version | --version | --versio | --versi | --vers | -V)
ac_init_version=: ;;
-with-* | --with-*)
ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
# Reject names that are not valid shell variable names.
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
as_fn_error $? "invalid package name: $ac_useropt"
ac_useropt_orig=$ac_useropt
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
case $ac_user_opts in
*"
"with_$ac_useropt"
"*) ;;
*) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig"
ac_unrecognized_sep=', ';;
esac
eval with_$ac_useropt=\$ac_optarg ;;
-without-* | --without-*)
ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'`
# Reject names that are not valid shell variable names.
expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
as_fn_error $? "invalid package name: $ac_useropt"
ac_useropt_orig=$ac_useropt
ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
case $ac_user_opts in
*"
"with_$ac_useropt"
"*) ;;
*) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig"
ac_unrecognized_sep=', ';;
esac
eval with_$ac_useropt=no ;;
--x)
# Obsolete; use --with-x.
with_x=yes ;;
-x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
| --x-incl | --x-inc | --x-in | --x-i)
ac_prev=x_includes ;;
-x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
| --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
x_includes=$ac_optarg ;;
-x-libraries | --x-libraries | --x-librarie | --x-librari \
| --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
ac_prev=x_libraries ;;
-x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
| --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
x_libraries=$ac_optarg ;;
-*) as_fn_error $? "unrecognized option: \`$ac_option'
Try \`$0 --help' for more information"
;;
*=*)
ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='`
# Reject names that are not valid shell variable names.
case $ac_envvar in #(
'' | [0-9]* | *[!_$as_cr_alnum]* )
as_fn_error $? "invalid variable name: \`$ac_envvar'" ;;
esac
eval $ac_envvar=\$ac_optarg
export $ac_envvar ;;
*)
# FIXME: should be removed in autoconf 3.0.
$as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2
expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
$as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2
: "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}"
;;
esac
done
if test -n "$ac_prev"; then
ac_option=--`echo $ac_prev | sed 's/_/-/g'`
as_fn_error $? "missing argument to $ac_option"
fi
if test -n "$ac_unrecognized_opts"; then
case $enable_option_checking in
no) ;;
fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;;
*) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;;
esac
fi
# Check all directory arguments for consistency.
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
datadir sysconfdir sharedstatedir localstatedir includedir \
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
libdir localedir mandir
do
eval ac_val=\$$ac_var
# Remove trailing slashes.
case $ac_val in
*/ )
ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'`
eval $ac_var=\$ac_val;;
esac
# Be sure to have absolute directory names.
case $ac_val in
[\\/$]* | ?:[\\/]* ) continue;;
NONE | '' ) case $ac_var in *prefix ) continue;; esac;;
esac
as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val"
done
# There might be people who depend on the old broken behavior: `$host'
# used to hold the argument of --host etc.
# FIXME: To remove some day.
build=$build_alias
host=$host_alias
target=$target_alias
# FIXME: To remove some day.
if test "x$host_alias" != x; then
if test "x$build_alias" = x; then
cross_compiling=maybe
elif test "x$build_alias" != "x$host_alias"; then
cross_compiling=yes
fi
fi
ac_tool_prefix=
test -n "$host_alias" && ac_tool_prefix=$host_alias-
test "$silent" = yes && exec 6>/dev/null
ac_pwd=`pwd` && test -n "$ac_pwd" &&
ac_ls_di=`ls -di .` &&
ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` ||
as_fn_error $? "working directory cannot be determined"
test "X$ac_ls_di" = "X$ac_pwd_ls_di" ||
as_fn_error $? "pwd does not report name of working directory"
# Find the source files, if location was not specified.
if test -z "$srcdir"; then
ac_srcdir_defaulted=yes
# Try the directory containing this script, then the parent directory.
ac_confdir=`$as_dirname -- "$as_myself" ||
$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
X"$as_myself" : 'X\(//\)[^/]' \| \
X"$as_myself" : 'X\(//\)$' \| \
X"$as_myself" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X"$as_myself" |
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
s//\1/
q
}
/^X\(\/\/\)[^/].*/{
s//\1/
q
}
/^X\(\/\/\)$/{
s//\1/
q
}
/^X\(\/\).*/{
s//\1/
q
}
s/.*/./; q'`
srcdir=$ac_confdir
if test ! -r "$srcdir/$ac_unique_file"; then
srcdir=..
fi
else
ac_srcdir_defaulted=no
fi
if test ! -r "$srcdir/$ac_unique_file"; then
test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .."
as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir"
fi
ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work"
ac_abs_confdir=`(
cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg"
pwd)`
# When building in place, set srcdir=.
if test "$ac_abs_confdir" = "$ac_pwd"; then
srcdir=.
fi
# Remove unnecessary trailing slashes from srcdir.
# Double slashes in file names in object file debugging info
# mess up M-x gdb in Emacs.
case $srcdir in
*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;;
esac
for ac_var in $ac_precious_vars; do
eval ac_env_${ac_var}_set=\${${ac_var}+set}
eval ac_env_${ac_var}_value=\$${ac_var}
eval ac_cv_env_${ac_var}_set=\${${ac_var}+set}
eval ac_cv_env_${ac_var}_value=\$${ac_var}
done
#
# Report the --help message.
#
if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
\`configure' configures mod_auth_openidc 2.3.3 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
To assign environment variables (e.g., CC, CFLAGS...), specify them as
VAR=VALUE. See below for descriptions of some of the useful variables.
Defaults for the options are specified in brackets.
Configuration:
-h, --help display this help and exit
--help=short display options specific to this package
--help=recursive display the short help of all the included packages
-V, --version display version information and exit
-q, --quiet, --silent do not print \`checking ...' messages
--cache-file=FILE cache test results in FILE [disabled]
-C, --config-cache alias for \`--cache-file=config.cache'
-n, --no-create do not create output files
--srcdir=DIR find the sources in DIR [configure dir or \`..']
Installation directories:
--prefix=PREFIX install architecture-independent files in PREFIX
[$ac_default_prefix]
--exec-prefix=EPREFIX install architecture-dependent files in EPREFIX
[PREFIX]
By default, \`make install' will install all the files in
\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify
an installation prefix other than \`$ac_default_prefix' using \`--prefix',
for instance \`--prefix=\$HOME'.
For better control, use the options below.
Fine tuning of the installation directories:
--bindir=DIR user executables [EPREFIX/bin]
--sbindir=DIR system admin executables [EPREFIX/sbin]
--libexecdir=DIR program executables [EPREFIX/libexec]
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
--libdir=DIR object code libraries [EPREFIX/lib]
--includedir=DIR C header files [PREFIX/include]
--oldincludedir=DIR C header files for non-gcc [/usr/include]
--datarootdir=DIR read-only arch.-independent data root [PREFIX/share]
--datadir=DIR read-only architecture-independent data [DATAROOTDIR]
--infodir=DIR info documentation [DATAROOTDIR/info]
--localedir=DIR locale-dependent data [DATAROOTDIR/locale]
--mandir=DIR man documentation [DATAROOTDIR/man]
--docdir=DIR documentation root
[DATAROOTDIR/doc/mod_auth_openidc]
--htmldir=DIR html documentation [DOCDIR]
--dvidir=DIR dvi documentation [DOCDIR]
--pdfdir=DIR pdf documentation [DOCDIR]
--psdir=DIR ps documentation [DOCDIR]
_ACEOF
cat <<\_ACEOF
_ACEOF
fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of mod_auth_openidc 2.3.3:";;
esac
cat <<\_ACEOF
Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
--without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
--with-apxs2=PATH Full path to the apxs2 executable.
--with-hiredis support Redis [default=check]
--with-jq=PATH location of your libjq installation
Some influential environment variables:
APXS2_OPTS Additional command line options to pass to apxs2.
PKG_CONFIG path to pkg-config utility
PKG_CONFIG_PATH
directories to add to pkg-config's search path
PKG_CONFIG_LIBDIR
path overriding pkg-config's built-in search path
CURL_CFLAGS C compiler flags for CURL, overriding pkg-config
CURL_LIBS linker flags for CURL, overriding pkg-config
OPENSSL_CFLAGS
C compiler flags for OPENSSL, overriding pkg-config
OPENSSL_LIBS
linker flags for OPENSSL, overriding pkg-config
APR_CFLAGS C compiler flags for APR, overriding pkg-config
APR_LIBS linker flags for APR, overriding pkg-config
JANSSON_CFLAGS
C compiler flags for JANSSON, overriding pkg-config
JANSSON_LIBS
linker flags for JANSSON, overriding pkg-config
CJOSE_CFLAGS
C compiler flags for CJOSE, overriding pkg-config
CJOSE_LIBS linker flags for CJOSE, overriding pkg-config
PCRE_CFLAGS C compiler flags for PCRE, overriding pkg-config
PCRE_LIBS linker flags for PCRE, overriding pkg-config
HIREDIS_CFLAGS
C compiler flags for HIREDIS, overriding pkg-config
HIREDIS_LIBS
linker flags for HIREDIS, overriding pkg-config
CC C compiler command
CFLAGS C compiler flags
LDFLAGS linker flags, e.g. -L if you have libraries in a
nonstandard directory
LIBS libraries to pass to the linker, e.g. -l
CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if
you have headers in a nonstandard directory
CPP C preprocessor
Use these variables to override the choices made by `configure' or to help
it to find libraries and programs with nonstandard names/locations.
Report bugs to .
_ACEOF
ac_status=$?
fi
if test "$ac_init_help" = "recursive"; then
# If there are subdirs, report their specific --help.
for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue
test -d "$ac_dir" ||
{ cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } ||
continue
ac_builddir=.
case "$ac_dir" in
.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
*)
ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
# A ".." for each directory in $ac_dir_suffix.
ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
case $ac_top_builddir_sub in
"") ac_top_builddir_sub=. ac_top_build_prefix= ;;
*) ac_top_build_prefix=$ac_top_builddir_sub/ ;;
esac ;;
esac
ac_abs_top_builddir=$ac_pwd
ac_abs_builddir=$ac_pwd$ac_dir_suffix
# for backward compatibility:
ac_top_builddir=$ac_top_build_prefix
case $srcdir in
.) # We are building in place.
ac_srcdir=.
ac_top_srcdir=$ac_top_builddir_sub
ac_abs_top_srcdir=$ac_pwd ;;
[\\/]* | ?:[\\/]* ) # Absolute name.
ac_srcdir=$srcdir$ac_dir_suffix;
ac_top_srcdir=$srcdir
ac_abs_top_srcdir=$srcdir ;;
*) # Relative name.
ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
ac_top_srcdir=$ac_top_build_prefix$srcdir
ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
esac
ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
cd "$ac_dir" || { ac_status=$?; continue; }
# Check for guested configure.
if test -f "$ac_srcdir/configure.gnu"; then
echo &&
$SHELL "$ac_srcdir/configure.gnu" --help=recursive
elif test -f "$ac_srcdir/configure"; then
echo &&
$SHELL "$ac_srcdir/configure" --help=recursive
else
$as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2
fi || ac_status=$?
cd "$ac_pwd" || { ac_status=$?; break; }
done
fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
mod_auth_openidc configure 2.3.3
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
This configure script is free software; the Free Software Foundation
gives unlimited permission to copy, distribute and modify it.
_ACEOF
exit
fi
## ------------------------ ##
## Autoconf initialization. ##
## ------------------------ ##
# ac_fn_c_try_compile LINENO
# --------------------------
# Try to compile conftest.$ac_ext, and return whether this succeeded.
ac_fn_c_try_compile ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
rm -f conftest.$ac_objext
if { { ac_try="$ac_compile"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_compile") 2>conftest.err
ac_status=$?
if test -s conftest.err; then
grep -v '^ *+' conftest.err >conftest.er1
cat conftest.er1 >&5
mv -f conftest.er1 conftest.err
fi
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; } && {
test -z "$ac_c_werror_flag" ||
test ! -s conftest.err
} && test -s conftest.$ac_objext; then :
ac_retval=0
else
$as_echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_retval=1
fi
eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
as_fn_set_status $ac_retval
} # ac_fn_c_try_compile
# ac_fn_c_try_cpp LINENO
# ----------------------
# Try to preprocess conftest.$ac_ext, and return whether this succeeded.
ac_fn_c_try_cpp ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
if { { ac_try="$ac_cpp conftest.$ac_ext"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_cpp conftest.$ac_ext") 2>conftest.err
ac_status=$?
if test -s conftest.err; then
grep -v '^ *+' conftest.err >conftest.er1
cat conftest.er1 >&5
mv -f conftest.er1 conftest.err
fi
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; } > conftest.i && {
test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" ||
test ! -s conftest.err
}; then :
ac_retval=0
else
$as_echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_retval=1
fi
eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
as_fn_set_status $ac_retval
} # ac_fn_c_try_cpp
# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES
# -------------------------------------------------------
# Tests whether HEADER exists, giving a warning if it cannot be compiled using
# the include files in INCLUDES and setting the cache variable VAR
# accordingly.
ac_fn_c_check_header_mongrel ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
if eval \${$3+:} false; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
$as_echo_n "checking for $2... " >&6; }
if eval \${$3+:} false; then :
$as_echo_n "(cached) " >&6
fi
eval ac_res=\$$3
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
$as_echo "$ac_res" >&6; }
else
# Is the header compilable?
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5
$as_echo_n "checking $2 usability... " >&6; }
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
$4
#include <$2>
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
ac_header_compiler=yes
else
ac_header_compiler=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5
$as_echo "$ac_header_compiler" >&6; }
# Is the header present?
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5
$as_echo_n "checking $2 presence... " >&6; }
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include <$2>
_ACEOF
if ac_fn_c_try_cpp "$LINENO"; then :
ac_header_preproc=yes
else
ac_header_preproc=no
fi
rm -f conftest.err conftest.i conftest.$ac_ext
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5
$as_echo "$ac_header_preproc" >&6; }
# So? What about this header?
case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #((
yes:no: )
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5
$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;}
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
;;
no:yes:* )
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5
$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;}
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5
$as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;}
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5
$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;}
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5
$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;}
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
( $as_echo "## ----------------------------------------- ##
## Report this to hans.zandbelt@zmartzone.eu ##
## ----------------------------------------- ##"
) | sed "s/^/$as_me: WARNING: /" >&2
;;
esac
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
$as_echo_n "checking for $2... " >&6; }
if eval \${$3+:} false; then :
$as_echo_n "(cached) " >&6
else
eval "$3=\$ac_header_compiler"
fi
eval ac_res=\$$3
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
$as_echo "$ac_res" >&6; }
fi
eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
} # ac_fn_c_check_header_mongrel
# ac_fn_c_try_run LINENO
# ----------------------
# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes
# that executables *can* be run.
ac_fn_c_try_run ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
if { { ac_try="$ac_link"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_link") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; } && { ac_try='./conftest$ac_exeext'
{ { case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_try") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; }; then :
ac_retval=0
else
$as_echo "$as_me: program exited with status $ac_status" >&5
$as_echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_retval=$ac_status
fi
rm -rf conftest.dSYM conftest_ipa8_conftest.oo
eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
as_fn_set_status $ac_retval
} # ac_fn_c_try_run
# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES
# -------------------------------------------------------
# Tests whether HEADER exists and can be compiled using the include files in
# INCLUDES, setting the cache variable VAR accordingly.
ac_fn_c_check_header_compile ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
$as_echo_n "checking for $2... " >&6; }
if eval \${$3+:} false; then :
$as_echo_n "(cached) " >&6
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
$4
#include <$2>
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
eval "$3=yes"
else
eval "$3=no"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
fi
eval ac_res=\$$3
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
$as_echo "$ac_res" >&6; }
eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
} # ac_fn_c_check_header_compile
# ac_fn_c_try_link LINENO
# -----------------------
# Try to link conftest.$ac_ext, and return whether this succeeded.
ac_fn_c_try_link ()
{
as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
rm -f conftest.$ac_objext conftest$ac_exeext
if { { ac_try="$ac_link"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_link") 2>conftest.err
ac_status=$?
if test -s conftest.err; then
grep -v '^ *+' conftest.err >conftest.er1
cat conftest.er1 >&5
mv -f conftest.er1 conftest.err
fi
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; } && {
test -z "$ac_c_werror_flag" ||
test ! -s conftest.err
} && test -s conftest$ac_exeext && {
test "$cross_compiling" = yes ||
test -x conftest$ac_exeext
}; then :
ac_retval=0
else
$as_echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_retval=1
fi
# Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
# created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
# interfere with the next link command; also delete a directory that is
# left behind by Apple's compiler. We do this before executing the actions.
rm -rf conftest.dSYM conftest_ipa8_conftest.oo
eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
as_fn_set_status $ac_retval
} # ac_fn_c_try_link
cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by mod_auth_openidc $as_me 2.3.3, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
_ACEOF
exec 5>>config.log
{
cat <<_ASUNAME
## --------- ##
## Platform. ##
## --------- ##
hostname = `(hostname || uname -n) 2>/dev/null | sed 1q`
uname -m = `(uname -m) 2>/dev/null || echo unknown`
uname -r = `(uname -r) 2>/dev/null || echo unknown`
uname -s = `(uname -s) 2>/dev/null || echo unknown`
uname -v = `(uname -v) 2>/dev/null || echo unknown`
/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown`
/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown`
/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown`
/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown`
/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown`
/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown`
/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown`
/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown`
/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown`
_ASUNAME
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
$as_echo "PATH: $as_dir"
done
IFS=$as_save_IFS
} >&5
cat >&5 <<_ACEOF
## ----------- ##
## Core tests. ##
## ----------- ##
_ACEOF
# Keep a trace of the command line.
# Strip out --no-create and --no-recursion so they do not pile up.
# Strip out --silent because we don't want to record it for future runs.
# Also quote any args containing shell meta-characters.
# Make two passes to allow for proper duplicate-argument suppression.
ac_configure_args=
ac_configure_args0=
ac_configure_args1=
ac_must_keep_next=false
for ac_pass in 1 2
do
for ac_arg
do
case $ac_arg in
-no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;;
-q | -quiet | --quiet | --quie | --qui | --qu | --q \
| -silent | --silent | --silen | --sile | --sil)
continue ;;
*\'*)
ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
esac
case $ac_pass in
1) as_fn_append ac_configure_args0 " '$ac_arg'" ;;
2)
as_fn_append ac_configure_args1 " '$ac_arg'"
if test $ac_must_keep_next = true; then
ac_must_keep_next=false # Got value, back to normal.
else
case $ac_arg in
*=* | --config-cache | -C | -disable-* | --disable-* \
| -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \
| -q | -quiet | --q* | -silent | --sil* | -v | -verb* \
| -with-* | --with-* | -without-* | --without-* | --x)
case "$ac_configure_args0 " in
"$ac_configure_args1"*" '$ac_arg' "* ) continue ;;
esac
;;
-* ) ac_must_keep_next=true ;;
esac
fi
as_fn_append ac_configure_args " '$ac_arg'"
;;
esac
done
done
{ ac_configure_args0=; unset ac_configure_args0;}
{ ac_configure_args1=; unset ac_configure_args1;}
# When interrupted or exit'd, cleanup temporary files, and complete
# config.log. We remove comments because anyway the quotes in there
# would cause problems or look ugly.
# WARNING: Use '\'' to represent an apostrophe within the trap.
# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug.
trap 'exit_status=$?
# Save into config.log some information that might help in debugging.
{
echo
$as_echo "## ---------------- ##
## Cache variables. ##
## ---------------- ##"
echo
# The following way of writing the cache mishandles newlines in values,
(
for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do
eval ac_val=\$$ac_var
case $ac_val in #(
*${as_nl}*)
case $ac_var in #(
*_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
esac
case $ac_var in #(
_ | IFS | as_nl) ;; #(
BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
*) { eval $ac_var=; unset $ac_var;} ;;
esac ;;
esac
done
(set) 2>&1 |
case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #(
*${as_nl}ac_space=\ *)
sed -n \
"s/'\''/'\''\\\\'\'''\''/g;
s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p"
;; #(
*)
sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
;;
esac |
sort
)
echo
$as_echo "## ----------------- ##
## Output variables. ##
## ----------------- ##"
echo
for ac_var in $ac_subst_vars
do
eval ac_val=\$$ac_var
case $ac_val in
*\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
esac
$as_echo "$ac_var='\''$ac_val'\''"
done | sort
echo
if test -n "$ac_subst_files"; then
$as_echo "## ------------------- ##
## File substitutions. ##
## ------------------- ##"
echo
for ac_var in $ac_subst_files
do
eval ac_val=\$$ac_var
case $ac_val in
*\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
esac
$as_echo "$ac_var='\''$ac_val'\''"
done | sort
echo
fi
if test -s confdefs.h; then
$as_echo "## ----------- ##
## confdefs.h. ##
## ----------- ##"
echo
cat confdefs.h
echo
fi
test "$ac_signal" != 0 &&
$as_echo "$as_me: caught signal $ac_signal"
$as_echo "$as_me: exit $exit_status"
} >&5
rm -f core *.core core.conftest.* &&
rm -f -r conftest* confdefs* conf$$* $ac_clean_files &&
exit $exit_status
' 0
for ac_signal in 1 2 13 15; do
trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal
done
ac_signal=0
# confdefs.h avoids OS command line length limits that DEFS can exceed.
rm -f -r conftest* confdefs.h
$as_echo "/* confdefs.h */" > confdefs.h
# Predefined preprocessor variables.
cat >>confdefs.h <<_ACEOF
#define PACKAGE_NAME "$PACKAGE_NAME"
_ACEOF
cat >>confdefs.h <<_ACEOF
#define PACKAGE_TARNAME "$PACKAGE_TARNAME"
_ACEOF
cat >>confdefs.h <<_ACEOF
#define PACKAGE_VERSION "$PACKAGE_VERSION"
_ACEOF
cat >>confdefs.h <<_ACEOF
#define PACKAGE_STRING "$PACKAGE_STRING"
_ACEOF
cat >>confdefs.h <<_ACEOF
#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT"
_ACEOF
cat >>confdefs.h <<_ACEOF
#define PACKAGE_URL "$PACKAGE_URL"
_ACEOF
# Let the site file select an alternate cache file if it wants to.
# Prefer an explicitly selected file to automatically selected ones.
ac_site_file1=NONE
ac_site_file2=NONE
if test -n "$CONFIG_SITE"; then
# We do not want a PATH search for config.site.
case $CONFIG_SITE in #((
-*) ac_site_file1=./$CONFIG_SITE;;
*/*) ac_site_file1=$CONFIG_SITE;;
*) ac_site_file1=./$CONFIG_SITE;;
esac
elif test "x$prefix" != xNONE; then
ac_site_file1=$prefix/share/config.site
ac_site_file2=$prefix/etc/config.site
else
ac_site_file1=$ac_default_prefix/share/config.site
ac_site_file2=$ac_default_prefix/etc/config.site
fi
for ac_site_file in "$ac_site_file1" "$ac_site_file2"
do
test "x$ac_site_file" = xNONE && continue
if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5
$as_echo "$as_me: loading site script $ac_site_file" >&6;}
sed 's/^/| /' "$ac_site_file" >&5
. "$ac_site_file" \
|| { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "failed to load site script $ac_site_file
See \`config.log' for more details" "$LINENO" 5; }
fi
done
if test -r "$cache_file"; then
# Some versions of bash will fail to source /dev/null (special files
# actually), so we avoid doing that. DJGPP emulates it as a regular file.
if test /dev/null != "$cache_file" && test -f "$cache_file"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5
$as_echo "$as_me: loading cache $cache_file" >&6;}
case $cache_file in
[\\/]* | ?:[\\/]* ) . "$cache_file";;
*) . "./$cache_file";;
esac
fi
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5
$as_echo "$as_me: creating cache $cache_file" >&6;}
>$cache_file
fi
# Check that the precious variables saved in the cache have kept the same
# value.
ac_cache_corrupted=false
for ac_var in $ac_precious_vars; do
eval ac_old_set=\$ac_cv_env_${ac_var}_set
eval ac_new_set=\$ac_env_${ac_var}_set
eval ac_old_val=\$ac_cv_env_${ac_var}_value
eval ac_new_val=\$ac_env_${ac_var}_value
case $ac_old_set,$ac_new_set in
set,)
{ $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
ac_cache_corrupted=: ;;
,set)
{ $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5
$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
ac_cache_corrupted=: ;;
,);;
*)
if test "x$ac_old_val" != "x$ac_new_val"; then
# differences in whitespace do not lead to failure.
ac_old_val_w=`echo x $ac_old_val`
ac_new_val_w=`echo x $ac_new_val`
if test "$ac_old_val_w" != "$ac_new_val_w"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5
$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
ac_cache_corrupted=:
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5
$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;}
eval $ac_var=\$ac_old_val
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5
$as_echo "$as_me: former value: \`$ac_old_val'" >&2;}
{ $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5
$as_echo "$as_me: current value: \`$ac_new_val'" >&2;}
fi;;
esac
# Pass precious variables to config.status.
if test "$ac_new_set" = set; then
case $ac_new_val in
*\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
*) ac_arg=$ac_var=$ac_new_val ;;
esac
case " $ac_configure_args " in
*" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy.
*) as_fn_append ac_configure_args " '$ac_arg'" ;;
esac
fi
done
if $ac_cache_corrupted; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
{ $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5
$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;}
as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5
fi
## -------------------- ##
## Main body of script. ##
## -------------------- ##
ac_ext=c
ac_cpp='$CPP $CPPFLAGS'
ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
ac_compiler_gnu=$ac_cv_c_compiler_gnu
NAMEVER=mod_auth_openidc-2.3.3
# This section defines the --with-apxs2 option.
# Check whether --with-apxs2 was given.
if test "${with_apxs2+set}" = set; then :
withval=$with_apxs2;
APXS2=${withval}
fi
if test "x$APXS2" = "x"; then
# The user didn't specify the --with-apxs2-option.
# Search for apxs2 in the specified directories
# Extract the first word of "apxs2", so it can be a program name with args.
set dummy apxs2; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
if ${ac_cv_path_APXS2+:} false; then :
$as_echo_n "(cached) " >&6
else
case $APXS2 in
[\\/]* | ?:[\\/]*)
ac_cv_path_APXS2="$APXS2" # Let the user override the test with a path.
;;
*)
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
as_dummy="/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
for as_dir in $as_dummy
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_exec_ext in '' $ac_executable_extensions; do
if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
ac_cv_path_APXS2="$as_dir/$ac_word$ac_exec_ext"
$as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
break 2
fi
done
done
IFS=$as_save_IFS
;;
esac
fi
APXS2=$ac_cv_path_APXS2
if test -n "$APXS2"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $APXS2" >&5
$as_echo "$APXS2" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
if test "x$APXS2" = "x"; then
# Didn't find apxs2 in any of the specified directories.
# Search for apxs instead.
# Extract the first word of "apxs", so it can be a program name with args.
set dummy apxs; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
if ${ac_cv_path_APXS2+:} false; then :
$as_echo_n "(cached) " >&6
else
case $APXS2 in
[\\/]* | ?:[\\/]*)
ac_cv_path_APXS2="$APXS2" # Let the user override the test with a path.
;;
*)
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
as_dummy="/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
for as_dir in $as_dummy
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_exec_ext in '' $ac_executable_extensions; do
if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
ac_cv_path_APXS2="$as_dir/$ac_word$ac_exec_ext"
$as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
break 2
fi
done
done
IFS=$as_save_IFS
;;
esac
fi
APXS2=$ac_cv_path_APXS2
if test -n "$APXS2"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $APXS2" >&5
$as_echo "$APXS2" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
fi
fi
# Test if $APXS2 exists and is an executable.
if test ! -x "$APXS2"; then
# $APXS2 isn't a executable file.
as_fn_error $? "
Could not find apxs2. Please specify the path to apxs2
using the --with-apxs2=/full/path/to/apxs2 option.
The executable may also be named 'apxs'.
" "$LINENO" 5
fi
# Replace any occurrences of @APXS2@ with the value of $APXS2 in the Makefile.
# Use environment varilable APXS2_OPTS to pass params to APXS2 command
# We need the curl library for HTTP callouts.
if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
if test -n "$ac_tool_prefix"; then
# Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args.
set dummy ${ac_tool_prefix}pkg-config; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
if ${ac_cv_path_PKG_CONFIG+:} false; then :
$as_echo_n "(cached) " >&6
else
case $PKG_CONFIG in
[\\/]* | ?:[\\/]*)
ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path.
;;
*)
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_exec_ext in '' $ac_executable_extensions; do
if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
$as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
break 2
fi
done
done
IFS=$as_save_IFS
;;
esac
fi
PKG_CONFIG=$ac_cv_path_PKG_CONFIG
if test -n "$PKG_CONFIG"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5
$as_echo "$PKG_CONFIG" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
fi
if test -z "$ac_cv_path_PKG_CONFIG"; then
ac_pt_PKG_CONFIG=$PKG_CONFIG
# Extract the first word of "pkg-config", so it can be a program name with args.
set dummy pkg-config; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
if ${ac_cv_path_ac_pt_PKG_CONFIG+:} false; then :
$as_echo_n "(cached) " >&6
else
case $ac_pt_PKG_CONFIG in
[\\/]* | ?:[\\/]*)
ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path.
;;
*)
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_exec_ext in '' $ac_executable_extensions; do
if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext"
$as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
break 2
fi
done
done
IFS=$as_save_IFS
;;
esac
fi
ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG
if test -n "$ac_pt_PKG_CONFIG"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5
$as_echo "$ac_pt_PKG_CONFIG" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
if test "x$ac_pt_PKG_CONFIG" = x; then
PKG_CONFIG=""
else
case $cross_compiling:$ac_tool_warned in
yes:)
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
ac_tool_warned=yes ;;
esac
PKG_CONFIG=$ac_pt_PKG_CONFIG
fi
else
PKG_CONFIG="$ac_cv_path_PKG_CONFIG"
fi
fi
if test -n "$PKG_CONFIG"; then
_pkg_min_version=0.9.0
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5
$as_echo_n "checking pkg-config is at least version $_pkg_min_version... " >&6; }
if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
PKG_CONFIG=""
fi
fi
pkg_failed=no
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for libcurl" >&5
$as_echo_n "checking for libcurl... " >&6; }
if test -n "$CURL_CFLAGS"; then
pkg_cv_CURL_CFLAGS="$CURL_CFLAGS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libcurl\""; } >&5
($PKG_CONFIG --exists --print-errors "libcurl") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_CURL_CFLAGS=`$PKG_CONFIG --cflags "libcurl" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
fi
else
pkg_failed=untried
fi
if test -n "$CURL_LIBS"; then
pkg_cv_CURL_LIBS="$CURL_LIBS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libcurl\""; } >&5
($PKG_CONFIG --exists --print-errors "libcurl") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_CURL_LIBS=`$PKG_CONFIG --libs "libcurl" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
fi
else
pkg_failed=untried
fi
if test $pkg_failed = yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
_pkg_short_errors_supported=yes
else
_pkg_short_errors_supported=no
fi
if test $_pkg_short_errors_supported = yes; then
CURL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libcurl" 2>&1`
else
CURL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libcurl" 2>&1`
fi
# Put the nasty error message in config.log where it belongs
echo "$CURL_PKG_ERRORS" >&5
as_fn_error $? "Package requirements (libcurl) were not met:
$CURL_PKG_ERRORS
Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.
Alternatively, you may set the environment variables CURL_CFLAGS
and CURL_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details." "$LINENO" 5
elif test $pkg_failed = untried; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it
is in your PATH or set the PKG_CONFIG environment variable to the full
path to pkg-config.
Alternatively, you may set the environment variables CURL_CFLAGS
and CURL_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.
To get pkg-config, see .
See \`config.log' for more details" "$LINENO" 5; }
else
CURL_CFLAGS=$pkg_cv_CURL_CFLAGS
CURL_LIBS=$pkg_cv_CURL_LIBS
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
fi
# We need OpenSSL for crypto and HTTPS callouts.
pkg_failed=no
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for openssl" >&5
$as_echo_n "checking for openssl... " >&6; }
if test -n "$OPENSSL_CFLAGS"; then
pkg_cv_OPENSSL_CFLAGS="$OPENSSL_CFLAGS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"openssl\""; } >&5
($PKG_CONFIG --exists --print-errors "openssl") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_OPENSSL_CFLAGS=`$PKG_CONFIG --cflags "openssl" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
fi
else
pkg_failed=untried
fi
if test -n "$OPENSSL_LIBS"; then
pkg_cv_OPENSSL_LIBS="$OPENSSL_LIBS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"openssl\""; } >&5
($PKG_CONFIG --exists --print-errors "openssl") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_OPENSSL_LIBS=`$PKG_CONFIG --libs "openssl" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
fi
else
pkg_failed=untried
fi
if test $pkg_failed = yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
_pkg_short_errors_supported=yes
else
_pkg_short_errors_supported=no
fi
if test $_pkg_short_errors_supported = yes; then
OPENSSL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "openssl" 2>&1`
else
OPENSSL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "openssl" 2>&1`
fi
# Put the nasty error message in config.log where it belongs
echo "$OPENSSL_PKG_ERRORS" >&5
as_fn_error $? "Package requirements (openssl) were not met:
$OPENSSL_PKG_ERRORS
Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.
Alternatively, you may set the environment variables OPENSSL_CFLAGS
and OPENSSL_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details." "$LINENO" 5
elif test $pkg_failed = untried; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it
is in your PATH or set the PKG_CONFIG environment variable to the full
path to pkg-config.
Alternatively, you may set the environment variables OPENSSL_CFLAGS
and OPENSSL_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.
To get pkg-config, see .
See \`config.log' for more details" "$LINENO" 5; }
else
OPENSSL_CFLAGS=$pkg_cv_OPENSSL_CFLAGS
OPENSSL_LIBS=$pkg_cv_OPENSSL_LIBS
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
fi
pkg_failed=no
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for apr-1, apr-util-1" >&5
$as_echo_n "checking for apr-1, apr-util-1... " >&6; }
if test -n "$APR_CFLAGS"; then
pkg_cv_APR_CFLAGS="$APR_CFLAGS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"apr-1, apr-util-1\""; } >&5
($PKG_CONFIG --exists --print-errors "apr-1, apr-util-1") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_APR_CFLAGS=`$PKG_CONFIG --cflags "apr-1, apr-util-1" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
fi
else
pkg_failed=untried
fi
if test -n "$APR_LIBS"; then
pkg_cv_APR_LIBS="$APR_LIBS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"apr-1, apr-util-1\""; } >&5
($PKG_CONFIG --exists --print-errors "apr-1, apr-util-1") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_APR_LIBS=`$PKG_CONFIG --libs "apr-1, apr-util-1" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
fi
else
pkg_failed=untried
fi
if test $pkg_failed = yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
_pkg_short_errors_supported=yes
else
_pkg_short_errors_supported=no
fi
if test $_pkg_short_errors_supported = yes; then
APR_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "apr-1, apr-util-1" 2>&1`
else
APR_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "apr-1, apr-util-1" 2>&1`
fi
# Put the nasty error message in config.log where it belongs
echo "$APR_PKG_ERRORS" >&5
as_fn_error $? "Package requirements (apr-1, apr-util-1) were not met:
$APR_PKG_ERRORS
Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.
Alternatively, you may set the environment variables APR_CFLAGS
and APR_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details." "$LINENO" 5
elif test $pkg_failed = untried; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it
is in your PATH or set the PKG_CONFIG environment variable to the full
path to pkg-config.
Alternatively, you may set the environment variables APR_CFLAGS
and APR_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.
To get pkg-config, see .
See \`config.log' for more details" "$LINENO" 5; }
else
APR_CFLAGS=$pkg_cv_APR_CFLAGS
APR_LIBS=$pkg_cv_APR_LIBS
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
fi
# We need Jansson for JSON parsing.
pkg_failed=no
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for jansson" >&5
$as_echo_n "checking for jansson... " >&6; }
if test -n "$JANSSON_CFLAGS"; then
pkg_cv_JANSSON_CFLAGS="$JANSSON_CFLAGS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"jansson\""; } >&5
($PKG_CONFIG --exists --print-errors "jansson") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_JANSSON_CFLAGS=`$PKG_CONFIG --cflags "jansson" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
fi
else
pkg_failed=untried
fi
if test -n "$JANSSON_LIBS"; then
pkg_cv_JANSSON_LIBS="$JANSSON_LIBS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"jansson\""; } >&5
($PKG_CONFIG --exists --print-errors "jansson") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_JANSSON_LIBS=`$PKG_CONFIG --libs "jansson" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
fi
else
pkg_failed=untried
fi
if test $pkg_failed = yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
_pkg_short_errors_supported=yes
else
_pkg_short_errors_supported=no
fi
if test $_pkg_short_errors_supported = yes; then
JANSSON_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "jansson" 2>&1`
else
JANSSON_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "jansson" 2>&1`
fi
# Put the nasty error message in config.log where it belongs
echo "$JANSSON_PKG_ERRORS" >&5
as_fn_error $? "Package requirements (jansson) were not met:
$JANSSON_PKG_ERRORS
Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.
Alternatively, you may set the environment variables JANSSON_CFLAGS
and JANSSON_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details." "$LINENO" 5
elif test $pkg_failed = untried; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it
is in your PATH or set the PKG_CONFIG environment variable to the full
path to pkg-config.
Alternatively, you may set the environment variables JANSSON_CFLAGS
and JANSSON_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.
To get pkg-config, see .
See \`config.log' for more details" "$LINENO" 5; }
else
JANSSON_CFLAGS=$pkg_cv_JANSSON_CFLAGS
JANSSON_LIBS=$pkg_cv_JANSSON_LIBS
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
fi
# cjose
pkg_failed=no
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for cjose" >&5
$as_echo_n "checking for cjose... " >&6; }
if test -n "$CJOSE_CFLAGS"; then
pkg_cv_CJOSE_CFLAGS="$CJOSE_CFLAGS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"cjose\""; } >&5
($PKG_CONFIG --exists --print-errors "cjose") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_CJOSE_CFLAGS=`$PKG_CONFIG --cflags "cjose" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
fi
else
pkg_failed=untried
fi
if test -n "$CJOSE_LIBS"; then
pkg_cv_CJOSE_LIBS="$CJOSE_LIBS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"cjose\""; } >&5
($PKG_CONFIG --exists --print-errors "cjose") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_CJOSE_LIBS=`$PKG_CONFIG --libs "cjose" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
fi
else
pkg_failed=untried
fi
if test $pkg_failed = yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
_pkg_short_errors_supported=yes
else
_pkg_short_errors_supported=no
fi
if test $_pkg_short_errors_supported = yes; then
CJOSE_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "cjose" 2>&1`
else
CJOSE_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "cjose" 2>&1`
fi
# Put the nasty error message in config.log where it belongs
echo "$CJOSE_PKG_ERRORS" >&5
as_fn_error $? "Package requirements (cjose) were not met:
$CJOSE_PKG_ERRORS
Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.
Alternatively, you may set the environment variables CJOSE_CFLAGS
and CJOSE_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details." "$LINENO" 5
elif test $pkg_failed = untried; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it
is in your PATH or set the PKG_CONFIG environment variable to the full
path to pkg-config.
Alternatively, you may set the environment variables CJOSE_CFLAGS
and CJOSE_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.
To get pkg-config, see .
See \`config.log' for more details" "$LINENO" 5; }
else
CJOSE_CFLAGS=$pkg_cv_CJOSE_CFLAGS
CJOSE_LIBS=$pkg_cv_CJOSE_LIBS
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
fi
# PCRE
pkg_failed=no
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for libpcre" >&5
$as_echo_n "checking for libpcre... " >&6; }
if test -n "$PCRE_CFLAGS"; then
pkg_cv_PCRE_CFLAGS="$PCRE_CFLAGS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libpcre\""; } >&5
($PKG_CONFIG --exists --print-errors "libpcre") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_PCRE_CFLAGS=`$PKG_CONFIG --cflags "libpcre" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
fi
else
pkg_failed=untried
fi
if test -n "$PCRE_LIBS"; then
pkg_cv_PCRE_LIBS="$PCRE_LIBS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libpcre\""; } >&5
($PKG_CONFIG --exists --print-errors "libpcre") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_PCRE_LIBS=`$PKG_CONFIG --libs "libpcre" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
fi
else
pkg_failed=untried
fi
if test $pkg_failed = yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
_pkg_short_errors_supported=yes
else
_pkg_short_errors_supported=no
fi
if test $_pkg_short_errors_supported = yes; then
PCRE_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libpcre" 2>&1`
else
PCRE_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libpcre" 2>&1`
fi
# Put the nasty error message in config.log where it belongs
echo "$PCRE_PKG_ERRORS" >&5
as_fn_error $? "Package requirements (libpcre) were not met:
$PCRE_PKG_ERRORS
Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.
Alternatively, you may set the environment variables PCRE_CFLAGS
and PCRE_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details." "$LINENO" 5
elif test $pkg_failed = untried; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it
is in your PATH or set the PKG_CONFIG environment variable to the full
path to pkg-config.
Alternatively, you may set the environment variables PCRE_CFLAGS
and PCRE_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.
To get pkg-config, see .
See \`config.log' for more details" "$LINENO" 5; }
else
PCRE_CFLAGS=$pkg_cv_PCRE_CFLAGS
PCRE_LIBS=$pkg_cv_PCRE_LIBS
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
fi
# Redis
# Check whether --with-hiredis was given.
if test "${with_hiredis+set}" = set; then :
withval=$with_hiredis;
else
with_hiredis=yes
fi
case "$with_hiredis" in #(
yes) :
if test "$HIREDIS_LIBS" == ""; then
pkg_failed=no
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for hiredis" >&5
$as_echo_n "checking for hiredis... " >&6; }
if test -n "$HIREDIS_CFLAGS"; then
pkg_cv_HIREDIS_CFLAGS="$HIREDIS_CFLAGS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"hiredis\""; } >&5
($PKG_CONFIG --exists --print-errors "hiredis") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_HIREDIS_CFLAGS=`$PKG_CONFIG --cflags "hiredis" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
fi
else
pkg_failed=untried
fi
if test -n "$HIREDIS_LIBS"; then
pkg_cv_HIREDIS_LIBS="$HIREDIS_LIBS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"hiredis\""; } >&5
($PKG_CONFIG --exists --print-errors "hiredis") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_HIREDIS_LIBS=`$PKG_CONFIG --libs "hiredis" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
fi
else
pkg_failed=untried
fi
if test $pkg_failed = yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
_pkg_short_errors_supported=yes
else
_pkg_short_errors_supported=no
fi
if test $_pkg_short_errors_supported = yes; then
HIREDIS_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "hiredis" 2>&1`
else
HIREDIS_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "hiredis" 2>&1`
fi
# Put the nasty error message in config.log where it belongs
echo "$HIREDIS_PKG_ERRORS" >&5
HAVE_LIBHIREDIS=0
elif test $pkg_failed = untried; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
HAVE_LIBHIREDIS=0
else
HIREDIS_CFLAGS=$pkg_cv_HIREDIS_CFLAGS
HIREDIS_LIBS=$pkg_cv_HIREDIS_LIBS
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
HAVE_LIBHIREDIS=1
fi ; else HAVE_LIBHIREDIS=1 ; fi ;; #(
no) :
HAVE_LIBHIREDIS=0 ;; #(
*) :
pkg_failed=no
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for hiredis" >&5
$as_echo_n "checking for hiredis... " >&6; }
if test -n "$HIREDIS_CFLAGS"; then
pkg_cv_HIREDIS_CFLAGS="$HIREDIS_CFLAGS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"hiredis\""; } >&5
($PKG_CONFIG --exists --print-errors "hiredis") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_HIREDIS_CFLAGS=`$PKG_CONFIG --cflags "hiredis" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
fi
else
pkg_failed=untried
fi
if test -n "$HIREDIS_LIBS"; then
pkg_cv_HIREDIS_LIBS="$HIREDIS_LIBS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"hiredis\""; } >&5
($PKG_CONFIG --exists --print-errors "hiredis") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_HIREDIS_LIBS=`$PKG_CONFIG --libs "hiredis" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
fi
else
pkg_failed=untried
fi
if test $pkg_failed = yes; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
_pkg_short_errors_supported=yes
else
_pkg_short_errors_supported=no
fi
if test $_pkg_short_errors_supported = yes; then
HIREDIS_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "hiredis" 2>&1`
else
HIREDIS_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "hiredis" 2>&1`
fi
# Put the nasty error message in config.log where it belongs
echo "$HIREDIS_PKG_ERRORS" >&5
HAVE_LIBHIREDIS=0
elif test $pkg_failed = untried; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
HAVE_LIBHIREDIS=0
else
HIREDIS_CFLAGS=$pkg_cv_HIREDIS_CFLAGS
HIREDIS_LIBS=$pkg_cv_HIREDIS_LIBS
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
HAVE_LIBHIREDIS=1
fi ;;
esac
# JQ
HAVE_LIBJQ=0
# Check whether --with-jq was given.
if test "${with_jq+set}" = set; then :
withval=$with_jq;
fi
if test -n "$with_jq"
then
JQ_CFLAGS="-I$with_jq/include"
JQ_LIBS="-L$with_jq/lib -ljq"
CPPFLAGS="$JQ_CFLAGS $CPPFLAGS"
ac_ext=c
ac_cpp='$CPP $CPPFLAGS'
ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
ac_compiler_gnu=$ac_cv_c_compiler_gnu
if test -n "$ac_tool_prefix"; then
# Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
set dummy ${ac_tool_prefix}gcc; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
if ${ac_cv_prog_CC+:} false; then :
$as_echo_n "(cached) " >&6
else
if test -n "$CC"; then
ac_cv_prog_CC="$CC" # Let the user override the test.
else
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_exec_ext in '' $ac_executable_extensions; do
if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
ac_cv_prog_CC="${ac_tool_prefix}gcc"
$as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
break 2
fi
done
done
IFS=$as_save_IFS
fi
fi
CC=$ac_cv_prog_CC
if test -n "$CC"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
$as_echo "$CC" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
fi
if test -z "$ac_cv_prog_CC"; then
ac_ct_CC=$CC
# Extract the first word of "gcc", so it can be a program name with args.
set dummy gcc; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
if ${ac_cv_prog_ac_ct_CC+:} false; then :
$as_echo_n "(cached) " >&6
else
if test -n "$ac_ct_CC"; then
ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
else
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_exec_ext in '' $ac_executable_extensions; do
if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
ac_cv_prog_ac_ct_CC="gcc"
$as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
break 2
fi
done
done
IFS=$as_save_IFS
fi
fi
ac_ct_CC=$ac_cv_prog_ac_ct_CC
if test -n "$ac_ct_CC"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
$as_echo "$ac_ct_CC" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
if test "x$ac_ct_CC" = x; then
CC=""
else
case $cross_compiling:$ac_tool_warned in
yes:)
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
ac_tool_warned=yes ;;
esac
CC=$ac_ct_CC
fi
else
CC="$ac_cv_prog_CC"
fi
if test -z "$CC"; then
if test -n "$ac_tool_prefix"; then
# Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
set dummy ${ac_tool_prefix}cc; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
if ${ac_cv_prog_CC+:} false; then :
$as_echo_n "(cached) " >&6
else
if test -n "$CC"; then
ac_cv_prog_CC="$CC" # Let the user override the test.
else
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_exec_ext in '' $ac_executable_extensions; do
if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
ac_cv_prog_CC="${ac_tool_prefix}cc"
$as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
break 2
fi
done
done
IFS=$as_save_IFS
fi
fi
CC=$ac_cv_prog_CC
if test -n "$CC"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
$as_echo "$CC" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
fi
fi
if test -z "$CC"; then
# Extract the first word of "cc", so it can be a program name with args.
set dummy cc; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
if ${ac_cv_prog_CC+:} false; then :
$as_echo_n "(cached) " >&6
else
if test -n "$CC"; then
ac_cv_prog_CC="$CC" # Let the user override the test.
else
ac_prog_rejected=no
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_exec_ext in '' $ac_executable_extensions; do
if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
ac_prog_rejected=yes
continue
fi
ac_cv_prog_CC="cc"
$as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
break 2
fi
done
done
IFS=$as_save_IFS
if test $ac_prog_rejected = yes; then
# We found a bogon in the path, so make sure we never use it.
set dummy $ac_cv_prog_CC
shift
if test $# != 0; then
# We chose a different compiler from the bogus one.
# However, it has the same basename, so the bogon will be chosen
# first if we set CC to just the basename; use the full file name.
shift
ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
fi
fi
fi
fi
CC=$ac_cv_prog_CC
if test -n "$CC"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
$as_echo "$CC" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
fi
if test -z "$CC"; then
if test -n "$ac_tool_prefix"; then
for ac_prog in cl.exe
do
# Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
set dummy $ac_tool_prefix$ac_prog; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
if ${ac_cv_prog_CC+:} false; then :
$as_echo_n "(cached) " >&6
else
if test -n "$CC"; then
ac_cv_prog_CC="$CC" # Let the user override the test.
else
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_exec_ext in '' $ac_executable_extensions; do
if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
$as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
break 2
fi
done
done
IFS=$as_save_IFS
fi
fi
CC=$ac_cv_prog_CC
if test -n "$CC"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
$as_echo "$CC" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
test -n "$CC" && break
done
fi
if test -z "$CC"; then
ac_ct_CC=$CC
for ac_prog in cl.exe
do
# Extract the first word of "$ac_prog", so it can be a program name with args.
set dummy $ac_prog; ac_word=$2
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
$as_echo_n "checking for $ac_word... " >&6; }
if ${ac_cv_prog_ac_ct_CC+:} false; then :
$as_echo_n "(cached) " >&6
else
if test -n "$ac_ct_CC"; then
ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
else
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_exec_ext in '' $ac_executable_extensions; do
if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
ac_cv_prog_ac_ct_CC="$ac_prog"
$as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
break 2
fi
done
done
IFS=$as_save_IFS
fi
fi
ac_ct_CC=$ac_cv_prog_ac_ct_CC
if test -n "$ac_ct_CC"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
$as_echo "$ac_ct_CC" >&6; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
test -n "$ac_ct_CC" && break
done
if test "x$ac_ct_CC" = x; then
CC=""
else
case $cross_compiling:$ac_tool_warned in
yes:)
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
ac_tool_warned=yes ;;
esac
CC=$ac_ct_CC
fi
fi
fi
test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "no acceptable C compiler found in \$PATH
See \`config.log' for more details" "$LINENO" 5; }
# Provide some information about the compiler.
$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
set X $ac_compile
ac_compiler=$2
for ac_option in --version -v -V -qversion; do
{ { ac_try="$ac_compiler $ac_option >&5"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_compiler $ac_option >&5") 2>conftest.err
ac_status=$?
if test -s conftest.err; then
sed '10a\
... rest of stderr output deleted ...
10q' conftest.err >conftest.er1
cat conftest.er1 >&5
fi
rm -f conftest.er1 conftest.err
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }
done
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
int
main ()
{
;
return 0;
}
_ACEOF
ac_clean_files_save=$ac_clean_files
ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out"
# Try to create an executable without -o first, disregard a.out.
# It will help us diagnose broken compilers, and finding out an intuition
# of exeext.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5
$as_echo_n "checking whether the C compiler works... " >&6; }
ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'`
# The possible output files:
ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*"
ac_rmfiles=
for ac_file in $ac_files
do
case $ac_file in
*.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
* ) ac_rmfiles="$ac_rmfiles $ac_file";;
esac
done
rm -f $ac_rmfiles
if { { ac_try="$ac_link_default"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_link_default") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then :
# Autoconf-2.13 could set the ac_cv_exeext variable to `no'.
# So ignore a value of `no', otherwise this would lead to `EXEEXT = no'
# in a Makefile. We should not override ac_cv_exeext if it was cached,
# so that the user can short-circuit this test for compilers unknown to
# Autoconf.
for ac_file in $ac_files ''
do
test -f "$ac_file" || continue
case $ac_file in
*.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj )
;;
[ab].out )
# We found the default executable, but exeext='' is most
# certainly right.
break;;
*.* )
if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no;
then :; else
ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
fi
# We set ac_cv_exeext here because the later test for it is not
# safe: cross compilers may not add the suffix if given an `-o'
# argument, so we may need to know it at that point already.
# Even if this section looks crufty: it has the advantage of
# actually working.
break;;
* )
break;;
esac
done
test "$ac_cv_exeext" = no && ac_cv_exeext=
else
ac_file=''
fi
if test -z "$ac_file"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
$as_echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error 77 "C compiler cannot create executables
See \`config.log' for more details" "$LINENO" 5; }
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5
$as_echo_n "checking for C compiler default output file name... " >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5
$as_echo "$ac_file" >&6; }
ac_exeext=$ac_cv_exeext
rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out
ac_clean_files=$ac_clean_files_save
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5
$as_echo_n "checking for suffix of executables... " >&6; }
if { { ac_try="$ac_link"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_link") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then :
# If both `conftest.exe' and `conftest' are `present' (well, observable)
# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will
# work properly (i.e., refer to `conftest.exe'), while it won't with
# `rm'.
for ac_file in conftest.exe conftest conftest.*; do
test -f "$ac_file" || continue
case $ac_file in
*.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
*.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
break;;
* ) break;;
esac
done
else
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "cannot compute suffix of executables: cannot compile and link
See \`config.log' for more details" "$LINENO" 5; }
fi
rm -f conftest conftest$ac_cv_exeext
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
$as_echo "$ac_cv_exeext" >&6; }
rm -f conftest.$ac_ext
EXEEXT=$ac_cv_exeext
ac_exeext=$EXEEXT
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
int
main ()
{
FILE *f = fopen ("conftest.out", "w");
return ferror (f) || fclose (f) != 0;
;
return 0;
}
_ACEOF
ac_clean_files="$ac_clean_files conftest.out"
# Check that the compiler produces executables we can run. If not, either
# the compiler is broken, or we cross compile.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5
$as_echo_n "checking whether we are cross compiling... " >&6; }
if test "$cross_compiling" != yes; then
{ { ac_try="$ac_link"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_link") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }
if { ac_try='./conftest$ac_cv_exeext'
{ { case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_try") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; }; then
cross_compiling=no
else
if test "$cross_compiling" = maybe; then
cross_compiling=yes
else
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "cannot run C compiled programs.
If you meant to cross compile, use \`--host'.
See \`config.log' for more details" "$LINENO" 5; }
fi
fi
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5
$as_echo "$cross_compiling" >&6; }
rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out
ac_clean_files=$ac_clean_files_save
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5
$as_echo_n "checking for suffix of object files... " >&6; }
if ${ac_cv_objext+:} false; then :
$as_echo_n "(cached) " >&6
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
int
main ()
{
;
return 0;
}
_ACEOF
rm -f conftest.o conftest.obj
if { { ac_try="$ac_compile"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
$as_echo "$ac_try_echo"; } >&5
(eval "$ac_compile") 2>&5
ac_status=$?
$as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then :
for ac_file in conftest.o conftest.obj conftest.*; do
test -f "$ac_file" || continue;
case $ac_file in
*.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;;
*) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'`
break;;
esac
done
else
$as_echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "cannot compute suffix of object files: cannot compile
See \`config.log' for more details" "$LINENO" 5; }
fi
rm -f conftest.$ac_cv_objext conftest.$ac_ext
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5
$as_echo "$ac_cv_objext" >&6; }
OBJEXT=$ac_cv_objext
ac_objext=$OBJEXT
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
$as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
if ${ac_cv_c_compiler_gnu+:} false; then :
$as_echo_n "(cached) " >&6
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
int
main ()
{
#ifndef __GNUC__
choke me
#endif
;
return 0;
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
ac_compiler_gnu=yes
else
ac_compiler_gnu=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
ac_cv_c_compiler_gnu=$ac_compiler_gnu
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5
$as_echo "$ac_cv_c_compiler_gnu" >&6; }
if test $ac_compiler_gnu = yes; then
GCC=yes
else
GCC=
fi
ac_test_CFLAGS=${CFLAGS+set}
ac_save_CFLAGS=$CFLAGS
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
$as_echo_n "checking whether $CC accepts -g... " >&6; }
if ${ac_cv_prog_cc_g+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_save_c_werror_flag=$ac_c_werror_flag
ac_c_werror_flag=yes
ac_cv_prog_cc_g=no
CFLAGS="-g"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
int
main ()
{
;
return 0;
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
ac_cv_prog_cc_g=yes
else
CFLAGS=""
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
int
main ()
{
;
return 0;
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
else
ac_c_werror_flag=$ac_save_c_werror_flag
CFLAGS="-g"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
int
main ()
{
;
return 0;
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
ac_cv_prog_cc_g=yes
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
ac_c_werror_flag=$ac_save_c_werror_flag
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5
$as_echo "$ac_cv_prog_cc_g" >&6; }
if test "$ac_test_CFLAGS" = set; then
CFLAGS=$ac_save_CFLAGS
elif test $ac_cv_prog_cc_g = yes; then
if test "$GCC" = yes; then
CFLAGS="-g -O2"
else
CFLAGS="-g"
fi
else
if test "$GCC" = yes; then
CFLAGS="-O2"
else
CFLAGS=
fi
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
$as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
if ${ac_cv_prog_cc_c89+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_cv_prog_cc_c89=no
ac_save_CC=$CC
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
#include
struct stat;
/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */
struct buf { int x; };
FILE * (*rcsopen) (struct buf *, struct stat *, int);
static char *e (p, i)
char **p;
int i;
{
return p[i];
}
static char *f (char * (*g) (char **, int), char **p, ...)
{
char *s;
va_list v;
va_start (v,p);
s = g (p, va_arg (v,int));
va_end (v);
return s;
}
/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has
function prototypes and stuff, but not '\xHH' hex character constants.
These don't provoke an error unfortunately, instead are silently treated
as 'x'. The following induces an error, until -std is added to get
proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an
array size at least. It's necessary to write '\x00'==0 to get something
that's true only with -std. */
int osf4_cc_array ['\x00' == 0 ? 1 : -1];
/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters
inside strings and character constants. */
#define FOO(x) 'x'
int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1];
int test (int i, double x);
struct s1 {int (*f) (int a);};
struct s2 {int (*f) (double a);};
int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
int argc;
char **argv;
int
main ()
{
return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1];
;
return 0;
}
_ACEOF
for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \
-Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
do
CC="$ac_save_CC $ac_arg"
if ac_fn_c_try_compile "$LINENO"; then :
ac_cv_prog_cc_c89=$ac_arg
fi
rm -f core conftest.err conftest.$ac_objext
test "x$ac_cv_prog_cc_c89" != "xno" && break
done
rm -f conftest.$ac_ext
CC=$ac_save_CC
fi
# AC_CACHE_VAL
case "x$ac_cv_prog_cc_c89" in
x)
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
$as_echo "none needed" >&6; } ;;
xno)
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
$as_echo "unsupported" >&6; } ;;
*)
CC="$CC $ac_cv_prog_cc_c89"
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
$as_echo "$ac_cv_prog_cc_c89" >&6; } ;;
esac
if test "x$ac_cv_prog_cc_c89" != xno; then :
fi
ac_ext=c
ac_cpp='$CPP $CPPFLAGS'
ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
ac_compiler_gnu=$ac_cv_c_compiler_gnu
ac_ext=c
ac_cpp='$CPP $CPPFLAGS'
ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
ac_compiler_gnu=$ac_cv_c_compiler_gnu
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5
$as_echo_n "checking how to run the C preprocessor... " >&6; }
# On Suns, sometimes $CPP names a directory.
if test -n "$CPP" && test -d "$CPP"; then
CPP=
fi
if test -z "$CPP"; then
if ${ac_cv_prog_CPP+:} false; then :
$as_echo_n "(cached) " >&6
else
# Double quotes because CPP needs to be expanded
for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp"
do
ac_preproc_ok=false
for ac_c_preproc_warn_flag in '' yes
do
# Use a header file that comes with gcc, so configuring glibc
# with a fresh cross-compiler works.
# Prefer to if __STDC__ is defined, since
# exists even on freestanding compilers.
# On the NeXT, cc -E runs the code through the compiler's parser,
# not just through cpp. "Syntax error" is here to catch this case.
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#ifdef __STDC__
# include
#else
# include
#endif
Syntax error
_ACEOF
if ac_fn_c_try_cpp "$LINENO"; then :
else
# Broken: fails on valid input.
continue
fi
rm -f conftest.err conftest.i conftest.$ac_ext
# OK, works on sane cases. Now check whether nonexistent headers
# can be detected and how.
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
_ACEOF
if ac_fn_c_try_cpp "$LINENO"; then :
# Broken: success on invalid input.
continue
else
# Passes both tests.
ac_preproc_ok=:
break
fi
rm -f conftest.err conftest.i conftest.$ac_ext
done
# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
rm -f conftest.i conftest.err conftest.$ac_ext
if $ac_preproc_ok; then :
break
fi
done
ac_cv_prog_CPP=$CPP
fi
CPP=$ac_cv_prog_CPP
else
ac_cv_prog_CPP=$CPP
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5
$as_echo "$CPP" >&6; }
ac_preproc_ok=false
for ac_c_preproc_warn_flag in '' yes
do
# Use a header file that comes with gcc, so configuring glibc
# with a fresh cross-compiler works.
# Prefer to if __STDC__ is defined, since
# exists even on freestanding compilers.
# On the NeXT, cc -E runs the code through the compiler's parser,
# not just through cpp. "Syntax error" is here to catch this case.
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#ifdef __STDC__
# include
#else
# include
#endif
Syntax error
_ACEOF
if ac_fn_c_try_cpp "$LINENO"; then :
else
# Broken: fails on valid input.
continue
fi
rm -f conftest.err conftest.i conftest.$ac_ext
# OK, works on sane cases. Now check whether nonexistent headers
# can be detected and how.
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
_ACEOF
if ac_fn_c_try_cpp "$LINENO"; then :
# Broken: success on invalid input.
continue
else
# Passes both tests.
ac_preproc_ok=:
break
fi
rm -f conftest.err conftest.i conftest.$ac_ext
done
# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
rm -f conftest.i conftest.err conftest.$ac_ext
if $ac_preproc_ok; then :
else
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
See \`config.log' for more details" "$LINENO" 5; }
fi
ac_ext=c
ac_cpp='$CPP $CPPFLAGS'
ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
ac_compiler_gnu=$ac_cv_c_compiler_gnu
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
$as_echo_n "checking for grep that handles long lines and -e... " >&6; }
if ${ac_cv_path_GREP+:} false; then :
$as_echo_n "(cached) " >&6
else
if test -z "$GREP"; then
ac_path_GREP_found=false
# Loop through the user's path and test for each of PROGNAME-LIST
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_prog in grep ggrep; do
for ac_exec_ext in '' $ac_executable_extensions; do
ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext"
as_fn_executable_p "$ac_path_GREP" || continue
# Check for GNU ac_path_GREP and select it if it is found.
# Check for GNU $ac_path_GREP
case `"$ac_path_GREP" --version 2>&1` in
*GNU*)
ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;;
*)
ac_count=0
$as_echo_n 0123456789 >"conftest.in"
while :
do
cat "conftest.in" "conftest.in" >"conftest.tmp"
mv "conftest.tmp" "conftest.in"
cp "conftest.in" "conftest.nl"
$as_echo 'GREP' >> "conftest.nl"
"$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break
diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
as_fn_arith $ac_count + 1 && ac_count=$as_val
if test $ac_count -gt ${ac_path_GREP_max-0}; then
# Best one so far, save it but keep looking for a better one
ac_cv_path_GREP="$ac_path_GREP"
ac_path_GREP_max=$ac_count
fi
# 10*(2^10) chars as input seems more than enough
test $ac_count -gt 10 && break
done
rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
esac
$ac_path_GREP_found && break 3
done
done
done
IFS=$as_save_IFS
if test -z "$ac_cv_path_GREP"; then
as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
fi
else
ac_cv_path_GREP=$GREP
fi
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5
$as_echo "$ac_cv_path_GREP" >&6; }
GREP="$ac_cv_path_GREP"
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
$as_echo_n "checking for egrep... " >&6; }
if ${ac_cv_path_EGREP+:} false; then :
$as_echo_n "(cached) " >&6
else
if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
then ac_cv_path_EGREP="$GREP -E"
else
if test -z "$EGREP"; then
ac_path_EGREP_found=false
# Loop through the user's path and test for each of PROGNAME-LIST
as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
for ac_prog in egrep; do
for ac_exec_ext in '' $ac_executable_extensions; do
ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext"
as_fn_executable_p "$ac_path_EGREP" || continue
# Check for GNU ac_path_EGREP and select it if it is found.
# Check for GNU $ac_path_EGREP
case `"$ac_path_EGREP" --version 2>&1` in
*GNU*)
ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
*)
ac_count=0
$as_echo_n 0123456789 >"conftest.in"
while :
do
cat "conftest.in" "conftest.in" >"conftest.tmp"
mv "conftest.tmp" "conftest.in"
cp "conftest.in" "conftest.nl"
$as_echo 'EGREP' >> "conftest.nl"
"$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
as_fn_arith $ac_count + 1 && ac_count=$as_val
if test $ac_count -gt ${ac_path_EGREP_max-0}; then
# Best one so far, save it but keep looking for a better one
ac_cv_path_EGREP="$ac_path_EGREP"
ac_path_EGREP_max=$ac_count
fi
# 10*(2^10) chars as input seems more than enough
test $ac_count -gt 10 && break
done
rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
esac
$ac_path_EGREP_found && break 3
done
done
done
IFS=$as_save_IFS
if test -z "$ac_cv_path_EGREP"; then
as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
fi
else
ac_cv_path_EGREP=$EGREP
fi
fi
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5
$as_echo "$ac_cv_path_EGREP" >&6; }
EGREP="$ac_cv_path_EGREP"
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5
$as_echo_n "checking for ANSI C header files... " >&6; }
if ${ac_cv_header_stdc+:} false; then :
$as_echo_n "(cached) " >&6
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
#include
#include
#include
int
main ()
{
;
return 0;
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
ac_cv_header_stdc=yes
else
ac_cv_header_stdc=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
if test $ac_cv_header_stdc = yes; then
# SunOS 4.x string.h does not declare mem*, contrary to ANSI.
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
_ACEOF
if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
$EGREP "memchr" >/dev/null 2>&1; then :
else
ac_cv_header_stdc=no
fi
rm -f conftest*
fi
if test $ac_cv_header_stdc = yes; then
# ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
_ACEOF
if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
$EGREP "free" >/dev/null 2>&1; then :
else
ac_cv_header_stdc=no
fi
rm -f conftest*
fi
if test $ac_cv_header_stdc = yes; then
# /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
if test "$cross_compiling" = yes; then :
:
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include
#include
#if ((' ' & 0x0FF) == 0x020)
# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
#else
# define ISLOWER(c) \
(('a' <= (c) && (c) <= 'i') \
|| ('j' <= (c) && (c) <= 'r') \
|| ('s' <= (c) && (c) <= 'z'))
# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
#endif
#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
int
main ()
{
int i;
for (i = 0; i < 256; i++)
if (XOR (islower (i), ISLOWER (i))
|| toupper (i) != TOUPPER (i))
return 2;
return 0;
}
_ACEOF
if ac_fn_c_try_run "$LINENO"; then :
else
ac_cv_header_stdc=no
fi
rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
conftest.$ac_objext conftest.beam conftest.$ac_ext
fi
fi
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5
$as_echo "$ac_cv_header_stdc" >&6; }
if test $ac_cv_header_stdc = yes; then
$as_echo "#define STDC_HEADERS 1" >>confdefs.h
fi
# On IRIX 5.3, sys/types and inttypes.h are conflicting.
for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \
inttypes.h stdint.h unistd.h
do :
as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default
"
if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
cat >>confdefs.h <<_ACEOF
#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
_ACEOF
fi
done
for ac_header in jq.h
do :
ac_fn_c_check_header_mongrel "$LINENO" "jq.h" "ac_cv_header_jq_h" "$ac_includes_default"
if test "x$ac_cv_header_jq_h" = xyes; then :
cat >>confdefs.h <<_ACEOF
#define HAVE_JQ_H 1
_ACEOF
else
HAVE_LIBJQ=0
fi
done
LDFLAGS="$JQ_LIBS $LDFLAGS"
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for jq_init in -ljq" >&5
$as_echo_n "checking for jq_init in -ljq... " >&6; }
if ${ac_cv_lib_jq_jq_init+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_check_lib_save_LIBS=$LIBS
LIBS="-ljq $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
#ifdef __cplusplus
extern "C"
#endif
char jq_init ();
int
main ()
{
return jq_init ();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
ac_cv_lib_jq_jq_init=yes
else
ac_cv_lib_jq_jq_init=no
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
LIBS=$ac_check_lib_save_LIBS
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_jq_jq_init" >&5
$as_echo "$ac_cv_lib_jq_jq_init" >&6; }
if test "x$ac_cv_lib_jq_jq_init" = xyes; then :
HAVE_LIBJQ=1
else
HAVE_LIBJQ=0
fi
if test "x$have_jq" = "x0" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: \"cannot find library for -ljq.\"" >&5
$as_echo "$as_me: WARNING: \"cannot find library for -ljq.\"" >&2;}
fi
fi
# Create Makefile from Makefile.in
ac_config_files="$ac_config_files Makefile"
cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure
# tests run on this system so they can be shared between configure
# scripts and configure runs, see configure's option --config-cache.
# It is not useful on other systems. If it contains results you don't
# want to keep, you may remove or edit it.
#
# config.status only pays attention to the cache file if you give it
# the --recheck option to rerun configure.
#
# `ac_cv_env_foo' variables (set or unset) will be overridden when
# loading this file, other *unset* `ac_cv_foo' will be assigned the
# following values.
_ACEOF
# The following way of writing the cache mishandles newlines in values,
# but we know of no workaround that is simple, portable, and efficient.
# So, we kill variables containing newlines.
# Ultrix sh set writes to stderr and can't be redirected directly,
# and sets the high bit in the cache file unless we assign to the vars.
(
for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do
eval ac_val=\$$ac_var
case $ac_val in #(
*${as_nl}*)
case $ac_var in #(
*_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
esac
case $ac_var in #(
_ | IFS | as_nl) ;; #(
BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
*) { eval $ac_var=; unset $ac_var;} ;;
esac ;;
esac
done
(set) 2>&1 |
case $as_nl`(ac_space=' '; set) 2>&1` in #(
*${as_nl}ac_space=\ *)
# `set' does not quote correctly, so add quotes: double-quote
# substitution turns \\\\ into \\, and sed turns \\ into \.
sed -n \
"s/'/'\\\\''/g;
s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
;; #(
*)
# `set' quotes correctly as required by POSIX, so do not add quotes.
sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
;;
esac |
sort
) |
sed '
/^ac_cv_env_/b end
t clear
:clear
s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
t end
s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
:end' >>confcache
if diff "$cache_file" confcache >/dev/null 2>&1; then :; else
if test -w "$cache_file"; then
if test "x$cache_file" != "x/dev/null"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5
$as_echo "$as_me: updating cache $cache_file" >&6;}
if test ! -f "$cache_file" || test -h "$cache_file"; then
cat confcache >"$cache_file"
else
case $cache_file in #(
*/* | ?:*)
mv -f confcache "$cache_file"$$ &&
mv -f "$cache_file"$$ "$cache_file" ;; #(
*)
mv -f confcache "$cache_file" ;;
esac
fi
fi
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5
$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;}
fi
fi
rm -f confcache
test "x$prefix" = xNONE && prefix=$ac_default_prefix
# Let make expand exec_prefix.
test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
# Transform confdefs.h into DEFS.
# Protect against shell expansion while executing Makefile rules.
# Protect against Makefile macro expansion.
#
# If the first sed substitution is executed (which looks for macros that
# take arguments), then branch to the quote section. Otherwise,
# look for a macro that doesn't take arguments.
ac_script='
:mline
/\\$/{
N
s,\\\n,,
b mline
}
t clear
:clear
s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g
t quote
s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g
t quote
b any
:quote
s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g
s/\[/\\&/g
s/\]/\\&/g
s/\$/$$/g
H
:any
${
g
s/^\n//
s/\n/ /g
p
}
'
DEFS=`sed -n "$ac_script" confdefs.h`
ac_libobjs=
ac_ltlibobjs=
U=
for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
# 1. Remove the extension, and $U if already installed.
ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
ac_i=`$as_echo "$ac_i" | sed "$ac_script"`
# 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR
# will be set to the directory where LIBOBJS objects are built.
as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext"
as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo'
done
LIBOBJS=$ac_libobjs
LTLIBOBJS=$ac_ltlibobjs
: "${CONFIG_STATUS=./config.status}"
ac_write_fail=0
ac_clean_files_save=$ac_clean_files
ac_clean_files="$ac_clean_files $CONFIG_STATUS"
{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5
$as_echo "$as_me: creating $CONFIG_STATUS" >&6;}
as_write_fail=0
cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1
#! $SHELL
# Generated by $as_me.
# Run this file to recreate the current configuration.
# Compiler output produced by configure, useful for debugging
# configure, is in config.log if it exists.
debug=false
ac_cs_recheck=false
ac_cs_silent=false
SHELL=\${CONFIG_SHELL-$SHELL}
export SHELL
_ASEOF
cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1
## -------------------- ##
## M4sh Initialization. ##
## -------------------- ##
# Be more Bourne compatible
DUALCASE=1; export DUALCASE # for MKS sh
if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
emulate sh
NULLCMD=:
# Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
# is contrary to our usage. Disable this feature.
alias -g '${1+"$@"}'='"$@"'
setopt NO_GLOB_SUBST
else
case `(set -o) 2>/dev/null` in #(
*posix*) :
set -o posix ;; #(
*) :
;;
esac
fi
as_nl='
'
export as_nl
# Printing a long string crashes Solaris 7 /usr/bin/printf.
as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
# Prefer a ksh shell builtin over an external printf program on Solaris,
# but without wasting forks for bash or zsh.
if test -z "$BASH_VERSION$ZSH_VERSION" \
&& (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
as_echo='print -r --'
as_echo_n='print -rn --'
elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
as_echo='printf %s\n'
as_echo_n='printf %s'
else
if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
as_echo_n='/usr/ucb/echo -n'
else
as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
as_echo_n_body='eval
arg=$1;
case $arg in #(
*"$as_nl"*)
expr "X$arg" : "X\\(.*\\)$as_nl";
arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
esac;
expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
'
export as_echo_n_body
as_echo_n='sh -c $as_echo_n_body as_echo'
fi
export as_echo_body
as_echo='sh -c $as_echo_body as_echo'
fi
# The user is always right.
if test "${PATH_SEPARATOR+set}" != set; then
PATH_SEPARATOR=:
(PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
(PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
PATH_SEPARATOR=';'
}
fi
# IFS
# We need space, tab and new line, in precisely that order. Quoting is
# there to prevent editors from complaining about space-tab.
# (If _AS_PATH_WALK were called with IFS unset, it would disable word
# splitting by setting IFS to empty value.)
IFS=" "" $as_nl"
# Find who we are. Look in the path if we contain no directory separator.
as_myself=
case $0 in #((
*[\\/]* ) as_myself=$0 ;;
*) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
for as_dir in $PATH
do
IFS=$as_save_IFS
test -z "$as_dir" && as_dir=.
test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
done
IFS=$as_save_IFS
;;
esac
# We did not find ourselves, most probably we were run as `sh COMMAND'
# in which case we are not to be found in the path.
if test "x$as_myself" = x; then
as_myself=$0
fi
if test ! -f "$as_myself"; then
$as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
exit 1
fi
# Unset variables that we do not need and which cause bugs (e.g. in
# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1"
# suppresses any "Segmentation fault" message there. '((' could
# trigger a bug in pdksh 5.2.14.
for as_var in BASH_ENV ENV MAIL MAILPATH
do eval test x\${$as_var+set} = xset \
&& ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
done
PS1='$ '
PS2='> '
PS4='+ '
# NLS nuisances.
LC_ALL=C
export LC_ALL
LANGUAGE=C
export LANGUAGE
# CDPATH.
(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
# as_fn_error STATUS ERROR [LINENO LOG_FD]
# ----------------------------------------
# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
# script with STATUS, using 1 if that was 0.
as_fn_error ()
{
as_status=$1; test $as_status -eq 0 && as_status=1
if test "$4"; then
as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
$as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
fi
$as_echo "$as_me: error: $2" >&2
as_fn_exit $as_status
} # as_fn_error
# as_fn_set_status STATUS
# -----------------------
# Set $? to STATUS, without forking.
as_fn_set_status ()
{
return $1
} # as_fn_set_status
# as_fn_exit STATUS
# -----------------
# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
as_fn_exit ()
{
set +e
as_fn_set_status $1
exit $1
} # as_fn_exit
# as_fn_unset VAR
# ---------------
# Portably unset VAR.
as_fn_unset ()
{
{ eval $1=; unset $1;}
}
as_unset=as_fn_unset
# as_fn_append VAR VALUE
# ----------------------
# Append the text in VALUE to the end of the definition contained in VAR. Take
# advantage of any shell optimizations that allow amortized linear growth over
# repeated appends, instead of the typical quadratic growth present in naive
# implementations.
if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
eval 'as_fn_append ()
{
eval $1+=\$2
}'
else
as_fn_append ()
{
eval $1=\$$1\$2
}
fi # as_fn_append
# as_fn_arith ARG...
# ------------------
# Perform arithmetic evaluation on the ARGs, and store the result in the
# global $as_val. Take advantage of shells that can avoid forks. The arguments
# must be portable across $(()) and expr.
if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
eval 'as_fn_arith ()
{
as_val=$(( $* ))
}'
else
as_fn_arith ()
{
as_val=`expr "$@" || test $? -eq 1`
}
fi # as_fn_arith
if expr a : '\(a\)' >/dev/null 2>&1 &&
test "X`expr 00001 : '.*\(...\)'`" = X001; then
as_expr=expr
else
as_expr=false
fi
if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
as_basename=basename
else
as_basename=false
fi
if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
as_dirname=dirname
else
as_dirname=false
fi
as_me=`$as_basename -- "$0" ||
$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
X"$0" : 'X\(//\)$' \| \
X"$0" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X/"$0" |
sed '/^.*\/\([^/][^/]*\)\/*$/{
s//\1/
q
}
/^X\/\(\/\/\)$/{
s//\1/
q
}
/^X\/\(\/\).*/{
s//\1/
q
}
s/.*/./; q'`
# Avoid depending upon Character Ranges.
as_cr_letters='abcdefghijklmnopqrstuvwxyz'
as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
as_cr_Letters=$as_cr_letters$as_cr_LETTERS
as_cr_digits='0123456789'
as_cr_alnum=$as_cr_Letters$as_cr_digits
ECHO_C= ECHO_N= ECHO_T=
case `echo -n x` in #(((((
-n*)
case `echo 'xy\c'` in
*c*) ECHO_T=' ';; # ECHO_T is single tab character.
xy) ECHO_C='\c';;
*) echo `echo ksh88 bug on AIX 6.1` > /dev/null
ECHO_T=' ';;
esac;;
*)
ECHO_N='-n';;
esac
rm -f conf$$ conf$$.exe conf$$.file
if test -d conf$$.dir; then
rm -f conf$$.dir/conf$$.file
else
rm -f conf$$.dir
mkdir conf$$.dir 2>/dev/null
fi
if (echo >conf$$.file) 2>/dev/null; then
if ln -s conf$$.file conf$$ 2>/dev/null; then
as_ln_s='ln -s'
# ... but there are two gotchas:
# 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
# 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
# In both cases, we have to default to `cp -pR'.
ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
as_ln_s='cp -pR'
elif ln conf$$.file conf$$ 2>/dev/null; then
as_ln_s=ln
else
as_ln_s='cp -pR'
fi
else
as_ln_s='cp -pR'
fi
rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
rmdir conf$$.dir 2>/dev/null
# as_fn_mkdir_p
# -------------
# Create "$as_dir" as a directory, including parents if necessary.
as_fn_mkdir_p ()
{
case $as_dir in #(
-*) as_dir=./$as_dir;;
esac
test -d "$as_dir" || eval $as_mkdir_p || {
as_dirs=
while :; do
case $as_dir in #(
*\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
*) as_qdir=$as_dir;;
esac
as_dirs="'$as_qdir' $as_dirs"
as_dir=`$as_dirname -- "$as_dir" ||
$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
X"$as_dir" : 'X\(//\)[^/]' \| \
X"$as_dir" : 'X\(//\)$' \| \
X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X"$as_dir" |
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
s//\1/
q
}
/^X\(\/\/\)[^/].*/{
s//\1/
q
}
/^X\(\/\/\)$/{
s//\1/
q
}
/^X\(\/\).*/{
s//\1/
q
}
s/.*/./; q'`
test -d "$as_dir" && break
done
test -z "$as_dirs" || eval "mkdir $as_dirs"
} || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
} # as_fn_mkdir_p
if mkdir -p . 2>/dev/null; then
as_mkdir_p='mkdir -p "$as_dir"'
else
test -d ./-p && rmdir ./-p
as_mkdir_p=false
fi
# as_fn_executable_p FILE
# -----------------------
# Test if FILE is an executable regular file.
as_fn_executable_p ()
{
test -f "$1" && test -x "$1"
} # as_fn_executable_p
as_test_x='test -x'
as_executable_p=as_fn_executable_p
# Sed expression to map a string onto a valid CPP name.
as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
# Sed expression to map a string onto a valid variable name.
as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
exec 6>&1
## ----------------------------------- ##
## Main body of $CONFIG_STATUS script. ##
## ----------------------------------- ##
_ASEOF
test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# Save the log message, to keep $0 and so on meaningful, and to
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
This file was extended by mod_auth_openidc $as_me 2.3.3, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
CONFIG_HEADERS = $CONFIG_HEADERS
CONFIG_LINKS = $CONFIG_LINKS
CONFIG_COMMANDS = $CONFIG_COMMANDS
$ $0 $@
on `(hostname || uname -n) 2>/dev/null | sed 1q`
"
_ACEOF
case $ac_config_files in *"
"*) set x $ac_config_files; shift; ac_config_files=$*;;
esac
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
# Files that config.status was made for.
config_files="$ac_config_files"
_ACEOF
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
ac_cs_usage="\
\`$as_me' instantiates files and other configuration actions
from templates according to the current configuration. Unless the files
and actions are specified as TAGs, all are instantiated by default.
Usage: $0 [OPTION]... [TAG]...
-h, --help print this help, then exit
-V, --version print version number and configuration settings, then exit
--config print configuration, then exit
-q, --quiet, --silent
do not print progress messages
-d, --debug don't remove temporary files
--recheck update $as_me by reconfiguring in the same conditions
--file=FILE[:TEMPLATE]
instantiate the configuration file FILE
Configuration files:
$config_files
Report bugs to ."
_ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
mod_auth_openidc config.status 2.3.3
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
Copyright (C) 2012 Free Software Foundation, Inc.
This config.status script is free software; the Free Software Foundation
gives unlimited permission to copy, distribute and modify it."
ac_pwd='$ac_pwd'
srcdir='$srcdir'
test -n "\$AWK" || AWK=awk
_ACEOF
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# The default lists apply if the user does not specify any file.
ac_need_defaults=:
while test $# != 0
do
case $1 in
--*=?*)
ac_option=`expr "X$1" : 'X\([^=]*\)='`
ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'`
ac_shift=:
;;
--*=)
ac_option=`expr "X$1" : 'X\([^=]*\)='`
ac_optarg=
ac_shift=:
;;
*)
ac_option=$1
ac_optarg=$2
ac_shift=shift
;;
esac
case $ac_option in
# Handling of the options.
-recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
ac_cs_recheck=: ;;
--version | --versio | --versi | --vers | --ver | --ve | --v | -V )
$as_echo "$ac_cs_version"; exit ;;
--config | --confi | --conf | --con | --co | --c )
$as_echo "$ac_cs_config"; exit ;;
--debug | --debu | --deb | --de | --d | -d )
debug=: ;;
--file | --fil | --fi | --f )
$ac_shift
case $ac_optarg in
*\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
'') as_fn_error $? "missing file argument" ;;
esac
as_fn_append CONFIG_FILES " '$ac_optarg'"
ac_need_defaults=false;;
--he | --h | --help | --hel | -h )
$as_echo "$ac_cs_usage"; exit ;;
-q | -quiet | --quiet | --quie | --qui | --qu | --q \
| -silent | --silent | --silen | --sile | --sil | --si | --s)
ac_cs_silent=: ;;
# This is an error.
-*) as_fn_error $? "unrecognized option: \`$1'
Try \`$0 --help' for more information." ;;
*) as_fn_append ac_config_targets " $1"
ac_need_defaults=false ;;
esac
shift
done
ac_configure_extra_args=
if $ac_cs_silent; then
exec 6>/dev/null
ac_configure_extra_args="$ac_configure_extra_args --silent"
fi
_ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
if \$ac_cs_recheck; then
set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
shift
\$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6
CONFIG_SHELL='$SHELL'
export CONFIG_SHELL
exec "\$@"
fi
_ACEOF
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
exec 5>>config.log
{
echo
sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
## Running $as_me. ##
_ASBOX
$as_echo "$ac_log"
} >&5
_ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
_ACEOF
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# Handling of arguments.
for ac_config_target in $ac_config_targets
do
case $ac_config_target in
"Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
*) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
esac
done
# If the user did not use the arguments to specify the items to instantiate,
# then the envvar interface is used. Set only those that are not.
# We use the long form for the default assignment because of an extremely
# bizarre bug on SunOS 4.1.3.
if $ac_need_defaults; then
test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
fi
# Have a temporary directory for convenience. Make it in the build tree
# simply because there is no reason against having it here, and in addition,
# creating and moving files from /tmp can sometimes cause problems.
# Hook for its removal unless debugging.
# Note that there is a small window in which the directory will not be cleaned:
# after its creation but before its name has been assigned to `$tmp'.
$debug ||
{
tmp= ac_tmp=
trap 'exit_status=$?
: "${ac_tmp:=$tmp}"
{ test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status
' 0
trap 'as_fn_exit 1' 1 2 13 15
}
# Create a (secure) tmp directory for tmp files.
{
tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` &&
test -d "$tmp"
} ||
{
tmp=./conf$$-$RANDOM
(umask 077 && mkdir "$tmp")
} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5
ac_tmp=$tmp
# Set up the scripts for CONFIG_FILES section.
# No need to generate them if there are no CONFIG_FILES.
# This happens for instance with `./config.status config.h'.
if test -n "$CONFIG_FILES"; then
ac_cr=`echo X | tr X '\015'`
# On cygwin, bash can eat \r inside `` if the user requested igncr.
# But we know of no other shell where ac_cr would be empty at this
# point, so we can use a bashism as a fallback.
if test "x$ac_cr" = x; then
eval ac_cr=\$\'\\r\'
fi
ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null`
if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then
ac_cs_awk_cr='\\r'
else
ac_cs_awk_cr=$ac_cr
fi
echo 'BEGIN {' >"$ac_tmp/subs1.awk" &&
_ACEOF
{
echo "cat >conf$$subs.awk <<_ACEOF" &&
echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' &&
echo "_ACEOF"
} >conf$$subs.sh ||
as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'`
ac_delim='%!_!# '
for ac_last_try in false false false false false :; do
. ./conf$$subs.sh ||
as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X`
if test $ac_delim_n = $ac_delim_num; then
break
elif $ac_last_try; then
as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
else
ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
fi
done
rm -f conf$$subs.sh
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK &&
_ACEOF
sed -n '
h
s/^/S["/; s/!.*/"]=/
p
g
s/^[^!]*!//
:repl
t repl
s/'"$ac_delim"'$//
t delim
:nl
h
s/\(.\{148\}\)..*/\1/
t more1
s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/
p
n
b repl
:more1
s/["\\]/\\&/g; s/^/"/; s/$/"\\/
p
g
s/.\{148\}//
t nl
:delim
h
s/\(.\{148\}\)..*/\1/
t more2
s/["\\]/\\&/g; s/^/"/; s/$/"/
p
b
:more2
s/["\\]/\\&/g; s/^/"/; s/$/"\\/
p
g
s/.\{148\}//
t delim
' >$CONFIG_STATUS || ac_write_fail=1
rm -f conf$$subs.awk
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
_ACAWK
cat >>"\$ac_tmp/subs1.awk" <<_ACAWK &&
for (key in S) S_is_set[key] = 1
FS = ""
}
{
line = $ 0
nfields = split(line, field, "@")
substed = 0
len = length(field[1])
for (i = 2; i < nfields; i++) {
key = field[i]
keylen = length(key)
if (S_is_set[key]) {
value = S[key]
line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3)
len += length(value) + length(field[++i])
substed = 1
} else
len += 1 + keylen
}
print line
}
_ACAWK
_ACEOF
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g"
else
cat
fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \
|| as_fn_error $? "could not setup config files machinery" "$LINENO" 5
_ACEOF
# VPATH may cause trouble with some makes, so we remove sole $(srcdir),
# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and
# trailing colons and then remove the whole line if VPATH becomes empty
# (actually we leave an empty line to preserve line numbers).
if test "x$srcdir" = x.; then
ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{
h
s///
s/^/:/
s/[ ]*$/:/
s/:\$(srcdir):/:/g
s/:\${srcdir}:/:/g
s/:@srcdir@:/:/g
s/^:*//
s/:*$//
x
s/\(=[ ]*\).*/\1/
G
s/\n//
s/^[^=]*=[ ]*$//
}'
fi
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
fi # test -n "$CONFIG_FILES"
eval set X " :F $CONFIG_FILES "
shift
for ac_tag
do
case $ac_tag in
:[FHLC]) ac_mode=$ac_tag; continue;;
esac
case $ac_mode$ac_tag in
:[FHL]*:*);;
:L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;;
:[FH]-) ac_tag=-:-;;
:[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
esac
ac_save_IFS=$IFS
IFS=:
set x $ac_tag
IFS=$ac_save_IFS
shift
ac_file=$1
shift
case $ac_mode in
:L) ac_source=$1;;
:[FH])
ac_file_inputs=
for ac_f
do
case $ac_f in
-) ac_f="$ac_tmp/stdin";;
*) # Look for the file first in the build tree, then in the source tree
# (if the path is not absolute). The absolute path cannot be DOS-style,
# because $ac_f cannot contain `:'.
test -f "$ac_f" ||
case $ac_f in
[\\/$]*) false;;
*) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
esac ||
as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;;
esac
case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
as_fn_append ac_file_inputs " '$ac_f'"
done
# Let's still pretend it is `configure' which instantiates (i.e., don't
# use $as_me), people would be surprised to read:
# /* config.h. Generated by config.status. */
configure_input='Generated from '`
$as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g'
`' by configure.'
if test x"$ac_file" != x-; then
configure_input="$ac_file. $configure_input"
{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5
$as_echo "$as_me: creating $ac_file" >&6;}
fi
# Neutralize special characters interpreted by sed in replacement strings.
case $configure_input in #(
*\&* | *\|* | *\\* )
ac_sed_conf_input=`$as_echo "$configure_input" |
sed 's/[\\\\&|]/\\\\&/g'`;; #(
*) ac_sed_conf_input=$configure_input;;
esac
case $ac_tag in
*:-:* | *:-) cat >"$ac_tmp/stdin" \
|| as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;;
esac
;;
esac
ac_dir=`$as_dirname -- "$ac_file" ||
$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
X"$ac_file" : 'X\(//\)[^/]' \| \
X"$ac_file" : 'X\(//\)$' \| \
X"$ac_file" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X"$ac_file" |
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
s//\1/
q
}
/^X\(\/\/\)[^/].*/{
s//\1/
q
}
/^X\(\/\/\)$/{
s//\1/
q
}
/^X\(\/\).*/{
s//\1/
q
}
s/.*/./; q'`
as_dir="$ac_dir"; as_fn_mkdir_p
ac_builddir=.
case "$ac_dir" in
.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
*)
ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
# A ".." for each directory in $ac_dir_suffix.
ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
case $ac_top_builddir_sub in
"") ac_top_builddir_sub=. ac_top_build_prefix= ;;
*) ac_top_build_prefix=$ac_top_builddir_sub/ ;;
esac ;;
esac
ac_abs_top_builddir=$ac_pwd
ac_abs_builddir=$ac_pwd$ac_dir_suffix
# for backward compatibility:
ac_top_builddir=$ac_top_build_prefix
case $srcdir in
.) # We are building in place.
ac_srcdir=.
ac_top_srcdir=$ac_top_builddir_sub
ac_abs_top_srcdir=$ac_pwd ;;
[\\/]* | ?:[\\/]* ) # Absolute name.
ac_srcdir=$srcdir$ac_dir_suffix;
ac_top_srcdir=$srcdir
ac_abs_top_srcdir=$srcdir ;;
*) # Relative name.
ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
ac_top_srcdir=$ac_top_build_prefix$srcdir
ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
esac
ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
case $ac_mode in
:F)
#
# CONFIG_FILE
#
_ACEOF
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# If the template does not know about datarootdir, expand it.
# FIXME: This hack should be removed a few years after 2.60.
ac_datarootdir_hack=; ac_datarootdir_seen=
ac_sed_dataroot='
/datarootdir/ {
p
q
}
/@datadir@/p
/@docdir@/p
/@infodir@/p
/@localedir@/p
/@mandir@/p'
case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in
*datarootdir*) ac_datarootdir_seen=yes;;
*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*)
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5
$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;}
_ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_datarootdir_hack='
s&@datadir@&$datadir&g
s&@docdir@&$docdir&g
s&@infodir@&$infodir&g
s&@localedir@&$localedir&g
s&@mandir@&$mandir&g
s&\\\${datarootdir}&$datarootdir&g' ;;
esac
_ACEOF
# Neutralize VPATH when `$srcdir' = `.'.
# Shell code in configure.ac might set extrasub.
# FIXME: do we really want to maintain this feature?
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_sed_extra="$ac_vpsub
$extrasub
_ACEOF
cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
:t
/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
s|@configure_input@|$ac_sed_conf_input|;t t
s&@top_builddir@&$ac_top_builddir_sub&;t t
s&@top_build_prefix@&$ac_top_build_prefix&;t t
s&@srcdir@&$ac_srcdir&;t t
s&@abs_srcdir@&$ac_abs_srcdir&;t t
s&@top_srcdir@&$ac_top_srcdir&;t t
s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t
s&@builddir@&$ac_builddir&;t t
s&@abs_builddir@&$ac_abs_builddir&;t t
s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
$ac_datarootdir_hack
"
eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \
>$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5
test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
{ ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } &&
{ ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \
"$ac_tmp/out"`; test -z "$ac_out"; } &&
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
which seems to be undefined. Please make sure it is defined" >&5
$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
which seems to be undefined. Please make sure it is defined" >&2;}
rm -f "$ac_tmp/stdin"
case $ac_file in
-) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";;
*) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";;
esac \
|| as_fn_error $? "could not create $ac_file" "$LINENO" 5
;;
esac
done # for ac_tag
as_fn_exit 0
_ACEOF
ac_clean_files=$ac_clean_files_save
test $ac_write_fail = 0 ||
as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5
# configure is writing to config.log, and then calls config.status.
# config.status does its own redirection, appending to config.log.
# Unfortunately, on DOS this fails, as config.log is still kept open
# by configure, so config.status won't be able to write to it; its
# output is simply discarded. So we exec the FD to /dev/null,
# effectively closing config.log, so it can be properly (re)opened and
# appended to by config.status. When coming back to configure, we
# need to make the FD available again.
if test "$no_create" != yes; then
ac_cs_success=:
ac_config_status_args=
test "$silent" = yes &&
ac_config_status_args="$ac_config_status_args --quiet"
exec 5>/dev/null
$SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false
exec 5>>config.log
# Use ||, not &&, to avoid exiting from the if with $? = 1, which
# would make configure fail if this is the last instruction.
$ac_cs_success || as_fn_exit 1
fi
if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5
$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}
fi
mod_auth_openidc-2.3.3/configure.ac 0000644 0000765 0000024 00000005721 13203320656 017151 0 ustar hzandbelt staff AC_INIT([mod_auth_openidc],[2.3.3],[hans.zandbelt@zmartzone.eu])
AC_SUBST(NAMEVER, AC_PACKAGE_TARNAME()-AC_PACKAGE_VERSION())
# This section defines the --with-apxs2 option.
AC_ARG_WITH(
[apxs2],
[ --with-apxs2=PATH Full path to the apxs2 executable.],
[
APXS2=${withval}
],)
if test "x$APXS2" = "x"; then
# The user didn't specify the --with-apxs2-option.
# Search for apxs2 in the specified directories
AC_PATH_PROG(APXS2, apxs2,,
/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin)
if test "x$APXS2" = "x"; then
# Didn't find apxs2 in any of the specified directories.
# Search for apxs instead.
AC_PATH_PROG(APXS2, apxs,,
/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin)
fi
fi
# Test if $APXS2 exists and is an executable.
if test ! -x "$APXS2"; then
# $APXS2 isn't a executable file.
AC_MSG_ERROR([
Could not find apxs2. Please specify the path to apxs2
using the --with-apxs2=/full/path/to/apxs2 option.
The executable may also be named 'apxs'.
])
fi
# Replace any occurrences of @APXS2@ with the value of $APXS2 in the Makefile.
AC_SUBST(APXS2)
# Use environment varilable APXS2_OPTS to pass params to APXS2 command
AC_ARG_VAR(APXS2_OPTS, [Additional command line options to pass to apxs2.])
# We need the curl library for HTTP callouts.
PKG_CHECK_MODULES(CURL, libcurl)
AC_SUBST(CURL_CFLAGS)
AC_SUBST(CURL_LIBS)
# We need OpenSSL for crypto and HTTPS callouts.
PKG_CHECK_MODULES(OPENSSL, openssl)
AC_SUBST(OPENSSL_CFLAGS)
AC_SUBST(OPENSSL_LIBS)
PKG_CHECK_MODULES(APR, [apr-1, apr-util-1])
AC_SUBST(APR_CFLAGS)
AC_SUBST(APR_LIBS)
# We need Jansson for JSON parsing.
PKG_CHECK_MODULES(JANSSON, jansson)
AC_SUBST(JANSSON_CFLAGS)
AC_SUBST(JANSSON_LIBS)
# cjose
PKG_CHECK_MODULES(CJOSE, cjose)
AC_SUBST(CJOSE_CFLAGS)
AC_SUBST(CJOSE_LIBS)
# PCRE
PKG_CHECK_MODULES(PCRE, libpcre)
AC_SUBST(PCRE_CFLAGS)
AC_SUBST(PCRE_LIBS)
# Redis
AC_ARG_WITH([hiredis],
[AS_HELP_STRING([--with-hiredis],
[support Redis @<:@default=check@:>@])],
[],
[with_hiredis=yes])
AS_CASE(["$with_hiredis"],
[yes], [if test "$HIREDIS_LIBS" == ""; then PKG_CHECK_MODULES([HIREDIS], [hiredis], [HAVE_LIBHIREDIS=1], [HAVE_LIBHIREDIS=0]) ; else [HAVE_LIBHIREDIS=1] ; fi],
[no], [HAVE_LIBHIREDIS=0],
[PKG_CHECK_MODULES([HIREDIS], [hiredis], [HAVE_LIBHIREDIS=1], [HAVE_LIBHIREDIS=0])])
AC_SUBST(HAVE_LIBHIREDIS)
AC_SUBST(HIREDIS_CFLAGS)
AC_SUBST(HIREDIS_LIBS)
# JQ
HAVE_LIBJQ=0
AC_ARG_WITH(jq,
[ --with-jq=PATH location of your libjq installation])
if test -n "$with_jq"
then
JQ_CFLAGS="-I$with_jq/include"
JQ_LIBS="-L$with_jq/lib -ljq"
CPPFLAGS="$JQ_CFLAGS $CPPFLAGS"
AC_CHECK_HEADERS([jq.h], , [HAVE_LIBJQ=0])
LDFLAGS="$JQ_LIBS $LDFLAGS"
AC_CHECK_LIB([jq], [jq_init], [HAVE_LIBJQ=1], [HAVE_LIBJQ=0])
if test "x$have_jq" = "x0" ; then
AC_MSG_WARN("cannot find library for -ljq.")
fi
fi
AC_SUBST(HAVE_LIBJQ)
AC_SUBST(JQ_CFLAGS)
AC_SUBST(JQ_LIBS)
# Create Makefile from Makefile.in
AC_CONFIG_FILES([Makefile])
AC_OUTPUT
mod_auth_openidc-2.3.3/Makefile.in 0000644 0000765 0000024 00000004570 13200625410 016722 0 ustar hzandbelt staff # Source files. mod_auth_openidc.c must be the first file.
SRC=src/mod_auth_openidc.c \
src/cache/file.c \
src/cache/memcache.c \
src/cache/shm.c \
src/cache/common.c \
src/oauth.c \
src/proto.c \
src/config.c \
src/util.c \
src/authz.c \
src/session.c \
src/metadata.c \
src/jose.c \
src/parse.c \
src/pcre_subst.c \
ifeq (@HAVE_LIBHIREDIS@, 1)
SRC += \
src/cache/redis.c
REDIS_CFLAGS=-DUSE_LIBHIREDIS @HIREDIS_CFLAGS@
REDIS_LIBS=@HIREDIS_LIBS@
endif
ifeq (@HAVE_LIBJQ@, 1)
JQ_CFLAGS=-DUSE_LIBJQ @JQ_CFLAGS@
JQ_LIBS=@JQ_LIBS@
endif
HDRS = \
$(JWT_HDRS) \
src/mod_auth_openidc.h \
src/jose.h \
src/parse.h \
src/cache/cache.h \
src/pcre_subst.h \
# Files to include when making a .tar.gz-file for distribution
DISTFILES=$(SRC) \
$(HDRS) \
test/test.c \
test/test-cmd.c \
test/stub.c \
configure \
configure.ac \
Makefile.in \
autogen.sh \
INSTALL \
README.md \
AUTHORS \
DISCLAIMER \
auth_openidc.conf \
LICENSE.txt \
ChangeLog
all: src/mod_auth_openidc.la
CFLAGS=@OPENSSL_CFLAGS@ @CURL_CFLAGS@ @JANSSON_CFLAGS@ @CJOSE_CFLAGS@ @PCRE_CFLAGS@ $(REDIS_CFLAGS) $(JQ_CFLAGS)
LIBS=@OPENSSL_LIBS@ @CURL_LIBS@ @JANSSON_LIBS@ @CJOSE_LIBS@ @PCRE_LIBS@ $(REDIS_LIBS) $(JQ_LIBS)
src/mod_auth_openidc.la: $(SRC) $(HDRS)
@APXS2@ @APXS2_OPTS@ -Wc,"-DNAMEVER=\"@NAMEVER@\" $(CFLAGS)" -Wl,"$(LIBS)" -Wc,-Wall -Wc,-g -c $(SRC)
configure: configure.ac
./autogen.sh
@NAMEVER@.tar.gz: $(DISTFILES)
tar -c --transform="s#^#@NAMEVER@/#" -vzf $@ $(DISTFILES)
test/test test/test-cmd: test/test.c test/stub.c src/mod_auth_openidc.la
@APXS2@ @APXS2_OPTS@ $(CFLAGS) -Wl,"$(LIBS)" -Isrc -Wc,-Wall -Wc,-g -c -o $@ $@.c test/stub.c $(SRC:.c=.lo) @APR_LIBS@
test-compile: test/test test/test-cmd
test: test-compile
test/test
.PHONY: install
install: src/mod_auth_openidc.la
@APXS2@ @APXS2_OPTS@ -i -n mod_auth_openidc src/mod_auth_openidc.la
.PHONY: distfile
distfile: @NAMEVER@.tar.gz
.PHONY: clean
clean:
rm -f src/mod_auth_openidc.la
rm -f src/*.o src/cache/*.o test/*.o
rm -f src/*.lo src/cache/*.lo test/*.lo
rm -f src/*.slo src/cache/*.slo test/*.slo
rm -rf src/.libs/ src/cache/.libs/ test/.libs
rm -rf test/test test/test-cmd
.PHONY: distclean
distclean: clean
rm -f Makefile config.log config.status @NAMEVER@.tar.gz *~ \
build-stamp config.guess config.sub
rm -rf debian/mod-auth_openidc
rm -f debian/files
.PHONY: fullclean
fullclean: distclean
rm -f configure aclocal.m4
mod_auth_openidc-2.3.3/autogen.sh 0000755 0000765 0000024 00000000076 13137273467 016677 0 ustar hzandbelt staff #!/bin/sh
autoreconf --force --install
rm -rf autom4te.cache/
mod_auth_openidc-2.3.3/INSTALL 0000644 0000765 0000024 00000003746 13203103324 015710 0 ustar hzandbelt staff Preferably you should use one of the pre-compiled binary packages, available for
various platforms, see:
https://github.com/zmartzone/mod_auth_openidc/wiki#11-where-can-i-get-binary-packages
and proceed with the Configuration section below.
If your platform is not supported or you want to run the latest code,
you can build from source as described below.
Installation from source
========================
You will require development headers and tools for the following
dependencies:
Apache (>=2.0)
cjose (>=0.4.1)
OpenSSL (>=0.9.8) (>=1.0.1 for Elliptic Curve support)
Curl (>=?)
Jansson (>=2.0) (JSON parser for C)
pcre3 (>=?) (Regular Expressions support)
pkg-config
and if you want Redis support:
hiredis (>=0.9.0) (Redis client for C)
Configure, make and install with:
(run ./autogen.sh first if you work straight from the github source tree)
./configure --with-apxs2=/opt/apache2/bin/apxs2
make
make install
Note that, depending on your distribution, apxs2 may be named apxs.
FreeBSD users can use one of the following two options to install mod_auth_openidc:
- To install the port: cd /usr/ports/www/mod_auth_openidc/ && make install clean
- To add the package: pkg install ap24-mod_auth_openidc
Configuration
=============
Edit the configuration file for your web server. Depending on
your distribution, it may be named '/etc/apache/httpd.conf' or something
different.
You need to add a LoadModule directive for mod_auth_openidc. This will
look similar to this:
LoadModule auth_openidc_module /usr/lib/apache2/modules/mod_auth_openidc.so
To find the full path to mod_auth_openidc.so, you may run:
apxs2 -q LIBEXECDIR
This will print the path where Apache stores modules. mod_auth_openidc.so
will be stored in that directory.
After you have added the LoadModule directive, you must add the configuration
for mod_auth_openidc. For a quickstart doing so, see the provided samples
in the README.md file.
For an exhaustive overview of all configuration primitives, see: auth_openidc.conf
mod_auth_openidc-2.3.3/README.md 0000644 0000765 0000024 00000021032 13203103444 016125 0 ustar hzandbelt staff [](https://travis-ci.org/zmartzone/mod_auth_openidc)
mod_auth_openidc
================
*mod_auth_openidc* is an authentication/authorization module for the Apache 2.x
HTTP server that functions as an **OpenID Connect Relying Party**, authenticating users against an
OpenID Connect Provider. It can also function as an **OAuth 2.0 Resource Server**, validating
OAuth 2.0 access tokens presented by OAuth 2.0 Clients.
Overview
--------
This module enables an Apache 2.x web server to operate as an [OpenID Connect](http://openid.net/specs/openid-connect-core-1_0.html)
*Relying Party* (RP) to an OpenID Connect *Provider* (OP). It authenticates users against an OpenID Connect Provider,
receives user identity information from the OP in a so called ID Token and passes the identity information
(a.k.a. claims) in the ID Token to applications hosted and protected by the Apache web server.
It can also be configured as an OAuth 2.0 *Resource Server* (RS), consuming bearer access tokens and validating
them against an OAuth 2.0 Authorization Server, authorizing the Clients based on the validation results.
The protected content and/or applications can be served by the Apache server itself or it can be served from elsewhere
when Apache is configured as a Reverse Proxy in front of the origin server(s).
By default the module sets the `REMOTE_USER` variable to the `id_token` `[sub]` claim, concatenated with the OP's Issuer
identifier (`[sub]@[iss]`). Other `id_token` claims are passed in HTTP headers and/or environment variables together with those
(optionally) obtained from the UserInfo endpoint.
It allows for authorization rules (based on standard Apache `Require` primitives) that can be matched against the set
of claims provided in the `id_token`/ `userinfo` claims.
*mod_auth_openidc* supports the following specifications:
- [OpenID Connect](http://openid.net/specs/openid-connect-core-1_0.html) Basic, Implicit, Hybrid and Refresh flows.
- [OpenID Connect Dynamic Client Registration](http://openid.net/specs/openid-connect-registration-1_0.html)
- [OpenID Provider Discovery](http://openid.net/specs/openid-connect-discovery-1_0.html)
- [OAuth 2.0 Form Post Response Mode](http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html)
- [Proof Key for Code Exchange by OAuth Public Clients](https://tools.ietf.org/html/rfc7636)
- [OpenID Connect Session Management](http://openid.net/specs/openid-connect-session-1_0.html). See the [Wiki](https://github.com/zmartzone/mod_auth_openidc/wiki/Session-Management) for information
on how to configure it.
Alternatively the module can operate as an OAuth 2.0 Resource Server to an OAuth 2.0 Authorization Server,
introspecting/validating bearer Access Tokens conforming to [OAuth 2.0 Token Introspection](https://tools.ietf.org/html/rfc7662) (or similar),
or verifiying them locally if they are JWTs.
The `REMOTE_USER` variable setting, passing claims in HTTP headers and authorization based on `Require` primitives
works in the same way as described for OpenID Connect above. See the [Wiki](https://github.com/zmartzone/mod_auth_openidc/wiki/OAuth-2.0-Resource-Server) for information
on how to configure it.
For an exhaustive description of all configuration options, see the file `auth_openidc.conf`
in this directory. This file can also serve as an include file for `httpd.conf`.
How to Use It
-------------
### OpenID Connect SSO with Google+ Sign-In
Sample configuration for using Google as your OpenID Connect Provider running on
`www.example.com` and `https://www.example.com/example/redirect_uri` registered
as the *redirect_uri* for the client through the Google API Console. You will also
have to enable the `Google+ API` under `APIs & auth` in the [Google API console](https://console.developers.google.com).
```apache
OIDCProviderMetadataURL https://accounts.google.com/.well-known/openid-configuration
OIDCClientID
OIDCClientSecret
OIDCRedirectURI https://www.example.com/example/redirect_uri
OIDCCryptoPassphrase
AuthType openid-connect
Require valid-user
```
Note if you want to securely restrict logins to a specific Google Apps domain you would not only
add the `hd=` setting to the `OIDCAuthRequestParams` primitive for skipping the Google Account
Chooser screen, but you must also ask for the `email` scope using `OIDCScope` and use a `Require claim`
authorization setting in the `Location` primitive similar to:
```apache
OIDCScope "openid email"
Require claim hd:
```
The above is an authorization example of an exact match of a provided claim against a string value.
For more authorization options see the [Wiki page on Authorization](https://github.com/zmartzone/mod_auth_openidc/wiki/Authorization).
### Quickstart with a generic OpenID Connect Provider
1. install and load `mod_auth_openidc.so` in your Apache server
1. configure your protected content/locations with `AuthType openid-connect`
1. set `OIDCRedirectURI` to a "vanity" URL within a location that is protected by mod_auth_openidc
1. register/generate a Client identifier and a secret with the OpenID Connect Provider and configure those in `OIDCClientID` and `OIDCClientSecret` respectively
1. and register the `OIDCRedirectURI` as the Redirect or Callback URI with your client at the Provider
1. configure `OIDCProviderMetadataURL` so it points to the Discovery metadata of your OpenID Connect Provider served on the `.well-known/openid-configuration` endpoint
1. configure a random password in `OIDCCryptoPassphrase` for session/state encryption purposes
```apache
LoadModule auth_openidc_module modules/mod_auth_openidc.so
OIDCProviderMetadataURL /.well-known/openid-configuration
OIDCClientID
OIDCClientSecret
OIDCRedirectURI https:///secure/redirect_uri
OIDCCryptoPassphrase
AuthType openid-connect
Require valid-user
```
For details on configuring multiple providers see the [Wiki](https://github.com/zmartzone/mod_auth_openidc/wiki/Multiple-Providers).
### PingFederate OAuth 2.0 Resource Server
Example config for using PingFederate as your OAuth 2.0 Authorization server,
based on the OAuth 2.0 PlayGround configuration and doing claims-based authorization, using
RFC 7662 compliant Token Introspection.
```apache
# remote validation
OIDCOAuthIntrospectionEndpoint https://localhost:9031/as/introspect.oauth2
OIDCOAuthIntrospectionEndpointAuth client_secret_basic
OIDCOAuthRemoteUserClaim Username
OIDCOAuthSSLValidateServer Off
OIDCOAuthClientID rs_client
OIDCOAuthClientSecret 2Federate
AuthType oauth20
Require claim client_id:ro_client
#Require claim scope~\bprofile\b
```
For details and additional options on the OAuth 2.0 Resource Server setup see the [Wiki](https://github.com/zmartzone/mod_auth_openidc/wiki/OAuth-2.0-Resource-Server).
### Quickstart with a generic OAuth 2.0 Resource Server
Using "local" validation of JWT bearer tokens:
1. install and load `mod_auth_openidc.so` in your Apache server
1. configure your protected APIs/locations with `AuthType oauth20` and `Require claim` directives to restrict access to specific clients/scopes/claims/resource-owners
1. configure local or remote bearer token validation following the [Wiki](https://github.com/zmartzone/mod_auth_openidc/wiki/OAuth-2.0-Resource-Server)
```apache
# local validation
OIDCOAuthVerifySharedKeys plain##
AuthType oauth20
Require claim sub:
```
Support
-------
See the Wiki pages with Frequently Asked Questions at:
https://github.com/zmartzone/mod_auth_openidc/wiki
There is a Google Group/mailing list at:
[mod_auth_openidc@googlegroups.com](mailto:mod_auth_openidc@googlegroups.com)
The corresponding forum/archive is at:
https://groups.google.com/forum/#!forum/mod_auth_openidc
For commercial support and consultancy you can contact:
[info@zmartzone.eu](mailto:info@zmartzone.eu)
Any questions/issues should go to the mailing list or the
primary author [hans.zandbelt@zmartzone.eu](mailto:hans.zandbelt@zmartzone.eu).
The Github issues tracker should be used only for bugs reports and feature requests.
Disclaimer
----------
*This software is open sourced by ZmartZone IAM. For commercial support
you can contact [ZmartZone IAM](https://www.zmartzone.eu) as described above.*
mod_auth_openidc-2.3.3/AUTHORS 0000644 0000765 0000024 00000003405 13202535004 015722 0 ustar hzandbelt staff The primary author of mod_auth_openidc is:
Hans Zandbelt
Thanks to the following people for contributing to mod_auth_openidc by
reporting bugs, providing fixes, suggesting useful features or other:
Dániel SÜTTŐ
Roland Hedberg
Bill Simon
Jim Fox
Martin Srom
Dejan Latinovic
Hiroyuki Wada
Gunnar Scherf
Terrence Fleury
Jeremy Archer
Forkbomber
Kanthi Vaidya
szakharchenko
John Bradley
Stefano Vercelli
David Bernick
Joseph Bester
Daniel Pfile
Rebecka Gulliksson
Ryan Kelly
John R. Dennis
steve-dave
glatzert
Amit Joshi
Andy Curtis
solsson
drdivano
AliceWonderMiscreations
Wouter Hund
Hans Keeler
Moritz Schlarb
remi-cc
hihellobolke
Horatiu Eugen Vlad
cristichiru
mod_auth_openidc-2.3.3/DISCLAIMER 0000644 0000765 0000024 00000002427 13137273467 016237 0 ustar hzandbelt staff /***************************************************************************
* Copyright (C) 2014-2017 Ping Identity Corporation
* All rights reserved.
*
* Ping Identity Corporation
* 1099 18th St Suite 2950
* Denver, CO 80202
* 303.468.2900
* http://www.pingidentity.com
*
* DISCLAIMER OF WARRANTIES:
*
* THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
* ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
* WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY
* WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
* USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
* YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
* WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
mod_auth_openidc-2.3.3/auth_openidc.conf 0000644 0000765 0000024 00000132501 13203320522 020161 0 ustar hzandbelt staff ########################################################################################
#
# Common Settings
#
########################################################################################
# (Mandatory)
# The redirect_uri for this OpenID Connect client; this is a vanity URL
# that must ONLY point to a path on your server protected by this module
# but it must NOT point to any actual content that needs to be served.
# You can use a relative URL like /protected/redirect_uri if you want to
# support multiple vhosts that belong to the same security domain in a dynamic way
#OIDCRedirectURI https://www.example.com/protected/redirect_uri
# (Mandatory)
# Set a password for crypto purposes, this is used for:
# - encryption of the (temporary) state cookie
# - encryption of cache entries, that may include the session cookie, see: OIDCCacheEncrypt and OIDCSessionType
# Note that an encrypted cache mechanism can be shared between servers if they use the same OIDCCryptoPassphrase
#OIDCCryptoPassphrase
#
# All other entries below this are optional though some may be required in a
# particular setup e.g. OAuth 2.0 Resource Server vs. OpenID Connect Relying Party
#
# When using multiple OpenID Connect Providers, possibly combined with Dynamic Client
# Registration and account-based OP Discovery.
# Specifies the directory that holds metadata files (must be writable for the Apache process/user).
# When not specified, it is assumed that we use a single statically configured provider as
# described under the section "OpenID Connect Provider" below, most likely using OIDCProviderMetadataURL.
#OIDCMetadataDir /var/cache/apache2/mod_auth_openidc/metadata
########################################################################################
#
# OpenID Connect Provider
#
# For configuration of a single static provider, not using OpenID Connect Provider Discovery.
#
########################################################################################
# URL where OpenID Connect Provider metadata can be found (e.g. https://accounts.google.com/.well-known/openid-configuration)
# The obtained metadata will be cached and refreshed every 24 hours.
# If set, individual entries below will not have to be configured but can be used to add
# extra entries/endpoints to settings obtained from the metadata.
# If OIDCProviderMetadataURL is not set, the entries below it will have to be configured for a single
# static OP configuration or OIDCMetadataDir will have to be set for configuration of multiple OPs.
#OIDCProviderMetadataURL
# OpenID Connect Provider issuer identifier (e.g. https://localhost:9031 or https://accounts.google.com)
# Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it.
#OIDCProviderIssuer
# OpenID Connect Provider Authorization Endpoint URL (e.g. https://localhost:9031/as/authorization.oauth2)
# Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it.
#OIDCProviderAuthorizationEndpoint
# OpenID Connect Provider JWKS URL (e.g. https://localhost:9031/pf/JWKS)
# i.e. the URL on which the signing keys for this OP are hosted, in JWK formatting
# Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it.
#OIDCProviderJwksUri
# OpenID Connect Provider Token Endpoint URL (e.g. https://localhost:9031/as/token.oauth2)
# Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it.
#OIDCProviderTokenEndpoint
# Authentication method for the OpenID Connect Provider Token Endpoint.
# One of "client_secret_basic", "client_secret_post", "client_secret_jwt" or "private_key_jwt".
# When "private_key_jwt" is used, OIDCPrivateKeyFiles and OIDCPublicKeyFiles must have been set.
# When not defined the default method from the specification is used, i.e. "client_secret_basic".
# Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it.
# NB: this can be overridden for dynamic client registration on a per-OP basis in the .conf file using the key: token_endpoint_auth
#OIDCProviderTokenEndpointAuth [ client_secret_basic | client_secret_post | client_secret_jwt | private_key_jwt]
# Extra parameters that need to be passed in the POST request to the Token Endpoint.
# Parameter names and values need to be provided in URL-encoded form.
# When not defined no extra parameters will be passed.
# NB: this can be overridden on a per-OP basis in the .conf file using the key: token_endpoint_params
#OIDCProviderTokenEndpointParams =[&=]*
# OpenID Connect Provider UserInfo Endpoint URL (e.g. https://localhost:9031/idp/userinfo.openid)
# When not defined no claims will be resolved from such endpoint.
# Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it.
#OIDCProviderUserInfoEndpoint
# OpenID OP Check Session iFrame URL, for Session Management purposes.
# When not defined, no Session Management will be applied.
# Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it.
#OIDCProviderCheckSessionIFrame
# OpenID OP End Session Endpoint URL, for Single Logout (Session Management) purposes.
# When not defined, no logout to the OP will be performed.
# Used when OIDCProviderMetadataURL is not defined or the metadata obtained from that URL does not set it.
#OIDCProviderEndSessionEndpoint
# Extra JSON parameters that need to be passed in the registration request to the Registration Endpoint.
# This settings serves as a default value for multiple OPs only.
# Parameter names and values need to be provided in JSON form and will be merged in to the request.
# When not defined no extra parameters will be passed.
# NB: this can be overridden on a per-OP basis in the .conf file using the key: registration_endpoint_params
#OIDCProviderRegistrationEndpointJson
# Define the OpenID Connect scope that is requested from the OP (eg. "openid email profile").
# When not defined, the bare minimal scope "openid" is used.
# NB: multiple scope values must be enclosed in a single pair of double quotes
# NB: this can be overridden on a per-OP basis in the .conf file using the key: scope
#OIDCScope ""
# Extra parameters that will be sent along with the Authorization Request.
# These must be URL-query-encoded as in: "display=popup&prompt=consent" or
# specific for Google's implementation: "approval_prompt=force".
# This is used against a statically configured (single) OP or serves as the default for discovered OPs.
# As an alternative to this option, one may choose to add the parameters as
# part of the URL set in OIDCProviderAuthorizationEndpoint or "authorization_endpoint"
# in the .provider metadata (though that would not work with Discovery OPs).
# The default is to not add extra parameters.
# NB: this can be overridden on a per-OP basis in the .conf file using the key: auth_request_params
#OIDCAuthRequestParams
# Require a valid SSL server certificate when communicating with the OP.
# (i.e. on token endpoint, UserInfo endpoint and Dynamic Client Registration endpoint)
# When not defined, the default value is "On".
# NB: this can be overridden on a per-OP basis in the .conf file using the key: ssl_validate_server
#OIDCSSLValidateServer [On|Off]
# The refresh interval in seconds for the claims obtained from the userinfo endpoint
# When not defined the default is 0, i.e. the claims are retrieved only at session creation time.
# NB: this can be overridden on a per-OP basis in the .conf file using the key: userinfo_refresh_interval
#OIDCUserInfoRefreshInterval
# The refresh interval in seconds for the JWKs key set obtained from the jwk_uri.
# When not defined the default is 3600 seconds.
# NB: this can be overridden on a per-OP basis in the .conf file using the key: jwks_refresh_interval
#OIDCJWKSRefreshInterval
# Defines the way in which the access token will be presented to the userinfo endpoint
# "authz_header" means that the token will be presented in an "Authorization: Bearer" header using HTTP GET
# "post_param" means that the token will be presented a form-encoded POST parameter using HTTP POST
# When not defined the default is "authz_header".
# NB: this can be overrridden on a per-OP basis in the .conf file using the key: userinfo_token_method
#OIDCUserInfoTokenMethod [authz_header|post_param]
# Defines the HTTP method used to pass the parameters in the Authentication Request to the Authorization Endpoint.
# "GET" means that the parameters will be passed as query parameters in an HTTP GET
# "POST" means that the parameters will be passed as form-post parameters in an HTTP POST
# When not defined the default is "GET".
# NB: this can be overrridden on a per-OP basis in the .conf file using the key: auth_request_method
# OIDCProviderAuthRequestMethod [ GET | POST ]
########################################################################################
#
# OpenID Connect Client
#
# Settings used by the client in communication with the OpenID Connect Provider(s),
# i.e. in Authorization Requests, Dynamic Client Registration and UserInfo Endpoint access.
# These settings are used when a single static provider is configured and serve as defaults
# when multiple providers are configured.
#
########################################################################################
# The response type (or OpenID Connect Flow) used (this serves as default value for discovered OPs too)
# When not defined the "code" response type is used.
# NB: this can be overridden on a per-OP basis in the .conf file using the key: response_type
#OIDCResponseType ["code"|"id_token"|"id_token token"|"code id_token"|"code token"|"code id_token token"]
# The response mode used (this serves as default value for discovered OPs too)
# When not defined the default response mode for the requested flow (OIDCResponseType) is used.
# NB: this can be overridden on a per-OP basis in the .conf file using the key: response_mode
#OIDCResponseMode [fragment|query|form_post]
# Only used for a single static provider has been configured, see below in OpenID Connect Provider.
# Client identifier used in calls to the statically configured OpenID Connect Provider.
#OIDCClientID
# Only used for a single static provider has been configured, see below in OpenID Connect Provider.
# Client secret used in calls to the statically configured OpenID Connect Provider.
# (not used/required in the Implicit Client Profile, i.e. when OIDCResponseType is "id_token")
#OIDCClientSecret
# Filename with the PEM-formatted client certificate used to authenticate the Client in calls to the
# token endpoint of the OAuth 2.0 Authorization server.
# NB: this can be overridden on a per-OP basis in the .conf file using the key: token_endpoint_tls_client_cert
#OIDCClientTokenEndpointCert
# Filename with the PEM-formatted private key that belongs to the client certificate used to authenticate the
# Client in calls to the token endpoint of the OAuth 2.0 Authorization server.
# NB: this can be overridden on a per-OP basis in the .conf file using the key: token_endpoint_tls_client_key
#OIDCClientTokenEndpointKey
# The client name that the client registers in dynamic registration with the OP.
# When not defined, no client name will be sent with the registration request.
# NB: this can be overridden on a per-OP basis in the .conf file using the key: client_name
#OIDCClientName
# The contacts that the client registers in dynamic registration with the OP.
# Must be formatted as e-mail addresses by specification.
# Single value only; when not defined, no contact e-mail address will be sent with the registration request.
# NB: this can be overridden on a per-OP basis in the .conf file using the key: client_contact
#OIDCClientContact
# The PKCE method used (this serves as default value for discovered OPs too)
# When not defined PKCE is not used.
# NB: this can be overridden on a per-OP basis in the .conf file using the key: pkce_method
#OIDCPKCEMethod [plain|S256|referred_tb]
# The OpenID Connect Bound Authentication policy used,
# see: http://openid.net/specs/openid-connect-token-bound-authentication-1_0.html
# "disabled": no referred token binding will be requested from the User Agent upon redirection to the OP
# "optional": referred token binding will be requested, the "cnf["tbh"]" claim is optional on return
# "required": referred token binding will be requested, the "cnf["tbh"]" claim must be present when the Client supports Token Binding
# "enforced": referred token binding will be requested, the "cnf["tbh"]" claim must be present and the User Agent must support Token Binding
#OIDCTokenBindingPolicy [disabled|optional|required|enforced]
# (used only in dynamic client registration)
# Define the Client JWKs URL (e.g. https://localhost/protected/?jwks=rsa)") that will be
# used during client registration to point to the JWK set with public keys for this client.
# If not defined the default ?jwks=rsa will be used, on which a JWK set
# is automatically published based on the OIDCPublicKeyFiles setting so normally you don't
# need to touch this unless this client is on a (test) host that is not reachable from the internet.
# NB: this can be overridden on a per-OP basis in the .conf file using the key: client_jwks_uri
#OIDCClientJwksUri
# (used only in dynamic client registration)
# The algorithm that the OP should use to sign the id_token.
# When not defined the default that the OP should use by spec is RS256.
# (ES??? algorithms only supported when using OpenSSL >= 1.0)
# NB: this can be overridden on a per-OP basis in the .conf file using the key: id_token_signed_response_alg
#OIDCIDTokenSignedResponseAlg [RS256|RS384|RS512|PS256|PS384|PS512|HS256|HS384|HS512|ES256|ES384|ES512]
# (used only in dynamic client registration)
# The algorithm that the OP should use to encrypt the Content Encryption Key that is used to encrypt the id_token.
# When not defined the default (by spec) is that the OP does not encrypt the id_token.
# NB: this can be overridden on a per-OP basis in the .conf file using the key: id_token_encrypted_response_alg
#OIDCIDTokenEncryptedResponseAlg [RSA1_5|A128KW|A256KW|RSA-OAEP]
# (used only in dynamic client registration)
# The algorithm that the OP should use to encrypt to the id_token with the Content Encryption Key.
# If OIDCIDTokenEncryptedResponseAlg is specified, the default for this value is A128CBC-HS256.
# When OIDCIDTokenEncryptedResponseEnc is included, OIDCIDTokenEncryptedResponseAlg MUST also be provided.
# (A256GCM algorithm only supported when using OpenSSL >= 1.0.1)
# NB: this can be overridden on a per-OP basis in the .conf file using the key: id_token_encrypted_response_enc
#OIDCIDTokenEncryptedResponseEnc [A128CBC-HS256|A256CBC-HS512|A256GCM]
# (used only in dynamic client registration)
# The algorithm that the OP should use to sign the UserInfo response
# When not defined the default (by spec) is that the OP does not sign the response.
# (ES??? algorithms only supported when using OpenSSL >= 1.0)
# NB: this can be overridden on a per-OP basis in the .conf file using the key: userinfo_signed_response_alg
#OIDCUserInfoSignedResponseAlg RS256|RS384|RS512|PS256|PS384|PS512|HS256|HS384|HS512|ES256|ES384|ES512]
# (used only in dynamic client registration)
# The algorithm that the OP should use to encrypt the Content Encryption Key that is used to encrypt the UserInfo response.
# When not defined the default (by spec) is that the OP does not encrypt the response.
# NB: this can be overridden on a per-OP basis in the .conf file using the key: userinfo_encrypted_response_alg
#OIDCUserInfoEncryptedResponseAlg [RSA1_5|A128KW|A256KW|RSA-OAEP]
# (used only in dynamic client registration)
# The algorithm that the OP should use to encrypt to encrypt the UserInfo response with the Content Encryption Key
# If OIDCUserInfoEncryptedResponseAlg is specified, the default for this value is A128CBC-HS256.
# When OIDCUserInfoEncryptedResponseEnc is included, OIDCUserInfoEncryptedResponseAlg MUST also be provided.
# (A256GCM algorithm only supported when using OpenSSL >= 1.0.1)
# NB: this can be overridden on a per-OP basis in the .conf file using the key: userinfo_encrypted_response_enc
#OIDCUserInfoEncryptedResponseEnc [A128CBC-HS256|A256CBC-HS512|A256GCM]
########################################################################################
#
# OAuth 2.0 Resource Server Settings
#
# Used when this module functions as a Resource Server against an OAuth 2.0 Authorization
# Server, introspecting/validating bearer Access Tokens.
#
########################################################################################
# (Mandatory when introspecting opaque access tokens, Optional when performing local JWT access token validation)
# OAuth 2.0 Authorization Server token introspection endpoint (e.g. https://localhost:9031/as/token.oauth2)
#OIDCOAuthIntrospectionEndpoint
# Client identifier used in token introspection calls to the OAuth 2.0 Authorization server.
#OIDCOAuthClientID