mod_auth_openidc-1.8.5/src/mod_auth_openidc.c 0000664 0001750 0001750 00000243653 12577725501 021504 0 ustar zandbelt zandbelt /*
* 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-2015 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:
* AES crypto: http://saju.net.in/code/misc/openssl_aes.c.txt
* session handling: Apache 2.4 mod_session.c
* session handling backport: http://contribsoft.caixamagica.pt/browser/internals/2012/apachecc/trunk/mod_session-port/src/util_port_compat.c
* shared memory caching: mod_auth_mellon
*
* @Author: Hans Zandbelt - hzandbelt@pingidentity.com
*
**************************************************************************/
#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)
// - check the Apache 2.4 compilation/#defines
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,
const char *authn_header) {
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 the header that mod_auth_openidc would set for the authenticated user? */
const int authn_header_matches = (k != NULL) && authn_header
&& (oidc_strnenvcmp(k, authn_header, -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 && !authn_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;
}
#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 = (char *) apr_table_get(r->headers_in, "X_FORWARDED_FOR");
/* 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 = (char *) apr_table_get(r->headers_in, "USER_AGENT");
/* 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));
/* 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", OIDCStateCookiePrefix, 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;
const 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;
}
c->cache->get(r, OIDC_CACHE_SECTION_PROVIDER, 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;
}
// TODO: make the expiry configurable
c->cache->set(r, OIDC_CACHE_SECTION_PROVIDER, c->provider.metadata_url,
s_json,
apr_time_now() + apr_time_from_sec(OIDC_CACHE_PROVIDER_METADATA_EXPIRY_DEFAULT));
} else {
/* correct parsing and validation was already done when it was put in the cache */
j_provider = json_loads(s_json, 0, 0);
}
*provider = apr_pcalloc(r->pool, sizeof(oidc_provider_t));
memcpy(*provider, &c->provider, sizeof(oidc_provider_t));
if (oidc_metadata_provider_parse(r, 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;
}
/*
* 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, json_t **proto_state) {
oidc_debug(r, "enter");
apr_jwt_t *jwt = NULL;
apr_jwt_error_t err;
if (apr_jwt_parse(r->pool, state, &jwt,
oidc_util_merge_symmetric_key(r->pool, c->private_keys,
c->provider.client_secret, "sha256"), &err) == FALSE) {
oidc_error(r,
"could not parse JWT from state: invalid unsolicited response: %s",
apr_jwt_e2s(r->pool, err));
return FALSE;
}
oidc_debug(r, "successfully parsed JWT from state");
if (jwt->payload.iss == NULL) {
oidc_error(r, "no \"iss\" could be retrieved from JWT state, aborting");
apr_jwt_destroy(jwt);
return FALSE;
}
oidc_provider_t *provider = oidc_get_provider_for_issuer(r, c,
jwt->payload.iss, FALSE);
if (provider == NULL) {
apr_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) {
apr_jwt_destroy(jwt);
return FALSE;
}
char *rfp = NULL;
if (apr_jwt_get_string(r->pool, jwt->payload.value.json, "rfp", TRUE, &rfp,
&err) == FALSE) {
oidc_error(r,
"no \"rfp\" claim could be retrieved from JWT state, aborting: %s",
apr_jwt_e2s(r->pool, err));
apr_jwt_destroy(jwt);
return FALSE;
}
if (strcmp(rfp, "iss") != 0) {
oidc_error(r, "\"rfp\" (%s) does not match \"iss\", aborting", rfp);
apr_jwt_destroy(jwt);
return FALSE;
}
char *target_link_uri = NULL;
apr_jwt_get_string(r->pool, jwt->payload.value.json, "target_link_uri",
FALSE, &target_link_uri, NULL);
if (target_link_uri == NULL) {
if (c->default_sso_url == NULL) {
oidc_error(r,
"no \"target_link_uri\" claim could be retrieved from JWT state and no OIDCDefaultURL is set, aborting");
apr_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);
apr_jwt_destroy(jwt);
return FALSE;
}
}
char *jti = NULL;
apr_jwt_get_string(r->pool, jwt->payload.value.json, "jti", FALSE, &jti,
NULL);
if (jti == NULL) {
apr_jwt_base64url_encode(r->pool, &jti,
(const char *) jwt->signature.bytes, jwt->signature.length, 0);
}
const char *replay = NULL;
c->cache->get(r, OIDC_CACHE_SECTION_JTI, jti, &replay);
if (replay != NULL) {
oidc_error(r,
"the jti value (%s) passed in the browser state was found in the cache already; possible replay attack!?",
jti);
apr_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 */
c->cache->set(r, OIDC_CACHE_SECTION_JTI, 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));
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,
provider->client_secret, NULL)) == FALSE) {
oidc_error(r, "state JWT signature could not be validated, aborting");
apr_jwt_destroy(jwt);
return FALSE;
}
oidc_debug(r, "successfully verified state JWT");
*proto_state = json_object();
json_object_set_new(*proto_state, "issuer", json_string(jwt->payload.iss));
json_object_set_new(*proto_state, "original_url",
json_string(target_link_uri));
json_object_set_new(*proto_state, "original_method", json_string("get"));
json_object_set_new(*proto_state, "response_mode",
json_string(provider->response_mode));
json_object_set_new(*proto_state, "response_type",
json_string(provider->response_type));
json_object_set_new(*proto_state, "timestamp",
json_integer(apr_time_sec(apr_time_now())));
apr_jwt_destroy(jwt);
return TRUE;
}
/*
* 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, json_t **proto_state) {
oidc_debug(r, "enter");
const char *cookieName = oidc_get_state_cookie_name(r, state);
/* 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);
/* decrypt the state obtained from the cookie */
char *svalue = NULL;
if (oidc_base64url_decode_decrypt_string(r, &svalue, cookieValue) <= 0)
return FALSE;
oidc_debug(r, "restored JSON state cookie value: %s", svalue);
json_error_t json_error;
*proto_state = json_loads(svalue, 0, &json_error);
if (*proto_state == NULL) {
oidc_error(r, "parsing JSON (json_loads) failed: %s", json_error.text);
return FALSE;
}
json_t *v = json_object_get(*proto_state, "nonce");
/* calculate the hash of the browser fingerprint concatenated with the nonce */
char *calc = oidc_get_browser_state_hash(r, json_string_value(v));
/* 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);
json_decref(*proto_state);
return FALSE;
}
v = json_object_get(*proto_state, "timestamp");
apr_time_t now = apr_time_sec(apr_time_now());
/* check that the timestamp is not beyond the valid interval */
if (now > json_integer_value(v) + c->state_timeout) {
oidc_error(r, "state has expired");
json_decref(*proto_state);
return FALSE;
}
char *s_value = json_dumps(*proto_state, JSON_ENCODE_ANY);
oidc_debug(r, "restored state: %s", s_value);
free(s_value);
/* 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, json_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
*/
char *s_value = json_dumps(proto_state, JSON_ENCODE_ANY);
/* encrypt the resulting JSON value */
char *cookieValue = NULL;
if (oidc_encrypt_base64url_encode_string(r, &cookieValue, s_value) <= 0) {
free(s_value);
oidc_error(r, "oidc_encrypt_base64url_encode_string failed");
return FALSE;
}
/* 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,
apr_time_now() + apr_time_from_sec(c->state_timeout));
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_setn(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, session_rec *session,
const char *session_key) {
/* get a handle to the directory config */
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
const char *s_claims = NULL;
json_t *j_claims = NULL;
/* get the string-encoded JSON object from the session */
oidc_session_get(r, session, session_key, &s_claims);
/* decode the string-encoded attributes in to a JSON structure */
if (s_claims != NULL) {
json_error_t json_error;
j_claims = json_loads(s_claims, 0, &json_error);
if (j_claims == NULL) {
/* whoops, JSON has been corrupted */
oidc_error(r,
"unable to parse \"%s\" JSON stored in the session (%s), returning internal server error",
json_error.text, session_key);
return FALSE;
}
}
/* set the resolved claims a HTTP headers for the application */
if (j_claims != NULL) {
oidc_util_set_app_infos(r, j_claims, cfg->claim_prefix,
cfg->claim_delimiter, dir_cfg->pass_info_in_headers,
dir_cfg->pass_info_in_env_vars);
/* set the claims JSON string in the request state so it is available for authz purposes later on */
oidc_request_state_set(r, session_key, s_claims);
/* 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);
/*
* log message about max session duration
*/
static void oidc_log_session_expires(request_rec *r, apr_time_t session_expires) {
char buf[APR_RFC822_DATE_LEN + 1];
apr_rfc822_date(buf, session_expires);
oidc_debug(r, "session expires %s (in %" APR_TIME_T_FMT " secs from now)",
buf, apr_time_sec(session_expires - apr_time_now()));
}
/*
* check if maximum session duration was exceeded
*/
static int oidc_check_max_session_duration(request_rec *r, oidc_cfg *cfg,
session_rec *session) {
const char *s_session_expires = NULL;
apr_time_t session_expires;
/* get the session expiry from the session data */
oidc_session_get(r, session, OIDC_SESSION_EXPIRES_SESSION_KEY,
&s_session_expires);
/* convert the string to a timestamp */
sscanf(s_session_expires, "%" APR_TIME_T_FMT, &session_expires);
/* 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_authenticate_user(r, cfg, NULL,
oidc_get_current_url(r, cfg), NULL,
NULL, NULL, NULL);
}
/* log message about max session duration */
oidc_log_session_expires(r, session_expires);
return OK;
}
/*
* 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,
session_rec *session) {
oidc_debug(r, "enter");
/* get a handle to the directory config */
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
/* check if the maximum session duration was exceeded */
int rc = oidc_check_max_session_duration(r, cfg, session);
if (rc != OK)
return rc;
/*
* 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
*/
if (cfg->scrub_request_headers != 0) {
/* scrub all headers starting with OIDC_ first */
oidc_scrub_request_headers(r, OIDC_DEFAULT_HEADER_PREFIX,
dir_cfg->authn_header);
/*
* 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(cfg->claim_prefix, OIDC_DEFAULT_HEADER_PREFIX)
!= cfg->claim_prefix)) {
oidc_scrub_request_headers(r, cfg->claim_prefix, NULL);
}
}
/* set the user authentication HTTP header if set and required */
if ((r->user != NULL) && (dir_cfg->authn_header != NULL)) {
oidc_debug(r, "setting authn header (%s) to: %s", dir_cfg->authn_header,
r->user);
apr_table_set(r->headers_in, dir_cfg->authn_header, r->user);
}
/* set the claims in the app headers + request state */
if (oidc_set_app_claims(r, cfg, session, OIDC_CLAIMS_SESSION_KEY) == FALSE)
return HTTP_INTERNAL_SERVER_ERROR;
if ((cfg->pass_idtoken_as & OIDC_PASS_IDTOKEN_AS_CLAIMS)) {
/* set the id_token in the app headers + request state */
if (oidc_set_app_claims(r, cfg, session,
OIDC_IDTOKEN_CLAIMS_SESSION_KEY) == FALSE)
return HTTP_INTERNAL_SERVER_ERROR;
}
if ((cfg->pass_idtoken_as & OIDC_PASS_IDTOKEN_AS_PAYLOAD)) {
const char *s_id_token = NULL;
/* get the string-encoded JSON object from the session */
oidc_session_get(r, session, OIDC_IDTOKEN_CLAIMS_SESSION_KEY,
&s_id_token);
/* pass it to the app in a header or environment variable */
oidc_util_set_app_info(r, "id_token_payload", s_id_token,
OIDC_DEFAULT_HEADER_PREFIX, dir_cfg->pass_info_in_headers,
dir_cfg->pass_info_in_env_vars);
}
if ((cfg->pass_idtoken_as & OIDC_PASS_IDTOKEN_AS_SERIALIZED)) {
const char *s_id_token = NULL;
/* get the compact serialized JWT from the session */
oidc_session_get(r, session, OIDC_IDTOKEN_SESSION_KEY, &s_id_token);
/* pass it to the app in a header or environment variable */
oidc_util_set_app_info(r, "id_token", s_id_token,
OIDC_DEFAULT_HEADER_PREFIX, dir_cfg->pass_info_in_headers,
dir_cfg->pass_info_in_env_vars);
}
/* set the access_token in the app headers */
const char *access_token = NULL;
oidc_session_get(r, session, OIDC_ACCESSTOKEN_SESSION_KEY, &access_token);
if (access_token != NULL) {
/* pass it to the app in a header or environment variable */
oidc_util_set_app_info(r, "access_token", access_token,
OIDC_DEFAULT_HEADER_PREFIX, dir_cfg->pass_info_in_headers,
dir_cfg->pass_info_in_env_vars);
}
/* set the expiry timestamp in the app headers */
const char *access_token_expires = NULL;
oidc_session_get(r, session, OIDC_ACCESSTOKEN_EXPIRES_SESSION_KEY,
&access_token_expires);
if (access_token_expires != NULL) {
/* pass it to the app in a header or environment variable */
oidc_util_set_app_info(r, "access_token_expires", access_token_expires,
OIDC_DEFAULT_HEADER_PREFIX, dir_cfg->pass_info_in_headers,
dir_cfg->pass_info_in_env_vars);
}
/*
* 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;
oidc_session_save(r, session);
}
/* 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,
json_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,
json_string_value(json_object_get(*proto_state, "issuer")), FALSE);
return (*provider != NULL);
}
/*
* restore POST parameters on original_url from HTML5 local storage
*/
static int oidc_restore_preserved_post(request_rec *r, const char *original_url) {
const char *java_script =
apr_psprintf(r->pool,
" \n", original_url);
const char *html_body = "
Restoring...
\n"
" \n";
return oidc_util_html_send(r, "Restoring...", java_script, "postOnLoad",
html_body, DONE);
}
/*
* 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", c->redirect_uri);
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,
json_t *proto_state, const char *error, const char *error_description) {
const char *prompt =
json_object_get(proto_state, "prompt") ?
apr_pstrdup(r->pool,
json_string_value(
json_object_get(proto_state, "prompt"))) :
NULL;
json_decref(proto_state);
if ((prompt != NULL) && (apr_strnatcmp(prompt, "none") == 0)) {
return oidc_session_redirect_parent_window_to_logout(r, c);
}
return oidc_util_html_send_error(r,
apr_psprintf(r->pool,
"The OpenID Connect Provider returned an error: %s", error),
error_description, DONE);
}
/*
* store the access token expiry timestamp in the session, based on the expires_in
*/
static void oidc_store_access_token_expiry(request_rec *r, session_rec *session,
int expires_in) {
if (expires_in != -1) {
oidc_session_set(r, session, OIDC_ACCESSTOKEN_EXPIRES_SESSION_KEY,
apr_psprintf(r->pool, "%" APR_TIME_T_FMT,
apr_time_sec(apr_time_now()) + expires_in));
}
}
/*
* set the unique user identifier that will be propagated in the Apache r->user and REMOTE_USER variables
*/
static apr_byte_t oidc_get_remote_user(request_rec *r, oidc_cfg *c,
oidc_provider_t *provider, apr_jwt_t *jwt, char **user) {
char *issuer = provider->issuer;
char *claim_name = apr_pstrdup(r->pool, c->remote_user_claim.claim_name);
int n = strlen(claim_name);
int post_fix_with_issuer = (claim_name[n - 1] == '@');
if (post_fix_with_issuer) {
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 */
char *username = NULL;
if (apr_jwt_get_string(r->pool, jwt->payload.value.json, claim_name, TRUE,
&username, NULL) == FALSE) {
oidc_error(r,
"OIDCRemoteUserClaim is set to \"%s\", but the id_token JSON payload did not contain a \"%s\" string",
c->remote_user_claim.claim_name, claim_name);
*user = NULL;
return FALSE;
}
/* set the unique username in the session (will propagate to r->user/REMOTE_USER) */
*user = post_fix_with_issuer ?
apr_psprintf(r->pool, "%s@%s", username, issuer) :
apr_pstrdup(r->pool, username);
if (c->remote_user_claim.reg_exp != NULL) {
char *error_str = NULL;
if (oidc_util_regexp_first_match(r->pool, *user,
c->remote_user_claim.reg_exp, user, &error_str) == FALSE) {
oidc_error(r, "oidc_util_regexp_first_match failed: %s", error_str);
*user = NULL;
return FALSE;
}
}
oidc_debug(r, "set user to \"%s\"", *user);
return TRUE;
}
/*
* store resolved information in the session
*/
static void oidc_save_in_session(request_rec *r, oidc_cfg *c,
session_rec *session, oidc_provider_t *provider, const char *remoteUser,
const char *id_token, apr_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) {
/* 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(r, session, OIDC_IDTOKEN_CLAIMS_SESSION_KEY,
id_token_jwt->payload.value.str);
/* store the compact serialized representation of the id_token for later reference */
oidc_session_set(r, session, OIDC_IDTOKEN_SESSION_KEY, id_token);
/* store the issuer in the session (at least needed for session mgmt and token refresh */
oidc_session_set(r, session, OIDC_ISSUER_SESSION_KEY, provider->issuer);
/* store the state and original URL in the session for handling browser-back more elegantly */
oidc_session_set(r, session, OIDC_REQUEST_STATE_SESSION_KEY, state);
oidc_session_set(r, session, OIDC_REQUEST_ORIGINAL_URL, original_url);
if ((session_state != NULL) && (provider->check_session_iframe != NULL)) {
/* store the session state and required parameters session management */
oidc_session_set(r, session, OIDC_SESSION_STATE_SESSION_KEY,
session_state);
oidc_session_set(r, session, OIDC_CHECK_IFRAME_SESSION_KEY,
provider->check_session_iframe);
oidc_session_set(r, session, OIDC_CLIENTID_SESSION_KEY,
provider->client_id);
}
if (provider->end_session_endpoint != NULL)
oidc_session_set(r, session, OIDC_LOGOUT_ENDPOINT_SESSION_KEY,
provider->end_session_endpoint);
/* 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(r, session, OIDC_CLAIMS_SESSION_KEY, claims);
}
/* see if we have an access_token */
if (access_token != NULL) {
/* store the access_token in the session context */
oidc_session_set(r, session, OIDC_ACCESSTOKEN_SESSION_KEY,
access_token);
/* store the associated expires_in value */
oidc_store_access_token_expiry(r, session, expires_in);
}
/* see if we have a refresh_token */
if (refresh_token != NULL) {
/* store the refresh_token in the session context */
oidc_session_set(r, session, OIDC_REFRESHTOKEN_SESSION_KEY,
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(r, session, OIDC_SESSION_EXPIRES_SESSION_KEY,
apr_psprintf(r->pool, "%" APR_TIME_T_FMT, session_expires));
/* log message about max session duration */
oidc_log_session_expires(r, session_expires);
/* store the session */
oidc_session_save(r, session);
}
/*
* 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,
json_t *proto_state, oidc_provider_t *provider, apr_table_t *params,
const char *response_mode, apr_jwt_t **jwt) {
apr_byte_t rc = FALSE;
const char *requested_response_type = json_string_value(
json_object_get(proto_state, "response_type"));
/* handle the requested response type/mode */
if (oidc_util_spaced_string_equals(r->pool, requested_response_type,
"code id_token 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,
"code id_token")) {
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,
"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,
"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,
"id_token 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,
"id_token")) {
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)) {
apr_jwt_destroy(*jwt);
*jwt = NULL;
}
return rc;
}
/*
* resolves claims from the user info endpoint and returns the stringified response
*/
static const char *oidc_resolve_claims_from_user_info_endpoint(request_rec *r,
oidc_cfg *c, oidc_provider_t *provider, apr_table_t *params) {
const char *result = NULL;
if (provider->userinfo_endpoint_url == NULL) {
oidc_debug(r,
"not resolving user info claims because userinfo_endpoint is not set");
} else if (apr_table_get(params, "access_token") == NULL) {
oidc_debug(r,
"not resolving user info claims because access_token is not provided");
} else if (oidc_proto_resolve_userinfo(r, c, provider,
apr_table_get(params, "access_token"), &result) == FALSE) {
oidc_debug(r,
"resolving user info claims failed, nothing will be stored in the session");
result = NULL;
}
return result;
}
/* handle the browser back on an authorization response */
static apr_byte_t oidc_handle_browser_back(request_rec *r, const char *r_state,
session_rec *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) {
oidc_session_get(r, session, OIDC_REQUEST_STATE_SESSION_KEY, &s_state);
oidc_session_get(r, session, OIDC_REQUEST_ORIGINAL_URL, &o_url);
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 */
apr_table_add(r->headers_out, "Location", 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,
session_rec *session, apr_table_t *params, const char *response_mode) {
oidc_debug(r, "enter, response_mode=%s", response_mode);
oidc_provider_t *provider = NULL;
json_t *proto_state = NULL;
apr_jwt_t *jwt = NULL;
/* see if this response came from a browser-back event */
if (oidc_handle_browser_back(r, apr_table_get(params, "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, "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);
apr_table_add(r->headers_out, "Location", 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, "error") != NULL)
return oidc_authorization_response_error(r, c, proto_state,
apr_table_get(params, "error"),
apr_table_get(params, "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, "expires_in"));
/*
* 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_resolve_claims_from_user_info_endpoint(r, c,
provider, params);
/* restore the original protected URL that the user was trying to access */
const char *original_url = apr_pstrdup(r->pool,
json_string_value(json_object_get(proto_state, "original_url")));
const char *original_method = apr_pstrdup(r->pool,
json_string_value(json_object_get(proto_state, "original_method")));
/* set the user */
if (oidc_get_remote_user(r, c, provider, jwt, &r->user) == TRUE) {
/* session management: if the user in the new response is not equal to the old one, error out */
if ((json_object_get(proto_state, "prompt") != NULL)
&& (apr_strnatcmp(
json_string_value(
json_object_get(proto_state, "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");
apr_jwt_destroy(jwt);
return oidc_authorization_response_error(r, c, proto_state,
"User changed!", NULL);
}
}
/* store resolved information in the session */
oidc_save_in_session(r, c, session, provider, r->user,
apr_table_get(params, "id_token"), jwt, claims,
apr_table_get(params, "access_token"), expires_in,
apr_table_get(params, "refresh_token"),
apr_table_get(params, "session_state"),
apr_table_get(params, "state"), original_url);
} 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 */
json_decref(proto_state);
apr_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;
/* check whether form post data was preserved; if so restore it */
if (apr_strnatcmp(original_method, "form_post") == 0) {
return oidc_restore_preserved_post(r, original_url);
}
/* log the successful response */
oidc_debug(r, "session created and stored, redirecting to original URL: %s",
original_url);
/* now we've authenticated the user so go back to the URL that he originally tried to access */
apr_table_add(r->headers_out, "Location", 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,
session_rec *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_strnatcmp(apr_table_get(params, "response_mode"),
"fragment") == 0))) {
return oidc_util_html_send_error(r, "mod_auth_openidc",
"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, "response_mode");
/* do the actual implicit work */
return oidc_handle_authorization_response(r, c, session, params,
response_mode ? 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, session_rec *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, "query");
}
/*
* present the user with an OP selection screen
*/
static int oidc_discovery(request_rec *r, oidc_cfg *cfg) {
oidc_debug(r, "enter");
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
/* obtain the URL we're currently accessing, to be stored in the state/session */
char *current_url = oidc_get_current_url(r, cfg);
/* generate CSRF token */
char *csrf = NULL;
if (oidc_proto_generate_nonce(r, &csrf, 8) == FALSE)
return HTTP_INTERNAL_SERVER_ERROR;
/* see if there's an external discovery page configured */
if (dir_cfg->discover_url != NULL) {
/* yes, assemble the parameters for external discovery */
char *url = apr_psprintf(r->pool, "%s%s%s=%s&%s=%s&%s=%s",
dir_cfg->discover_url,
strchr(dir_cfg->discover_url, '?') != NULL ? "&" : "?",
OIDC_DISC_RT_PARAM, oidc_util_escape_string(r, current_url),
OIDC_DISC_CB_PARAM,
oidc_util_escape_string(r, cfg->redirect_uri),
OIDC_CSRF_NAME, oidc_util_escape_string(r, csrf));
/* 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);
/* do the actual redirect to an external discovery page */
apr_table_add(r->headers_out, "Location", 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, "mod_auth_openidc",
"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 *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%s
\n", s,
cfg->redirect_uri, OIDC_DISC_OP_PARAM,
oidc_util_escape_string(r, issuer), OIDC_DISC_RT_PARAM,
oidc_util_escape_string(r, current_url), OIDC_CSRF_NAME, csrf,
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);
/* now send the HTML contents to the user agent */
return oidc_util_html_send(r, "OpenID Connect Provider Discovery",
"", NULL,
s, HTTP_UNAUTHORIZED);
}
/*
* 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) {
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 *method = "get";
// TODO: restore method from discovery too or generate state before doing discover (and losing startSSO effect)
/*
const char *content_type = apr_table_get(r->headers_in, "Content-Type");
char *method =
((r->method_number == M_POST)
&& (apr_strnatcmp(content_type,
"application/x-www-form-urlencoded") == 0)) ?
"form_post" : "redirect";
*/
/* create the state between request/response */
json_t *proto_state = json_object();
json_object_set_new(proto_state, "original_url", json_string(original_url));
json_object_set_new(proto_state, "original_method", json_string(method));
json_object_set_new(proto_state, "issuer", json_string(provider->issuer));
json_object_set_new(proto_state, "response_type",
json_string(provider->response_type));
json_object_set_new(proto_state, "nonce", json_string(nonce));
json_object_set_new(proto_state, "timestamp",
json_integer(apr_time_sec(apr_time_now())));
if (provider->response_mode)
json_object_set_new(proto_state, "response_mode",
json_string(provider->response_mode));
if (prompt)
json_object_set_new(proto_state, "prompt", json_string(prompt));
/* 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 */
oidc_authorization_request_set_cookie(r, c, state, proto_state);
/*
* 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, c->redirect_uri, &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 {
char *p = strstr(o_uri.hostname, c->cookie_domain);
if ((p == NULL) || (apr_strnatcmp(c->cookie_domain, p) != 0)) {
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,
c->redirect_uri, state, proto_state, id_token_hint,
auth_request_params);
}
/*
* 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);
}
/*
* 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_t r_uri;
apr_uri_parse(r->pool, target_link_uri, &o_uri);
apr_uri_parse(r->pool, cfg->redirect_uri, &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 */
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
if (dir_cfg->cookie_path != NULL) {
char *p = strstr(o_uri.path, dir_cfg->cookie_path);
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(dir_cfg->cookie_path)) {
int n = strlen(dir_cfg->cookie_path);
if (dir_cfg->cookie_path[n - 1] == '/')
n--;
if (o_uri.path[n] != '/') {
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;
oidc_provider_t *provider = NULL;
oidc_util_get_request_parameter(r, OIDC_DISC_OP_PARAM, &issuer);
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_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);
/* 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\"",
issuer, target_link_uri, login_hint);
if (issuer == NULL) {
return oidc_util_html_send_error(r, "mod_auth_openidc",
"Wherever you came from, it sent you here with the wrong parameters...",
HTTP_INTERNAL_SERVER_ERROR);
}
if (target_link_uri == NULL) {
if (c->default_sso_url == NULL) {
return oidc_util_html_send_error(r, "mod_auth_openidc",
"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, "mod_auth_openidc",
"\"target_link_uri\" parameter does not match configuration settings, aborting to prevent an open redirect.",
HTTP_UNAUTHORIZED);
}
/* find out if the user entered an account name or selected an OP manually */
if (strstr(issuer, "@") != NULL) {
if (login_hint == NULL) {
login_hint = apr_pstrdup(r->pool, issuer);
//char *p = strstr(issuer, "@");
//*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, "mod_auth_openidc",
"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] == '/')
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);
}
/* something went wrong */
return oidc_util_html_send_error(r, "mod_auth_openidc",
"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_get_style_logout(const char *logout_param_value) {
return ((logout_param_value != NULL) && (apr_strnatcmp(logout_param_value,
OIDC_GET_STYLE_LOGOUT_PARAM_VALUE) == 0));
}
/*
* handle a local logout
*/
static int oidc_handle_logout_request(request_rec *r, oidc_cfg *c,
session_rec *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_get_style_logout(url)) {
/* set recommended cache control headers */
apr_table_add(r->err_headers_out, "Cache-Control",
"no-cache, no-store");
apr_table_add(r->err_headers_out, "Pragma", "no-cache");
apr_table_add(r->err_headers_out, "P3P", "CAO PSA OUR");
apr_table_add(r->err_headers_out, "Expires", "0");
apr_table_add(r->err_headers_out, "X-Frame-Options", "DENY");
/* see if this is PF-PA style logout in which case we return a transparent pixel */
const char *accept = apr_table_get(r->headers_in, "Accept");
if ((accept) && strstr(accept, "image/png")) {
return oidc_util_http_send(r,
(const char *) &oidc_transparent_pixel,
sizeof(oidc_transparent_pixel), "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 */
apr_table_add(r->headers_out, "Location", url);
return HTTP_MOVED_TEMPORARILY;
}
/*
* perform (single) logout
*/
static int oidc_handle_logout(request_rec *r, oidc_cfg *c, session_rec *session) {
/* pickup the command or URL where the user wants to go after logout */
char *url = NULL;
oidc_util_get_request_parameter(r, "logout", &url);
oidc_debug(r, "enter (url=%s)", url);
if (oidc_is_get_style_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, 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, url, 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, url, error_description,
HTTP_INTERNAL_SERVER_ERROR);
}
}
const char *end_session_endpoint = NULL;
oidc_session_get(r, session, OIDC_LOGOUT_ENDPOINT_SESSION_KEY,
&end_session_endpoint);
if (end_session_endpoint != NULL) {
const char *id_token_hint = NULL;
oidc_session_get(r, session, OIDC_IDTOKEN_SESSION_KEY, &id_token_hint);
char *logout_request = apr_psprintf(r->pool, "%s%s",
end_session_endpoint,
strchr(end_session_endpoint, '?') != NULL ? "&" : "?");
logout_request = apr_psprintf(r->pool, "%sid_token_hint=%s",
logout_request, oidc_util_escape_string(r, id_token_hint));
if (url != NULL) {
logout_request = apr_psprintf(r->pool,
"%s&post_logout_redirect_uri=%s", logout_request,
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, "jwks", &jwks_type);
char *jwks = apr_pstrdup(r->pool, "{ \"keys\" : [");
apr_hash_index_t *hi = NULL;
apr_byte_t first = TRUE;
apr_jwt_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;
apr_jwk_t *jwk = NULL;
char *s_json = NULL;
apr_hash_this(hi, (const void**) &s_kid, NULL, (void**) &jwk);
if (apr_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 apr_jwk_to_json: %s",
apr_jwt_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), "application/json", DONE);
}
static int oidc_handle_session_management_iframe_op(request_rec *r, oidc_cfg *c,
session_rec *session, const char *check_session_iframe) {
oidc_debug(r, "enter");
if (check_session_iframe == NULL) {
oidc_debug(r, "no check_session_iframe configured for current OP");
return DONE;
}
apr_table_add(r->headers_out, "Location", check_session_iframe);
return HTTP_MOVED_TEMPORARILY;
}
static int oidc_handle_session_management_iframe_rp(request_rec *r, oidc_cfg *c,
session_rec *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 = NULL;
oidc_session_get(r, session, OIDC_SESSION_STATE_SESSION_KEY,
&session_state);
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";
java_script = apr_psprintf(r->pool, java_script, origin, client_id,
session_state, op_iframe_id, s_poll_interval, c->redirect_uri,
c->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,
session_rec *session) {
char *cmd = NULL;
const char *issuer = NULL, *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, "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) {
oidc_session_get(r, session, OIDC_CHECK_IFRAME_SESSION_KEY,
&check_session_iframe);
if (check_session_iframe != NULL) {
return oidc_handle_session_management_iframe_op(r, c, session,
check_session_iframe);
}
return DONE;
}
/* see if this is a request for the RP iframe */
if (apr_strnatcmp("iframe_rp", cmd) == 0) {
oidc_session_get(r, session, OIDC_CLIENTID_SESSION_KEY, &client_id);
oidc_session_get(r, session, OIDC_CHECK_IFRAME_SESSION_KEY,
&check_session_iframe);
if ((client_id != NULL) && (check_session_iframe != NULL)) {
return oidc_handle_session_management_iframe_rp(r, c, session,
client_id, check_session_iframe);
}
return DONE;
}
/* see if this is a request check the login state with the OP */
if (apr_strnatcmp("check", cmd) == 0) {
oidc_session_get(r, session, OIDC_IDTOKEN_SESSION_KEY, &id_token_hint);
oidc_session_get(r, session, OIDC_ISSUER_SESSION_KEY, &issuer);
if (issuer != NULL)
provider = oidc_get_provider_for_issuer(r, c, issuer, FALSE);
if ((id_token_hint != NULL) && (provider != NULL)) {
return oidc_authenticate_user(r, c, provider,
apr_psprintf(r->pool, "%s?session=iframe_rp",
c->redirect_uri), NULL, id_token_hint, "none", NULL);
}
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,
session_rec *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, "refresh", &return_to);
oidc_util_get_request_parameter(r, "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;
}
char *s_access_token = NULL;
oidc_session_get(r, session, OIDC_ACCESSTOKEN_SESSION_KEY,
(const char **) &s_access_token);
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;
}
s_access_token = NULL;
/* get the refresh token that was stored in the session */
const char *refresh_token = NULL;
oidc_session_get(r, session, OIDC_REFRESHTOKEN_SESSION_KEY, &refresh_token);
if (refresh_token == NULL) {
oidc_warn(r,
"refresh token request handler called but no refresh_token was found in the session");
error_code = "no_refresh_token_exists";
goto end;
}
/* get a handle to the provider configuration */
const char *issuer = NULL;
oidc_provider_t *provider = NULL;
oidc_session_get(r, session, OIDC_ISSUER_SESSION_KEY, &issuer);
if (issuer == NULL) {
oidc_error(r, "session corrupted: no issuer found in session");
error_code = "session_corruption";
goto end;
}
provider = oidc_get_provider_for_issuer(r, c, issuer, FALSE);
if (provider == NULL) {
oidc_error(r, "session corrupted: no provider found for issuer: %s",
issuer);
error_code = "session_corruption";
goto end;
}
/* elements returned in the refresh response */
char *s_id_token = NULL;
int expires_in = -1;
char *s_token_type = 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");
error_code = "refresh_failed";
goto end;
}
/* store the new access_token in the session and discard the old one */
oidc_session_set(r, session, OIDC_ACCESSTOKEN_SESSION_KEY, s_access_token);
oidc_store_access_token_expiry(r, session, expires_in);
/* 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(r, session, OIDC_REFRESHTOKEN_SESSION_KEY,
s_refresh_token);
/* store the session */
oidc_session_save(r, session);
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, '?') ? "&" : "?",
oidc_util_escape_string(r, error_code));
/* add the redirect location header */
apr_table_add(r->headers_out, "Location", return_to);
return HTTP_MOVED_TEMPORARILY;
}
/*
* handle all requests to the redirect_uri
*/
int oidc_handle_redirect_uri_request(request_rec *r, oidc_cfg *c,
session_rec *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, "logout")) {
/* handle logout */
return oidc_handle_logout(r, c, session);
} else if (oidc_util_request_has_parameter(r, "jwks")) {
/* handle JWKs request */
return oidc_handle_jwks(r, c);
} else if (oidc_util_request_has_parameter(r, "session")) {
/* handle session management request */
return oidc_handle_session_management(r, c, session);
} else if (oidc_util_request_has_parameter(r, "refresh")) {
/* handle refresh token request */
return oidc_handle_refresh_token_request(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, "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);
}
/* something went wrong */
return oidc_util_html_send_error(r, "mod_auth_openidc",
apr_psprintf(r->pool,
"The OpenID Connect callback URL received an invalid request: %s",
r->args), HTTP_INTERNAL_SERVER_ERROR);
}
/*
* main routine: handle OpenID Connect authentication
*/
static int oidc_check_userid_openidc(request_rec *r, oidc_cfg *c) {
/* check if this is a sub-request or an initial request */
if (ap_is_initial_req(r)) {
/* load the session from the request state; this will be a new "empty" session if no state exists */
session_rec *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, c->redirect_uri)) {
/* handle request to the redirect_uri */
return oidc_handle_redirect_uri_request(r, c, session);
/* initial request to non-redirect URI, check if we have an existing session */
} else if (session->remote_user != NULL) {
/* set the user in the main request for further (incl. sub-request) processing */
r->user = (char *) session->remote_user;
/* this is initial request and we already have a session */
return oidc_handle_existing_session(r, c, 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);
return OK;
}
/*
* else: not initial request, but we could not find a session, so:
* just hit the default flow for unauthenticated users
*/
}
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
/* find out which action we need to take when encountering an unauthenticated request */
switch (dir_cfg->unauth_action) {
case RETURN401:
return HTTP_UNAUTHORIZED;
case PASS:
return OK;
case AUTHENTICATE:
break;
}
/* else: no session (regardless of whether it is main or sub-request), go and authenticate the user */
return oidc_authenticate_user(r, c, NULL, oidc_get_current_url(r, c), NULL,
NULL, NULL, NULL);
}
/*
* 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), "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), "oauth20") == 0)
return oidc_oauth_check_userid(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_CLAIMS_SESSION_KEY);
const char *s_id_token = oidc_request_state_get(r,
OIDC_IDTOKEN_CLAIMS_SESSION_KEY);
json_error_t json_error;
if (s_claims != NULL) {
*claims = json_loads(s_claims, 0, &json_error);
if (*claims == NULL) {
oidc_error(r, "could not restore claims from request state: %s",
json_error.text);
}
}
if (s_id_token != NULL) {
*id_token = json_loads(s_id_token, 0, &json_error);
if (*id_token == NULL) {
oidc_error(r, "could not restore id_token from request state: %s",
json_error.text);
}
}
}
#if MODULE_MAGIC_NUMBER_MAJOR >= 20100714
/*
* 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) {
/* 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);
/* dispatch to the >=2.4 specific authz routine */
authz_status rc = oidc_authz_worker24(r, claims ? claims : id_token, require_args);
/* cleanup */
if (claims) json_decref(claims);
if (id_token) json_decref(id_token);
return rc;
}
#else
/*
* 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) {
/* 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(id_token, claims);
/* dispatch to the <2.4 specific authz routine */
int rc = oidc_authz_worker(r, claims ? claims : id_token, reqs,
reqs_arr->nelts);
/* cleanup */
if (claims)
json_decref(claims);
if (id_token)
json_decref(id_token);
return rc;
}
#endif
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-1.8.5/src/cache/file.c 0000644 0001750 0001750 00000034154 12532644156 020152 0 ustar zandbelt zandbelt /*
* 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-2015 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 - hzandbelt@pingidentity.com
*/
#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-connect-"
/* 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,
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);
/* log a successful cache hit */
oidc_debug(r,
"cache hit for key \"%s\" (%" APR_SIZE_T_FMT " bytes, expiring in: %" APR_TIME_T_FMT ")",
key, info.len, apr_time_sec(info.expire - apr_time_now()));
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);
} 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] == '.')
|| (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_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 */
if ((rc = oidc_cache_file_write(r, path, fd, (void *) value, info.len))
!= APR_SUCCESS)
return FALSE;
/* unlock and close the written file */
apr_file_unlock(fd);
apr_file_close(fd);
/* log our success */
oidc_debug(r,
"set entry for key \"%s\" (%" APR_SIZE_T_FMT " bytes, expires in: %" APR_TIME_T_FMT ")",
key, info.len, apr_time_sec(expiry - apr_time_now()));
return TRUE;
}
oidc_cache_t oidc_cache_file = {
NULL,
oidc_cache_file_post_config,
NULL,
oidc_cache_file_get,
oidc_cache_file_set,
NULL
};
mod_auth_openidc-1.8.5/src/cache/memcache.c 0000644 0001750 0001750 00000020673 12544424377 021002 0 ustar zandbelt zandbelt /*
* 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-2015 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 - hzandbelt@pingidentity.com
*/
#include "apr_general.h"
#include "apr_strings.h"
#include "apr_hash.h"
#include "apr_memcache.h"
#include
#include
#include
#include "../mod_auth_openidc.h"
// TODO: proper memcache error reporting (server unreachable etc.)
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, " ", &tok);
while (split) {
nservers++;
split = apr_strtok(NULL, " ", &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, " ", &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, " ", &tok);
}
return OK;
}
/*
* 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);
}
/*
* 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_debug(r, "enter, section=\"%s\", key=\"%s\"", section, key);
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) {
oidc_debug(r, "apr_memcache_getp: key %s not found in cache",
oidc_cache_memcache_get_key(r->pool, section, key));
return FALSE;
} else if (rv != APR_SUCCESS) {
// TODO: error strings ?
oidc_error(r, "apr_memcache_getp returned an error; perhaps your memcache server is not available?");
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_debug(r, "enter, section=\"%s\", key=\"%s\"", section, key);
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));
} else if (rv != APR_SUCCESS) {
// TODO: error strings ?
oidc_error(r,
"apr_memcache_delete returned an error; perhaps your memcache server is not available?");
}
} 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);
// TODO: error strings ?
if (rv != APR_SUCCESS) {
oidc_error(r, "apr_memcache_set returned an error; perhaps your memcache server is not available?");
}
}
return (rv == APR_SUCCESS);
}
oidc_cache_t oidc_cache_memcache = {
oidc_cache_memcache_cfg_create,
oidc_cache_memcache_post_config,
NULL,
oidc_cache_memcache_get,
oidc_cache_memcache_set,
NULL
};
mod_auth_openidc-1.8.5/src/cache/shm.c 0000644 0001750 0001750 00000024666 12532644156 020031 0 ustar zandbelt zandbelt /*
* 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-2015 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 - hzandbelt@pingidentity.com
*/
#include
#include
#include
#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_debug(r, "enter, section=\"%s\", key=\"%s\"", section, key);
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 (*value == NULL) ? FALSE : 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_debug(r, "enter, section=\"%s\", key=\"%s\", value size=%llu", section,
key, value ? (unsigned long long )strlen(value) : 0);
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) {
rv = apr_shm_destroy(context->shm);
oidc_sdebug(s, "apr_shm_destroy returned: %d", rv);
context->shm = NULL;
}
oidc_cache_mutex_destroy(s, context->mutex);
return rv;
}
oidc_cache_t oidc_cache_shm = {
oidc_cache_shm_cfg_create,
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-1.8.5/src/cache/lock.c 0000644 0001750 0001750 00000011741 12545545114 020156 0 ustar zandbelt zandbelt /*
* 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-2015 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.
*
* global lock implementation
*
* @Author: Hans Zandbelt - hzandbelt@pingidentity.com
*/
#ifndef WIN32
#include
#endif
#include "apr_general.h"
#include
#include
#include
#ifdef AP_NEED_SET_MUTEX_PERMS
#include "unixd.h"
#endif
#include "../mod_auth_openidc.h"
/* 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;
return ctx;
}
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",
m->mutex_filename);
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 ");
return FALSE;
}
#endif
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",
m->mutex_filename);
}
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 [%d]", rv);
return FALSE;
}
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 [%d]", rv);
return FALSE;
}
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) {
rv = apr_global_mutex_destroy(m->mutex);
if (rv != APR_SUCCESS) {
oidc_swarn(s, "apr_global_mutex_destroy failed: [%d]", rv);
}
m->mutex = NULL;
}
return rv;
}
mod_auth_openidc-1.8.5/src/oauth.c 0000644 0001750 0001750 00000042742 12544514411 017303 0 ustar zandbelt zandbelt /*
* 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-2015 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 - hzandbelt@pingidentity.com
*/
#include
#include
#include
#include
#include
#include "mod_auth_openidc.h"
extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
/*
* 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, const char **response) {
/* get a handle to the directory config */
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
/* 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);
/* see if we want to do basic auth or post-param-based auth */
const char *basic_auth = NULL;
if ((c->oauth.client_id != NULL) && (c->oauth.client_secret != NULL)) {
if ((c->oauth.introspection_endpoint_auth != NULL)
&& (apr_strnatcmp(c->oauth.introspection_endpoint_auth,
"client_secret_post") == 0)) {
apr_table_addn(params, "client_id", c->oauth.client_id);
apr_table_addn(params, "client_secret", c->oauth.client_secret);
} else {
basic_auth = apr_psprintf(r->pool, "%s:%s", c->oauth.client_id,
c->oauth.client_secret);
}
}
/* call the endpoint with the constructed parameter set and return the resulting response */
return apr_strnatcmp(c->oauth.introspection_endpoint_method, "GET") == 0 ?
oidc_util_http_get(r, c->oauth.introspection_endpoint_url, params,
basic_auth, NULL, c->oauth.ssl_validate_server, response,
c->http_timeout_long, c->outgoing_proxy,
dir_cfg->pass_cookies) :
oidc_util_http_post_form(r, c->oauth.introspection_endpoint_url,
params, basic_auth, NULL, c->oauth.ssl_validate_server,
response, c->http_timeout_long, c->outgoing_proxy,
dir_cfg->pass_cookies);
}
/*
* get the authorization header that should contain a bearer token
*/
static apr_byte_t oidc_oauth_get_bearer_token(request_rec *r,
const char **access_token) {
/* get the authorization header */
const char *auth_line;
auth_line = apr_table_get(r->headers_in, "Authorization");
if (!auth_line) {
oidc_debug(r, "no authorization header found");
return FALSE;
}
/* look for the Bearer keyword */
if (apr_strnatcasecmp(ap_getword(r->pool, &auth_line, ' '), "Bearer")) {
oidc_error(r, "client used unsupported authentication scheme: %s",
r->uri);
return FALSE;
}
/* 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);
/* 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;
}
/*
* 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;
const char *json = NULL;
/* see if we've got the claims for this access_token cached already */
c->cache->get(r, OIDC_CACHE_SECTION_ACCESS_TOKEN, access_token, &json);
if (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, &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, json, &result) == FALSE)
return FALSE;
json_t *active = json_object_get(result, "active");
apr_time_t cache_until;
if (active != NULL) {
if (json_is_boolean(active)) {
if (!json_is_true(active)) {
oidc_debug(r,
"\"active\" boolean object with value \"false\" found in response JSON object");
json_decref(result);
return FALSE;
}
} else if (json_is_string(active)) {
if (apr_strnatcasecmp(json_string_value(active), "true") != 0) {
oidc_debug(r,
"\"active\" string object with value that is not equal to \"true\" found in response JSON object: %s",
json_string_value(active));
json_decref(result);
return FALSE;
}
} else {
oidc_debug(r,
"no \"active\" boolean or string object found in response JSON object");
json_decref(result);
return FALSE;
}
if (oidc_oauth_parse_and_cache_token_expiry(r, c, result, "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 */
c->cache->set(r, OIDC_CACHE_SECTION_ACCESS_TOKEN, access_token,
json, cache_until);
} 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,
"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 */
c->cache->set(r, OIDC_CACHE_SECTION_ACCESS_TOKEN, access_token,
json, cache_until);
}
} else {
/* we got the claims for this access_token in our cache, decode it in to a JSON structure */
json_error_t json_error;
result = json_loads(json, 0, &json_error);
if (result == NULL) {
oidc_error(r, "cached JSON was corrupted: %s", json_error.text);
return FALSE;
}
}
/* return the access_token JSON object */
json_t *tkn = json_object_get(result, "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, "client_id", json_object_get(result, "client_id"));
json_object_set(tkn, "scope", json_object_get(result, "scope"));
//oidc_oauth_spaced_string_to_array(r, result, "scope", tkn, "scopes");
/* return only the pimped access_token results */
*token = json_deep_copy(tkn);
char *s_token = json_dumps(*token, 0);
*response = apr_pstrdup(r->pool, s_token);
free(s_token);
json_decref(result);
} else {
//oidc_oauth_spaced_string_to_array(r, result, "scope", result, "scopes");
/* assume spec compliant introspection */
*token = result;
*response = apr_pstrdup(r->pool, json);
}
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_oauth_set_remote_user(request_rec *r, oidc_cfg *c,
json_t *token) {
/* get the configured claim name to populate REMOTE_USER with (defaults to "Username") */
char *claim_name = apr_pstrdup(r->pool, c->oauth.remote_user_claim.claim_name);
/* get the claim value from the resolved token JSON response to use as the REMOTE_USER key */
json_t *username = json_object_get(token, claim_name);
if ((username == NULL) || (!json_is_string(username))) {
oidc_warn(r, "response JSON object did not contain a \"%s\" string",
claim_name);
return FALSE;
}
r->user = apr_pstrdup(r->pool, json_string_value(username));
if (c->oauth.remote_user_claim.reg_exp != NULL) {
char *error_str = NULL;
if (oidc_util_regexp_first_match(r->pool, r->user, c->oauth.remote_user_claim.reg_exp, &r->user, &error_str) == FALSE) {
oidc_error(r, "oidc_util_regexp_first_match failed: %s", error_str);
r->user = NULL;
return FALSE;
}
}
oidc_debug(r, "set REMOTE_USER to claim %s=%s", claim_name,
json_string_value(username));
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)
* - iat slack (OIDCIDTokenIatSlack)
*
* 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) {
apr_jwt_error_t err;
apr_jwt_t *jwt = NULL;
if (apr_jwt_parse(r->pool, access_token, &jwt,
oidc_util_merge_symmetric_key(r->pool, c->private_keys,
c->oauth.client_secret, NULL), &err) == FALSE) {
oidc_error(r, "could not parse JWT from access_token: %s",
apr_jwt_e2s(r->pool, err));
return FALSE;
}
/* validate the access token JWT, validating optional exp + iat */
if (oidc_proto_validate_jwt(r, jwt, NULL, FALSE, FALSE,
c->provider.idtoken_iat_slack) == FALSE) {
apr_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");
apr_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;
}
/*
* 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);
return OK;
}
/* check if this is a request for the public (encryption) keys */
} else if ((c->redirect_uri != NULL)
&& (oidc_util_request_matches_url(r, c->redirect_uri))) {
if (oidc_util_request_has_parameter(r, "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)
return HTTP_UNAUTHORIZED;
/* 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 HTTP_UNAUTHORIZED;
} 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 HTTP_UNAUTHORIZED;
}
/* check that we've got something back */
if (token == NULL) {
oidc_error(r, "could not resolve claims (token == NULL)");
return HTTP_UNAUTHORIZED;
}
/* 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_CLAIMS_SESSION_KEY, (const char *) s_token);
/* set the REMOTE_USER variable */
if (oidc_oauth_set_remote_user(r, c, token) == FALSE) {
oidc_error(r,
"remote user could not be set, aborting with HTTP_UNAUTHORIZED");
return HTTP_UNAUTHORIZED;
}
/* get a handle to the director config */
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
/* set the user authentication HTTP header if set and required */
if ((r->user != NULL) && (dir_cfg->authn_header != NULL)) {
oidc_debug(r, "setting authn header (%s) to: %s", dir_cfg->authn_header,
r->user);
apr_table_set(r->headers_in, dir_cfg->authn_header, r->user);
}
/* set the resolved claims in the HTTP headers for the target application */
oidc_util_set_app_infos(r, token, c->claim_prefix, c->claim_delimiter,
dir_cfg->pass_info_in_headers, dir_cfg->pass_info_in_env_vars);
/* set the access_token in the app headers */
if (access_token != NULL) {
oidc_util_set_app_info(r, "access_token", access_token,
OIDC_DEFAULT_HEADER_PREFIX, dir_cfg->pass_info_in_headers,
dir_cfg->pass_info_in_env_vars);
}
/* free JSON resources */
json_decref(token);
return OK;
}
mod_auth_openidc-1.8.5/src/proto.c 0000664 0001750 0001750 00000151650 12577725501 017341 0 ustar zandbelt zandbelt /*
* 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-2015 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 - hzandbelt@pingidentity.com
*/
#include
#include
#include
#include
#include "mod_auth_openidc.h"
#include
#include
extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
/*
* send an OpenID Connect authorization request to the specified provider preserving POST parameters using HTML5 storage
*/
int oidc_proto_authorization_request_post_preserve(request_rec *r,
const char *authorization_request) {
/* 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;
}
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_html_escape(r->pool, elts[i].key),
oidc_util_html_escape(r->pool, elts[i].val),
i < arr->nelts - 1 ? "," : "");
}
json = apr_psprintf(r->pool, "{ %s }", json);
char *java_script =
apr_psprintf(r->pool,
" \n", json, authorization_request);
return oidc_util_html_send(r, "Preserving...", java_script,
"preserveOnLoad", "Preserving...
", 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, json_t *proto_state,
const char *id_token_hint, const char *auth_request_params) {
/* log some stuff */
char *s_value = json_dumps(proto_state, JSON_ENCODE_ANY);
oidc_debug(r, "enter, issuer=%s, redirect_uri=%s, state=%s, proto_state=%s",
provider->issuer, redirect_uri, state, s_value);
free(s_value);
/* assemble the full URL as the authorization request to the OP where we want to redirect to */
char *authorization_request = apr_psprintf(r->pool, "%s%s",
provider->authorization_endpoint_url,
strchr(provider->authorization_endpoint_url, '?') != NULL ?
"&" : "?");
authorization_request = apr_psprintf(r->pool, "%sresponse_type=%s",
authorization_request,
oidc_util_escape_string(r,
json_string_value(
json_object_get(proto_state, "response_type"))));
authorization_request = apr_psprintf(r->pool, "%s&scope=%s",
authorization_request, oidc_util_escape_string(r, provider->scope));
authorization_request = apr_psprintf(r->pool, "%s&client_id=%s",
authorization_request,
oidc_util_escape_string(r, provider->client_id));
authorization_request = apr_psprintf(r->pool, "%s&state=%s",
authorization_request, oidc_util_escape_string(r, state));
authorization_request = apr_psprintf(r->pool, "%s&redirect_uri=%s",
authorization_request, oidc_util_escape_string(r, redirect_uri));
/* add the nonce if set */
if (json_object_get(proto_state, "nonce") != NULL)
authorization_request = apr_psprintf(r->pool, "%s&nonce=%s",
authorization_request,
oidc_util_escape_string(r,
json_string_value(
json_object_get(proto_state, "nonce"))));
/* add the response_mode if explicitly set */
if (json_object_get(proto_state, "response_mode") != NULL)
authorization_request = apr_psprintf(r->pool, "%s&response_mode=%s",
authorization_request,
oidc_util_escape_string(r,
json_string_value(
json_object_get(proto_state,
"response_mode"))));
/* add the login_hint if provided */
if (login_hint != NULL)
authorization_request = apr_psprintf(r->pool, "%s&login_hint=%s",
authorization_request, oidc_util_escape_string(r, login_hint));
/* add the id_token_hint if provided */
if (id_token_hint != NULL)
authorization_request = apr_psprintf(r->pool, "%s&id_token_hint=%s",
authorization_request,
oidc_util_escape_string(r, id_token_hint));
/* add the prompt setting if provided (e.g. "none" for no-GUI checks) */
if (json_object_get(proto_state, "prompt") != NULL)
authorization_request = apr_psprintf(r->pool, "%s&prompt=%s",
authorization_request,
oidc_util_escape_string(r,
json_string_value(
json_object_get(proto_state, "prompt"))));
/* add any statically configured custom authorization request parameters */
if (provider->auth_request_params != NULL) {
authorization_request = apr_psprintf(r->pool, "%s&%s",
authorization_request, provider->auth_request_params);
}
/* add any dynamically configured custom authorization request parameters */
if (auth_request_params != NULL) {
authorization_request = apr_psprintf(r->pool, "%s&%s",
authorization_request, auth_request_params);
}
/* preserve POSTed form parameters if enabled */
if (apr_strnatcmp(
json_string_value(json_object_get(proto_state, "original_method")),
"form_post") == 0)
return oidc_proto_authorization_request_post_preserve(r,
authorization_request);
/* cleanup */
json_decref(proto_state);
/* add the redirect location header */
apr_table_add(r->headers_out, "Location", authorization_request);
/* some more logging */
oidc_debug(r, "adding outgoing header: Location: %s",
authorization_request);
/* and tell Apache to return an HTTP Redirect (302) message */
return HTTP_MOVED_TEMPORARILY;
}
/*
* 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, "state")
&& (oidc_util_request_has_parameter(r, "id_token")
|| oidc_util_request_has_parameter(r, "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) {
unsigned char *nonce_bytes = apr_pcalloc(r->pool, len);
if (apr_generate_random_bytes(nonce_bytes, len) != APR_SUCCESS) {
oidc_error(r, "apr_generate_random_bytes returned an error");
return FALSE;
}
if (oidc_base64url_encode(r, nonce, (const char *) nonce_bytes, len, TRUE)
<= 0) {
oidc_error(r, "oidc_base64url_encode returned an error");
return FALSE;
}
return TRUE;
}
/*
* 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, apr_jwt_t *jwt) {
apr_jwt_error_t err;
/* see if we have this nonce cached already */
const char *replay = NULL;
cfg->cache->get(r, OIDC_CACHE_SECTION_NONCE, 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 (apr_jwt_get_string(r->pool, jwt->payload.value.json, "nonce", TRUE,
&j_nonce, &err) == FALSE) {
oidc_error(r,
"id_token JSON payload did not contain a \"nonce\" string: %s",
apr_jwt_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 */
cfg->cache->set(r, OIDC_CACHE_SECTION_NONCE, 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, apr_jwt_payload_t *id_token_payload) {
char *azp = NULL;
apr_jwt_get_string(r->pool, id_token_payload->value.json, "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 \"azp\" claim (%s) is present in the id_token, but is not equal to the configured client_id (%s)",
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, "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 \"aud\" claim value (%s) in the id_token",
provider->client_id, 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 \"aud\" claim value in the id_token is an array with more than 1 element, but \"azp\" claim is not present (a SHOULD in the spec...)");
}
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 \"aud\" claim",
provider->client_id);
return FALSE;
}
} else {
oidc_error(r,
"id_token JSON payload \"aud\" claim is not a string nor an array");
return FALSE;
}
} else {
oidc_error(r, "id_token JSON payload did not contain an \"aud\" claim");
return FALSE;
}
return TRUE;
}
/*
* validate "iat" claim in JWT
*/
static apr_byte_t oidc_proto_validate_iat(request_rec *r, apr_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 == APR_JWT_CLAIM_TIME_EMPTY) {
if (is_mandatory) {
oidc_error(r, "JWT did not contain an \"iat\" number value");
return FALSE;
}
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, apr_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 == APR_JWT_CLAIM_TIME_EMPTY) {
if (is_mandatory) {
oidc_error(r, "JWT did not contain an \"exp\" number value");
return FALSE;
}
return TRUE;
}
/* see if now is beyond the JWT expiry timestamp */
if (now > jwt->payload.exp) {
oidc_error(r,
"\"exp\" validation failure (%ld): JWT expired %ld seconds ago",
(long)jwt->payload.exp, (long)(now - jwt->payload.exp));
return FALSE;
}
return TRUE;
}
/*
* validate a JSON Web token
*/
apr_byte_t oidc_proto_validate_jwt(request_rec *r, apr_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 \"iss\" string (requested value: %s)",
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 \"iss\" value in id_token (%s)",
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, apr_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 \"sub\" string value");
return FALSE;
}
/* verify the "aud" and "azp" values */
if (oidc_proto_validate_aud_and_azp(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, apr_jwt_t *jwt,
json_t *j_jwks, apr_hash_t *result) {
apr_byte_t rc = TRUE;
apr_jwt_error_t err;
char *x5t = NULL;
apr_jwk_t *jwk = NULL;
const char *key_type = apr_jwt_signature_to_jwk_type(r->pool, jwt);
if (key_type == NULL) {
oidc_error(r, "unsupported signing algorithm in JWT header: %s",
jwt->header.alg);
return FALSE;
}
apr_jwt_get_string(r->pool, jwt->header.value.json, "x5t", FALSE, &x5t,
NULL);
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, "keys");
if ((keys == NULL) || !(json_is_array(keys))) {
oidc_error(r, "\"keys\" array element is not a JSON array");
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);
/* check that it is a JSON object */
if (!json_is_object(elem)) {
oidc_warn(r,
"\"keys\" array element is not a JSON object, skipping");
continue;
}
/* get the key type and see if it is the type that we are looking for */
json_t *kty = json_object_get(elem, "kty");
if ((!json_is_string(kty))
|| (strcmp(json_string_value(kty), key_type) != 0))
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)) {
oidc_debug(r, "no kid/x5t to match, include matching key type");
rc = apr_jwk_parse_json(r->pool, elem, &jwk, &err);
if (rc == FALSE)
oidc_error(r, "JWK parsing failed: %s",
apr_jwt_e2s(r->pool, err));
else if (jwk->kid)
apr_hash_set(result, jwk->kid, APR_HASH_KEY_STRING, jwk);
else
apr_hash_set(result, apr_psprintf(r->pool, "%d", i), APR_HASH_KEY_STRING, jwk);
continue;
}
/* we are looking for a specific kid, get the kid from the current element */
json_t *ekid = json_object_get(elem, "kid");
if ((ekid != NULL) && json_is_string(ekid)
&& (jwt->header.kid != NULL)) {
/* compare the requested kid against the current element */
if (apr_strnatcmp(jwt->header.kid, json_string_value(ekid)) == 0) {
oidc_debug(r, "found matching kid: \"%s\"", jwt->header.kid);
rc = apr_jwk_parse_json(r->pool, elem, &jwk, &err);
if (rc == FALSE)
oidc_error(r, "JWK parsing failed: %s",
apr_jwt_e2s(r->pool, err));
else
apr_hash_set(result, jwk->kid, APR_HASH_KEY_STRING, jwk);
break;
}
}
/* we are looking for a specific x5t, get the x5t from the current element */
json_t *ex5t = json_object_get(elem, "x5t");
if ((ex5t != NULL) && json_is_string(ex5t) && (x5t != NULL)) {
/* compare the requested kid against the current element */
if (apr_strnatcmp(x5t, json_string_value(ex5t)) == 0) {
oidc_debug(r, "found matching x5t: \"%s\"", x5t);
rc = apr_jwk_parse_json(r->pool, elem, &jwk, &err);
if (rc == FALSE)
oidc_error(r, "JWK parsing failed: %s",
apr_jwt_e2s(r->pool, err));
else
apr_hash_set(result, x5t, APR_HASH_KEY_STRING, jwk);
break;
}
}
}
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,
apr_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, apr_jwt_t *jwt,
const oidc_jwks_uri_t *jwks_uri, apr_hash_t *static_keys) {
apr_jwt_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 (apr_jws_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)
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 (apr_jws_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",
apr_jwt_e2s(r->pool, err));
return FALSE;
}
oidc_debug(r,
"JWT signature verification with algorithm \"%s\" was successful",
jwt->header.alg);
return TRUE;
}
/*
* 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,
apr_jwt_t **jwt, apr_byte_t is_code_flow) {
char buf[APR_RFC822_DATE_LEN + 1];
apr_jwt_error_t err;
oidc_debug(r, "enter");
if (apr_jwt_parse(r->pool, id_token, jwt,
oidc_util_merge_symmetric_key(r->pool, cfg->private_keys,
provider->client_secret, "sha256"), &err) == FALSE) {
oidc_error(r, "apr_jwt_parse failed for JWT with header \"%s\": %s",
apr_jwt_header_to_string(r->pool, id_token, NULL),
apr_jwt_e2s(r->pool, err));
apr_jwt_destroy(*jwt);
*jwt = NULL;
return FALSE;
}
oidc_debug(r,
"successfully parsed (and possibly decrypted) JWT with header: \"%s\"",
apr_jwt_header_to_string(r->pool, id_token, NULL));
// make signature validation exception for 'code' flow and the algorithm NONE
if (is_code_flow == FALSE || strcmp((*jwt)->header.alg, "none") != 0) {
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,
provider->client_secret, NULL)) == FALSE) {
oidc_error(r,
"id_token signature could not be validated, aborting");
apr_jwt_destroy(*jwt);
*jwt = NULL;
return FALSE;
}
}
/* 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");
apr_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, "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 Bearer authentication against a UserInfo endpoint!",
token_type, provider->userinfo_endpoint_url, provider->issuer);
return FALSE;
}
return TRUE;
}
/*
* 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) {
/* get a handle to the directory config */
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
const char *response = NULL;
/* see if we need to do basic auth or auth-through-post-params (both applied through the HTTP POST method though) */
const char *basic_auth = NULL;
if ((provider->token_endpoint_auth == NULL)
|| (apr_strnatcmp(provider->token_endpoint_auth,
"client_secret_basic") == 0)) {
basic_auth = apr_psprintf(r->pool, "%s:%s", provider->client_id,
provider->client_secret);
} else {
apr_table_addn(params, "client_id", provider->client_id);
apr_table_addn(params, "client_secret", provider->client_secret);
}
/* 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, NULL, provider->ssl_validate_server, &response,
cfg->http_timeout_long, cfg->outgoing_proxy,
dir_cfg->pass_cookies) == 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, "id_token", id_token, NULL);
/* get the access_token from the parsed response */
oidc_json_object_get_string(r->pool, result, "access_token", access_token,
NULL);
/* get the token type from the parsed response */
oidc_json_object_get_string(r->pool, result, "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, "expires_in", expires_in, -1);
/* get the refresh_token from the parsed response */
oidc_json_object_get_string(r->pool, result, "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
*/
apr_byte_t oidc_proto_resolve_code(request_rec *r, oidc_cfg *cfg,
oidc_provider_t *provider, const char *code, 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_addn(params, "grant_type", "authorization_code");
apr_table_addn(params, "code", code);
apr_table_addn(params, "redirect_uri", cfg->redirect_uri);
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_addn(params, "grant_type", "refresh_token");
apr_table_addn(params, "refresh_token", rtoken);
apr_table_addn(params, "scope", provider->scope);
return oidc_proto_token_endpoint_request(r, cfg, provider, params, id_token,
access_token, token_type, expires_in, refresh_token);
}
/*
* 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 *access_token,
const char **response) {
/* get a handle to the directory config */
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
oidc_debug(r, "enter, endpoint=%s, access_token=%s",
provider->userinfo_endpoint_url, access_token);
/* get the JSON response */
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,
dir_cfg->pass_cookies) == FALSE)
return FALSE;
/* decode and check for an "error" response */
json_t *claims = NULL;
if (oidc_util_decode_json_and_check_error(r, *response, &claims) == FALSE)
return FALSE;
json_decref(claims);
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) {
/* get a handle to the directory config */
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
// 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, '@');
if (domain == NULL) {
oidc_error(r, "invalid account name");
return FALSE;
}
domain++;
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_addn(params, "resource", resource);
apr_table_addn(params, "rel", "http://openid.net/specs/connect/1.0/issuer");
const 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,
dir_cfg->pass_cookies) == 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;
}
*issuer = apr_pstrdup(r->pool, json_string_value(j_href));
oidc_debug(r,
"returning issuer \"%s\" for account \"%s\" after doing successful webfinger-based discovery",
*issuer, acct);
json_decref(j_response);
return TRUE;
}
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 = apr_jws_hash_length(alg) / 2;
apr_jwt_error_t err;
/* hash the provided access_token */
if (apr_jws_hash_string(r->pool, alg, value, &calc, &calc_len,
&err) == FALSE) {
oidc_error(r, "apr_jws_hash_string failed: %s",
apr_jwt_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, &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, apr_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;
apr_jwt_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,
apr_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) = "code id_token";
*(const char**) apr_array_push(required_for_flows) = "code id_token token";
if (oidc_proto_validate_hash_value(r, provider, jwt, response_type, code,
"c_hash", required_for_flows) == FALSE) {
oidc_error(r, "could not validate code against 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, apr_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) = "id_token token";
*(const char**) apr_array_push(required_for_flows) = "code id_token token";
if (oidc_proto_validate_hash_value(r, provider, jwt, response_type,
access_token, "at_hash", required_for_flows) == FALSE) {
oidc_error(r, "could not validate access token against 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) = "code";
*(const char**) apr_array_push(result) = "id_token";
*(const char**) apr_array_push(result) = "id_token token";
*(const char**) apr_array_push(result) = "code id_token";
*(const char**) apr_array_push(result) = "code token";
*(const char**) apr_array_push(result) = "code id_token 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, "id_token")) {
if (id_token == NULL) {
oidc_error(r,
"requested flow is \"%s\" but no \"id_token\" parameter found in the code response",
response_type);
return FALSE;
}
} else {
if (id_token != NULL) {
oidc_warn(r,
"requested flow is \"%s\" but there is an \"id_token\" parameter in the code response that will be dropped",
response_type);
}
}
/*
* check access_token parameter
*/
if (!oidc_util_spaced_string_contains(r->pool, response_type, "token")) {
if (access_token == NULL) {
oidc_error(r,
"requested flow is \"%s\" but no \"access_token\" parameter found in the code response",
response_type);
return FALSE;
}
if (token_type == NULL) {
oidc_error(r,
"requested flow is \"%s\" but no \"token_type\" parameter found in the code response",
response_type);
return FALSE;
}
} else {
if (access_token != NULL) {
oidc_warn(r,
"requested flow is \"%s\" but there is an \"access_token\" parameter in the code response that will be dropped",
response_type);
}
if (token_type != NULL) {
oidc_warn(r,
"requested flow is \"%s\" but there is a \"token_type\" parameter in the code response that will be dropped",
response_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,
"code")) {
if (code == NULL) {
oidc_error(r,
"the requested response type was (%s) but the response does not contain a \"code\" parameter",
requested_response_type);
return FALSE;
}
} else if (code != NULL) {
oidc_error(r,
"the requested response type was (%s) but the response contains a \"code\" parameter",
requested_response_type);
return FALSE;
}
if (oidc_util_spaced_string_contains(r->pool, requested_response_type,
"id_token")) {
if (id_token == NULL) {
oidc_error(r,
"the requested response type was (%s) but the response does not contain an \"id_token\" parameter",
requested_response_type);
return FALSE;
}
} else if (id_token != NULL) {
oidc_error(r,
"the requested response type was (%s) but the response contains an \"id_token\" parameter",
requested_response_type);
return FALSE;
}
if (oidc_util_spaced_string_contains(r->pool, requested_response_type,
"token")) {
if (access_token == NULL) {
oidc_error(r,
"the requested response type was (%s) but the response does not contain an \"access_token\" parameter",
requested_response_type);
return FALSE;
}
} else if (access_token != NULL) {
oidc_error(r,
"the requested response type was (%s) but the response contains an \"access_token\" parameter",
requested_response_type);
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,
json_t *proto_state, const char *response_mode,
const char *default_response_mode) {
const char *requested_response_mode =
json_object_get(proto_state, "response_mode") ?
json_string_value(
json_object_get(proto_state, "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;
}
/*
* 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_and_response_mode(
request_rec *r, const char *requested_response_type,
apr_table_t *params, json_t *proto_state, const char *response_mode,
const char *default_response_mode) {
const char *code = apr_table_get(params, "code");
const char *id_token = apr_table_get(params, "id_token");
const char *access_token = apr_table_get(params, "access_token");
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, json_t *proto_state, oidc_provider_t *provider,
const char *response_type, apr_table_t *params, apr_jwt_t **jwt,
apr_byte_t must_validate_code) {
const char *code = apr_table_get(params, "code");
const char *id_token = apr_table_get(params, "id_token");
apr_byte_t is_code_flow = (oidc_util_spaced_string_contains(r->pool,
response_type, "code") == TRUE)
&& (oidc_util_spaced_string_contains(r->pool, response_type,
"id_token") == FALSE);
json_t *nonce = json_object_get(proto_state, "nonce");
if (oidc_proto_parse_idtoken(r, c, provider, id_token,
nonce ? json_string_value(nonce) : NULL, 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) {
char *id_token = NULL;
char *access_token = NULL;
char *token_type = NULL;
int expires_in = -1;
char *refresh_token = NULL;
if (oidc_proto_resolve_code(r, c, provider, apr_table_get(params, "code"),
&id_token, &access_token, &token_type, &expires_in,
&refresh_token) == 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, "id_token") == NULL) && (id_token != NULL)) {
apr_table_set(params, "id_token", id_token);
}
if ((apr_table_get(params, "access_token") == NULL)
&& (access_token != NULL)) {
apr_table_set(params, "access_token", access_token);
if (token_type != NULL)
apr_table_set(params, "token_type", token_type);
if (expires_in != -1)
apr_table_set(params, "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, "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, json_t *proto_state, oidc_provider_t *provider,
apr_table_t *params, const char *response_mode, apr_jwt_t **jwt) {
oidc_debug(r, "enter");
static const char *response_type = "code id_token";
if (oidc_proto_validate_response_type_and_response_mode(r, response_type,
params, proto_state, response_mode, "fragment") == 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, "access_token");
apr_table_unset(params, "token_type");
apr_table_unset(params, "expires_in");
apr_table_unset(params, "refresh_token");
if (oidc_proto_resolve_code_and_validate_response(r, c, provider,
response_type, params) == 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, json_t *proto_state, oidc_provider_t *provider,
apr_table_t *params, const char *response_mode, apr_jwt_t **jwt) {
oidc_debug(r, "enter");
static const char *response_type = "code token";
if (oidc_proto_validate_response_type_and_response_mode(r, response_type,
params, proto_state, response_mode, "fragment") == FALSE)
return FALSE;
/* clear parameters that should only be set from the token endpoint */
apr_table_unset(params, "id_token");
apr_table_unset(params, "refresh_token");
if (oidc_proto_resolve_code_and_validate_response(r, c, provider,
response_type, params) == 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, json_t *proto_state, oidc_provider_t *provider,
apr_table_t *params, const char *response_mode, apr_jwt_t **jwt) {
oidc_debug(r, "enter");
static const char *response_type = "code";
if (oidc_proto_validate_response_type_and_response_mode(r, response_type,
params, proto_state, response_mode, "query") == FALSE)
return FALSE;
/* clear parameters that should only be set from the token endpoint */
apr_table_unset(params, "access_token");
apr_table_unset(params, "token_type");
apr_table_unset(params, "expires_in");
apr_table_unset(params, "id_token");
apr_table_unset(params, "refresh_token");
if (oidc_proto_resolve_code_and_validate_response(r, c, provider,
response_type, params) == 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, "access_token") != NULL)
&& (oidc_proto_validate_access_token(r, provider, *jwt,
response_type, apr_table_get(params, "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, json_t *proto_state,
oidc_provider_t *provider, apr_table_t *params,
const char *response_mode, apr_jwt_t **jwt) {
if (oidc_proto_validate_response_type_and_response_mode(r, response_type,
params, proto_state, response_mode, "fragment") == 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, json_t *proto_state, oidc_provider_t *provider,
apr_table_t *params, const char *response_mode, apr_jwt_t **jwt) {
oidc_debug(r, "enter");
static const char *response_type = "code id_token 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, "access_token")) == FALSE)
return FALSE;
/* clear parameters that should only be set from the token endpoint */
apr_table_unset(params, "refresh_token");
if (oidc_proto_resolve_code_and_validate_response(r, c, provider,
response_type, params) == 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, json_t *proto_state,
oidc_provider_t *provider, apr_table_t *params,
const char *response_mode, apr_jwt_t **jwt) {
oidc_debug(r, "enter");
static const char *response_type = "id_token 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, "access_token")) == FALSE)
return FALSE;
/* clear parameters that should not be part of this flow */
apr_table_unset(params, "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, json_t *proto_state, oidc_provider_t *provider,
apr_table_t *params, const char *response_mode, apr_jwt_t **jwt) {
oidc_debug(r, "enter");
static const char *response_type = "id_token";
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, "token_type");
apr_table_unset(params, "expires_in");
apr_table_unset(params, "refresh_token");
return TRUE;
}
mod_auth_openidc-1.8.5/src/crypto.c 0000644 0001750 0001750 00000015567 12532644156 017517 0 ustar zandbelt zandbelt /*
* 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-2015 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.
*
* based on http://saju.net.in/code/misc/openssl_aes.c.txt
*
* @Author: Hans Zandbelt - hzandbelt@pingidentity.com
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "mod_auth_openidc.h"
/*
* initialize the crypto context in the server configuration record; the passphrase is set already
*/
static apr_byte_t oidc_crypto_init(oidc_cfg *cfg, server_rec *s) {
if (cfg->encrypt_ctx != NULL)
return TRUE;
unsigned char *key_data = (unsigned char *) cfg->crypto_passphrase;
int key_data_len = strlen(cfg->crypto_passphrase);
unsigned int s_salt[] = { 41892, 72930 };
unsigned char *salt = (unsigned char *) &s_salt;
int i, nrounds = 5;
unsigned char key[32], iv[32];
/*
* Gen key & IV for AES 256 CBC mode. A SHA1 digest is used to hash the supplied key material.
* nrounds is the number of times the we hash the material. More rounds are more secure but
* slower.
*/
i = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha1(), salt, key_data,
key_data_len, nrounds, key, iv);
if (i != 32) {
oidc_serror(s, "key size must be 256 bits!");
return FALSE;
}
cfg->encrypt_ctx = apr_palloc(s->process->pool, sizeof(EVP_CIPHER_CTX));
cfg->decrypt_ctx = apr_palloc(s->process->pool, sizeof(EVP_CIPHER_CTX));
/* initialize the encoding context */
EVP_CIPHER_CTX_init(cfg->encrypt_ctx);
if (!EVP_EncryptInit_ex(cfg->encrypt_ctx, EVP_aes_256_cbc(), NULL, key,
iv)) {
oidc_serror(s, "EVP_EncryptInit_ex on the encrypt context failed: %s",
ERR_error_string(ERR_get_error(), NULL));
return FALSE;
}
/* initialize the decoding context */
EVP_CIPHER_CTX_init(cfg->decrypt_ctx);
if (!EVP_DecryptInit_ex(cfg->decrypt_ctx, EVP_aes_256_cbc(), NULL, key,
iv)) {
oidc_serror(s, "EVP_DecryptInit_ex on the decrypt context failed: %s",
ERR_error_string(ERR_get_error(), NULL));
return FALSE;
}
return TRUE;
}
/*
* AES encrypt plaintext
*/
unsigned char *oidc_crypto_aes_encrypt(request_rec *r, oidc_cfg *cfg,
unsigned char *plaintext, int *len) {
if (oidc_crypto_init(cfg, r->server) == FALSE)
return NULL;
/* max ciphertext len for a n bytes of plaintext is n + AES_BLOCK_SIZE -1 bytes */
int c_len = *len + AES_BLOCK_SIZE, f_len = 0;
unsigned char *ciphertext = apr_palloc(r->pool, c_len);
/* allows reusing of 'e' for multiple encryption cycles */
if (!EVP_EncryptInit_ex(cfg->encrypt_ctx, NULL, NULL, NULL, NULL)) {
oidc_error(r, "EVP_EncryptInit_ex failed: %s",
ERR_error_string(ERR_get_error(), NULL));
return NULL;
}
/* update ciphertext, c_len is filled with the length of ciphertext generated, len is the size of plaintext in bytes */
if (!EVP_EncryptUpdate(cfg->encrypt_ctx, ciphertext, &c_len, plaintext,
*len)) {
oidc_error(r, "EVP_EncryptUpdate failed: %s",
ERR_error_string(ERR_get_error(), NULL));
return NULL;
}
/* update ciphertext with the final remaining bytes */
if (!EVP_EncryptFinal_ex(cfg->encrypt_ctx, ciphertext + c_len, &f_len)) {
oidc_error(r, "EVP_EncryptFinal_ex failed: %s",
ERR_error_string(ERR_get_error(), NULL));
return NULL;
}
*len = c_len + f_len;
return ciphertext;
}
/*
* AES decrypt ciphertext
*/
unsigned char *oidc_crypto_aes_decrypt(request_rec *r, oidc_cfg *cfg,
unsigned char *ciphertext, int *len) {
if (oidc_crypto_init(cfg, r->server) == FALSE)
return NULL;
/* because we have padding ON, we must allocate an extra cipher block size of memory */
int p_len = *len, f_len = 0;
unsigned char *plaintext = apr_palloc(r->pool, p_len + AES_BLOCK_SIZE);
/* allows reusing of 'e' for multiple encryption cycles */
if (!EVP_DecryptInit_ex(cfg->decrypt_ctx, NULL, NULL, NULL, NULL)) {
oidc_error(r, "EVP_DecryptInit_ex failed: %s",
ERR_error_string(ERR_get_error(), NULL));
return NULL;
}
/* update plaintext, p_len is filled with the length of plaintext generated, len is the size of ciphertext in bytes */
if (!EVP_DecryptUpdate(cfg->decrypt_ctx, plaintext, &p_len, ciphertext,
*len)) {
oidc_error(r, "EVP_DecryptUpdate failed: %s",
ERR_error_string(ERR_get_error(), NULL));
return NULL;
}
/* update plaintext with the final remaining bytes */
if (!EVP_DecryptFinal_ex(cfg->decrypt_ctx, plaintext + p_len, &f_len)) {
oidc_error(r, "EVP_DecryptFinal_ex failed: %s",
ERR_error_string(ERR_get_error(), NULL));
return NULL;
}
*len = p_len + f_len;
return plaintext;
}
/*
* cleanup the crypto context in the server configuration record
*/
apr_byte_t oidc_crypto_destroy(oidc_cfg *cfg, server_rec *s) {
if (cfg->encrypt_ctx == NULL)
return TRUE;
EVP_CIPHER_CTX_cleanup(cfg->encrypt_ctx);
EVP_CIPHER_CTX_cleanup(cfg->decrypt_ctx);
cfg->encrypt_ctx = NULL;
cfg->decrypt_ctx = NULL;
return TRUE;
}
mod_auth_openidc-1.8.5/src/config.c 0000664 0001750 0001750 00000223201 12577725501 017433 0 ustar zandbelt zandbelt /*
* 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-2015 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 - hzandbelt@pingidentity.com
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "mod_auth_openidc.h"
#define OPENSSL_THREAD_DEFINES
#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_22_SERVER_CACHE
/* 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 "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
/* 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 "POST"
/* default OAuth 2.0 non-spec compliant introspection expiry claim name */
#define OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_NAME "expires_in"
/* default OAuth 2.0 non-spec compliant introspection expiry claim format */
#define OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_FORMAT "relative"
/* default OAuth 2.0 non-spec compliant introspection expiry claim required */
#define OIDC_DEFAULT_OAUTH_EXPIRY_CLAIM_REQUIRED TRUE
extern module AP_MODULE_DECLARE_DATA auth_openidc_module;
/*
* 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) {
apr_uri_t url;
if (apr_uri_parse(cmd->pool, arg, &url) != APR_SUCCESS) {
return apr_psprintf(cmd->pool,
"oidc_set_url_slot_type: configuration value '%s' could not be parsed as a URL!",
arg);
}
if (url.scheme == NULL) {
return apr_psprintf(cmd->pool,
"oidc_set_url_slot_type: configuration value '%s' could not be parsed as a URL (no scheme set)!",
arg);
}
if (type == NULL) {
if ((apr_strnatcmp(url.scheme, "http") != 0)
&& (apr_strnatcmp(url.scheme, "https") != 0)) {
return apr_psprintf(cmd->pool,
"oidc_set_url_slot_type: configuration value '%s' could not be parsed as a HTTP/HTTPs URL (scheme != http/https)!",
arg);
}
} else if (apr_strnatcmp(url.scheme, type) != 0) {
return apr_psprintf(cmd->pool,
"oidc_set_url_slot_type: configuration value '%s' could not be parsed as a \"%s\" URL (scheme == %s != \"%s\")!",
arg, type, url.scheme, type);
}
if (url.hostname == NULL) {
return apr_psprintf(cmd->pool,
"oidc_set_url_slot_type: configuration value '%s' could not be parsed as a HTTP/HTTPs URL (no hostname set, check your slashes)!",
arg);
}
return ap_set_string_slot(cmd, ptr, arg);
}
/*
* 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 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);
char s_err[128];
apr_dir_t *dir;
apr_status_t rc = APR_SUCCESS;
/* ensure the directory exists */
if ((rc = apr_dir_open(&dir, arg, cmd->pool)) != APR_SUCCESS) {
return apr_psprintf(cmd->pool,
"oidc_set_dir_slot: could not access directory '%s' (%s)", arg,
apr_strerror(rc, s_err, sizeof(s_err)));
}
/* and cleanup... */
if ((rc = apr_dir_close(dir)) != APR_SUCCESS) {
return apr_psprintf(cmd->pool,
"oidc_set_dir_slot: could not close directory '%s' (%s)", arg,
apr_strerror(rc, s_err, sizeof(s_err)));
}
return ap_set_string_slot(cmd, cfg, arg);
}
/*
* 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);
size_t sz, limit;
char d;
limit = strlen(value);
for (sz = 0; sz < limit; sz++) {
d = value[sz];
if ((d < '0' || d > '9') && (d < 'a' || d > 'z') && (d < 'A' || d > 'Z')
&& d != '.' && d != '-') {
return (apr_psprintf(cmd->pool,
"oidc_set_cookie_domain: invalid character (%c) in %s",
d, cmd->directive->directive));
}
}
cfg->cookie_domain = apr_pstrdup(cmd->pool, value);
return NULL;
}
/*
* 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);
if (apr_strnatcmp(arg, "server-cache") == 0) {
cfg->session_type = OIDC_SESSION_TYPE_22_SERVER_CACHE;
} else if (apr_strnatcmp(arg, "client-cookie") == 0) {
cfg->session_type = OIDC_SESSION_TYPE_22_CLIENT_COOKIE;
} else {
return (apr_psprintf(cmd->pool,
"oidc_set_session_type: invalid value for %s (%s); must be one of \"server-cache\" or \"client-cookie\"",
cmd->directive->directive, arg));
}
return NULL;
}
/*
* 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);
char *endptr;
int v = strtol(arg, &endptr, 10);
if ((*arg == '\0') || (*endptr != '\0')) {
return apr_psprintf(cmd->pool,
"Invalid value for directive %s, expected integer",
cmd->directive->directive);
}
cfg->cache_shm_entry_size_max =
v > OIDC_MINIMUM_CACHE_SHM_ENTRY_SIZE_MAX ?
v : OIDC_MINIMUM_CACHE_SHM_ENTRY_SIZE_MAX;
return NULL;
}
/*
* 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);
if (apr_strnatcmp(arg, "file") == 0) {
cfg->cache = &oidc_cache_file;
} else if (apr_strnatcmp(arg, "memcache") == 0) {
cfg->cache = &oidc_cache_memcache;
} else if (apr_strnatcmp(arg, "shm") == 0) {
cfg->cache = &oidc_cache_shm;
#ifdef USE_LIBHIREDIS
} else if (apr_strnatcmp(arg, "redis") == 0) {
cfg->cache = &oidc_cache_redis;
#endif
} else {
return (apr_psprintf(cmd->pool,
#ifdef USE_LIBHIREDIS
"oidc_set_cache_type: invalid value for %s (%s); must be one of \"shm\", \"memcache\", \"redis\" or \"file\"",
cmd->directive->directive, arg));
#else
"oidc_set_cache_type: invalid value for %s (%s); must be one of \"shm\", \"memcache\" or \"file\"",
cmd->directive->directive, arg));
#endif
}
return NULL;
}
/*
* 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);
if ((apr_strnatcmp(arg, "client_secret_post") == 0)
|| (apr_strnatcmp(arg, "client_secret_basic") == 0)) {
return ap_set_string_slot(cmd, cfg, arg);
}
return "parameter must be 'client_secret_post' or 'client_secret_basic'";
}
/*
* 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);
if (oidc_proto_flow_is_supported(cmd->pool, arg)) {
return ap_set_string_slot(cmd, cfg, arg);
}
return apr_psprintf(cmd->pool, "parameter must be one of %s",
apr_array_pstrcat(cmd->pool, oidc_proto_supported_flows(cmd->pool),
'|'));
}
/*
* 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);
if ((apr_strnatcmp(arg, "fragment") == 0)
|| (apr_strnatcmp(arg, "query") == 0)
|| (apr_strnatcmp(arg, "form_post") == 0)) {
return ap_set_string_slot(cmd, cfg, arg);
}
return "parameter must be 'fragment', 'query' or 'form_post'";
}
/*
* 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);
if (apr_jws_algorithm_is_supported(cmd->pool, arg)) {
return ap_set_string_slot(cmd, cfg, arg);
}
return apr_psprintf(cmd->pool, "parameter must be one of %s",
apr_array_pstrcat(cmd->pool,
apr_jws_supported_algorithms(cmd->pool), '|'));
}
/*
* 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);
if (apr_jwe_algorithm_is_supported(cmd->pool, arg)) {
return ap_set_string_slot(cmd, cfg, arg);
}
return apr_psprintf(cmd->pool, "parameter must be one of %s",
apr_array_pstrcat(cmd->pool,
apr_jwe_supported_algorithms(cmd->pool), '|'));
}
/*
* 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);
if (apr_jwe_encryption_is_supported(cmd->pool, arg)) {
return ap_set_string_slot(cmd, cfg, arg);
}
return apr_psprintf(cmd->pool, "parameter must be one of %s",
apr_array_pstrcat(cmd->pool,
apr_jwe_supported_encryptions(cmd->pool), '|'));
}
/*
* 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);
char *endptr = NULL;
long n = strtol(arg, &endptr, 10);
if ((*arg == '\0') || (*endptr != '\0')) {
return apr_psprintf(cmd->pool,
"Invalid value for directive %s, expected integer",
cmd->directive->directive);
}
if (n < 10) {
return apr_psprintf(cmd->pool,
"Invalid value for directive %s, must not be less than 10 seconds",
cmd->directive->directive);
}
if (n > 86400) {
return apr_psprintf(cmd->pool,
"Invalid value for directive %s, must not be greater than 86400 seconds (24 hours)",
cmd->directive->directive);
}
cfg->session_inactivity_timeout = n;
return NULL;
}
/*
* 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);
char *endptr = NULL;
long n = strtol(arg, &endptr, 10);
if ((*arg == '\0') || (*endptr != '\0')) {
return apr_psprintf(cmd->pool,
"Invalid value for directive %s, expected integer",
cmd->directive->directive);
}
if (n == 0) {
cfg->provider.session_max_duration = 0;
return NULL;
}
if (n < 300) {
return apr_psprintf(cmd->pool,
"Invalid value for directive %s, must not be less than 5 minutes (300 seconds)",
cmd->directive->directive);
}
if (n > 86400 * 365) {
return apr_psprintf(cmd->pool,
"Invalid value for directive %s, must not be greater than 1 year (31536000 seconds)",
cmd->directive->directive);
}
cfg->provider.session_max_duration = n;
return NULL;
}
/*
* 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) {
apr_jwk_t *jwk = NULL;
apr_jwt_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);
if (apr_jwk_parse_rsa_public_key(cmd->pool, arg, &jwk, &err) == FALSE) {
return apr_psprintf(cmd->pool,
"apr_jwk_parse_rsa_public_key failed for \"%s\": %s", arg,
apr_jwt_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_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);
*shared_keys = oidc_util_merge_symmetric_key(cmd->pool, *shared_keys, arg,
NULL);
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);
apr_jwk_t *jwk = NULL;
apr_jwt_error_t err;
if (apr_jwk_parse_rsa_private_key(cmd->pool, arg, &jwk, &err) == FALSE) {
return apr_psprintf(cmd->pool,
"apr_jwk_parse_rsa_private_key failed for \"%s\": %s", arg,
apr_jwt_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;
}
static int oidc_pass_idtoken_as_str2int(const char *v) {
if (apr_strnatcmp(v, "claims") == 0)
return OIDC_PASS_IDTOKEN_AS_CLAIMS;
if (apr_strnatcmp(v, "payload") == 0)
return OIDC_PASS_IDTOKEN_AS_PAYLOAD;
if (apr_strnatcmp(v, "serialized") == 0)
return OIDC_PASS_IDTOKEN_AS_SERIALIZED;
return -1;
}
/*
* 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);
int b = oidc_pass_idtoken_as_str2int(v1);
if (b != -1) {
cfg->pass_idtoken_as = b;
} else {
return apr_psprintf(cmd->pool, "Invalid value \"%s\" for directive %s",
v1, cmd->directive->directive);
}
if (v2) {
b = oidc_pass_idtoken_as_str2int(v2);
if (b != -1) {
cfg->pass_idtoken_as |= b;
} else {
return apr_psprintf(cmd->pool,
"Invalid value \"%s\" for directive %s", v2,
cmd->directive->directive);
}
if (v3) {
b = oidc_pass_idtoken_as_str2int(v3);
if (b != -1) {
cfg->pass_idtoken_as |= b;
} else {
return apr_psprintf(cmd->pool,
"Invalid value \"%s\" for directive %s", v3,
cmd->directive->directive);
}
}
}
return NULL;
}
/*
* 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);
cfg->oauth.introspection_token_expiry_claim_name = apr_pstrdup(cmd->pool,
claim_name);
if (claim_format) {
if ((apr_strnatcmp(claim_format, "absolute") == 0)
|| (apr_strnatcmp(claim_format, "relative") == 0)) {
cfg->oauth.introspection_token_expiry_claim_format = apr_pstrdup(
cmd->pool, claim_format);
} else {
return apr_psprintf(cmd->pool,
"Invalid value \"%s\" for directive %s; must be either \"absolute\" or \"relative\"",
claim_format, cmd->directive->directive);
}
}
if (claim_required) {
if (apr_strnatcmp(claim_required, "mandatory") == 0)
cfg->oauth.introspection_token_expiry_claim_required = TRUE;
else if (apr_strnatcmp(claim_required, "optional") == 0) {
cfg->oauth.introspection_token_expiry_claim_required = FALSE;
} else {
return apr_psprintf(cmd->pool,
"Invalid value \"%s\" for directive %s; must be either \"mandatory\" or \"optional\"",
claim_required, cmd->directive->directive);
}
}
return NULL;
}
/*
* specify cookies to pass on to the OP/AS
*/
static const char * oidc_set_pass_cookies(cmd_parms *cmd, void *m,
const char *arg) {
oidc_dir_cfg *dir_cfg = (oidc_dir_cfg *) m;
*(const char**) apr_array_push(dir_cfg->pass_cookies) = 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);
if ((apr_strnatcmp(arg, "GET") == 0) || (apr_strnatcmp(arg, "POST") == 0)) {
return ap_set_string_slot(cmd, cfg, arg);
}
return "parameter must be 'GET' or 'POST'";
}
/*
* 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) {
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;
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;
if (apr_strnatcmp(arg, "both") == 0) {
dir_cfg->pass_info_in_headers = 1;
dir_cfg->pass_info_in_env_vars = 1;
return NULL;
}
if (apr_strnatcmp(arg, "headers") == 0) {
dir_cfg->pass_info_in_headers = 1;
dir_cfg->pass_info_in_env_vars = 0;
return NULL;
}
if (apr_strnatcmp(arg, "environment") == 0) {
dir_cfg->pass_info_in_headers = 0;
dir_cfg->pass_info_in_env_vars = 1;
return NULL;
}
if (apr_strnatcmp(arg, "none") == 0) {
dir_cfg->pass_info_in_headers = 0;
dir_cfg->pass_info_in_env_vars = 0;
return NULL;
}
return "parameter must be one of 'both', 'headers', 'environment' or 'none";
}
/*
* 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;
if (apr_strnatcmp(arg, "auth") == 0) {
dir_cfg->unauth_action = AUTHENTICATE;
return NULL;
}
if (apr_strnatcmp(arg, "pass") == 0) {
dir_cfg->unauth_action = PASS;
return NULL;
}
if (apr_strnatcmp(arg, "401") == 0) {
dir_cfg->unauth_action = RETURN401;
return NULL;
}
return "parameter must be one of 'auth', 'pass' or '401";
}
/*
* OIDCReturn401 is deprecated
*/
static const char * oidc_return_401(cmd_parms *cmd, void *struct_ptr, int arg) {
return "this primitive is deprecated: use \"OIDCUnAuthAction 401\" instead";
}
/*
* 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.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.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->oauth.ssl_validate_server = OIDC_DEFAULT_SSL_VALIDATE_SERVER;
c->oauth.client_id = NULL;
c->oauth.client_secret = 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_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.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_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->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 = OIDC_DEFAULT_CLAIM_PREFIX;
c->remote_user_claim.claim_name = OIDC_DEFAULT_CLAIM_REMOTE_USER;
c->remote_user_claim.reg_exp = NULL;
c->pass_idtoken_as = OIDC_PASS_IDTOKEN_AS_CLAIMS;
c->cookie_http_only = OIDC_DEFAULT_COOKIE_HTTPONLY;
c->outgoing_proxy = NULL;
c->crypto_passphrase = NULL;
c->scrub_request_headers = OIDC_DEFAULT_SCRUB_REQUEST_HEADERS;
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.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.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->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_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_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.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_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->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 =
apr_strnatcmp(add->claim_prefix, OIDC_DEFAULT_CLAIM_PREFIX) != 0 ?
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->pass_idtoken_as =
add->pass_idtoken_as != OIDC_PASS_IDTOKEN_AS_CLAIMS ?
add->pass_idtoken_as : base->pass_idtoken_as;
c->cookie_http_only =
add->cookie_http_only != OIDC_DEFAULT_COOKIE_HTTPONLY ?
add->cookie_http_only : base->cookie_http_only;
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;
return c;
}
/*
* 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 = NULL;
c->cookie = OIDC_DEFAULT_COOKIE;
c->cookie_path = OIDC_DEFAULT_COOKIE_PATH;
c->authn_header = OIDC_DEFAULT_AUTHN_HEADER;
c->unauth_action = AUTHENTICATE;
c->pass_cookies = apr_array_make(pool, 0, sizeof(const char *));
c->pass_info_in_headers = 1;
c->pass_info_in_env_vars = 1;
return (c);
}
/*
* 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 =
add->discover_url != NULL ? add->discover_url : base->discover_url;
c->cookie = (
apr_strnatcasecmp(add->cookie, OIDC_DEFAULT_COOKIE) != 0 ?
add->cookie : base->cookie);
c->cookie_path = (
apr_strnatcasecmp(add->cookie_path, OIDC_DEFAULT_COOKIE_PATH) != 0 ?
add->cookie_path : base->cookie_path);
c->authn_header = (
add->authn_header != OIDC_DEFAULT_AUTHN_HEADER ?
add->authn_header : base->authn_header);
c->unauth_action = (
add->unauth_action != AUTHENTICATE ?
add->unauth_action : base->unauth_action);
c->pass_cookies = (
apr_is_empty_array(add->pass_cookies) != 0 ?
add->pass_cookies : base->pass_cookies);
c->pass_info_in_headers = (
add->pass_info_in_headers != 1 ?
add->pass_info_in_headers : base->pass_info_in_headers);
c->pass_info_in_env_vars = (
add->pass_info_in_env_vars != 1 ?
add->pass_info_in_env_vars : base->pass_info_in_env_vars);
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;
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");
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");
// TODO: this depends on the configured OIDCResponseType now
// if (c->provider.token_endpoint_url == NULL)
// return oidc_check_config_error(s, "OIDCProviderTokenEndpoint");
} else {
apr_uri_parse(s->process->pconf, c->provider.metadata_url, &r_uri);
if (apr_strnatcmp(r_uri.scheme, "http") == 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");
// TODO: this depends on the configured OIDCResponseType now
if (c->provider.client_secret == NULL)
return oidc_check_config_error(s, "OIDCClientSecret");
} 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 (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) {
char *p = strstr(r_uri.hostname, c->cookie_domain);
if ((p == NULL) || (apr_strnatcmp(c->cookie_domain, p) != 0)) {
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)
|| (cfg->crypto_passphrase != 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(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);
oidc_crypto_destroy(cfg, sp);
if (cfg->cache->destroy != NULL) {
if (cfg->cache->destroy(sp) != APR_SUCCESS) {
oidc_serror(sp, "cache destroy function failed");
}
}
sp = sp->next;
}
#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", NAMEVERSION);
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, apr_pool_cleanup_null);
oidc_session_init();
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 authz_oidc_provider = {
&oidc_authz_checker,
NULL,
};
#endif
/*
* initialize cache context in child process if required
*/
static void oidc_child_init(apr_pool_t *p, server_rec *s) {
while (s != NULL) {
oidc_cfg *cfg = (oidc_cfg *) ap_get_module_config(s->module_config,
&auth_openidc_module);
if (cfg->cache->child_init != NULL) {
if (cfg->cache->child_init(p, s) != APR_SUCCESS) {
oidc_serror(s, "cfg->cache->child_init failed");
}
}
s = s->next;
}
}
/*
* 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_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_NAME, "0", &authz_oidc_provider, AP_AUTH_INTERNAL_PER_CONF);
#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_string_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|A128GCM|A192GCM|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|A128GCM|A192GCM|A256GCM]"),
AP_INIT_FLAG("OIDCSSLValidateServer",
oidc_set_flag_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("OIDCJWKSRefreshInterval",
oidc_set_int_slot,
(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_int_slot,
(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("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("OIDCRedirectURI", oidc_set_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_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_TAKE1("OIDCClaimPrefix", oidc_set_string_slot,
(void*)APR_OFFSETOF(oidc_cfg, claim_prefix),
RSRC_CONF,
"The prefix to use when setting claims in the HTTP headers."),
AP_INIT_TAKE12("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_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_auth_basic)"),
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_FLAG("OIDCOAuthSSLValidateServer",
oidc_set_flag_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_TAKE12("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\"."),
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_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("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_pass_cookies,
(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_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_FLAG("OIDCReturn401", oidc_return_401,
(void *) APR_OFFSETOF(oidc_dir_cfg, unauth_action),
RSRC_CONF|ACCESS_CONF|OR_AUTHCFG,
"Indicates whether a user will be redirected to the Provider when not authenticated (Off) or a 401 will be returned (On)."),
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: \"auth\" (default) means redirect to OP for authentication, \"pass\" means always allow the request but pass claims when authenticated, \"401\" means return HTTP 401."),
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)."),
{ NULL }
};
mod_auth_openidc-1.8.5/src/util.c 0000644 0001750 0001750 00000131503 12545544130 017134 0 ustar zandbelt zandbelt /*
* 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-2015 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 - hzandbelt@pingidentity.com
*/
#include
#include
#include
#include
#include
#include
#include
#include "http_protocol.h"
#include
#include "mod_auth_openidc.h"
#include
/* 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;
}
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);
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 */
enc_len--;
if (enc[enc_len - 1] == ',')
enc_len--;
if (enc[enc_len - 1] == ',')
enc_len--;
enc[enc_len] = '\0';
}
*dst = enc;
return enc_len;
}
/*
* base64url decode a string
*/
int oidc_base64url_decode(request_rec *r, char **dst, const char *src) {
if (src == NULL) {
oidc_error(r, "not decoding anything; src=NULL");
return -1;
}
char *dec = apr_pstrdup(r->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(r->pool, dec, "==", NULL);
break;
case 3:
dec = apr_pstrcat(r->pool, dec, "=", NULL);
break;
default:
return 0;
}
int dlen = apr_base64_decode_len(dec);
*dst = apr_palloc(r->pool, dlen);
return apr_base64_decode(*dst, dec);
}
/*
* encrypt and base64url encode a string
*/
int oidc_encrypt_base64url_encode_string(request_rec *r, char **dst,
const char *src) {
oidc_cfg *c = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
int crypted_len = strlen(src) + 1;
unsigned char *crypted = oidc_crypto_aes_encrypt(r, c,
(unsigned char *) src, &crypted_len);
if (crypted == NULL) {
oidc_error(r, "oidc_crypto_aes_encrypt failed");
return -1;
}
return oidc_base64url_encode(r, dst, (const char *) crypted, crypted_len, 1);
}
/*
* decrypt and base64url decode a string
*/
int oidc_base64url_decode_decrypt_string(request_rec *r, char **dst,
const char *src) {
oidc_cfg *c = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
char *decbuf = NULL;
int dec_len = oidc_base64url_decode(r, &decbuf, src);
if (dec_len <= 0) {
oidc_error(r, "oidc_base64url_decode failed");
return -1;
}
*dst = (char *) oidc_crypto_aes_decrypt(r, c, (unsigned char *) decbuf,
&dec_len);
if (*dst == NULL) {
oidc_error(r, "oidc_crypto_aes_decrypt failed");
return -1;
}
return dec_len;
}
/*
* 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) {
const char chars[6] = { '&', '\'', '\"', '>', '<', '\0' };
const char * const replace[] =
{ "&", "'", """, ">", "<", };
unsigned int i, j = 0, k, n = 0, len = strlen(chars);
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 = 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 = apr_table_get(r->headers_in, "X-Forwarded-Proto");
/* 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
}
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 oidc_cfg *c, const char *scheme_str) {
/* first see if there's a proxy/load-balancer in front of us */
const char *port_str = apr_table_get(r->headers_in, "X-Forwarded-Port");
if (port_str == NULL) {
/* if not we'll take the port from the Host header (as set by the client or ProxyPreserveHost) */
const char *host_hdr = apr_table_get(r->headers_in, "Host");
port_str = strchr(host_hdr, ':');
if (port_str == NULL) {
/* if no port was set in the Host header we'll determine it locally */
const apr_port_t port = r->connection->local_addr->port;
apr_byte_t print_port = TRUE;
if ((apr_strnatcmp(scheme_str, "https") == 0) && port == 443)
print_port = FALSE;
else if ((apr_strnatcmp(scheme_str, "http") == 0) && port == 80)
print_port = FALSE;
if (print_port)
port_str = apr_psprintf(r->pool, "%u", port);
} else {
port_str++;
}
}
return port_str;
}
/*
* get the URL that is currently being accessed
*/
char *oidc_get_current_url(const request_rec *r, const oidc_cfg *c) {
const char *scheme_str = oidc_get_current_url_scheme(r);
const char *port_str = oidc_get_current_url_port(r, c, scheme_str);
port_str = port_str ? apr_psprintf(r->pool, ":%s", port_str) : "";
const char *host_str = apr_table_get(r->headers_in, "Host");
char *p = strchr(host_str, ':');
if (p != NULL)
*p = '\0';
char *url = apr_pstrcat(r->pool, scheme_str, "://", host_str, port_str,
r->uri, (r->args != NULL && *r->args != '\0' ? "?" : ""), r->args,
NULL);
oidc_debug(r, "current URL '%s'", url);
return url;
}
/* maximum size of any response returned in HTTP calls */
#define OIDC_CURL_MAX_RESPONSE_SIZE 65536
/* buffer to hold HTTP call responses */
typedef struct oidc_curl_buffer {
char buf[OIDC_CURL_MAX_RESPONSE_SIZE];
size_t written;
} oidc_curl_buffer;
/*
* callback for CURL to write bytes that come back from an HTTP call
*/
size_t oidc_curl_write(const void *ptr, size_t size, size_t nmemb, void *stream) {
oidc_curl_buffer *curlBuffer = (oidc_curl_buffer *) stream;
if ((nmemb * size) + curlBuffer->written >= OIDC_CURL_MAX_RESPONSE_SIZE)
return 0;
memcpy((curlBuffer->buf + curlBuffer->written), ptr, (nmemb * size));
curlBuffer->written += (nmemb * size);
return (nmemb * size);
}
/* context structure for encoding parameters */
typedef struct oidc_http_encode_t {
request_rec *r;
const char *encoded_params;
} oidc_http_encode_t;
/*
* add a url-form-encoded name/value pair
*/
static int oidc_http_add_form_url_encoded_param(void* rec, const char* key,
const char* value) {
oidc_http_encode_t *ctx = (oidc_http_encode_t*) rec;
const char *sep = apr_strnatcmp(ctx->encoded_params, "") == 0 ? "" : "&";
ctx->encoded_params = apr_psprintf(ctx->r->pool, "%s%s%s=%s",
ctx->encoded_params, sep, oidc_util_escape_string(ctx->r, key),
oidc_util_escape_string(ctx->r, value));
return 1;
}
/*
* 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,
const char **response, int timeout, const char *outgoing_proxy,
apr_array_header_t *pass_cookies) {
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",
url, data, content_type, basic_auth, bearer_token,
ssl_validate_server);
curl = curl_easy_init();
if (curl == NULL) {
oidc_error(r, "curl_easy_init() error");
return FALSE;
}
/* 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.written = 0;
memset(curlBuffer.buf, '\0', sizeof(curlBuffer.buf));
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &curlBuffer);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, oidc_curl_write);
#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 (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, "Content-type: %s", content_type));
}
/* see if we need to add any custom headers */
if (h_list != NULL)
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, h_list);
/* 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);
rv = FALSE;
goto out;
}
*response = apr_pstrndup(r->pool, curlBuffer.buf, curlBuffer.written);
/* set and log the response */
oidc_debug(r, "response=%s", *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,
const char **response, int timeout, const char *outgoing_proxy,
apr_array_header_t *pass_cookies) {
if ((params != NULL) && (apr_table_elts(params)->nelts > 0)) {
oidc_http_encode_t data = { r, "" };
apr_table_do(oidc_http_add_form_url_encoded_param, &data, params, NULL);
const char *sep = strchr(url, '?') != NULL ? "&" : "?";
url = apr_psprintf(r->pool, "%s%s%s", url, sep, data.encoded_params);
oidc_debug(r, "get URL=\"%s\"", url);
}
return oidc_util_http_call(r, url, NULL, NULL, basic_auth, bearer_token,
ssl_validate_server, response, timeout, outgoing_proxy,
pass_cookies);
}
/*
* 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,
const char **response, int timeout, const char *outgoing_proxy,
apr_array_header_t *pass_cookies) {
const char *data = NULL;
if ((params != NULL) && (apr_table_elts(params)->nelts > 0)) {
oidc_http_encode_t encode_data = { r, "" };
apr_table_do(oidc_http_add_form_url_encoded_param, &encode_data, params,
NULL);
data = encode_data.encoded_params;
oidc_debug(r, "post data=\"%s\"", data);
}
return oidc_util_http_call(r, url, data,
"application/x-www-form-urlencoded", basic_auth, bearer_token,
ssl_validate_server, response, timeout, outgoing_proxy,
pass_cookies);
}
/*
* execute HTTP POST request with JSON-encoded data
*/
apr_byte_t oidc_util_http_post_json(request_rec *r, const char *url,
const json_t *json, const char *basic_auth, const char *bearer_token,
int ssl_validate_server, const char **response, int timeout,
const char *outgoing_proxy, apr_array_header_t *pass_cookies) {
char *data = NULL;
if (json != NULL) {
char *s_value = json_dumps(json, 0);
data = apr_pstrdup(r->pool, s_value);
free(s_value);
}
return oidc_util_http_call(r, url, data, "application/json", basic_auth,
bearer_token, ssl_validate_server, response, timeout,
outgoing_proxy, pass_cookies);
}
/*
* 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[0] == '\0')
return apr_pstrdup(r->pool, "/");
for (i = strlen(p) - 1; i > 0; i--)
if (p[i] == '/')
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);
oidc_dir_cfg *d = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
if (d->cookie_path != NULL) {
if (strncmp(d->cookie_path, requestPath, strlen(d->cookie_path)) == 0)
rv = d->cookie_path;
else {
oidc_warn(r,
"OIDCCookiePath (%s) not a substring of request path, using request path (%s) for cookie",
d->cookie_path, requestPath);
rv = requestPath;
}
} else {
rv = requestPath;
}
return (rv);
}
/*
* 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) {
oidc_cfg *c = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
char *headerString, *currentCookies, *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;Path=%s%s%s%s%s", cookieName,
cookieValue,
oidc_util_get_cookie_path(r),
(expiresString == NULL) ?
"" : apr_psprintf(r->pool, "; expires=%s", expiresString),
c->cookie_domain != NULL ?
apr_psprintf(r->pool, ";Domain=%s", c->cookie_domain) : "",
((apr_strnatcasecmp("https", oidc_get_current_url_scheme(r)) == 0) ?
";Secure" : ""),
c->cookie_http_only != FALSE ? ";HttpOnly" : "");
/* use r->err_headers_out so we always print our headers (even on 302 redirect) - headers_out only prints on 2xx responses */
apr_table_add(r->err_headers_out, "Set-Cookie", headerString);
/* see if we need to add it to existing cookies */
if ((currentCookies = (char *) apr_table_get(r->headers_in, "Cookie"))
== NULL)
apr_table_add(r->headers_in, "Cookie", headerString);
else
apr_table_set(r->headers_in, "Cookie",
(apr_pstrcat(r->pool, headerString, ";", currentCookies, NULL)));
/* do some logging */
oidc_debug(r, "adding outgoing header: Set-Cookie: %s", 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,
(char *) apr_table_get(r->headers_in, "Cookie"));
if (cookies != NULL) {
/* tokenize on ; to find the cookie we want */
cookie = apr_strtok(cookies, ";", &tokenizerCtx);
do {
while (cookie != NULL && *cookie == ' ')
cookie++;
/* see if we've found the cookie that we're looking for */
if (strncmp(cookie, cookieName, strlen(cookieName)) == 0) {
/* 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, ";", &tokenizerCtx);
} while (cookie != NULL);
}
/* log what we've found */
oidc_debug(r, "returning \"%s\" = %s", cookieName, rv ? apr_psprintf(r->pool, "\"%s\"", rv) : "");
return rv;
}
/*
* 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));
apr_uri_parse(r->pool, url, &uri);
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_pstrndup(r->pool, r->args, strlen(r->args));
p = apr_strtok(args, "&", &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, "&", &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)) {
char *s_value = json_dumps(value, JSON_ENCODE_ANY);
oidc_error(r, "%s: response contained an \"%s\" entry with value: \"%s\"",
log, key, s_value);
free(s_value);
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, "error",
"oidc_util_check_json_error") == TRUE) {
oidc_util_json_string_print(r, json, "error_description",
"oidc_util_check_json_error");
return TRUE;
}
return FALSE;
}
/*
* 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) {
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", json_error.text);
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;
}
// 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, int 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);
if (ap_pass_brigade(r->output_filters, bb) != APR_SUCCESS)
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), "text/html", status_code);
}
/*
* send a user-facing error to the browser
*/
int oidc_util_html_send_error(request_rec *r, const char *error,
const char *description, int status_code) {
char *html_body = "";
if (error != NULL) {
html_body = apr_psprintf(r->pool, "%sError:
%s
",
html_body, oidc_util_html_escape(r->pool, error));
}
if (description != NULL) {
html_body = apr_psprintf(r->pool, "%sDescription:
%s
",
html_body, oidc_util_html_escape(r->pool, description));
}
return oidc_util_html_send(r, "Error", NULL, NULL, html_body, status_code);
}
/*
* read all bytes from the HTTP request
*/
static apr_byte_t oidc_util_read(request_rec *r, const char **rbuf) {
if (ap_setup_client_block(r, REQUEST_CHUNKED_ERROR) != OK)
return FALSE;
if (ap_should_client_block(r)) {
char argsbuffer[HUGE_STRING_LEN];
int rsize, len_read, rpos = 0;
long length = r->remaining;
*rbuf = apr_pcalloc(r->pool, length + 1);
while ((len_read = ap_get_client_block(r, argsbuffer,
sizeof(argsbuffer))) > 0) {
if ((rpos + len_read) > length) {
rsize = length - rpos;
} else {
rsize = len_read;
}
memcpy((char*) *rbuf + rpos, argsbuffer, rsize);
rpos += rsize;
}
}
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, const char *data) {
const char *key, *val, *p = data;
while (p && *p && (val = ap_getword(r->pool, &p, '&'))) {
key = ap_getword(r->pool, &val, '=');
key = oidc_util_unescape_string(r, key);
val = oidc_util_unescape_string(r, val);
apr_table_set(table, key, val);
}
oidc_debug(r, "parsed: \"%s\" in to %d elements", data,
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) {
const 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, 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(r->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;
}
/*
* 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 (strcmp(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] == '/')) ?
n2 : (((n2 == n1 + 1) && (b[n2 - 1] == '/')) ? 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 (strcmp(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 an HTTP header 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) {
/*
* 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 = ' ';
/* do some logging about this event */
oidc_debug(r, "setting header \"%s: %s\"", s_name, s_value);
/* now set the actual header name/value */
apr_table_set(r->headers_in, 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 */
char *s_value = json_dumps(j_value, 0);
oidc_util_set_app_info(r, s_key, s_value, claim_prefix, as_header,
as_env_var);
free(s_value);
/* 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, "");
int 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 *response_type, const char *match) {
apr_hash_t *ht = oidc_util_spaced_string_to_hashtable(pool, response_type);
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;
}
/*
* merge two JSON objects
*/
apr_byte_t oidc_util_json_merge(json_t *src, json_t *dst) {
const char *key;
json_t *value = NULL;
void *iter = NULL;
if ((src == NULL) || (dst == NULL)) return FALSE;
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);
}
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, '&'))) {
key = ap_getword(pool, &val, '=');
ap_unescape_url((char *) key);
ap_unescape_url((char *) val);
apr_table_addn(table, key, val);
}
}
}
/*
* 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,
const char *client_secret, const char *hash_algo) {
apr_jwt_error_t err;
apr_jwk_t *jwk = NULL;
unsigned char *key = NULL;
unsigned int key_len;
apr_hash_t *result = NULL;
result = (keys != NULL) ? apr_hash_copy(pool, keys) : apr_hash_make(pool);
if (client_secret != NULL) {
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 */
apr_jws_hash_bytes(pool, hash_algo,
(const unsigned char *) client_secret,
strlen(client_secret), &key, &key_len, &err);
}
if (apr_jwk_parse_symmetric_key(pool, key, key_len, &jwk, &err) == TRUE) {
apr_hash_set(result, jwk->kid, APR_HASH_KEY_STRING, jwk);
}
}
return result;
}
/*
* 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 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;
}
mod_auth_openidc-1.8.5/src/authz.c 0000644 0001750 0001750 00000024003 12563446341 017313 0 ustar zandbelt zandbelt /*
* 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-2015 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 - hzandbelt@pingidentity.com
*/
#include
#include
#include
#include "mod_auth_openidc.h"
#include
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
*/
static 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) == ':') {
/* 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) == '~') {
/* skip the tilde */
spec_c++;
if (oidc_authz_match_expression(r, spec_c, val) == TRUE)
return TRUE;
}
iter = json_object_iter_next((json_t *) claims, iter);
}
return FALSE;
}
/*
* Apache <2.4 authorization routine: match the claims from the authenticated user against the Require primitive
*/
int oidc_authz_worker(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;
/* 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);
if (apr_strnatcasecmp(token, OIDC_REQUIRE_NAME) != 0) {
continue;
}
/* ok, we have a "Require claim" 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.
*/
while (*requirement) {
token = ap_getword_conf(r->pool, &requirement);
count_oauth_claims++;
oidc_debug(r, "evaluating claim specification: %s", token);
if (oidc_authz_match_claim(r, token, claims) == TRUE) {
/* if *any* claim matches, then authorization has succeeded and all of the others are ignored */
oidc_debug(r, "require claim '%s' matched", token);
return OK;
}
}
}
/* if there weren't any "Require claim" directives, we're irrelevant */
if (!have_oauthattr) {
oidc_debug(r, "no claim 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' 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;
}
#if MODULE_MAGIC_NUMBER_MAJOR >= 20100714
/*
* 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) {
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 specification: %s", w);
/* see if we can match any of out input claims against this Require'd value */
if (oidc_authz_match_claim(r, w, claims) == TRUE) {
oidc_debug(r, "require claim '%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' missing specification(s) in configuration, denying");
}
return AUTHZ_DENIED;
}
#endif
mod_auth_openidc-1.8.5/src/session.c 0000644 0001750 0001750 00000042250 12545543560 017650 0 ustar zandbelt zandbelt /*
* 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-2015 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 - hzandbelt@pingidentity.com
*/
#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 "remote-user"
/* the name of the session expiry attribute in the session */
#define OIDC_SESSION_EXPIRY_KEY "oidc-expiry"
/* the name of the uuid attribute in the session */
#define OIDC_SESSION_UUID_KEY "oidc-uuid"
static apr_status_t (*ap_session_load_fn)(request_rec *r, session_rec **z) =
NULL;
static apr_status_t (*ap_session_get_fn)(request_rec *r, session_rec *z,
const char *key, const char **value) = NULL;
static apr_status_t (*ap_session_set_fn)(request_rec *r, session_rec *z,
const char *key, const char *value) = NULL;
static apr_status_t (*ap_session_save_fn)(request_rec *r, session_rec *z) = NULL;
apr_status_t oidc_session_load(request_rec *r, session_rec **zz) {
apr_status_t rc = ap_session_load_fn(r, zz);
(*zz)->remote_user = apr_table_get((*zz)->entries,
OIDC_SESSION_REMOTE_USER_KEY);
const char *uuid = apr_table_get((*zz)->entries, OIDC_SESSION_UUID_KEY);
oidc_debug(r, "%s", uuid ? uuid : "");
if (uuid != NULL)
apr_uuid_parse((*zz)->uuid, uuid);
return rc;
}
apr_status_t oidc_session_save(request_rec *r, session_rec *z) {
oidc_session_set(r, z, OIDC_SESSION_REMOTE_USER_KEY, z->remote_user);
char key[APR_UUID_FORMATTED_LENGTH + 1];
apr_uuid_format((char *) &key, z->uuid);
oidc_debug(r, "%s", key);
oidc_session_set(r, z, OIDC_SESSION_UUID_KEY, key);
return ap_session_save_fn(r, z);
}
apr_status_t oidc_session_get(request_rec *r, session_rec *z, const char *key,
const char **value) {
return ap_session_get_fn(r, z, key, value);
}
apr_status_t oidc_session_set(request_rec *r, session_rec *z, const char *key,
const char *value) {
return ap_session_set_fn(r, z, key, value);
}
apr_status_t oidc_session_kill(request_rec *r, session_rec *z) {
apr_table_clear(z->entries);
z->expiry = 0;
z->encoded = NULL;
return ap_session_save_fn(r, z);
}
#ifndef OIDC_SESSION_USE_APACHE_SESSIONS
// compatibility stuff copied from:
// http://contribsoft.caixamagica.pt/browser/internals/2012/apachecc/trunk/mod_session-port/src/util_port_compat.c
#define T_ESCAPE_URLENCODED (64)
static const unsigned char test_c_table[256] = { 32, 126, 126, 126, 126, 126,
126, 126, 126, 126, 127, 126, 126, 126, 126, 126, 126, 126, 126, 126,
126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 14, 64, 95,
70, 65, 102, 65, 65, 73, 73, 1, 64, 72, 0, 0, 74, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 104, 79, 79, 72, 79, 79, 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 79, 95, 79, 71, 0, 71, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 79, 103, 79, 65, 126, 118, 118, 118, 118, 118, 118, 118, 118, 118,
118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118,
118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118,
118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118,
118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118,
118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118,
118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118,
118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118,
118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 118,
118, 118, 118, 118, 118, 118, 118 };
#define TEST_CHAR(c, f) (test_c_table[(unsigned)(c)] & (f))
static const char c2x_table[] = "0123456789abcdef";
static APR_INLINE unsigned char *c2x(unsigned what, unsigned char prefix,
unsigned char *where) {
#if APR_CHARSET_EBCDIC
what = apr_xlate_conv_byte(ap_hdrs_to_ascii, (unsigned char)what);
#endif /*APR_CHARSET_EBCDIC*/
*where++ = prefix;
*where++ = c2x_table[what >> 4];
*where++ = c2x_table[what & 0xf];
return where;
}
static char x2c(const char *what) {
register char digit;
#if !APR_CHARSET_EBCDIC
digit =
((what[0] >= 'A') ? ((what[0] & 0xdf) - 'A') + 10 : (what[0] - '0'));
digit *= 16;
digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 : (what[1] - '0'));
#else /*APR_CHARSET_EBCDIC*/
char xstr[5];
xstr[0]='0';
xstr[1]='x';
xstr[2]=what[0];
xstr[3]=what[1];
xstr[4]='\0';
digit = apr_xlate_conv_byte(ap_hdrs_from_ascii,
0xFF & strtol(xstr, NULL, 16));
#endif /*APR_CHARSET_EBCDIC*/
return (digit);
}
#ifndef WIN32
AP_DECLARE(char *) ap_escape_urlencoded_buffer(char *copy, const char *buffer) {
const unsigned char *s = (const unsigned char *) buffer;
unsigned char *d = (unsigned char *) copy;
unsigned c;
while ((c = *s)) {
if (TEST_CHAR(c, T_ESCAPE_URLENCODED)) {
d = c2x(c, '%', d);
} else if (c == ' ') {
*d++ = '+';
} else {
*d++ = c;
}
++s;
}
*d = '\0';
return copy;
}
#endif
static int oidc_session_unescape_url(char *url, const char *forbid,
const char *reserved) {
register int badesc, badpath;
char *x, *y;
badesc = 0;
badpath = 0;
/* Initial scan for first '%'. Don't bother writing values before
* seeing a '%' */
y = strchr(url, '%');
if (y == NULL) {
return OK;
}
for (x = y; *y; ++x, ++y) {
if (*y != '%') {
*x = *y;
} else {
if (!apr_isxdigit(*(y + 1)) || !apr_isxdigit(*(y + 2))) {
badesc = 1;
*x = '%';
} else {
char decoded;
decoded = x2c(y + 1);
if ((decoded == '\0')
|| (forbid && ap_strchr_c(forbid, decoded))) {
badpath = 1;
*x = decoded;
y += 2;
} else if (reserved && ap_strchr_c(reserved, decoded)) {
*x++ = *y++;
*x++ = *y++;
*x = *y;
} else {
*x = decoded;
y += 2;
}
}
}
}
*x = '\0';
if (badesc) {
return HTTP_BAD_REQUEST;
} else if (badpath) {
return HTTP_NOT_FOUND;
} else {
return OK;
}
}
#ifndef WIN32
AP_DECLARE(int) ap_unescape_urlencoded(char *query) {
char *slider;
/* replace plus with a space */
if (query) {
for (slider = query; *slider; slider++) {
if (*slider == '+') {
*slider = ' ';
}
}
}
/* unescape everything else */
return oidc_session_unescape_url(query, NULL, NULL);
}
#endif
// copied from mod_session.c
static apr_status_t oidc_session_identity_decode(request_rec * r,
session_rec * z) {
char *last = NULL;
char *encoded, *pair;
const char *sep = "&";
//oidc_debug(r, "decoding %s", z->encoded);
/* sanity check - anything to decode? */
if (!z->encoded) {
return APR_SUCCESS;
}
/* decode what we have */
encoded = apr_pstrdup(r->pool, z->encoded);
pair = apr_strtok(encoded, sep, &last);
while (pair && pair[0]) {
char *plast = NULL;
const char *psep = "=";
char *key = apr_strtok(pair, psep, &plast);
char *val = apr_strtok(NULL, psep, &plast);
//oidc_debug(r, "decoding %s=%s", key, val);
if (key && *key) {
if (!val || !*val) {
apr_table_unset(z->entries, key);
} else if (!ap_unescape_urlencoded(key)
&& !ap_unescape_urlencoded(val)) {
if (!strcmp(OIDC_SESSION_EXPIRY_KEY, key)) {
z->expiry = (apr_time_t) apr_atoi64(val);
} else {
apr_table_set(z->entries, key, val);
}
}
}
pair = apr_strtok(NULL, sep, &last);
}
z->encoded = NULL;
return APR_SUCCESS;
}
// copied from mod_session.c
static int oidc_identity_count(int *count, const char *key, const char *val) {
*count += strlen(key) * 3 + strlen(val) * 3 + 1;
return 1;
}
// copied from mod_session.c
static int oidc_identity_concat(char *buffer, const char *key, const char *val) {
char *slider = buffer;
int length = strlen(slider);
slider += length;
if (length) {
*slider = '&';
slider++;
}
ap_escape_urlencoded_buffer(slider, key);
slider += strlen(slider);
*slider = '=';
slider++;
ap_escape_urlencoded_buffer(slider, val);
return 1;
}
// copied from mod_session.c
static apr_status_t oidc_session_identity_encode(request_rec * r,
session_rec * z) {
char *buffer = NULL;
int length = 0;
if (z->expiry) {
char *expiry = apr_psprintf(z->pool, "%" APR_INT64_T_FMT, z->expiry);
apr_table_setn(z->entries, OIDC_SESSION_EXPIRY_KEY, expiry);
}
apr_table_do(
(int (*)(void *, const char *, const char *)) oidc_identity_count,
&length, z->entries, NULL);
buffer = apr_pcalloc(r->pool, length + 1);
apr_table_do(
(int (*)(void *, const char *, const char *)) oidc_identity_concat,
buffer, z->entries, NULL);
z->encoded = buffer;
return APR_SUCCESS;
}
/* load the session from the cache using the cookie as the index */
static apr_status_t oidc_session_load_cache(request_rec *r, session_rec *z) {
oidc_cfg *c = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
oidc_dir_cfg *d = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
/* get the cookie that should be our uuid/key */
char *uuid = oidc_util_get_cookie(r, d->cookie);
/* get the string-encoded session from the cache based on the key */
if (uuid != NULL) {
c->cache->get(r, OIDC_CACHE_SECTION_SESSION, uuid, &z->encoded);
//oidc_util_set_cookie(r, d->cookie, "");
}
return (z->encoded != NULL) ? APR_SUCCESS : APR_EGENERAL;
}
/*
* save the session to the cache using a cookie for the index
*/
static apr_status_t oidc_session_save_cache(request_rec *r, session_rec *z) {
oidc_cfg *c = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
oidc_dir_cfg *d = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
char key[APR_UUID_FORMATTED_LENGTH + 1];
apr_uuid_format((char *) &key, z->uuid);
if (z->encoded && z->encoded[0]) {
/* set the uuid in the cookie */
oidc_util_set_cookie(r, d->cookie, key, -1);
/* store the string-encoded session in the cache */
c->cache->set(r, OIDC_CACHE_SECTION_SESSION, key, z->encoded,
z->expiry);
} else {
/* clear the cookie */
oidc_util_set_cookie(r, d->cookie, "", 0);
/* remove the session from the cache */
c->cache->set(r, OIDC_CACHE_SECTION_SESSION, key, NULL, 0);
}
return APR_SUCCESS;
}
static apr_status_t oidc_session_load_cookie(request_rec *r, session_rec *z) {
oidc_dir_cfg *d = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
char *cookieValue = oidc_util_get_cookie(r, d->cookie);
if (cookieValue != NULL) {
if (oidc_base64url_decode_decrypt_string(r, (char **) &z->encoded,
cookieValue) <= 0) {
//oidc_util_set_cookie(r, d->cookie, "");
oidc_warn(r, "cookie value possibly corrupted");
return APR_EGENERAL;
}
}
return APR_SUCCESS;
}
static apr_status_t oidc_session_save_cookie(request_rec *r, session_rec *z) {
oidc_dir_cfg *d = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
char *cookieValue = "";
if (z->encoded && z->encoded[0]) {
if (oidc_encrypt_base64url_encode_string(r, &cookieValue, z->encoded) <= 0) {
oidc_error(r, "oidc_encrypt_base64url_encode_string failed");
return APR_EGENERAL;
}
}
oidc_util_set_cookie(r, d->cookie, cookieValue, -1);
return APR_SUCCESS;
}
/*
* load the session from the request context, create a new one if no luck
*/
static apr_status_t oidc_session_load_22(request_rec *r, session_rec **zz) {
oidc_cfg *c = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
/* first see if this is a sub-request and it was set already in the main request */
if (((*zz) = (session_rec *) oidc_request_state_get(r, "session")) != NULL) {
oidc_debug(r, "loading session from request state");
return APR_SUCCESS;
}
/* allocate space for the session object and fill it */
session_rec *z = (*zz = apr_pcalloc(r->pool, sizeof(session_rec)));
z->pool = r->pool;
/* get a new uuid for this session */
z->uuid = (apr_uuid_t *) apr_pcalloc(z->pool, sizeof(apr_uuid_t));
apr_uuid_get(z->uuid);
z->remote_user = NULL;
z->encoded = NULL;
z->entries = apr_table_make(z->pool, 10);
apr_status_t rc = APR_SUCCESS;
if (c->session_type == OIDC_SESSION_TYPE_22_SERVER_CACHE) {
/* load the session from the cache */
rc = oidc_session_load_cache(r, z);
} else if (c->session_type == OIDC_SESSION_TYPE_22_CLIENT_COOKIE) {
/* load the session from a self-contained cookie */
rc = oidc_session_load_cookie(r, z);
} else {
oidc_error(r, "oidc_session_load_22: unknown session type: %d",
c->session_type);
rc = APR_EGENERAL;
}
/* see if it worked out */
if (rc != APR_SUCCESS)
return rc;
/* yup, now decode the info */
if (oidc_session_identity_decode(r, z) != APR_SUCCESS)
return APR_EGENERAL;
/* check whether it has expired */
if (apr_time_now() > z->expiry) {
oidc_warn(r, "session restored from cache has expired");
apr_table_clear(z->entries);
z->expiry = 0;
z->encoded = NULL;
return APR_EGENERAL;
}
/* store this session in the request context, so it is available to sub-requests */
oidc_request_state_set(r, "session", (const char *) z);
return APR_SUCCESS;
}
/*
* save a session to the cache
*/
static apr_status_t oidc_session_save_22(request_rec *r, session_rec *z) {
oidc_cfg *c = ap_get_module_config(r->server->module_config,
&auth_openidc_module);
/* encode the actual state in to the encoded string */
oidc_session_identity_encode(r, z);
/* store this session in the request context, so it is available to sub-requests as a quicker-than-file-backend cache */
oidc_request_state_set(r, "session", (const char *) z);
apr_status_t rc = APR_SUCCESS;
if (c->session_type == OIDC_SESSION_TYPE_22_SERVER_CACHE) {
/* store the session in the cache */
rc = oidc_session_save_cache(r, z);
} else if (c->session_type == OIDC_SESSION_TYPE_22_CLIENT_COOKIE) {
/* store the session in a self-contained cookie */
rc = oidc_session_save_cookie(r, z);
} else {
oidc_error(r, "unknown session type: %d", c->session_type);
rc = APR_EGENERAL;
}
return rc;
}
/*
* get a value from the session based on the name from a name/value pair
*/
static apr_status_t oidc_session_get_22(request_rec *r, session_rec *z,
const char *key, const char **value) {
/* just return the value for the key */
*value = apr_table_get(z->entries, key);
return OK;
}
/*
* set a name/value key pair in the session
*/
static apr_status_t oidc_session_set_22(request_rec *r, session_rec *z,
const char *key, const char *value) {
/* only set it if non-NULL, otherwise delete the entry */
if (value) {
apr_table_set(z->entries, key, value);
} else {
apr_table_unset(z->entries, key);
}
return OK;
}
/*
* session initialization for pre-2.4
*/
apr_status_t oidc_session_init() {
if (!ap_session_load_fn || !ap_session_get_fn || !ap_session_set_fn
|| !ap_session_save_fn) {
ap_session_load_fn = oidc_session_load_22;
ap_session_get_fn = oidc_session_get_22;
ap_session_set_fn = oidc_session_set_22;
ap_session_save_fn = oidc_session_save_22;
}
return OK;
}
#else
/*
* use Apache 2.4 session handling
*/
#include
apr_status_t oidc_session_init() {
if (!ap_session_load_fn || !ap_session_get_fn || !ap_session_set_fn || !ap_session_save_fn) {
ap_session_load_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_load);
ap_session_get_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_get);
ap_session_set_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_set);
ap_session_save_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_save);
}
return OK;
}
#endif
mod_auth_openidc-1.8.5/src/metadata.c 0000664 0001750 0001750 00000121515 12577725501 017753 0 ustar zandbelt zandbelt /*
* 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-2015 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 - hzandbelt@pingidentity.com
*/
#include
#include
#include
#include
#include
#include
#include "mod_auth_openidc.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"
/*
* 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] == '/')
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, '.');
*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, &buf) == FALSE)
return FALSE;
/* decode the JSON contents of the buffer */
json_error_t json_error;
*result = json_loads(buf, 0, &json_error);
if (*result == NULL) {
/* something went wrong */
oidc_error(r, "JSON parsing (%s) returned an error: %s", path,
json_error.text);
return FALSE;
}
if (!json_is_object(*result)) {
/* oops, no JSON */
oidc_error(r, "parsed JSON from (%s) did not contain a JSON object",
path);
json_decref(*result);
return FALSE;
}
return TRUE;
}
/*
* 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, const json_t *json, const char *key,
apr_byte_t is_mandatory) {
apr_uri_t uri;
json_t *entry = NULL;
entry = json_object_get(json, key);
if (entry == NULL) {
if (is_mandatory) {
oidc_error(r,
"%s (%s) JSON metadata does not contain the mandatory \"%s\" entry",
type, issuer, key);
}
return (!is_mandatory);
}
if (!json_is_string(entry)) {
oidc_error(r,
"%s (%s) JSON metadata contains a \"%s\" entry, but it is not a string value",
type, issuer, key);
return FALSE;
}
if (apr_uri_parse(r->pool, json_string_value(entry), &uri) != APR_SUCCESS) {
oidc_error(r,
"%s (%s) JSON metadata contains a \"%s\" entry, but it is not a valid URI",
type, issuer, key);
return FALSE;
}
return TRUE;
}
/*
* check to see if JSON provider metadata is valid
*/
static apr_byte_t oidc_metadata_provider_is_valid(request_rec *r,
json_t *j_provider, const char *issuer) {
/* get the "issuer" from the provider metadata and double-check that it matches what we looked for */
json_t *j_issuer = json_object_get(j_provider, "issuer");
if ((j_issuer == NULL) || (!json_is_string(j_issuer))) {
oidc_error(r,
"provider (%s) JSON metadata did not contain an \"issuer\" string",
issuer);
return FALSE;
}
/* check that the issuer matches */
if (issuer != NULL) {
if (oidc_util_issuer_match(issuer, json_string_value(j_issuer)) == FALSE) {
oidc_warn(r,
"requested issuer (%s) does not match the \"issuer\" value in the provider metadata file: %s",
issuer, json_string_value(j_issuer));
//return FALSE;
}
}
/* verify that the provider supports the a flow that we implement */
json_t *j_response_types_supported = json_object_get(j_provider,
"response_types_supported");
if ((j_response_types_supported != NULL)
&& (json_is_array(j_response_types_supported))) {
int i = 0;
for (i = 0; i < json_array_size(j_response_types_supported); i++) {
json_t *elem = json_array_get(j_response_types_supported, i);
if (!json_is_string(elem)) {
oidc_error(r,
"unhandled in-array JSON non-string object type [%d]",
elem->type);
continue;
}
if (oidc_proto_flow_is_supported(r->pool, json_string_value(elem)))
break;
}
if (i == json_array_size(j_response_types_supported)) {
oidc_warn(r,
"could not find a supported response type in provider metadata (%s) for entry \"response_types_supported\"; assuming that \"code\" flow is supported...",
issuer);
//return FALSE;
}
} else {
oidc_warn(r,
"provider (%s) JSON metadata did not contain a \"response_types_supported\" array; assuming that \"code\" flow is supported...",
issuer);
// TODO: hey, this is required-by-spec stuff right?
}
/* verify that the provider supports a response_mode that we implement */
json_t *response_modes_supported = json_object_get(j_provider,
"response_modes_supported");
if ((response_modes_supported != NULL)
&& (json_is_array(response_modes_supported))) {
int i = 0;
for (i = 0; i < json_array_size(response_modes_supported); i++) {
json_t *elem = json_array_get(response_modes_supported, 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), "fragment") == 0)
|| (apr_strnatcmp(json_string_value(elem), "query") == 0)
|| (apr_strnatcmp(json_string_value(elem), "form_post") == 0))
break;
}
if (i == json_array_size(response_modes_supported)) {
oidc_warn(r,
"could not find a supported response mode in provider metadata (%s) for entry \"response_modes_supported\"",
issuer);
return FALSE;
}
} else {
oidc_debug(r,
"provider (%s) JSON metadata did not contain a \"response_modes_supported\" array; assuming that \"fragment\" and \"query\" are supported",
issuer);
}
/* check the required authorization endpoint */
if (oidc_metadata_is_valid_uri(r, "provider", issuer, j_provider,
"authorization_endpoint", TRUE) == FALSE)
return FALSE;
/* check the optional token endpoint */
if (oidc_metadata_is_valid_uri(r, "provider", issuer, j_provider,
"token_endpoint", FALSE) == FALSE)
return FALSE;
/* check the optional user info endpoint */
if (oidc_metadata_is_valid_uri(r, "provider", issuer, j_provider,
"userinfo_endpoint", FALSE) == FALSE)
return FALSE;
/* check the optional JWKs URI */
if (oidc_metadata_is_valid_uri(r, "provider", issuer, j_provider,
"jwks_uri", FALSE) == FALSE)
return FALSE;
/* find out what type of authentication the token endpoint supports (we only support post or basic) */
json_t *j_token_endpoint_auth_methods_supported = json_object_get(
j_provider, "token_endpoint_auth_methods_supported");
if ((j_token_endpoint_auth_methods_supported == NULL)
|| (!json_is_array(j_token_endpoint_auth_methods_supported))) {
oidc_debug(r,
"provider (%s) JSON metadata did not contain a \"token_endpoint_auth_methods_supported\" array, assuming \"client_secret_basic\" is supported",
issuer);
} else {
int i;
for (i = 0;
i < json_array_size(j_token_endpoint_auth_methods_supported);
i++) {
json_t *elem = json_array_get(
j_token_endpoint_auth_methods_supported, i);
if (!json_is_string(elem)) {
oidc_warn(r,
"unhandled in-array JSON object type [%d] in provider (%s) metadata for entry \"token_endpoint_auth_methods_supported\"",
elem->type, issuer);
continue;
}
if (strcmp(json_string_value(elem), "client_secret_post") == 0) {
break;
}
if (strcmp(json_string_value(elem), "client_secret_basic") == 0) {
break;
}
}
if (i == json_array_size(j_token_endpoint_auth_methods_supported)) {
oidc_error(r,
"could not find a supported value [client_secret_post|client_secret_basic] in provider (%s) metadata for entry \"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) {
/* get a handle to the client_id we need to use for this provider */
json_t *j_client_id = json_object_get(j_client, "client_id");
if ((j_client_id == NULL) || (!json_is_string(j_client_id))) {
oidc_error(r,
"client (%s) JSON metadata did not contain a \"client_id\" string",
issuer);
return FALSE;
}
/* get a handle to the client_secret we need to use for this provider */
json_t *j_client_secret = json_object_get(j_client, "client_secret");
if ((j_client_secret == NULL) || (!json_is_string(j_client_secret))) {
oidc_warn(r,
"client (%s) JSON metadata did not contain a \"client_secret\" string",
issuer);
//return FALSE;
}
/* the expiry timestamp from the JSON object */
json_t *expires_at = json_object_get(j_client, "client_secret_expires_at");
if ((expires_at == NULL) || (!json_is_integer(expires_at))) {
oidc_debug(r,
"client (%s) metadata did not contain a \"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 (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, "keys");
if ((keys == NULL) || (!json_is_array(keys))) {
oidc_error(r,
"JWKs JSON metadata obtained from URL \"%s\" did not contain a \"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,
apr_jose_is_supported_function_t jose_is_supported_function) {
json_t *value = json_object_get(j_conf, key);
if (value != NULL) {
if (!json_is_string(value)) {
oidc_error(r,
"(%s) JSON conf data has \"%s\" entry but it is not a string",
issuer, key);
return FALSE;
}
if (jose_is_supported_function(r->pool,
json_string_value(value)) == FALSE) {
oidc_error(r,
"(%s) JSON conf data has \"%s\" entry but it contains an unsupported algorithm or encryption type: \"%s\"",
issuer, key, json_string_value(value));
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,
"id_token_signed_response_alg",
apr_jws_algorithm_is_supported) == FALSE)
return FALSE;
if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer,
"id_token_encrypted_response_alg",
apr_jwe_algorithm_is_supported) == FALSE)
return FALSE;
if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer,
"id_token_encrypted_response_enc",
apr_jwe_encryption_is_supported) == FALSE)
return FALSE;
if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer,
"userinfo_signed_response_alg",
apr_jws_algorithm_is_supported) == FALSE)
return FALSE;
if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer,
"userinfo_encrypted_response_alg",
apr_jwe_algorithm_is_supported) == FALSE)
return FALSE;
if (oidc_metadata_conf_jose_is_supported(r, j_conf, issuer,
"userinfo_encrypted_response_enc",
apr_jwe_encryption_is_supported) == FALSE)
return FALSE;
return TRUE;
}
/*
* write JSON metadata to a file
*/
static apr_byte_t oidc_metadata_file_write(request_rec *r, const char *path,
const char *data) {
// TODO: completely erase the contents of the file if it already exists....
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_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;
}
/*
* 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, const char **response) {
/* get a handle to the directory config */
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
/* assemble the JSON registration request */
json_t *data = json_object();
json_object_set_new(data, "client_name",
json_string(provider->client_name));
json_object_set_new(data, "redirect_uris",
json_pack("[s]", cfg->redirect_uri));
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, "response_types", response_types);
if (provider->client_contact != NULL) {
json_object_set_new(data, "contacts",
json_pack("[s]", provider->client_contact));
}
if (provider->client_jwks_uri) {
json_object_set_new(data, "jwks_uri",
json_string(provider->client_jwks_uri));
} else if (cfg->public_keys != NULL) {
json_object_set_new(data, "jwks_uri",
json_string(
apr_psprintf(r->pool, "%s?jwks=rsa",
cfg->redirect_uri)));
}
if (provider->id_token_signed_response_alg != NULL) {
json_object_set_new(data, "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, "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, "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, "userinfo_signed_response_alg",
json_string(provider->userinfo_signed_response_alg));
}
if (provider->userinfo_encrypted_response_alg != NULL) {
json_object_set_new(data, "userinfo_encrypted_response_alg",
json_string(provider->userinfo_encrypted_response_alg));
}
if (provider->userinfo_encrypted_response_enc != NULL) {
json_object_set_new(data, "userinfo_encrypted_response_enc",
json_string(provider->userinfo_encrypted_response_enc));
}
json_object_set_new(data, "initiate_login_uri",
json_string(cfg->redirect_uri));
json_object_set_new(data, "logout_uri",
json_string(
apr_psprintf(r->pool, "%s?logout=%s", cfg->redirect_uri,
OIDC_GET_STYLE_LOGOUT_PARAM_VALUE)));
if (cfg->default_slo_url != NULL) {
json_object_set_new(data, "post_logout_redirect_uris",
json_pack("[s]", cfg->default_slo_url));
}
if (provider->registration_endpoint_json != NULL) {
json_error_t json_error;
json_t *json = json_loads(provider->registration_endpoint_json, 0,
&json_error);
if (json == NULL) {
oidc_error(r, "JSON parsing returned an error: %s",
json_error.text);
} else {
if (!json_is_object(json)) {
oidc_error(r, "parsed JSON did not contain a JSON object");
} else {
oidc_util_json_merge(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,
dir_cfg->pass_cookies) == 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) {
const char *response = NULL;
/* get a handle to the directory config */
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
/* 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, dir_cfg->pass_cookies) == 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 */
cfg->cache->set(r, OIDC_CACHE_SECTION_JWKS,
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 */
const char *value = NULL;
cfg->cache->get(r, OIDC_CACHE_SECTION_JWKS,
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,
const char **response) {
/* get a handle to the directory config */
oidc_dir_cfg *dir_cfg = ap_get_module_config(r->per_dir_config,
&auth_openidc_module);
/* 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,
dir_cfg->pass_cookies) == 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, *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 */
const 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);
/* see if we have valid metadata already, if so, return it */
if (oidc_metadata_file_read_json(r, provider_path, j_provider) == TRUE) {
/* return the validation result */
return oidc_metadata_provider_is_valid(r, *j_provider, issuer);
}
if (!allow_discovery) {
oidc_warn(r,
"no metadata found for the requested issuer (%s), and Discovery is not allowed",
issuer);
return FALSE;
}
// TODO: how to do validity/expiry checks on provider metadata
/* 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] != '/' ? "/" : "");
/* 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)
return FALSE;
/* since it is valid, write the obtained provider metadata file */
if (oidc_metadata_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) \"registration_endpoint\" string",
issuer);
return FALSE;
}
/* try and get client metadata by registering the client at the registration endpoint */
const 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_metadata_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(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] == '.')
continue;
/* skip other non-provider entries */
char *ext = strrchr(fi.name, '.');
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 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, 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, "issuer",
&provider->issuer, NULL);
}
if (provider->authorization_endpoint_url == NULL) {
/* get a handle to the authorization endpoint */
oidc_json_object_get_string(r->pool, j_provider,
"authorization_endpoint", &provider->authorization_endpoint_url,
NULL);
}
if (provider->token_endpoint_url == NULL) {
/* get a handle to the token endpoint */
oidc_json_object_get_string(r->pool, j_provider, "token_endpoint",
&provider->token_endpoint_url, NULL);
}
if (provider->userinfo_endpoint_url == NULL) {
/* get a handle to the user_info endpoint */
oidc_json_object_get_string(r->pool, j_provider, "userinfo_endpoint",
&provider->userinfo_endpoint_url, NULL);
}
if (provider->jwks_uri == NULL) {
/* get a handle to the jwks_uri endpoint */
oidc_json_object_get_string(r->pool, j_provider, "jwks_uri",
&provider->jwks_uri, NULL);
}
if (provider->registration_endpoint_url == NULL) {
/* get a handle to the client registration endpoint */
oidc_json_object_get_string(r->pool, j_provider,
"registration_endpoint", &provider->registration_endpoint_url,
NULL);
}
if (provider->check_session_iframe == NULL) {
/* get a handle to the check session iframe */
oidc_json_object_get_string(r->pool, j_provider, "check_session_iframe",
&provider->check_session_iframe, NULL);
}
if (provider->end_session_endpoint == NULL) {
/* get a handle to the end session endpoint */
oidc_json_object_get_string(r->pool, j_provider, "end_session_endpoint",
&provider->end_session_endpoint, NULL);
}
if (provider->token_endpoint_auth == NULL) {
/* find a supported token_endpoint_auth_method in the provider metadata */
json_t *j_token_endpoint_auth_methods_supported = json_object_get(
j_provider, "token_endpoint_auth_methods_supported");
const char *token_endpoint_auth = NULL;
/* loop through the array provided by the issuer and see if there's a supported method */
if ((j_token_endpoint_auth_methods_supported != NULL)
&& (json_is_array(j_token_endpoint_auth_methods_supported))) {
int i;
for (i = 0;
i < json_array_size(j_token_endpoint_auth_methods_supported);
i++) {
json_t *elem = json_array_get(
j_token_endpoint_auth_methods_supported, i);
if (!json_is_string(elem)) {
oidc_error(r,
"unhandled in-array JSON object type [%d] in provider metadata for entry \"token_endpoint_auth_methods_supported\"",
elem->type);
continue;
}
/* take first supported method and prefer post over basic */
if ((apr_strnatcmp(json_string_value(elem),
"client_secret_post") == 0)
|| (apr_strnatcmp(json_string_value(elem),
"client_secret_basic") == 0)) {
token_endpoint_auth = json_string_value(elem);
break;
}
}
}
/* store the method if found */
if (token_endpoint_auth != NULL) {
provider->token_endpoint_auth = apr_pstrdup(r->pool,
token_endpoint_auth);
}
}
return TRUE;
}
/*
* 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_json_object_get_string(r->pool, j_conf, "client_jwks_uri",
&provider->client_jwks_uri, cfg->provider.client_jwks_uri);
oidc_json_object_get_string(r->pool, j_conf, "id_token_signed_response_alg",
&provider->id_token_signed_response_alg,
cfg->provider.id_token_signed_response_alg);
oidc_json_object_get_string(r->pool, j_conf,
"id_token_encrypted_response_alg",
&provider->id_token_encrypted_response_alg,
cfg->provider.id_token_encrypted_response_alg);
oidc_json_object_get_string(r->pool, j_conf,
"id_token_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_json_object_get_string(r->pool, j_conf, "userinfo_signed_response_alg",
&provider->userinfo_signed_response_alg,
cfg->provider.userinfo_signed_response_alg);
oidc_json_object_get_string(r->pool, j_conf,
"userinfo_encrypted_response_alg",
&provider->userinfo_encrypted_response_alg,
cfg->provider.userinfo_encrypted_response_alg);
oidc_json_object_get_string(r->pool, j_conf,
"userinfo_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_json_object_get_int(r->pool, j_conf, "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, "scope", &provider->scope,
cfg->provider.scope);
/* see if we've got a custom JWKs refresh interval */
oidc_json_object_get_int(r->pool, j_conf, "jwks_refresh_interval",
&provider->jwks_refresh_interval,
cfg->provider.jwks_refresh_interval);
/* see if we've got a custom IAT slack interval */
oidc_json_object_get_int(r->pool, j_conf, "idtoken_iat_slack",
&provider->idtoken_iat_slack, cfg->provider.idtoken_iat_slack);
/* see if we've got a custom max session duration */
oidc_json_object_get_int(r->pool, j_conf, "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, "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, "token_endpoint_params",
&provider->token_endpoint_params,
cfg->provider.token_endpoint_params);
/* get the response mode to use */
oidc_json_object_get_string(r->pool, j_conf, "response_mode",
&provider->response_mode, cfg->provider.response_mode);
/* get the client name */
oidc_json_object_get_string(r->pool, j_conf, "client_name",
&provider->client_name, cfg->provider.client_name);
/* get the client contact */
oidc_json_object_get_string(r->pool, j_conf, "client_contact",
&provider->client_contact, cfg->provider.client_contact);
/* get the dynamic client registration token */
oidc_json_object_get_string(r->pool, j_conf, "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, "registration_endpoint_json",
&provider->registration_endpoint_json,
cfg->provider.registration_endpoint_json);
/* get the flow to use */
oidc_json_object_get_string(r->pool, j_conf, "response_type",
&provider->response_type, NULL);
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, "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, "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, "token_endpoint_auth_method",
&token_endpoint_auth, NULL);
if (token_endpoint_auth != NULL) {
if ((apr_strnatcmp(token_endpoint_auth, "client_secret_post") == 0)
|| (apr_strnatcmp(token_endpoint_auth, "client_secret_basic")
== 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 \"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, "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, 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-1.8.5/src/jose/apr_jwt.c 0000644 0001750 0001750 00000033217 12545545263 020600 0 ustar zandbelt zandbelt /*
* 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-2015 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 - hzandbelt@pingidentity.com
*/
#include
#include "apr_jose.h"
#include
#ifdef WIN32
#define snprintf _snprintf
#endif
/*
* assemble an error report
*/
void _apr_jwt_error_set(apr_jwt_error_t *error, const char *source,
const int line, const char *function, const char *msg, ...) {
if (error == NULL)
return;
snprintf(error->source, APR_JWT_ERROR_SOURCE_LENGTH, "%s", source);
error->line = line;
snprintf(error->function, APR_JWT_ERROR_FUNCTION_LENGTH, "%s", function);
va_list ap;
va_start(ap, msg);
vsnprintf(error->text, APR_JWT_ERROR_TEXT_LENGTH, msg, ap);
va_end(ap);
}
/*
* check if a string is an element of an array of strings
*/
apr_byte_t apr_jwt_array_has_string(apr_array_header_t *haystack,
const char *needle) {
int i;
for (i = 0; i < haystack->nelts; i++) {
if (apr_strnatcmp(((const char**) haystack->elts)[i], needle) == 0)
return TRUE;
}
return FALSE;
}
/*
* base64url encode a string
*/
int apr_jwt_base64url_encode(apr_pool_t *pool, char **dst, const char *src,
int src_len, int padding) {
if ((src == NULL) || (src_len <= 0))
return -1;
int enc_len = apr_base64_encode_len(src_len);
char *enc = apr_palloc(pool, enc_len);
apr_base64_encode(enc, (const char *) src, src_len);
int i = 0;
while (enc[i] != '\0') {
if (enc[i] == '+')
enc[i] = '-';
if (enc[i] == '/')
enc[i] = '_';
if (enc[i] == '=') {
if (padding == 1) {
enc[i] = ',';
} else {
enc[i] = '\0';
enc_len--;
}
}
i++;
}
*dst = enc;
return enc_len;
}
/*
* base64url decode a string
*/
int apr_jwt_base64url_decode(apr_pool_t *pool, char **dst, const char *src,
int padding) {
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++;
}
if (padding == 1) {
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);
}
/*
* parse JSON object from string in to JWT value
*/
static apr_byte_t apr_jwt_base64url_decode_object(apr_pool_t *pool,
const char *str, apr_jwt_value_t *value, apr_jwt_error_t *err) {
/* base64url-decode the string representation into value->str */
if (apr_jwt_base64url_decode(pool, &value->str, str, 1) <= 0) {
apr_jwt_error(err, "apr_jwt_base64url_decode of (%s) failed", str);
return FALSE;
}
/* decode the string in to a JSON structure into value->json */
json_error_t json_error;
value->json = json_loads(value->str, 0, &json_error);
/* check that we've actually got a JSON value back */
if (value->json == NULL) {
apr_jwt_error(err, "JSON parsing (json_loads) failed: %s (%s)",
json_error.text, value->str);
return FALSE;
}
/* check that the value is a JSON object */
if (!json_is_object(value->json)) {
apr_jwt_error(err, "JSON value is not an object");
return FALSE;
}
return TRUE;
}
/*
* get (optional) string from JWT
*/
apr_byte_t apr_jwt_get_string(apr_pool_t *pool, json_t *json,
const char *claim_name, apr_byte_t is_mandatory, char **result,
apr_jwt_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) {
apr_jwt_error(err,
"mandatory JSON key \"%s\" was found but the type is not a string",
claim_name);
return FALSE;
}
} else if (is_mandatory) {
apr_jwt_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 apr_jwt_get_timestamp(apr_pool_t *pool, json_t *json,
const char *claim_name, apr_byte_t is_mandatory, json_int_t *result,
apr_jwt_error_t *err) {
*result = APR_JWT_CLAIM_TIME_EMPTY;
json_t *v = json_object_get(json, claim_name);
if (v != NULL) {
if (json_is_integer(v)) {
*result = json_integer_value(v);
} else if (is_mandatory) {
apr_jwt_error(err,
"mandatory JSON key \"%s\" was found but the type is not a number",
claim_name);
return FALSE;
}
} else if (is_mandatory) {
apr_jwt_error(err, "mandatory JSON key \"%s\" could not be found",
claim_name);
return FALSE;
}
return TRUE;
}
/*
* parse a JWT header
*/
static apr_byte_t apr_jwt_parse_header_object(apr_pool_t *pool,
const char *s_header, apr_jwt_header_t *header, apr_jwt_error_t *err) {
/* decode the JWT JSON header */
if (apr_jwt_base64url_decode_object(pool, s_header, &header->value,
err) == FALSE)
return FALSE;
/* parse the (mandatory) signing algorithm */
if (apr_jwt_get_string(pool, header->value.json, "alg", TRUE, &header->alg,
err) == FALSE)
return FALSE;
/* parse the (optional) kid */
apr_jwt_get_string(pool, header->value.json, "kid", FALSE, &header->kid,
NULL);
/* parse the (optional) enc */
apr_jwt_get_string(pool, header->value.json, "enc", FALSE, &header->enc,
NULL);
return TRUE;
}
/*
* parse JWT payload
*/
static apr_byte_t apr_jwt_parse_payload(apr_pool_t *pool, const char *s_payload,
apr_jwt_payload_t *payload, apr_jwt_error_t *err) {
/* decode the JWT JSON payload */
if (apr_jwt_base64url_decode_object(pool, s_payload, &payload->value,
err) == FALSE)
return FALSE;
/* get the (optional) "issuer" value from the JSON payload */
apr_jwt_get_string(pool, payload->value.json, "iss", FALSE, &payload->iss,
NULL);
/* get the (optional) "exp" value from the JSON payload */
apr_jwt_get_timestamp(pool, payload->value.json, "exp", FALSE,
&payload->exp,
NULL);
/* get the (optional) "iat" value from the JSON payload */
apr_jwt_get_timestamp(pool, payload->value.json, "iat", FALSE,
&payload->iat,
NULL);
/* get the (optional) "sub" value from the JSON payload */
apr_jwt_get_string(pool, payload->value.json, "sub", FALSE, &payload->sub,
NULL);
return TRUE;
}
/*
* parse JWT signature
*/
static apr_byte_t apr_jwt_parse_signature(apr_pool_t *pool,
const char *s_signature, apr_jwt_signature_t *signature) {
signature->length = apr_jwt_base64url_decode(pool,
(char **) &signature->bytes, s_signature, 1);
return (signature->length > 0);
}
/*
* parse a JWT that uses compact serialization (i.e. elements separated by dots) in to an array of strings
*/
static apr_array_header_t *apr_jwt_compact_deserialize(apr_pool_t *pool,
const char *str) {
apr_array_header_t *result = apr_array_make(pool, 6, sizeof(const char*));
if ((str != NULL) && (strlen(str) > 0)) {
char *s = apr_pstrdup(pool, str);
while (s) {
char *p = strchr(s, '.');
if (p != NULL)
*p = '\0';
*(const char**) apr_array_push(result) = apr_pstrdup(pool, s);
if (p == NULL)
break;
s = ++p;
}
}
return result;
}
/*
* parse a JOSE header from a compact serialized string
*/
apr_byte_t apr_jwt_header_parse(apr_pool_t *pool, const char *s_json,
apr_array_header_t **unpacked, apr_jwt_header_t *header,
apr_jwt_error_t *err) {
*unpacked = apr_jwt_compact_deserialize(pool, s_json);
if ((*unpacked)->nelts < 1) {
apr_jwt_error(err, "could not deserialize at least one element");
return FALSE;
}
if (apr_jwt_parse_header_object(pool, ((const char**) (*unpacked)->elts)[0],
header, err) == FALSE)
return FALSE;
return TRUE;
}
/*
* return plain deserialized header text
*/
const char *apr_jwt_header_to_string(apr_pool_t *pool, const char *s_json,
apr_jwt_error_t *err) {
apr_array_header_t *unpacked = NULL;
apr_jwt_header_t header;
if (apr_jwt_header_parse(pool, s_json, &unpacked, &header, err) == FALSE)
return NULL;
json_decref(header.value.json);
return header.value.str;
}
/*
* return the JWK type for the JWT signature verification
*/
const char *apr_jwt_signature_to_jwk_type(apr_pool_t *pool, apr_jwt_t *jwt) {
if (apr_jws_signature_is_rsa(pool, jwt)) {
return "RSA";
}
#if (OPENSSL_VERSION_NUMBER >= 0x01000000)
if (apr_jws_signature_is_ec(pool, jwt)) {
return "EC";
}
#endif
if (apr_jws_signature_is_hmac(pool, jwt)) {
return "oct";
}
return NULL;
}
/* see if we can deal with this type of JWT (JWS/JWE) */
static apr_byte_t apr_jwt_is_supported(apr_pool_t *pool, apr_jwt_t *jwt,
apr_jwt_error_t *err) {
if (apr_jws_algorithm_is_supported(pool, jwt->header.alg) == FALSE) {
if (apr_jwe_algorithm_is_supported(pool, jwt->header.alg) == FALSE) {
apr_jwt_error(err, "unsupported algorithm in JWT header: \"%s\"",
jwt->header.alg);
return FALSE;
}
if (apr_jwe_encryption_is_supported(pool, jwt->header.enc) == FALSE) {
apr_jwt_error(err,
"unsupported content encryption algorithm in (%s) encrypted JWT header: \"%s\"",
jwt->header.alg, jwt->header.enc);
return FALSE;
}
}
return TRUE;
}
/*
* parse and (optionally) decrypt a JSON Web Token
*/
apr_byte_t apr_jwt_parse(apr_pool_t *pool, const char *s_json,
apr_jwt_t **j_jwt, apr_hash_t *keys, apr_jwt_error_t *err) {
*j_jwt = apr_pcalloc(pool, sizeof(apr_jwt_t));
apr_jwt_t *jwt = *j_jwt;
apr_array_header_t *unpacked = NULL;
if (apr_jwt_header_parse(pool, s_json, &unpacked, &jwt->header,
err) == FALSE)
return FALSE;
if (unpacked->nelts < 2) {
apr_jwt_error(err,
"could not successfully deserialize 2 or more elements from JWT header");
return FALSE;
}
if (apr_jwt_is_supported(pool, jwt, err) == FALSE)
return FALSE;
if (apr_jwe_is_encrypted_jwt(pool, &jwt->header)) {
char *decrypted = NULL;
if ((apr_jwe_decrypt_jwt(pool, &jwt->header, unpacked, keys, &decrypted,
err) == FALSE) || (decrypted == NULL))
return FALSE;
apr_array_clear(unpacked);
unpacked = NULL;
json_decref(jwt->header.value.json);
if (apr_jwt_header_parse(pool, (const char *) decrypted, &unpacked,
&jwt->header, err) == FALSE)
return FALSE;
if (unpacked->nelts < 2) {
apr_jwt_error(err,
"could not successfully deserialize 2 or more elements from decrypted JWT header");
return FALSE;
}
}
/* concat the base64url-encoded payload to the base64url-encoded header for signature verification purposes */
jwt->message = apr_pstrcat(pool, ((const char**) unpacked->elts)[0], ".",
((const char**) unpacked->elts)[1], NULL);
/* parse the payload fields */
if (apr_jwt_parse_payload(pool, ((const char**) unpacked->elts)[1],
&jwt->payload, err) == FALSE) {
json_decref(jwt->header.value.json);
return FALSE;
}
if (unpacked->nelts > 2 && strcmp(jwt->header.alg, "none") != 0) {
/* remainder is the signature */
if (apr_jwt_parse_signature(pool, ((const char**) unpacked->elts)[2],
&jwt->signature) == FALSE) {
json_decref(jwt->header.value.json);
json_decref(jwt->payload.value.json);
apr_jwt_error(err,
"could not successfully parse (base64urldecode) JWT signature");
return FALSE;
}
}
return TRUE;
}
/* destroy resources allocated for JWT */
void apr_jwt_destroy(apr_jwt_t *jwt) {
if (jwt) {
if (jwt->header.value.json)
json_decref(jwt->header.value.json);
if (jwt->payload.value.json)
json_decref(jwt->payload.value.json);
}
}
/* timing-safe byte sequence comparison */
apr_byte_t apr_jwt_memcmp(const void *in_a, const void *in_b, size_t len) {
// TODO: this is copied from OpenSSL 1.0.1 to avoid version issues on various platforms
// we could use #ifdef's to use OpenSSL where possible
size_t i;
const unsigned char *a = in_a;
const unsigned char *b = in_b;
unsigned char x = 0;
for (i = 0; i < len; i++)
x |= a[i] ^ b[i];
return x ? FALSE : TRUE;
}
mod_auth_openidc-1.8.5/src/jose/apr_jwk.c 0000644 0001750 0001750 00000036515 12532644156 020570 0 ustar zandbelt zandbelt /*
* 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-2015 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 Key handling
*
* @Author: Hans Zandbelt - hzandbelt@pingidentity.com
*/
#include
#include
#include
#include
#include "apr_jose.h"
/*
* parse an RSA JWK in raw format (n,e,d)
*/
static apr_byte_t apr_jwk_parse_rsa_raw(apr_pool_t *pool, json_t *json,
apr_jwk_key_rsa_t **jwk_key_rsa, apr_jwt_error_t *err) {
/* allocate space */
*jwk_key_rsa = apr_pcalloc(pool, sizeof(apr_jwk_key_rsa_t));
apr_jwk_key_rsa_t *key = *jwk_key_rsa;
/* parse the mandatory modulus */
char *s_modulus = NULL;
if (apr_jwt_get_string(pool, json, "n", TRUE, &s_modulus, err) == FALSE)
return FALSE;
/* base64url decode the modulus and get its size */
key->modulus_len = apr_jwt_base64url_decode(pool, (char **) &key->modulus,
s_modulus, 1);
if (key->modulus_len <= 0) {
apr_jwt_error(err, "apr_jwt_base64url_decode of modulus failed");
return FALSE;
}
/* parse the mandatory exponent */
char *s_exponent = NULL;
if (apr_jwt_get_string(pool, json, "e", TRUE, &s_exponent, err) == FALSE)
return FALSE;
/* base64url decode the exponent and get its size */
key->exponent_len = apr_jwt_base64url_decode(pool, (char **) &key->exponent,
s_exponent, 1);
if (key->exponent_len <= 0) {
apr_jwt_error(err, "apr_jwt_base64url_decode of exponent failed");
return FALSE;
}
/* parse the optional private exponent */
char *s_private_exponent = NULL;
apr_jwt_get_string(pool, json, "d", FALSE, &s_private_exponent, NULL);
if (s_private_exponent != NULL) {
/* base64url decode the private exponent and get its size */
key->private_exponent_len = apr_jwt_base64url_decode(pool,
(char **) &key->private_exponent, s_private_exponent, 1);
if (key->private_exponent_len <= 0) {
apr_jwt_error(err,
"apr_jwt_base64url_decode of private exponent failed");
return FALSE;
}
}
/* that went well */
return TRUE;
}
/*
* 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 apr_jwk_rsa_bio_to_key(apr_pool_t *pool, BIO *input,
apr_jwk_key_rsa_t **jwk_key_rsa, int is_private_key,
apr_jwt_error_t *err) {
X509 *x509 = NULL;
EVP_PKEY *pkey = NULL;
apr_byte_t rv = FALSE;
if (is_private_key) {
/* get the private key struct from the BIO */
if ((pkey = PEM_read_bio_PrivateKey(input, NULL, NULL, NULL)) == NULL) {
apr_jwt_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) {
apr_jwt_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) {
apr_jwt_error_openssl(err, "X509_get_pubkey");
goto end;
}
}
/* allocate space */
*jwk_key_rsa = apr_pcalloc(pool, sizeof(apr_jwk_key_rsa_t));
apr_jwk_key_rsa_t *key = *jwk_key_rsa;
/* get the RSA key from the public key struct */
RSA *rsa = EVP_PKEY_get1_RSA(pkey);
if (rsa == NULL) {
apr_jwt_error_openssl(err, "EVP_PKEY_get1_RSA");
goto end;
}
/* convert the modulus bignum in to a key/len */
key->modulus_len = BN_num_bytes(rsa->n);
key->modulus = apr_pcalloc(pool, key->modulus_len);
BN_bn2bin(rsa->n, key->modulus);
/* convert the exponent bignum in to a key/len */
key->exponent_len = BN_num_bytes(rsa->e);
key->exponent = apr_pcalloc(pool, key->exponent_len);
BN_bn2bin(rsa->e, key->exponent);
/* convert the private exponent bignum in to a key/len */
if (rsa->d != NULL) {
key->private_exponent_len = BN_num_bytes(rsa->d);
key->private_exponent = apr_pcalloc(pool, key->private_exponent_len);
BN_bn2bin(rsa->d, key->private_exponent);
}
RSA_free(rsa);
rv = TRUE;
end:
if (pkey)
EVP_PKEY_free(pkey);
if (x509)
X509_free(x509);
return rv;
}
/*
* parse an RSA JWK in X.509 format (x5c)
*/
static apr_byte_t apr_jwk_parse_rsa_x5c(apr_pool_t *pool, json_t *json,
apr_jwk_t *jwk, apr_jwt_error_t *err) {
apr_byte_t rv = FALSE;
/* get the "x5c" array element from the JSON object */
json_t *v = json_object_get(json, "x5c");
if (v == NULL) {
apr_jwt_error(err, "JSON key \"%s\" could not be found", "x5c");
return FALSE;
}
if (!json_is_array(v)) {
apr_jwt_error(err,
"JSON key \"%s\" was found but its value is not a JSON array",
"x5c");
return FALSE;
}
/* take the first element of the array */
v = json_array_get(v, 0);
if (v == NULL) {
apr_jwt_error(err, "first element in JSON array is \"null\"");
return FALSE;
}
if (!json_is_string(v)) {
apr_jwt_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, "-----BEGIN CERTIFICATE-----\n");
while (i < strlen(s_x5c)) {
s = apr_psprintf(pool, "%s%s\n", s, apr_pstrndup(pool, s_x5c + i, len));
i += len;
}
s = apr_psprintf(pool, "%s-----END CERTIFICATE-----\n", s);
BIO *input = NULL;
/* put it in BIO memory */
if ((input = BIO_new(BIO_s_mem())) == NULL) {
apr_jwt_error_openssl(err, "memory allocation BIO_new/BIO_s_mem");
return FALSE;
}
if (BIO_puts(input, s) <= 0) {
BIO_free(input);
apr_jwt_error_openssl(err, "BIO_puts");
return FALSE;
}
/* do the actual parsing */
rv = apr_jwk_rsa_bio_to_key(pool, input, &jwk->key.rsa, FALSE, err);
BIO_free(input);
return rv;
}
/*
* parse an RSA JWK
*/
static apr_byte_t apr_jwk_parse_rsa(apr_pool_t *pool, json_t *json,
apr_jwk_t *jwk, apr_jwt_error_t *err) {
jwk->type = APR_JWK_KEY_RSA;
char *s_test = NULL;
apr_jwt_get_string(pool, json, "n", FALSE, &s_test, NULL);
if (s_test != NULL)
return apr_jwk_parse_rsa_raw(pool, json, &jwk->key.rsa, err);
json_t *v = json_object_get(json, "x5c");
if (v != NULL)
return apr_jwk_parse_rsa_x5c(pool, json, jwk, err);
apr_jwt_error(err,
"wrong or unsupported RSA key representation, no \"n\" or \"x5c\" key found in JWK JSON value");
return FALSE;
}
/*
* parse an EC JWK
*/
static apr_byte_t apr_jwk_parse_ec(apr_pool_t *pool, json_t *json,
apr_jwk_t *jwk, apr_jwt_error_t *err) {
/* allocated space and set key type */
jwk->type = APR_JWK_KEY_EC;
jwk->key.ec = apr_pcalloc(pool, sizeof(apr_jwk_key_ec_t));
/* parse x */
char *s_x = NULL;
if (apr_jwt_get_string(pool, json, "x", TRUE, &s_x, err) == FALSE)
return FALSE;
/* base64url decode x and get its size */
jwk->key.ec->x_len = apr_jwt_base64url_decode(pool,
(char **) &jwk->key.ec->x, s_x, 1);
if (jwk->key.ec->x_len <= 0) {
apr_jwt_error(err, "apr_jwt_base64url_decode of x length failed");
return FALSE;
}
/* parse y */
char *s_y = NULL;
if (apr_jwt_get_string(pool, json, "y", TRUE, &s_y, err) == FALSE)
return FALSE;
/* base64url decode y and get its size */
jwk->key.ec->y_len = apr_jwt_base64url_decode(pool,
(char **) &jwk->key.ec->y, s_y, 1);
if (jwk->key.ec->y_len <= 0) {
apr_jwt_error(err, "apr_jwt_base64url_decode of y length failed");
return FALSE;
}
/* that went well */
return TRUE;
}
/*
* parse a an octet sequence used to represent a symmetric key
*/
static apr_byte_t apr_jwk_parse_oct(apr_pool_t *pool, json_t *json,
apr_jwk_t *jwk, apr_jwt_error_t *err) {
/* allocated space and set key type */
jwk->type = APR_JWK_KEY_OCT;
jwk->key.oct = apr_pcalloc(pool, sizeof(apr_jwk_key_oct_t));
/* parse k */
char *s_k = NULL;
if (apr_jwt_get_string(pool, json, "k", TRUE, &s_k, err) == FALSE)
return FALSE;
/* base64url decode k and get its size */
jwk->key.oct->k_len = apr_jwt_base64url_decode(pool,
(char **) &jwk->key.oct->k, s_k, 1);
if (jwk->key.oct->k_len <= 0) {
apr_jwt_error(err, "apr_jwt_base64url_decode of k length failed");
return FALSE;
}
/* that went well */
return TRUE;
}
/*
* calculate a hash and base64url encode the result
*/
static apr_byte_t apr_jwk_hash_and_base64urlencode(apr_pool_t *pool,
const unsigned char *input, const int input_len, char **output,
apr_jwt_error_t *err) {
unsigned int hash_len = SHA_DIGEST_LENGTH;
unsigned char hash[SHA_DIGEST_LENGTH];
// TODO: upgrade to SHA2?
/* hash it */
if (!SHA1(input, input_len, hash)) {
apr_jwt_error_openssl(err, "SHA1");
return FALSE;
}
/* base64url encode the key fingerprint */
if (apr_jwt_base64url_encode(pool, output, (const char *) hash, hash_len, 0)
<= 0) {
apr_jwt_error(err, "apr_jwt_base64url_encode of hash failed");
return FALSE;
}
return TRUE;
}
/*
* parse a symmetric key in to an "oct" JWK
*/
apr_byte_t apr_jwk_parse_symmetric_key(apr_pool_t *pool, const unsigned char *key,
unsigned int key_len, apr_jwk_t **j_jwk, apr_jwt_error_t *err) {
/* allocate memory for the JWK */
*j_jwk = apr_pcalloc(pool, sizeof(apr_jwk_t));
apr_jwk_t *jwk = *j_jwk;
/* allocated space and set key type */
jwk->type = APR_JWK_KEY_OCT;
jwk->key.oct = apr_pcalloc(pool, sizeof(apr_jwk_key_oct_t));
// /* set the values */
jwk->key.oct->k = apr_pcalloc(pool, key_len);
memcpy(jwk->key.oct->k, key, key_len);
jwk->key.oct->k_len = key_len;
/* calculate a unique key identifier (kid) by fingerprinting the key params */
if (apr_jwk_hash_and_base64urlencode(pool, jwk->key.oct->k,
jwk->key.oct->k_len, &jwk->kid, err) == FALSE)
return FALSE;
return TRUE;
}
/*
* parse JSON JWK
*/
apr_byte_t apr_jwk_parse_json(apr_pool_t *pool, json_t *json, apr_jwk_t **j_jwk,
apr_jwt_error_t *err) {
/* check that we've actually got a JSON value back */
if (json == NULL) {
apr_jwt_error(err, "JWK JSON is NULL");
return FALSE;
}
/* check that the value is a JSON object */
if (!json_is_object(json)) {
apr_jwt_error(err, "JWK JSON is not a JSON object");
return FALSE;
}
/* allocate memory for the JWK */
*j_jwk = apr_pcalloc(pool, sizeof(apr_jwk_t));
apr_jwk_t *jwk = *j_jwk;
/* get the mandatory key type */
char *kty = NULL;
if (apr_jwt_get_string(pool, json, "kty", TRUE, &kty, err) == FALSE)
return FALSE;
/* get the optional kid */
apr_jwt_get_string(pool, json, "kid", FALSE, &jwk->kid, NULL);
/* parse the key */
if (apr_strnatcmp(kty, "RSA") == 0)
return apr_jwk_parse_rsa(pool, json, jwk, err);
if (apr_strnatcmp(kty, "EC") == 0)
return apr_jwk_parse_ec(pool, json, jwk, err);
if (apr_strnatcmp(kty, "oct") == 0)
return apr_jwk_parse_oct(pool, json, jwk, err);
apr_jwt_error(err,
"wrong or unsupported JWK key representation \"%s\" (\"RSA\", \"EC\" and \"oct\" are supported key types)",
kty);
return FALSE;
}
/*
* convert RSA key to JWK JSON string representation and kid
*/
apr_byte_t apr_jwk_to_json(apr_pool_t *pool, apr_jwk_t *jwk, char **s_json,
apr_jwt_error_t *err) {
if (jwk->type != APR_JWK_KEY_RSA) {
apr_jwt_error(err, "non RSA keys (%d) not yet supported", jwk->type);
return FALSE;
}
apr_jwk_key_rsa_t *key = jwk->key.rsa;
unsigned char *n_enc = NULL;
int n_len = apr_jwt_base64url_encode(pool, (char **) &n_enc,
(const char *) key->modulus, key->modulus_len, 0);
if (n_len <= 0) {
apr_jwt_error(err, "apr_jwt_base64url_encode of modulus failed");
return FALSE;
}
unsigned char *e_enc = NULL;
if (apr_jwt_base64url_encode(pool, (char **) &e_enc,
(const char *) key->exponent, key->exponent_len, 0) <= 0) {
apr_jwt_error(err,
"apr_jwt_base64url_encode of public exponent failed");
return FALSE;
}
unsigned char *d_enc = NULL;
if (key->private_exponent_len > 0) {
if (apr_jwt_base64url_encode(pool, (char **) &d_enc,
(const char *) key->private_exponent, key->private_exponent_len,
0) <= 0) {
apr_jwt_error(err,
"apr_jwt_base64url_encode of private exponent failed");
return FALSE;
}
}
char *p = apr_psprintf(pool, "{ \"kty\" : \"RSA\"");
p = apr_psprintf(pool, "%s, \"n\": \"%s\"", p, n_enc);
p = apr_psprintf(pool, "%s, \"e\": \"%s\"", p, e_enc);
if (d_enc != NULL)
p = apr_psprintf(pool, "%s, \"d\": \"%s\"", p, d_enc);
p = apr_psprintf(pool, "%s, \"kid\" : \"%s\"", p, jwk->kid);
p = apr_psprintf(pool, "%s }", p);
*s_json = p;
return TRUE;
}
static apr_byte_t apr_jwk_parse_rsa_key(apr_pool_t *pool, int is_private_key,
const char *filename, apr_jwk_t **j_jwk, apr_jwt_error_t *err) {
BIO *input = NULL;
apr_jwk_key_rsa_t *key = NULL;
apr_byte_t rv = FALSE;
if ((input = BIO_new(BIO_s_file())) == NULL) {
apr_jwt_error_openssl(err, "BIO_new/BIO_s_file");
goto end;
}
if (BIO_read_filename(input, filename) <= 0) {
apr_jwt_error_openssl(err, "BIO_read_filename");
goto end;
}
if (apr_jwk_rsa_bio_to_key(pool, input, &key, is_private_key, err) == FALSE)
goto end;
/* allocate memory for the JWK */
*j_jwk = apr_pcalloc(pool, sizeof(apr_jwk_t));
apr_jwk_t *jwk = *j_jwk;
jwk->type = APR_JWK_KEY_RSA;
jwk->key.rsa = key;
/* calculate a unique key identifier (kid) by fingerprinting the key params */
// TODO: based just on sha1 hash of modulus "n" now..., could do this based on jwk->value.str
if (apr_jwk_hash_and_base64urlencode(pool, key->modulus, key->modulus_len,
&jwk->kid, err) == FALSE)
goto end;
rv = TRUE;
end:
if (input)
BIO_free(input);
return rv;
}
apr_byte_t apr_jwk_parse_rsa_private_key(apr_pool_t *pool, const char *filename,
apr_jwk_t **j_jwk, apr_jwt_error_t *err) {
return apr_jwk_parse_rsa_key(pool, TRUE, filename, j_jwk, err);
}
apr_byte_t apr_jwk_parse_rsa_public_key(apr_pool_t *pool, const char *filename,
apr_jwk_t **j_jwk, apr_jwt_error_t *err) {
return apr_jwk_parse_rsa_key(pool, FALSE, filename, j_jwk, err);
}
mod_auth_openidc-1.8.5/src/jose/apr_jws.c 0000644 0001750 0001750 00000037761 12534765211 020602 0 ustar zandbelt zandbelt /*
* 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-2015 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 Signatures handling
*
* @Author: Hans Zandbelt - hzandbelt@pingidentity.com
*/
#include
#include
#include
#include
#include
#include
#include
#include "apr_jose.h"
#include
#include
/*
* return all supported signing algorithms
*/
apr_array_header_t *apr_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) = "RS256";
*(const char**) apr_array_push(result) = "RS384";
*(const char**) apr_array_push(result) = "RS512";
*(const char**) apr_array_push(result) = "PS256";
*(const char**) apr_array_push(result) = "PS384";
*(const char**) apr_array_push(result) = "PS512";
*(const char**) apr_array_push(result) = "HS256";
*(const char**) apr_array_push(result) = "HS384";
*(const char**) apr_array_push(result) = "HS512";
#if (OPENSSL_VERSION_NUMBER >= 0x01000000)
*(const char**) apr_array_push(result) = "ES256";
*(const char**) apr_array_push(result) = "ES384";
*(const char**) apr_array_push(result) = "ES512";
#endif
*(const char**) apr_array_push(result) = "none";
return result;
}
/*
* check if the provided signing algorithm is supported
*/
apr_byte_t apr_jws_algorithm_is_supported(apr_pool_t *pool, const char *alg) {
return apr_jwt_array_has_string(apr_jws_supported_algorithms(pool), alg);
}
/*
* helper function to determine the type of signature on a JWT
*/
static apr_byte_t apr_jws_signature_starts_with(apr_pool_t *pool,
const char *alg, const char *match) {
if (alg == NULL)
return FALSE;
return (strncmp(alg, match, strlen(match)) == 0);
}
/*
* return OpenSSL digest for JWK algorithm
*/
static char *apr_jws_alg_to_openssl_digest(const char *alg) {
if ((strcmp(alg, "RS256") == 0) || (strcmp(alg, "PS256") == 0)
|| (strcmp(alg, "HS256") == 0) || (strcmp(alg, "ES256") == 0)) {
return "sha256";
}
if ((strcmp(alg, "RS384") == 0) || (strcmp(alg, "PS384") == 0)
|| (strcmp(alg, "HS384") == 0) || (strcmp(alg, "ES384") == 0)) {
return "sha384";
}
if ((strcmp(alg, "RS512") == 0) || (strcmp(alg, "PS512") == 0)
|| (strcmp(alg, "HS512") == 0) || (strcmp(alg, "ES512") == 0)) {
return "sha512";
}
if (strcmp(alg, "NONE") == 0) {
return "NONE";
}
return NULL;
}
/*
* return an EVP structure for the specified algorithm
*/
const EVP_MD *apr_jws_crypto_alg_to_evp(apr_pool_t *pool, const char *alg,
apr_jwt_error_t *err) {
const EVP_MD *result = NULL;
char *digest = apr_jws_alg_to_openssl_digest(alg);
if (digest == NULL) {
apr_jwt_error(err,
"no OpenSSL digest algorithm name found for algorithm \"%s\"",
alg);
return NULL;
}
result = EVP_get_digestbyname(digest);
if (result == NULL) {
apr_jwt_error(err,
"no OpenSSL digest algorithm found for algorithm \"%s\"",
digest);
return NULL;
}
return result;
}
/*
* verify HMAC signature on JWT
*/
static apr_byte_t apr_jws_verify_hmac(apr_pool_t *pool, apr_jwt_t *jwt,
apr_jwk_t *jwk, apr_jwt_error_t *err) {
if (jwk->type != APR_JWK_KEY_OCT) {
apr_jwt_error(err,
"key type of provided JWK cannot be used for HMAC verification: %d",
jwk->type);
return FALSE;
}
/* get the OpenSSL digest function */
const EVP_MD *digest = NULL;
if ((digest = apr_jws_crypto_alg_to_evp(pool, jwt->header.alg, err)) == NULL)
return FALSE;
/* prepare the message */
unsigned char *msg = (unsigned char *) jwt->message;
unsigned int msg_len = strlen(jwt->message);
/* prepare the hash */
unsigned int md_len = 0;
unsigned char md[EVP_MAX_MD_SIZE];
/* apply the HMAC function to the message with the provided key */
if (!HMAC(digest, jwk->key.oct->k, jwk->key.oct->k_len, msg, msg_len, md,
&md_len)) {
apr_jwt_error_openssl(err, "HMAC");
return FALSE;
}
/* check that the length of the hash matches what was provided to us in the signature */
if (md_len != jwt->signature.length) {
apr_jwt_error(err,
"calculated hash length (%d) differs from the length of the signature provided in the JWT (%d)", md_len, jwt->signature.length);
return FALSE;
}
/* do a comparison of the provided hash value against calculated hash value */
if (apr_jwt_memcmp(md, jwt->signature.bytes, md_len) == FALSE) {
apr_jwt_error(err,
"calculated hash differs from the signature provided in the JWT");
return FALSE;
}
/* all OK if we got to here */
return TRUE;
}
/*
* hash a byte sequence with the specified algorithm
*/
apr_byte_t apr_jws_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, apr_jwt_error_t *err) {
unsigned char md_value[EVP_MAX_MD_SIZE];
EVP_MD_CTX ctx;
EVP_MD_CTX_init(&ctx);
const EVP_MD *evp_digest = NULL;
if ((evp_digest = EVP_get_digestbyname(s_digest)) == NULL) {
apr_jwt_error(err,
"no OpenSSL digest algorithm found for algorithm \"%s\"",
s_digest);
return FALSE;
}
if (!EVP_DigestInit_ex(&ctx, evp_digest, NULL)) {
apr_jwt_error_openssl(err, "EVP_DigestInit_ex");
return FALSE;
}
if (!EVP_DigestUpdate(&ctx, input, input_len)) {
apr_jwt_error_openssl(err, "EVP_DigestUpdate");
return FALSE;
}
if (!EVP_DigestFinal_ex(&ctx, md_value, output_len)) {
apr_jwt_error_openssl(err, "EVP_DigestFinal_ex");
return FALSE;
}
EVP_MD_CTX_cleanup(&ctx);
*output = apr_pcalloc(pool, *output_len);
memcpy(*output, md_value, *output_len);
return TRUE;
}
/*
* hash a string value with the specified algorithm
*/
apr_byte_t apr_jws_hash_string(apr_pool_t *pool, const char *alg,
const char *msg, char **hash, unsigned int *hash_len,
apr_jwt_error_t *err) {
char *s_digest = apr_jws_alg_to_openssl_digest(alg);
if (s_digest == NULL) {
apr_jwt_error(err,
"no OpenSSL digest algorithm name found for algorithm \"%s\"",
alg);
return FALSE;
}
return apr_jws_hash_bytes(pool, s_digest, (const unsigned char *) msg,
strlen(msg), (unsigned char **) hash, hash_len, err);
}
/*
* return hash length
*/
int apr_jws_hash_length(const char *alg) {
if ((strcmp(alg, "RS256") == 0) || (strcmp(alg, "PS256") == 0)
|| (strcmp(alg, "HS256") == 0) || (strcmp(alg, "ES256") == 0)) {
return 32;
}
if ((strcmp(alg, "RS384") == 0) || (strcmp(alg, "PS384") == 0)
|| (strcmp(alg, "HS384") == 0) || (strcmp(alg, "ES384") == 0)) {
return 48;
}
if ((strcmp(alg, "RS512") == 0) || (strcmp(alg, "PS512") == 0)
|| (strcmp(alg, "HS512") == 0) || (strcmp(alg, "ES512") == 0)) {
return 64;
}
return 0;
}
/*
* verify HMAC signature on JWT
*/
static apr_byte_t apr_jws_verify_rsa(apr_pool_t *pool, apr_jwt_t *jwt,
apr_jwk_t *jwk, apr_jwt_error_t *err) {
apr_byte_t rc = FALSE;
/* get the OpenSSL digest function */
const EVP_MD *digest = NULL;
if ((digest = apr_jws_crypto_alg_to_evp(pool, jwt->header.alg, err)) == NULL)
return FALSE;
EVP_MD_CTX ctx;
EVP_MD_CTX_init(&ctx);
RSA * pubkey = RSA_new();
BIGNUM * modulus = BN_new();
BIGNUM * exponent = BN_new();
BN_bin2bn(jwk->key.rsa->modulus, jwk->key.rsa->modulus_len, modulus);
BN_bin2bn(jwk->key.rsa->exponent, jwk->key.rsa->exponent_len, exponent);
pubkey->n = modulus;
pubkey->e = exponent;
EVP_PKEY* pRsaKey = EVP_PKEY_new();
if (!EVP_PKEY_assign_RSA(pRsaKey, pubkey)) {
pRsaKey = NULL;
apr_jwt_error_openssl(err, "EVP_PKEY_assign_RSA");
goto end;
}
if (apr_jws_signature_starts_with(pool, jwt->header.alg, "PS") == TRUE) {
int status = 0;
unsigned char *pDecrypted = apr_pcalloc(pool, jwt->signature.length);
status = RSA_public_decrypt(jwt->signature.length, jwt->signature.bytes,
pDecrypted, pubkey, RSA_NO_PADDING);
if (status == -1) {
apr_jwt_error_openssl(err, "RSA_public_decrypt");
goto end;
}
unsigned char *pDigest = apr_pcalloc(pool, RSA_size(pubkey));
unsigned int uDigestLen = RSA_size(pubkey);
if (!EVP_DigestInit(&ctx, digest)) {
apr_jwt_error_openssl(err, "EVP_DigestInit");
goto end;
}
if (!EVP_DigestUpdate(&ctx, jwt->message, strlen(jwt->message))) {
apr_jwt_error_openssl(err, "EVP_DigestUpdate");
goto end;
}
if (!EVP_DigestFinal(&ctx, pDigest, &uDigestLen)) {
apr_jwt_error_openssl(err, "wrong key? EVP_DigestFinal");
goto end;
}
/* verify the data */
status = RSA_verify_PKCS1_PSS(pubkey, pDigest, digest, pDecrypted,
-2 /* salt length recovered from signature*/);
if (status != 1) {
apr_jwt_error_openssl(err, "RSA_verify_PKCS1_PSS");
goto end;
}
rc = TRUE;
} else if (apr_jws_signature_starts_with(pool, jwt->header.alg,
"RS") == TRUE) {
if (!EVP_VerifyInit_ex(&ctx, digest, NULL)) {
apr_jwt_error_openssl(err, "EVP_VerifyInit_ex");
goto end;
}
if (!EVP_VerifyUpdate(&ctx, jwt->message, strlen(jwt->message))) {
apr_jwt_error_openssl(err, "EVP_VerifyUpdate");
goto end;
}
if (!EVP_VerifyFinal(&ctx, (const unsigned char *) jwt->signature.bytes,
jwt->signature.length, pRsaKey)) {
apr_jwt_error_openssl(err, "wrong key? EVP_VerifyFinal");
goto end;
}
rc = TRUE;
}
end:
if (pRsaKey) {
EVP_PKEY_free(pRsaKey);
} else if (pubkey) {
RSA_free(pubkey);
}
EVP_MD_CTX_cleanup(&ctx);
return rc;
}
#if (OPENSSL_VERSION_NUMBER >= 0x01000000)
/*
* return the OpenSSL Elliptic Curve NID for a JWT algorithm
*/
static int apr_jws_ec_alg_to_curve(const char *alg) {
if (strcmp(alg, "ES256") == 0)
return NID_X9_62_prime256v1;
if (strcmp(alg, "ES384") == 0)
return NID_secp384r1;
if (strcmp(alg, "ES512") == 0)
return NID_secp521r1;
return -1;
}
/*
* verify EC signature on JWT
*/
static apr_byte_t apr_jws_verify_ec(apr_pool_t *pool, apr_jwt_t *jwt,
apr_jwk_t *jwk, apr_jwt_error_t *err) {
int nid = apr_jws_ec_alg_to_curve(jwt->header.alg);
if (nid == -1) {
apr_jwt_error(err,
"no OpenSSL Elliptic Curve identifier found for algorithm \"%s\"",
jwt->header.alg);
return FALSE;
}
EC_GROUP *curve = EC_GROUP_new_by_curve_name(nid);
if (curve == NULL) {
apr_jwt_error(err,
"no OpenSSL Elliptic Curve found for algorithm \"%s\"",
jwt->header.alg);
return FALSE;
}
apr_byte_t rc = FALSE;
/* get the OpenSSL digest function */
const EVP_MD *digest = NULL;
if ((digest = apr_jws_crypto_alg_to_evp(pool, jwt->header.alg, err)) == NULL)
return FALSE;
EVP_MD_CTX ctx;
EVP_MD_CTX_init(&ctx);
EC_KEY * pubkey = EC_KEY_new();
EC_KEY_set_group(pubkey, curve);
BIGNUM * x = BN_new();
BIGNUM * y = BN_new();
BN_bin2bn(jwk->key.ec->x, jwk->key.ec->x_len, x);
BN_bin2bn(jwk->key.ec->y, jwk->key.ec->y_len, y);
if (!EC_KEY_set_public_key_affine_coordinates(pubkey, x, y)) {
apr_jwt_error_openssl(err, "EC_KEY_set_public_key_affine_coordinates");
return FALSE;
}
EVP_PKEY* pEcKey = EVP_PKEY_new();
if (!EVP_PKEY_assign_EC_KEY(pEcKey, pubkey)) {
pEcKey = NULL;
apr_jwt_error_openssl(err, "EVP_PKEY_assign_EC_KEY");
goto end;
}
ctx.pctx = EVP_PKEY_CTX_new(pEcKey, NULL);
if (!EVP_PKEY_verify_init(ctx.pctx)) {
apr_jwt_error_openssl(err, "EVP_PKEY_verify_init");
goto end;
}
if (!EVP_VerifyInit_ex(&ctx, digest, NULL)) {
apr_jwt_error_openssl(err, "EVP_VerifyInit_ex");
goto end;
}
if (!EVP_VerifyUpdate(&ctx, jwt->message, strlen(jwt->message))) {
apr_jwt_error_openssl(err, "EVP_VerifyUpdate");
goto end;
}
if (!EVP_VerifyFinal(&ctx, (const unsigned char *) jwt->signature.bytes,
jwt->signature.length, pEcKey)) {
apr_jwt_error_openssl(err, "wrong key? EVP_VerifyFinal");
goto end;
}
rc = TRUE;
end:
if (pEcKey) {
EVP_PKEY_free(pEcKey);
} else if (pubkey) {
EC_KEY_free(pubkey);
}
EVP_MD_CTX_cleanup(&ctx);
return rc;
}
#endif
/*
* check if the signature on the JWT is HMAC-based
*/
apr_byte_t apr_jws_signature_is_hmac(apr_pool_t *pool, apr_jwt_t *jwt) {
return apr_jws_signature_starts_with(pool, jwt->header.alg, "HS");
}
/*
* check if the signature on the JWT is RSA-based
*/
apr_byte_t apr_jws_signature_is_rsa(apr_pool_t *pool, apr_jwt_t *jwt) {
return apr_jws_signature_starts_with(pool, jwt->header.alg, "RS")
|| apr_jws_signature_starts_with(pool, jwt->header.alg, "PS");
}
#if (OPENSSL_VERSION_NUMBER >= 0x01000000)
/*
* check if the signature on the JWT is Elliptic Curve based
*/
apr_byte_t apr_jws_signature_is_ec(apr_pool_t *pool, apr_jwt_t *jwt) {
return apr_jws_signature_starts_with(pool, jwt->header.alg, "ES");
}
#endif
/*
* check the signature on a JWT against the provided keys
*/
static apr_byte_t apr_jws_verify_with_jwk(apr_pool_t *pool, apr_jwt_t *jwt,
apr_jwk_t *jwk, apr_jwt_error_t *err) {
apr_byte_t rc = FALSE;
if (apr_jws_signature_is_hmac(pool, jwt)) {
rc = (jwk->type == APR_JWK_KEY_OCT)
&& apr_jws_verify_hmac(pool, jwt, jwk, err);
} else if (apr_jws_signature_is_rsa(pool, jwt)) {
rc = (jwk->type == APR_JWK_KEY_RSA)
&& apr_jws_verify_rsa(pool, jwt, jwk, err);
#if (OPENSSL_VERSION_NUMBER >= 0x01000000)
} else if (apr_jws_signature_is_ec(pool, jwt)) {
rc = (jwk->type == APR_JWK_KEY_EC)
&& apr_jws_verify_ec(pool, jwt, jwk, err);
#endif
}
return rc;
}
/*
* verify the signature on a JWT
*/
apr_byte_t apr_jws_verify(apr_pool_t *pool, apr_jwt_t *jwt, apr_hash_t *keys,
apr_jwt_error_t *err) {
apr_byte_t rc = FALSE;
apr_jwk_t *jwk = NULL;
apr_hash_index_t *hi;
if (jwt->header.kid != NULL) {
jwk = apr_hash_get(keys, jwt->header.kid,
APR_HASH_KEY_STRING);
if (jwk != NULL) {
rc = apr_jws_verify_with_jwk(pool, jwt, jwk, err);
} else {
apr_jwt_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);
rc = apr_jws_verify_with_jwk(pool, jwt, jwk, err);
if (rc == TRUE)
break;
}
if (rc == FALSE)
apr_jwt_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;
}
mod_auth_openidc-1.8.5/src/jose/apr_jwe.c 0000664 0001750 0001750 00000047577 12577725501 020601 0 ustar zandbelt zandbelt /*
* 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-2015 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 Encryption handling
*
* @Author: Hans Zandbelt - hzandbelt@pingidentity.com
*/
#include
#include
#include
#include
#include
#include
#include
#include "apr_jose.h"
/*
* return all supported content encryption key algorithms
*/
apr_array_header_t *apr_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) = "RSA1_5";
*(const char**) apr_array_push(result) = "A128KW";
*(const char**) apr_array_push(result) = "A192KW";
*(const char**) apr_array_push(result) = "A256KW";
*(const char**) apr_array_push(result) = "RSA-OAEP";
return result;
}
/*
* check if the provided content encryption key algorithm is supported
*/
apr_byte_t apr_jwe_algorithm_is_supported(apr_pool_t *pool, const char *alg) {
return apr_jwt_array_has_string(apr_jwe_supported_algorithms(pool), alg);
}
/*
* return all supported encryption algorithms
*/
apr_array_header_t *apr_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) = "A128CBC-HS256";
*(const char**) apr_array_push(result) = "A192CBC-HS384";
*(const char**) apr_array_push(result) = "A256CBC-HS512";
#if (OPENSSL_VERSION_NUMBER >= 0x1000100f)
*(const char**) apr_array_push(result) = "A128GCM";
*(const char**) apr_array_push(result) = "A192GCM";
*(const char**) apr_array_push(result) = "A256GCM";
#endif
return result;
}
/*
* check if the provided encryption algorithm is supported
*/
apr_byte_t apr_jwe_encryption_is_supported(apr_pool_t *pool, const char *enc) {
return apr_jwt_array_has_string(apr_jwe_supported_encryptions(pool), enc);
}
/*
* check if the the JWT is encrypted
*/
apr_byte_t apr_jwe_is_encrypted_jwt(apr_pool_t *pool, apr_jwt_header_t *hdr) {
return (apr_jwe_algorithm_is_supported(pool, hdr->alg)
&& (apr_jwe_encryption_is_supported(pool, hdr->enc)));
}
/*
* return OpenSSL cipher for JWE encryption algorithm
*/
static const EVP_CIPHER *apr_jwe_enc_to_openssl_cipher(const char *enc) {
if (apr_strnatcmp(enc, "A128CBC-HS256") == 0) {
return EVP_aes_128_cbc();
}
if (apr_strnatcmp(enc, "A192CBC-HS384") == 0) {
return EVP_aes_192_cbc();
}
if (apr_strnatcmp(enc, "A256CBC-HS512") == 0) {
return EVP_aes_256_cbc();
}
#if (OPENSSL_VERSION_NUMBER >= 0x1000100f)
if (apr_strnatcmp(enc, "A128CM") == 0) {
return EVP_aes_128_gcm();
}
if (apr_strnatcmp(enc, "A192GCM") == 0) {
return EVP_aes_192_gcm();
}
if (apr_strnatcmp(enc, "A256GCM") == 0) {
return EVP_aes_256_gcm();
}
#endif
return NULL;
}
/*
* return OpenSSL hash for JWE encryption algorithm
*/
static const EVP_MD *apr_jwe_enc_to_openssl_hash(const char *enc) {
if (apr_strnatcmp(enc, "A128CBC-HS256") == 0) {
return EVP_sha256();
}
if (apr_strnatcmp(enc, "A192CBC-HS384") == 0) {
return EVP_sha384();
}
if (apr_strnatcmp(enc, "A256CBC-HS512") == 0) {
return EVP_sha512();
}
return NULL;
}
/*
* convert a JWK (RSA) key to an OpenSSL RSA key
*/
static RSA* apr_jwe_jwk_to_openssl_rsa_key(apr_jwk_t *jwk) {
RSA * key = RSA_new();
BIGNUM * modulus = BN_new();
BIGNUM * exponent = BN_new();
BN_bin2bn(jwk->key.rsa->modulus, jwk->key.rsa->modulus_len, modulus);
BN_bin2bn(jwk->key.rsa->exponent, jwk->key.rsa->exponent_len, exponent);
BIGNUM * private_exp = NULL;
/* check if there's a private_exponent component, i.e. this is a private key */
if (jwk->key.rsa->private_exponent != NULL) {
private_exp = BN_new();
BN_bin2bn(jwk->key.rsa->private_exponent,
jwk->key.rsa->private_exponent_len, private_exp);
}
key->n = modulus;
key->e = exponent;
/* private_exp is NULL for public keys */
key->d = private_exp;
return key;
}
/*
* pointer to base64url decoded JWT elements
*/
typedef struct apr_jwe_unpacked_t {
char *value;
int len;
} apr_jwe_unpacked_t;
/*
* base64url decode deserialized JWT elements
*/
static apr_array_header_t *apr_jwe_unpacked_base64url_decode(apr_pool_t *pool,
apr_array_header_t *unpacked) {
apr_array_header_t *result = apr_array_make(pool, unpacked->nelts,
sizeof(const char*));
int i;
for (i = 0; i < unpacked->nelts; i++) {
apr_jwe_unpacked_t *elem = apr_pcalloc(pool,
sizeof(apr_jwe_unpacked_t));
elem->len = apr_jwt_base64url_decode(pool, &elem->value,
((const char**) unpacked->elts)[i], 1);
if (elem->len <= 0)
continue;
APR_ARRAY_PUSH(result, apr_jwe_unpacked_t *) = elem;
}
return result;
}
/* indexes in to a compact serialized JSON element */
#define APR_JWE_ENCRYPTED_KEY_INDEX 1
#define APR_JWE_INITIALIZATION_VECTOR_INDEX 2
#define APR_JWE_CIPHER_TEXT_INDEX 3
#define APR_JWE_AUTHENTICATION_TAG_INDEX 4
/*
* decrypt RSA encrypted Content Encryption Key
*/
static apr_byte_t apr_jwe_decrypt_cek_rsa(apr_pool_t *pool, int padding,
apr_jwt_header_t *header, apr_array_header_t *unpacked_decoded,
apr_jwk_t *jwk_rsa, unsigned char **cek, int *cek_len,
apr_jwt_error_t *err) {
RSA *pkey = apr_jwe_jwk_to_openssl_rsa_key(jwk_rsa);
if (pkey == NULL) {
apr_jwt_error(err, "apr_jwe_jwk_to_openssl_rsa_key failed");
return FALSE;
}
/* find and decrypt Content Encryption Key */
apr_jwe_unpacked_t *encrypted_key =
((apr_jwe_unpacked_t **) unpacked_decoded->elts)[APR_JWE_ENCRYPTED_KEY_INDEX];
*cek = apr_pcalloc(pool, RSA_size(pkey));
*cek_len = RSA_private_decrypt(encrypted_key->len,
(const unsigned char *) encrypted_key->value, *cek, pkey, padding);
if (*cek_len <= 0)
apr_jwt_error_openssl(err, "RSA_private_decrypt");
/* free allocated resources */
RSA_free(pkey);
/* set return value based on decrypt result */
return (*cek_len > 0);
}
/*
* decrypt AES wrapped Content Encryption Key with the provided symmetric key
*/
static apr_byte_t apr_jwe_decrypt_cek_oct_aes(apr_pool_t *pool,
apr_jwt_header_t *header, apr_array_header_t *unpacked_decoded,
const unsigned char *shared_key, const int shared_key_len,
unsigned char **cek, int *cek_len, apr_jwt_error_t *err) {
/* determine key length in bits */
int key_bits_len = 0;
if (apr_strnatcmp(header->alg, "A128KW") == 0) key_bits_len = 128;
if (apr_strnatcmp(header->alg, "A192KW") == 0) key_bits_len = 192;
if (apr_strnatcmp(header->alg, "A256KW") == 0) key_bits_len = 256;
if (shared_key_len * 8 < key_bits_len) {
apr_jwt_error(err,
"symmetric key length is too short: %d (should be at least %d)",
shared_key_len * 8, key_bits_len);
return FALSE;
}
/* create the AES decryption key from the shared key */
AES_KEY akey;
if (AES_set_decrypt_key((const unsigned char *) shared_key, key_bits_len,
&akey) < 0) {
apr_jwt_error_openssl(err, "AES_set_decrypt_key");
return FALSE;
}
/* determine the Content Encryption Key key length based on the content encryption algorithm */
*cek_len = (apr_strnatcmp(header->enc, "A128CBC-HS256") == 0) ? 32 : 64;
/* get the encrypted key from the compact serialized JSON representation */
apr_jwe_unpacked_t *encrypted_key = APR_ARRAY_IDX(unpacked_decoded,
APR_JWE_ENCRYPTED_KEY_INDEX, apr_jwe_unpacked_t *);
/* unwrap the AES key */
*cek = apr_pcalloc(pool, *cek_len);
int rv = AES_unwrap_key(&akey, (const unsigned char*) NULL, *cek,
(const unsigned char *) encrypted_key->value, encrypted_key->len);
if (rv <= 0)
apr_jwt_error_openssl(err, "AES_unwrap_key");
/* return success based on the return value of AES_unwrap_key */
return (rv > 0);
}
/*
* try to decrypt the Content Encryption key with the specified JWK
*/
static apr_byte_t apr_jwe_decrypt_cek_with_jwk(apr_pool_t *pool,
apr_jwt_header_t *header, apr_array_header_t *unpacked_decoded,
apr_jwk_t *jwk, unsigned char **cek, int *cek_len, apr_jwt_error_t *err) {
apr_byte_t rc = FALSE;
if (apr_strnatcmp(header->alg, "RSA1_5") == 0) {
rc = (jwk->type == APR_JWK_KEY_RSA)
&& apr_jwe_decrypt_cek_rsa(pool, RSA_PKCS1_PADDING, header,
unpacked_decoded, jwk, cek, cek_len, err);
} else if ((apr_strnatcmp(header->alg, "A128KW") == 0)
|| (apr_strnatcmp(header->alg, "A192KW") == 0)
|| (apr_strnatcmp(header->alg, "A256KW") == 0)) {
rc = (jwk->type == APR_JWK_KEY_OCT)
&& apr_jwe_decrypt_cek_oct_aes(pool, header, unpacked_decoded,
jwk->key.oct->k, jwk->key.oct->k_len, cek, cek_len,
err);
} else if (apr_strnatcmp(header->alg, "RSA-OAEP") == 0) {
rc = (jwk->type == APR_JWK_KEY_RSA)
&& apr_jwe_decrypt_cek_rsa(pool, RSA_PKCS1_OAEP_PADDING, header,
unpacked_decoded, jwk, cek, cek_len, err);
}
return rc;
}
/*
* decrypt the Content Encryption Key with one out of a list of keys
* based on the content key encryption algorithm in the header
*/
static apr_byte_t apr_jwe_decrypt_cek(apr_pool_t *pool,
apr_jwt_header_t *header, apr_array_header_t *unpacked_decoded,
apr_hash_t *keys, unsigned char **cek, int *cek_len,
apr_jwt_error_t *err) {
apr_byte_t rc = FALSE;
apr_jwk_t *jwk = NULL;
apr_hash_index_t *hi;
if (header->kid != NULL) {
jwk = apr_hash_get(keys, header->kid,
APR_HASH_KEY_STRING);
if (jwk != NULL) {
rc = apr_jwe_decrypt_cek_with_jwk(pool, header, unpacked_decoded,
jwk, cek, cek_len, err);
} else {
apr_jwt_error(err, "could not find key with kid: %s", 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);
rc = apr_jwe_decrypt_cek_with_jwk(pool, header, unpacked_decoded,
jwk, cek, cek_len, err);
if (rc == TRUE)
break;
}
}
return rc;
}
#if (OPENSSL_VERSION_NUMBER >= 0x1000100f)
/*
* Decrypt AES-GCM content
*/
apr_byte_t apr_jwe_decrypt_content_aesgcm(apr_pool_t *pool,
apr_jwt_header_t *header, apr_jwe_unpacked_t *cipher_text,
unsigned char *cek, int cek_len, apr_jwe_unpacked_t *iv, char *aad,
int aad_len, apr_jwe_unpacked_t *tag, char **decrypted,
apr_jwt_error_t *err) {
EVP_CIPHER_CTX *ctx;
int outlen, rv;
ctx = EVP_CIPHER_CTX_new();
if (!EVP_DecryptInit_ex(ctx, apr_jwe_enc_to_openssl_cipher(header->enc),
NULL, NULL, NULL)) {
apr_jwt_error_openssl(err, "EVP_DecryptInit_ex (aes-gcm)");
return FALSE;
}
unsigned char *plaintext = apr_palloc(pool,
cipher_text->len
+ EVP_CIPHER_block_size(
apr_jwe_enc_to_openssl_cipher(header->enc)));
/* set IV length, omit for 96 bits */
//EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, sizeof(gcm_iv), NULL);
// TODO: check cek_len == ??
// TODO: check iv->len == 96 bits
/* specify key and IV */
if (!EVP_DecryptInit_ex(ctx, NULL, NULL, cek,
(unsigned char *) iv->value)) {
apr_jwt_error_openssl(err, "EVP_DecryptInit_ex (iv)");
return FALSE;
}
/* zero or more calls to specify any AAD */
if (!EVP_DecryptUpdate(ctx, NULL, &outlen, (unsigned char *) aad,
aad_len)) {
apr_jwt_error_openssl(err, "EVP_DecryptUpdate (aad)");
return FALSE;
}
/* decrypt plaintext */
if (!EVP_DecryptUpdate(ctx, plaintext, &outlen,
(unsigned char *) cipher_text->value, cipher_text->len)) {
apr_jwt_error_openssl(err, "EVP_DecryptUpdate (ciphertext)");
return FALSE;
}
/* set expected tag value. */
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, tag->len, tag->value)) {
apr_jwt_error_openssl(err, "EVP_CIPHER_CTX_ctrl");
return FALSE;
}
/* finalise: note get no output for GCM */
rv = EVP_DecryptFinal_ex(ctx, plaintext, &outlen);
EVP_CIPHER_CTX_free(ctx);
if (rv > 0) {
*decrypted = (char *) plaintext;
return TRUE;
}
apr_jwt_error_openssl(err, "EVP_DecryptFinal_ex");
return FALSE;
}
#endif
/*
* Decrypt A128CBC-HS256, A192CBC-HS384 and A256CBC-HS512 content
*/
apr_byte_t apr_jwe_decrypt_content_aescbc(apr_pool_t *pool,
apr_jwt_header_t *header, const unsigned char *msg, int msg_len,
apr_jwe_unpacked_t *cipher_text, unsigned char *cek, int cek_len,
apr_jwe_unpacked_t *iv, char *aad, int aad_len,
apr_jwe_unpacked_t *auth_tag, char **decrypted, apr_jwt_error_t *err) {
/* extract MAC key from CEK: second half of CEK bits */
unsigned char *mac_key = apr_pcalloc(pool, cek_len / 2);
memcpy(mac_key, cek, cek_len / 2);
/* extract encryption key from CEK: first half of CEK bits */
unsigned char *enc_key = apr_pcalloc(pool, cek_len / 2);
memcpy(enc_key, cek + cek_len / 2, cek_len / 2);
/* calculate the Authentication Tag value over AAD + IV + ciphertext + AAD length */
unsigned int md_len = 0;
unsigned char md[EVP_MAX_MD_SIZE];
if (!HMAC(apr_jwe_enc_to_openssl_hash(header->enc), mac_key, cek_len / 2,
msg, msg_len, md, &md_len)) {
apr_jwt_error_openssl(err, "Authentication Tag calculation HMAC");
return FALSE;
}
/* use only the first half of the bits */
md_len = md_len / 2;
/* verify the provided Authentication Tag against what we've calculated ourselves */
if (md_len != auth_tag->len) {
apr_jwt_error(err,
"calculated Authentication Tag hash length differs from the length of the Authentication Tag length in the encrypted JWT");
return FALSE;
}
if (apr_jwt_memcmp(md, auth_tag->value, md_len) == FALSE) {
apr_jwt_error(err,
"calculated Authentication Tag hash differs from the Authentication Tag in the encrypted JWT");
return FALSE;
}
/* if everything still OK, now AES (128/192/256) decrypt the ciphertext */
int p_len = cipher_text->len, f_len = 0;
/* allocate ciphertext length + one block padding for plaintext */
unsigned char *plaintext = apr_palloc(pool, p_len + AES_BLOCK_SIZE);
/* initialize decryption context */
EVP_CIPHER_CTX decrypt_ctx;
EVP_CIPHER_CTX_init(&decrypt_ctx);
/* pass the extracted encryption key and Initialization Vector */
if (!EVP_DecryptInit_ex(&decrypt_ctx,
apr_jwe_enc_to_openssl_cipher(header->enc), NULL, enc_key,
(const unsigned char *) iv->value)) {
apr_jwt_error_openssl(err, "EVP_DecryptInit_ex");
return FALSE;
}
/* decrypt the ciphertext in to the plaintext */
if (!EVP_DecryptUpdate(&decrypt_ctx, plaintext, &p_len,
(const unsigned char *) cipher_text->value, cipher_text->len)) {
apr_jwt_error_openssl(err, "EVP_DecryptUpdate");
return FALSE;
}
/* decrypt the remaining bits/padding */
if (!EVP_DecryptFinal_ex(&decrypt_ctx, plaintext + p_len, &f_len)) {
apr_jwt_error_openssl(err, "EVP_DecryptFinal_ex");
return FALSE;
}
plaintext[p_len + f_len] = '\0';
*decrypted = (char *) plaintext;
/* cleanup */
EVP_CIPHER_CTX_cleanup(&decrypt_ctx);
/* if we got here, all must be fine */
return TRUE;
}
static unsigned char *apr_jwe_cek_dummy =
(unsigned char *) "01234567890123456789012345678901";
static int apr_jwe_cek_len_dummy = 32;
/*
* decrypt encrypted JWT
*/
apr_byte_t apr_jwe_decrypt_jwt(apr_pool_t *pool, apr_jwt_header_t *header,
apr_array_header_t *unpacked, apr_hash_t *keys, char **decrypted,
apr_jwt_error_t *err_r) {
apr_jwt_error_t err_dummy, *err = err_r;
unsigned char *cek = NULL;
int cek_len = 0;
/* base64url decode all elements of the compact serialized JSON representation */
apr_array_header_t *unpacked_decoded = apr_jwe_unpacked_base64url_decode(
pool, unpacked);
/* since this is an encrypted JWT it must have 5 elements */
if (unpacked_decoded->nelts != 5) {
apr_jwt_error(err,
"could not successfully base64url decode 5 elements from encrypted JWT header but only %d",
unpacked_decoded->nelts);
return FALSE;
}
/* decrypt the Content Encryption Key */
if (apr_jwe_decrypt_cek(pool, header, unpacked_decoded, keys, &cek,
&cek_len, err) == FALSE) {
/* substitute dummy CEK to avoid timing attacks */
cek = apr_jwe_cek_dummy;
cek_len = apr_jwe_cek_len_dummy;
/* save the original error, now in err_r */
err = &err_dummy;
}
/* get the other elements (Initialization Vector, encrypted text and Authentication Tag) from the compact serialized JSON representation */
apr_jwe_unpacked_t *iv = APR_ARRAY_IDX(unpacked_decoded,
APR_JWE_INITIALIZATION_VECTOR_INDEX, apr_jwe_unpacked_t *);
apr_jwe_unpacked_t *cipher_text = APR_ARRAY_IDX(unpacked_decoded,
APR_JWE_CIPHER_TEXT_INDEX, apr_jwe_unpacked_t *);
apr_jwe_unpacked_t *auth_tag = APR_ARRAY_IDX(unpacked_decoded,
APR_JWE_AUTHENTICATION_TAG_INDEX, apr_jwe_unpacked_t *);
/* determine the Additional Authentication Data: the protected JSON header */
char *aad = NULL;
if (apr_jwt_base64url_encode(pool, &aad, (const char *) header->value.str,
strlen(header->value.str), 0) <= 0) {
apr_jwt_error(err, "apr_jwt_base64url_encode of JSON header failed");
return FALSE;
}
int aad_len = strlen(aad);
/* Additional Authentication Data length in # of bits in 64 bit length field */
uint64_t al = aad_len * 8;
/* concatenate AAD + IV + ciphertext + AAD length field */
int msg_len = aad_len + iv->len + cipher_text->len + sizeof(uint64_t);
const unsigned char *msg = apr_pcalloc(pool, msg_len);
char *p = (char*) msg;
memcpy(p, aad, aad_len);
p += aad_len;
memcpy(p, iv->value, iv->len);
p += iv->len;
memcpy(p, cipher_text->value, cipher_text->len);
p += cipher_text->len;
/* check if we are on a big endian or little endian machine */
int c = 1;
if (*(char *) &c == 1) {
// little endian machine: reverse AAD length for big endian representation
al = (al & 0x00000000FFFFFFFF) << 32 | (al & 0xFFFFFFFF00000000) >> 32;
al = (al & 0x0000FFFF0000FFFF) << 16 | (al & 0xFFFF0000FFFF0000) >> 16;
al = (al & 0x00FF00FF00FF00FF) << 8 | (al & 0xFF00FF00FF00FF00) >> 8;
}
memcpy(p, &al, sizeof(uint64_t));
if ((apr_strnatcmp(header->enc, "A128CBC-HS256") == 0)
|| (apr_strnatcmp(header->enc, "A192CBC-HS384") == 0)
|| (apr_strnatcmp(header->enc, "A256CBC-HS512") == 0)) {
return apr_jwe_decrypt_content_aescbc(pool, header, msg, msg_len,
cipher_text, cek, cek_len, iv, aad, aad_len, auth_tag,
decrypted, err_r);
#if (OPENSSL_VERSION_NUMBER >= 0x1000100f)
} else if ((apr_strnatcmp(header->enc, "A128GCM") == 0)
|| (apr_strnatcmp(header->enc, "A192GCM") == 0)
|| (apr_strnatcmp(header->enc, "A256GCM") == 0)) {
return apr_jwe_decrypt_content_aesgcm(pool, header, cipher_text, cek,
cek_len, iv, aad, aad_len, auth_tag, decrypted, err_r);
#endif
}
return FALSE;
}
mod_auth_openidc-1.8.5/src/cache/redis.c 0000644 0001750 0001750 00000026147 12551525012 020332 0 ustar zandbelt zandbelt /*
* 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-2015 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 - hzandbelt@pingidentity.com
*/
#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"
/*
* connect to Redis server
*/
static redisContext * 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 */
redisContext *ctx = NULL;
apr_pool_userdata_get((void **) &ctx, OIDC_CACHE_REDIS_CONTEXT,
r->server->process->pool);
if (ctx == NULL) {
/* no connection, connect to the configured Redis server */
ctx = redisConnect(context->host_str, context->port);
/* check for errors */
if ((ctx == NULL) || (ctx->err != 0)) {
oidc_error(r, "failed to connect to Redis server (%s:%d): '%s'",
context->host_str, context->port, ctx->errstr);
return NULL;
}
/* store the connection in the process context */
apr_pool_userdata_set(ctx, OIDC_CACHE_REDIS_CONTEXT,
(apr_status_t (*)(void *)) redisFree, r->server->process->pool);
/* log the connection */
oidc_debug(r, "successfully connected to Redis server (%s:%d)",
context->host_str, context->port);
}
return ctx;
}
/*
* 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, ...) {
redisContext *ctx = 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 */
ctx = oidc_cache_redis_connect(r, context);
if (ctx == NULL)
break;
if (context->passwd != NULL) {
redisAppendCommand(ctx, apr_psprintf(r->pool, "AUTH %s", context->passwd));
}
/* execute the command */
va_list args;
va_start(args, format);
redisvAppendCommand(ctx, format, args);
va_end(args);
if (context->passwd != NULL) {
/* get the reply for the AUTH command */
redisGetReply(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(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, ctx->errstr);
/* cleanup, we may try again (once) after reconnecting */
redisFree(ctx);
apr_pool_userdata_set(NULL, OIDC_CACHE_REDIS_CONTEXT,
apr_pool_cleanup_null, r->server->process->pool);
}
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_debug(r, "enter, section=\"%s\", key=\"%s\"", section, key);
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_debug(r, "enter, section=\"%s\", key=\"%s\"", section, key);
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 = {
oidc_cache_redis_cfg_create,
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-1.8.5/src/jose/apr_jose.h 0000644 0001750 0001750 00000024436 12545531131 020731 0 ustar zandbelt zandbelt /*
* 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-2015 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 - hzandbelt@pingidentity.com
*/
#ifndef _APR_JOSE_H_
#define _APR_JOSE_H_
#include
#include "apr_pools.h"
#include "apr_tables.h"
#include "apr_hash.h"
#include "apr_strings.h"
#include "jansson.h"
#define APR_JWT_CLAIM_TIME_EMPTY -1
#define APR_JWT_ERROR_TEXT_LENGTH 200
#define APR_JWT_ERROR_SOURCE_LENGTH 80
#define APR_JWT_ERROR_FUNCTION_LENGTH 80
/* struct for returning errors to the caller */
typedef struct {
char source[JSON_ERROR_SOURCE_LENGTH];
int line;
char function[APR_JWT_ERROR_FUNCTION_LENGTH];
char text[APR_JWT_ERROR_TEXT_LENGTH];
} apr_jwt_error_t;
void _apr_jwt_error_set(apr_jwt_error_t *, const char *, const int,
const char *, const char *msg, ...);
#define apr_jwt_error(err, msg, ...) _apr_jwt_error_set(err, __FILE__, __LINE__, __FUNCTION__, msg, ##__VA_ARGS__)
#define apr_jwt_error_openssl(err, msg, ...) _apr_jwt_error_set(err, __FILE__, __LINE__, __FUNCTION__, "%s() failed: %s", msg, ERR_error_string(ERR_get_error(), NULL), ##__VA_ARGS__)
#define apr_jwt_e2s(pool, err) apr_psprintf(pool, "[%s:%d: %s]: %s\n", err.source, err.line, err.function, err.text)
/*
* JSON Web Token handling
*/
/* a parsed JWT "element", header or payload */
typedef struct apr_jwt_value_t {
/* parsed JSON struct representation */
json_t *json;
/* string representation */
char *str;
} apr_jwt_value_t;
/* a parsed JWT header */
typedef struct apr_jwt_header_t {
/* parsed header value */
apr_jwt_value_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;
} apr_jwt_header_t;
/* parsed JWT payload */
typedef struct apr_jwt_payload_t {
/* parsed payload value */
apr_jwt_value_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 */
json_int_t exp;
/* parsed JWT "iat" claim value; issued-at timestamp */
json_int_t iat;
} apr_jwt_payload_t;
/* parsed JWT signature */
typedef struct apr_jwt_signature_t {
/* raw (base64url-decoded) signature value */
unsigned char *bytes;
/* length of the raw signature value */
int length;
} apr_jwt_signature_t;
/* parsed JWT */
typedef struct apr_jwt_t {
/* parsed JWT header */
apr_jwt_header_t header;
/* parsed JWT payload */
apr_jwt_payload_t payload;
/* decoded JWT signature */
apr_jwt_signature_t signature;
/* base64url-encoded header+payload (for signature verification purposes) */
char *message;
} apr_jwt_t;
/* helpers */
typedef apr_byte_t (*apr_jose_is_supported_function_t)(apr_pool_t *,
const char *);
apr_byte_t apr_jwt_array_has_string(apr_array_header_t *haystack,
const char *needle);
int apr_jwt_base64url_encode(apr_pool_t *pool, char **dst, const char *src,
int src_len, int padding);
int apr_jwt_base64url_decode(apr_pool_t *pool, char **dst, const char *src,
int padding);
const char *apr_jwt_header_to_string(apr_pool_t *pool, const char *s_json,
apr_jwt_error_t *err);
/* return a string claim value from a JSON Web Token */
apr_byte_t apr_jwt_get_string(apr_pool_t *pool, json_t *json,
const char *claim_name, apr_byte_t is_mandatory, char **result,
apr_jwt_error_t *err);
/* parse a string into a JSON Web Token struct and (optionally) decrypt it */
apr_byte_t apr_jwt_parse(apr_pool_t *pool, const char *s_json,
apr_jwt_t **j_jwt, apr_hash_t *keys, apr_jwt_error_t *err);
/* destroy resources allocated for JWT */
void apr_jwt_destroy(apr_jwt_t *);
/* exported for the purpose of the test suite */
apr_byte_t apr_jwt_header_parse(apr_pool_t *pool, const char *s_json,
apr_array_header_t **unpacked, apr_jwt_header_t *header,
apr_jwt_error_t *err);
/* return the JWK type for the JWT signature verification */
const char *apr_jwt_signature_to_jwk_type(apr_pool_t *pool, apr_jwt_t *jwt);
/*
* JSON Web Key handling
*/
/* JWK key type */
typedef enum apr_jwk_type_e {
/* RSA JWT key type */
APR_JWK_KEY_RSA,
/* EC JWT key type */
APR_JWK_KEY_EC,
/* oct JWT key type */
APR_JWK_KEY_OCT,
} apr_jwk_type_e;
/* parsed RSA JWK key */
typedef struct apr_jwk_key_rsa_t {
/* (binary) RSA modulus */
unsigned char *modulus;
/* length of the binary RSA modulus */
int modulus_len;
/* (binary) RSA exponent */
unsigned char *exponent;
/* length of the binary RSA exponent */
int exponent_len;
/* (binary) RSA private exponent */
unsigned char *private_exponent;
/* length of the binary private RSA exponent */
int private_exponent_len;
} apr_jwk_key_rsa_t;
/* parsed EC JWK key */
typedef struct apr_jwk_key_ec_t {
/* x */
unsigned char *x;
/* length of x */
int x_len;
/* y */
unsigned char *y;
/* length of y */
int y_len;
} apr_jwk_key_ec_t;
/* parsed oct JWK key */
typedef struct apr_jwk_key_oct_t {
/* k octets */
unsigned char *k;
/* length of k */
int k_len;
} apr_jwk_key_oct_t;
/* parsed JWK key */
typedef struct apr_jwk_t {
/* key identifier */
char *kid;
/* type of JWK key */
apr_jwk_type_e type;
/* union/pointer to parsed JWK key */
union {
apr_jwk_key_rsa_t *rsa;
apr_jwk_key_ec_t *ec;
apr_jwk_key_oct_t *oct;
} key;
} apr_jwk_t;
/* parse a JSON representation in to a JSON Web Key struct (also storing the string representation */
apr_byte_t apr_jwk_parse_json(apr_pool_t *pool, json_t *j_json,
apr_jwk_t **j_jwk, apr_jwt_error_t *err);
/* parse a symmetric key in to a JSON Web Key (oct) struct */
apr_byte_t apr_jwk_parse_symmetric_key(apr_pool_t *pool,
const unsigned char *key, unsigned int key_len, apr_jwk_t **j_jwk,
apr_jwt_error_t *err);
/* parse an RSA private key from a PEM formatted file */
apr_byte_t apr_jwk_parse_rsa_private_key(apr_pool_t *pool, const char *filename,
apr_jwk_t **j_jwk, apr_jwt_error_t *err);
/* parse an RSA public key from a PEM formatted file */
apr_byte_t apr_jwk_parse_rsa_public_key(apr_pool_t *pool, const char *filename,
apr_jwk_t **j_jwk, apr_jwt_error_t *err);
apr_byte_t apr_jwk_to_json(apr_pool_t *pool, apr_jwk_t *jwk, char **s_json,
apr_jwt_error_t *err);
/*
* JSON Web Token Signature handling
*/
/* return all supported signing algorithms */
apr_array_header_t *apr_jws_supported_algorithms(apr_pool_t *pool);
/* check if the provided signing algorithm is supported */
apr_byte_t apr_jws_algorithm_is_supported(apr_pool_t *pool, const char *alg);
/* check if the signature on a JWT is of type HMAC */
apr_byte_t apr_jws_signature_is_hmac(apr_pool_t *pool, apr_jwt_t *jwt);
/* check if the signature on a JWT is of type RSA */
apr_byte_t apr_jws_signature_is_rsa(apr_pool_t *pool, apr_jwt_t *jwt);
/* check if the signature on a JWT is of type Elliptic Curve */
apr_byte_t apr_jws_signature_is_ec(apr_pool_t *pool, apr_jwt_t *jwt);
/* verify the signature on a JWT */
apr_byte_t apr_jws_verify(apr_pool_t *pool, apr_jwt_t *jwt, apr_hash_t *keys,
apr_jwt_error_t *err);
/* hash byte sequence */
apr_byte_t apr_jws_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, apr_jwt_error_t *err);
/* hash a string */
apr_byte_t apr_jws_hash_string(apr_pool_t *pool, const char *alg,
const char *msg, char **hash, unsigned int *hash_len,
apr_jwt_error_t *err);
/* length of hash */
int apr_jws_hash_length(const char *alg);
/*
* JSON Web Token Encryption handling
*/
/* return all supported content encryption key algorithms */
apr_array_header_t *apr_jwe_supported_algorithms(apr_pool_t *pool);
/* check if the provided content encryption key algorithm is supported */
apr_byte_t apr_jwe_algorithm_is_supported(apr_pool_t *pool, const char *alg);
/* return all supported encryption algorithms */
apr_array_header_t *apr_jwe_supported_encryptions(apr_pool_t *pool);
/* check if the provided encryption algorithm is supported */
apr_byte_t apr_jwe_encryption_is_supported(apr_pool_t *pool, const char *enc);
apr_byte_t apr_jwe_is_encrypted_jwt(apr_pool_t *pool, apr_jwt_header_t *hdr);
apr_byte_t apr_jwe_decrypt_jwt(apr_pool_t *pool, apr_jwt_header_t *header,
apr_array_header_t *unpacked, apr_hash_t *keys, char **decrypted,
apr_jwt_error_t *err);
apr_byte_t apr_jwt_memcmp(const void *in_a, const void *in_b, size_t len);
#endif /* _APR_JOSE_H_ */
mod_auth_openidc-1.8.5/src/mod_auth_openidc.h 0000664 0001750 0001750 00000057131 12577725501 021503 0 ustar zandbelt zandbelt /*
* 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-2015 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 - hzandbelt@pingidentity.com
*/
#ifndef MOD_AUTH_OPENIDC_H_
#define MOD_AUTH_OPENIDC_H_
#include
#include
#include
#include
#include
#include
#include
#include
#include "apr_memcache.h"
#include "apr_shm.h"
#include "apr_global_mutex.h"
#include "jose/apr_jose.h"
#include "cache/cache.h"
#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
/* key for storing the claims in the session context */
#define OIDC_CLAIMS_SESSION_KEY "claims"
/* key for storing the id_token in the session context */
#define OIDC_IDTOKEN_CLAIMS_SESSION_KEY "id_token_claims"
/* key for storing the raw id_token in the session context */
#define OIDC_IDTOKEN_SESSION_KEY "id_token"
/* key for storing the access_token in the session context */
#define OIDC_ACCESSTOKEN_SESSION_KEY "access_token"
/* key for storing the access_token expiry in the session context */
#define OIDC_ACCESSTOKEN_EXPIRES_SESSION_KEY "access_token_expires"
/* key for storing the refresh_token in the session context */
#define OIDC_REFRESHTOKEN_SESSION_KEY "refresh_token"
/* key for storing maximum session duration in the session context */
#define OIDC_SESSION_EXPIRES_SESSION_KEY "session_expires"
/* key for storing request state */
#define OIDC_REQUEST_STATE_SESSION_KEY "request_state"
/* key for storing the original URL */
#define OIDC_REQUEST_ORIGINAL_URL "original_url"
/* key for storing the session_state in the session context */
#define OIDC_SESSION_STATE_SESSION_KEY "session_state"
/* key for storing the issuer in the session context */
#define OIDC_ISSUER_SESSION_KEY "issuer"
/* key for storing the client_id in the session context */
#define OIDC_CLIENTID_SESSION_KEY "client_id"
/* key for storing the check_session_iframe in the session context */
#define OIDC_CHECK_IFRAME_SESSION_KEY "check_session_iframe"
/* key for storing the end_session_endpoint in the session context */
#define OIDC_LOGOUT_ENDPOINT_SESSION_KEY "end_session_endpoint"
/* 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 original URL in the discovery response */
#define OIDC_DISC_RT_PARAM "target_link_uri"
/* 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"
/* value that indicates to use server-side cache based session tracking */
#define OIDC_SESSION_TYPE_22_SERVER_CACHE 0
/* value that indicates to use client cookie based session tracking */
#define OIDC_SESSION_TYPE_22_CLIENT_COOKIE 1
/* nonce bytes length */
#define OIDC_PROTO_NONCE_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
/* prefix of the cookie that binds the state in the authorization request/response to the browser */
#define OIDCStateCookiePrefix "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_NAME "claim"
/* 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 the name of the cookie/parameter for CSRF protection */
#define OIDC_CSRF_NAME "x_csrf"
/* cache sections */
#define OIDC_CACHE_SECTION_JTI "jti"
#define OIDC_CACHE_SECTION_SESSION "session"
#define OIDC_CACHE_SECTION_NONCE "nonce"
#define OIDC_CACHE_SECTION_JWKS "jwks"
#define OIDC_CACHE_SECTION_ACCESS_TOKEN "access_token"
#define OIDC_CACHE_SECTION_PROVIDER "provider"
typedef enum {
AUTHENTICATE, PASS, RETURN401
} unauthenticated_action;
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;
// 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;
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;
} oidc_provider_t ;
typedef struct oidc_remote_user_claim_t {
const char *claim_name;
const char *reg_exp;
} oidc_remote_user_claim_t;
typedef struct oidc_oauth_t {
int ssl_validate_server;
char *client_id;
char *client_secret;
char *introspection_endpoint_url;
char *introspection_endpoint_method;
char *introspection_endpoint_params;
char *introspection_endpoint_auth;
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;
/* 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;
/* 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
/* 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;
char *cookie_domain;
char *claim_delimiter;
char *claim_prefix;
oidc_remote_user_claim_t remote_user_claim;
int pass_idtoken_as;
int cookie_http_only;
char *outgoing_proxy;
char *crypto_passphrase;
EVP_CIPHER_CTX *encrypt_ctx;
EVP_CIPHER_CTX *decrypt_ctx;
} oidc_cfg;
typedef struct oidc_dir_cfg {
/* (optional) external OP discovery page */
char *discover_url;
char *cookie_path;
char *cookie;
char *authn_header;
unauthenticated_action unauth_action;
apr_array_header_t *pass_cookies;
apr_byte_t pass_info_in_headers;
apr_byte_t pass_info_in_env_vars;
} oidc_dir_cfg;
int oidc_check_user_id(request_rec *r);
#if MODULE_MAGIC_NUMBER_MAJOR >= 20100714
authz_status oidc_authz_checker(request_rec *r, const char *require_args, const void *parsed_require_args);
#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);
// oidc_oauth
int oidc_oauth_check_userid(request_rec *r, oidc_cfg *c);
// oidc_proto.c
int oidc_proto_authorization_request(request_rec *r, struct oidc_provider_t *provider, const char *login_hint, const char *redirect_uri, const char *state, json_t *proto_state, const char *id_token_hint, const char *auth_request_params);
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_resolve_code(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *code, char **id_token, char **access_token, char **token_type, int *expires_in, char **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);
apr_byte_t oidc_proto_resolve_userinfo(request_rec *r, oidc_cfg *cfg, oidc_provider_t *provider, const char *access_token, const char **response);
apr_byte_t oidc_proto_account_based_discovery(request_rec *r, oidc_cfg *cfg, const char *acct, 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, apr_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, apr_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, apr_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, json_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, apr_jwt_t **jwt);
apr_byte_t oidc_proto_authorization_response_code_idtoken(request_rec *r, oidc_cfg *c, json_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, apr_jwt_t **jwt);
apr_byte_t oidc_proto_handle_authorization_response_code_token(request_rec *r, oidc_cfg *c, json_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, apr_jwt_t **jwt);
apr_byte_t oidc_proto_handle_authorization_response_code(request_rec *r, oidc_cfg *c, json_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, apr_jwt_t **jwt);
apr_byte_t oidc_proto_handle_authorization_response_idtoken_token(request_rec *r, oidc_cfg *c, json_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, apr_jwt_t **jwt);
apr_byte_t oidc_proto_handle_authorization_response_idtoken(request_rec *r, oidc_cfg *c, json_t *proto_state, oidc_provider_t *provider, apr_table_t *params, const char *response_mode, apr_jwt_t **jwt);
// non-static for test.c
apr_byte_t oidc_proto_validate_access_token(request_rec *r, oidc_provider_t *provider, apr_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, apr_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, apr_jwt_t *jwt);
// oidc_authz.c
int oidc_authz_worker(request_rec *r, const json_t *const claims, const require_line *const reqs, int nelts);
#if MODULE_MAGIC_NUMBER_MAJOR >= 20100714
authz_status oidc_authz_worker24(request_rec *r, const json_t * const claims, const char *require_line);
#endif
// oidc_config.c
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);
// 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(request_rec *r, char **dst, const char *src);
int oidc_encrypt_base64url_encode_string(request_rec *r, char **dst, const char *src);
int oidc_base64url_decode_decrypt_string(request_rec *r, char **dst, const char *src);
char *oidc_get_current_url(const request_rec *r, const oidc_cfg *c);
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);
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, const char **response, int timeout, const char *outgoing_proxy, apr_array_header_t *pass_cookies);
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, const char **response, int timeout, const char *outgoing_proxy, apr_array_header_t *pass_cookies);
apr_byte_t oidc_util_http_post_json(request_rec *r, const char *url, const json_t *data, const char *basic_auth, const char *bearer_token, int ssl_validate_server, const char **response, int timeout, const char *outgoing_proxy, apr_array_header_t *pass_cookies);
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);
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, int 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, const 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, char **result);
apr_byte_t oidc_util_issuer_match(const char *a, const char *b);
int oidc_util_html_send_error(request_rec *r, 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 *response_type, 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);
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_symmetric_key(apr_pool_t *pool, apr_hash_t *private_keys, const char *secret, const char *hash_algo);
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_first_match(apr_pool_t *pool, const char *input, const char *regexp, char **output, char **error_str);
apr_byte_t oidc_util_json_merge(json_t *src, json_t *dst);
// oidc_crypto.c
unsigned char *oidc_crypto_aes_encrypt(request_rec *r, oidc_cfg *cfg, unsigned char *plaintext, int *len);
unsigned char *oidc_crypto_aes_decrypt(request_rec *r, oidc_cfg *cfg, unsigned char *ciphertext, int *len);
apr_byte_t oidc_crypto_destroy(oidc_cfg *cfg, server_rec *s);
// 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, const char **response);
apr_byte_t oidc_metadata_provider_parse(request_rec *r, 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
#if MODULE_MAGIC_NUMBER_MAJOR_NOT_WORKING_YET >= 20081201
// this stuff should make it easy to migrate to the post 2.3 mod_session infrastructure
#include "mod_session.h"
//#define OIDC_SESSION_USE_APACHE_SESSIONS 1
#else
typedef struct {
apr_pool_t *pool; /* pool to be used for this session */
apr_uuid_t *uuid; /* anonymous uuid of this particular session */
const char *remote_user; /* user who owns this particular session */
apr_table_t *entries; /* key value pairs */
const char *encoded; /* the encoded version of the key value pairs */
apr_time_t expiry; /* if > 0, the time of expiry of this session */
long maxage; /* if > 0, the maxage of the session, from
* which expiry is calculated */
int dirty; /* dirty flag */
int cached; /* true if this session was loaded from a
* cache of some kind */
int written; /* true if this session has already been
* written */
} session_rec;
#endif
apr_status_t oidc_session_init();
apr_status_t oidc_session_load(request_rec *r, session_rec **z);
apr_status_t oidc_session_get(request_rec *r, session_rec *z, const char *key, const char **value);
apr_status_t oidc_session_set(request_rec *r, session_rec *z, const char *key, const char *value);
apr_status_t oidc_session_save(request_rec *r, session_rec *z);
apr_status_t oidc_session_kill(request_rec *r, session_rec *z);
#endif /* MOD_AUTH_OPENIDC_H_ */
mod_auth_openidc-1.8.5/src/cache/cache.h 0000644 0001750 0001750 00000007630 12532644156 020302 0 ustar zandbelt zandbelt /*
* 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-2015 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 - hzandbelt@pingidentity.com
*/
#ifndef _MOD_AUTH_OPENIDC_CACHE_H_
#define _MOD_AUTH_OPENIDC_CACHE_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 {
oidc_cache_cfg_create create_config;
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;
} 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);
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-1.8.5/test/test.c 0000744 0001750 0001750 00000133313 12563645374 017344 0 ustar zandbelt zandbelt /*
* 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-2015 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 - hzandbelt@pingidentity.com
*
**************************************************************************/
#include
#include
#include
#include
#include "apr.h"
#include "apr_errno.h"
#include "apr_general.h"
#include "apr_time.h"
#include "jose/apr_jose.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, apr_jwt_e2s(pool, 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_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 *test_jwt_array_has_string(apr_pool_t *pool) {
apr_array_header_t *haystack = apr_array_make(pool, 3, sizeof(const char*));
*(const char**) apr_array_push(haystack) = "a";
*(const char**) apr_array_push(haystack) = "b";
*(const char**) apr_array_push(haystack) = "c";
TST_ASSERT("jwt_array_has_string (1)",
apr_jwt_array_has_string(haystack, "a"));
TST_ASSERT("jwt_array_has_string (2)",
apr_jwt_array_has_string(haystack, "d") == FALSE);
return 0;
}
static char *test_jwt_url_encode_decode(apr_pool_t *pool) {
char *dst = NULL;
char *src = "abcd";
TST_ASSERT("apr_jwt_base64url_encode (1)",
apr_jwt_base64url_encode(pool, &dst, src, strlen(src), 0));
TST_ASSERT_STR("apr_jwt_base64url_encode (2)", dst, "YWJjZA");
src = dst;
TST_ASSERT("apr_jwt_base64url_decode (1)",
apr_jwt_base64url_decode(pool, &dst, src, 1));
TST_ASSERT_STR("apr_jwt_base64url_decode (2)", dst, "abcd");
return 0;
}
static char *test_jwt_header_to_string(apr_pool_t *pool) {
const char * s = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9";
apr_jwt_error_t err;
const char *dst = apr_jwt_header_to_string(pool, s, &err);
TST_ASSERT_STR("apr_jwt_header_to_string", dst,
"{\"typ\":\"JWT\",\r\n \"alg\":\"HS256\"}");
return 0;
}
static char *_jwk_parse(apr_pool_t *pool, const char *s, apr_jwk_t **jwk,
apr_jwt_error_t *err) {
json_t *j_jwk = json_loads(s, 0, NULL);
TST_ASSERT("json_loads", ((j_jwk != NULL) && (json_is_object(j_jwk))));
TST_ASSERT_ERR("apr_jwk_parse_json",
apr_jwk_parse_json(pool, j_jwk, jwk, err), pool, (*err));
json_decref(j_jwk);
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");
apr_jwt_error_t err;
apr_jwt_t *jwt = NULL;
TST_ASSERT_ERR("apr_jwt_parse", apr_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);
apr_jwk_t *jwk;
const char * k =
"{\"kty\":\"oct\", \"k\":\"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow\"}";
jwk = NULL;
TST_ASSERT_ERR("apr_jwk_parse_json", _jwk_parse(pool, k, &jwk, &err) == 0,
pool, err);
apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk);
TST_ASSERT_ERR("apr_jws_verify", apr_jws_verify(pool, jwt, keys, &err),
pool, err);
apr_jwt_destroy(jwt);
s[5] = '.';
TST_ASSERT_ERR("corrupted header (1) apr_jwt_parse",
apr_jwt_parse(pool, s, &jwt, NULL, &err) == FALSE, pool, err);
apr_jwt_destroy(jwt);
s[0] = '\0';
TST_ASSERT_ERR("corrupted header (2) apr_jwt_parse",
apr_jwt_parse(pool, s, &jwt, NULL, &err) == FALSE, pool, err);
apr_jwt_destroy(jwt);
return 0;
}
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 =
apr_pstrdup(pool,
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IloxTkNqb2plaUhBaWItR204dkZFNnlhNmxQTSJ9.eyJub25jZSI6ImF2U2s3UzY5RzRrRUU4S200YlBpT2pyZkNoSHQ2bk80WjM5N0xwX2JRbmMsIiwiaWF0IjoxNDExNTgwODc2LCJhdF9oYXNoIjoieVRxc29PTlpidVdiTjZUYmdldnVEUSIsInN1YiI6IjYzNDNhMjljLTUzOTktNDRhNy05YjM1LTQ5OTBmNDM3N2M5NiIsImFtciI6InBhc3N3b3JkIiwiYXV0aF90aW1lIjoxNDExNTc3MjY3LCJpZHAiOiJpZHNydiIsIm5hbWUiOiJrc29uYXR5IiwiaXNzIjoiaHR0cHM6Ly9hZ3N5bmMuY29tIiwiYXVkIjoiYWdzeW5jX2ltcGxpY2l0IiwiZXhwIjoxNDExNTg0NDc1LCJuYmYiOjE0MTE1ODA4NzV9.lEG-DgHHa0JuOEuOTBvCqyexjRVcKXBnJJm289o2HyTgclpH80DsOMED9RlXCFfuDY7nw9i2cxUmIMAV42AdTxkMPomK3chytcajvpAZJirlk653bo9GTDXJSKZr5fwyEu--qahsoT5t9qvoWyFdYkvmMHFw1-mAHDGgVe23voc9jPuFFIhRRqIn4e8ikzN4VQeEV1UXJD02kYYFn2TRWURgiFyVeTr2r0MTn-auCEsFS_AfR1Bl_kmpMfqwrsicf5MTBvfPJeuSMt3t3d3LOGBkg36_z21X-ZRN7wy1KTjagr7iQ_y5csIpmtqs_QM55TTB9dW1HIosJPhiuMEJEA");
apr_jwt_t *jwt = NULL;
apr_jwt_error_t err;
TST_ASSERT_ERR("apr_jwt_parse",
apr_jwt_parse(pool, s_jwt, &jwt, NULL, &err), pool, err);
char *s_key =
"{"
"\"kty\": \"RSA\","
"\"use\": \"sig\","
"\"kid\": \"Z1NCjojeiHAib-Gm8vFE6ya6lPM\","
"\"x5t\": \"Z1NCjojeiHAib-Gm8vFE6ya6lPM\","
"\"x5c\": ["
"\"MIIFHTCCBAWgAwIBAgIHJ9VlLewUkDANBgkqhkiG9w0BAQsFADCBtDELMAkGA1UEBhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMuMS0wKwYDVQQLEyRodHRwOi8vY2VydHMuZ29kYWRkeS5jb20vcmVwb3NpdG9yeS8xMzAxBgNVBAMTKkdvIERhZGR5IFNlY3VyZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjAeFw0xNDA4MTIxNzE0MDdaFw0xNzA4MTgxODI5MjJaMDoxITAfBgNVBAsTGERvbWFpbiBDb250cm9sIFZhbGlkYXRlZDEVMBMGA1UEAwwMKi5hZ3N5bmMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3lDyn/ZvG32Pw5kYbRuVxHsPfe9Xt8s9vOXnt8z7/T+hZZvealNhCxz9VEwTJ7TsZ9CLi5c30FjoEJYFkKddLAdxKo0oOXWc/AWrQvPwht9a+o6dX2fL/9CmXW1hGHXMH0qiLMrFqMSzZeh+GUY6F1woE/eKsAo6LOhP8X77FlEQT2Eu71wu8KC4B3sH/9QTco50KNw14+bRY5j2V2TZelvsXJnvrN4lXtEVYWFkREKeXzMH8DhDyZzh0NcHa7dFBa7rDusyfIHjuP6uAju/Ao6hhdOGjlKePMVtfusWBAI7MWDChLTqiCTvlZnCpkpTTh5m+i7TbE1TwmdbLceq1wIDAQABo4IBqzCCAacwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDgYDVR0PAQH/BAQDAgWgMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwuZ29kYWRkeS5jb20vZ2RpZzJzMS04Ny5jcmwwUwYDVR0gBEwwSjBIBgtghkgBhv1tAQcXATA5MDcGCCsGAQUFBwIBFitodHRwOi8vY2VydGlmaWNhdGVzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkvMHYGCCsGAQUFBwEBBGowaDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZ29kYWRkeS5jb20vMEAGCCsGAQUFBzAChjRodHRwOi8vY2VydGlmaWNhdGVzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkvZ2RpZzIuY3J0MB8GA1UdIwQYMBaAFEDCvSeOzDSDMKIz1/tss/C0LIDOMCMGA1UdEQQcMBqCDCouYWdzeW5jLmNvbYIKYWdzeW5jLmNvbTAdBgNVHQ4EFgQUxqwQ5mJfzESbA5InigohAq4lhIYwDQYJKoZIhvcNAQELBQADggEBADXv4q7iw3yCDuVS+edPcyWQJPWo3X7xx83g2omcsqDIoEMgsRLGidiINttAhSIAlUyd9Nsp5cGsT/2ZJMbjRFhhVhRHf61O+F60ZYuKPUKWlXB1Nkk4f48/6PGc5Tu/MXdXttpuIP4Jlbpc0dtv59wrrFs9Sf1V7NuHS96IhxfnBO3J1s3ipudoUwjNtBxN/7vUFzfRuHl1+/oQxhmKDxBDpk0v1iLJTeMkMgc+wPGO55gLR6+5l9qWuuE+fIHeS+LHMzchkBBYJMtbmf/KZfwMA8AOsnGXQOXzpf7Sg8VIiVdeaB0NY1eWyRBisQkivk6wm+7G2VYKh9OeVdX4XqQ=\""
"]"
"}";
apr_hash_t *keys = apr_hash_make(pool);
apr_jwk_t *jwk = NULL;
TST_ASSERT_ERR("apr_jwk_parse_json",
_jwk_parse(pool, s_key, &jwk, &err) == 0, pool, err);
apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk);
TST_ASSERT_ERR("apr_jws_verify", apr_jws_verify(pool, jwt, keys, &err),
pool, err);
apr_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"
".");
apr_jwt_error_t err;
apr_jwt_t *jwt = NULL;
TST_ASSERT_ERR("apr_jwt_parse", apr_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);
apr_jwt_destroy(jwt);
return 0;
}
static char *test_jwt_get_string(apr_pool_t *pool) {
//apr_jwt_get_string
const char *s =
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9"
".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ"
".dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
apr_jwt_t *jwt = NULL;
apr_jwt_error_t err;
TST_ASSERT_ERR("apr_jwt_parse", apr_jwt_parse(pool, s, &jwt, NULL, &err),
pool, err);
char *dst;
dst = NULL;
TST_ASSERT("apr_jwt_get_string (1a)",
apr_jwt_get_string(pool, jwt->header.value.json, "typ", TRUE, &dst, &err));
TST_ASSERT_STR("apr_jwt_get_string (1b)", dst, "JWT");
dst = NULL;
TST_ASSERT("apr_jwt_get_string (2a)",
apr_jwt_get_string(pool, jwt->header.value.json, "alg", TRUE, &dst, &err));
TST_ASSERT_STR("apr_jwt_get_string (2b)", dst, "HS256");
dst = NULL;
TST_ASSERT("apr_jwt_get_string (3a)",
apr_jwt_get_string(pool, jwt->header.value.json, "dummy", FALSE, &dst, &err));
TST_ASSERT_STR("apr_jwt_get_string (3b)", dst, NULL);
apr_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\"}";
apr_jwt_error_t err;
apr_jwk_t *jwk;
jwk = NULL;
TST_ASSERT_ERR("apr_jwk_parse_json (1)",
_jwk_parse(pool, s, &jwk, &err) == 0, pool, err);
// 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(
"apr_jwk_parse_json (draft-ietf-jose-json-web-key-41#appendix-A.3 #1)",
_jwk_parse(pool, s, &jwk, &err) == 0, pool, err);
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(
"apr_jwk_parse_json (draft-ietf-jose-json-web-key-41#appendix-A.3 #2)",
_jwk_parse(pool, s, &jwk, &err) == 0, pool, err);
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(
"apr_jwk_parse_json (draft-ietf-jose-cookbook-08#section-3.1, EC Public Key)",
_jwk_parse(pool, s, &jwk, &err) == 0, pool, err);
// 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(
"apr_jwk_parse_json (draft-ietf-jose-cookbook-08#section-3.2, EC Private Key)",
_jwk_parse(pool, s, &jwk, &err) == 0, pool, err);
// 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(
"apr_jwk_parse_json (draft-ietf-jose-cookbook-08#section-3.3, RSA Public Key)",
_jwk_parse(pool, s, &jwk, &err) == 0, pool, err);
// 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(
"apr_jwk_parse_json (draft-ietf-jose-cookbook-08#section-3.4, RSA Private Key)",
_jwk_parse(pool, s, &jwk, &err) == 0, pool, err);
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\""
"}";
apr_jwt_error_t err;
apr_hash_t *keys = apr_hash_make(pool);
apr_jwk_t *jwk = NULL;
TST_ASSERT_ERR("apr_jwk_parse_json", _jwk_parse(pool, k, &jwk, &err) == 0,
pool, err);
apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk);
apr_array_header_t *unpacked = NULL;
apr_jwt_header_t header;
memset(&header, 0, sizeof(header));
TST_ASSERT_ERR("apr_jwt_header_parse",
apr_jwt_header_parse(pool, s, &unpacked, &header, &err), pool, err);
char *decrypted = NULL;
TST_ASSERT_ERR("apr_jwe_decrypt_jwt",
apr_jwe_decrypt_jwt(pool, &header, unpacked, keys, &decrypted,
&err), pool, err);
TST_ASSERT_STR("decrypted", decrypted, "Live long and prosper.");
json_decref(header.value.json);
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\""
"}";
apr_jwt_error_t err;
apr_hash_t *keys = apr_hash_make(pool);
apr_jwk_t *jwk = NULL;
TST_ASSERT_ERR("apr_jwk_parse_json", _jwk_parse(pool, k, &jwk, &err) == 0,
pool, err);
apr_hash_set(keys, jwk->kid, APR_HASH_KEY_STRING, jwk);
apr_array_header_t *unpacked = NULL;
apr_jwt_header_t header;
memset(&header, 0, sizeof(header));
TST_ASSERT_ERR("apr_jwt_header_parse",
apr_jwt_header_parse(pool, s, &unpacked, &header, &err), pool, err);
char *decrypted = NULL;
TST_ASSERT_ERR("apr_jwe_decrypt_jwt",
apr_jwe_decrypt_jwt(pool, &header, unpacked, keys, &decrypted,
&err), pool, err);
TST_ASSERT_STR("decrypted", 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.");
json_decref(header.value.json);
return 0;
}
static char *test_plaintext_decrypt_symmetric(apr_pool_t *pool) {
apr_jwt_error_t err;
apr_hash_t *keys = apr_hash_make(pool);
apr_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("apr_jwk_parse_json", _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";
apr_array_header_t *unpacked = NULL;
apr_jwt_header_t header;
memset(&header, 0, sizeof(header));
TST_ASSERT_ERR("apr_jwt_header_parse",
apr_jwt_header_parse(pool, s, &unpacked, &header, &err), pool, err);
char *decrypted = NULL;
TST_ASSERT_ERR("apr_jwe_decrypt_jwt",
apr_jwe_decrypt_jwt(pool, &header, unpacked, keys, &decrypted,
&err), pool, err);
TST_ASSERT_STR("decrypted", decrypted, "Live long and prosper.");
json_decref(header.value.json);
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\""
"}";
apr_jwt_error_t err;
apr_hash_t *keys = apr_hash_make(pool);
apr_jwk_t *jwk = NULL;
apr_jwt_t *jwt = NULL;
TST_ASSERT_ERR("apr_jwk_parse_json (encryption key)",
_jwk_parse(pool, ek, &jwk, &err) == 0, pool, err);
apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk);
TST_ASSERT_ERR("apr_jwt_parse", apr_jwt_parse(pool, s, &jwt, keys, &err),
pool, err);
TST_ASSERT_ERR("apr_jwk_parse_json (signing key)",
_jwk_parse(pool, sk, &jwk, &err) == 0, pool, err);
apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk);
TST_ASSERT_ERR("apr_jws_verify", apr_jws_verify(pool, jwt, keys, &err),
pool, err);
TST_ASSERT_STR("header.alg", jwt->header.alg, "RS256");
TST_ASSERT_STR("payload.iss", jwt->payload.iss, "joe");
TST_ASSERT_LONG("payload.exp", (long )jwt->payload.exp, 1300819380L);
apr_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\""
"}";
apr_jwt_error_t err;
apr_hash_t *keys = apr_hash_make(pool);
apr_jwk_t *jwk = NULL;
TST_ASSERT_ERR("apr_jwk_parse_json", _jwk_parse(pool, k, &jwk, &err) == 0,
pool, err);
apr_hash_set(keys, "dummy", APR_HASH_KEY_STRING, jwk);
apr_array_header_t *unpacked = NULL;
apr_jwt_header_t header;
memset(&header, 0, sizeof(header));
TST_ASSERT_ERR("apr_jwt_header_parse",
apr_jwt_header_parse(pool, s, &unpacked, &header, &err), pool, err);
char *decrypted = NULL;
TST_ASSERT_ERR("apr_jwe_decrypt_jwt",
apr_jwe_decrypt_jwt(pool, &header, unpacked, keys, &decrypted,
&err), pool, err);
TST_ASSERT_STR("decrypted", decrypted, "The true sign of intelligence is not knowledge but imagination.");
TST_ASSERT_STR("header.alg", header.alg, "RSA-OAEP");
json_decref(header.value.json);
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";
apr_jwt_error_t err;
apr_jwt_t *jwt = NULL;
TST_ASSERT_ERR("apr_jwt_parse", apr_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));
apr_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";
apr_jwt_error_t err;
apr_jwt_t *jwt = NULL;
TST_ASSERT_ERR("apr_jwt_parse", apr_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));
apr_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.response_type = "code";
provider.auth_request_params = NULL;
const char *redirect_uri = "https://www.example.com/protected/";
const char *state = "12345";
json_t * proto_state = json_object();
json_object_set_new(proto_state, "nonce", json_string("anonce"));
json_object_set_new(proto_state, "original_url", json_string("https://localhost/protected/index.php"));
json_object_set_new(proto_state, "original_method", json_string("get"));
json_object_set_new(proto_state, "issuer", json_string(provider.issuer));
json_object_set_new(proto_state, "response_type", json_string(provider.response_type));
json_object_set_new(proto_state, "timestamp", json_integer(apr_time_sec(apr_time_now())));
TST_ASSERT("oidc_proto_authorization_request (1)",
oidc_proto_authorization_request(r, &provider, NULL, redirect_uri, state, proto_state, 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");
apr_jwt_t *jwt = NULL;
apr_jwt_error_t err;
TST_ASSERT_ERR("apr_jwt_parse",
apr_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);
apr_jwt_destroy(jwt);
return 0;
}
static char * test_proto_validate_jwt(request_rec *r) {
apr_jwt_t *jwt = NULL;
apr_jwt_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("apr_jwt_parse",
apr_jwt_parse(r->pool, s_jwt, &jwt, NULL, &err), r->pool, err);
TST_ASSERT_ERR("apr_jws_verify",
apr_jws_verify(r->pool, jwt, oidc_util_merge_symmetric_key(r->pool, NULL, s_secret, NULL), &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);
apr_jwt_destroy(jwt);
return 0;
}
static char * all_tests(apr_pool_t *pool, request_rec *r) {
char *message;
TST_RUN(test_jwt_array_has_string, pool);
TST_RUN(test_jwt_url_encode_decode, pool);
TST_RUN(test_jwt_header_to_string, pool);
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
TST_RUN(test_jwt_verify_rsa, 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);
return 0;
}
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 = 80;
request->unparsed_uri = "/bla?foo=bar¶m1=value1";
request->args = "foo=bar¶m1=value1";
apr_uri_parse(request->pool,
"http://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;
cfg->cache->post_config(request->server);
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_digests();
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-1.8.5/test/stub.c 0000644 0001750 0001750 00000010100 12542520310 017300 0 ustar zandbelt zandbelt #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(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 "";
}
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(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 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;
}
mod_auth_openidc-1.8.5/configure 0000775 0001750 0001750 00000334451 12577725535 017163 0 ustar zandbelt zandbelt #! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.69 for mod_auth_openidc 1.8.5.
#
# 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"
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: hzandbelt@pingidentity.com 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='1.8.5'
PACKAGE_STRING='mod_auth_openidc 1.8.5'
PACKAGE_BUGREPORT='hzandbelt@pingidentity.com'
PACKAGE_URL=''
ac_subst_vars='LTLIBOBJS
LIBOBJS
HAVE_LIBHIREDIS
HIREDIS_LIBS
HIREDIS_CFLAGS
PCRE_LIBS
PCRE_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
'
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
PCRE_CFLAGS
PCRE_LIBS
HIREDIS_CFLAGS
HIREDIS_LIBS'
# 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 1.8.5 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 1.8.5:";;
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]
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
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
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 1.8.5
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. ##
## ------------------------ ##
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 1.8.5, 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-1.8.5
# 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 CURL" >&5
$as_echo_n "checking for CURL... " >&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" >&5
$as_echo_n "checking for APR... " >&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
# PCRE
pkg_failed=no
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for PCRE" >&5
$as_echo_n "checking for PCRE... " >&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
# 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 1.8.5, 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 1.8.5
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-1.8.5/configure.ac 0000664 0001750 0001750 00000004550 12577725501 017525 0 ustar zandbelt zandbelt AC_INIT([mod_auth_openidc],[1.8.5],[hzandbelt@pingidentity.com])
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)
# 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)
# Create Makefile from Makefile.in
AC_CONFIG_FILES([Makefile])
AC_OUTPUT
mod_auth_openidc-1.8.5/Makefile.in 0000644 0001750 0001750 00000004425 12532644156 017300 0 ustar zandbelt zandbelt
JWT_SRC = \
src/jose/apr_jwt.c \
src/jose/apr_jwk.c \
src/jose/apr_jws.c \
src/jose/apr_jwe.c
JWT_HDRS = \
src/jose/apr_jose.h
# 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/lock.c \
src/oauth.c \
src/proto.c \
src/crypto.c \
src/config.c \
src/util.c \
src/authz.c \
src/session.c \
src/metadata.c \
$(JWT_SRC)
ifeq (@HAVE_LIBHIREDIS@, 1)
SRC += \
src/cache/redis.c
REDIS_CFLAGS=-DUSE_LIBHIREDIS @HIREDIS_CFLAGS@
REDIS_LIBS=@HIREDIS_LIBS@
endif
HDRS = \
$(JWT_HDRS) \
src/mod_auth_openidc.h \
src/cache/cache.h
# Files to include when making a .tar.gz-file for distribution
DISTFILES=$(SRC) \
$(HDRS) \
test/test.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@ @PCRE_CFLAGS@ $(REDIS_CFLAGS)
LIBS=@OPENSSL_LIBS@ @CURL_LIBS@ @JANSSON_LIBS@ @PCRE_LIBS@ $(REDIS_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/*.c src/mod_auth_openidc.la
@APXS2@ @APXS2_OPTS@ $(CFLAGS) -Wl,"$(LIBS)" -Isrc -Wc,-Wall -Wc,-g -c -o $@ test/*.c $(SRC:.c=.lo) @APR_LIBS@
test-compile: test/test
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 src/jose/*.o test/*.o
rm -f src/*.lo src/cache/*.lo src/jose/*.lo test/*.lo
rm -f src/*.slo src/cache/*.slo src/jose/*.slo test/*.slo
rm -rf src/.libs/ src/cache/.libs/ src/jose/.libs/ test/.libs
rm -rf test/test
.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-1.8.5/autogen.sh 0000744 0001750 0001750 00000000076 12516231751 017223 0 ustar zandbelt zandbelt #!/bin/sh
autoreconf --force --install
rm -rf autom4te.cache/
mod_auth_openidc-1.8.5/INSTALL 0000644 0001750 0001750 00000003277 12532644156 016270 0 ustar zandbelt zandbelt If your looking for binary packages, please see:
https://github.com/pingidentity/mod_auth_openidc/wiki#8-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)
OpenSSL (>=0.9.8) (>=1.0 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.
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-1.8.5/README.md 0000644 0001750 0001750 00000032113 12532645756 016514 0 ustar zandbelt zandbelt mod_auth_openidc
================
**mod_auth_openidc** is an authentication/authorization module for the Apache 2.x
HTTP server that authenticates users against an OpenID Connect Provider. It can also
function as an OAuth 2.0 Resource Server, validating access tokens presented by
OAuth 2.0 clients against an OAuth 2.0 Authorization Server.
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, consuming bearer access
tokens and introspecting/validating them against a token introspection endpoint of an
OAuth 2.0 Authorization Server, authorizing clients based on the introspection 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 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.
This module supports all defined OpenID Connect flows, including *Basic Client Profile*,
*Implicit Client Profile*, *Hybrid Flows* and the *Refresh Flow*. It supports connecting
to multiple OpenID Connect Providers through reading/writing provider metadata files
in a specified metadata directory.
It supports [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) through domain
or account names and [OAuth 2.0 Form Post Response Mode]
(http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html).
It also supports [OpenID Connect Session Management draft 22]
(http://openid.net/specs/openid-connect-session-1_0.html). See the [Wiki]
(https://github.com/pingidentity/mod_auth_openidc/wiki/Session-Management) for information
on how to configure it.
Additionally it can operate as an OAuth 2.0 Resource Server to an OAuth 2.0 Authorization Server,
introspecting/validating bearer Access Tokens conforming to [OAuth Token Introspection]
(https://tools.ietf.org/html/draft-ietf-oauth-introspection-05) or similar. 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/pingidentity/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/pingidentity/mod_auth_openidc/wiki/Authorization).
### Access Control with Google OAuth 2.0
Sample configuration where **mod_auth_openidc** acts as an OAuth 2.0 Resource Server using Google as the
Authorization Server. This allows us to expose protected resources only to (non-browser/in-browser/native) clients
that are able to present a valid access token obtained from Google. **mod_auth_openidc** will validate the
`access_token` against Google's token info endpoint and use the claims returned in the response for
authorization purposes. The following configuration allows access only to a specific client:
```apache
OIDCOAuthIntrospectionEndpoint https://www.googleapis.com/oauth2/v1/tokeninfo
OIDCOAuthIntrospectionTokenParamName access_token
OIDCOAuthRemoteUserClaim user_id
Authtype oauth20
Require claim issued_to:412063239660.apps.googleusercontent.com
```
Note that this is not an OpenID Connect SSO scenario where users are authenticated but rather a "pure" OAuth 2.0
scenario where **mod_auth_openidc** is the OAuth 2.0 Resource Server instead of the RP/client. How the actual
client accessing the protected resources got its access token is not relevant to this Apache Resource Server setup.
###OpenID Connect SSO with multiple OpenID Connect Providers
Sample configuration for multiple OpenID Connect providers, which triggers OpenID
Connect Discovery first to find the user's OP.
`OIDCMetadataDir` points to a directory that contains files that contain per-provider
configuration data. For each provider, there are 3 types of files in the directory:
1. `.provider`
contains (standardized) OpenID Connect Discovery OP JSON metadata where each
name of the file is the url-encoded issuer name of the OP that is described
by the metadata in that file.
2. `.client`
contains statically configured or dynamically registered Dynamic Client Registration
specific JSON metadata (based on the OpenID Connect Client Registration specification)
and the filename is the url-encoded issuer name of the OP that this client is registered
with. Sample client metadata for issuer `https://localhost:9031`, so the client metadata
filename is `localhost%3A9031.client`:
{
"client_id" : "ac_oic_client",
"client_secret" : "abc123DEFghijklmnop4567rstuvwxyzZYXWUT8910SRQPOnmlijhoauthplaygroundapplication"
}
3. `.conf`
contains **mod_auth_openidc** specific custom JSON metadata that can be used to overrule
some of the settings defined in `auth_openidc.conf` on a per-client basis. The filename
is the URL-encoded issuer name of the OP that this client is registered with.
Entries that can be included in the .conf file are:
"ssl_validate_server" overrides OIDCSSLValidateServer (value 0 or 1...)
"scope" overrides OIDCScope
"response_type" overrides OIDCResponseType
"response_mode" overrides OIDCResponseMode
"client_name" overrides OIDCClientName
"client_contact" overrides OIDCClientContact
"idtoken_iat_slack" overrides OIDCIDTokenIatSlack
"session_max_duration" overrides OIDCSessionMaxDuration
"jwks_refresh_interval" overrides OIDCJWKSRefreshInterval
"client_jwks_uri" overrides OIDCClientJwksUri
"id_token_signed_response_alg" overrides OIDCIDTokenSignedResponseAlg
"id_token_encrypted_response_alg" overrides OIDCIDTokenEncryptedResponseAlg
"id_token_encrypted_response_enc" overrides OIDCIDTokenEncryptedResponseEnc
"userinfo_signed_response_alg" overrides OIDCUserInfoSignedResponseAlg
"userinfo_encrypted_response_alg" overrides OIDCUserInfoEncryptedResponseAlg
"userinfo_encrypted_response_enc" overrides OIDCUserInfoEncryptedResponseEnc
"auth_request_params" overrides OIDCAuthRequestParams
"token_endpoint_params" overrides OIDCProviderTokenEndpointParams
"registration_endpoint_json" overrides OIDCProviderRegistrationEndpointJson
"registration_token" an access_token that will be used on client registration calls for the associated OP
Sample client metadata for issuer `https://localhost:9031`, so the **mod_auth_openidc**
configuration filename is `localhost%3A9031.conf`:
{
"ssl_validate_server" : 0,
"scope" : "openid email profile"
}
And the related **mod_auth_openidc** Apache config section:
```apache
OIDCMetadataDir /metadata
OIDCRedirectURI https://www.example.com/example/redirect_uri/
OIDCCryptoPassphrase
AuthType openid-connect
Require valid-user
```
If you do not want to use the internal discovery page (you really shouldn't...), you
can have the user being redirected to an external discovery page by setting
`OIDCDiscoverURL`. That URL will be accessed with 2 parameters, `oidc_callback` and
`target_link_uri` (both URLs). The `target_link_uri` parameter value needs to be returned to the
`oidc_callback` URL (again in the `target_link_uri parameter`) together with an
`iss` parameter that contains the URL-encoded issuer value of the
selected Provider, or a URL-encoded account name for OpenID Connect Discovery
purposes (aka. e-mail style identifier), or a domain name.
Sample callback:
?target_link_uri=&iss=[||][&login_hint=][&auth_request_params=]
This is also the OpenID Connect specified way of triggering 3rd party initiated SSO
to a specific provider when multiple OPs have been configured. In that case the callback
may also contain a "login_hint" parameter with the login identifier the user might use to log in.
An additional **mod_auth_openidc** specific parameter named `auth_request_params` may also be passed
in, see the [Wiki](https://github.com/pingidentity/mod_auth_openidc/wiki#13-how-can-i-add-custom-parameters-to-the-authorization-request)
for its usage.
###OpenID Connect SSO & OAuth 2.0 Access Control with PingFederate
Another example config for using PingFederate as your OpenID Connect OP and/or
OAuth 2.0 Authorization server, based on the OAuth 2.0 PlayGround 3.x default
configuration and doing claims-based authorization. (running on `localhost` and
`https://localhost/example/redirect_uri/` registered as *redirect_uri* for the
client `ac_oic_client`)
```apache
OIDCProviderMetadataURL https://macbook:9031/.well-known/openid-configuration
OIDCSSLValidateServer Off
OIDCClientID ac_oic_client
OIDCClientSecret abc123DEFghijklmnop4567rstuvwxyzZYXWUT8910SRQPOnmlijhoauthplaygroundapplication
OIDCRedirectURI https://localhost/example/redirect_uri/
OIDCCryptoPassphrase
OIDCScope "openid email profile"
OIDCOAuthIntrospectionEndpoint https://macbook:9031/as/token.oauth2
OIDCOAuthIntrospectionEndpointParams grant_type=urn%3Apingidentity.com%3Aoauth2%3Agrant_type%3Avalidate_bearer
OIDCOAuthIntrospectionEndpointAuth client_secret_basic
OIDCOAuthRemoteUserClaim Username
OIDCOAuthSSLValidateServer Off
OIDCOAuthClientID rs_client
OIDCOAuthClientSecret 2Federate
AuthType openid-connect
#Require valid-user
Require claim sub:joe
AuthType oauth20
#Require valid-user
Require claim Username:joe
#Require claim scope~\bprofile\b
```
Support
-------
See the Wiki pages with Frequently Asked Questions at:
https://github.com/pingidentity/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
Disclaimer
----------
*This software is open sourced by Ping Identity but not supported commercially
as such. Any questions/issues should go to the mailing list, the Github issues
tracker or the author [hzandbelt@pingidentity.com](mailto:hzandbelt@pingidentity.com)
directly See also the DISCLAIMER file in this directory.*
mod_auth_openidc-1.8.5/AUTHORS 0000664 0001750 0001750 00000001665 12577725501 016313 0 ustar zandbelt zandbelt 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:
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
mod_auth_openidc-1.8.5/DISCLAIMER 0000644 0001750 0001750 00000002427 12532644156 016572 0 ustar zandbelt zandbelt /***************************************************************************
* Copyright (C) 2014-2015 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-1.8.5/auth_openidc.conf 0000664 0001750 0001750 00000071171 12577725501 020553 0 ustar zandbelt zandbelt ########################################################################################
#
# 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.
#OIDCRedirectURI https://www.example.com/protected/redirect_uri
# (Mandatory)
# Set a password for crypto purposes, used in state and (optionally) by-value session cookies.
#OIDCCryptoPassphrase
# (Optional)
# Define the cookie path for the "state" and "session" cookies.
# When not defined the default is a server-wide "/".
#OIDCCookiePath
# (Optional)
# Specify the domain for which the "state" and "session" cookies will be set.
# This must match the OIDCRedirectURI and the URL on which you host your protected
# application. When not defined the default is the server name.
#OIDCCookieDomain
# (Optional)
# 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
########################################################################################
#
# (Optional)
#
# 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 not set, the entries below 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 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
# (Optional)
# 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
# (Optional)
# Authentication method for the OpenID Connect Provider Token Endpoint.
# One of "client_secret_basic" or "client_secret_post".
# 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.
#OIDCProviderTokenEndpointAuth
# (Optional)
# 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 =[&=]*
# (Optional)
# 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
# (Optional)
# 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
# (Optional)
# 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
# (Optional)
# 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
########################################################################################
#
# (Optional)
#
# 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.
#
########################################################################################
# (Optional)
# 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]
# (Optional)
# 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"]
# (Optional)
# 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"]
# (Optional)
# 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
# (Optional)
# 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
# (Optional)
# 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
# (Optional)
# 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
# (Optional)
# 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: this can be overridden on a per-OP basis in the .conf file using the key: scope
#OIDCScope
########################################################################################
#
# (Optional)
#
# 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.
#
########################################################################################
# (Optional)
# Client identifier used in token introspection calls to the OAuth 2.0 Authorization server.
#OIDCOAuthClientID
# (Optional)
# Client secret used in token introspection calls to the OAuth 2.0 Authorization server.
#OIDCOAuthClientSecret
# (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
# (Optional)
# Define the HTTP method to use for the introspection call. Must be GET or POST.
# When not defined the default is POST.
#OIDCOAuthIntrospectionEndpointMethod [GET|POST]
# (Optional)
# Extra parameters that need to be passed in the POST request to the Introspection Endpoint.
# Parameter names and values need to be provided in URL-encoded form.
# When not defined no extra parameters will be passed.
#OIDCOAuthIntrospectionEndpointParams =[&=]*
# (Optional)
# Authentication method for the OAuth 2.0 Authorization Server introspection endpoint,
# Must be either "client_secret_basic" or "client_secret_post; when not defined "client_secret_basic" is used.
#OIDCOAuthIntrospectionEndpointAuth [client_secret_basic|client_secret_post]
# (Optional)
# Name of the parameter whose value carries the access token value in an validation request to the token introspection endpoint.
# When not defined the default "token" is used.
#OIDCOAuthIntrospectionTokenParamName
# (Optional)
# Defines the name of the claim that contains the token expiry timestamp, whether it is absolute (seconds since
# 1970), relative (seconds from now after which the token will expire), and whether it is optional.
# If the claim is optional and not found in the response, the introspection result will not be cached.
# (which means that the overall performance may suffer)
#
# Only applies when the "active" claim is not found in the introspection response, which is interpreted as
# an introspection method that does not conform to draft-ietf-oauth-introspection, but is custom.
#
# When not defined the default "expires_in" is used, the expiry is "relative" and mandatory, matching
# Google and PingFederate's introspection behavior.
#OIDCOAuthTokenExpiryClaim [absolute|relative] [mandatory|optional]
# (Optional)
# Require a valid SSL server certificate when communicating with the Authorization Server
# on the token introspection endpoint. When not defined, the default value is "On".
#OIDCOAuthSSLValidateServer [On|Off]
# (Optional)
# The symmetric shared key(s) that can be used for local JWT access token validation.
# NB: this is one or more plain secret(s), so NOT hex or base64 encoded.
# When not defined, no access token validation with shared keys will be performed.
#OIDCOAuthVerifySharedKeys [+]
# (Optional)
# The fully qualified names of the files that contain the X.509 certificates with the RSA public
# keys that can be used for local JWT access token verification.
# When not defined, no access token validation with statically configured certificates will be performed.
#OIDCOAuthVerifyCertFiles [+]
# (Optional)
# The JWKs URL on which the Authorization publishes the keys used to sign its JWT access tokens.
# When not defined local validation of JWTs can still be done using statically configured keys,
# by setting OIDCOAuthVerifyCertFiles and/or OIDCOAuthVerifySharedKeys.
#OIDCOAuthVerifyJwksUri
# (Optional)
# The claim that is used when setting the REMOTE_USER variable on OAuth 2.0 protected paths.
# When not defined the default "sub" is used.
#
# An optional regular expression can be added as a 2nd parameter that will be applied to the
# claim value from the 1st parameter and the first match returned from that expression will
# be set as the REMOTE_USER. E.g. to strip a domain from an e-mail style address you'd use ^(.*)@
#OIDCOAuthRemoteUserClaim []
########################################################################################
#
# (Optional)
#
# Cache Settings
#
########################################################################################
# (Optional)
# Cache type, used for temporary storage that is shared across Apache processes/servers for:
# a) session state
# b) nonce values to prevent replay attacks
# c) validated OAuth 2.0 tokens
# d) JWK sets that have been retrieved from jwk_uri's
# e) resolved OP metadata when using OIDCProviderMetadataUrl
# f) JWT ID claims (jti) when using OP-init-SSO
# must be one of \"shm\", \"memcache\", \"file\" or, if Redis support is compiled in, \"redis\"
# When not defined, "shm" (shared memory) is used.
#OIDCCacheType [shm|memcache|file[|redis]]
# (Optional)
# When using OIDCCacheType "shm":
# Specifies the maximum number of name/value pair entries that can be cached.
# When caching a large number of entries the cache size limit may be reached and the
# least recently used entry will be overwritten. If this happens within 1 hour,
# errors will be displayed in the error.log and the OIDCCacheShmMax value may be increased.
# When not specified, a default of 500 entries is used.
# OIDCCacheShmMax
# (Optional)
# When using OIDCCacheType "shm":
# Specifies the maximum size for a single cache entry in bytes with a minimum of 8464 bytes.
# When caching large values such as numbers of attributes in a session or large metadata documents the
# entry size limit may be overrun, in which case errors will be displayed in the error.log
# and the OIDCCacheShmEntrySizeMax value has to be increased.
# When not specified, a default entry size of 16913 bytes (16384 value + 512 key + 17 overhead) is used.
# OIDCCacheShmEntrySizeMax
# (Optional)
# When using OIDCCacheType "file":
# Directory that holds cache files; must be writable for the Apache process/user.
# When not specified a system defined temporary directory (/tmp) will be used.
#OIDCCacheDir /var/cache/apache2/mod_auth_openidc/cache
# (Optional)
# When using OIDCCacheType "file":
# Cache file clean interval in seconds (only triggered on writes).
# When not specified a default of 60 seconds is used.
# OIDCCacheFileCleanInterval
# (Optional)
# Required when using OIDCCacheType "memcache":
# Specifies the memcache servers used for caching as a space separated list of [:] tuples.
#OIDCMemCacheServers "([:])+"
# (Optional)
# Required if Redis support is compiled in and when using OIDCCacheType "redis":
# Specifies the Redis server used for caching as a [:] tuple.
#OIDCRedisCacheServer [:]
# (Optional)
# Password to be used if the Redis server requires authentication: http://redis.io/commands/auth
# When not specified, no authentication is performed.
#OIDCRedisCachePassword
########################################################################################
#
# (Optional)
#
# Advanced Settings
#
########################################################################################
# (Optional)
# Interval in seconds after which the session will be invalidated when no interaction has occurred.
# When not defined, the default is 300 seconds.
#OIDCSessionInactivityTimeout
# (Optional)
# Defines an external OP Discovery page. That page will be called with:
# ?target_link_uri=&oidc_callback=
#
# An Issuer selection can be passed back to the callback URL as in:
# ?target_link_uri=&iss=[${issuer}|${domain}|${e-mail-style-account-name}][&login_hint=][&auth_request_params=]
# where the parameter contains the URL-encoded issuer value of
# the selected Provider, or a URL-encoded account name for OpenID
# Connect Discovery purposes (aka. e-mail style identifier), or a domain name.
#
# When not defined the bare-bones internal OP Discovery page is used.
#OIDCDiscoverURL
# (Optional)
# Defines a default URL to be used in case of 3rd-party or OP initiated
# SSO when no explicit target_link_uri has been provided. The user is also
# sent to this URL is in case an invalid authorization response was received.
# When not defined, 3rd-party SSO must be done with a specified \"target_link_uri\" parameter.
#OIDCDefaultURL
# (Optional)
# Defines a default URL where the user is sent to after logout, which may be overridden explicitly during logout.
# When not defined and no URL was passed explicitly, a default internal page will be shown.
#OIDCDefaultLoggedOutURL
# (Optional)
# 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
# (Optional)
# The fully qualified names of the files that contain the PEM-formatted X.509 certificates
# that contain the RSA public keys to be used for JWT (OP state/id_token) encryption by the OP.
# These keys must correspond to the private keys defined in OIDCPrivateKeyFiles.
# When not defined no encryption will be requested.
#OIDCPublicKeyFiles [+]
# (Optional)
# The fully qualified names of the files that contain the PEM-formatted RSA private
# keys that can be used to decrypt content sent to us by the OP.
# These keys must correspond to the public keys defined in OIDCPublicKeyFiles.
# When not defined no decryption will be possible.
#OIDCPrivateKeyFiles [+]
# (Optional)
# 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
# (Optional)
# The algorithm that the OP should use to sign the id_token (used only in dynamic client registration)
# 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]
# (Optional)
# 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)
# 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]
# (Optional)
# The algorithm that the OP should use to encrypt to the id_token with the
# Content Encryption Key (used only in dynamic client registration)
# If OIDCIDTokenEncryptedResponseAlg is specified, the default for this value is A128CBC-HS256.
# When OIDCIDTokenEncryptedResponseEnc is included, OIDCIDTokenEncryptedResponseAlg MUST also be provided.
# (????GCM algorithms 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|A128GCM|A192GCM|A256GCM]
# (Optional)
# The algorithm that the OP should use to sign the UserInfo response
# (used only in dynamic client registration)
# 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]
# (Optional)
# 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)
# 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]
# (Optional)
# 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)
# If OIDCUserInfoEncryptedResponseAlg is specified, the default for this value is A128CBC-HS256.
# When OIDCUserInfoEncryptedResponseEnc is included, OIDCUserInfoEncryptedResponseAlg MUST also be provided.
# (????GCM algorithms 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|A128GCM|A192GCM|A256GCM]
# (Optional)
# 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
# (Optional)
# Acceptable offset (before and after) for checking the \"iat\" (= issued at) timestamp in the id_token.
# When not defined the default is 600 seconds.
# NB: this can be overridden on a per-OP basis in the .conf file using the key: idtoken_iat_slack
#OIDCIDTokenIatSlack
# (Optional)
# Maximum duration of the application session
# When not defined the default is 8 hours (3600 * 8 seconds).
# When set to 0, the session duration will be set equal to the expiry time of the ID token.
# NB: this can be overridden on a per-OP basis in the .conf file using the key: session_max_duration
#OIDCSessionMaxDuration
# (Optional)
# Define the cookie name for the session cookie.
# When not defined the default is "mod_auth_openidc_session".
#OIDCCookie
# (Optional)
# Defines whether the HttpOnly flag will be set on cookies.
# When not defined the default is On.
#OIDCCookieHTTPOnly [On|Off]
# (Optional)
# The prefix to use when setting claims (openid-connect or oauth20) in the HTTP headers/environment variables.
# When not defined, the default "OIDC_CLAIM_" is used.
#OIDCClaimPrefix
# (Optional)
# The delimiter to use when setting multi-valued claims (openid-connect or oauth20) in the HTTP headers/environment variables.
# When not defined the default "," is used.
#OIDCClaimDelimiter
# (Optional)
# The claim that is used when setting the REMOTE_USER variable on OpenID Connect protected paths.
# If the claim name is postfixed with a \"@\", the claim value will be post-fixed with the
# \"iss\" value value (with leading "https://" stripped) to make this value unique across different OPs.
# When not defined the default "sub@" is used.
#
# An optional regular expression can be added as a 2nd parameter that will be applied to the
# resulting value from the 1st parameter and the first match returned from that expression will
# be set as the REMOTE_USER. E.g. to strip a domain from an e-mail style address you'd use ^(.*)@
#OIDCRemoteUserClaim [@] []
# (Optional)
# Define the way(s) in which the id_token contents are passed to the application. Must be one or several of:
# "claims" : the claims in the id_token are passed in individual headers/environment variables
# "payload" : the payload of the id_token is passed as a JSON object in the "OIDC_id_token_payload" header/environment variable
# "serialized" : the complete id_token is passed in compact serialized format in the "OIDC_id_token" header/environment variable
# When not defined the default "claims" is used.
#OIDCPassIDTokenAs [claims|payload|serialized]+
# (Optional)
# Define the way in which the claims are passed to the application environment:
# "none": no claims are passed
# "environment": claims are passed as environment variables
# "headers": claims are passed in headers (also useful in reverse proxy scenario's)
# "both": claims are passed as both headers as well as environment variables (default)
# When not defined the default is "both"
#OIDCPassClaimsAs [none|headers|environment|both]
# (Optional)
# Specify the HTTP header variable name to set with the name of the authenticated user,
# i.e. copy what is set in REMOTE_USER and configured in OIDCRemoteUserClaim.
# When not defined no such header is added.
# This setting can be configured for both the "openid-connect" and "oauth20" AuthType on
# a server-wide or directory level.
#OIDCAuthNHeader
# (Optional)
# Timeout in seconds for long duration HTTP calls. This is used for most requests to remote endpoints/servers.
# When not defined the default of 60 seconds is used.
#OIDCHTTPTimeoutLong
# (Optional)
# Timeout in seconds for short duration HTTP calls; used for Client Registration and OP Discovery requests.
# When not defined the default of 5 seconds is used.
#OIDCHTTPTimeoutShort
# (Optional)
# Time to live in seconds for state parameter i.e. the interval in which the authorization request
# and the corresponding response need to be processed. When not defined the default of 300 seconds is used.
#OIDCStateTimeout
# (Optional)
# OpenID Connect session storage type.
# "server-cache" server-side caching storage.
# "client-cookie" uses browser-side sessions stored in a cookie.
# Note that due to cookie size limitations on most web browsers (usually 4096 bytes), "client-cookie" will
# not work if the resulting session data is too long, which depends on the scope of information you request.
# When not defined the default "server-cache" is used.
#OIDCSessionType [server-cache|client-cookie]
# (Optional)
# Scrub user name and claim headers (as configured above) from the user's request.
# The default is "On"; use "Off" only for testing and debugging because it renders your system insecure.
#OIDCScrubRequestHeaders [On|Off]
# (Optional)
# Specify an outgoing proxy for your network.
# When not defined no outgoing proxy is used.
#OIDCOutgoingProxy [:]
# (Optional)
# Defines the action to be taken when an unauthenticated request is made.
# "auth" means that the user is redirected to the OpenID Connect Provider or Discovery page.
# "401" means that HTTP 401 Unauthorized is returned.
# "pass" means that an unauthenticated request will pass but claims will still be passed when a user happens to be authenticated already
# Useful in Location/Directory/Proxy path contexts that serve AJAX/Javascript calls and for "anonymous access"
# When not defined the default "auth" is used.
#OIDCUnAuthAction [auth|pass|401]
# (Optional)
# Specify the names of cookies to pickup from the browser and send along on backchannel
# calls to the OP and AS endpoints. This can be used for load-balancing purposes.
# When not defined, no such cookies are sent.
#OIDCPassCookies []+
mod_auth_openidc-1.8.5/LICENSE.txt 0000644 0001750 0001750 00000026136 12516231751 017054 0 ustar zandbelt zandbelt
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed 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.
mod_auth_openidc-1.8.5/ChangeLog 0000664 0001750 0001750 00000037670 12577725501 017022 0 ustar zandbelt zandbelt 9/21/2015
- support (non-sid-based) HTTP logout: http://openid.net/specs/openid-connect-logout-1_0.html
- release 1.8.5
9/16/2015
- improve logging on provider/client/conf metadata parsing failures; closes #94
- bump to 1.8.5rc7
9/9/2015
- fix parsing of OIDCOAuthTokenExpiryClaim, thanks to @bester #90
- bump to 1.8.5rc6
9/4/2015
- add CSRF protection to external Discovery as well
- allow browser back after authorization response, see #89
- handle invalid (expired) authorization response state by sending the user to OIDCDefaultURL, see #86
- bump to 1.8.5rc5
8/26/2015
- add CSRF protection to Discovery
- bump to 1.8.5rc4
8/19/2015
- support encrypted JWTs using A192KW for key encryption and A192CBC-HS384 for content encryption
- bump to 1.8.5rc3
8/15/2015
- support encrypted JWTs using RSA-OAEP for key encryption and A128GCM,A192GCM,A256GCM for content encryption
- bump to 1.8.5rc2
8/4/2015
- support for OIDCUnAuthAction: how to act on unauthenticated requests (OIDCReturn401 is deprecated)
- bump to 1.8.5rc1
7/15/2015
- add authentication option for Redis server with OIDCRedisCachePassword
- bump to 1.8.5rc0
7/3/2015
- allow for compilation on Windows using VS 2013
- bump to 1.8.4
6/30/2015
- improve memcache logging: don't report cache misses as an error, thanks to @scottdear
- work around JSON timestamp print modifier issue (%lld) on some platforms, thanks to @ralphvanetten
- bump to 1.8.4rc3
6/24/2015
- support passing claims as environment variables (OIDCPassClaimsAs)
- bump to 1.8.4rc1
6/22/2015
- correct debug printout in oidc_util_read_form_encoded_params
6/20/2015
- avoid double free of JWT after parsing errors have been encountered
- bump to 1.8.4rc0
6/19/2015
- make public keys for encrypted access tokens available in OAuth-only configurations; fixes #74
- remove exceptions for accounts.google.com since Google is OpenID Connect compliant now
- release 1.8.3
6/15/2015
- add a complete JWT sample that includes validation of "exp" and "iat" to the test suite
6/10/2015
- allow JSON string values for the "active" claim in access token validation responses
- bump to 1.8.3rc4
6/7/2015
- improve error logging on non-supported JWT signing/encryption algorithms
- bump to 1.8.3rc3
5/31/2015
- merge id_token ("iss"!) and user info claims for authz processing
- bump to 1.8.3rc2
5/29/2015
- fix hash comparison when padded, thanks to @steverc as mentioned in #65
- fix post-logout URL being set to SSO URL
- add post-logout URL validation, thanks to @davidbernick
- bump to 1.8.3rc1
5/18/2015
- fix OpenSSL version detection for Elliptic Curve support in apr_jwt_signature_to_jwk_type: include opensslv.h
- fix hash length calculation for Elliptic Curve algorithms
- release 1.8.2
5/5/2015
- release 1.8.1
4/21/2015
- allow setting OIDCDiscoverURL inside of Directory and Location directives as well
- bump to 1.8.1rc5
4/20/2015
- allow setting OIDCCookie outside of Directory and Location directives as well
- bump to 1.8.1rc4
4/17/2015
- add support for applying regular expressions to OIDCRemoteUserClaim and OIDCOAuthRemoteUserClaim
- bump to 1.8.1rc3
4/12/2015
- make token expiry parsing of introspection result configurable (OIDCOAuthTokenExpiryClaim)
- increase SHM cache key size from 255 to 512 (allows for JWT access tokens cache keys for introspection result)
- bump to 1.8.1rc2
4/1/2015
- avoid timing attacks on symmetric key signature/hash comparisons as pointed out by @timmclean
- bump to 1.8.1rc1
3/19/2015
- merge #57: fix build with OpenSSL <1.0 re. apr_jws_signature_is_ec (thanks to @szakharchenko)
2/26/2015
- release 1.8.0
2/23/2015
- avoid including line feeds in header values (thanks to @forkbomber and @ekanthi)
- bump to 1.8.0rc5
2/16/2015
- fix free() crash on simple-valued error printouts
- fix returning keys without a "kid"
- fix searching for keys with a "x5t" thumbprint
- refactor response type handling; more strict matching of requested response type
- make compiled in Redis support optional
- fix oauth.introspection_endpoint_method in initialization
- bump to 1.8.0rc4
2/15/2015
- add support for configurable introspection HTTP method (OIDCOAuthIntrospectionEndpointMethod)
- add preliminary support for GET-style logout
- bump to 1.8.0rc2
2/12/2015
- add support for configuration of maximum session duration
- bump to 1.8.0rc1
2/9/2015
- check JWT signature against all configured keys (jwks_uri) if no "kid" is provided, not just the first one
- revise JOSE JWS handling part 2
- complete support for local JWT access token validation
- bump to 1.8.0rc0
2/5/2015
- fix symmetric key decryption of JWTs (A128CBC-HS256/A256CBC-HS512)
- sha256 client secrets before using them as symmetric keys for decryption
- extended decryption test coverage; avoid double printouts on error
- refactor JWT header parsing
- simplify JWK URI refresh handling
- release 1.7.3
2/4/2015
- revise JOSE JWK handling part 1
- change change target_uri parameter name to target_link_uri following draft-bradley-oauth-jwt-encoded-state-03
- extended tests with stubs
- refactor JWT validation (iss, exp, iat)
- fix memory leak with RSA key conversion in apr_jwk.c - apr_jwk_rsa_bio_to_key
- bump to 1.7.3rc4
1/25/2015
- Allow {... "error": null ...} in JSON responses. (@fatlotus)
1/22/2015
- fix configuration validation check where no config would be checked if OIDCProviderIssuer is set
but both OIDRedirectURI and OIDCCryptoPassphrase are not set
- add preliminary support for local JWT access token validation
- bump to 1.7.3rc1
- sanitize set cookie syntax (get rid of extraneous ";")
1/21/2015
- add support for OIDCOAuthIntrospectionTokenParamName (incl. Google OAuth 2.0 access token validation)
- add a sample OAuth 2.0 config for Google access tokens to README.md
- release 1.7.2
- add APXS2_OPTS to configure.ac to accommodate RPM builds
- bump to 1.7.3rc0
- fix JWT timestamp (iat/exp) initialization issue
- fix README.md on Google's scope required for returning the "hd" claim
1/14/2015
- add Apache function stubs to enable extending the scope of tests
- add tests for oidc_proto_validate_access_token and oidc_proto_validate_code
- bump to 1.7.2rc3
1/12/2015
- improve accuracy of expired/invalid session handling
1/11/2015
- add error type and return values to JOSE functions
- fix return result on error in function that decrypts CEK
- bump to 1.7.2rc2
1/1/2015
- update copyright to 2015
- use json_int_t (seconds) for "exp" and "iat" fields, instead of apr_time_t (microseconds)
- correct expiry debug printout
- bump to 1.7.2rc1
12/15/2014
- fix Redis reconnect behavior: avoid keep reconnecting after first failure
- bump to 1.7.2rc0
12/12/2014
- support passing cookies specified in OIDCPassCookies from browser on to OP/AS calls (for loadbalancing purposes)
- release 1.7.1
- document OIDCPassCookies in auth_openidc.conf
12/10/2014
- reconnect to the Redis server after I/O failure as raised in #43
- bump to 1.7.1rc4
12/8/2014
- return http 500 when detecting requests that will loop on return
- bump to 1.7.1rc3
12/3/2014
- require the expiring access_token on the refresh hook for XSRF protection
- pass error codes back to the caller when the refresh hook fails
- bump to 1.7.1rc2
12/2/2014
- improve handling of non-string (=non-compliant) error responses
11/26/2014
- make shared memory cache entry max size configurable through OIDCCacheShmEntrySizeMax
- add OIDCReturn401 configuration primitive
- bump to 1.7.1rc1
11/11/2014
- allow OIDCRedirectURI's with an empty path and fix crash; thanks to @CedricCabessa
11/9/2014
- support for adding configurable JSON parameters to Dynamic Client Registration requests
11/5/2014
- release 1.7.0
10/30/2014
- correct expires_in conversion
- first stab at HTML templating and make all html HTML 4.01 Strict compliant
- bump to 1.7.0rc4
10/29/2014
- document refresh flow
10/28/2014
- scrub all OIDC_ headers
- add support for the refresh_token flow + hook
- pass the expires_in as an absolute timestamp in OIDC_access_token_expires
- use a global mutex for the Redis cache
- bump to 1.7.0rc3
10/27/2014
- generalize support for OAuth 2.0 token introspection and conform to:
https://tools.ietf.org/html/draft-ietf-oauth-introspection-00
10/26/2014
- support regular expressions in Require statements
10/24/2014
- add support for Redis cache backend; there's a dependency on hiredis headers/library now
10/21/2014
- refactor nonce generation and remove base64url padding from value
10/13/2014
- add libssl-dev to Debian control build dependencies
- release 1.6.0
10/6/2014
- apply html encoding to error display
- bump version number to 1.6.0rc4
10/2/2014
- avoid crash when downloading metadata from OIDCProviderMetadataURL fails
- set OIDCProviderMetadataURL retrieval interval to 24 hours
- return error on configurations mixing OIDCProviderMetadataURL and OIDCMetadataDir
- bump version number to 1.6.0rc3
10/1/2014
- support provider configuration from a metadata URL (OIDCProviderMetadataURL)
- bump version number to 1.6.0rc2
9/30/2014
- be less strict about issuer validation in metadata
- refactor metadata.c
- improve logging about userinfo endpoint
9/29/2014
- refactor cache so it is partitioned in to sections (i.e. avoid future key collisions)
9/25/2014
- add support for "x5c" JWK representation
- return JWKS on jwks_uri with content-type "application/json"
9/17/2014
- remove support for the X-Frame-Options as it is not needed in 302s
- create and use log macros that printout function name
9/16/2014
- support for passing runtime determined authentication request parameters in the discovery response
- include name/version in logs and bump to 1.6.0rc1
- don't use the X-Frame-Options by default
9/13/2014
- add support for the X-Frame-Options header as recommended by the spec
9/12/2014
- set expiry on state cookies; don't clear session cookie after cache miss or corruption
- fix JSON array memory leak in oauth.c
9/10/2014
- merge #34 (g10f), fix session management Javascript bug
9/3/2014
- improve error handling on hitting the redirect URI directly
- fix set_slot functions for algorithm/url used as default for dynamic registration
- rewording of auth_openidc.conf docs on JWK settings
9/1/2014
- add session management based on http://openid.net/specs/openid-connect-session-1_0.html (draft 21)
8/29/2014
- add configuration option to POST extra parameters to the token endpoint
8/26/2014
- correct cookie_path comparison
- release 1.5.5
8/20/2014
- correctly error out on flows where no id_token was provided ("token")
8/19/2014
- fix debug printout on open redirect prevention
- cleanup in-memory crypto context on shutdown
- use default of "/" for OIDCCookiePath to simplify quickstart/simple deployments
- disable OIDCMetadataDir in sample/default config file
- clear session cookie after cache miss or corruption
8/18/2014
- add HttpOnly flag to cookies by default; can be disabled by config
8/14/2014
- support for passing the id_token in multiple formats (claims|payload|serialized)
- release 1.5.4
8/13/2014
- pass the access_token in OIDC_access_token header to the application
8/9/2014
- merge #21 (Latinovic) to close #18 (big endian JWE issue)
- merge #20 (wadahiro), support for "none" JWT signing algorithm
8/1/2014
- fix cache initialization/destroy leak
- release 1.5.3
7/26/2014
- refactor http code; cleanup JSON encoding in client registration
- refactor padding handling in base64url encoding/decoding
7/20/2014
- check for open redirect on passed target_link_uri
- prevent JWE timing attacks on CEK; add JWE test
- include client_id and scope values in resolved access_token
7/1/2014
- pass JSON objects in app HTTP headers as plain JSON
- correct printout in id_token hash comparisons
- add more tests
- release 1.5.2
6/12/2014
- support third-party-initiated login as defined in the spec
- release 1.5.1
- fix PF OAuth 2.0 RS functionality after upgrading to jansson
6/6/2014
- more changes for Debian packaging (1.5-3)
6/5/2014
- do not set Secure cookies for plain HTTP
- add warning/errors when configured hosts/domains do not match
- release 1.5
- changes for Debian packaging
6/4/2014
- fix passing integer claims on non-Mac OS X systems
- fix claims-based authorization with integer values (@martinsrom)
- fix getting the id_token from request state and error logging
- add AUTHORS file with credits
- migrate README to Markdown README.md
6/3/2014
- change JSON parser from https://github.com/moriyoshi/apr-json to http://www.digip.org/jansson/
6/2/2014
- handle X-Forwarded-Proto/X-Forwarded-Port when running behind a proxy/load-balancer
- release version 1.4
6/1/2014
- compile with OpenSSL <1.0 and but then disable Elliptic Curve verification
- fix jwks_uri setting in nested vhosts
- use OpenSSL_add_all_digests in initialization and EVP_cleanup on shutdown
5/31/2014
- README additions/improvements
5/29/2014
- correct big endian detection
- allow for key identification in JWKs based on thumbprint (x5t)
5/24/2014
- add cache destroy function and destroy shm cache resources on shutdown
5/23/2014
- doc corrections to auth_openidc.conf
5/22/2014
- add implementation of OP-initiated-SSO based on:
http://tools.ietf.org/html/draft-bradley-oauth-jwt-encoded-state-01
- fix nonce caching for replay prevention
5/21/2014
- correct README on enabling Google+ APIs before applying the sample Google configs
- fix AuthNHeader setting and allow server-wide config too
- avoid segfault on corrupted/non- JSON/JWT input
5/20/2014
- fix URL assembly when running on non-standard port
- release 1.3
5/17/2014
- support outgoing proxy using OIDCOutgoingProxy
- correct sample configs in documentation for missing OIDCCookiePath
- support OIDCCookiePath in server-wide config as well
5/13/2014
- support configurable (custom) query parameters in the authorization request
5/12/2014
- support encrypted JWTs using A128KW and A256KW for the Content Encryption Key
- support A256CBC-HS512 encrypted JWTs
- support custom client JWKs URI
5/8/2014
- support encrypted JWTs using RSA1_5 and A128CBC-HS256
5/2/2014
- do not use ap_get_remote_host for browser fingerprinting
5/1/2014
- split out custom client config into separate .conf file
- allow to override client_contact, client_name and registration_token in .conf file
- remove OIDCRegistrationToken command for the static OP config
4/29/2014
- support JWT verification of ES256, ES384 and ES512 algorithms
4/28/2014
- support configurable response_mode (fragment, query or form_post)
- use nonce in all flows except for OP Google and flows "code" or "code token"
4/26/2014
- make client secret optional (support self-issued OP)
4/25/2014
- support Hybrid flows
4/24/2014
- fix using Bearer token Authorization header on JSON POST calls
- support using a Bearer token on client registration calls
4/22/2014
- match request and response type
- check at_hash value on "token id_token" implicit flow
- use shared memory caching by default
- release 1.2
4/19/2014
- store response_type in state and make state a JSON object
4/18/2014
- support RSASSA-PSS token signing algorithms (PS256,PS384,PS512)
4/17/2014
- improve session inactivity timeout handling
4/16/2014
- set REMOTE_USER and HTTP headers on OAuth 2.0 protected paths
4/15/2014
- add session inactivity timeout
- register all supported response_types during client registration and try
to pick the one that matches the configured default
- use long timeouts on JWK retrieval calls
- allow for non-null but empty query parameters on implicit authorization response
- simplify azp/aud and nonce handling
- change session_type naming (to "server-cache"/"client-cookie")
4/14/2014
- factor out JOSE related code
4/3/2014
- add configurable claim name for the REMOTE_USER variable, optionally postfixed with the url-encoded
issuer value; the default for the remote username is "sub@" now, makeing it unique across OPs
- some refactoring of id_token validation functions
- add INSTALL, move auth_openidc.conf to main directory
- release 1.1
3/28/2014
- fix Require claim name mismatch for Apache 2.4
- fix hmac method/printout naming artifacts from earlier
auto-search-and-replace
- release v1.0.1
3/27/2014
- initial import named mod_auth_openidc
- updated README
- fix debian/changelog